Updated Macedonian Translation <arangela@cvs.gnome.org>
[rhythmbox.git] / podcast / rb-podcast-manager.c
blob66c0c855c012fd07ff0cf77105633e7cd4307770
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include <string.h>
24 #define __USE_XOPEN
25 #include <time.h>
27 #include <glib/gi18n.h>
28 #include <glib/gstdio.h>
29 #include <gtk/gtk.h>
30 #include <libgnomevfs/gnome-vfs-uri.h>
32 #include "rb-preferences.h"
33 #include "eel-gconf-extensions.h"
34 #include "rb-podcast-manager.h"
35 #include "rb-file-helpers.h"
36 #include "rb-debug.h"
37 #include "rb-podcast-marshal.h"
38 #include "rhythmdb.h"
39 #include "rhythmdb-query-model.h"
40 #include "rb-podcast-parse.h"
41 #include "rb-dialog.h"
42 #include "rb-metadata.h"
44 #define CONF_STATE_PODCAST_PREFIX CONF_PREFIX "/state/podcast"
45 #define CONF_STATE_PODCAST_DOWNLOAD_DIR CONF_STATE_PODCAST_PREFIX "/download_prefix"
46 #define CONF_STATE_PODCAST_DOWNLOAD_INTERVAL CONF_STATE_PODCAST_PREFIX "/download_interval"
47 #define CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME CONF_STATE_PODCAST_PREFIX "/download_next_time"
49 enum
51 PROP_0,
52 PROP_DB
55 enum
57 UPDATE_EVERY_HOUR,
58 UPDATE_EVERY_DAY,
59 UPDATE_EVERY_WEEK,
60 UPDATE_MANUALLY
63 enum
65 STATUS_CHANGED,
66 START_DOWNLOAD,
67 FINISH_DOWNLOAD,
68 PROCESS_ERROR,
69 FEED_UPDATES_AVALIABLE,
70 LAST_SIGNAL
73 typedef enum
75 EVENT_INSERT_FEED,
76 EVENT_ERROR_FEED
77 }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;
106 /* used on donwload thread */
107 typedef struct
109 RBPodcastManager *pd;
110 RhythmDBEntry *entry;
111 GnomeVFSAsyncHandle *read_handle;
112 GnomeVFSURI *write_uri;
113 GnomeVFSURI *read_uri;
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;
129 static guint rb_podcast_manager_signals[LAST_SIGNAL] = { 0 };
131 /* functions */
132 static void rb_podcast_manager_class_init (RBPodcastManagerClass *klass);
133 static void rb_podcast_manager_init (RBPodcastManager *dp);
134 static GObject *rb_podcast_manager_constructor (GType type, guint n_construct_properties,
135 GObjectConstructParam *construct_properties);
136 static void rb_podcast_manager_finalize (GObject *object);
137 static void rb_podcast_manager_set_property (GObject *object,
138 guint prop_id,
139 const GValue *value,
140 GParamSpec *pspec);
141 static void rb_podcast_manager_get_property (GObject *object,
142 guint prop_id,
143 GValue *value,
144 GParamSpec *pspec);
145 static void rb_podcast_manager_copy_post (RBPodcastManager *pd);
146 static int rb_podcast_manager_mkdir_with_parents (const gchar *pathname,
147 int mode);
148 static gboolean rb_podcast_manager_sync_head_cb (gpointer data);
149 static gboolean rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
150 GtkTreePath *path,
151 GtkTreeIter *iter,
152 RBPodcastManager *data);
153 static gboolean rb_podcast_manager_save_metadata (RhythmDB *db,
154 RhythmDBEntry *entry,
155 const char* uri);
156 static void rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd,
157 RhythmDBEntry *entry);
158 static void rb_podcast_manager_db_entry_deleted_cb (RBPodcastManager *pd,
159 RhythmDBEntry *entry);
160 static gboolean rb_podcast_manager_next_file (RBPodcastManager * pd);
161 static void rb_podcast_manager_insert_feed (RBPodcastManager *pd, RBPodcastChannel *data);
162 static void rb_podcast_manager_abort_subscribe (RBPodcastManager *pd);
164 /* event loop */
165 static gboolean rb_podcast_manager_event_loop (RBPodcastManager *pd) ;
166 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)
192 static void
193 rb_podcast_manager_class_init (RBPodcastManagerClass *klass)
195 GObjectClass *object_class = G_OBJECT_CLASS (klass);
197 object_class->constructor = rb_podcast_manager_constructor;
198 object_class->finalize = rb_podcast_manager_finalize;
200 object_class->set_property = rb_podcast_manager_set_property;
201 object_class->get_property = rb_podcast_manager_get_property;
203 g_object_class_install_property (object_class,
204 PROP_DB,
205 g_param_spec_object ("db",
206 "db",
207 "database",
208 RHYTHMDB_TYPE,
209 G_PARAM_READWRITE));
211 rb_podcast_manager_signals[STATUS_CHANGED] =
212 g_signal_new ("status_changed",
213 G_OBJECT_CLASS_TYPE (object_class),
214 GTK_RUN_LAST,
215 G_STRUCT_OFFSET (RBPodcastManagerClass, status_changed),
216 NULL, NULL,
217 rb_podcast_marshal_VOID__POINTER_ULONG,
218 G_TYPE_NONE,
220 G_TYPE_POINTER,
221 G_TYPE_ULONG);
223 rb_podcast_manager_signals[START_DOWNLOAD] =
224 g_signal_new ("start_download",
225 G_OBJECT_CLASS_TYPE (object_class),
226 GTK_RUN_LAST,
227 G_STRUCT_OFFSET (RBPodcastManagerClass, start_download),
228 NULL, NULL,
229 g_cclosure_marshal_VOID__POINTER,
230 G_TYPE_NONE,
232 G_TYPE_POINTER);
234 rb_podcast_manager_signals[FINISH_DOWNLOAD] =
235 g_signal_new ("finish_download",
236 G_OBJECT_CLASS_TYPE (object_class),
237 GTK_RUN_LAST,
238 G_STRUCT_OFFSET (RBPodcastManagerClass, finish_download),
239 NULL, NULL,
240 g_cclosure_marshal_VOID__POINTER,
241 G_TYPE_NONE,
243 G_TYPE_POINTER);
245 rb_podcast_manager_signals[FEED_UPDATES_AVALIABLE] =
246 g_signal_new ("feed_updates_avaliable",
247 G_OBJECT_CLASS_TYPE (object_class),
248 GTK_RUN_LAST,
249 G_STRUCT_OFFSET (RBPodcastManagerClass, feed_updates_avaliable),
250 NULL, NULL,
251 g_cclosure_marshal_VOID__POINTER,
252 G_TYPE_NONE,
254 G_TYPE_POINTER);
256 rb_podcast_manager_signals[PROCESS_ERROR] =
257 g_signal_new ("process_error",
258 G_OBJECT_CLASS_TYPE (object_class),
259 GTK_RUN_LAST,
260 G_STRUCT_OFFSET (RBPodcastManagerClass, process_error),
261 NULL, NULL,
262 g_cclosure_marshal_VOID__STRING,
263 G_TYPE_NONE,
265 G_TYPE_STRING);
267 g_type_class_add_private (klass, sizeof (RBPodcastManagerPrivate));
270 static void
271 rb_podcast_manager_init (RBPodcastManager *pd)
273 pd->priv = RB_PODCAST_MANAGER_GET_PRIVATE (pd);
275 pd->priv->source_sync = 0;
276 pd->priv->mutex_job = g_mutex_new();
277 pd->priv->download_list_mutex = g_mutex_new();
278 pd->priv->event_queue = g_async_queue_new ();
279 pd->priv->db = NULL;
280 eel_gconf_monitor_add (CONF_STATE_PODCAST_PREFIX);
283 static GObject *
284 rb_podcast_manager_constructor (GType type, guint n_construct_properties,
285 GObjectConstructParam *construct_properties)
287 RBPodcastManager *pd;
289 pd = RB_PODCAST_MANAGER (G_OBJECT_CLASS (rb_podcast_manager_parent_class)
290 ->constructor (type, n_construct_properties, construct_properties));
292 pd->priv->update_interval_notify_id = eel_gconf_notification_add (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL,
293 rb_podcast_manager_config_changed,
294 pd);
297 return G_OBJECT (pd);
301 static void
302 rb_podcast_manager_finalize (GObject *object)
304 RBPodcastManager *pd;
305 g_return_if_fail (object != NULL);
306 g_return_if_fail (RB_IS_PODCAST_MANAGER (object));
308 pd = RB_PODCAST_MANAGER(object);
310 g_return_if_fail (pd->priv != NULL);
313 eel_gconf_monitor_remove (CONF_STATE_PODCAST_PREFIX);
315 if (pd->priv->source_sync) {
316 g_source_remove (pd->priv->source_sync);
317 pd->priv->source_sync = 0;
321 eel_gconf_notification_remove (pd->priv->update_interval_notify_id);
324 if (pd->priv->download_list) {
325 g_list_foreach (pd->priv->download_list, (GFunc)g_free, NULL);
326 g_list_free (pd->priv->download_list);
330 g_mutex_free (pd->priv->mutex_job);
331 g_mutex_free (pd->priv->download_list_mutex);
332 g_async_queue_unref (pd->priv->event_queue);
334 G_OBJECT_CLASS (rb_podcast_manager_parent_class)->finalize (object);
335 rb_debug ("Podcast Manager END");
338 static void
339 rb_podcast_manager_set_property (GObject *object,
340 guint prop_id,
341 const GValue *value,
342 GParamSpec *pspec)
344 RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
346 switch (prop_id) {
347 case PROP_DB:
348 if (pd->priv->db) {
349 g_signal_handlers_disconnect_by_func (G_OBJECT (pd->priv->db),
350 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
351 pd);
353 g_signal_handlers_disconnect_by_func (G_OBJECT (pd->priv->db),
354 G_CALLBACK (rb_podcast_manager_db_entry_deleted_cb),
355 pd);
359 pd->priv->db = g_value_get_object (value);
361 g_signal_connect_object (G_OBJECT (pd->priv->db),
362 "entry-added",
363 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
364 pd, G_CONNECT_SWAPPED);
366 g_signal_connect_object (G_OBJECT (pd->priv->db),
367 "entry_deleted",
368 G_CALLBACK (rb_podcast_manager_db_entry_deleted_cb),
369 pd, G_CONNECT_SWAPPED);
371 break;
372 default:
373 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
377 static void
378 rb_podcast_manager_get_property (GObject *object,
379 guint prop_id,
380 GValue *value,
381 GParamSpec *pspec)
383 RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
385 switch (prop_id) {
386 case PROP_DB:
387 g_value_set_object (value, pd->priv->db);
388 break;
389 default:
390 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
396 RBPodcastManager *
397 rb_podcast_manager_new (RhythmDB *db)
399 RBPodcastManager *pd;
401 pd = g_object_new (RB_TYPE_PODCAST_MANAGER, "db", db, NULL);
402 return pd;
406 void
407 rb_podcast_manager_download_entry (RBPodcastManager *pd, RhythmDBEntry *entry)
409 gulong status;
410 if (entry == NULL)
411 return;
413 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
414 if ((status < RHYTHMDB_PODCAST_STATUS_COMPLETE) ||
415 (status == RHYTHMDB_PODCAST_STATUS_WAITING)) {
416 RBPodcastManagerInfo *data;
417 if (status < RHYTHMDB_PODCAST_STATUS_COMPLETE) {
418 GValue status_val = { 0, };
419 g_value_init (&status_val, G_TYPE_ULONG);
420 g_value_set_ulong (&status_val, RHYTHMDB_PODCAST_STATUS_WAITING);
421 rhythmdb_entry_set_nonotify (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &status_val);
422 g_value_unset (&status_val);
424 rb_debug ("Try insert entry for download.");
425 data = download_info_new();
426 data->pd = pd;
427 data->entry = entry;
428 g_mutex_lock (pd->priv->download_list_mutex);
429 pd->priv->download_list = g_list_append (pd->priv->download_list, data);
430 g_mutex_unlock (pd->priv->download_list_mutex);
431 gtk_idle_add ((GtkFunction) rb_podcast_manager_next_file , pd);
435 gboolean
436 rb_podcast_manager_entry_downloaded (RhythmDBEntry *entry)
438 gulong status;
439 const gchar *file_name;
440 gulong type;
442 type = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TYPE);
443 g_assert (type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
445 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
446 file_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
448 return (status != RHYTHMDB_PODCAST_STATUS_ERROR && file_name != NULL);
451 void
452 rb_podcast_manager_start_sync (RBPodcastManager *pd)
454 gint next_time;
455 if (pd->priv->next_time > 0) {
456 next_time = pd->priv->next_time;
457 } else {
458 next_time = eel_gconf_get_integer(CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME);
461 if (next_time > 0) {
462 if (pd->priv->source_sync != 0) {
463 g_source_remove (pd->priv->source_sync);
464 pd->priv->source_sync = 0;
466 next_time = next_time - ((int)time (NULL));
467 if (next_time <= 0) {
468 rb_podcast_manager_update_feeds (pd);
469 pd->priv->next_time = 0;
470 rb_podcast_manager_update_synctime (pd);
471 return;
473 pd->priv->source_sync = g_timeout_add (next_time * 1000, (GSourceFunc ) rb_podcast_manager_sync_head_cb, pd);
478 static gboolean
479 rb_podcast_manager_sync_head_cb (gpointer data)
481 RBPodcastManager *pd = RB_PODCAST_MANAGER (data);
482 rb_podcast_manager_update_feeds (pd);
483 pd->priv->source_sync = 0;
484 pd->priv->next_time = 0;
485 rb_podcast_manager_update_synctime (RB_PODCAST_MANAGER (data));
486 return FALSE;
489 void
490 rb_podcast_manager_update_feeds (RBPodcastManager *pd)
492 GtkTreeModel* query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty(pd->priv->db));
494 rhythmdb_do_full_query (pd->priv->db,
495 RHYTHMDB_QUERY_RESULTS (query_model),
496 RHYTHMDB_QUERY_PROP_EQUALS,
497 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
498 RHYTHMDB_QUERY_END);
500 gtk_tree_model_foreach (query_model,
501 (GtkTreeModelForeachFunc) rb_podcast_manager_head_query_cb,
502 pd);
504 g_object_unref (query_model);
507 static gboolean
508 rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
509 GtkTreePath *path, GtkTreeIter *iter,
510 RBPodcastManager *manager)
512 const char* uri;
513 RhythmDBEntry* entry;
514 guint status;
516 gtk_tree_model_get (query_model, iter, 0, &entry, -1);
517 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
518 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
519 if (status == 1)
520 rb_podcast_manager_subscribe_feed (manager, uri);
522 return FALSE;
525 static gboolean
526 rb_podcast_manager_next_file (RBPodcastManager * pd)
529 rb_debug ("try lock file_process mutex");
530 if (g_mutex_trylock (pd->priv->mutex_job) == TRUE) {
531 gint size;
533 g_mutex_lock (pd->priv->download_list_mutex);
534 size = g_list_length (pd->priv->download_list);
535 g_mutex_unlock (pd->priv->download_list_mutex);
537 if (size > 0)
538 rb_podcast_manager_copy_post (pd);
539 else
540 g_mutex_unlock (pd->priv->mutex_job);
541 } else {
542 rb_debug ("not start");
545 return FALSE;
548 static void
549 rb_podcast_manager_copy_post (RBPodcastManager *pd)
551 GnomeVFSURI *remote_uri = NULL;
552 GnomeVFSURI *local_uri = NULL;
553 GValue location_val = { 0, };
554 const char *location, *album_name;
555 char *short_name, *local_file_name;
556 char *dir_name, *conf_dir_name;
557 RBPodcastManagerInfo *data = NULL;
558 RhythmDBEntry *entry;
560 rb_debug ("Stating copy file");
561 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;
573 entry = data->entry;
575 g_assert (entry != NULL);
577 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
578 album_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
580 rb_debug ("processing %s", location);
582 remote_uri = gnome_vfs_uri_new (location);
583 if (!remote_uri) {
584 rb_debug ("Error downloading podcast: could not create remote uri");
585 goto next_step;
588 if (gnome_vfs_uri_is_local (remote_uri)) {
589 rb_debug ("Error downloading podcast: uri is local");
590 goto next_step;
594 conf_dir_name = rb_podcast_manager_get_podcast_dir (pd);
595 dir_name = g_build_filename (conf_dir_name,
596 album_name,
597 NULL);
598 g_free (conf_dir_name);
600 if (!g_file_test (dir_name, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
601 if (rb_podcast_manager_mkdir_with_parents (dir_name, 0750) != 0) {
602 rb_debug ("Error downloading podcast: could not create local dirs");
603 goto next_step;
607 short_name = gnome_vfs_uri_extract_short_name (remote_uri);
608 local_file_name = g_build_filename (dir_name,
609 short_name,
610 NULL);
611 g_free (short_name);
612 g_free (dir_name);
614 rb_debug ("creating file %s\n", local_file_name);
616 local_uri = gnome_vfs_uri_new (local_file_name);
617 if (!local_uri) {
618 rb_debug ("Error downloading podcast: could not create local uri");
619 goto next_step;
622 if (g_file_test (local_file_name, G_FILE_TEST_EXISTS)) {
623 guint64 remote_size;
624 GnomeVFSFileInfo *info = gnome_vfs_file_info_new ();
625 GnomeVFSResult result;
627 result = gnome_vfs_get_file_info_uri (remote_uri, info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
628 if (result != GNOME_VFS_OK) {
629 rb_debug ("unable to retrieve info on remote of podcast");
630 goto next_step;
631 } else {
632 remote_size = info->size;
635 result = gnome_vfs_get_file_info (local_file_name, info, GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
636 if (result != GNOME_VFS_OK) {
637 rb_debug ("unable to retrieve info on local copy of podcast");
638 goto next_step;
639 } else if (remote_size == info->size) {
640 GValue val = {0,};
641 char *uri = gnome_vfs_uri_to_string (local_uri, GNOME_VFS_URI_HIDE_NONE);
643 rb_debug ("podcast %s already downloaded", location);
645 g_value_init (&val, G_TYPE_ULONG);
646 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
647 rhythmdb_entry_set (pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
648 g_value_unset (&val);
650 g_value_init (&val, G_TYPE_STRING);
651 g_value_set_string (&val, uri);
652 rhythmdb_entry_set (pd->priv->db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
653 g_value_unset (&val);
655 rb_podcast_manager_save_metadata (pd->priv->db, data->entry, uri);
656 rhythmdb_commit (pd->priv->db);
658 goto next_step;
659 } else if (remote_size > info->size) {
660 /* TODO: suport resume file */
661 } else {
662 /* the local file is larger. replace it */
665 gnome_vfs_file_info_unref (info);
668 g_free (local_file_name);
670 data->read_uri = remote_uri;
671 data->write_uri = local_uri;
673 start_job (data);
674 return;
676 next_step:
678 if (remote_uri)
679 gnome_vfs_uri_unref (remote_uri);
681 if (local_uri)
682 gnome_vfs_uri_unref (local_uri);
684 g_mutex_lock (pd->priv->download_list_mutex);
685 pd->priv->download_list = g_list_remove (pd->priv->download_list, (gconstpointer ) data);
686 g_mutex_unlock (pd->priv->download_list_mutex);
688 download_info_free (data);
689 data = NULL;
691 g_mutex_unlock (pd->priv->mutex_job);
692 gtk_idle_add ((GtkFunction) rb_podcast_manager_next_file , pd);
696 static int
697 rb_podcast_manager_mkdir_with_parents (const gchar *pathname,
698 int mode)
700 gchar *fn, *p;
702 if (pathname == NULL || *pathname == '\0')
704 return -1;
707 fn = g_strdup (pathname);
709 if (g_path_is_absolute (fn))
710 p = (gchar *) g_path_skip_root (fn);
711 else
712 p = fn;
716 while (*p && !G_IS_DIR_SEPARATOR (*p))
717 p++;
719 if (!*p)
720 p = NULL;
721 else
722 *p = '\0';
724 if (!g_file_test (fn, G_FILE_TEST_EXISTS))
726 if (g_mkdir (fn, mode) == -1)
728 g_free (fn);
729 return -1;
732 else if (!g_file_test (fn, G_FILE_TEST_IS_DIR))
734 g_free (fn);
735 return -1;
737 if (p)
739 *p++ = G_DIR_SEPARATOR;
740 while (*p && G_IS_DIR_SEPARATOR (*p))
741 p++;
744 while (p);
746 g_free (fn);
748 return 0;
751 gboolean
752 rb_podcast_manager_subscribe_feed (RBPodcastManager *pd, const char* url)
754 RBPodcastThreadInfo *info;
755 gchar *valid_url = gnome_vfs_make_uri_from_input (url);
757 if (valid_url == NULL) {
758 rb_error_dialog (NULL, _("Invalid URL"),
759 _("The URL \"%s\" is not valid, please check it."), url);
760 return FALSE;
763 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, valid_url);
764 if (entry) {
765 if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TYPE) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
766 /* added as something else, probably iradio */
767 rb_error_dialog (NULL, _("URL already added"),
768 _("The URL \"%s\" has already been added as a radio station. "
769 "If this is a podcast feed, please remove the radio station."), url);
770 return FALSE;
774 info = g_new0 (RBPodcastThreadInfo, 1);
775 info->pd = pd;
776 info->url = valid_url;
778 g_async_queue_ref (info->pd->priv->event_queue);
779 g_thread_create ((GThreadFunc) rb_podcast_manager_thread_parse_feed,
780 info, FALSE, NULL);
782 return TRUE;
785 static gpointer
786 rb_podcast_manager_thread_parse_feed (RBPodcastThreadInfo *info)
788 RBPodcastManagerEvent *event = g_new0 (RBPodcastManagerEvent, 1);
789 RBPodcastChannel *feed = g_new0 (RBPodcastChannel, 1);
791 rb_podcast_parse_load_feed (feed, info->url);
793 event->channel = feed;
794 event->type = (feed->title == NULL) ? EVENT_ERROR_FEED : EVENT_INSERT_FEED;
796 g_async_queue_push (info->pd->priv->event_queue, event);
797 g_idle_add ((GSourceFunc) rb_podcast_manager_event_loop, info->pd);
799 g_free (info->url);
800 g_free (info);
801 return NULL;
804 gboolean
805 rb_podcast_manager_add_post (RhythmDB *db,
806 const char *name,
807 const char *title,
808 const char *subtitle,
809 const char *generator,
810 const char *uri,
811 const char *description,
812 gulong status,
813 gulong date,
814 gulong duration,
815 guint64 filesize)
818 if (uri && name && title && date && g_utf8_validate(uri, -1, NULL)) {
819 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (db, uri);
820 GValue val = {0,};
821 GTimeVal time;
823 if (entry)
824 return FALSE;
826 entry = rhythmdb_entry_new (db,
827 RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
828 uri);
829 if (entry == NULL)
830 return FALSE;
832 g_value_init (&val, G_TYPE_STRING);
833 g_value_set_string (&val, name);
834 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_ALBUM, &val);
836 g_value_reset (&val);
837 g_value_set_static_string (&val, _("Podcast"));
838 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_GENRE, &val);
840 g_value_reset (&val);
841 g_value_set_string (&val, title);
842 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_TITLE, &val);
844 g_value_reset (&val);
845 if (subtitle)
846 g_value_set_string (&val, subtitle);
847 else
848 g_value_set_static_string (&val, "");
849 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_SUBTITLE, &val);
851 g_value_reset (&val);
852 if (description)
853 g_value_set_string (&val, description);
854 else
855 g_value_set_static_string (&val, "");
856 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_DESCRIPTION, &val);
858 g_value_reset (&val);
859 if (generator)
860 g_value_set_string (&val, generator);
861 else
862 g_value_set_static_string (&val, "");
863 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_ARTIST, &val);
864 g_value_unset (&val);
866 g_value_init (&val, G_TYPE_ULONG);
867 g_value_set_ulong (&val, status);
868 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_STATUS, &val);
870 g_value_reset (&val);
871 g_value_set_ulong (&val, date);
872 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_POST_TIME, &val);
874 g_value_reset (&val);
875 g_value_set_ulong (&val, duration);
876 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_DURATION, &val);
878 g_value_reset (&val);
879 g_value_set_ulong (&val, 0);
880 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_LAST_PLAYED, &val);
882 /* first seen */
883 g_get_current_time (&time);
884 g_value_reset (&val);
885 g_value_set_ulong (&val, time.tv_sec);
886 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &val);
887 g_value_unset (&val);
889 /* initialize the rating */
890 g_value_init (&val, G_TYPE_DOUBLE);
891 g_value_set_double (&val, 2.5);
892 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_RATING, &val);
893 g_value_unset (&val);
895 g_value_init (&val, G_TYPE_UINT64);
896 g_value_set_uint64 (&val, filesize);
897 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_FILE_SIZE, &val);
898 g_value_unset (&val);
900 return TRUE;
901 } else {
902 return FALSE;
906 static gboolean
907 rb_podcast_manager_save_metadata (RhythmDB *db, RhythmDBEntry *entry, const char* uri)
909 RBMetaData *md = rb_metadata_new();
910 GError *error = NULL;
911 GValue val = { 0, };
912 const char *mime;
914 rb_debug("Loading podcast metadata");
915 rb_metadata_load (md, uri, &error);
917 if (error != NULL) {
918 /* this probably isn't an audio enclosure. or some other error */
919 g_value_init (&val, G_TYPE_ULONG);
920 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
921 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
922 g_value_unset (&val);
924 g_value_init (&val, G_TYPE_STRING);
925 g_value_set_string (&val, error->message);
926 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
927 g_value_unset (&val);
929 rhythmdb_commit (db);
931 g_object_unref (md);
932 return FALSE;
935 mime = rb_metadata_get_mime (md);
936 if (mime) {
937 g_value_init (&val, G_TYPE_STRING);
938 g_value_set_string (&val, mime);
939 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MIMETYPE, &val);
940 g_value_unset (&val);
943 if (rb_metadata_get (md,
944 RB_METADATA_FIELD_DURATION,
945 &val)) {
946 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val);
947 g_value_unset (&val);
950 if (rb_metadata_get (md,
951 RB_METADATA_FIELD_BITRATE,
952 &val)) {
953 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_BITRATE, &val);
954 g_value_unset (&val);
957 rhythmdb_commit (db);
959 g_object_unref (md);
960 return TRUE;
963 static void
964 rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd, RhythmDBEntry *entry)
967 gulong type = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TYPE);
968 if (type != RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
969 return;
971 rb_podcast_manager_download_entry (pd, entry);
974 static void
975 write_job_data (RBPodcastManagerInfo *data)
978 GValue val = {0, };
979 RhythmDB *db = data->pd->priv->db;
981 rb_debug ("in the write_job");
983 g_value_init (&val, G_TYPE_UINT64);
984 g_value_set_uint64 (&val, data->total_size);
985 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_FILE_SIZE, &val);
986 g_value_unset (&val);
988 g_value_init (&val, G_TYPE_ULONG);
989 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
990 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_STATUS, &val);
991 g_value_unset (&val);
993 rb_podcast_manager_save_metadata (db, data->entry,
994 gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE));
996 rhythmdb_commit (db);
999 static void
1000 download_info_free (RBPodcastManagerInfo *data)
1002 if (data->write_uri) {
1003 gnome_vfs_uri_unref (data->write_uri);
1004 data->write_uri = NULL;
1007 if (data->read_uri) {
1008 gnome_vfs_uri_unref (data->read_uri);
1009 data->read_uri = NULL;
1013 g_mutex_free (data->mutex_working);
1015 g_free (data);
1018 static RBPodcastManagerInfo*
1019 download_info_new (void)
1021 RBPodcastManagerInfo *data = g_new0 (RBPodcastManagerInfo, 1);
1022 data->pd = NULL;
1023 data->entry = NULL;
1024 data->write_uri = NULL;
1025 data->read_uri = NULL;
1026 data->mutex_working = g_mutex_new ();
1027 data->total_size = 0;
1028 data->progress = 0;
1029 data->canceled = FALSE;
1031 return data;
1034 static void
1035 start_job (RBPodcastManagerInfo *data)
1038 GList *source_uri_list = NULL;
1039 GList *target_uri_list = NULL;
1041 rb_debug ("start job");
1043 GDK_THREADS_ENTER ();
1044 g_signal_emit (data->pd, rb_podcast_manager_signals[START_DOWNLOAD],
1045 0, data->entry);
1046 GDK_THREADS_LEAVE ();
1048 source_uri_list = g_list_prepend (source_uri_list, data->read_uri);
1049 target_uri_list = g_list_prepend (target_uri_list, data->write_uri);
1051 g_mutex_lock (data->mutex_working);
1053 rb_debug ("start async copy");
1054 gnome_vfs_async_xfer ( &data->read_handle,
1055 source_uri_list,
1056 target_uri_list,
1057 GNOME_VFS_XFER_DEFAULT ,
1058 GNOME_VFS_XFER_ERROR_MODE_ABORT,
1059 GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
1060 GNOME_VFS_PRIORITY_DEFAULT,
1061 (GnomeVFSAsyncXferProgressCallback ) download_progress_update_cb,
1062 data,
1063 (GnomeVFSXferProgressCallback ) download_progress_cb,
1064 data);
1068 void
1069 rb_podcast_manager_cancel_all (RBPodcastManager *pd)
1071 guint i;
1072 guint lst_len;
1073 GList *lst;
1075 g_mutex_lock (pd->priv->download_list_mutex);
1076 lst = g_list_reverse (pd->priv->download_list);
1077 g_mutex_unlock (pd->priv->download_list_mutex);
1079 rb_debug ("cancel all job %d", g_list_length (lst));
1080 lst_len = g_list_length (lst);
1082 for (i=0; i < lst_len; i++) {
1083 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) lst->data;
1084 lst = lst->next;
1085 cancel_job (data);
1086 rb_debug ("cancel next job");
1089 if (lst_len > 0) {
1090 g_mutex_lock (pd->priv->mutex_job);
1091 g_mutex_unlock (pd->priv->mutex_job);
1095 static void
1096 end_job (RBPodcastManagerInfo *data)
1098 RBPodcastManager *pd = data->pd;
1100 rb_debug ("end_job");
1102 g_mutex_lock (data->pd->priv->download_list_mutex);
1103 data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, (gconstpointer) data);
1104 g_mutex_unlock (data->pd->priv->download_list_mutex);
1106 g_mutex_unlock (data->mutex_working);
1109 if (data->canceled != TRUE) {
1110 GDK_THREADS_ENTER ();
1112 g_signal_emit (data->pd, rb_podcast_manager_signals[FINISH_DOWNLOAD],
1113 0, data->entry);
1115 GDK_THREADS_LEAVE ();
1118 download_info_free (data);
1119 g_mutex_unlock (pd->priv->mutex_job);
1121 gtk_idle_add ((GtkFunction) rb_podcast_manager_next_file, pd);
1124 static void
1125 cancel_job (RBPodcastManagerInfo *data)
1127 if (g_mutex_trylock (data->mutex_working) == FALSE) {
1128 rb_debug ("async cancel");
1129 data->canceled = TRUE;
1131 else {
1132 rb_debug ("job cancel");
1134 g_mutex_lock (data->pd->priv->download_list_mutex);
1135 data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, (gconstpointer ) data);
1136 g_mutex_unlock (data->pd->priv->download_list_mutex);
1138 g_mutex_unlock (data->mutex_working);
1140 download_info_free (data);
1141 data = NULL;
1145 static guint
1146 download_progress_cb (GnomeVFSXferProgressInfo *info, gpointer cb_data)
1148 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) cb_data;
1150 if (data == NULL) {
1151 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1154 if (info->status != GNOME_VFS_XFER_PROGRESS_STATUS_OK) {
1155 GValue val = {0, };
1156 rb_debug ("error on download");
1157 g_value_init (&val, G_TYPE_ULONG);
1158 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
1159 GDK_THREADS_ENTER ();
1160 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1161 rhythmdb_commit (data->pd->priv->db);
1162 GDK_THREADS_LEAVE ();
1163 g_value_unset (&val);
1164 end_job (data);
1165 data = NULL;
1166 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1169 if (rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_MOUNTPOINT) == NULL) {
1170 GValue val = {0,};
1171 RhythmDB *db = data->pd->priv->db;
1173 g_value_init (&val, G_TYPE_STRING);
1174 g_value_set_string (&val,
1175 gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE));
1176 rhythmdb_entry_set (db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
1177 g_value_unset (&val);
1178 rhythmdb_commit (db);
1181 if (info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) {
1182 if (data->canceled != TRUE) {
1183 rb_debug ("download completed");
1184 data->total_size = info->file_size;
1185 write_job_data (data);
1187 end_job (data);
1188 data = NULL;
1189 return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
1192 if (data->canceled == TRUE) {
1193 rb_debug ("job canceled");
1194 gnome_vfs_async_cancel (data->read_handle);
1195 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1199 return 1;
1202 static guint
1203 download_progress_update_cb (GnomeVFSAsyncHandle *handle, GnomeVFSXferProgressInfo *info, gpointer cb_data)
1206 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) cb_data;
1208 if (data == NULL) {
1209 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1213 if ((info->phase == GNOME_VFS_XFER_PHASE_COPYING) &&
1214 (data->entry != NULL)) {
1215 guint local_progress = 0;
1217 if (info->file_size > 0)
1218 local_progress = (gint) 100 * info->total_bytes_copied / info->file_size;
1220 if (local_progress != data->progress) {
1221 GValue val = {0,};
1223 g_value_init (&val, G_TYPE_ULONG);
1224 g_value_set_ulong (&val, local_progress);
1225 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1226 g_value_unset (&val);
1228 GDK_THREADS_ENTER ();
1230 g_signal_emit (data->pd, rb_podcast_manager_signals[STATUS_CHANGED],
1231 0, data->entry, local_progress);
1233 GDK_THREADS_LEAVE ();
1234 data->progress = local_progress;
1238 return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
1241 void
1242 rb_podcast_manager_unsubscribe_feed (RhythmDB *db, const char* url)
1244 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (db, url);
1245 if (entry) {
1246 GValue val = {0, };
1247 g_value_init (&val, G_TYPE_ULONG);
1248 g_value_set_ulong (&val, 0);
1249 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
1250 g_value_unset (&val);
1255 gboolean
1256 rb_podcast_manager_remove_feed (RBPodcastManager *pd, const char* url, gboolean remove_files)
1258 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, url);
1259 if (entry) {
1260 rb_podcast_manager_set_remove_files (pd, remove_files);
1261 rhythmdb_entry_delete (pd->priv->db, entry);
1262 rhythmdb_commit (pd->priv->db);
1263 return TRUE;
1266 return FALSE;
1269 static void
1270 rb_podcast_manager_db_entry_deleted_cb (RBPodcastManager *pd, RhythmDBEntry *entry)
1273 gulong type = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TYPE);
1275 if ((type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) && (pd->priv->remove_files == TRUE) )
1277 const gchar *file_name;
1278 const gchar *dir_name;
1279 const gchar *conf_dir_name;
1280 GnomeVFSURI *uri;
1282 /* make sure we're not downloading it */
1283 rb_podcast_manager_cancel_download (pd, entry);
1285 file_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
1286 if (file_name == NULL) {
1287 /* episode has not been downloaded */
1288 return;
1291 uri = gnome_vfs_uri_new (file_name);
1293 if ((uri != NULL) && (gnome_vfs_uri_is_local (uri) == TRUE)) {
1294 gnome_vfs_unlink (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT));
1296 /* remove dir */
1297 rb_debug ("removing dir");
1298 conf_dir_name = eel_gconf_get_string (CONF_STATE_PODCAST_DOWNLOAD_DIR);
1300 dir_name = g_build_filename (conf_dir_name,
1301 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM),
1302 NULL);
1303 gnome_vfs_remove_directory (dir_name);
1306 else if (type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
1308 GtkTreeModel* query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty(pd->priv->db));
1309 GtkTreeIter iter;
1311 rhythmdb_do_full_query (pd->priv->db,
1312 RHYTHMDB_QUERY_RESULTS (query_model),
1313 RHYTHMDB_QUERY_PROP_EQUALS,
1314 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
1315 RHYTHMDB_QUERY_PROP_LIKE,
1316 RHYTHMDB_PROP_SUBTITLE, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
1317 RHYTHMDB_QUERY_END);
1319 if (gtk_tree_model_get_iter_first (query_model, &iter)) {
1320 gboolean has_next;
1321 do {
1322 RhythmDBEntry *entry;
1323 gtk_tree_model_get (query_model, &iter, 0, &entry, -1);
1324 has_next = gtk_tree_model_iter_next (query_model, &iter);
1325 rhythmdb_entry_delete (pd->priv->db, entry);
1326 } while (has_next);
1327 rhythmdb_commit (pd->priv->db);
1333 void
1334 rb_podcast_manager_cancel_download (RBPodcastManager *pd, RhythmDBEntry *entry)
1336 GList *lst;
1338 g_mutex_lock (pd->priv->download_list_mutex);
1340 lst = pd->priv->download_list;
1341 while (lst) {
1342 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) lst->data;
1343 if (data->entry == entry) {
1344 rb_debug ("Found job");
1345 break;
1347 lst = lst->next;
1349 g_mutex_unlock (pd->priv->download_list_mutex);
1351 if (lst)
1352 cancel_job (lst->data);
1356 static void
1357 rb_podcast_manager_update_synctime (RBPodcastManager *pd)
1359 gint value;
1360 gint index = eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL);
1362 switch (index)
1364 case UPDATE_EVERY_HOUR:
1365 value = time (NULL) + 3600;
1366 break;
1367 case UPDATE_EVERY_DAY:
1368 value = time (NULL) + (3600 * 24);
1369 break;
1370 case UPDATE_EVERY_WEEK:
1371 value = time (NULL) + (3600 * 24 * 7);
1372 break;
1373 case UPDATE_MANUALLY:
1374 value = 0;
1375 break;
1376 default:
1377 value = 0;
1380 eel_gconf_set_integer (CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME, value);
1381 eel_gconf_suggest_sync ();
1382 pd->priv->next_time = value;
1383 rb_podcast_manager_start_sync (pd);
1386 static void rb_podcast_manager_config_changed (GConfClient* client,
1387 guint cnxn_id,
1388 GConfEntry *entry,
1389 gpointer user_data)
1391 rb_podcast_manager_update_synctime (RB_PODCAST_MANAGER (user_data));
1395 void
1396 rb_podcast_manager_set_remove_files (RBPodcastManager *pd, gboolean flag)
1398 pd->priv->remove_files = flag;
1402 gboolean
1403 rb_podcast_manager_get_remove_files (RBPodcastManager *pd)
1405 return pd->priv->remove_files;
1408 static void
1409 rb_podcast_manager_insert_feed (RBPodcastManager *pd, RBPodcastChannel *data)
1411 GValue description_val = { 0, };
1412 GValue title_val = { 0, };
1413 GValue subtitle_val = { 0, };
1414 GValue summary_val = { 0, };
1415 GValue lang_val = { 0, };
1416 GValue copyright_val = { 0, };
1417 GValue image_val = { 0, };
1418 GValue author_val = { 0, };
1419 GValue status_val = { 0, };
1420 GValue last_post_val = { 0, };
1421 GValue last_update_val = { 0, };
1422 gulong last_post = 0;
1423 gulong new_last_post;
1424 gboolean new_feed, updated;
1425 RhythmDB *db = pd->priv->db;
1427 RhythmDBEntry *entry;
1429 GList *lst_songs;
1431 if (data->title == NULL) {
1432 g_list_free (data->posts);
1433 g_free (data);
1434 return;
1437 new_feed = TRUE;
1439 /* processing podcast head */
1440 entry = rhythmdb_entry_lookup_by_location (db, (gchar* )data->url);
1441 if (entry) {
1442 if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TYPE) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
1443 return;
1445 rb_debug ("Head found");
1446 g_value_init (&status_val, G_TYPE_ULONG);
1447 g_value_set_ulong (&status_val, 1);
1448 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1449 g_value_unset (&status_val);
1450 last_post = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_POST_TIME);
1451 new_feed = FALSE;
1452 } else {
1453 rb_debug ("Insert new entry");
1454 entry = rhythmdb_entry_new (db,
1455 RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
1456 (gchar*) data->url);
1457 if (entry == NULL)
1458 return;
1459 rb_debug("New entry create\n");
1461 g_value_init (&title_val, G_TYPE_STRING);
1462 g_value_set_string (&title_val, (gchar* ) data->title);
1463 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_TITLE, &title_val);
1464 g_value_unset (&title_val);
1466 g_value_init (&author_val, G_TYPE_STRING);
1467 if (data->author)
1468 g_value_set_string (&author_val, (gchar* ) data->author);
1469 else
1470 g_value_set_static_string (&author_val, _("Unknown"));
1471 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_ARTIST, &author_val);
1472 g_value_unset (&author_val);
1475 if (data->subtitle) {
1476 g_value_init (&subtitle_val, G_TYPE_STRING);
1477 g_value_set_string (&subtitle_val, (gchar* ) data->subtitle);
1478 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_SUBTITLE, &subtitle_val);
1479 g_value_unset (&subtitle_val);
1482 if (data->description) {
1483 g_value_init (&description_val, G_TYPE_STRING);
1484 g_value_set_string (&description_val, (gchar* ) data->description);
1485 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_DESCRIPTION, &description_val);
1486 g_value_unset (&description_val);
1489 if (data->summary) {
1490 g_value_init (&summary_val, G_TYPE_STRING);
1491 g_value_set_string (&summary_val, (gchar* ) data->summary);
1492 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_SUMMARY, &summary_val);
1493 g_value_unset (&summary_val);
1496 if (data->lang) {
1497 g_value_init (&lang_val, G_TYPE_STRING);
1498 g_value_set_string (&lang_val, (gchar* ) data->lang);
1499 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_LANG, &lang_val);
1500 g_value_unset (&lang_val);
1503 if (data->copyright) {
1504 g_value_init (&copyright_val, G_TYPE_STRING);
1505 g_value_set_string (&copyright_val, (gchar* ) data->copyright);
1506 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_COPYRIGHT, &copyright_val);
1507 g_value_unset (&copyright_val);
1510 if (data->img) {
1511 g_value_init (&image_val, G_TYPE_STRING);
1512 g_value_set_string (&image_val, (gchar* ) data->img);
1513 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_IMAGE, &image_val);
1514 g_value_unset (&image_val);
1517 g_value_init (&status_val, G_TYPE_ULONG);
1518 g_value_set_ulong (&status_val, 1);
1519 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1520 g_value_unset (&status_val);
1522 rb_debug("Podcast head Inserted");
1525 /* insert episodes */
1526 new_last_post = last_post;
1528 updated = FALSE;
1529 for (lst_songs = data->posts; lst_songs != NULL; lst_songs = g_list_next (lst_songs)) {
1530 RBPodcastItem *item = (RBPodcastItem *) lst_songs->data;
1532 if (item->pub_date > last_post || item->pub_date == 0) {
1533 gulong status;
1534 updated = TRUE;
1536 /* last episode gets status RHYTHMDB_PODCAST_STATUS_WAITING, so that it begins downloading */
1537 if (lst_songs == (g_list_last (data->posts)))
1538 status = RHYTHMDB_PODCAST_STATUS_WAITING;
1539 else
1540 status = RHYTHMDB_PODCAST_STATUS_PAUSED;
1542 rb_podcast_manager_add_post (db,
1543 (gchar*) data->title,
1544 (gchar*) item->title,
1545 (gchar*) data->url,
1546 (gchar*) (item->author ? item->author : data->author),
1547 (gchar*) item->url,
1548 (gchar*) item->description,
1549 status,
1550 (gulong) (item->pub_date > 0 ? item->pub_date : data->pub_date),
1551 (gulong) item->duration,
1552 item->filesize);
1553 if (item->pub_date > new_last_post)
1554 new_last_post = item->pub_date;
1558 if (updated)
1559 g_signal_emit (pd, rb_podcast_manager_signals[FEED_UPDATES_AVALIABLE],
1560 0, entry);
1562 if (data->pub_date > new_last_post)
1563 new_last_post = data->pub_date;
1565 g_value_init (&last_post_val, G_TYPE_ULONG);
1566 g_value_set_ulong (&last_post_val, new_last_post);
1568 if (new_feed)
1569 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
1570 else
1571 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
1572 g_value_unset (&last_post_val);
1574 g_value_init (&last_update_val, G_TYPE_ULONG);
1575 g_value_set_ulong (&last_update_val, time(NULL));
1577 if (new_feed)
1578 rhythmdb_entry_set_uninserted (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1579 else
1580 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1581 g_value_unset (&last_update_val);
1583 rhythmdb_commit (db);
1587 static gboolean
1588 rb_podcast_manager_event_loop (RBPodcastManager *pd)
1590 RBPodcastManagerEvent *event;
1592 while ((event = g_async_queue_try_pop (pd->priv->event_queue))) {
1593 switch (event->type)
1595 case EVENT_INSERT_FEED:
1596 rb_podcast_manager_insert_feed (pd, event->channel);
1597 break;
1598 case EVENT_ERROR_FEED:
1600 gchar *error_msg;
1601 error_msg = g_strdup_printf (_("There was a problem adding this podcast. Please verify the URL: %s"),
1602 (gchar*) event->channel->url);
1603 g_signal_emit (G_OBJECT (pd),
1604 rb_podcast_manager_signals[PROCESS_ERROR],
1605 0, error_msg);
1606 g_free (error_msg);
1607 break;
1611 rb_podcast_parse_channel_free (event->channel);
1612 g_free (event);
1615 g_async_queue_unref (pd->priv->event_queue);
1617 return FALSE;
1620 static void
1621 rb_podcast_manager_abort_subscribe (RBPodcastManager *pd)
1623 RBPodcastManagerEvent *event;
1625 /* remove all event processing functions */
1626 while (g_idle_remove_by_data (pd))
1629 /* purge the event queue */
1630 while ((event = g_async_queue_try_pop (pd->priv->event_queue))) {
1631 rb_podcast_parse_channel_free (event->channel);
1632 g_free (event);
1636 void
1637 rb_podcast_manager_shutdown (RBPodcastManager *pd)
1639 rb_podcast_manager_cancel_all (pd);
1640 rb_podcast_manager_abort_subscribe (pd);
1643 gchar *
1644 rb_podcast_manager_get_podcast_dir (RBPodcastManager *pd)
1646 gchar *conf_dir_name = eel_gconf_get_string (CONF_STATE_PODCAST_DOWNLOAD_DIR);
1648 if (conf_dir_name == NULL || (strcmp (conf_dir_name, "") == 0)) {
1649 conf_dir_name = g_build_filename (g_get_home_dir (),
1650 "Podcasts",
1651 NULL);
1652 eel_gconf_set_string (CONF_STATE_PODCAST_DOWNLOAD_DIR, conf_dir_name);
1655 return conf_dir_name;