class Grilo(GObject.GObject): __gsignals__ = { 'ready': (GObject.SignalFlags.RUN_FIRST, None, ()), 'changes-pending': (GObject.SignalFlags.RUN_FIRST, None, ()), 'new-source-added': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Source, )) } METADATA_KEYS = [ Grl.METADATA_KEY_ALBUM, Grl.METADATA_KEY_ALBUM_ARTIST, Grl.METADATA_KEY_ARTIST, Grl.METADATA_KEY_CREATION_DATE, Grl.METADATA_KEY_DURATION, Grl.METADATA_KEY_ID, Grl.METADATA_KEY_LYRICS, Grl.METADATA_KEY_THUMBNAIL, Grl.METADATA_KEY_TITLE, Grl.METADATA_KEY_URL ] METADATA_THUMBNAIL_KEYS = [ Grl.METADATA_KEY_ID, Grl.METADATA_KEY_THUMBNAIL, ] CHANGED_MEDIA_MAX_ITEMS = 500 CHANGED_MEDIA_SIGNAL_TIMEOUT = 2000 def __repr__(self): return '<Grilo>' @log def __init__(self): GObject.GObject.__init__(self) self.playlist_path = GLib.build_filenamev([GLib.get_user_data_dir(), "gnome-music", "playlists"]) if not (GLib.file_test(self.playlist_path, GLib.FileTest.IS_DIR)): GLib.mkdir_with_parents(self.playlist_path, int("0755", 8)) Grl.init(None) self.options = Grl.OperationOptions() self.options.set_resolution_flags(Grl.ResolutionFlags.FAST_ONLY | Grl.ResolutionFlags.IDLE_RELAY) self.full_options = Grl.OperationOptions() self.full_options.set_resolution_flags(Grl.ResolutionFlags.FULL | Grl.ResolutionFlags.IDLE_RELAY) self.sources = {} self.blacklist = ['grl-filesystem', 'grl-bookmarks', 'grl-metadata-store', 'grl-podcasts'] self.tracker = None self.changed_media_ids = [] self.pending_event_id = 0 self.changes_pending = {'Albums': False, 'Artists': False, 'Songs': False} self.pending_changed_medias = [] self.registry = Grl.Registry.get_default() self.sparqltracker = TrackerWrapper().tracker @log def _find_sources(self): self.registry.connect('source_added', self._on_source_added) self.registry.connect('source_removed', self._on_source_removed) try: self.registry.load_all_plugins(True) except GLib.GError: logger.error('Failed to load plugins.') if self.tracker is not None: logger.debug("tracker found") def _rate_limited_content_changed(self, mediaSource, changedMedias, changeType, locationUnknown): [self.pending_changed_medias.append(media) for media in changedMedias] if self.content_changed_timeout is None: self.content_changed_timeout = GLib.timeout_add( 500, self._on_content_changed, mediaSource, self.pending_changed_medias, changeType, locationUnknown) @log def _on_content_changed(self, mediaSource, changedMedias, changeType, locationUnknown): try: with self.tracker.handler_block(self.notification_handler): for media in changedMedias: media_id = media.get_id() if changeType == Grl.SourceChangeType.ADDED: # Check that this media is an audio file query = "select DISTINCT rdf:type nie:mimeType(?urn) as mime-type" +\ " { ?urn rdf:type nie:InformationElement . FILTER (tracker:id(?urn) = %s) }" % media_id mimeType = grilo.tracker.query_sync(query, [Grl.METADATA_KEY_MIME], grilo.options)[0].get_mime() if mimeType and mimeType.startswith("audio"): self.changed_media_ids.append(media_id) if changeType == Grl.SourceChangeType.REMOVED: # There is no way to check that removed item is a media # so always do the refresh # todo: remove one single url try: self.changed_media_ids.append(media.get_id()) except Exception as e: logger.warn("Skipping %s", media) if self.changed_media_ids == []: self.pending_changed_medias = [] if self.content_changed_timeout is not None: GLib.source_remove(self.content_changed_timeout) self.content_changed_timeout = None return False self.changed_media_ids = list(set(self.changed_media_ids)) logger.debug("Changed medias: %s", self.changed_media_ids) if len(self.changed_media_ids) >= self.CHANGED_MEDIA_MAX_ITEMS: self.emit_change_signal() elif self.changed_media_ids != []: if self.pending_event_id > 0: GLib.Source.remove(self.pending_event_id) self.pending_event_id = 0 self.pending_event_id = GLib.timeout_add(self.CHANGED_MEDIA_SIGNAL_TIMEOUT, self.emit_change_signal) except Exception as e: logger.warn("Exception in _on_content_changed: %s", e) finally: self.pending_changed_medias = [] if self.content_changed_timeout is not None: GLib.source_remove(self.content_changed_timeout) self.content_changed_timeout = None return False @log def emit_change_signal(self): self.changed_media_ids = [] self.pending_event_id = 0 self.changes_pending['Albums'] = True self.changes_pending['Artists'] = True self.changes_pending['Songs'] = True self.emit('changes-pending') return False @log def _on_source_added(self, pluginRegistry, mediaSource): if ("net:plaintext" in mediaSource.get_tags() or mediaSource.get_id() in self.blacklist): try: pluginRegistry.unregister_source(mediaSource) except GLib.GError: logger.error("Failed to unregister %s.", mediaSource.get_id()) return id = mediaSource.get_id() logger.debug("new grilo source %s was added", id) try: ops = mediaSource.supported_operations() if id == 'grl-tracker-source': if ops & Grl.SupportedOps.SEARCH: logger.debug("found searchable tracker source") self.sources[id] = mediaSource self.tracker = mediaSource self.search_source = mediaSource if self.tracker is not None: self.emit('ready') self.tracker.notify_change_start() self.content_changed_timeout = None self.notification_handler = self.tracker.connect( 'content-changed', self._rate_limited_content_changed) elif (id.startswith('grl-upnp')): logger.debug("found upnp source %s", id) self.sources[id] = mediaSource self.emit('new-source-added', mediaSource) elif (ops & Grl.SupportedOps.SEARCH and mediaSource.get_supported_media() & Grl.MediaType.AUDIO): logger.debug("source %s is searchable", id) self.sources[id] = mediaSource self.emit('new-source-added', mediaSource) except Exception as e: logger.debug("Source %s: exception %s", id, e) @log def _on_source_removed(self, pluginRegistry, mediaSource): pass @log def populate_artists(self, offset, callback, count=-1): self.populate_items(Query.all_artists(), offset, callback, count) @log def populate_albums(self, offset, callback, count=-1): self.populate_items(Query.all_albums(), offset, callback, count) @log def populate_songs(self, offset, callback, count=-1): self.populate_items(Query.all_songs(), offset, callback, count) @log def populate_playlists(self, offset, callback, count=-1): self.populate_items(Query.all_playlists(), offset, callback, count) @log def populate_album_songs(self, album, callback, count=-1): if album.get_source() == 'grl-tracker-source': self.populate_items(Query.album_songs(album.get_id()), 0, callback, count) else: source = self.sources[album.get_source()] length = len(album.tracks) for i, track in enumerate(album.tracks): callback(source, None, track, length - (i + 1), None) callback(source, None, None, 0, None) @log def populate_playlist_songs(self, playlist, callback, count=-1): self.populate_items(Query.playlist_songs(playlist.get_id()), 0, callback, count) @log def populate_custom_query(self, query, callback, count=-1, data=None): self.populate_items(query, 0, callback, count, data) @log def populate_items(self, query, offset, callback, count=-1, data=None): options = self.options.copy() options.set_skip(offset) if count != -1: options.set_count(count) def _callback(source, param, item, remaining, data, error): callback(source, param, item, remaining, data) self.tracker.query(query, self.METADATA_KEYS, options, _callback, data) @log def toggle_favorite(self, song_item): # TODO: change "bool(song_item.get_lyrics())" --> song_item.get_favourite() once query works properly # TODO: when .set/get_favourite work, set_favourite outside loop: item.set_favourite(!item.get_favourite()) if bool(song_item.get_lyrics()): # is favorite self.sparqltracker.update(Query.remove_favorite(song_item.get_url()), GLib.PRIORITY_DEFAULT, None) song_item.set_lyrics("") else: # not favorite self.sparqltracker.update(Query.add_favorite(song_item.get_url()), GLib.PRIORITY_DEFAULT, None) song_item.set_lyrics("i'm truthy") @log def search(self, q, callback, data=None): options = self.options.copy() self._search_callback_counter = 0 @log def _search_callback(source, param, item, remaining, data, error): callback(source, param, item, remaining, data) self._search_callback_counter += 1 @log def _multiple_search_callback(source, param, item, remaining, data, error): callback(source, param, item, remaining, data) if self.search_source: if self.search_source.get_id().startswith('grl-upnp'): options.set_type_filter(Grl.TypeFilter.AUDIO) self.search_source.search(q, self.METADATA_KEYS, options, _search_callback, data) else: Grl.multiple_search([self.sources[key] for key in self.sources if key != 'grl-tracker-source'], q, self.METADATA_KEYS, options, _multiple_search_callback, data) @log def get_album_art_for_item(self, item, callback): item_id = item.get_id() if item.is_audio(): query = Query.get_album_for_song_id(item_id) else: query = Query.get_album_for_album_id(item_id) options = self.full_options.copy() options.set_count(1) self.search_source.query(query, self.METADATA_THUMBNAIL_KEYS, options, callback) @log def get_playlist_with_id(self, playlist_id, callback): options = self.options.copy() query = Query.get_playlist_with_id(playlist_id) self.tracker.query(query, self.METADATA_KEYS, options, callback, None) @log def get_playlist_song_with_id(self, playlist_id, entry_id, callback): options = self.options.copy() query = Query.get_playlist_song_with_id(playlist_id, entry_id) self.tracker.query(query, self.METADATA_KEYS, options, callback, None)
class Grilo(GObject.GObject): __gsignals__ = { 'ready': (GObject.SIGNAL_RUN_FIRST, None, ()), 'changes-pending': (GObject.SIGNAL_RUN_FIRST, None, ()), 'new-source-added': (GObject.SIGNAL_RUN_FIRST, None, (Grl.Source, )) } METADATA_KEYS = [ Grl.METADATA_KEY_ID, Grl.METADATA_KEY_TITLE, Grl.METADATA_KEY_ARTIST, Grl.METADATA_KEY_ALBUM, Grl.METADATA_KEY_DURATION, Grl.METADATA_KEY_CREATION_DATE, Grl.METADATA_KEY_URL, Grl.METADATA_KEY_LYRICS, Grl.METADATA_KEY_THUMBNAIL] METADATA_THUMBNAIL_KEYS = [ Grl.METADATA_KEY_ID, Grl.METADATA_KEY_THUMBNAIL, ] CHANGED_MEDIA_MAX_ITEMS = 500 CHANGED_MEDIA_SIGNAL_TIMEOUT = 2000 @log def __init__(self): GObject.GObject.__init__(self) self.playlist_path = GLib.build_filenamev([GLib.get_user_data_dir(), "gnome-music", "playlists"]) if not (GLib.file_test(self.playlist_path, GLib.FileTest.IS_DIR)): GLib.mkdir_with_parents(self.playlist_path, int("0755", 8)) self.options = Grl.OperationOptions() self.options.set_flags(Grl.ResolutionFlags.FAST_ONLY | Grl.ResolutionFlags.IDLE_RELAY) self.full_options = Grl.OperationOptions() self.full_options.set_flags(Grl.ResolutionFlags.FULL | Grl.ResolutionFlags.IDLE_RELAY) self.sources = {} self.blacklist = ['grl-filesystem', 'grl-bookmarks', 'grl-metadata-store', 'grl-podcasts'] self.tracker = None self.changed_media_ids = [] self.pending_event_id = 0 self.changes_pending = {'Albums': False, 'Artists': False, 'Songs': False} self.registry = Grl.Registry.get_default() self.sparqltracker = TrackerWrapper().tracker @log def _find_sources(self): self.registry.connect('source_added', self._on_source_added) self.registry.connect('source_removed', self._on_source_removed) try: self.registry.load_all_plugins() except GLib.GError: logger.error('Failed to load plugins.') if self.tracker is not None: logger.debug("tracker found") @log def _on_content_changed(self, mediaSource, changedMedias, changeType, locationUnknown): try: for media in changedMedias: media_id = media.get_id() if changeType == Grl.SourceChangeType.ADDED: # Check that this media is an audio file query = "select DISTINCT rdf:type nie:mimeType(?urn) as mime-type" +\ " { ?urn rdf:type nie:InformationElement . FILTER (tracker:id(?urn) = %s) }" % media_id mimeType = grilo.tracker.query_sync(query, [Grl.METADATA_KEY_MIME], grilo.options)[0].get_mime() if mimeType and mimeType.startswith("audio"): self.changed_media_ids.append(media_id) if changeType == Grl.SourceChangeType.REMOVED: # There is no way to check that removed item is a media # so always do the refresh # todo: remove one single url self.changed_media_ids.append(media.get_id()) if len(self.changed_media_ids) >= self.CHANGED_MEDIA_MAX_ITEMS: self.emit_change_signal() elif self.changed_media_ids != []: if self.pending_event_id > 0: GLib.Source.remove(self.pending_event_id) self.pending_event_id = 0 self.pending_event_id = GLib.timeout_add(self.CHANGED_MEDIA_SIGNAL_TIMEOUT, self.emit_change_signal) except Exception as e: logger.warn("Exception in _on_content_changed: %s" % e) @log def emit_change_signal(self): self.changed_media_ids = [] self.pending_event_id = 0 self.changes_pending['Albums'] = True self.changes_pending['Artists'] = True self.changes_pending['Songs'] = True self.emit('changes-pending') return False @log def _on_source_added(self, pluginRegistry, mediaSource): id = mediaSource.get_id() logger.debug("new grilo source %s was added" % id) try: ops = mediaSource.supported_operations() if id == 'grl-tracker-source': if ops & Grl.SupportedOps.SEARCH: logger.debug("found searchable tracker source") self.sources[id] = mediaSource self.tracker = mediaSource self.search_source = mediaSource if self.tracker is not None: self.emit('ready') self.tracker.notify_change_start() self.tracker.connect('content-changed', self._on_content_changed) elif (id.startswith('grl-upnp')): logger.debug("found upnp source %s" % id) self.sources[id] = mediaSource self.emit('new-source-added', mediaSource) elif (id not in self.blacklist) and (ops & Grl.SupportedOps.SEARCH) and \ (mediaSource.get_supported_media() & Grl.MediaType.AUDIO): logger.debug("source %s is searchable" % id) self.sources[id] = mediaSource self.emit('new-source-added', mediaSource) except Exception as e: logger.debug("Source %s: exception %s" % (id, e)) @log def _on_source_removed(self, pluginRegistry, mediaSource): pass @log def populate_artists(self, offset, callback, count=-1): self.populate_items(Query.all_artists(), offset, callback, count) @log def populate_albums(self, offset, callback, count=-1): self.populate_items(Query.all_albums(), offset, callback, count) @log def populate_songs(self, offset, callback, count=-1): self.populate_items(Query.all_songs(), offset, callback, count) @log def populate_playlists(self, offset, callback, count=-1): self.populate_items(Query.all_playlists(), offset, callback, count) @log def populate_album_songs(self, album, callback, count=-1): if album.get_source() == 'grl-tracker-source': self.populate_items(Query.album_songs(album.get_id()), 0, callback, count) else: source = self.sources[album.get_source()] length = len(album.tracks) for i, track in enumerate(album.tracks): callback(source, None, track, length - (i + 1), None) callback(source, None, None, 0, None) @log def populate_playlist_songs(self, playlist, callback, count=-1): self.populate_items(Query.playlist_songs(playlist.get_id()), 0, callback, count) @log def populate_custom_query(self, query, callback, count=-1, data=None): self.populate_items(query, 0, callback, count, data) @log def populate_items(self, query, offset, callback, count=-1, data=None): options = self.options.copy() options.set_skip(offset) if count != -1: options.set_count(count) def _callback(source, param, item, remaining, data, error): callback(source, param, item, remaining, data) self.tracker.query(query, self.METADATA_KEYS, options, _callback, data) @log def toggle_favorite(self, song_item): # TODO: change "bool(song_item.get_lyrics())" --> song_item.get_favourite() once query works properly # TODO: when .set/get_favourite work, set_favourite outside loop: item.set_favourite(!item.get_favourite()) if bool(song_item.get_lyrics()): # is favorite self.sparqltracker.update(Query.remove_favorite(song_item.get_url()), GLib.PRIORITY_DEFAULT, None) song_item.set_lyrics("") else: # not favorite self.sparqltracker.update(Query.add_favorite(song_item.get_url()), GLib.PRIORITY_DEFAULT, None) song_item.set_lyrics("i'm truthy") @log def search(self, q, callback, data=None): options = self.options.copy() @log def _search_callback(source, param, item, remaining, data, error): callback(source, param, item, remaining, data) if self.search_source: if self.search_source.get_id().startswith('grl-upnp'): options.set_type_filter(Grl.TypeFilter.AUDIO) self.search_source.search(q, self.METADATA_KEYS, options, _search_callback, data) else: Grl.multiple_search([self.sources[key] for key in self.sources if key != 'grl-tracker-source'], q, self.METADATA_KEYS, options, _search_callback, data) @log def get_album_art_for_item(self, item, callback, data=None): item_id = item.get_id() query = None if isinstance(item, Grl.MediaAudio): query = Query.get_album_for_song_id(item_id) else: query = Query.get_album_for_album_id(item_id) options = self.full_options.copy() options.set_count(1) self.search_source.query(query, self.METADATA_THUMBNAIL_KEYS, options, callback, data) @log def get_playlist_with_id(self, playlist_id, callback): options = self.options.copy() query = Query.get_playlist_with_id(playlist_id) self.tracker.query(query, self.METADATA_KEYS, options, callback, None) @log def get_playlist_song_with_id(self, playlist_id, entry_id, callback): options = self.options.copy() query = Query.get_playlist_song_with_id(playlist_id, entry_id) self.tracker.query(query, self.METADATA_KEYS, options, callback, None)
class Playlists(GObject.GObject): __gsignals__ = { 'playlist-created': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, )), 'playlist-deleted': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, )), 'playlist-updated': (GObject.SignalFlags.RUN_FIRST, None, (int, )), 'song-added-to-playlist': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, Grl.Media)), 'song-removed-from-playlist': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, Grl.Media)), } instance = None tracker = None def __repr__(self): return '<Playlists>' @classmethod def get_default(cls, tracker=None): if cls.instance: return cls.instance else: cls.instance = Playlists() return cls.instance @log def __init__(self): GObject.GObject.__init__(self) self.tracker = TrackerWrapper().tracker self._static_playlists = StaticPlaylists() grilo.connect('ready', self._on_grilo_ready) @log def _on_grilo_ready(self, data=None): self.fetch_or_create_static_playlists() @log def fetch_or_create_static_playlists(self): """For all static playlists: get ID, if exists; if not, create the playlist and get ID.""" def callback(obj, result, playlist): cursor = obj.query_finish(result) while (cursor.next(None)): playlist.ID = cursor.get_integer(1) if not playlist.ID: # create the playlist playlist.ID = self.create_playlist_and_return_id( playlist.TITLE, playlist.TAG_TEXT) self.update_static_playlist(playlist) for playlist in self._static_playlists.get_all(): self.tracker.query_async( Query.get_playlist_with_tag(playlist.TAG_TEXT), None, callback, playlist) @log def update_playcount(self, song_url): query = Query.update_playcount(song_url) self.tracker.update(query, GLib.PRIORITY_LOW, None) @log def update_last_played(self, song_url): cur_time = time.strftime(sparql_dateTime_format, time.gmtime()) query = Query.update_last_played(song_url, cur_time) self.tracker.update(query, GLib.PRIORITY_LOW, None) @log def update_static_playlist(self, playlist): """Given a static playlist (subclass of StaticPlaylists), updates according to its query.""" # Clear the playlist self.clear_playlist(playlist) @log def clear_playlist(self, playlist): """Starts cleaning the playlist""" query = Query.clear_playlist_with_id(playlist.ID) self.tracker.update_async(query, GLib.PRIORITY_LOW, None, self._static_playlist_cleared_cb, playlist) @log def _static_playlist_cleared_cb(self, connection, res, playlist): """After clearing the playlist, start querying the playlist's songs""" # Get a list of matching songs self.tracker.query_async(playlist.QUERY, None, self._static_playlist_query_cb, playlist) @log def _static_playlist_query_cb(self, connection, res, playlist): """Fetch the playlist's songs""" final_query = '' # Get a list of matching songs try: cursor = self.tracker.query_finish(res) except GLib.Error as err: logger.warn("Error: %s, %s", err.__class__, err) return def callback(conn, res, final_query): uri = cursor.get_string(0)[0] final_query += Query.add_song_to_playlist(playlist.ID, uri) try: has_next = cursor.next_finish(res) except GLib.Error as err: logger.warn("Error: %s, %s", err.__class__, err) has_next = False # Only perform the update when the cursor reached the end if has_next: cursor.next_async(None, callback, final_query) return self.tracker.update_blank_async(final_query, GLib.PRIORITY_LOW, None, None, None) # tell system we updated the playlist so playlist is reloaded self.emit('playlist-updated', playlist.ID) # Asynchronously form the playlist's final query cursor.next_async(None, callback, final_query) @log def update_all_static_playlists(self): for playlist in self._static_playlists.get_all(): self.update_static_playlist(playlist) @log def create_playlist_and_return_id(self, title, tag_text): self.tracker.update_blank(Query.create_tag(tag_text), GLib.PRIORITY_LOW, None) data = self.tracker.update_blank( Query.create_playlist_with_tag(title, tag_text), GLib.PRIORITY_LOW, None) playlist_urn = data.get_child_value(0).get_child_value(0).\ get_child_value(0).get_child_value(1).get_string() cursor = self.tracker.query(Query.get_playlist_with_urn(playlist_urn), None) if not cursor or not cursor.next(): return return cursor.get_integer(0) @log def create_playlist(self, title): def get_callback(source, param, item, count, data, error): if item: self.emit('playlist-created', item) def query_callback(conn, res, data): cursor = conn.query_finish(res) if not cursor or not cursor.next(): return playlist_id = cursor.get_integer(0) grilo.get_playlist_with_id(playlist_id, get_callback) def update_callback(conn, res, data): playlist_urn = conn.update_blank_finish(res)[0][0]['playlist'] self.tracker.query_async(Query.get_playlist_with_urn(playlist_urn), None, query_callback, None) self.tracker.update_blank_async(Query.create_playlist(title), GLib.PRIORITY_LOW, None, update_callback, None) @log def delete_playlist(self, item): def update_callback(conn, res, data): conn.update_finish(res) self.emit('playlist-deleted', item) self.tracker.update_async(Query.delete_playlist(item.get_id()), GLib.PRIORITY_LOW, None, update_callback, None) @log def add_to_playlist(self, playlist, items): def get_callback(source, param, item, count, data, error): if item: self.emit('song-added-to-playlist', playlist, item) def query_callback(conn, res, data): cursor = conn.query_finish(res) if not cursor or not cursor.next(): return entry_id = cursor.get_integer(0) grilo.get_playlist_song_with_id(playlist.get_id(), entry_id, get_callback) def update_callback(conn, res, data): entry_urn = conn.update_blank_finish(res)[0][0]['entry'] self.tracker.query_async( Query.get_playlist_song_with_urn(entry_urn), None, query_callback, None) for item in items: uri = item.get_url() if not uri: continue self.tracker.update_blank_async( Query.add_song_to_playlist(playlist.get_id(), uri), GLib.PRIORITY_LOW, None, update_callback, None) @log def remove_from_playlist(self, playlist, items): def update_callback(conn, res, data): conn.update_finish(res) self.emit('song-removed-from-playlist', playlist, data) for item in items: self.tracker.update_async( Query.remove_song_from_playlist(playlist.get_id(), item.get_id()), GLib.PRIORITY_LOW, None, update_callback, item) @log def is_static_playlist(self, playlist): """Checks whether the given playlist is static or not :return: True if the playlist is static :rtype: bool """ for static_playlist_id in self._static_playlists.get_ids(): if playlist.get_id() == static_playlist_id: return True return False
class Playlists(GObject.GObject): __gsignals__ = { 'playlist-created': (GObject.SIGNAL_RUN_FIRST, None, (Grl.Media,)), 'playlist-deleted': (GObject.SIGNAL_RUN_FIRST, None, (Grl.Media,)), 'playlist-updated': (GObject.SIGNAL_RUN_FIRST, None, (int,)), 'song-added-to-playlist': ( GObject.SIGNAL_RUN_FIRST, None, (Grl.Media, Grl.Media) ), 'song-removed-from-playlist': ( GObject.SIGNAL_RUN_FIRST, None, (Grl.Media, Grl.Media) ), } instance = None tracker = None @classmethod def get_default(self, tracker=None): if self.instance: return self.instance else: self.instance = Playlists() return self.instance @log def __init__(self): GObject.GObject.__init__(self) self.tracker = TrackerWrapper().tracker @log def fetch_or_create_static_playlists(self): """For all static playlists: get ID, if exists; if not, create the playlist and get ID.""" playlists = [cls for name, cls in inspect.getmembers(StaticPlaylists) if inspect.isclass(cls) and not (name == "__class__")] # hacky def callback(obj, result, playlist): cursor = obj.query_finish(result) while (cursor.next(None)): playlist.ID = cursor.get_integer(1) if not playlist.ID: # create the playlist playlist.ID = self.create_playlist_and_return_id(playlist.TITLE, playlist.TAG_TEXT) self.update_static_playlist(playlist) for playlist in playlists: self.tracker.query_async( Query.get_playlist_with_tag(playlist.TAG_TEXT), None, callback, playlist) @log def clear_playlist_with_id(self, playlist_id): query = Query.clear_playlist_with_id(playlist_id) self.tracker.update(query, GLib.PRIORITY_DEFAULT, None) @log def update_playcount(self, song_url): query = Query.update_playcount(song_url) self.tracker.update(query, GLib.PRIORITY_DEFAULT, None) @log def update_last_played(self, song_url): cur_time = time.strftime(sparql_dateTime_format, time.gmtime()) query = Query.update_last_played(song_url, cur_time) self.tracker.update(query, GLib.PRIORITY_DEFAULT, None) @log def update_static_playlist(self, playlist): """Given a static playlist (subclass of StaticPlaylists), updates according to its query.""" # Clear the playlist self.clear_playlist_with_id(playlist.ID) final_query = '' # Get a list of matching songs cursor = self.tracker.query(playlist.QUERY, None) if not cursor: return # For each song run 'add song to playlist' while cursor.next(): uri = cursor.get_string(0)[0] final_query += Query.add_song_to_playlist(playlist.ID, uri) self.tracker.update_blank_async(final_query, GLib.PRIORITY_DEFAULT, None, None, None) # tell system we updated the playlist so playlist is reloaded self.emit('playlist-updated', playlist.ID) @log def update_all_static_playlists(self): playlists = [cls for name, cls in inspect.getmembers(StaticPlaylists) if inspect.isclass(cls) and not (name == "__class__")] # hacky for playlist in playlists: self.update_static_playlist(playlist) @log def create_playlist_and_return_id(self, title, tag_text): self.tracker.update_blank(Query.create_tag(tag_text), GLib.PRIORITY_DEFAULT, None) data = self.tracker.update_blank( Query.create_playlist_with_tag(title, tag_text), GLib.PRIORITY_DEFAULT, None) playlist_urn = data.get_child_value(0).get_child_value(0).\ get_child_value(0).get_child_value(1).get_string() cursor = self.tracker.query( Query.get_playlist_with_urn(playlist_urn), None) if not cursor or not cursor.next(): return return cursor.get_integer(0) @log def create_playlist(self, title): def get_callback(source, param, item, count, data, error): if item: self.emit('playlist-created', item) def query_callback(conn, res, data): cursor = conn.query_finish(res) if not cursor or not cursor.next(): return playlist_id = cursor.get_integer(0) grilo.get_playlist_with_id(playlist_id, get_callback) def update_callback(conn, res, data): playlist_urn = conn.update_blank_finish(res)[0][0]['playlist'] self.tracker.query_async( Query.get_playlist_with_urn(playlist_urn), None, query_callback, None ) self.tracker.update_blank_async( Query.create_playlist(title), GLib.PRIORITY_DEFAULT, None, update_callback, None ) @log def delete_playlist(self, item): def update_callback(conn, res, data): conn.update_finish(res) self.emit('playlist-deleted', item) self.tracker.update_async( Query.delete_playlist(item.get_id()), GLib.PRIORITY_DEFAULT, None, update_callback, None ) @log def add_to_playlist(self, playlist, items): def get_callback(source, param, item, count, data, error): if item: self.emit('song-added-to-playlist', playlist, item) def query_callback(conn, res, data): cursor = conn.query_finish(res) if not cursor or not cursor.next(): return entry_id = cursor.get_integer(0) grilo.get_playlist_song_with_id( playlist.get_id(), entry_id, get_callback ) def update_callback(conn, res, data): entry_urn = conn.update_blank_finish(res)[0][0]['entry'] self.tracker.query_async( Query.get_playlist_song_with_urn(entry_urn), None, query_callback, None ) for item in items: uri = item.get_url() if not uri: continue self.tracker.update_blank_async( Query.add_song_to_playlist(playlist.get_id(), uri), GLib.PRIORITY_DEFAULT, None, update_callback, None ) @log def remove_from_playlist(self, playlist, items): def update_callback(conn, res, data): conn.update_finish(res) self.emit('song-removed-from-playlist', playlist, data) for item in items: self.tracker.update_async( Query.remove_song_from_playlist( playlist.get_id(), item.get_id() ), GLib.PRIORITY_DEFAULT, None, update_callback, item )
class Playlists(GObject.GObject): __gsignals__ = { 'playlist-created': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media,)), 'playlist-deleted': (GObject.SignalFlags.RUN_FIRST, None, (Grl.Media,)), 'playlist-updated': (GObject.SignalFlags.RUN_FIRST, None, (int,)), 'song-added-to-playlist': ( GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, Grl.Media) ), 'song-removed-from-playlist': ( GObject.SignalFlags.RUN_FIRST, None, (Grl.Media, Grl.Media) ), } instance = None tracker = None def __repr__(self): return '<Playlists>' @classmethod def get_default(cls, tracker=None): if cls.instance: return cls.instance else: cls.instance = Playlists() return cls.instance @log def __init__(self): GObject.GObject.__init__(self) self.tracker = TrackerWrapper().tracker StaticPlaylists() @log def fetch_or_create_static_playlists(self): """For all static playlists: get ID, if exists; if not, create the playlist and get ID.""" playlists = [cls for name, cls in inspect.getmembers(StaticPlaylists) if inspect.isclass(cls) and not (name == "__class__")] # hacky def callback(obj, result, playlist): cursor = obj.query_finish(result) while (cursor.next(None)): playlist.ID = cursor.get_integer(1) if not playlist.ID: # create the playlist playlist.ID = self.create_playlist_and_return_id(playlist.TITLE, playlist.TAG_TEXT) self.update_static_playlist(playlist) for playlist in playlists: self.tracker.query_async( Query.get_playlist_with_tag(playlist.TAG_TEXT), None, callback, playlist) @log def clear_playlist_with_id(self, playlist_id): query = Query.clear_playlist_with_id(playlist_id) self.tracker.update(query, GLib.PRIORITY_LOW, None) @log def update_playcount(self, song_url): query = Query.update_playcount(song_url) self.tracker.update(query, GLib.PRIORITY_LOW, None) @log def update_last_played(self, song_url): cur_time = time.strftime(sparql_dateTime_format, time.gmtime()) query = Query.update_last_played(song_url, cur_time) self.tracker.update(query, GLib.PRIORITY_LOW, None) @log def update_static_playlist(self, playlist): """Given a static playlist (subclass of StaticPlaylists), updates according to its query.""" # Clear the playlist self.clear_playlist_with_id(playlist.ID) final_query = '' # Get a list of matching songs cursor = self.tracker.query(playlist.QUERY, None) if not cursor: return # For each song run 'add song to playlist' while cursor.next(): uri = cursor.get_string(0)[0] final_query += Query.add_song_to_playlist(playlist.ID, uri) self.tracker.update_blank_async(final_query, GLib.PRIORITY_LOW, None, None, None) # tell system we updated the playlist so playlist is reloaded self.emit('playlist-updated', playlist.ID) @log def update_all_static_playlists(self): playlists = [cls for name, cls in inspect.getmembers(StaticPlaylists) if inspect.isclass(cls) and not (name == "__class__")] # hacky for playlist in playlists: self.update_static_playlist(playlist) @log def create_playlist_and_return_id(self, title, tag_text): self.tracker.update_blank(Query.create_tag(tag_text), GLib.PRIORITY_LOW, None) data = self.tracker.update_blank( Query.create_playlist_with_tag(title, tag_text), GLib.PRIORITY_LOW, None) playlist_urn = data.get_child_value(0).get_child_value(0).\ get_child_value(0).get_child_value(1).get_string() cursor = self.tracker.query( Query.get_playlist_with_urn(playlist_urn), None) if not cursor or not cursor.next(): return return cursor.get_integer(0) @log def create_playlist(self, title): def get_callback(source, param, item, count, data, error): if item: self.emit('playlist-created', item) def query_callback(conn, res, data): cursor = conn.query_finish(res) if not cursor or not cursor.next(): return playlist_id = cursor.get_integer(0) grilo.get_playlist_with_id(playlist_id, get_callback) def update_callback(conn, res, data): playlist_urn = conn.update_blank_finish(res)[0][0]['playlist'] self.tracker.query_async( Query.get_playlist_with_urn(playlist_urn), None, query_callback, None ) self.tracker.update_blank_async( Query.create_playlist(title), GLib.PRIORITY_LOW, None, update_callback, None ) @log def delete_playlist(self, item): def update_callback(conn, res, data): conn.update_finish(res) self.emit('playlist-deleted', item) self.tracker.update_async( Query.delete_playlist(item.get_id()), GLib.PRIORITY_LOW, None, update_callback, None ) @log def add_to_playlist(self, playlist, items): def get_callback(source, param, item, count, data, error): if item: self.emit('song-added-to-playlist', playlist, item) def query_callback(conn, res, data): cursor = conn.query_finish(res) if not cursor or not cursor.next(): return entry_id = cursor.get_integer(0) grilo.get_playlist_song_with_id( playlist.get_id(), entry_id, get_callback ) def update_callback(conn, res, data): entry_urn = conn.update_blank_finish(res)[0][0]['entry'] self.tracker.query_async( Query.get_playlist_song_with_urn(entry_urn), None, query_callback, None ) for item in items: uri = item.get_url() if not uri: continue self.tracker.update_blank_async( Query.add_song_to_playlist(playlist.get_id(), uri), GLib.PRIORITY_LOW, None, update_callback, None ) @log def remove_from_playlist(self, playlist, items): def update_callback(conn, res, data): conn.update_finish(res) self.emit('song-removed-from-playlist', playlist, data) for item in items: self.tracker.update_async( Query.remove_song_from_playlist( playlist.get_id(), item.get_id() ), GLib.PRIORITY_LOW, None, update_callback, item )