Updated Macedonian Translation <ufo@linux.net.mk>
[rhythmbox.git] / podcast / rb-podcast-manager.c
blob9606a7c8624d93d53fb15f23d3d1e3bd7b5deb59
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 GMutex *mutex_working;
115 guint total_size;
116 guint progress;
117 gboolean canceled;
118 } RBPodcastManagerInfo;
120 /* used on subscribe thread */
121 typedef struct
123 RBPodcastManager *pd;
124 char *url;
125 } RBPodcastThreadInfo;
127 static guint rb_podcast_manager_signals[LAST_SIGNAL] = { 0 };
129 /* functions */
130 static void rb_podcast_manager_class_init (RBPodcastManagerClass *klass);
131 static void rb_podcast_manager_init (RBPodcastManager *dp);
132 static GObject *rb_podcast_manager_constructor (GType type, guint n_construct_properties,
133 GObjectConstructParam *construct_properties);
134 static void rb_podcast_manager_finalize (GObject *object);
135 static void rb_podcast_manager_set_property (GObject *object,
136 guint prop_id,
137 const GValue *value,
138 GParamSpec *pspec);
139 static void rb_podcast_manager_get_property (GObject *object,
140 guint prop_id,
141 GValue *value,
142 GParamSpec *pspec);
143 static void rb_podcast_manager_copy_post (RBPodcastManager *pd);
144 static gboolean rb_podcast_manager_sync_head_cb (gpointer data);
145 static gboolean rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
146 GtkTreePath *path,
147 GtkTreeIter *iter,
148 RBPodcastManager *data);
149 static gboolean rb_podcast_manager_save_metadata (RhythmDB *db,
150 RhythmDBEntry *entry,
151 const char *uri);
152 static void rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd,
153 RhythmDBEntry *entry);
154 static void rb_podcast_manager_db_entry_deleted_cb (RBPodcastManager *pd,
155 RhythmDBEntry *entry);
156 static gboolean rb_podcast_manager_next_file (RBPodcastManager * pd);
157 static void rb_podcast_manager_insert_feed (RBPodcastManager *pd, RBPodcastChannel *data);
158 static void rb_podcast_manager_abort_subscribe (RBPodcastManager *pd);
160 /* event loop */
161 static gboolean rb_podcast_manager_event_loop (RBPodcastManager *pd) ;
162 static gpointer rb_podcast_manager_thread_parse_feed (RBPodcastThreadInfo *info);
164 /* async read file functions */
165 static guint download_progress_cb (GnomeVFSXferProgressInfo *info,
166 gpointer data);
167 static guint download_progress_update_cb (GnomeVFSAsyncHandle *handle,
168 GnomeVFSXferProgressInfo *info,
169 gpointer data);
171 /* internal functions */
172 static void download_info_free (RBPodcastManagerInfo *data);
173 static RBPodcastManagerInfo *download_info_new (void);
174 static void start_job (RBPodcastManagerInfo *data);
175 static void end_job (RBPodcastManagerInfo *data);
176 static void cancel_job (RBPodcastManagerInfo *pd);
177 static void write_job_data (RBPodcastManagerInfo *data);
178 static void rb_podcast_manager_update_synctime (RBPodcastManager *pd);
179 static void rb_podcast_manager_config_changed (GConfClient* client,
180 guint cnxn_id,
181 GConfEntry *entry,
182 gpointer user_data);
184 G_DEFINE_TYPE (RBPodcastManager, rb_podcast_manager, G_TYPE_OBJECT)
186 static void
187 rb_podcast_manager_class_init (RBPodcastManagerClass *klass)
189 GObjectClass *object_class = G_OBJECT_CLASS (klass);
191 object_class->constructor = rb_podcast_manager_constructor;
192 object_class->finalize = rb_podcast_manager_finalize;
194 object_class->set_property = rb_podcast_manager_set_property;
195 object_class->get_property = rb_podcast_manager_get_property;
197 g_object_class_install_property (object_class,
198 PROP_DB,
199 g_param_spec_object ("db",
200 "db",
201 "database",
202 RHYTHMDB_TYPE,
203 G_PARAM_READWRITE));
205 rb_podcast_manager_signals[STATUS_CHANGED] =
206 g_signal_new ("status_changed",
207 G_OBJECT_CLASS_TYPE (object_class),
208 GTK_RUN_LAST,
209 G_STRUCT_OFFSET (RBPodcastManagerClass, status_changed),
210 NULL, NULL,
211 rb_marshal_VOID__BOXED_ULONG,
212 G_TYPE_NONE,
214 RHYTHMDB_TYPE_ENTRY,
215 G_TYPE_ULONG);
217 rb_podcast_manager_signals[START_DOWNLOAD] =
218 g_signal_new ("start_download",
219 G_OBJECT_CLASS_TYPE (object_class),
220 GTK_RUN_LAST,
221 G_STRUCT_OFFSET (RBPodcastManagerClass, start_download),
222 NULL, NULL,
223 g_cclosure_marshal_VOID__BOXED,
224 G_TYPE_NONE,
226 RHYTHMDB_TYPE_ENTRY);
228 rb_podcast_manager_signals[FINISH_DOWNLOAD] =
229 g_signal_new ("finish_download",
230 G_OBJECT_CLASS_TYPE (object_class),
231 GTK_RUN_LAST,
232 G_STRUCT_OFFSET (RBPodcastManagerClass, finish_download),
233 NULL, NULL,
234 g_cclosure_marshal_VOID__BOXED,
235 G_TYPE_NONE,
237 RHYTHMDB_TYPE_ENTRY);
239 rb_podcast_manager_signals[FEED_UPDATES_AVALIABLE] =
240 g_signal_new ("feed_updates_avaliable",
241 G_OBJECT_CLASS_TYPE (object_class),
242 GTK_RUN_LAST,
243 G_STRUCT_OFFSET (RBPodcastManagerClass, feed_updates_avaliable),
244 NULL, NULL,
245 g_cclosure_marshal_VOID__BOXED,
246 G_TYPE_NONE,
248 RHYTHMDB_TYPE_ENTRY);
250 rb_podcast_manager_signals[PROCESS_ERROR] =
251 g_signal_new ("process_error",
252 G_OBJECT_CLASS_TYPE (object_class),
253 GTK_RUN_LAST,
254 G_STRUCT_OFFSET (RBPodcastManagerClass, process_error),
255 NULL, NULL,
256 g_cclosure_marshal_VOID__STRING,
257 G_TYPE_NONE,
259 G_TYPE_STRING);
261 g_type_class_add_private (klass, sizeof (RBPodcastManagerPrivate));
264 static void
265 rb_podcast_manager_init (RBPodcastManager *pd)
267 pd->priv = RB_PODCAST_MANAGER_GET_PRIVATE (pd);
269 pd->priv->source_sync = 0;
270 pd->priv->mutex_job = g_mutex_new();
271 pd->priv->download_list_mutex = g_mutex_new();
272 pd->priv->event_queue = g_async_queue_new ();
273 pd->priv->db = NULL;
274 eel_gconf_monitor_add (CONF_STATE_PODCAST_PREFIX);
277 static GObject *
278 rb_podcast_manager_constructor (GType type, guint n_construct_properties,
279 GObjectConstructParam *construct_properties)
281 RBPodcastManager *pd;
283 pd = RB_PODCAST_MANAGER (G_OBJECT_CLASS (rb_podcast_manager_parent_class)
284 ->constructor (type, n_construct_properties, construct_properties));
286 pd->priv->update_interval_notify_id = eel_gconf_notification_add (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL,
287 rb_podcast_manager_config_changed,
288 pd);
290 return G_OBJECT (pd);
294 static void
295 rb_podcast_manager_finalize (GObject *object)
297 RBPodcastManager *pd;
298 g_return_if_fail (object != NULL);
299 g_return_if_fail (RB_IS_PODCAST_MANAGER (object));
301 pd = RB_PODCAST_MANAGER(object);
303 g_return_if_fail (pd->priv != NULL);
305 eel_gconf_monitor_remove (CONF_STATE_PODCAST_PREFIX);
307 if (pd->priv->source_sync) {
308 g_source_remove (pd->priv->source_sync);
309 pd->priv->source_sync = 0;
312 eel_gconf_notification_remove (pd->priv->update_interval_notify_id);
314 if (pd->priv->download_list) {
315 g_list_foreach (pd->priv->download_list, (GFunc)g_free, NULL);
316 g_list_free (pd->priv->download_list);
319 g_mutex_free (pd->priv->mutex_job);
320 g_mutex_free (pd->priv->download_list_mutex);
321 g_async_queue_unref (pd->priv->event_queue);
323 G_OBJECT_CLASS (rb_podcast_manager_parent_class)->finalize (object);
324 rb_debug ("Podcast Manager END");
327 static void
328 rb_podcast_manager_set_property (GObject *object,
329 guint prop_id,
330 const GValue *value,
331 GParamSpec *pspec)
333 RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
335 switch (prop_id) {
336 case PROP_DB:
337 if (pd->priv->db) {
338 g_signal_handlers_disconnect_by_func (G_OBJECT (pd->priv->db),
339 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
340 pd);
342 g_signal_handlers_disconnect_by_func (G_OBJECT (pd->priv->db),
343 G_CALLBACK (rb_podcast_manager_db_entry_deleted_cb),
344 pd);
348 pd->priv->db = g_value_get_object (value);
350 g_signal_connect_object (G_OBJECT (pd->priv->db),
351 "entry-added",
352 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
353 pd, G_CONNECT_SWAPPED);
355 g_signal_connect_object (G_OBJECT (pd->priv->db),
356 "entry_deleted",
357 G_CALLBACK (rb_podcast_manager_db_entry_deleted_cb),
358 pd, G_CONNECT_SWAPPED);
360 break;
361 default:
362 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
366 static void
367 rb_podcast_manager_get_property (GObject *object,
368 guint prop_id,
369 GValue *value,
370 GParamSpec *pspec)
372 RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
374 switch (prop_id) {
375 case PROP_DB:
376 g_value_set_object (value, pd->priv->db);
377 break;
378 default:
379 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
384 RBPodcastManager *
385 rb_podcast_manager_new (RhythmDB *db)
387 RBPodcastManager *pd;
389 pd = g_object_new (RB_TYPE_PODCAST_MANAGER, "db", db, NULL);
390 return pd;
393 void
394 rb_podcast_manager_download_entry (RBPodcastManager *pd,
395 RhythmDBEntry *entry)
397 gulong status;
399 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
401 if (entry == NULL)
402 return;
404 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
405 if ((status < RHYTHMDB_PODCAST_STATUS_COMPLETE) ||
406 (status == RHYTHMDB_PODCAST_STATUS_WAITING)) {
407 RBPodcastManagerInfo *data;
408 if (status < RHYTHMDB_PODCAST_STATUS_COMPLETE) {
409 GValue status_val = { 0, };
410 g_value_init (&status_val, G_TYPE_ULONG);
411 g_value_set_ulong (&status_val, RHYTHMDB_PODCAST_STATUS_WAITING);
412 rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &status_val);
413 g_value_unset (&status_val);
415 rb_debug ("Try insert entry for download.");
416 data = download_info_new();
417 data->pd = pd;
418 data->entry = entry;
419 g_mutex_lock (pd->priv->download_list_mutex);
420 pd->priv->download_list = g_list_append (pd->priv->download_list, data);
421 g_mutex_unlock (pd->priv->download_list_mutex);
422 gtk_idle_add ((GtkFunction) rb_podcast_manager_next_file , pd);
426 gboolean
427 rb_podcast_manager_entry_downloaded (RhythmDBEntry *entry)
429 gulong status;
430 const gchar *file_name;
431 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
433 g_assert (type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
435 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
436 file_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
438 return (status != RHYTHMDB_PODCAST_STATUS_ERROR && file_name != NULL);
441 void
442 rb_podcast_manager_start_sync (RBPodcastManager *pd)
444 gint next_time;
446 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
448 if (pd->priv->next_time > 0) {
449 next_time = pd->priv->next_time;
450 } else {
451 next_time = eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME);
454 if (next_time > 0) {
455 if (pd->priv->source_sync != 0) {
456 g_source_remove (pd->priv->source_sync);
457 pd->priv->source_sync = 0;
459 next_time = next_time - ((int)time (NULL));
460 if (next_time <= 0) {
461 rb_podcast_manager_update_feeds (pd);
462 pd->priv->next_time = 0;
463 rb_podcast_manager_update_synctime (pd);
464 return;
466 pd->priv->source_sync = g_timeout_add (next_time * 1000, (GSourceFunc ) rb_podcast_manager_sync_head_cb, pd);
471 static gboolean
472 rb_podcast_manager_sync_head_cb (gpointer data)
474 RBPodcastManager *pd = RB_PODCAST_MANAGER (data);
475 rb_podcast_manager_update_feeds (pd);
476 pd->priv->source_sync = 0;
477 pd->priv->next_time = 0;
478 rb_podcast_manager_update_synctime (RB_PODCAST_MANAGER (data));
479 return FALSE;
482 void
483 rb_podcast_manager_update_feeds (RBPodcastManager *pd)
485 GtkTreeModel *query_model;
487 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
489 query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (pd->priv->db));
491 rhythmdb_do_full_query (pd->priv->db,
492 RHYTHMDB_QUERY_RESULTS (query_model),
493 RHYTHMDB_QUERY_PROP_EQUALS,
494 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
495 RHYTHMDB_QUERY_END);
497 gtk_tree_model_foreach (query_model,
498 (GtkTreeModelForeachFunc) rb_podcast_manager_head_query_cb,
499 pd);
501 g_object_unref (query_model);
504 static gboolean
505 rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
506 GtkTreePath *path,
507 GtkTreeIter *iter,
508 RBPodcastManager *manager)
510 const char *uri;
511 RhythmDBEntry *entry;
512 guint status;
514 gtk_tree_model_get (query_model, iter, 0, &entry, -1);
515 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
516 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
518 if (status == 1)
519 rb_podcast_manager_subscribe_feed (manager, uri);
521 rhythmdb_entry_unref (entry);
523 return FALSE;
526 static gboolean
527 rb_podcast_manager_next_file (RBPodcastManager * pd)
530 rb_debug ("try lock file_process mutex");
531 if (g_mutex_trylock (pd->priv->mutex_job) == TRUE) {
532 gint size;
534 g_mutex_lock (pd->priv->download_list_mutex);
535 size = g_list_length (pd->priv->download_list);
536 g_mutex_unlock (pd->priv->download_list_mutex);
538 if (size > 0)
539 rb_podcast_manager_copy_post (pd);
540 else
541 g_mutex_unlock (pd->priv->mutex_job);
542 } else {
543 rb_debug ("not start");
546 return FALSE;
549 static void
550 rb_podcast_manager_copy_post (RBPodcastManager *pd)
552 GnomeVFSURI *remote_uri = NULL;
553 GnomeVFSURI *local_uri = NULL;
554 GValue location_val = { 0, };
555 const char *location, *album_name;
556 char *short_name, *local_file_name;
557 char *dir_name, *conf_dir_name;
558 RBPodcastManagerInfo *data = NULL;
559 RhythmDBEntry *entry;
561 rb_debug ("Stating copy file");
562 g_value_init (&location_val, G_TYPE_STRING);
564 /* get first element of list */
565 g_mutex_lock (pd->priv->download_list_mutex);
566 data = (RBPodcastManagerInfo *) g_list_first (pd->priv->download_list)->data;
567 g_mutex_unlock (pd->priv->download_list_mutex);
569 if (data == NULL)
570 return;
572 entry = data->entry;
574 g_assert (entry != NULL);
576 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
577 album_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
579 rb_debug ("processing %s", location);
581 remote_uri = gnome_vfs_uri_new (location);
582 if (!remote_uri) {
583 rb_debug ("Error downloading podcast: could not create remote uri");
584 goto next_step;
587 conf_dir_name = rb_podcast_manager_get_podcast_dir (pd);
588 dir_name = g_build_filename (conf_dir_name,
589 album_name,
590 NULL);
591 g_free (conf_dir_name);
593 if (!g_file_test (dir_name, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
594 if (g_mkdir_with_parents (dir_name, 0750) == -1) {
595 rb_debug ("Error downloading podcast: could not create local dirs %s", dir_name);
596 goto next_step;
600 short_name = gnome_vfs_uri_extract_short_name (remote_uri);
601 local_file_name = g_build_filename (dir_name,
602 short_name,
603 NULL);
604 g_free (short_name);
605 g_free (dir_name);
607 rb_debug ("creating file %s\n", local_file_name);
609 local_uri = gnome_vfs_uri_new (local_file_name);
610 if (!local_uri) {
611 rb_debug ("Error downloading podcast: could not create local uri");
612 goto next_step;
615 if (g_file_test (local_file_name, G_FILE_TEST_EXISTS)) {
616 guint64 remote_size;
617 GnomeVFSFileInfo *info = gnome_vfs_file_info_new ();
618 GnomeVFSResult result;
620 result = gnome_vfs_get_file_info_uri (remote_uri, info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
621 if (result != GNOME_VFS_OK) {
622 rb_debug ("unable to retrieve info on remote of podcast");
623 goto next_step;
624 } else {
625 remote_size = info->size;
628 result = gnome_vfs_get_file_info (local_file_name, info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
629 if (result != GNOME_VFS_OK) {
630 rb_debug ("unable to retrieve info on local copy of podcast");
631 goto next_step;
632 } else if (remote_size == info->size) {
633 GValue val = {0,};
634 char *uri = gnome_vfs_uri_to_string (local_uri, GNOME_VFS_URI_HIDE_NONE);
635 char *canon_uri = rb_canonicalise_uri (uri);
636 g_free (uri);
638 rb_debug ("podcast %s already downloaded", location);
640 g_value_init (&val, G_TYPE_ULONG);
641 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
642 rhythmdb_entry_set (pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
643 g_value_unset (&val);
645 g_value_init (&val, G_TYPE_STRING);
646 g_value_set_string (&val, canon_uri);
647 rhythmdb_entry_set (pd->priv->db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
648 g_value_unset (&val);
650 rb_podcast_manager_save_metadata (pd->priv->db, data->entry, canon_uri);
651 rhythmdb_commit (pd->priv->db);
652 g_free (canon_uri);
654 goto next_step;
655 } else if (remote_size > info->size) {
656 /* TODO: suport resume file */
657 } else {
658 /* the local file is larger. replace it */
661 gnome_vfs_file_info_unref (info);
664 g_free (local_file_name);
666 data->read_uri = remote_uri;
667 data->write_uri = local_uri;
669 start_job (data);
670 return;
672 next_step:
674 if (remote_uri)
675 gnome_vfs_uri_unref (remote_uri);
677 if (local_uri)
678 gnome_vfs_uri_unref (local_uri);
680 g_mutex_lock (pd->priv->download_list_mutex);
681 pd->priv->download_list = g_list_remove (pd->priv->download_list, (gconstpointer ) data);
682 g_mutex_unlock (pd->priv->download_list_mutex);
684 download_info_free (data);
685 data = NULL;
687 g_mutex_unlock (pd->priv->mutex_job);
688 gtk_idle_add ((GtkFunction) rb_podcast_manager_next_file , pd);
691 gboolean
692 rb_podcast_manager_subscribe_feed (RBPodcastManager *pd, const char *url)
694 RBPodcastThreadInfo *info;
695 gchar *valid_url = gnome_vfs_make_uri_from_input (url);
697 if (valid_url == NULL) {
698 rb_error_dialog (NULL, _("Invalid URL"),
699 _("The URL \"%s\" is not valid, please check it."), url);
700 return FALSE;
703 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, valid_url);
704 if (entry) {
705 if (rhythmdb_entry_get_entry_type (entry) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
706 /* added as something else, probably iradio */
707 rb_error_dialog (NULL, _("URL already added"),
708 _("The URL \"%s\" has already been added as a radio station. "
709 "If this is a podcast feed, please remove the radio station."), url);
710 return FALSE;
714 info = g_new0 (RBPodcastThreadInfo, 1);
715 info->pd = pd;
716 info->url = valid_url;
718 g_async_queue_ref (info->pd->priv->event_queue);
719 g_thread_create ((GThreadFunc) rb_podcast_manager_thread_parse_feed,
720 info, FALSE, NULL);
722 return TRUE;
725 static gpointer
726 rb_podcast_manager_thread_parse_feed (RBPodcastThreadInfo *info)
728 RBPodcastManagerEvent *event = g_new0 (RBPodcastManagerEvent, 1);
729 RBPodcastChannel *feed = g_new0 (RBPodcastChannel, 1);
731 if (rb_podcast_parse_load_feed (feed, info->url)) {
732 event->channel = feed;
733 event->type = (feed->title == NULL) ? EVENT_ERROR_FEED : EVENT_INSERT_FEED;
735 g_async_queue_push (info->pd->priv->event_queue, event);
736 g_idle_add ((GSourceFunc) rb_podcast_manager_event_loop, info->pd);
739 g_free (info->url);
740 g_free (info);
741 return NULL;
744 RhythmDBEntry *
745 rb_podcast_manager_add_post (RhythmDB *db,
746 const char *name,
747 const char *title,
748 const char *subtitle,
749 const char *generator,
750 const char *uri,
751 const char *description,
752 gulong date,
753 gulong duration,
754 guint64 filesize)
756 RhythmDBEntry *entry;
757 GValue val = {0,};
758 GTimeVal time;
760 if (!uri || !name || !title || !date || !g_utf8_validate(uri, -1, NULL)) {
761 return NULL;
763 entry = rhythmdb_entry_lookup_by_location (db, uri);
764 if (entry)
765 return NULL;
767 entry = rhythmdb_entry_new (db,
768 RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
769 uri);
770 if (entry == NULL)
771 return NULL;
773 g_value_init (&val, G_TYPE_STRING);
774 g_value_set_string (&val, name);
775 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ALBUM, &val);
777 g_value_reset (&val);
778 g_value_set_static_string (&val, _("Podcast"));
779 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_GENRE, &val);
781 g_value_reset (&val);
782 g_value_set_string (&val, title);
783 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &val);
785 g_value_reset (&val);
786 if (subtitle)
787 g_value_set_string (&val, subtitle);
788 else
789 g_value_set_static_string (&val, "");
790 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUBTITLE, &val);
792 g_value_reset (&val);
793 if (description)
794 g_value_set_string (&val, description);
795 else
796 g_value_set_static_string (&val, "");
797 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DESCRIPTION, &val);
799 g_value_reset (&val);
800 if (generator)
801 g_value_set_string (&val, generator);
802 else
803 g_value_set_static_string (&val, "");
804 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &val);
805 g_value_unset (&val);
807 g_value_init (&val, G_TYPE_ULONG);
808 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_PAUSED);
809 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
811 g_value_reset (&val);
812 g_value_set_ulong (&val, date);
813 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &val);
815 g_value_reset (&val);
816 g_value_set_ulong (&val, duration);
817 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val);
819 g_value_reset (&val);
820 g_value_set_ulong (&val, 0);
821 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_PLAYED, &val);
823 /* first seen */
824 g_get_current_time (&time);
825 g_value_reset (&val);
826 g_value_set_ulong (&val, time.tv_sec);
827 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &val);
828 g_value_unset (&val);
830 /* initialize the rating */
831 g_value_init (&val, G_TYPE_DOUBLE);
832 g_value_set_double (&val, 2.5);
833 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &val);
834 g_value_unset (&val);
836 g_value_init (&val, G_TYPE_UINT64);
837 g_value_set_uint64 (&val, filesize);
838 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FILE_SIZE, &val);
839 g_value_unset (&val);
841 return entry;
844 static gboolean
845 rb_podcast_manager_save_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *uri)
847 RBMetaData *md = rb_metadata_new();
848 GError *error = NULL;
849 GValue val = { 0, };
850 const char *mime;
852 rb_debug("Loading podcast metadata");
853 rb_metadata_load (md, uri, &error);
855 if (error != NULL) {
856 /* this probably isn't an audio enclosure. or some other error */
857 g_value_init (&val, G_TYPE_ULONG);
858 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
859 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
860 g_value_unset (&val);
862 g_value_init (&val, G_TYPE_STRING);
863 g_value_set_string (&val, error->message);
864 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
865 g_value_unset (&val);
867 rhythmdb_commit (db);
869 g_object_unref (md);
870 g_error_free (error);
872 return FALSE;
875 mime = rb_metadata_get_mime (md);
876 if (mime) {
877 g_value_init (&val, G_TYPE_STRING);
878 g_value_set_string (&val, mime);
879 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MIMETYPE, &val);
880 g_value_unset (&val);
883 if (rb_metadata_get (md,
884 RB_METADATA_FIELD_DURATION,
885 &val)) {
886 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val);
887 g_value_unset (&val);
890 if (rb_metadata_get (md,
891 RB_METADATA_FIELD_BITRATE,
892 &val)) {
893 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_BITRATE, &val);
894 g_value_unset (&val);
897 rhythmdb_commit (db);
899 g_object_unref (md);
900 return TRUE;
903 static void
904 rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd, RhythmDBEntry *entry)
906 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
908 if (type != RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
909 return;
911 rb_podcast_manager_download_entry (pd, entry);
914 static void
915 write_job_data (RBPodcastManagerInfo *data)
918 GValue val = {0, };
919 RhythmDB *db = data->pd->priv->db;
921 rb_debug ("in the write_job");
923 g_value_init (&val, G_TYPE_UINT64);
924 g_value_set_uint64 (&val, data->total_size);
925 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_FILE_SIZE, &val);
926 g_value_unset (&val);
928 g_value_init (&val, G_TYPE_ULONG);
929 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
930 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_STATUS, &val);
931 g_value_unset (&val);
933 rb_podcast_manager_save_metadata (db, data->entry,
934 gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE));
936 rhythmdb_commit (db);
939 static void
940 download_info_free (RBPodcastManagerInfo *data)
942 if (data->write_uri) {
943 gnome_vfs_uri_unref (data->write_uri);
944 data->write_uri = NULL;
947 if (data->read_uri) {
948 gnome_vfs_uri_unref (data->read_uri);
949 data->read_uri = NULL;
952 g_mutex_free (data->mutex_working);
954 g_free (data);
957 static RBPodcastManagerInfo*
958 download_info_new (void)
960 RBPodcastManagerInfo *data = g_new0 (RBPodcastManagerInfo, 1);
961 data->pd = NULL;
962 data->entry = NULL;
963 data->write_uri = NULL;
964 data->read_uri = NULL;
965 data->mutex_working = g_mutex_new ();
966 data->total_size = 0;
967 data->progress = 0;
968 data->canceled = FALSE;
970 return data;
973 static void
974 start_job (RBPodcastManagerInfo *data)
977 GList *source_uri_list = NULL;
978 GList *target_uri_list = NULL;
980 rb_debug ("start job");
982 GDK_THREADS_ENTER ();
983 g_signal_emit (data->pd, rb_podcast_manager_signals[START_DOWNLOAD],
984 0, data->entry);
985 GDK_THREADS_LEAVE ();
987 source_uri_list = g_list_prepend (source_uri_list, data->read_uri);
988 target_uri_list = g_list_prepend (target_uri_list, data->write_uri);
990 g_mutex_lock (data->mutex_working);
992 rb_debug ("start async copy");
993 gnome_vfs_async_xfer ( &data->read_handle,
994 source_uri_list,
995 target_uri_list,
996 GNOME_VFS_XFER_DEFAULT ,
997 GNOME_VFS_XFER_ERROR_MODE_ABORT,
998 GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
999 GNOME_VFS_PRIORITY_DEFAULT,
1000 (GnomeVFSAsyncXferProgressCallback ) download_progress_update_cb,
1001 data,
1002 (GnomeVFSXferProgressCallback ) download_progress_cb,
1003 data);
1007 void
1008 rb_podcast_manager_cancel_all (RBPodcastManager *pd)
1010 guint i;
1011 guint lst_len;
1012 GList *lst;
1014 g_mutex_lock (pd->priv->download_list_mutex);
1015 lst = g_list_reverse (pd->priv->download_list);
1016 g_mutex_unlock (pd->priv->download_list_mutex);
1018 rb_debug ("cancel all job %d", g_list_length (lst));
1019 lst_len = g_list_length (lst);
1021 for (i=0; i < lst_len; i++) {
1022 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) lst->data;
1023 lst = lst->next;
1024 cancel_job (data);
1025 rb_debug ("cancel next job");
1028 if (lst_len > 0) {
1029 g_mutex_lock (pd->priv->mutex_job);
1030 g_mutex_unlock (pd->priv->mutex_job);
1034 static void
1035 end_job (RBPodcastManagerInfo *data)
1037 RBPodcastManager *pd = data->pd;
1039 rb_debug ("end_job");
1041 g_mutex_lock (data->pd->priv->download_list_mutex);
1042 data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, (gconstpointer) data);
1043 g_mutex_unlock (data->pd->priv->download_list_mutex);
1045 g_mutex_unlock (data->mutex_working);
1047 if (data->canceled != TRUE) {
1048 GDK_THREADS_ENTER ();
1050 g_signal_emit (data->pd, rb_podcast_manager_signals[FINISH_DOWNLOAD],
1051 0, data->entry);
1053 GDK_THREADS_LEAVE ();
1056 download_info_free (data);
1057 g_mutex_unlock (pd->priv->mutex_job);
1059 gtk_idle_add ((GtkFunction) rb_podcast_manager_next_file, pd);
1062 static void
1063 cancel_job (RBPodcastManagerInfo *data)
1065 if (g_mutex_trylock (data->mutex_working) == FALSE) {
1066 rb_debug ("async cancel");
1067 data->canceled = TRUE;
1069 else {
1070 rb_debug ("job cancel");
1072 g_mutex_lock (data->pd->priv->download_list_mutex);
1073 data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, (gconstpointer ) data);
1074 g_mutex_unlock (data->pd->priv->download_list_mutex);
1076 g_mutex_unlock (data->mutex_working);
1078 download_info_free (data);
1079 data = NULL;
1083 static guint
1084 download_progress_cb (GnomeVFSXferProgressInfo *info, gpointer cb_data)
1086 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) cb_data;
1088 if (data == NULL) {
1089 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1092 if (info->status != GNOME_VFS_XFER_PROGRESS_STATUS_OK ||
1093 ((info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) && (info->file_size == 0))) {
1094 GValue val = {0, };
1095 rb_debug ("error on download");
1096 g_value_init (&val, G_TYPE_ULONG);
1097 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
1098 GDK_THREADS_ENTER ();
1099 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1100 rhythmdb_commit (data->pd->priv->db);
1101 GDK_THREADS_LEAVE ();
1102 g_value_unset (&val);
1103 end_job (data);
1104 data = NULL;
1105 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1108 if (rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_MOUNTPOINT) == NULL) {
1109 GValue val = {0,};
1110 RhythmDB *db = data->pd->priv->db;
1111 char *uri = gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE);
1112 char *canon_uri = rb_canonicalise_uri (uri);
1113 g_free (uri);
1115 g_value_init (&val, G_TYPE_STRING);
1116 g_value_set_string (&val, canon_uri);
1117 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
1118 g_value_unset (&val);
1119 rhythmdb_commit (db);
1120 g_free (canon_uri);
1123 if (info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) {
1124 if (data->canceled != TRUE) {
1125 rb_debug ("download completed");
1126 data->total_size = info->file_size;
1127 write_job_data (data);
1129 end_job (data);
1130 data = NULL;
1131 return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
1134 if (data->canceled == TRUE) {
1135 rb_debug ("job canceled");
1136 gnome_vfs_async_cancel (data->read_handle);
1137 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1140 return 1;
1143 static guint
1144 download_progress_update_cb (GnomeVFSAsyncHandle *handle, GnomeVFSXferProgressInfo *info, gpointer cb_data)
1147 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) cb_data;
1149 if (data == NULL) {
1150 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1153 if ((info->phase == GNOME_VFS_XFER_PHASE_COPYING) &&
1154 (data->entry != NULL)) {
1155 guint local_progress = 0;
1157 if (info->file_size > 0)
1158 local_progress = (gint) 100 * info->total_bytes_copied / info->file_size;
1160 if (local_progress != data->progress) {
1161 GValue val = {0,};
1163 g_value_init (&val, G_TYPE_ULONG);
1164 g_value_set_ulong (&val, local_progress);
1165 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1166 g_value_unset (&val);
1168 GDK_THREADS_ENTER ();
1170 g_signal_emit (data->pd, rb_podcast_manager_signals[STATUS_CHANGED],
1171 0, data->entry, local_progress);
1173 GDK_THREADS_LEAVE ();
1174 data->progress = local_progress;
1178 return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
1181 void
1182 rb_podcast_manager_unsubscribe_feed (RhythmDB *db, const char *url)
1184 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (db, url);
1185 if (entry) {
1186 GValue val = {0, };
1187 g_value_init (&val, G_TYPE_ULONG);
1188 g_value_set_ulong (&val, 0);
1189 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
1190 g_value_unset (&val);
1195 gboolean
1196 rb_podcast_manager_remove_feed (RBPodcastManager *pd, const char *url, gboolean remove_files)
1198 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, url);
1200 if (entry) {
1201 rb_debug ("Removing podcast feed: %s remove_files: %d", url, remove_files);
1203 rb_podcast_manager_set_remove_files (pd, remove_files);
1204 rhythmdb_entry_delete (pd->priv->db, entry);
1205 rhythmdb_commit (pd->priv->db);
1206 return TRUE;
1209 return FALSE;
1212 static void
1213 rb_podcast_manager_db_entry_deleted_cb (RBPodcastManager *pd,
1214 RhythmDBEntry *entry)
1216 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
1218 if ((type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) && (pd->priv->remove_files == TRUE)) {
1219 const char *file_name;
1220 const char *dir_name;
1221 const char *conf_dir_name;
1222 GnomeVFSResult result;
1224 rb_debug ("Handling entry deleted");
1226 /* make sure we're not downloading it */
1227 rb_podcast_manager_cancel_download (pd, entry);
1229 file_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
1230 if (file_name == NULL) {
1231 /* episode has not been downloaded */
1232 rb_debug ("Episode not downloaded, skipping.");
1233 return;
1236 result = gnome_vfs_unlink (file_name);
1237 if (result != GNOME_VFS_OK) {
1238 rb_debug ("Removing episode failed: %s", gnome_vfs_result_to_string (result));
1239 return;
1242 /* remove dir */
1243 rb_debug ("removing dir");
1244 conf_dir_name = eel_gconf_get_string (CONF_STATE_PODCAST_DOWNLOAD_DIR);
1246 dir_name = g_build_filename (conf_dir_name,
1247 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM),
1248 NULL);
1249 gnome_vfs_remove_directory (dir_name);
1251 } else if (type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
1252 GtkTreeModel *query_model;
1253 GtkTreeIter iter;
1255 query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (pd->priv->db));
1256 rhythmdb_do_full_query (pd->priv->db,
1257 RHYTHMDB_QUERY_RESULTS (query_model),
1258 RHYTHMDB_QUERY_PROP_EQUALS,
1259 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
1260 RHYTHMDB_QUERY_PROP_LIKE,
1261 RHYTHMDB_PROP_SUBTITLE, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
1262 RHYTHMDB_QUERY_END);
1264 if (gtk_tree_model_get_iter_first (query_model, &iter)) {
1265 gboolean has_next;
1266 do {
1267 RhythmDBEntry *entry;
1269 gtk_tree_model_get (query_model, &iter, 0, &entry, -1);
1270 has_next = gtk_tree_model_iter_next (query_model, &iter);
1271 rhythmdb_entry_delete (pd->priv->db, entry);
1272 rhythmdb_entry_unref (entry);
1274 } while (has_next);
1276 rhythmdb_commit (pd->priv->db);
1279 g_object_unref (query_model);
1283 void
1284 rb_podcast_manager_cancel_download (RBPodcastManager *pd, RhythmDBEntry *entry)
1286 GList *lst;
1288 g_mutex_lock (pd->priv->download_list_mutex);
1290 lst = pd->priv->download_list;
1291 while (lst) {
1292 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) lst->data;
1293 if (data->entry == entry) {
1294 rb_debug ("Found job");
1295 break;
1297 lst = lst->next;
1299 g_mutex_unlock (pd->priv->download_list_mutex);
1301 if (lst)
1302 cancel_job (lst->data);
1305 static void
1306 rb_podcast_manager_update_synctime (RBPodcastManager *pd)
1308 gint value;
1309 gint index = eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL);
1311 switch (index)
1313 case UPDATE_EVERY_HOUR:
1314 value = time (NULL) + 3600;
1315 break;
1316 case UPDATE_EVERY_DAY:
1317 value = time (NULL) + (3600 * 24);
1318 break;
1319 case UPDATE_EVERY_WEEK:
1320 value = time (NULL) + (3600 * 24 * 7);
1321 break;
1322 case UPDATE_MANUALLY:
1323 value = 0;
1324 break;
1325 default:
1326 value = 0;
1329 eel_gconf_set_integer (CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME, value);
1330 eel_gconf_suggest_sync ();
1331 pd->priv->next_time = value;
1332 rb_podcast_manager_start_sync (pd);
1335 static void
1336 rb_podcast_manager_config_changed (GConfClient* client,
1337 guint cnxn_id,
1338 GConfEntry *entry,
1339 gpointer user_data)
1341 rb_podcast_manager_update_synctime (RB_PODCAST_MANAGER (user_data));
1344 void
1345 rb_podcast_manager_set_remove_files (RBPodcastManager *pd, gboolean flag)
1347 pd->priv->remove_files = flag;
1351 gboolean
1352 rb_podcast_manager_get_remove_files (RBPodcastManager *pd)
1354 return pd->priv->remove_files;
1357 static void
1358 rb_podcast_manager_insert_feed (RBPodcastManager *pd, RBPodcastChannel *data)
1360 GValue description_val = { 0, };
1361 GValue title_val = { 0, };
1362 GValue subtitle_val = { 0, };
1363 GValue summary_val = { 0, };
1364 GValue lang_val = { 0, };
1365 GValue copyright_val = { 0, };
1366 GValue image_val = { 0, };
1367 GValue author_val = { 0, };
1368 GValue status_val = { 0, };
1369 GValue last_post_val = { 0, };
1370 GValue last_update_val = { 0, };
1371 gulong last_post = 0;
1372 gulong new_last_post;
1373 GList *download_entries = NULL;
1374 gboolean new_feed, updated, download_last;
1375 RhythmDB *db = pd->priv->db;
1377 RhythmDBEntry *entry;
1379 GList *lst_songs;
1381 if (data->title == NULL) {
1382 g_list_free (data->posts);
1383 g_free (data);
1384 return;
1387 new_feed = TRUE;
1389 /* processing podcast head */
1390 entry = rhythmdb_entry_lookup_by_location (db, (gchar *)data->url);
1391 if (entry) {
1392 if (rhythmdb_entry_get_entry_type (entry) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
1393 return;
1395 rb_debug ("Head found");
1396 g_value_init (&status_val, G_TYPE_ULONG);
1397 g_value_set_ulong (&status_val, 1);
1398 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1399 g_value_unset (&status_val);
1400 last_post = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_POST_TIME);
1401 new_feed = FALSE;
1402 } else {
1403 rb_debug ("Insert new entry");
1404 entry = rhythmdb_entry_new (db,
1405 RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
1406 (gchar *) data->url);
1407 if (entry == NULL)
1408 return;
1409 rb_debug("New entry create\n");
1411 g_value_init (&title_val, G_TYPE_STRING);
1412 g_value_set_string (&title_val, (gchar * ) data->title);
1413 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &title_val);
1414 g_value_unset (&title_val);
1416 g_value_init (&author_val, G_TYPE_STRING);
1417 if (data->author)
1418 g_value_set_string (&author_val, (gchar *) data->author);
1419 else
1420 g_value_set_static_string (&author_val, _("Unknown"));
1421 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &author_val);
1422 g_value_unset (&author_val);
1424 if (data->subtitle) {
1425 g_value_init (&subtitle_val, G_TYPE_STRING);
1426 g_value_set_string (&subtitle_val, (gchar *) data->subtitle);
1427 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUBTITLE, &subtitle_val);
1428 g_value_unset (&subtitle_val);
1431 if (data->description) {
1432 g_value_init (&description_val, G_TYPE_STRING);
1433 g_value_set_string (&description_val, (gchar *) data->description);
1434 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DESCRIPTION, &description_val);
1435 g_value_unset (&description_val);
1438 if (data->summary) {
1439 g_value_init (&summary_val, G_TYPE_STRING);
1440 g_value_set_string (&summary_val, (gchar *) data->summary);
1441 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUMMARY, &summary_val);
1442 g_value_unset (&summary_val);
1445 if (data->lang) {
1446 g_value_init (&lang_val, G_TYPE_STRING);
1447 g_value_set_string (&lang_val, (gchar *) data->lang);
1448 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LANG, &lang_val);
1449 g_value_unset (&lang_val);
1452 if (data->copyright) {
1453 g_value_init (&copyright_val, G_TYPE_STRING);
1454 g_value_set_string (&copyright_val, (gchar *) data->copyright);
1455 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_COPYRIGHT, &copyright_val);
1456 g_value_unset (&copyright_val);
1459 if (data->img) {
1460 g_value_init (&image_val, G_TYPE_STRING);
1461 g_value_set_string (&image_val, (gchar *) data->img);
1462 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_IMAGE, &image_val);
1463 g_value_unset (&image_val);
1466 g_value_init (&status_val, G_TYPE_ULONG);
1467 g_value_set_ulong (&status_val, 1);
1468 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1469 g_value_unset (&status_val);
1471 rb_debug("Podcast head Inserted");
1474 /* insert episodes */
1475 new_last_post = last_post;
1477 updated = FALSE;
1478 download_last = (eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL) != UPDATE_MANUALLY);
1479 for (lst_songs = data->posts; lst_songs != NULL; lst_songs = g_list_next (lst_songs)) {
1480 RBPodcastItem *item = (RBPodcastItem *) lst_songs->data;
1481 RhythmDBEntry *post_entry;
1483 if (item->pub_date > last_post || item->pub_date == 0) {
1484 updated = TRUE;
1486 post_entry =
1487 rb_podcast_manager_add_post (db,
1488 (gchar *) data->title,
1489 (gchar *) item->title,
1490 (gchar *) data->url,
1491 (gchar *) (item->author ? item->author : data->author),
1492 (gchar *) item->url,
1493 (gchar *) item->description,
1494 (gulong) (item->pub_date > 0 ? item->pub_date : data->pub_date),
1495 (gulong) item->duration,
1496 item->filesize);
1497 if (post_entry && item->pub_date >= new_last_post) {
1498 if (item->pub_date > new_last_post) {
1499 g_list_free (download_entries);
1500 download_entries = NULL;
1502 download_entries = g_list_prepend (download_entries, post_entry);
1503 new_last_post = item->pub_date;
1508 if (download_last) {
1509 GValue status = {0,};
1510 GList *t;
1512 g_value_init (&status, G_TYPE_ULONG);
1513 g_value_set_ulong (&status, RHYTHMDB_PODCAST_STATUS_WAITING);
1514 for (t = download_entries; t != NULL; t = g_list_next (t)) {
1515 rhythmdb_entry_set (db,
1516 (RhythmDBEntry*) t->data,
1517 RHYTHMDB_PROP_STATUS,
1518 &status);
1520 g_value_unset (&status);
1522 g_list_free (download_entries);
1524 if (updated)
1525 g_signal_emit (pd, rb_podcast_manager_signals[FEED_UPDATES_AVALIABLE],
1526 0, entry);
1528 if (data->pub_date > new_last_post)
1529 new_last_post = data->pub_date;
1531 g_value_init (&last_post_val, G_TYPE_ULONG);
1532 g_value_set_ulong (&last_post_val, new_last_post);
1534 if (new_feed)
1535 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
1536 else
1537 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
1538 g_value_unset (&last_post_val);
1540 g_value_init (&last_update_val, G_TYPE_ULONG);
1541 g_value_set_ulong (&last_update_val, time(NULL));
1543 if (new_feed)
1544 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1545 else
1546 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1547 g_value_unset (&last_update_val);
1549 rhythmdb_commit (db);
1552 static gboolean
1553 rb_podcast_manager_event_loop (RBPodcastManager *pd)
1555 RBPodcastManagerEvent *event;
1557 while ((event = g_async_queue_try_pop (pd->priv->event_queue))) {
1558 switch (event->type)
1560 case EVENT_INSERT_FEED:
1561 rb_podcast_manager_insert_feed (pd, event->channel);
1562 break;
1563 case EVENT_ERROR_FEED:
1565 gchar *error_msg;
1566 error_msg = g_strdup_printf (_("There was a problem adding this podcast. Please verify the URL: %s"),
1567 (gchar *) event->channel->url);
1568 g_signal_emit (G_OBJECT (pd),
1569 rb_podcast_manager_signals[PROCESS_ERROR],
1570 0, error_msg);
1571 g_free (error_msg);
1572 break;
1576 rb_podcast_parse_channel_free (event->channel);
1577 g_free (event);
1580 g_async_queue_unref (pd->priv->event_queue);
1582 return FALSE;
1585 static void
1586 rb_podcast_manager_abort_subscribe (RBPodcastManager *pd)
1588 RBPodcastManagerEvent *event;
1590 /* remove all event processing functions */
1591 while (g_idle_remove_by_data (pd))
1594 /* purge the event queue */
1595 while ((event = g_async_queue_try_pop (pd->priv->event_queue))) {
1596 rb_podcast_parse_channel_free (event->channel);
1597 g_free (event);
1601 void
1602 rb_podcast_manager_shutdown (RBPodcastManager *pd)
1604 rb_podcast_manager_cancel_all (pd);
1605 rb_podcast_manager_abort_subscribe (pd);
1608 gchar *
1609 rb_podcast_manager_get_podcast_dir (RBPodcastManager *pd)
1611 gchar *conf_dir_name = eel_gconf_get_string (CONF_STATE_PODCAST_DOWNLOAD_DIR);
1613 if (conf_dir_name == NULL || (strcmp (conf_dir_name, "") == 0)) {
1614 conf_dir_name = g_build_filename (g_get_home_dir (),
1615 "Podcasts",
1616 NULL);
1617 eel_gconf_set_string (CONF_STATE_PODCAST_DOWNLOAD_DIR, conf_dir_name);
1620 return conf_dir_name;