diff -Nru unity-scope-clementine-0.1daily13.04.10ubuntu.unity.experimental.certified/data/clementine.scope.in unity-scope-clementine-0.1daily13.05.07ubuntu.unity.experimental.certified/data/clementine.scope.in --- unity-scope-clementine-0.1daily13.04.10ubuntu.unity.experimental.certified/data/clementine.scope.in 2013-04-10 07:21:26.000000000 +0000 +++ unity-scope-clementine-0.1daily13.05.07ubuntu.unity.experimental.certified/data/clementine.scope.in 2013-05-07 07:22:12.000000000 +0000 @@ -1,16 +1,16 @@ [Scope] DBusName=com.canonical.Unity.Scope.Music.Clementine DBusPath=/com/canonical/unity/scope/music/clementine -Icon= +Icon=/usr/share/unity/icons/lens-nav-music.svg QueryBinary=clementine _Keywords=clementine; RequiredMetadata= -OptionalMetadata= +OptionalMetadata=album[s];artist[s];genre[s];year[i];track_length[i];track_number[i] Loader=/usr/share/unity-scopes/clementine/unity_clementine_daemon.py RemoteContent=false Type=music _Name=Clementine -_Description=Find Clementine items +_Description=This is an Ubuntu search plugin that enables information from Clementine to be searched and displayed in the Dash underneath the Music header. If you do not wish to search this content source, you can disable this search plugin. _SearchHint=Search Clementine [Desktop Entry] diff -Nru unity-scope-clementine-0.1daily13.04.10ubuntu.unity.experimental.certified/debian/changelog unity-scope-clementine-0.1daily13.05.07ubuntu.unity.experimental.certified/debian/changelog --- unity-scope-clementine-0.1daily13.04.10ubuntu.unity.experimental.certified/debian/changelog 2013-05-07 16:48:24.000000000 +0000 +++ unity-scope-clementine-0.1daily13.05.07ubuntu.unity.experimental.certified/debian/changelog 2013-05-07 16:48:25.000000000 +0000 @@ -1,3 +1,10 @@ +unity-scope-clementine (0.1daily13.05.07ubuntu.unity.experimental.certified-0ubuntu1) raring; urgency=low + + * Automatic snapshot from revision 24 (ubuntu-unity/experimental- + certified) + + -- Ubuntu daily release Tue, 07 May 2013 07:22:36 +0000 + unity-scope-clementine (0.1daily13.04.10ubuntu.unity.experimental.certified-0ubuntu1) raring; urgency=low [ Mark Tully ] diff -Nru unity-scope-clementine-0.1daily13.04.10ubuntu.unity.experimental.certified/src/unity_clementine_daemon.py unity-scope-clementine-0.1daily13.05.07ubuntu.unity.experimental.certified/src/unity_clementine_daemon.py --- unity-scope-clementine-0.1daily13.04.10ubuntu.unity.experimental.certified/src/unity_clementine_daemon.py 2013-04-10 07:21:26.000000000 +0000 +++ unity-scope-clementine-0.1daily13.05.07ubuntu.unity.experimental.certified/src/unity_clementine_daemon.py 2013-05-07 07:22:12.000000000 +0000 @@ -18,14 +18,11 @@ from gi.repository import Unity import gettext import urllib.parse -import dbus import hashlib import unicodedata import os -import sys import shutil import sqlite3 -import dbus APP_NAME = 'unity-scope-clementine' LOCAL_PATH = '/usr/share/locale/' @@ -45,7 +42,7 @@ DEFAULT_RESULT_MIMETYPE = 'taglib/mp3' DEFAULT_RESULT_TYPE = Unity.ResultType.PERSONAL CLEMENTINE_DBFILE = os.getenv("HOME") + "/.config/Clementine/clementine.db" -CLEMENTIME_BACKUP_FILE = os.getenv("HOME") + "/.config/Clementine/clementine-scope-backup.db" +CLEMENTINE_BACKUP_FILE = os.getenv("HOME") + "/.config/Clementine/clementine-scope-backup.db" c1 = {'id': 'songs', 'name': _('Songs'), @@ -59,66 +56,64 @@ FILTERS = [] -m1 = {'id' :'album', - 'type' :'s', - 'field':Unity.SchemaFieldType.OPTIONAL} -m2 = {'id' :'artist', - 'type' :'s', - 'field':Unity.SchemaFieldType.OPTIONAL} -m3 = {'id' :'genre', - 'type' :'s', - 'field':Unity.SchemaFieldType.OPTIONAL} -m4 = {'id' :'year', - 'type' :'i', - 'field':Unity.SchemaFieldType.OPTIONAL} -EXTRA_METADATA = [m1, m2, m3, m4] - -REFRESH_TIMEOUT = 300 -PREVIEW_PLAYER_DBUS_NAME = "com.canonical.Unity.Lens.Music.PreviewPlayer" -PREVIEW_PLAYER_DBUS_PATH = "/com/canonical/Unity/Lens/Music/PreviewPlayer" -PREVIEW_PLAYER_DBUS_IFACE = PREVIEW_PLAYER_DBUS_NAME +m1 = {'id': 'album', + 'type': 's', + 'field': Unity.SchemaFieldType.OPTIONAL} +m2 = {'id': 'artist', + 'type': 's', + 'field': Unity.SchemaFieldType.OPTIONAL} +m3 = {'id': 'genre', + 'type': 's', + 'field': Unity.SchemaFieldType.OPTIONAL} +m4 = {'id': 'year', + 'type': 'i', + 'field': Unity.SchemaFieldType.OPTIONAL} +m5 = {'id': 'track_length', + 'type': 'i', + 'field': Unity.SchemaFieldType.OPTIONAL} +m6 = {'id': 'track_number', + 'type': 'i', + 'field': Unity.SchemaFieldType.OPTIONAL} +EXTRA_METADATA = [m1, m2, m3, m4, m5, m6] + +SEARCH_SQL = '''SELECT title, filename, artist, album, albumartist, art_automatic, year, genre, art_manual, track, length + FROM songs + WHERE album LIKE '%%%s%%' OR artist LIKE '%%%s%%' OR title LIKE '%%%s%%' ORDER BY track''' + +ALBUM_SQL = '''SELECT title, filename, artist, album, albumartist, art_automatic, year, genre, art_manual, track, length + FROM songs + WHERE album LIKE '%%%s%%' AND artist LIKE '%%%s%%' ORDER BY track''' -tracks = [] - -def get_music_from_clementine(): +def get_music_from_clementine(query): ''' - Parses Clementine's database into a form we can use + Parses Clementine's database into a form we can use using the supplied SQL query ''' - # Copy clementine's database to a backup so we can run searches on that rather than the main database tracks = [] - if not os.path.exists(CLEMENTINE_DBFILE): + if not os.path.exists(CLEMENTINE_BACKUP_FILE): return tracks - shutil.copy2(CLEMENTINE_DBFILE, CLEMENTIME_BACKUP_FILE) - - # Grab all the data we need from the backup - conn = sqlite3.connect(CLEMENTIME_BACKUP_FILE) + conn = sqlite3.connect(CLEMENTINE_BACKUP_FILE) cursor = conn.cursor() - # Go through the safe and grab track names, their uris, the artist name, the album title and the track's mimetypes - # We'll have to call them all mp3s as the mimetype isn't explicitly in the database, but as long as it's an audio mimetype, it shouldn't matter - cursor.execute('''SELECT title, filename, artist, album, "taglib/mp3", albumartist, art_automatic, year, genre, art_manual, track, length - FROM songs - ORDER BY track''') + cursor.execute(query) tracks = cursor.fetchall() cursor.close() - print("Updated tracks from Clementine database") return tracks def get_album_art(track): # First try manually set album art - if not track[9] is None: - if not track[9] == "(embedded)": - return track[9] + if not track[8] is None: + if not track[8] == "(embedded)": + return track[8] # Next try automatically set album art - if not track[6] is None: - if not track[6] == "(embedded)": - return track[6] + if not track[5] is None: + if not track[5] == "(embedded)": + return track[5] # Try thumbnailing any embedded album art and use that - hashname = '%s\t%s' % (unicodedata.normalize("NFKD", track[5]), unicodedata.normalize("NFKD", track[3])) + hashname = '%s\t%s' % (unicodedata.normalize("NFKD", track[4]), unicodedata.normalize("NFKD", track[3])) file_hash = hashlib.md5(hashname.encode('utf-8')).hexdigest() tb_filename = os.path.join(os.path.expanduser("~/.cache/media-art"), ("album-" + file_hash)) + ".jpg" if os.path.exists(tb_filename): @@ -149,171 +144,81 @@ Search for help documents matching the search string ''' results = [] - tracks = get_music_from_clementine() - trackresults = [] + shutil.copy2(CLEMENTINE_DBFILE, CLEMENTINE_BACKUP_FILE) + tracks = get_music_from_clementine(SEARCH_SQL % (search, search, search)) albumresults = [] for track in tracks: - title = u"" if track[0] is None else track[0] - uri = u"" if track[1] is None else track[1] - artist = u"" if track[2] is None else track[2] - album = u"" if track[3] is None else track[3] - mimetype = u"" if track[4] is None else track[4] - albumartist = u"" if track[5] is None else track[5] - year = 0 if track[7] is None else track[7] - genre = u"" if track[8] is None else track[8] - trackname = title + u" - " + album + u" - " + artist - if search.lower() in trackname.lower(): - albumart = get_album_art(track) - albumuri = "album://" + albumartist + "/" + album - if track not in trackresults: - results.append({'uri': uri.decode('utf-8'), - 'icon': albumart, - 'category': 0, - 'mimetype': mimetype, - 'title': title, - 'comment': artist, - 'album':GLib.Variant('s', album), - 'artist':GLib.Variant('s', artist), - 'genre':GLib.Variant('s', genre), - 'year':GLib.Variant('i', year)}) - trackresults.append(track) - - if album not in albumresults: - results.append({'uri': albumuri, - 'icon': albumart, - 'category': 1, - 'mimetype': mimetype, - 'title': album, - 'comment': artist, - 'album':GLib.Variant('s', album), - 'artist':GLib.Variant('s', artist), - 'genre':GLib.Variant('s', genre), - 'year':GLib.Variant('i', year)}) - albumresults.append(album) + title = "" if track[0] is None else track[0] + uri = "" if track[1] is None else track[1].decode('utf-8') + artist = "" if track[2] is None else track[2] + album = "" if track[3] is None else track[3] + albumartist = "" if track[4] is None else track[4] + year = 0 if track[6] is None else track[6] + genre = "" if track[7] is None else track[7] + track_length = 0 if track[10] is None else track[10] / 1000000000 + track_number = 0 if track[9] is None else track[9] + + albumart = get_album_art(track) + albumuri = "album://" + albumartist + "/" + album + results.append({'uri': urllib.parse.unquote(uri), + 'icon': albumart, + 'category': 0, + 'title': title, + 'album': GLib.Variant('s', album), + 'artist': GLib.Variant('s', artist), + 'genre': GLib.Variant('s', genre), + 'year': GLib.Variant('i', year), + 'track_length': GLib.Variant('i', track_length), + 'track_number': GLib.Variant('i', track_number)}) + + if album not in albumresults: + results.append({'uri': albumuri, + 'icon': albumart, + 'category': 1, + 'title': album, + 'album': GLib.Variant('s', album), + 'artist': GLib.Variant('s', artist), + 'genre': GLib.Variant('s', genre), + 'year': GLib.Variant('i', year)}) + albumresults.append(album) return results -def activate(scope, uri): - import subprocess - albumtracks = [] - albumtracks.append("clementine") - albumtracks.append("-a") - # If uri starts with album:// then we need to play all the songs on it - if uri.startswith("album://"): - for track in tracks: - album = "album://" + track[5] + "/" + track[3] - if not album.find(uri) == -1: - albumtrack = urllib.parse.unquote(str(track[1])) - albumtracks.append(albumtrack) - subprocess.Popen(albumtracks) - else: - albumtracks.append(uri) - subprocess.Popen(albumtracks) - return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH, goto_uri='') - - -def show_in_folder(scope, uri): - """ Shows the folder containing the selected track as requested from the Preview - """ - if uri.startswith("album://"): - for track in tracks: - album = "album://" + track[2] + "/" + track[3] - if not album.find(uri) == -1: - filename = track[1] - continue - else: - filename = uri - dirname = os.path.dirname(filename) - dirname = dirname.replace("%20", "\ ") - os.system("xdg-open '%s'" % str(dirname)) - return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH, goto_uri='') - - -def preview_uri(scope, uri): - """Preview request handler""" - albumtracks = [] - isalbum = False - if uri.startswith("album://"): - isalbum = True - for track in tracks: - album = "album://" + track[2] + "/" + track[3] - if not album.find(uri) == -1: - albumtracks.append(track) - albumtracks.sort(key=lambda track: int(track[7])) - else: - for track in tracks: - album = "file://" + track[1] - if not album.find(uri) == -1: - albumtracks.append(track) - iteration = model.get_first_iter() - end_iter = model.get_last_iter() - while iteration != end_iter: - if model.get_value(iteration, 0) == uri: - title = model.get_value(iteration, 5) - description = model.get_value(iteration, 6) - if model.get_value(iteration, 1) == "musique": - image = "file:///usr/share/icons/hicolor/scalable/apps/audacious.svg" - else: - image = "file://%s" % model.get_value(iteration, 1) +class Preview(Unity.ResultPreviewer): - preview = Unity.MusicPreview.new(title, description, None) - preview.props.image_source_uri = image - for albumtrack in albumtracks: - if isalbum: - track = Unity.TrackMetadata.full("file://" + urllib.parse.unquote(str(albumtrack[1])), # uri - int(albumtrack[7]), # track number - albumtrack[0], # track title - albumtrack[2], # artist - albumtrack[3], # album - int(albumtrack[8]) / 1000) # track length - else: - preview = Unity.MusicPreview.new(albumtrack[0], "", None) - preview.props.image_source_uri = image - track = Unity.TrackMetadata.full("file://" + urllib.parse.unquote(str(albumtrack[1])), - int(albumtrack[7]), - albumtrack[0], - albumtrack[2], - albumtrack[3], - int(albumtrack[8]) / 1000) + def do_run(self): + album = self.result.metadata['album'].get_string() + artist = self.result.metadata['artist'].get_string() + preview = Unity.MusicPreview.new(self.result.title, '', None) + preview.props.image_source_uri = 'file://%s' % self.result.icon_hint + preview.props.subtitle = self.result.metadata['artist'].get_string() + if self.result.uri.startswith("album://"): + tracks = get_music_from_clementine(ALBUM_SQL % (album, artist)) + for track in tracks: + track = Unity.TrackMetadata.full(track[1].decode('utf-8'), + track[9], + track[0], + track[2], + track[3], + track[10] / 1000000000) preview.add_track(track) + else: + track = Unity.TrackMetadata.full(self.result.uri, + self.result.metadata['track_number'].get_int32(), + self.result.title, + self.result.metadata['artist'].get_string(), + self.result.metadata['album'].get_string(), + self.result.metadata['track_length'].get_int32()) + preview.add_track(track) + + icon = Gio.FileIcon.new(Gio.file_new_for_path(PROVIDER_ICON)) + view_action = Unity.PreviewAction.new("play", _("Play"), None) + preview.add_action(view_action) + show_action = Unity.PreviewAction.new("show", _("Show in Folder"), None) + preview.add_action(show_action) + return preview - # Add the "Play" action - play_action = Unity.PreviewAction.new("activate_uri", "Play", None) - play_action.connect("activated", activate) - preview.add_action(play_action) - - # Add the "Show in folder" action - show_action = Unity.PreviewAction.new("show_in_folder", "Show In Folder", None) - show_action.connect("activated", show_in_folder) - preview.add_action(show_action) - - preview.connect("play", play) - preview.connect("pause", pause) - preview.connect("closed", closed) - break - iteration = model.next(iteration) - if preview is None: - print("Couldn't find model row for requested preview uri: '%s'", uri) - return preview - - -def play(preview, uri): - """Plays the selected track as selected in the Preview""" - player = self.bus.get_object(PREVIEW_PLAYER_DBUS_NAME, PREVIEW_PLAYER_DBUS_PATH) - dbus.Interface(player, PREVIEW_PLAYER_DBUS_IFACE).Play(uri) - - -def pause(preview, uri): - """Pauses the selected track as selected in the Preview""" - player = self.bus.get_object(PREVIEW_PLAYER_DBUS_NAME, PREVIEW_PLAYER_DBUS_PATH) - dbus.Interface(player, PREVIEW_PLAYER_DBUS_IFACE).Pause() - - -def closed(preview): - """Stops playing when the previre is closed""" - player = self.bus.get_object(PREVIEW_PLAYER_DBUS_NAME, PREVIEW_PLAYER_DBUS_PATH) - dbus.Interface(player, PREVIEW_PLAYER_DBUS_IFACE).Close() # Classes below this point establish communication # with Unity, you probably shouldn't modify them. @@ -348,19 +253,8 @@ i['comment'] = '' if not 'dnd_uri' in i or not i['dnd_uri'] or i['dnd_uri'] == '': i['dnd_uri'] = i['uri'] - i['metadata'] = {} - if EXTRA_METADATA: - for e in i: - for m in EXTRA_METADATA: - if m['id'] == e: - i['metadata'][e] = i[e] - i['metadata']['provider_credits'] = GLib.Variant('s', PROVIDER_CREDITS) - result = Unity.ScopeResult.create(str(i['uri']), str(i['icon']), - i['category'], i['result_type'], - str(i['mimetype']), str(i['title']), - str(i['comment']), str(i['dnd_uri']), - i['metadata']) - result_set.add_result(result) + i['provider_credits'] = GLib.Variant('s', PROVIDER_CREDITS) + result_set.add_result(**i) except Exception as error: print(error) @@ -416,6 +310,36 @@ se = MySearch(search_context) return se + def do_activate(self, result, metadata, id): + album = result.metadata['album'].get_string() + artist = result.metadata['artist'].get_string() + + if id == 'show': + if result.uri.startswith("album://"): + tracks = get_music_from_clementine(ALBUM_SQL % (album, artist)) + filename = tracks[0][1].decode('utf-8') + else: + filename = result.uri + dirname = os.path.dirname(filename) + os.system("xdg-open '%s'" % str(dirname)) + else: + albumtracks = '' + if result.uri.startswith('album://'): + tracks = get_music_from_clementine(ALBUM_SQL % (album, artist)) + for track in tracks: + albumtracks = '%s \'%s\'' % (albumtracks, track[1].decode('utf-8')) + else: + albumtracks = result.uri + os.system('clementine -a %s' % albumtracks) + + return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH, goto_uri=None) + + def do_create_previewer(self, result, metadata): + rp = Preview() + rp.set_scope_result(result) + rp.set_search_metadata(metadata) + return rp + def load_scope(): return Scope() diff -Nru unity-scope-clementine-0.1daily13.04.10ubuntu.unity.experimental.certified/tests/test_clementine.py unity-scope-clementine-0.1daily13.05.07ubuntu.unity.experimental.certified/tests/test_clementine.py --- unity-scope-clementine-0.1daily13.04.10ubuntu.unity.experimental.certified/tests/test_clementine.py 2013-04-10 07:21:26.000000000 +0000 +++ unity-scope-clementine-0.1daily13.05.07ubuntu.unity.experimental.certified/tests/test_clementine.py 2013-05-07 07:22:12.000000000 +0000 @@ -11,17 +11,18 @@ self.results = [] def do_add_result(self, result): - self.results.append({'uri':result.uri, - 'title':result.title, - 'comment':result.comment, - 'icon':result.icon_hint}) + self.results.append({'uri': result.uri, + 'title': result.title, + 'comment': result.comment, + 'icon': result.icon_hint}) + class ScopeTestCase(TestCase): def init_scope(self, scope_path): self.scope_module = imp.load_source('scope', scope_path) self.scope = self.scope_module.load_scope() - def perform_query(self, query, filter_set = Unity.FilterSet.new()): + def perform_query(self, query, filter_set=Unity.FilterSet.new()): result_set = ResultSet() ctx = Unity.SearchContext.create(query, 0, filter_set, None, result_set, None) @@ -30,7 +31,7 @@ return result_set -class TestAskUbuntu(ScopeTestCase): +class TestClementine(ScopeTestCase): def setUp(self): self.init_scope('src/unity_clementine_daemon.py') @@ -38,10 +39,10 @@ self.scope = None self.scope_module = None - def test_questions_search(self): + def test_search(self): self.scope_module.CLEMENTINE_DBFILE = 'tests/data/mock_clementine_pass.db' - self.scope_module.CLEMENTIME_BACKUP_FILE = 'tests/data/mock_clementine_backup.db' - expected_results = ["file:///home/mark/Music/Bell%20X1/Flock/01.%20Reacharound.mp3", + self.scope_module.CLEMENTINE_BACKUP_FILE = 'tests/data/mock_clementine_backup.db' + expected_results = ["file:///home/mark/Music/Bell X1/Flock/01. Reacharound.mp3", "Reacharound", "album://Bell X1/Flock", "Flock"] @@ -54,10 +55,9 @@ results.append(result_set.results[1]['title']) self.assertEqual(results, expected_results) - - def test_questions_failing_search(self): + def test_failing_search(self): self.scope_module.CLEMENTINE_DBFILE = 'tests/data/mock_clementine_fail' - self.scope_module.CLEMENTIME_BACKUP_FILE = 'tests/data/mock_clementine_backup.db' + self.scope_module.CLEMENTINE_BACKUP_FILE = 'tests/data/mock_clementine_backup.db' for s in ['upnriitnyt']: result_set = self.perform_query(s) self.assertEqual(len(result_set.results), 0)