-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Add PlaylistQuery plugin #2380
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# -*- coding: utf-8 -*- | ||
# This file is part of beets. | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining | ||
# a copy of this software and associated documentation files (the | ||
# "Software"), to deal in the Software without restriction, including | ||
# without limitation the rights to use, copy, modify, merge, publish, | ||
# distribute, sublicense, and/or sell copies of the Software, and to | ||
# permit persons to whom the Software is furnished to do so, subject to | ||
# the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be | ||
# included in all copies or substantial portions of the Software. | ||
|
||
import os | ||
from beets.plugins import BeetsPlugin | ||
from beets.dbcore import FieldQuery, types | ||
from beets.util.confit import NotFoundError | ||
|
||
class PlaylistQuery(FieldQuery): | ||
"""Matches files listed by a playlist file. | ||
""" | ||
relative_path = None | ||
playlist_dir = None | ||
|
||
def __init__(self, field, pattern, fast=True): | ||
super(PlaylistQuery, self).__init__(field, pattern, fast) | ||
|
||
playlist_file = pattern + '.m3u' | ||
playlist_path = os.path.join(self.playlist_dir, playlist_file) | ||
|
||
self.paths = [] | ||
with open(playlist_path, 'r') as f: | ||
for line in f: | ||
if line[0] == '#': | ||
# ignore comments, and extm3u extension | ||
continue | ||
self.paths.append(os.path.normpath( | ||
os.path.join(self.relative_path, line.decode('utf-8').rstrip()) | ||
)) | ||
|
||
def match(self, item): | ||
return item.path.decode('utf-8') in self.paths | ||
|
||
|
||
class PlaylistType(types.String): | ||
"""Custom type for playlist query. | ||
""" | ||
query = PlaylistQuery | ||
|
||
|
||
class PlaylistQueryPlugin(BeetsPlugin): | ||
item_types = {'playlist': PlaylistType()} | ||
|
||
def __init__(self): | ||
super(PlaylistQueryPlugin, self).__init__() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'll likely want to set defaults for the plugin's configuration here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the documentation could benefit from a small section on how to specify config defaults for plugins, it is currently missing so I had to look at other plugins for examples. |
||
self.register_listener('library_opened', self.library_opened) | ||
|
||
PlaylistQuery.playlist_dir = self.config['playlist_dir'].as_filename() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using global state to keep track of this could be a little problematic for testing—the right thing to do here might be to look up the configuration option directly in the query class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How will this work with access needed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, yes, that is a problem. We haven't had a case where a query needed a reference to the database before—maybe we need to add that capability? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the moment a Query object is constructed with the field and pattern, which makes it very clean. I think it is unwise to expand that in the default case, since most of the time it will go unused. However, I can see potential for the Builder pattern here - have a Plugin object instantiate Query objects that it controls. So currently, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure; good idea. Another relatively simple way would be to use a closure—that is, the plugin would just set I suppose one way to judge whether we've done it "right" is to ask whether the plugin would break if beets were ever to support multiple libraries for some reason—if there weren't a single, global There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't think of a good way to avoid using global state somewhere, without rewriting how the entire mechanism for registering queries works. Closures wouldn't work easily, since the PlaylistQuery class is defined inside PlaylistType, of which an instance belongs to PlaylistQueryPlugin, so the PlaylistQuery class inside the (now global) PlaylistType instance needs to be changed on I don't think these two configuration variables as global state is necessarily a problem, though. If you consider PlaylistQueryPlugin and PlaylistQuery to be separate units of code, then each can be unit tested independently, where the global state isn't such a big deal. |
||
|
||
def library_opened(self, lib): | ||
try: | ||
relative_to = self.config['relative_to'].as_choice(['base', 'playlist']) | ||
except NotFoundError: | ||
relative_to = 'base' | ||
|
||
if relative_to == 'playlist': | ||
PlaylistQuery.relative_path = PlaylistQuery.playlist_dir | ||
else: | ||
PlaylistQuery.relative_path = lib.directory | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a difference between setting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aha, that makes sense! Thanks for clarifying. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want
fast=False
here unless you implement aclause
method (i.e., a pure-SQL way of executing the query).