2006-09-24 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / podcast / rb-podcast-manager.c
blob36d32f4013373a22f2953bd0394f9e4bfe55aa7d
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implemetation files for podcast download manager
5 * Copyright (C) 2005 Renato Araujo Oliveira Filho - INdT <renato.filho@indt.org.br>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "config.h"
25 #include <string.h>
26 #define __USE_XOPEN
27 #include <time.h>
29 #include <glib/gi18n.h>
30 #include <glib/gstdio.h>
31 #include <gtk/gtk.h>
32 #include <libgnomevfs/gnome-vfs-uri.h>
34 #include "rb-preferences.h"
35 #include "eel-gconf-extensions.h"
36 #include "rb-podcast-manager.h"
37 #include "rb-file-helpers.h"
38 #include "rb-debug.h"
39 #include "rb-marshal.h"
40 #include "rhythmdb.h"
41 #include "rhythmdb-query-model.h"
42 #include "rb-podcast-parse.h"
43 #include "rb-dialog.h"
44 #include "rb-metadata.h"
46 #define CONF_STATE_PODCAST_PREFIX CONF_PREFIX "/state/podcast"
47 #define CONF_STATE_PODCAST_DOWNLOAD_DIR CONF_STATE_PODCAST_PREFIX "/download_prefix"
48 #define CONF_STATE_PODCAST_DOWNLOAD_INTERVAL CONF_STATE_PODCAST_PREFIX "/download_interval"
49 #define CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME CONF_STATE_PODCAST_PREFIX "/download_next_time"
51 enum
53 PROP_0,
54 PROP_DB
57 enum
59 UPDATE_EVERY_HOUR,
60 UPDATE_EVERY_DAY,
61 UPDATE_EVERY_WEEK,
62 UPDATE_MANUALLY
65 enum
67 STATUS_CHANGED,
68 START_DOWNLOAD,
69 FINISH_DOWNLOAD,
70 PROCESS_ERROR,
71 FEED_UPDATES_AVALIABLE,
72 LAST_SIGNAL
75 typedef enum
77 EVENT_INSERT_FEED,
78 EVENT_ERROR_FEED
79 }RBPodcastEventType;
81 struct RBPodcastManagerPrivate
83 RhythmDB *db;
84 GList *download_list;
85 guint next_time;
86 guint source_sync;
87 guint update_interval_notify_id;
88 GMutex *mutex_job;
89 GMutex *download_list_mutex;
91 gboolean remove_files;
93 GAsyncQueue *event_queue;
96 #define RB_PODCAST_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PODCAST_MANAGER, RBPodcastManagerPrivate))
98 /* used on event loop */
99 typedef struct
101 RBPodcastEventType type;
102 RBPodcastChannel *channel;
103 } RBPodcastManagerEvent;
105 /* used on donwload thread */
106 typedef struct
108 RBPodcastManager *pd;
109 RhythmDBEntry *entry;
110 GnomeVFSAsyncHandle *read_handle;
111 GnomeVFSURI *write_uri;
112 GnomeVFSURI *read_uri;
113 char *query_string;
114 GMutex *mutex_working;
116 guint total_size;
117 guint progress;
118 gboolean canceled;
119 } RBPodcastManagerInfo;
121 /* used on subscribe thread */
122 typedef struct
124 RBPodcastManager *pd;
125 char *url;
126 } RBPodcastThreadInfo;
128 static guint rb_podcast_manager_signals[LAST_SIGNAL] = { 0 };
130 /* functions */
131 static void rb_podcast_manager_class_init (RBPodcastManagerClass *klass);
132 static void rb_podcast_manager_init (RBPodcastManager *dp);
133 static GObject *rb_podcast_manager_constructor (GType type, guint n_construct_properties,
134 GObjectConstructParam *construct_properties);
135 static void rb_podcast_manager_finalize (GObject *object);
136 static void rb_podcast_manager_set_property (GObject *object,
137 guint prop_id,
138 const GValue *value,
139 GParamSpec *pspec);
140 static void rb_podcast_manager_get_property (GObject *object,
141 guint prop_id,
142 GValue *value,
143 GParamSpec *pspec);
144 static void rb_podcast_manager_copy_post (RBPodcastManager *pd);
145 static void rb_podcast_manager_download_file_info_cb (GnomeVFSAsyncHandle *handle,
146 GList *results,
147 RBPodcastManagerInfo *data);
148 static void rb_podcast_manager_abort_download (RBPodcastManagerInfo *data);
149 static gboolean rb_podcast_manager_sync_head_cb (gpointer data);
150 static gboolean rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
151 GtkTreePath *path,
152 GtkTreeIter *iter,
153 RBPodcastManager *data);
154 static gboolean rb_podcast_manager_save_metadata (RhythmDB *db,
155 RhythmDBEntry *entry,
156 const char *uri);
157 static void rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd,
158 RhythmDBEntry *entry);
159 static void rb_podcast_manager_db_entry_deleted_cb (RBPodcastManager *pd,
160 RhythmDBEntry *entry);
161 static gboolean rb_podcast_manager_next_file (RBPodcastManager * pd);
162 static void rb_podcast_manager_insert_feed (RBPodcastManager *pd, RBPodcastChannel *data);
163 static void rb_podcast_manager_abort_subscribe (RBPodcastManager *pd);
165 /* event loop */
166 static gboolean rb_podcast_manager_event_loop (RBPodcastManager *pd) ;
167 static gpointer rb_podcast_manager_thread_parse_feed (RBPodcastThreadInfo *info);
169 /* async read file functions */
170 static guint download_progress_cb (GnomeVFSXferProgressInfo *info,
171 gpointer data);
172 static guint download_progress_update_cb (GnomeVFSAsyncHandle *handle,
173 GnomeVFSXferProgressInfo *info,
174 gpointer data);
176 /* internal functions */
177 static void download_info_free (RBPodcastManagerInfo *data);
178 static RBPodcastManagerInfo *download_info_new (void);
179 static void start_job (RBPodcastManagerInfo *data);
180 static void end_job (RBPodcastManagerInfo *data);
181 static void cancel_job (RBPodcastManagerInfo *pd);
182 static void write_job_data (RBPodcastManagerInfo *data);
183 static void rb_podcast_manager_update_synctime (RBPodcastManager *pd);
184 static void rb_podcast_manager_config_changed (GConfClient* client,
185 guint cnxn_id,
186 GConfEntry *entry,
187 gpointer user_data);
189 G_DEFINE_TYPE (RBPodcastManager, rb_podcast_manager, G_TYPE_OBJECT)
191 static void
192 rb_podcast_manager_class_init (RBPodcastManagerClass *klass)
194 GObjectClass *object_class = G_OBJECT_CLASS (klass);
196 object_class->constructor = rb_podcast_manager_constructor;
197 object_class->finalize = rb_podcast_manager_finalize;
199 object_class->set_property = rb_podcast_manager_set_property;
200 object_class->get_property = rb_podcast_manager_get_property;
202 g_object_class_install_property (object_class,
203 PROP_DB,
204 g_param_spec_object ("db",
205 "db",
206 "database",
207 RHYTHMDB_TYPE,
208 G_PARAM_READWRITE));
210 rb_podcast_manager_signals[STATUS_CHANGED] =
211 g_signal_new ("status_changed",
212 G_OBJECT_CLASS_TYPE (object_class),
213 GTK_RUN_LAST,
214 G_STRUCT_OFFSET (RBPodcastManagerClass, status_changed),
215 NULL, NULL,
216 rb_marshal_VOID__BOXED_ULONG,
217 G_TYPE_NONE,
219 RHYTHMDB_TYPE_ENTRY,
220 G_TYPE_ULONG);
222 rb_podcast_manager_signals[START_DOWNLOAD] =
223 g_signal_new ("start_download",
224 G_OBJECT_CLASS_TYPE (object_class),
225 GTK_RUN_LAST,
226 G_STRUCT_OFFSET (RBPodcastManagerClass, start_download),
227 NULL, NULL,
228 g_cclosure_marshal_VOID__BOXED,
229 G_TYPE_NONE,
231 RHYTHMDB_TYPE_ENTRY);
233 rb_podcast_manager_signals[FINISH_DOWNLOAD] =
234 g_signal_new ("finish_download",
235 G_OBJECT_CLASS_TYPE (object_class),
236 GTK_RUN_LAST,
237 G_STRUCT_OFFSET (RBPodcastManagerClass, finish_download),
238 NULL, NULL,
239 g_cclosure_marshal_VOID__BOXED,
240 G_TYPE_NONE,
242 RHYTHMDB_TYPE_ENTRY);
244 rb_podcast_manager_signals[FEED_UPDATES_AVALIABLE] =
245 g_signal_new ("feed_updates_avaliable",
246 G_OBJECT_CLASS_TYPE (object_class),
247 GTK_RUN_LAST,
248 G_STRUCT_OFFSET (RBPodcastManagerClass, feed_updates_avaliable),
249 NULL, NULL,
250 g_cclosure_marshal_VOID__BOXED,
251 G_TYPE_NONE,
253 RHYTHMDB_TYPE_ENTRY);
255 rb_podcast_manager_signals[PROCESS_ERROR] =
256 g_signal_new ("process_error",
257 G_OBJECT_CLASS_TYPE (object_class),
258 GTK_RUN_LAST,
259 G_STRUCT_OFFSET (RBPodcastManagerClass, process_error),
260 NULL, NULL,
261 g_cclosure_marshal_VOID__STRING,
262 G_TYPE_NONE,
264 G_TYPE_STRING);
266 g_type_class_add_private (klass, sizeof (RBPodcastManagerPrivate));
269 static void
270 rb_podcast_manager_init (RBPodcastManager *pd)
272 pd->priv = RB_PODCAST_MANAGER_GET_PRIVATE (pd);
274 pd->priv->source_sync = 0;
275 pd->priv->mutex_job = g_mutex_new();
276 pd->priv->download_list_mutex = g_mutex_new();
277 pd->priv->event_queue = g_async_queue_new ();
278 pd->priv->db = NULL;
279 eel_gconf_monitor_add (CONF_STATE_PODCAST_PREFIX);
282 static GObject *
283 rb_podcast_manager_constructor (GType type, guint n_construct_properties,
284 GObjectConstructParam *construct_properties)
286 RBPodcastManager *pd;
288 pd = RB_PODCAST_MANAGER (G_OBJECT_CLASS (rb_podcast_manager_parent_class)
289 ->constructor (type, n_construct_properties, construct_properties));
291 pd->priv->update_interval_notify_id = eel_gconf_notification_add (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL,
292 rb_podcast_manager_config_changed,
293 pd);
295 return G_OBJECT (pd);
299 static void
300 rb_podcast_manager_finalize (GObject *object)
302 RBPodcastManager *pd;
303 g_return_if_fail (object != NULL);
304 g_return_if_fail (RB_IS_PODCAST_MANAGER (object));
306 pd = RB_PODCAST_MANAGER(object);
308 g_return_if_fail (pd->priv != NULL);
310 eel_gconf_monitor_remove (CONF_STATE_PODCAST_PREFIX);
312 if (pd->priv->source_sync) {
313 g_source_remove (pd->priv->source_sync);
314 pd->priv->source_sync = 0;
317 eel_gconf_notification_remove (pd->priv->update_interval_notify_id);
319 if (pd->priv->download_list) {
320 g_list_foreach (pd->priv->download_list, (GFunc)g_free, NULL);
321 g_list_free (pd->priv->download_list);
324 g_mutex_free (pd->priv->mutex_job);
325 g_mutex_free (pd->priv->download_list_mutex);
326 g_async_queue_unref (pd->priv->event_queue);
328 G_OBJECT_CLASS (rb_podcast_manager_parent_class)->finalize (object);
329 rb_debug ("Podcast Manager END");
332 static void
333 rb_podcast_manager_set_property (GObject *object,
334 guint prop_id,
335 const GValue *value,
336 GParamSpec *pspec)
338 RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
340 switch (prop_id) {
341 case PROP_DB:
342 if (pd->priv->db) {
343 g_signal_handlers_disconnect_by_func (G_OBJECT (pd->priv->db),
344 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
345 pd);
347 g_signal_handlers_disconnect_by_func (G_OBJECT (pd->priv->db),
348 G_CALLBACK (rb_podcast_manager_db_entry_deleted_cb),
349 pd);
353 pd->priv->db = g_value_get_object (value);
355 g_signal_connect_object (G_OBJECT (pd->priv->db),
356 "entry-added",
357 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
358 pd, G_CONNECT_SWAPPED);
360 g_signal_connect_object (G_OBJECT (pd->priv->db),
361 "entry_deleted",
362 G_CALLBACK (rb_podcast_manager_db_entry_deleted_cb),
363 pd, G_CONNECT_SWAPPED);
365 break;
366 default:
367 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
371 static void
372 rb_podcast_manager_get_property (GObject *object,
373 guint prop_id,
374 GValue *value,
375 GParamSpec *pspec)
377 RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
379 switch (prop_id) {
380 case PROP_DB:
381 g_value_set_object (value, pd->priv->db);
382 break;
383 default:
384 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
389 RBPodcastManager *
390 rb_podcast_manager_new (RhythmDB *db)
392 RBPodcastManager *pd;
394 pd = g_object_new (RB_TYPE_PODCAST_MANAGER, "db", db, NULL);
395 return pd;
398 void
399 rb_podcast_manager_download_entry (RBPodcastManager *pd,
400 RhythmDBEntry *entry)
402 gulong status;
404 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
406 if (entry == NULL)
407 return;
409 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
410 if ((status < RHYTHMDB_PODCAST_STATUS_COMPLETE) ||
411 (status == RHYTHMDB_PODCAST_STATUS_WAITING)) {
412 RBPodcastManagerInfo *data;
413 if (status < RHYTHMDB_PODCAST_STATUS_COMPLETE) {
414 GValue status_val = { 0, };
415 g_value_init (&status_val, G_TYPE_ULONG);
416 g_value_set_ulong (&status_val, RHYTHMDB_PODCAST_STATUS_WAITING);
417 rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &status_val);
418 g_value_unset (&status_val);
420 rb_debug ("Try insert entry for download.");
421 data = download_info_new();
422 data->pd = pd;
423 data->entry = entry;
424 g_mutex_lock (pd->priv->download_list_mutex);
425 pd->priv->download_list = g_list_append (pd->priv->download_list, data);
426 g_mutex_unlock (pd->priv->download_list_mutex);
427 g_idle_add ((GtkFunction) rb_podcast_manager_next_file , pd);
431 gboolean
432 rb_podcast_manager_entry_downloaded (RhythmDBEntry *entry)
434 gulong status;
435 const gchar *file_name;
436 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
438 g_assert (type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
440 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
441 file_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
443 return (status != RHYTHMDB_PODCAST_STATUS_ERROR && file_name != NULL);
446 void
447 rb_podcast_manager_start_sync (RBPodcastManager *pd)
449 gint next_time;
451 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
453 if (pd->priv->next_time > 0) {
454 next_time = pd->priv->next_time;
455 } else {
456 next_time = eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME);
459 if (next_time > 0) {
460 if (pd->priv->source_sync != 0) {
461 g_source_remove (pd->priv->source_sync);
462 pd->priv->source_sync = 0;
464 next_time = next_time - ((int)time (NULL));
465 if (next_time <= 0) {
466 rb_podcast_manager_update_feeds (pd);
467 pd->priv->next_time = 0;
468 rb_podcast_manager_update_synctime (pd);
469 return;
471 pd->priv->source_sync = g_timeout_add (next_time * 1000, (GSourceFunc ) rb_podcast_manager_sync_head_cb, pd);
476 static gboolean
477 rb_podcast_manager_sync_head_cb (gpointer data)
479 RBPodcastManager *pd = RB_PODCAST_MANAGER (data);
481 GDK_THREADS_ENTER ();
483 rb_podcast_manager_update_feeds (pd);
484 pd->priv->source_sync = 0;
485 pd->priv->next_time = 0;
486 rb_podcast_manager_update_synctime (RB_PODCAST_MANAGER (data));
487 GDK_THREADS_LEAVE ();
488 return FALSE;
491 void
492 rb_podcast_manager_update_feeds (RBPodcastManager *pd)
494 GtkTreeModel *query_model;
496 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
498 query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (pd->priv->db));
500 rhythmdb_do_full_query (pd->priv->db,
501 RHYTHMDB_QUERY_RESULTS (query_model),
502 RHYTHMDB_QUERY_PROP_EQUALS,
503 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
504 RHYTHMDB_QUERY_END);
506 gtk_tree_model_foreach (query_model,
507 (GtkTreeModelForeachFunc) rb_podcast_manager_head_query_cb,
508 pd);
510 g_object_unref (query_model);
513 static gboolean
514 rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
515 GtkTreePath *path,
516 GtkTreeIter *iter,
517 RBPodcastManager *manager)
519 const char *uri;
520 RhythmDBEntry *entry;
521 guint status;
523 gtk_tree_model_get (query_model, iter, 0, &entry, -1);
524 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
525 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
527 if (status == 1)
528 rb_podcast_manager_subscribe_feed (manager, uri);
530 rhythmdb_entry_unref (entry);
532 return FALSE;
535 static gboolean
536 rb_podcast_manager_next_file (RBPodcastManager * pd)
538 GDK_THREADS_ENTER ();
540 rb_debug ("try lock file_process mutex");
541 if (g_mutex_trylock (pd->priv->mutex_job) == TRUE) {
542 gint size;
544 g_mutex_lock (pd->priv->download_list_mutex);
545 size = g_list_length (pd->priv->download_list);
546 g_mutex_unlock (pd->priv->download_list_mutex);
548 if (size > 0)
549 rb_podcast_manager_copy_post (pd);
550 else
551 g_mutex_unlock (pd->priv->mutex_job);
552 } else {
553 rb_debug ("not start");
556 GDK_THREADS_LEAVE ();
557 return FALSE;
560 static void
561 rb_podcast_manager_copy_post (RBPodcastManager *pd)
563 const char *location;
564 RBPodcastManagerInfo *data;
565 char *query_string;
567 /* get first element of list */
568 g_mutex_lock (pd->priv->download_list_mutex);
569 data = (RBPodcastManagerInfo *) g_list_first (pd->priv->download_list)->data;
570 g_mutex_unlock (pd->priv->download_list_mutex);
572 g_assert (data != NULL);
573 g_assert (data->entry != NULL);
575 location = rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION);
576 rb_debug ("processing %s", location);
578 /* gnome-vfs currently doesn't handle HTTP query strings correctly.
579 * so we do it ourselves.
581 query_string = strrchr (location, '?');
582 if (query_string != NULL) {
583 char *base_uri;
585 base_uri = g_strdup (location);
586 query_string = strrchr (base_uri, '?');
587 *query_string++ = '\0';
588 rb_debug ("hiding query string %s from gnome-vfs", query_string);
590 data->read_uri = gnome_vfs_uri_new (base_uri);
591 if (data->read_uri != NULL) {
592 char *full_uri;
594 full_uri = g_strdup_printf ("%s?%s",
595 data->read_uri->text,
596 query_string);
597 g_free (data->read_uri->text);
598 data->read_uri->text = full_uri;
600 /* include the question mark in data->query_string to make
601 * the later check easier.
603 query_string--;
604 *query_string = '?';
605 data->query_string = g_strdup (query_string);
607 g_free (base_uri);
608 } else {
609 data->read_uri = gnome_vfs_uri_new (location);
611 if (data->read_uri == NULL) {
612 rb_debug ("Error downloading podcast: could not create remote uri");
613 rb_podcast_manager_abort_download (data);
614 return;
617 gnome_vfs_async_get_file_info (&data->read_handle,
618 g_list_prepend (NULL, data->read_uri),
619 GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
620 GNOME_VFS_PRIORITY_DEFAULT,
621 (GnomeVFSAsyncGetFileInfoCallback) rb_podcast_manager_download_file_info_cb,
622 data);
625 static void
626 rb_podcast_manager_download_file_info_cb (GnomeVFSAsyncHandle *handle,
627 GList *results,
628 RBPodcastManagerInfo *data)
630 GnomeVFSGetFileInfoResult *result = results->data;
631 char *local_file_name;
632 char *local_file_path;
633 char *dir_name;
634 char *conf_dir_name;
636 rb_debug ("got file info results for %s",
637 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION));
639 if (result->result != GNOME_VFS_OK) {
641 GValue val = {0,};
643 g_value_init (&val, G_TYPE_ULONG);
644 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
645 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
646 g_value_unset (&val);
648 g_value_init (&val, G_TYPE_STRING);
649 g_value_set_string (&val, gnome_vfs_result_to_string (result->result));
650 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
651 g_value_unset (&val);
653 rhythmdb_commit (data->pd->priv->db);
655 rb_debug ("get_file_info request failed");
656 rb_podcast_manager_abort_download (data);
657 return;
660 /* construct download directory */
661 conf_dir_name = rb_podcast_manager_get_podcast_dir (data->pd);
662 dir_name = g_build_filename (conf_dir_name,
663 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_ALBUM),
664 NULL);
665 g_free (conf_dir_name);
667 if (g_mkdir_with_parents (dir_name, 0750) == -1) {
668 rb_debug ("Could not create podcast download directory %s", dir_name);
669 /* FIXME: display error to user */
670 g_free (dir_name);
671 rb_podcast_manager_abort_download (data);
672 return;
675 /* if the filename ends with the query string from the original URI,
676 * remove it.
678 if (data->query_string &&
679 g_str_has_suffix (result->file_info->name, data->query_string)) {
680 local_file_name = g_strdup (result->file_info->name);
681 local_file_name[strlen (local_file_name) - strlen (data->query_string)] = '\0';
682 rb_debug ("removing query string \"%s\" -> local file name \"%s\"", data->query_string, local_file_name);
683 } else {
684 local_file_name = result->file_info->name;
687 /* construct local filename */
688 local_file_path = g_build_filename (dir_name,
689 local_file_name,
690 NULL);
692 if (local_file_name != result->file_info->name)
693 g_free (local_file_name);
695 g_free (dir_name);
696 rb_debug ("creating file %s", local_file_path);
698 data->write_uri = gnome_vfs_uri_new (local_file_path);
699 if (data->write_uri == NULL) {
700 g_warning ("Could not create local podcast URI for %s", local_file_path);
701 rb_podcast_manager_abort_download (data);
702 return;
705 if (g_file_test (local_file_path, G_FILE_TEST_EXISTS)) {
706 guint64 local_size;
707 GnomeVFSFileInfo *local_info;
708 GnomeVFSResult local_result;
710 local_info = gnome_vfs_file_info_new ();
711 local_result = gnome_vfs_get_file_info (local_file_path,
712 local_info,
713 GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
714 local_size = local_info->size;
715 gnome_vfs_file_info_unref (local_info);
717 if (local_result != GNOME_VFS_OK) {
718 g_warning ("Could not get info on downloaded podcast file %s",
719 local_file_path);
720 rb_podcast_manager_abort_download (data);
721 return;
722 } else if (result->file_info->size == local_size) {
723 GValue val = {0,};
724 char *uri;
725 char *canon_uri;
727 uri = gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE);
728 canon_uri = rb_canonicalise_uri (uri);
729 g_free (uri);
731 rb_debug ("podcast %s already downloaded",
732 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION));
734 g_value_init (&val, G_TYPE_ULONG);
735 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
736 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
737 g_value_unset (&val);
739 g_value_init (&val, G_TYPE_STRING);
740 g_value_set_string (&val, canon_uri);
741 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
742 g_value_unset (&val);
744 rb_podcast_manager_save_metadata (data->pd->priv->db, data->entry, canon_uri);
745 rhythmdb_commit (data->pd->priv->db);
746 g_free (canon_uri);
748 rb_podcast_manager_abort_download (data);
749 return;
750 } else if (result->file_info->size > local_size) {
751 /* TODO: support resume file */
752 rb_debug ("podcast episode already partially downloaded, but we can't resume downloads");
753 } else {
754 /* the local file is larger. replace it */
758 rb_debug ("starting download");
759 g_free (local_file_path);
760 start_job (data);
763 static void
764 rb_podcast_manager_abort_download (RBPodcastManagerInfo *data)
766 RBPodcastManager *mgr = data->pd;
768 g_mutex_lock (mgr->priv->download_list_mutex);
769 mgr->priv->download_list = g_list_remove (mgr->priv->download_list, (gconstpointer) data);
770 g_mutex_unlock (mgr->priv->download_list_mutex);
772 download_info_free (data);
774 g_mutex_unlock (mgr->priv->mutex_job);
775 g_idle_add ((GtkFunction) rb_podcast_manager_next_file, mgr);
778 gboolean
779 rb_podcast_manager_subscribe_feed (RBPodcastManager *pd, const char *url)
781 RBPodcastThreadInfo *info;
782 gchar *valid_url = gnome_vfs_make_uri_from_input (url);
784 if (valid_url == NULL) {
785 rb_error_dialog (NULL, _("Invalid URL"),
786 _("The URL \"%s\" is not valid, please check it."), url);
787 return FALSE;
790 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, valid_url);
791 if (entry) {
792 if (rhythmdb_entry_get_entry_type (entry) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
793 /* added as something else, probably iradio */
794 rb_error_dialog (NULL, _("URL already added"),
795 _("The URL \"%s\" has already been added as a radio station. "
796 "If this is a podcast feed, please remove the radio station."), url);
797 return FALSE;
801 info = g_new0 (RBPodcastThreadInfo, 1);
802 info->pd = pd;
803 info->url = valid_url;
805 g_async_queue_ref (info->pd->priv->event_queue);
806 g_thread_create ((GThreadFunc) rb_podcast_manager_thread_parse_feed,
807 info, FALSE, NULL);
809 return TRUE;
812 static gpointer
813 rb_podcast_manager_thread_parse_feed (RBPodcastThreadInfo *info)
815 RBPodcastManagerEvent *event = g_new0 (RBPodcastManagerEvent, 1);
816 RBPodcastChannel *feed = g_new0 (RBPodcastChannel, 1);
818 if (rb_podcast_parse_load_feed (feed, info->url)) {
819 event->channel = feed;
820 event->type = (feed->title == NULL) ? EVENT_ERROR_FEED : EVENT_INSERT_FEED;
822 g_async_queue_push (info->pd->priv->event_queue, event);
823 g_idle_add ((GSourceFunc) rb_podcast_manager_event_loop, info->pd);
826 g_free (info->url);
827 g_free (info);
828 return NULL;
831 RhythmDBEntry *
832 rb_podcast_manager_add_post (RhythmDB *db,
833 const char *name,
834 const char *title,
835 const char *subtitle,
836 const char *generator,
837 const char *uri,
838 const char *description,
839 gulong date,
840 gulong duration,
841 guint64 filesize)
843 RhythmDBEntry *entry;
844 GValue val = {0,};
845 GTimeVal time;
847 if (!uri || !name || !title || !date || !g_utf8_validate(uri, -1, NULL)) {
848 return NULL;
850 entry = rhythmdb_entry_lookup_by_location (db, uri);
851 if (entry)
852 return NULL;
854 entry = rhythmdb_entry_new (db,
855 RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
856 uri);
857 if (entry == NULL)
858 return NULL;
860 g_value_init (&val, G_TYPE_STRING);
861 g_value_set_string (&val, name);
862 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ALBUM, &val);
864 g_value_reset (&val);
865 g_value_set_static_string (&val, _("Podcast"));
866 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_GENRE, &val);
868 g_value_reset (&val);
869 g_value_set_string (&val, title);
870 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &val);
872 g_value_reset (&val);
873 if (subtitle)
874 g_value_set_string (&val, subtitle);
875 else
876 g_value_set_static_string (&val, "");
877 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUBTITLE, &val);
879 g_value_reset (&val);
880 if (description)
881 g_value_set_string (&val, description);
882 else
883 g_value_set_static_string (&val, "");
884 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DESCRIPTION, &val);
886 g_value_reset (&val);
887 if (generator)
888 g_value_set_string (&val, generator);
889 else
890 g_value_set_static_string (&val, "");
891 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &val);
892 g_value_unset (&val);
894 g_value_init (&val, G_TYPE_ULONG);
895 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_PAUSED);
896 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
898 g_value_reset (&val);
899 g_value_set_ulong (&val, date);
900 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &val);
902 g_value_reset (&val);
903 g_value_set_ulong (&val, duration);
904 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val);
906 g_value_reset (&val);
907 g_value_set_ulong (&val, 0);
908 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_PLAYED, &val);
910 /* first seen */
911 g_get_current_time (&time);
912 g_value_reset (&val);
913 g_value_set_ulong (&val, time.tv_sec);
914 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &val);
915 g_value_unset (&val);
917 /* initialize the rating */
918 g_value_init (&val, G_TYPE_DOUBLE);
919 g_value_set_double (&val, 2.5);
920 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &val);
921 g_value_unset (&val);
923 g_value_init (&val, G_TYPE_UINT64);
924 g_value_set_uint64 (&val, filesize);
925 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FILE_SIZE, &val);
926 g_value_unset (&val);
928 return entry;
931 static gboolean
932 rb_podcast_manager_save_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *uri)
934 RBMetaData *md = rb_metadata_new();
935 GError *error = NULL;
936 GValue val = { 0, };
937 const char *mime;
939 rb_debug("Loading podcast metadata");
940 rb_metadata_load (md, uri, &error);
942 if (error != NULL) {
943 /* this probably isn't an audio enclosure. or some other error */
944 g_value_init (&val, G_TYPE_ULONG);
945 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
946 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
947 g_value_unset (&val);
949 g_value_init (&val, G_TYPE_STRING);
950 g_value_set_string (&val, error->message);
951 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
952 g_value_unset (&val);
954 rhythmdb_commit (db);
956 g_object_unref (md);
957 g_error_free (error);
959 return FALSE;
962 mime = rb_metadata_get_mime (md);
963 if (mime) {
964 g_value_init (&val, G_TYPE_STRING);
965 g_value_set_string (&val, mime);
966 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MIMETYPE, &val);
967 g_value_unset (&val);
970 if (rb_metadata_get (md,
971 RB_METADATA_FIELD_DURATION,
972 &val)) {
973 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val);
974 g_value_unset (&val);
977 if (rb_metadata_get (md,
978 RB_METADATA_FIELD_BITRATE,
979 &val)) {
980 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_BITRATE, &val);
981 g_value_unset (&val);
984 rhythmdb_commit (db);
986 g_object_unref (md);
987 return TRUE;
990 static void
991 rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd, RhythmDBEntry *entry)
993 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
995 if (type != RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
996 return;
998 rb_podcast_manager_download_entry (pd, entry);
1001 static void
1002 write_job_data (RBPodcastManagerInfo *data)
1005 GValue val = {0, };
1006 RhythmDB *db = data->pd->priv->db;
1008 rb_debug ("in the write_job");
1010 g_value_init (&val, G_TYPE_UINT64);
1011 g_value_set_uint64 (&val, data->total_size);
1012 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_FILE_SIZE, &val);
1013 g_value_unset (&val);
1015 g_value_init (&val, G_TYPE_ULONG);
1016 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
1017 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1018 g_value_unset (&val);
1020 rb_podcast_manager_save_metadata (db, data->entry,
1021 gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE));
1023 rhythmdb_commit (db);
1026 static void
1027 download_info_free (RBPodcastManagerInfo *data)
1029 if (data->write_uri) {
1030 gnome_vfs_uri_unref (data->write_uri);
1031 data->write_uri = NULL;
1034 if (data->read_uri) {
1035 gnome_vfs_uri_unref (data->read_uri);
1036 data->read_uri = NULL;
1038 if (data->query_string) {
1039 g_free (data->query_string);
1040 data->query_string = NULL;
1043 g_mutex_free (data->mutex_working);
1045 g_free (data);
1048 static RBPodcastManagerInfo*
1049 download_info_new (void)
1051 RBPodcastManagerInfo *data = g_new0 (RBPodcastManagerInfo, 1);
1052 data->mutex_working = g_mutex_new ();
1053 return data;
1056 static void
1057 start_job (RBPodcastManagerInfo *data)
1060 GList *source_uri_list = NULL;
1061 GList *target_uri_list = NULL;
1063 rb_debug ("start job");
1065 GDK_THREADS_ENTER ();
1066 g_signal_emit (data->pd, rb_podcast_manager_signals[START_DOWNLOAD],
1067 0, data->entry);
1068 GDK_THREADS_LEAVE ();
1070 source_uri_list = g_list_prepend (source_uri_list, data->read_uri);
1071 target_uri_list = g_list_prepend (target_uri_list, data->write_uri);
1073 g_mutex_lock (data->mutex_working);
1075 rb_debug ("start async copy");
1076 gnome_vfs_async_xfer ( &data->read_handle,
1077 source_uri_list,
1078 target_uri_list,
1079 GNOME_VFS_XFER_DEFAULT ,
1080 GNOME_VFS_XFER_ERROR_MODE_ABORT,
1081 GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
1082 GNOME_VFS_PRIORITY_DEFAULT,
1083 (GnomeVFSAsyncXferProgressCallback ) download_progress_update_cb,
1084 data,
1085 (GnomeVFSXferProgressCallback ) download_progress_cb,
1086 data);
1090 void
1091 rb_podcast_manager_cancel_all (RBPodcastManager *pd)
1093 guint i;
1094 guint lst_len;
1095 GList *lst;
1097 g_mutex_lock (pd->priv->download_list_mutex);
1098 lst = g_list_reverse (pd->priv->download_list);
1099 g_mutex_unlock (pd->priv->download_list_mutex);
1101 rb_debug ("cancel all job %d", g_list_length (lst));
1102 lst_len = g_list_length (lst);
1104 for (i=0; i < lst_len; i++) {
1105 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) lst->data;
1106 lst = lst->next;
1107 cancel_job (data);
1108 rb_debug ("cancel next job");
1111 if (lst_len > 0) {
1112 g_mutex_lock (pd->priv->mutex_job);
1113 g_mutex_unlock (pd->priv->mutex_job);
1117 static void
1118 end_job (RBPodcastManagerInfo *data)
1120 RBPodcastManager *pd = data->pd;
1122 rb_debug ("end_job");
1124 g_mutex_lock (data->pd->priv->download_list_mutex);
1125 data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, (gconstpointer) data);
1126 g_mutex_unlock (data->pd->priv->download_list_mutex);
1128 g_mutex_unlock (data->mutex_working);
1130 if (data->canceled != TRUE) {
1131 GDK_THREADS_ENTER ();
1133 g_signal_emit (data->pd, rb_podcast_manager_signals[FINISH_DOWNLOAD],
1134 0, data->entry);
1136 GDK_THREADS_LEAVE ();
1139 download_info_free (data);
1140 g_mutex_unlock (pd->priv->mutex_job);
1142 g_idle_add ((GtkFunction) rb_podcast_manager_next_file, pd);
1145 static void
1146 cancel_job (RBPodcastManagerInfo *data)
1148 if (g_mutex_trylock (data->mutex_working) == FALSE) {
1149 rb_debug ("async cancel");
1150 data->canceled = TRUE;
1152 else {
1153 rb_debug ("job cancel");
1155 g_mutex_lock (data->pd->priv->download_list_mutex);
1156 data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, (gconstpointer ) data);
1157 g_mutex_unlock (data->pd->priv->download_list_mutex);
1159 g_mutex_unlock (data->mutex_working);
1161 download_info_free (data);
1162 data = NULL;
1166 static guint
1167 download_progress_cb (GnomeVFSXferProgressInfo *info, gpointer cb_data)
1169 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) cb_data;
1171 if (data == NULL) {
1172 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1175 if (info->status != GNOME_VFS_XFER_PROGRESS_STATUS_OK ||
1176 ((info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) && (info->file_size == 0))) {
1177 GValue val = {0, };
1178 rb_debug ("error on download");
1179 g_value_init (&val, G_TYPE_ULONG);
1180 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
1181 GDK_THREADS_ENTER ();
1182 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1183 rhythmdb_commit (data->pd->priv->db);
1184 GDK_THREADS_LEAVE ();
1185 g_value_unset (&val);
1186 end_job (data);
1187 data = NULL;
1188 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1191 if (rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_MOUNTPOINT) == NULL) {
1192 GValue val = {0,};
1193 RhythmDB *db = data->pd->priv->db;
1194 char *uri = gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE);
1195 char *canon_uri = rb_canonicalise_uri (uri);
1196 g_free (uri);
1198 g_value_init (&val, G_TYPE_STRING);
1199 g_value_set_string (&val, canon_uri);
1200 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
1201 g_value_unset (&val);
1202 rhythmdb_commit (db);
1203 g_free (canon_uri);
1206 if (info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) {
1207 if (data->canceled != TRUE) {
1208 rb_debug ("download completed");
1209 data->total_size = info->file_size;
1210 write_job_data (data);
1212 end_job (data);
1213 data = NULL;
1214 return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
1217 if (data->canceled == TRUE) {
1218 rb_debug ("job canceled");
1219 gnome_vfs_async_cancel (data->read_handle);
1220 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1223 return 1;
1226 static guint
1227 download_progress_update_cb (GnomeVFSAsyncHandle *handle, GnomeVFSXferProgressInfo *info, gpointer cb_data)
1230 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) cb_data;
1232 if (data == NULL) {
1233 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1236 if ((info->phase == GNOME_VFS_XFER_PHASE_COPYING) &&
1237 (data->entry != NULL)) {
1238 guint local_progress = 0;
1240 if (info->file_size > 0)
1241 local_progress = (gint) 100 * info->total_bytes_copied / info->file_size;
1243 if (local_progress != data->progress) {
1244 GValue val = {0,};
1246 g_value_init (&val, G_TYPE_ULONG);
1247 g_value_set_ulong (&val, local_progress);
1248 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1249 g_value_unset (&val);
1251 GDK_THREADS_ENTER ();
1253 g_signal_emit (data->pd, rb_podcast_manager_signals[STATUS_CHANGED],
1254 0, data->entry, local_progress);
1256 GDK_THREADS_LEAVE ();
1257 data->progress = local_progress;
1261 return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
1264 void
1265 rb_podcast_manager_unsubscribe_feed (RhythmDB *db, const char *url)
1267 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (db, url);
1268 if (entry) {
1269 GValue val = {0, };
1270 g_value_init (&val, G_TYPE_ULONG);
1271 g_value_set_ulong (&val, 0);
1272 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
1273 g_value_unset (&val);
1278 gboolean
1279 rb_podcast_manager_remove_feed (RBPodcastManager *pd, const char *url, gboolean remove_files)
1281 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, url);
1283 if (entry) {
1284 rb_debug ("Removing podcast feed: %s remove_files: %d", url, remove_files);
1286 rb_podcast_manager_set_remove_files (pd, remove_files);
1287 rhythmdb_entry_delete (pd->priv->db, entry);
1288 rhythmdb_commit (pd->priv->db);
1289 return TRUE;
1292 return FALSE;
1295 static void
1296 rb_podcast_manager_db_entry_deleted_cb (RBPodcastManager *pd,
1297 RhythmDBEntry *entry)
1299 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
1301 if ((type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) && (pd->priv->remove_files == TRUE)) {
1302 const char *file_name;
1303 const char *dir_name;
1304 const char *conf_dir_name;
1305 GnomeVFSResult result;
1307 rb_debug ("Handling entry deleted");
1309 /* make sure we're not downloading it */
1310 rb_podcast_manager_cancel_download (pd, entry);
1312 file_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
1313 if (file_name == NULL) {
1314 /* episode has not been downloaded */
1315 rb_debug ("Episode not downloaded, skipping.");
1316 return;
1319 result = gnome_vfs_unlink (file_name);
1320 if (result != GNOME_VFS_OK) {
1321 rb_debug ("Removing episode failed: %s", gnome_vfs_result_to_string (result));
1322 return;
1325 /* remove dir */
1326 rb_debug ("removing dir");
1327 conf_dir_name = eel_gconf_get_string (CONF_STATE_PODCAST_DOWNLOAD_DIR);
1329 dir_name = g_build_filename (conf_dir_name,
1330 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM),
1331 NULL);
1332 gnome_vfs_remove_directory (dir_name);
1334 } else if (type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
1335 GtkTreeModel *query_model;
1336 GtkTreeIter iter;
1338 query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (pd->priv->db));
1339 rhythmdb_do_full_query (pd->priv->db,
1340 RHYTHMDB_QUERY_RESULTS (query_model),
1341 RHYTHMDB_QUERY_PROP_EQUALS,
1342 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
1343 RHYTHMDB_QUERY_PROP_LIKE,
1344 RHYTHMDB_PROP_SUBTITLE, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
1345 RHYTHMDB_QUERY_END);
1347 if (gtk_tree_model_get_iter_first (query_model, &iter)) {
1348 gboolean has_next;
1349 do {
1350 RhythmDBEntry *entry;
1352 gtk_tree_model_get (query_model, &iter, 0, &entry, -1);
1353 has_next = gtk_tree_model_iter_next (query_model, &iter);
1354 rhythmdb_entry_delete (pd->priv->db, entry);
1355 rhythmdb_entry_unref (entry);
1357 } while (has_next);
1359 rhythmdb_commit (pd->priv->db);
1362 g_object_unref (query_model);
1366 void
1367 rb_podcast_manager_cancel_download (RBPodcastManager *pd, RhythmDBEntry *entry)
1369 GList *lst;
1371 g_mutex_lock (pd->priv->download_list_mutex);
1373 lst = pd->priv->download_list;
1374 while (lst) {
1375 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) lst->data;
1376 if (data->entry == entry) {
1377 rb_debug ("Found job");
1378 break;
1380 lst = lst->next;
1382 g_mutex_unlock (pd->priv->download_list_mutex);
1384 if (lst)
1385 cancel_job (lst->data);
1388 static void
1389 rb_podcast_manager_update_synctime (RBPodcastManager *pd)
1391 gint value;
1392 gint index = eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL);
1394 switch (index)
1396 case UPDATE_EVERY_HOUR:
1397 value = time (NULL) + 3600;
1398 break;
1399 case UPDATE_EVERY_DAY:
1400 value = time (NULL) + (3600 * 24);
1401 break;
1402 case UPDATE_EVERY_WEEK:
1403 value = time (NULL) + (3600 * 24 * 7);
1404 break;
1405 case UPDATE_MANUALLY:
1406 value = 0;
1407 break;
1408 default:
1409 g_warning ("unknown download-inteval");
1410 value = 0;
1413 eel_gconf_set_integer (CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME, value);
1414 eel_gconf_suggest_sync ();
1415 pd->priv->next_time = value;
1416 rb_podcast_manager_start_sync (pd);
1419 static void
1420 rb_podcast_manager_config_changed (GConfClient* client,
1421 guint cnxn_id,
1422 GConfEntry *entry,
1423 gpointer user_data)
1425 rb_podcast_manager_update_synctime (RB_PODCAST_MANAGER (user_data));
1428 void
1429 rb_podcast_manager_set_remove_files (RBPodcastManager *pd, gboolean flag)
1431 pd->priv->remove_files = flag;
1435 gboolean
1436 rb_podcast_manager_get_remove_files (RBPodcastManager *pd)
1438 return pd->priv->remove_files;
1441 static void
1442 rb_podcast_manager_insert_feed (RBPodcastManager *pd, RBPodcastChannel *data)
1444 GValue description_val = { 0, };
1445 GValue title_val = { 0, };
1446 GValue subtitle_val = { 0, };
1447 GValue summary_val = { 0, };
1448 GValue lang_val = { 0, };
1449 GValue copyright_val = { 0, };
1450 GValue image_val = { 0, };
1451 GValue author_val = { 0, };
1452 GValue status_val = { 0, };
1453 GValue last_post_val = { 0, };
1454 GValue last_update_val = { 0, };
1455 gulong last_post = 0;
1456 gulong new_last_post;
1457 GList *download_entries = NULL;
1458 gboolean new_feed, updated, download_last;
1459 RhythmDB *db = pd->priv->db;
1461 RhythmDBEntry *entry;
1463 GList *lst_songs;
1465 if (data->title == NULL) {
1466 g_list_free (data->posts);
1467 g_free (data);
1468 return;
1471 new_feed = TRUE;
1473 /* processing podcast head */
1474 entry = rhythmdb_entry_lookup_by_location (db, (gchar *)data->url);
1475 if (entry) {
1476 if (rhythmdb_entry_get_entry_type (entry) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
1477 return;
1479 rb_debug ("Head found");
1480 g_value_init (&status_val, G_TYPE_ULONG);
1481 g_value_set_ulong (&status_val, 1);
1482 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1483 g_value_unset (&status_val);
1484 last_post = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_POST_TIME);
1485 new_feed = FALSE;
1486 } else {
1487 rb_debug ("Insert new entry");
1488 entry = rhythmdb_entry_new (db,
1489 RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
1490 (gchar *) data->url);
1491 if (entry == NULL)
1492 return;
1493 rb_debug("New entry create\n");
1495 g_value_init (&title_val, G_TYPE_STRING);
1496 g_value_set_string (&title_val, (gchar * ) data->title);
1497 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &title_val);
1498 g_value_unset (&title_val);
1500 g_value_init (&author_val, G_TYPE_STRING);
1501 if (data->author)
1502 g_value_set_string (&author_val, (gchar *) data->author);
1503 else
1504 g_value_set_static_string (&author_val, _("Unknown"));
1505 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &author_val);
1506 g_value_unset (&author_val);
1508 if (data->subtitle) {
1509 g_value_init (&subtitle_val, G_TYPE_STRING);
1510 g_value_set_string (&subtitle_val, (gchar *) data->subtitle);
1511 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUBTITLE, &subtitle_val);
1512 g_value_unset (&subtitle_val);
1515 if (data->description) {
1516 g_value_init (&description_val, G_TYPE_STRING);
1517 g_value_set_string (&description_val, (gchar *) data->description);
1518 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DESCRIPTION, &description_val);
1519 g_value_unset (&description_val);
1522 if (data->summary) {
1523 g_value_init (&summary_val, G_TYPE_STRING);
1524 g_value_set_string (&summary_val, (gchar *) data->summary);
1525 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUMMARY, &summary_val);
1526 g_value_unset (&summary_val);
1529 if (data->lang) {
1530 g_value_init (&lang_val, G_TYPE_STRING);
1531 g_value_set_string (&lang_val, (gchar *) data->lang);
1532 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LANG, &lang_val);
1533 g_value_unset (&lang_val);
1536 if (data->copyright) {
1537 g_value_init (&copyright_val, G_TYPE_STRING);
1538 g_value_set_string (&copyright_val, (gchar *) data->copyright);
1539 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_COPYRIGHT, &copyright_val);
1540 g_value_unset (&copyright_val);
1543 if (data->img) {
1544 g_value_init (&image_val, G_TYPE_STRING);
1545 g_value_set_string (&image_val, (gchar *) data->img);
1546 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_IMAGE, &image_val);
1547 g_value_unset (&image_val);
1550 g_value_init (&status_val, G_TYPE_ULONG);
1551 g_value_set_ulong (&status_val, 1);
1552 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1553 g_value_unset (&status_val);
1555 rb_debug("Podcast head Inserted");
1558 /* insert episodes */
1559 new_last_post = last_post;
1561 updated = FALSE;
1562 download_last = (eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL) != UPDATE_MANUALLY);
1563 for (lst_songs = data->posts; lst_songs != NULL; lst_songs = g_list_next (lst_songs)) {
1564 RBPodcastItem *item = (RBPodcastItem *) lst_songs->data;
1565 RhythmDBEntry *post_entry;
1567 if (item->pub_date > last_post || item->pub_date == 0) {
1568 updated = TRUE;
1570 post_entry =
1571 rb_podcast_manager_add_post (db,
1572 (gchar *) data->title,
1573 (gchar *) item->title,
1574 (gchar *) data->url,
1575 (gchar *) (item->author ? item->author : data->author),
1576 (gchar *) item->url,
1577 (gchar *) item->description,
1578 (gulong) (item->pub_date > 0 ? item->pub_date : data->pub_date),
1579 (gulong) item->duration,
1580 item->filesize);
1581 if (post_entry && item->pub_date >= new_last_post) {
1582 if (item->pub_date > new_last_post) {
1583 g_list_free (download_entries);
1584 download_entries = NULL;
1586 download_entries = g_list_prepend (download_entries, post_entry);
1587 new_last_post = item->pub_date;
1592 if (download_last) {
1593 GValue status = {0,};
1594 GList *t;
1596 g_value_init (&status, G_TYPE_ULONG);
1597 g_value_set_ulong (&status, RHYTHMDB_PODCAST_STATUS_WAITING);
1598 for (t = download_entries; t != NULL; t = g_list_next (t)) {
1599 rhythmdb_entry_set (db,
1600 (RhythmDBEntry*) t->data,
1601 RHYTHMDB_PROP_STATUS,
1602 &status);
1604 g_value_unset (&status);
1606 g_list_free (download_entries);
1608 if (updated)
1609 g_signal_emit (pd, rb_podcast_manager_signals[FEED_UPDATES_AVALIABLE],
1610 0, entry);
1612 if (data->pub_date > new_last_post)
1613 new_last_post = data->pub_date;
1615 g_value_init (&last_post_val, G_TYPE_ULONG);
1616 g_value_set_ulong (&last_post_val, new_last_post);
1618 if (new_feed)
1619 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
1620 else
1621 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
1622 g_value_unset (&last_post_val);
1624 g_value_init (&last_update_val, G_TYPE_ULONG);
1625 g_value_set_ulong (&last_update_val, time(NULL));
1627 if (new_feed)
1628 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1629 else
1630 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1631 g_value_unset (&last_update_val);
1633 rhythmdb_commit (db);
1636 static gboolean
1637 rb_podcast_manager_event_loop (RBPodcastManager *pd)
1639 RBPodcastManagerEvent *event;
1641 GDK_THREADS_ENTER ();
1643 while ((event = g_async_queue_try_pop (pd->priv->event_queue))) {
1644 switch (event->type)
1646 case EVENT_INSERT_FEED:
1647 rb_podcast_manager_insert_feed (pd, event->channel);
1648 break;
1649 case EVENT_ERROR_FEED:
1651 gchar *error_msg;
1652 error_msg = g_strdup_printf (_("There was a problem adding this podcast. Please verify the URL: %s"),
1653 (gchar *) event->channel->url);
1654 g_signal_emit (G_OBJECT (pd),
1655 rb_podcast_manager_signals[PROCESS_ERROR],
1656 0, error_msg);
1657 g_free (error_msg);
1658 break;
1662 rb_podcast_parse_channel_free (event->channel);
1663 g_free (event);
1666 g_async_queue_unref (pd->priv->event_queue);
1668 GDK_THREADS_LEAVE ();
1669 return FALSE;
1672 static void
1673 rb_podcast_manager_abort_subscribe (RBPodcastManager *pd)
1675 RBPodcastManagerEvent *event;
1677 /* remove all event processing functions */
1678 while (g_idle_remove_by_data (pd))
1681 /* purge the event queue */
1682 while ((event = g_async_queue_try_pop (pd->priv->event_queue))) {
1683 rb_podcast_parse_channel_free (event->channel);
1684 g_free (event);
1688 void
1689 rb_podcast_manager_shutdown (RBPodcastManager *pd)
1691 rb_podcast_manager_cancel_all (pd);
1692 rb_podcast_manager_abort_subscribe (pd);
1695 gchar *
1696 rb_podcast_manager_get_podcast_dir (RBPodcastManager *pd)
1698 gchar *conf_dir_name = eel_gconf_get_string (CONF_STATE_PODCAST_DOWNLOAD_DIR);
1700 if (conf_dir_name == NULL || (strcmp (conf_dir_name, "") == 0)) {
1701 conf_dir_name = g_build_filename (g_get_home_dir (),
1702 "Podcasts",
1703 NULL);
1704 eel_gconf_set_string (CONF_STATE_PODCAST_DOWNLOAD_DIR, conf_dir_name);
1707 return conf_dir_name;