def test_authority(self): cases = [ ('', None), ('//', ''), ('//', b''), ('//example.com', 'example.com'), ('//example.com', b'example.com'), ('//example.com', 'example.com:'), ('//example.com', b'example.com:'), ('//[email protected]', '*****@*****.**'), ('//[email protected]', b'*****@*****.**'), ('//example.com:42', 'example.com:42'), ('//example.com:42', b'example.com:42'), ('//[email protected]:42', '[email protected]:42'), ('//[email protected]:42', b'[email protected]:42'), ('//[email protected]:42', '[email protected]:42'), ('//[email protected]:42', b'[email protected]:42'), ('//user@[::1]:42', 'user@[::1]:42'), ('//user@[::1]:42', b'user@[::1]:42'), ('//user:[email protected]', 'user:[email protected]'), ('//user:[email protected]', b'user:[email protected]'), ] for uri, authority in cases: self.check(uri, authority=authority) # invalid authority type for authority in (True, 42, 3.14, ipaddress.IPv6Address('::1')): with self.assertRaises(TypeError, msg='authority=%r' % authority): uricompose(authority=authority)
def _find_albums(self, query): statement = ('select id, album, day, month, year, ' 'albumartist, disctotal, ' 'mb_albumid, artpath, mb_albumartistid ' 'from albums where 1=1 ') statement += self._build_statement(query, 'genre', 'genre') statement += self._build_statement(query, 'artist', 'albumartist') statement += self._build_statement(query, 'album', 'album') statement += self._build_statement(query, 'mb_albumid', 'mb_albumid') statement += self._build_statement(query, 'date', 'year') result = self._query_beets_db(statement) albums = [] for row in result: date = self._build_date(row[4], row[3], row[2]) artist = Artist(name=row[5], musicbrainz_id=row[9], uri=uricompose('beetslocal', None, 'artist:%s:' % row[9])) albums.append( Album( name=row[1], date=date, artists=[artist], # num_tracks=row[6], num_discs=row[6], musicbrainz_id=row[7], # images=[row[8]], uri=uricompose('beetslocal', None, 'album:%s:' % row[0]))) return albums
def uri(identifier='', filename=None, scheme=Extension.ext_name, **kwargs): if filename: return uritools.uricompose(scheme, path=identifier, fragment=filename) elif kwargs: return uritools.uricompose(scheme, path=identifier, query=kwargs) else: return '%s:%s' % (scheme, identifier)
def _browse_directory(self, uri, order=('type', 'name')): query = dict(uritools.urisplit(uri).getquerylist()) type = query.pop('type', None) role = query.pop('role', None) # TODO: handle these in schema (generically)? if type == 'date': format = query.get('format', '%Y-%m-%d') return map(_dateref, schema.dates(self._connect(), format=format)) if type == 'genre': return map(_genreref, schema.list_distinct(self._connect(), 'genre')) # noqa # Fix #38: keep sort order of album tracks; this also applies # to composers and performers if type == Ref.TRACK and 'album' in query: order = ('disc_no', 'track_no', 'name') roles = role or ('artist', 'albumartist') # FIXME: re-think 'roles'... refs = [] for ref in schema.browse(self._connect(), type, order, role=roles, **query): # noqa if ref.type == Ref.TRACK or (not query and not role): refs.append(ref) elif ref.type == Ref.ALBUM: refs.append(Ref.directory(uri=uritools.uricompose( 'local', None, 'directory', dict(query, type=Ref.TRACK, album=ref.uri) # noqa ), name=ref.name)) elif ref.type == Ref.ARTIST: refs.append(Ref.directory(uri=uritools.uricompose( 'local', None, 'directory', dict(query, **{role: ref.uri}) ), name=ref.name)) else: logger.warn('Unexpected SQLite browse result: %r', ref) return refs
def uri(identifier="", filename=None, scheme=Extension.ext_name, **kwargs): if filename: return uritools.uricompose(scheme, path=identifier, fragment=filename) elif kwargs: return uritools.uricompose(scheme, path=identifier, query=kwargs) else: return f"{scheme}:{identifier}"
def _find_albums(self, query): statement = ('select id, album, day, month, year, ' 'albumartist, disctotal, ' 'mb_albumid, artpath, mb_albumartistid ' 'from albums where 1=1 ') statement += self._build_statement(query, 'genre', 'genre') statement += self._build_statement(query, 'artist', 'albumartist') statement += self._build_statement(query, 'album', 'album') statement += self._build_statement(query, 'mb_albumid', 'mb_albumid') statement += self._build_statement(query, 'date', 'year') result = self._query_beets_db(statement) albums = [] for row in result: date = self._build_date(row[4], row[3], row[2]) artist = Artist(name=row[5], musicbrainz_id=row[9], uri=uricompose('beetslocal', None, 'artist:%s:' % row[9])) albums.append(Album(name=row[1], date=date, artists=[artist], # num_tracks=row[6], num_discs=row[6], musicbrainz_id=row[7], # images=[row[8]], uri=uricompose('beetslocal', None, 'album:%s:' % row[0]))) return albums
def _find_tracks(self, query): statement = ('select id, title, day, month, year, artist, album, ' 'composer, track, disc, length, bitrate, comments, ' 'mb_trackid, mtime, genre, tracktotal, disctotal, ' 'mb_albumid, mb_albumartistid, albumartist, mb_artistid ' 'from items where 1=1 ') statement += self._build_statement(query, 'track_name', 'title') statement += self._build_statement(query, 'genre', 'genre') statement += self._build_statement(query, 'artist', 'artist') statement += self._build_statement(query, 'album', 'album') statement += self._build_statement(query, 'composer', 'composer') statement += self._build_statement(query, 'mb_trackid', 'mb_trackid') statement += self._build_statement(query, 'mb_albumid', 'mb_albumid') statement += self._build_statement(query, 'mb_albumartistid', 'mb_albumartistid') statement += self._build_statement(query, 'date', 'year') tracks = [] result = self._query_beets_db(statement) for row in result: date = self._build_date(row[4], row[3], row[2]) artist = Artist(name=row[5], musicbrainz_id=row[21], uri=uricompose('beetslocal', None, 'artist:%s:' % row[21])) albumartist = Artist(name=row[20], musicbrainz_id=row[19], uri=uricompose('beetslocal', None, 'artist:%s:' % row[19])) composer = Artist(name=row[7], musicbrainz_id='', uri=uricompose('beetslocal', None, 'composer:%s:' % row[7])) album = Album(name=row[6], date=date, artists=[albumartist], num_tracks=row[16], num_discs=row[17], musicbrainz_id=row[18], uri=uricompose('beetslocal', None, 'mb_album:%s:' % row[18])) tracks.append( Track(name=row[1], artists=[artist], album=album, composers=[composer], track_no=row[8], disc_no=row[9], date=date, length=int(row[10] * 1000), bitrate=row[11], comment=row[12], musicbrainz_id=row[13], last_modified=int(row[14] * 1000), genre=row[15], uri=uricompose('beetslocal', None, 'track:%s:' % row[0]))) return tracks
def _browse_directory(self, uri, order=("type", "name COLLATE NOCASE")): query = dict(uritools.urisplit(uri).getquerylist()) type = query.pop("type", None) role = query.pop("role", None) # TODO: handle these in schema (generically)? if type == "date": format = query.get("format", "%Y-%m-%d") return list( map(date_ref, schema.dates(self._connect(), format=format))) if type == "genre": return list( map(genre_ref, schema.list_distinct(self._connect(), "genre"))) # Fix #38: keep sort order of album tracks; this also applies # to composers and performers if type == Ref.TRACK and "album" in query: order = ("disc_no", "track_no", "name") if type == Ref.ARTIST and self._config["use_artist_sortname"]: order = ("coalesce(sortname, name) COLLATE NOCASE", ) roles = role or ("artist", "albumartist") # FIXME: re-think 'roles'... refs = [] for ref in schema.browse(self._connect(), type, order, role=roles, **query): # noqa if ref.type == Ref.TRACK or (not query and not role): refs.append(ref) elif ref.type == Ref.ALBUM: refs.append( Ref.directory( uri=uritools.uricompose( "local", None, "directory", dict(query, type=Ref.TRACK, album=ref.uri), # noqa ), name=ref.name, )) elif ref.type == Ref.ARTIST: refs.append( Ref.directory( uri=uritools.uricompose("local", None, "directory", dict(query, **{role: ref.uri})), name=ref.name, )) else: logger.warning("Unexpected SQLite browse result: %r", ref) return refs
def test_scheme(self): cases = [ ('foo+bar:', 'foo+bar'), ('foo+bar:', b'foo+bar'), ('foo+bar:', 'FOO+BAR'), ('foo+bar:', b'FOO+BAR'), ] for uri, scheme in cases: self.check(uri, scheme=scheme) # invalid scheme for scheme in ('', 'foo:', '\xf6lk\xfcrbis'): with self.assertRaises(ValueError, msg='scheme=%r' % scheme): uricompose(scheme=scheme)
def root_directory(self): root = self.__browse_root if not root: return None elif root.startswith(('file:', 'http:', 'https:')): uri = uritools.uridefrag('podcast+' + root).uri elif os.path.isabs(root): uri = uritools.uricompose('podcast+file', '', root) elif self.__config_dir: uri = uritools.uricompose('podcast+file', '', os.path.join(self.__config_dir, root)) else: return None return models.Ref.directory(name='Podcasts', uri=uri)
def browse(self, uri): logger.debug(u'browse called with uri %s' % uri) result = [] localpath = urisplit(uri).path # import pdb; pdb.set_trace() if localpath == 'root': result = self._media_dirs() else: directory = localpath logger.debug(u'directory is %s' % directory) for name in os.listdir(directory): child = os.path.join(directory, name) uri = uricompose(self.URI_PREFIX, None, child) if self.backend._follow_symlinks: st = os.stat(child) else: st = os.lstat(child) if not self.backend._show_hidden and name.startswith(b'.'): continue elif stat.S_ISDIR(st.st_mode): result.append(models.Ref.directory(name=name, uri=uri)) elif stat.S_ISREG(st.st_mode): result.append(models.Ref.track(name=name, uri=uri)) else: logger.warn(u'Strange file encountered %s' % child) pass result.sort(key=operator.attrgetter('name')) return result
def search(self, query=None, uris=None, exact=False): logger.debug(u'Search query: %s in uris: %s' % (query, uris)) # import pdb; pdb.set_trace() query = self._sanitize_query(query) logger.debug(u'Search sanitized query: %s ' % query) if exact: return self._find_exact(query, uris) albums = [] if not query: uri = 'beetslocal:search-all' tracks = self.lib.items() albums = self.lib.albums() else: uri = uricompose('beetslocal', None, 'search', query) track_query = self._build_beets_track_query(query) logger.debug(u'Build Query "%s":' % track_query) tracks = self.lib.items(track_query) if 'track_name' not in query: # when trackname queried dont search for albums album_query = self._build_beets_album_query(query) logger.debug('Build Query "%s":' % album_query) albums = self.lib.albums(album_query) logger.debug(u"Query found %s tracks and %s albums" % (len(tracks), len(albums))) return SearchResult( uri=uri, tracks=[self._convert_item(track) for track in tracks], albums=[self._convert_album(album) for album in albums])
def _lastmodifiedref(option): timestamp = int(time.time()) - option[0] * 24*60*1000 return Ref.directory( uri=uritools.uricompose('local', None, 'directory', {'last_modified': timestamp}), name=option[1] )
def search(self, query=None, uris=None): albums = [] logger.debug("Search query: %s in uris: %s" % (query, uris)) if not query: uri = 'beetslocal:search-all' tracks = self.lib.items() # albums = self.lib.albums() # albums not used til advanced_search else: uri = uricompose('beetslocal', None, 'search', query) self._validate_query(query) track_query = self._build_beets_track_query(query) logger.debug('Build Query "%s":' % track_query) tracks = self.lib.items(track_query) # if not 'track_name' in query: # when trackname queried dont search for albums # album_query = self._build_beets_album_query(query) # logger.debug('Build Query "%s":' % album_query) # albums = self.lib.albums(album_query) logger.debug("Query found %s tracks and %s albums" % (len(tracks), len(albums))) return SearchResult( uri=uri, tracks=[self._convert_item(track) for track in tracks] # albums=[self._convert_album(album) for album in albums] )
def _album(metadata, images=[]): identifier = metadata['identifier'] uri = uritools.uricompose(SCHEME, path=identifier) name = metadata.get('title', identifier) artists = parse_creator(metadata.get('creator')) date = parse_date(metadata.get('date')) return Album(uri=uri, name=name, artists=artists, date=date, images=images)
def __init__(self, config): if not set(("uri", "name", "user", "password")).issubset(config): raise ValueError("Incomplete config dictionary") # QUESTION thread local connection (pool) vs sharing a serialized connection, pro/cons? # from sqlalchemy.orm import sessionmaker, scoped_session # Session = scoped_session(sessionmaker(bind=engine)) # when using a connection pool how do we prevent notifiying ourself on database changes? self.lock = Lock() self.notify = None uri_parts = urisplit(config["uri"]) if self.DIALECT_SQLITE == uri_parts.scheme: self.engine = create_engine( config["uri"], connect_args={"check_same_thread": False}) self.conn = self.engine.connect() self.conn.execute( text("PRAGMA foreign_keys = ON").execution_options( autocommit=True)) elif self.DIALECT_POSTGRESQL == uri_parts.scheme: uri = uricompose(scheme=uri_parts.scheme, host=uri_parts.host, port=uri_parts.getport(default=5432), path="/{}".format(config["name"]), userinfo="{}:{}".format(config["user"], config["password"])) self.engine = create_engine(uri) self.conn = self.engine.connect() self.notify = PGNotify(uri) else: raise ValueError("Engine '{engine}' not supported".format( engine=uri_parts.scheme))
def search(self, query=None, uris=None, exact=False): logger.debug(u'Search query: %s in uris: %s' % (query, uris)) # import pdb; pdb.set_trace() query = self._sanitize_query(query) logger.debug(u'Search sanitized query: %s ' % query) if exact: return self._find_exact(query, uris) albums = [] if not query: uri = 'beetslocal:search-all' tracks = self.lib.items() albums = self.lib.albums() else: uri = uricompose('beetslocal', None, 'search', query) track_query = self._build_beets_track_query(query) logger.debug(u'Build Query "%s":' % track_query) tracks = self.lib.items(track_query) if 'track_name' not in query: # when trackname queried dont search for albums album_query = self._build_beets_album_query(query) logger.debug('Build Query "%s":' % album_query) albums = self.lib.albums(album_query) logger.debug(u"Query found %s tracks and %s albums" % (len(tracks), len(albums))) return SearchResult( uri=uri, tracks=[self._convert_item(track) for track in tracks], albums=[self._convert_album(album) for album in albums] )
def _path_in_url(self, key): return uritools.uricompose( scheme='http', host=self.endpoint, port=self.port, path='/v2/keys/{}'.format(key.lstrip('/')) )
def browse(self, uri): logger.debug(u"Browse being called for %s" % uri) level = urisplit(uri).path query = self._sanitize_query(dict(urisplit(uri).getquerylist())) logger.debug("Got parsed to level: %s - query: %s" % (level, query)) result = [] if not level: logger.error("No level for uri %s" % uri) # import pdb; pdb.set_trace() if level == 'root': for row in self._browse_genre(): result.append(Ref.directory( uri=uricompose('beetslocal', None, 'genre', dict(genre=row[0])), name=row[0] if bool(row[0]) else u'No Genre')) elif level == "genre": # artist refs not browsable via mpd for row in self._browse_artist(query): result.append(Ref.directory( uri=uricompose('beetslocal', None, 'artist', dict(genre=query['genre'][0], artist=row[1])), name=row[0] if bool(row[0]) else u'No Artist')) elif level == "artist": for album in self._browse_album(query): result.append(Ref.album( uri=uricompose('beetslocal', None, 'album', dict(album=album.id)), name=album.album)) elif level == "album": for track in self._browse_track(query): result.append(Ref.track( uri="beetslocal:track:%s:%s" % ( track.id, uriencode(track.path, '/')), name=track.title)) else: logger.debug('Unknown URI: %s', uri) # logger.debug(result) return result
def root_directory(self): root = self.__browse_root if not root: return None elif root.startswith(('file:', 'http:', 'https:')): uri = uritools.uridefrag('podcast+' + root).uri return models.Ref.directory(name='Podcasts', uri=uri) elif os.path.isabs(root): uri = uritools.uricompose('podcast+file', '', root) return models.Ref.directory(name='Podcasts', uri=uri) elif self.__config_dir: path = os.path.join(self.__config_dir, root) uri = uritools.uricompose('podcast+file', '', path) return models.Ref.directory(name='Podcasts', uri=uri) else: logger.error('Cannot retrieve Podcast root directory') return None
def __add_server(self, obj): udn = obj['UDN'] obj['URI'] = uritools.uricompose(Extension.ext_name, udn) key = udn.lower() if key not in self: self.__log_server_action('Found', obj) with self.__lock: self.__servers[key] = obj
def test_query(self): from collections import OrderedDict as od cases = [ ('?', ''), ('?', b''), ('?', []), ('?', {}), ('?name', 'name'), ('?name', b'name'), ('?name', [('name', None)]), ('?name', [(b'name', None)]), ('?name', {'name': None}), ('?name', {b'name': None}), ('?name=foo', 'name=foo'), ('?name=foo', b'name=foo'), ('?name=foo', [('name', 'foo')]), ('?name=foo', [('name', b'foo')]), ('?name=foo', [(b'name', b'foo')]), ('?name=foo', {'name': 'foo'}), ('?name=foo', {'name': b'foo'}), ('?name=foo', {'name': ['foo']}), ('?name=foo', {'name': [b'foo']}), ('?name=foo', {b'name': b'foo'}), ('?name=foo', {b'name': [b'foo']}), ('?name=42', [('name', 42)]), ('?name=42', {'name': 42}), ('?name=42', {'name': [42]}), ('?name=foo&type=bar', [('name', 'foo'), ('type', 'bar')]), ('?name=foo&type=bar', od([('name', 'foo'), ('type', 'bar')])), ('?name=foo&name=bar', [('name', 'foo'), ('name', 'bar')]), ('?name=foo&name=bar', {'name': ['foo', 'bar']}), ('?name=a/b/c', dict(name='a/b/c')), ('?name=a:b:c', dict(name='a:b:c')), ('?name=a?b?c', dict(name='a?b?c')), ('?name=a@b@c', dict(name='a@b@c')), ('?name=a%23b%23c', dict(name='a#b#c')), ('?name=a%26b%26c', dict(name='a&b&c')), ('?name=a%3Bb%3Bc', dict(name='a;b;c')), ] for uri, query in cases: self.check(uri, query=query) # invalid query type for query in (0, [1]): with self.assertRaises(TypeError, msg='query=%r' % query): uricompose(query=query)
def _ref(metadata): identifier = metadata['identifier'] uri = uritools.uricompose(SCHEME, path=identifier) name = metadata.get('title', identifier) if metadata.get('mediatype', 'collection') == 'collection': return Ref.directory(uri=uri, name=name) else: return Ref.album(uri=uri, name=name)
def __add_server(self, obj): udn = obj["UDN"] obj["URI"] = uritools.uricompose(Extension.ext_name, udn) key = udn.lower() if key not in self: self.__log_server_action("Found", obj) with self.__lock: self.__servers[key] = obj
def parametrizeUri(baseUri, params): parts = uritools.urisplit(baseUri) uri = uritools.uricompose(parts.scheme, parts.host, parts.path, params, port=parts.port) return uri
def _media_dirs(self): result = [] for media_dir in self.backend.media_dirs: dir = models.Ref.directory( name=media_dir['name'], uri=uricompose(self.URI_PREFIX, None, media_dir['path'])) result.append(dir) return result
def search(self, query=None, limit=100, offset=0, uris=None, exact=False): q = [] for field, values in query.items() if query else []: q.extend((field, value) for value in values) filters = [f for uri in uris or [] for f in self._filters(uri) if f] with self._connect() as c: tracks = schema.search_tracks(c, q, limit, offset, exact, filters) uri = uritools.uricompose("local", path="search", query=q) return SearchResult(uri=uri, tracks=tracks)
def search(self, query=None, limit=100, offset=0, uris=None, exact=False): q = [] for field, values in (query.items() if query else []): q.extend((field, value) for value in values) filters = [f for uri in uris or [] for f in self._filters(uri) if f] with self._connect() as c: tracks = schema.search_tracks(c, q, limit, offset, exact, filters) uri = uritools.uricompose('local', path='search', query=q) return SearchResult(uri=uri, tracks=tracks)
def _convert_album(self, album): """ Transforms a beets album into a mopidy Track """ if not album: return album_kwargs = {} artist_kwargs = {} if 'album' in album: album_kwargs['name'] = album['album'] if 'disctotal' in album: album_kwargs['num_discs'] = album['disctotal'] if 'tracktotal' in album: album_kwargs['num_tracks'] = album['tracktotal'] if 'mb_albumid' in album: album_kwargs['musicbrainz_id'] = album['mb_albumid'] album_kwargs['date'] = None if self.backend.use_original_release_date: if 'original_year' in album: album_kwargs['date'] = self._build_date( album['original_year'], album['original_month'], album['original_day']) else: if 'year' in album: album_kwargs['date'] = self._build_date(album['year'], album['month'], album['day']) # if 'added' in item: # album_kwargs['last_modified'] = album['added'] # if 'artpath' in album: # album_kwargs['images'] = [album['artpath']] if 'albumartist' in album: artist_kwargs['name'] = album['albumartist'] if 'mb_albumartistid' in album: artist_kwargs['musicbrainz_id'] = album['mb_albumartistid'] if artist_kwargs: artist = Artist(**artist_kwargs) album_kwargs['artists'] = [artist] if 'id' in album: album_kwargs['uri'] = uricompose('beetslocal', None, 'album:%s:' % album['id']) album = Album(**album_kwargs) return album
def _search(self, *terms): result = self.backend.client.search( _query(*terms, **self._search_filter), fields=['identifier', 'title', 'creator', 'date'], sort=self._config['search_order'], rows=self._config['search_limit'] ) uri = uricompose(Extension.ext_name, query=result.query) return SearchResult(uri=uri, albums=[_album(doc) for doc in result])
def _browse_directory(self, uri): query = dict(uritools.urisplit(str(uri)).getquerylist()) type = query.pop('type', None) role = query.pop('role', None) # TODO: handle these in schema (generically)? if type == 'date': format = query.get('format', '%Y-%m-%d') return map(_dateref, schema.dates(self._connect(), format=format)) if type == 'genre': return map(_genreref, schema.genres(self._connect())) if type == 'last_modified': return map(_lastmodifiedref, ((7, 'Last 7 days'), (30, 'Last month'), (92, 'Last 3 months'), (157, 'Last 6 months'), (365, 'Last year')) ) roles = role or ('artist', 'albumartist') refs = [] for ref in schema.browse(self._connect(), type, role=roles, **query): if ref.type == Ref.TRACK or (not query and not role): # FIXME: artist refs not browsable via mpd if ref.type == Ref.ARTIST: refs.append(ref.copy(type=Ref.DIRECTORY)) else: refs.append(ref) elif ref.type == Ref.ALBUM: uri = uritools.uricompose('local', None, 'directory', dict( query, type=Ref.TRACK, album=ref.uri )) refs.append(Ref.directory(uri=uri, name=ref.name)) elif ref.type == Ref.ARTIST: uri = uritools.uricompose('local', None, 'directory', dict( query, **{role: ref.uri} )) refs.append(Ref.directory(uri=uri, name=ref.name)) else: logger.warn('Unexpected SQLite browse result: %r', ref) return refs
def _convert_album(self, album): """ Transforms a beets album into a mopidy Track """ if not album: return album_kwargs = {} artist_kwargs = {} if 'album' in album: album_kwargs['name'] = album['album'] if 'disctotal' in album: album_kwargs['num_discs'] = album['disctotal'] if 'tracktotal' in album: album_kwargs['num_tracks'] = album['tracktotal'] if 'mb_albumid' in album: album_kwargs['musicbrainz_id'] = album['mb_albumid'] album_kwargs['date'] = None if self.backend.use_original_release_date: if 'original_year' in album: album_kwargs['date'] = self._build_date( album['original_year'], album['original_month'], album['original_day']) else: if 'year' in album: album_kwargs['date'] = self._build_date( album['year'], album['month'], album['day']) # if 'added' in item: # album_kwargs['last_modified'] = album['added'] # if 'artpath' in album: # album_kwargs['images'] = [album['artpath']] if 'albumartist' in album: artist_kwargs['name'] = album['albumartist'] if 'mb_albumartistid' in album: artist_kwargs['musicbrainz_id'] = album['mb_albumartistid'] if artist_kwargs: artist = Artist(**artist_kwargs) album_kwargs['artists'] = [artist] if 'id' in album: album_kwargs['uri'] = uricompose('beetslocal', None, 'album:%s:' % album['id']) album = Album(**album_kwargs) return album
def browse(self, uri): logger.debug(u"Browse being called for %s" % uri) level = urisplit(uri).path query = self._sanitize_query(dict(urisplit(uri).getquerylist())) logger.debug("Got parsed to level: %s - query: %s" % (level, query)) result = [] if not level: logger.error("No level for uri %s" % uri) # import pdb; pdb.set_trace() if level == 'root': for row in self._browse_genre(): result.append( Ref.directory( uri=uricompose('beetslocal', None, 'genre', dict(genre=row[0])), name=row[0] if bool(row[0]) else u'No Genre')) elif level == "genre": # artist refs not browsable via mpd for row in self._browse_artist(query): result.append( Ref.directory( uri=uricompose( 'beetslocal', None, 'artist', dict(genre=query['genre'][0], artist=row[1])), name=row[0] if bool(row[0]) else u'No Artist')) elif level == "artist": for album in self._browse_album(query): result.append( Ref.album(uri=uricompose('beetslocal', None, 'album', dict(album=album.id)), name=album.album)) elif level == "album": for track in self._browse_track(query): result.append( Ref.track(uri="beetslocal:track:%s:%s" % (track.id, uriencode(track.path, '/')), name=track.title)) else: logger.debug('Unknown URI: %s', uri) # logger.debug(result) return result
def parse(source): if isinstance(source, basestring): url = uritools.uricompose('file', '', source) else: url = source.geturl() root = ElementTree.parse(source).getroot() if root.tag == 'rss': return RssFeed(url, root) elif root.tag == 'opml': return OpmlFeed(url, root) else: raise TypeError('Not a recognized podcast feed: %s', url)
def search(self, query=None, limit=100, offset=0, uris=None, exact=False): q = [] for field, values in (query.items() if query else []): if isinstance(values, basestring): q.append((field, values)) else: q.extend((field, value) for value in values) filters = [f for uri in uris or [] for f in self._filters(uri) if f] with self._connect() as c: tracks = schema.search_tracks(c, q, limit, offset, exact, filters) uri = uritools.uricompose('local', path='search', query=q) return SearchResult(uri=uri, tracks=tracks)
def search(self, query=None, limit=100, offset=0, uris=None, exact=False): q = [] for field, values in (query.items() if query else []): q.extend((field, value) for value in values) # temporary workaround until Mopidy core sets limit if self._config['search_limit'] is not None: limit = self._config['search_limit'] filters = [f for uri in uris or [] for f in self._filters(uri) if f] with self._connect() as c: tracks = schema.search_tracks(c, q, limit, offset, exact, filters) uri = uritools.uricompose('local', path='search', query=q) return SearchResult(uri=uri, tracks=tracks)
def _browse_directory(self, uri): query = dict(uritools.urisplit(str(uri)).getquerylist()) type = query.pop('type', None) role = query.pop('role', None) # TODO: handle these in schema (generically)? if type == 'date': format = query.get('format', '%Y-%m-%d') return map(_dateref, schema.dates(self._connect(), format=format)) if type == 'genre': return map(_genreref, schema.genres(self._connect())) if type == 'last_modified': return map(_lastmodifiedref, ((7, 'Last 7 days'), (30, 'Last month'), (92, 'Last 3 months'), (157, 'Last 6 months'), (365, 'Last year'))) roles = role or ('artist', 'albumartist') refs = [] for ref in schema.browse(self._connect(), type, role=roles, **query): if ref.type == Ref.TRACK or (not query and not role): # FIXME: artist refs not browsable via mpd if ref.type == Ref.ARTIST: refs.append(ref.copy(type=Ref.DIRECTORY)) else: refs.append(ref) elif ref.type == Ref.ALBUM: uri = uritools.uricompose( 'local', None, 'directory', dict(query, type=Ref.TRACK, album=ref.uri)) refs.append(Ref.directory(uri=uri, name=ref.name)) elif ref.type == Ref.ARTIST: uri = uritools.uricompose('local', None, 'directory', dict(query, **{role: ref.uri})) refs.append(Ref.directory(uri=uri, name=ref.name)) else: logger.warn('Unexpected SQLite browse result: %r', ref) return refs
def compose(scheme=None, authority=None, path=None, query=None, fragment=None, port=None): parts = uritools.uricompose(scheme=scheme, host=authority, port=port, path=path, query=query, fragment=fragment) return uritools.uriunsplit(parts)
def test_path(self): cases = [ ('foo', 'foo'), ('foo', b'foo'), ('foo+bar', 'foo+bar'), ('foo+bar', b'foo+bar'), ('foo%20bar', 'foo bar'), ('foo%20bar', b'foo bar'), ('./this:that', 'this:that'), ('./this:that', b'this:that'), ('./this:that/', 'this:that/'), ('./this:that/', b'this:that/'), ] for uri, path in cases: self.check(uri, path=path) # invalid path with authority for path in ('foo', b'foo'): with self.assertRaises(ValueError, msg='path=%r' % path): uricompose(authority='auth', path=path) # invalid path without authority for path in ('//', b'//', '//foo', b'//foo'): with self.assertRaises(ValueError, msg='path=%r' % path): uricompose(path=path)
def build_url(cls, base, additional_params=None): url = uritools.urisplit(base) query_params = url.getquerydict() if additional_params is not None: query_params.update(additional_params) for k, v in additional_params.items(): if v is None: query_params.pop(k) return uritools.uricompose(scheme=url.scheme, host=url.host, port=url.port, path=url.path, query=query_params, fragment=url.fragment)
def build_uri(scheme=DEFAULT_SCALE_URI_SCHEME, namespace=DEFAULT_SCALE_URI_NAMESPACE, path=None, relative_path=None, **kwargs): """ Build a URI from the specified parameters. If you don't specify path to create a complete path, you can specify a relative path to have it build one for you on top of the optionally-specified namespace that helps avoid collision with core scale client URIs. NOTE: these parameters are just for conventional purposes and don't do any significant namespace separation, management, or API exposure currently... :param path: an absolute URI path (will skip over the namespace and relative one when present) :param scheme: first part of the URI identifying the protocol/scheme/etc. (default is scale-specific for local use) :param namespace: prepended on relative path (default is scale-specific); the scale core may handle the different namespaces separately :param relative_path: e.g. your/path/goes/here but note that you are responsible for managing this path hierarchy! :param kwargs: all these are passed to uritools.uricompose :return: """ # First build up the path we'll use parts_to_use = [] if not path and not relative_path: raise ValueError( "must specify at least a component path (can be a simple name string) or " ) elif not path and relative_path: if namespace: parts_to_use.append(namespace) parts_to_use.append(relative_path) else: parts_to_use.append(path) # TODO: check for ignored args and warn user? only when logging enabled... # if relative_path: # First, trim any leading/trailing slashes final_parts = [] for part in parts_to_use: while part.startswith('/'): part = part[1:] while part.endswith('/'): part = part[:-1] final_parts.append(part) # Note that we enforce a leading / for the path! path = '/' + '/'.join(final_parts) return uritools.uricompose(scheme=scheme, path=path, **kwargs)
def _track(metadata, file, album): identifier = metadata['identifier'] filename = file['name'] uri = uritools.uricompose(SCHEME, path=identifier, fragment=filename) name = file.get('title', filename) return Track( uri=uri, name=name, album=album, artists=album.artists, track_no=parse_track(file.get('track')), date=parse_date(file.get('date'), album.date), length=parse_length(file.get('length')), bitrate=parse_bitrate(file.get('bitrate')), last_modified=parse_mtime(file.get('mtime')) )
def get_safe_redirect_target(arg='next'): """Get URL to redirect to and ensure that it is local. :param arg: URL argument. :returns: The redirect target or ``None``. """ for target in request.args.get(arg), request.referrer: if target: redirect_uri = uritools.urisplit(target) allowed_hosts = current_app.config.get('APP_ALLOWED_HOSTS', []) if redirect_uri.host in allowed_hosts: return target elif redirect_uri.path: return uritools.uricompose(path=redirect_uri.path, query=redirect_uri.query) return None
def test_you_can_register_and_login_using_an_unregistered_ssl_cert_with_email( self): certAttr = self.getCertAttributes() params = dict(email="*****@*****.**") parts = uritools.urisplit(Config.BASE_URL) url = uritools.uricompose(parts.scheme, parts.host, parts.path, params) self.controller.interface.set_request_context(url) self.controller.mail = FakeMail() resp = self.sslLoginWithCert(certAttr.cert) cred = Credential.get("certificate", certAttr.identifier) self.deleteUser(cred.user) self.assertEqual(resp.status_code, 200) responseText = self.getResponseText(resp) self.assertTrue(CREDENTIAL_REPRESENTATION in responseText) self.assertTrue( '{"credentialType": "emailcheck", "identifier":' in responseText)
def _browse_artist(self, uri, order=('type', 'name')): with self._connect() as c: albums = schema.browse(c, Ref.ALBUM, order, albumartist=uri) refs = schema.browse(c, order=order, artist=uri) uris, tracks = {ref.uri for ref in albums}, [] for ref in refs: if ref.type == Ref.TRACK: tracks.append(ref) elif ref.type == Ref.ALBUM and ref.uri not in uris: uri = uritools.uricompose('local', None, 'directory', dict( type=Ref.TRACK, album=ref.uri, artist=uri )) albums.append(Ref.directory(uri=uri, name=ref.name)) else: logger.debug('Skipped SQLite browse result %s', ref.uri) albums.sort(key=operator.attrgetter('name')) return albums + tracks
def _find_artists(self, query): statement = ('select Distinct albumartist, mb_albumartistid' ' from albums where 1=1 ') statement += self._build_statement(query, 'genre', 'genre') statement += self._build_statement(query, 'artist', 'albumartist') statement += self._build_statement(query, 'date', 'year') statement += self._build_statement(query, 'mb_albumartistid', 'mb_albumartistid') artists = [] result = self._query_beets_db(statement) for row in result: artists.append( Artist(name=row[0], musicbrainz_id=row[1], uri=uricompose('beetslocal', None, 'artist:%s:' % row[1]))) return artists
def load_uri(self, uri: str) -> dict: """Return the JSON object associated with given URI :param uri: URI string """ result = urisplit(uri) location = uricompose(result.scheme, result.authority, result.path) loader = self.scheme_to_loader[result.scheme] resource = self.load_resource_from_loader(loader, location) if result.fragment: assert result.fragment.startswith("/") reference = Reference(result.fragment[1:]) return reference.extract(resource) return resource
def _find_artists(self, query): statement = ('select Distinct albumartist, mb_albumartistid' ' from albums where 1=1 ') statement += self._build_statement(query, 'genre', 'genre') statement += self._build_statement(query, 'artist', 'albumartist') statement += self._build_statement(query, 'date', 'year') statement += self._build_statement(query, 'mb_albumartistid', 'mb_albumartistid') artists = [] result = self._query_beets_db(statement) for row in result: artists.append(Artist(name=row[0], musicbrainz_id=row[1], uri=uricompose('beetslocal', None, 'artist:%s:' % row[1]))) return artists
def build_uri(scheme=DEFAULT_SCALE_URI_SCHEME, namespace=DEFAULT_SCALE_URI_NAMESPACE, path=None, relative_path=None, **kwargs): """ Build a URI from the specified parameters. If you don't specify path to create a complete path, you can specify a relative path to have it build one for you on top of the optionally-specified namespace that helps avoid collision with core scale client URIs. NOTE: these parameters are just for conventional purposes and don't do any significant namespace separation, management, or API exposure currently... :param path: an absolute URI path (will skip over the namespace and relative one when present) :param scheme: first part of the URI identifying the protocol/scheme/etc. (default is scale-specific for local use) :param namespace: prepended on relative path (default is scale-specific); the scale core may handle the different namespaces separately :param relative_path: e.g. your/path/goes/here but note that you are responsible for managing this path hierarchy! :param kwargs: all these are passed to uritools.uricompose :return: """ # First build up the path we'll use parts_to_use = [] if not path and not relative_path: raise ValueError("must specify at least a component path (can be a simple name string) or ") elif not path and relative_path: if namespace: parts_to_use.append(namespace) parts_to_use.append(relative_path) else: parts_to_use.append(path) # TODO: check for ignored args and warn user? only when logging enabled... # if relative_path: # First, trim any leading/trailing slashes final_parts = [] for part in parts_to_use: while part.startswith('/'): part = part[1:] while part.endswith('/'): part = part[:-1] final_parts.append(part) # Note that we enforce a leading / for the path! path = '/' + '/'.join(final_parts) return uritools.uricompose(scheme=scheme, path=path, **kwargs)
def _find_exact(self, query=None, uris=None): logger.debug("Find query: %s in uris: %s" % (query, uris)) # artists = [] albums = [] if not (('track_name' in query) or ('composer' in query)): # when trackname or composer is queried dont search for albums albums = self._find_albums(query) logger.debug("Find found %s albums" % len(albums)) # artists=self._find_artists(query) # logger.debug("Find found %s artists" % len(artists)) tracks = self._find_tracks(query) logger.debug(u'Find found %s tracks' % len(tracks)) return SearchResult( uri=uricompose('beetslocal', None, 'find', query), # artists=artists, albums=albums, tracks=tracks)
def _compose_url(self, url, kwargs): """ Compose a URL starting with the given URL (or self.url if that URL is None) and using the values in kwargs. :param str url: The base URL to use. If None, ``self.url`` will be used instead. :param dict kwargs: A dictionary of values to override in the base URL. Relevant keys will be popped from the dictionary. """ if url is None: url = self.url if url is None: raise ValueError( 'url not provided and this client has no url attribute') split_result = urisplit(url) userinfo = split_result.userinfo # Build up the kwargs to pass to uricompose compose_kwargs = {} for key in ['scheme', 'host', 'port', 'path', 'fragment']: if key in kwargs: compose_kwargs[key] = kwargs.pop(key) else: compose_kwargs[key] = getattr(split_result, key) if 'params' in kwargs: compose_kwargs['query'] = kwargs.pop('params') else: compose_kwargs['query'] = split_result.query # Take the userinfo out of the URL and pass as 'auth' to treq so it can # be used for HTTP basic auth headers if 'auth' not in kwargs and userinfo is not None: # treq expects a 2-tuple (username, password) kwargs['auth'] = tuple(userinfo.split(':', 2)) return uricompose(**compose_kwargs)