2
* Copyright (C) 2011 Canonical Ltd
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 3 as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authored by Alex Launi <alex.launi@canonical.com>
22
namespace Unity.MusicLens {
24
public class RhythmboxScope : SimpleScope
26
private RhythmboxCollection collection;
27
private bool db_ready;
28
private FileMonitor rb_xml_monitor;
29
private PreviewPlayer preview_player;
30
private Unity.MusicPreview? music_preview;
32
public RhythmboxScope ()
36
scope = new Unity.Scope ("/com/canonical/unity/scope/rhythmbox");
37
scope.search_in_global = true;
38
scope.activate_uri.connect (activate);
39
scope.preview_uri.connect (preview);
40
scope.sources.add_option ("rhythmbox", _("Rhythmbox"), null);
43
collection = new RhythmboxCollection ();
47
protected override int num_results_without_search { get {return 100; } }
48
protected override int num_results_global_search { get { return 20; } }
49
protected override int num_results_lens_search { get { return 50; } }
51
private async void prepare_rhythmbox_play_queue (string[] exec_args)
53
var main_exec = exec_args; //needed from vala >= 0.15.1: captured parameters of array type are no more copied implicitly
54
Thread.create<void> (() =>
58
Process.spawn_command_line_sync ("rhythmbox-client --pause");
59
Process.spawn_command_line_sync ("rhythmbox-client --clear-queue");
60
Process.spawn_sync (null, main_exec, null, SpawnFlags.SEARCH_PATH,
62
Process.spawn_command_line_sync ("rhythmbox-client --next");
63
// make sure we're playing
64
Process.spawn_command_line_sync ("rhythmbox-client --play");
68
warning ("%s", err.message);
70
Idle.add (prepare_rhythmbox_play_queue.callback);
75
public Unity.Preview preview (string uri)
78
GLib.ThemedIcon? icon_file = null;
80
if (Uri.parse_scheme (uri) == "album")
82
string album_key = uri.substring (8);
84
foreach (unowned Track track in collection.get_album_tracks_detailed (album_key))
86
if (music_preview == null)
88
if (track.artwork_path != null)
90
icon_file = new GLib.ThemedIcon(track.artwork_path);
92
music_preview = new Unity.MusicPreview (track.album, track.album_artist, icon_file);
94
var play_action = new Unity.PreviewAction ("play_album", _("Play"), null);
95
play_action.activated.connect (play_album_preview);
96
music_preview.add_action (play_action);
98
var show_folder_action = new Unity.PreviewAction ("show_in_folder", _("Show in Folder"), null);
99
show_folder_action.activated.connect (show_in_folder);
100
music_preview.add_action (show_folder_action);
102
var tm = new Unity.TrackMetadata();
104
tm.track_no = track.track_number;
105
tm.title = track.title;
106
tm.length = track.duration;
107
music_preview.add_track(tm);
110
else if (Uri.parse_scheme (uri) == "file") // preview single track, ignore radio stations
112
Track? track = collection.get_album_track (uri);
115
if (track.artwork_path != null)
117
icon_file = new GLib.ThemedIcon(track.artwork_path);
119
music_preview = new Unity.MusicPreview (track.title, track.album_artist, icon_file);
121
var play_action = new Unity.PreviewAction ("play_track", _("Play"), null);
122
play_action.activated.connect (play_track_in_rhythmbox);
123
music_preview.add_action (play_action);
125
var show_folder_action = new Unity.PreviewAction ("show_in_folder", _("Show in Folder"), null);
126
show_folder_action.activated.connect (show_in_folder);
127
music_preview.add_action (show_folder_action);
129
var tm = new Unity.TrackMetadata();
131
tm.track_no = track.track_number;
132
tm.title = track.title;
133
tm.length = track.duration;
134
music_preview.add_track(tm);
140
music_preview.play.connect (play);
141
music_preview.pause.connect (pause);
142
music_preview.closed.connect (closed);
145
return music_preview;
148
private Unity.ActivationResponse show_in_folder (string uri)
150
File? track_file = null;
152
if (Uri.parse_scheme (uri) == "album")
154
string album_key = uri.substring (8);
155
var tracks = collection.get_album_tracks (album_key);
156
if (tracks.length () > 0)
158
track_file = File.new_for_uri (tracks.nth_data (0));
163
track_file = File.new_for_uri (uri);
166
File? parent_dir = track_file.get_parent ();
167
if (parent_dir != null)
171
GLib.AppInfo.launch_default_for_uri (parent_dir.get_uri (), null);
172
return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
176
warning ("Failed to launch application for uri '%s': %s", parent_dir.get_uri (), e.message);
181
warning ("Failed to get parent dir for uri: '%s'", uri);
183
return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
186
private Unity.ActivationResponse play_track_in_rhythmbox (string uri)
188
return activate (uri);
191
private Unity.ActivationResponse play_album_preview (string uri)
193
return activate (uri);
196
public void closed (Unity.Preview preview)
198
if (preview_player != null)
202
preview_player.close ();
206
warning ("Failed to close preview player: %s", e.message);
211
public void play (Unity.Preview preview, string uri)
213
debug ("play request: '%s'", uri);
215
if (preview_player == null)
217
preview_player = new PreviewPlayer ();
218
preview_player.progress.connect (on_progress_changed);
219
preview_player.connect_to ();
222
// we will receive state back in on_progress_changed, but set it now so that it's immediately reflected in the dash
223
music_preview.current_track_uri = uri;
224
music_preview.current_progress = 0.0f;
225
music_preview.current_track_state = Unity.MusicPreview.TrackState.PLAYING;
227
preview_player.play (uri);
230
public void pause (Unity.Preview preview, string uri)
232
debug ("pause request: '%s'", uri);
233
if (preview_player != null)
235
// we will receive state back in on_progress_changed, but set it now so that it's immediately reflected in the dash
236
music_preview.current_track_uri = uri;
237
music_preview.current_track_state = Unity.MusicPreview.TrackState.PAUSED;
239
preview_player.pause ();
243
private void on_progress_changed (string uri, Unity.MusicPreview.TrackState state, double progress)
247
music_preview.current_track_uri = uri;
248
music_preview.current_track_state = state;
249
music_preview.current_progress = (float)progress;
254
* Tells rhythmbox to play the selected uri(s)
256
public Unity.ActivationResponse activate (string uri)
258
string[] exec = {"rhythmbox-client"};
262
if (Uri.parse_scheme (uri) == "album")
264
// this will be a bit crazy, let's do it in a separate thread
266
string album_key = uri.substring (8);
267
foreach (unowned string track_uri in collection.get_album_tracks (album_key))
272
prepare_rhythmbox_play_queue.begin (exec);
273
return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
277
exec += "--play-uri";
283
debug ("Spawning rb '%s'", string.joinv (" ", exec));
284
Process.spawn_async (null,
287
SpawnFlags.SEARCH_PATH,
291
return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
292
} catch (SpawnError error) {
293
warning ("Failed to launch URI %s", uri);
294
return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
298
public override async void perform_search (LensSearch search,
299
SearchType search_type,
300
owned List<FilterParser> filters,
301
int max_results = -1,
302
Cancellable? cancellable = null)
304
int category_override = -1;
305
if (search_type == SearchType.GLOBAL)
307
category_override = Category.MUSIC;
308
// the lens shouldn't display anything for empty search
309
if (is_search_empty (search)) return;
314
// parse the DB lazily
315
var tdb_path = Path.build_filename (Environment.get_user_cache_dir (),
316
"rhythmbox", "album-art",
318
collection.parse_metadata_file (tdb_path);
320
var xml_path = Path.build_filename (Environment.get_user_data_dir (),
321
"rhythmbox", "rhythmdb.xml");
322
collection.parse_file (xml_path);
323
if (rb_xml_monitor == null)
325
// re-parse the file if it changes
326
File xml_file = File.new_for_path (xml_path);
329
rb_xml_monitor = xml_file.monitor (FileMonitorFlags.NONE);
330
rb_xml_monitor.changed.connect (() => { db_ready = false; });
334
warning ("%s", err.message);
341
collection.search (search, search_type, filters,
342
max_results, category_override);