1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2010 Jonathan Matthew <jonathan@d14n.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include <glib.h>
32 #include <glib/gi18n.h>
33
34 #include "rb-podcast-settings.h"
35 #include "rb-podcast-main-source.h"
36 #include "rb-podcast-entry-types.h"
37 #include "rb-shell.h"
38 #include "rb-builder-helpers.h"
39 #include "rb-file-helpers.h"
40 #include "rb-util.h"
41 #include "rb-application.h"
42
43 struct _RBPodcastMainSourcePrivate
44 {
45 GtkWidget *config_widget;
46 };
47
G_DEFINE_TYPE(RBPodcastMainSource,rb_podcast_main_source,RB_TYPE_PODCAST_SOURCE)48 G_DEFINE_TYPE (RBPodcastMainSource, rb_podcast_main_source, RB_TYPE_PODCAST_SOURCE)
49
50
51 RBSource *
52 rb_podcast_main_source_new (RBShell *shell, RBPodcastManager *podcast_manager)
53 {
54 RBSource *source;
55 RhythmDBQuery *base_query;
56 RhythmDB *db;
57 GSettings *settings;
58 GtkBuilder *builder;
59 GMenu *toolbar;
60
61 g_object_get (shell, "db", &db, NULL);
62 base_query = rhythmdb_query_parse (db,
63 RHYTHMDB_QUERY_PROP_EQUALS,
64 RHYTHMDB_PROP_TYPE,
65 RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
66 RHYTHMDB_QUERY_END);
67 g_object_unref (db);
68
69 settings = g_settings_new (PODCAST_SETTINGS_SCHEMA);
70
71 builder = rb_builder_load ("podcast-toolbar.ui", NULL);
72 toolbar = G_MENU (gtk_builder_get_object (builder, "podcast-toolbar"));
73 rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
74
75 source = RB_SOURCE (g_object_new (RB_TYPE_PODCAST_MAIN_SOURCE,
76 "name", _("Podcasts"),
77 "shell", shell,
78 "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
79 "podcast-manager", podcast_manager,
80 "base-query", base_query,
81 "settings", g_settings_get_child (settings, "source"),
82 "toolbar-menu", toolbar,
83 "show-all-feeds", TRUE,
84 NULL));
85 g_object_unref (settings);
86 g_object_unref (builder);
87
88 rhythmdb_query_free (base_query);
89
90 rb_shell_register_entry_type_for_source (shell, source,
91 RHYTHMDB_ENTRY_TYPE_PODCAST_FEED);
92 rb_shell_register_entry_type_for_source (shell, source,
93 RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
94
95 return source;
96 }
97
98 void
rb_podcast_main_source_add_subsources(RBPodcastMainSource * source)99 rb_podcast_main_source_add_subsources (RBPodcastMainSource *source)
100 {
101 RhythmDBQuery *query;
102 RBSource *podcast_subsource;
103 RBPodcastManager *podcast_mgr;
104 RhythmDB *db;
105 RBShell *shell;
106
107 g_object_get (source,
108 "shell", &shell,
109 "podcast-manager", &podcast_mgr,
110 NULL);
111 g_object_get (shell, "db", &db, NULL);
112
113 query = rhythmdb_query_parse (db,
114 RHYTHMDB_QUERY_PROP_EQUALS,
115 RHYTHMDB_PROP_TYPE,
116 RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
117 RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN,
118 RHYTHMDB_PROP_FIRST_SEEN,
119 3600 * 24 * 7,
120 RHYTHMDB_QUERY_END);
121
122 podcast_subsource = rb_podcast_source_new (shell,
123 podcast_mgr,
124 query,
125 _("New Episodes"),
126 "document-open-recent-symbolic");
127 rhythmdb_query_free (query);
128 rb_source_set_hidden_when_empty (podcast_subsource, TRUE);
129 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (podcast_subsource), RB_DISPLAY_PAGE (source));
130
131 query = rhythmdb_query_parse (db,
132 RHYTHMDB_QUERY_PROP_EQUALS,
133 RHYTHMDB_PROP_TYPE,
134 RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
135 RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN,
136 RHYTHMDB_PROP_LAST_SEEN,
137 3600 * 24 * 7,
138 RHYTHMDB_QUERY_END);
139
140 podcast_subsource = rb_podcast_source_new (shell,
141 podcast_mgr,
142 query,
143 _("New Downloads"), /* better name? */
144 "folder-download-symbolic");
145 rhythmdb_query_free (query);
146 rb_source_set_hidden_when_empty (podcast_subsource, TRUE);
147 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (podcast_subsource), RB_DISPLAY_PAGE (source));
148
149 g_object_unref (db);
150 g_object_unref (shell);
151 }
152
153 static void
start_download_cb(RBPodcastManager * pd,RhythmDBEntry * entry,RBPodcastMainSource * source)154 start_download_cb (RBPodcastManager *pd,
155 RhythmDBEntry *entry,
156 RBPodcastMainSource *source)
157 {
158 RBShell *shell;
159 char *podcast_name;
160
161 podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
162
163 g_object_get (source, "shell", &shell, NULL);
164 rb_shell_notify_custom (shell, 4000, _("Downloading podcast"), podcast_name, NULL, FALSE);
165 g_object_unref (shell);
166
167 g_free (podcast_name);
168 }
169
170 static void
finish_download_cb(RBPodcastManager * pd,RhythmDBEntry * entry,RBPodcastMainSource * source)171 finish_download_cb (RBPodcastManager *pd,
172 RhythmDBEntry *entry,
173 RBPodcastMainSource *source)
174 {
175 RBShell *shell;
176 char *podcast_name;
177
178 podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
179
180 g_object_get (source, "shell", &shell, NULL);
181 rb_shell_notify_custom (shell, 4000, _("Finished downloading podcast"), podcast_name, NULL, FALSE);
182 g_object_unref (shell);
183
184 g_free (podcast_name);
185 }
186
187 static void
feed_updates_available_cb(RBPodcastManager * pd,RhythmDBEntry * entry,RBPodcastMainSource * source)188 feed_updates_available_cb (RBPodcastManager *pd,
189 RhythmDBEntry *entry,
190 RBPodcastMainSource *source)
191 {
192 RBShell *shell;
193 char *podcast_name;
194
195 podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
196
197 g_object_get (source, "shell", &shell, NULL);
198 rb_shell_notify_custom (shell, 4000, _("New updates available from"), podcast_name, NULL, FALSE);
199 g_object_unref (shell);
200
201 g_free (podcast_name);
202
203 }
204
205 static void
error_dialog_response_cb(GtkDialog * dialog,int response,RBPodcastMainSource * source)206 error_dialog_response_cb (GtkDialog *dialog, int response, RBPodcastMainSource *source)
207 {
208 const char *url = g_object_get_data (G_OBJECT (dialog), "feed-url");
209
210 if (response == GTK_RESPONSE_YES) {
211 RBPodcastManager *pd;
212 g_object_get (source, "podcast-manager", &pd, NULL);
213 rb_podcast_manager_insert_feed_url (pd, url);
214 g_object_unref (pd);
215 }
216
217 gtk_widget_destroy (GTK_WIDGET (dialog));
218 }
219
220 static void
feed_error_cb(RBPodcastManager * pd,const char * url,const char * error,gboolean existing,RBPodcastMainSource * source)221 feed_error_cb (RBPodcastManager *pd,
222 const char *url,
223 const char *error,
224 gboolean existing,
225 RBPodcastMainSource *source)
226 {
227 GtkWidget *dialog;
228
229 /* if the podcast feed doesn't already exist in the db,
230 * ask if the user wants to add it anyway; if it already
231 * exists, there's nothing to do besides reporting the error.
232 */
233 dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source))),
234 GTK_DIALOG_DESTROY_WITH_PARENT,
235 GTK_MESSAGE_ERROR,
236 existing ? GTK_BUTTONS_OK : GTK_BUTTONS_YES_NO,
237 _("Error in podcast"));
238
239 if (existing) {
240 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
241 "%s", error);
242 } else {
243 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
244 _("%s. Would you like to add the podcast feed anyway?"), error);
245 }
246
247 gtk_window_set_title (GTK_WINDOW (dialog), "");
248 gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
249
250 g_object_set_data_full (G_OBJECT (dialog), "feed-url", g_strdup (url), g_free);
251 g_signal_connect (dialog, "response", G_CALLBACK (error_dialog_response_cb), source);
252
253 gtk_widget_show_all (dialog);
254 }
255
256 static void
rb_podcast_main_source_btn_file_change_cb(GtkFileChooserButton * widget,RBPodcastSource * source)257 rb_podcast_main_source_btn_file_change_cb (GtkFileChooserButton *widget, RBPodcastSource *source)
258 {
259 GSettings *settings;
260 char *uri;
261
262 settings = g_settings_new (PODCAST_SETTINGS_SCHEMA);
263
264 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (widget));
265 g_settings_set_string (settings, PODCAST_DOWNLOAD_DIR_KEY, uri);
266 g_free (uri);
267
268 g_object_unref (settings);
269 }
270
271 static GtkWidget *
impl_get_config_widget(RBDisplayPage * page,RBShellPreferences * prefs)272 impl_get_config_widget (RBDisplayPage *page, RBShellPreferences *prefs)
273 {
274 RBPodcastMainSource *source = RB_PODCAST_MAIN_SOURCE (page);
275 RBPodcastManager *podcast_mgr;
276 GtkBuilder *builder;
277 GtkWidget *update_interval;
278 GtkWidget *btn_file;
279 GSettings *settings;
280 char *download_dir;
281
282 if (source->priv->config_widget)
283 return source->priv->config_widget;
284
285 builder = rb_builder_load ("podcast-prefs.ui", source);
286 source->priv->config_widget = GTK_WIDGET (gtk_builder_get_object (builder, "podcast_vbox"));
287
288 btn_file = GTK_WIDGET (gtk_builder_get_object (builder, "location_chooser"));
289 gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (btn_file),
290 rb_music_dir (),
291 NULL);
292
293 g_object_get (source,
294 "podcast-manager", &podcast_mgr,
295 NULL);
296 download_dir = rb_podcast_manager_get_podcast_dir (podcast_mgr);
297
298 gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (btn_file),
299 download_dir);
300 g_object_unref (podcast_mgr);
301 g_free (download_dir);
302
303 g_signal_connect_object (btn_file,
304 "selection-changed",
305 G_CALLBACK (rb_podcast_main_source_btn_file_change_cb),
306 source, 0);
307
308 update_interval = GTK_WIDGET (gtk_builder_get_object (builder, "update_interval"));
309 g_object_set (update_interval, "id-column", 1, NULL);
310
311 settings = g_settings_new (PODCAST_SETTINGS_SCHEMA);
312 g_settings_bind (settings, PODCAST_DOWNLOAD_INTERVAL,
313 update_interval, "active-id",
314 G_SETTINGS_BIND_DEFAULT);
315 g_object_unref (settings);
316
317 return source->priv->config_widget;
318 }
319
320 static guint
impl_want_uri(RBSource * source,const char * uri)321 impl_want_uri (RBSource *source, const char *uri)
322 {
323 if (g_str_has_prefix (uri, "http://") == FALSE)
324 return 0;
325
326 if (g_str_has_suffix (uri, ".xml") ||
327 g_str_has_suffix (uri, ".rss"))
328 return 100;
329
330 return 0;
331 }
332
333 static void
impl_add_uri(RBSource * source,const char * uri,const char * title,const char * genre,RBSourceAddCallback callback,gpointer data,GDestroyNotify destroy_data)334 impl_add_uri (RBSource *source,
335 const char *uri,
336 const char *title,
337 const char *genre,
338 RBSourceAddCallback callback,
339 gpointer data,
340 GDestroyNotify destroy_data)
341 {
342 RBPodcastManager *podcast_mgr;
343
344 g_object_get (source, "podcast-manager", &podcast_mgr, NULL);
345 rb_podcast_manager_subscribe_feed (podcast_mgr, uri, FALSE);
346 g_object_unref (podcast_mgr);
347
348 if (callback != NULL) {
349 callback (source, uri, data);
350 if (destroy_data != NULL) {
351 destroy_data (data);
352 }
353 }
354 }
355
356 static void
impl_constructed(GObject * object)357 impl_constructed (GObject *object)
358 {
359 RBPodcastMainSource *source;
360 RBPodcastManager *podcast_mgr;
361
362 RB_CHAIN_GOBJECT_METHOD (rb_podcast_main_source_parent_class, constructed, object);
363 source = RB_PODCAST_MAIN_SOURCE (object);
364
365 g_object_get (source, "podcast-manager", &podcast_mgr, NULL);
366
367 g_signal_connect_object (podcast_mgr,
368 "start_download",
369 G_CALLBACK (start_download_cb),
370 source, 0);
371
372 g_signal_connect_object (podcast_mgr,
373 "finish_download",
374 G_CALLBACK (finish_download_cb),
375 source, 0);
376
377 g_signal_connect_object (podcast_mgr,
378 "feed_updates_available",
379 G_CALLBACK (feed_updates_available_cb),
380 source, 0);
381
382 g_signal_connect_object (podcast_mgr,
383 "process_error",
384 G_CALLBACK (feed_error_cb),
385 source, 0);
386
387 rb_display_page_set_icon_name (RB_DISPLAY_PAGE (source), "application-rss+xml-symbolic");
388 }
389
390 static void
impl_dispose(GObject * object)391 impl_dispose (GObject *object)
392 {
393 RBPodcastMainSource *source;
394
395 source = RB_PODCAST_MAIN_SOURCE (object);
396 if (source->priv->config_widget != NULL) {
397 g_object_unref (source->priv->config_widget);
398 source->priv->config_widget = NULL;
399 }
400
401 G_OBJECT_CLASS (rb_podcast_main_source_parent_class)->dispose (object);
402 }
403
404 static void
rb_podcast_main_source_init(RBPodcastMainSource * source)405 rb_podcast_main_source_init (RBPodcastMainSource *source)
406 {
407 source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source,
408 RB_TYPE_PODCAST_MAIN_SOURCE,
409 RBPodcastMainSourcePrivate);
410 }
411
412 static void
rb_podcast_main_source_class_init(RBPodcastMainSourceClass * klass)413 rb_podcast_main_source_class_init (RBPodcastMainSourceClass *klass)
414 {
415 GObjectClass *object_class = G_OBJECT_CLASS (klass);
416 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
417 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
418
419 object_class->dispose = impl_dispose;
420 object_class->constructed = impl_constructed;
421
422 page_class->get_config_widget = impl_get_config_widget;
423
424 source_class->want_uri = impl_want_uri;
425 source_class->add_uri = impl_add_uri;
426
427 g_type_class_add_private (klass, sizeof (RBPodcastMainSourcePrivate));
428 }
429