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