udapted vi.po
[rhythmbox.git] / podcast / rb-podcast-manager.c
blob3da2c6e6d1399d882a7b35181fc2b83cf7c92292
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"
45 #include "rb-util.h"
47 #define CONF_STATE_PODCAST_PREFIX CONF_PREFIX "/state/podcast"
48 #define CONF_STATE_PODCAST_DOWNLOAD_DIR CONF_STATE_PODCAST_PREFIX "/download_prefix"
49 #define CONF_STATE_PODCAST_DOWNLOAD_INTERVAL CONF_STATE_PODCAST_PREFIX "/download_interval"
50 #define CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME CONF_STATE_PODCAST_PREFIX "/download_next_time"
52 enum
54 PROP_0,
55 PROP_DB
58 enum
60 UPDATE_EVERY_HOUR,
61 UPDATE_EVERY_DAY,
62 UPDATE_EVERY_WEEK,
63 UPDATE_MANUALLY
66 enum
68 STATUS_CHANGED,
69 START_DOWNLOAD,
70 FINISH_DOWNLOAD,
71 PROCESS_ERROR,
72 FEED_UPDATES_AVAILABLE,
73 LAST_SIGNAL
76 typedef enum
78 RESULT_PARSE_OK,
79 RESULT_PARSE_ERROR
80 } RBPodcastParseResultType;
82 /* passed from feed parsing threads back to main thread */
83 typedef struct
85 RBPodcastParseResultType result;
86 RBPodcastChannel *channel;
87 RBPodcastManager *pd;
88 } RBPodcastManagerParseResult;
90 typedef struct
92 RBPodcastManager *pd;
93 RhythmDBEntry *entry;
94 GnomeVFSAsyncHandle *read_handle;
95 GnomeVFSURI *write_uri;
96 GnomeVFSURI *read_uri;
97 char *query_string;
99 guint total_size;
100 guint progress;
101 gboolean cancelled;
102 } RBPodcastManagerInfo;
104 typedef struct
106 RBPodcastManager *pd;
107 char *url;
108 } RBPodcastThreadInfo;
110 struct RBPodcastManagerPrivate
112 RhythmDB *db;
113 GList *download_list;
114 RBPodcastManagerInfo *active_download;
115 guint next_time;
116 guint source_sync;
117 guint update_interval_notify_id;
118 gboolean shutdown;
120 gboolean remove_files;
123 #define RB_PODCAST_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PODCAST_MANAGER, RBPodcastManagerPrivate))
126 static guint rb_podcast_manager_signals[LAST_SIGNAL] = { 0 };
128 /* functions */
129 static void rb_podcast_manager_class_init (RBPodcastManagerClass *klass);
130 static void rb_podcast_manager_init (RBPodcastManager *dp);
131 static GObject *rb_podcast_manager_constructor (GType type, guint n_construct_properties,
132 GObjectConstructParam *construct_properties);
133 static void rb_podcast_manager_dispose (GObject *object);
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_download_file_info_cb (GnomeVFSAsyncHandle *handle,
144 GList *results,
145 RBPodcastManagerInfo *data);
146 static void rb_podcast_manager_abort_download (RBPodcastManagerInfo *data);
147 static gboolean rb_podcast_manager_sync_head_cb (gpointer data);
148 static gboolean rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
149 GtkTreePath *path,
150 GtkTreeIter *iter,
151 RBPodcastManager *data);
152 static gboolean rb_podcast_manager_save_metadata (RhythmDB *db,
153 RhythmDBEntry *entry,
154 const char *uri);
155 static void rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd,
156 RhythmDBEntry *entry);
157 static void rb_podcast_manager_db_entry_deleted_cb (RBPodcastManager *pd,
158 RhythmDBEntry *entry);
159 static gboolean rb_podcast_manager_next_file (RBPodcastManager * pd);
160 static void rb_podcast_manager_insert_feed (RBPodcastManager *pd, RBPodcastChannel *data);
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 void start_job (RBPodcastManagerInfo *data);
174 static gboolean end_job (RBPodcastManagerInfo *data);
175 static void cancel_job (RBPodcastManagerInfo *pd);
176 static void rb_podcast_manager_update_synctime (RBPodcastManager *pd);
177 static void rb_podcast_manager_config_changed (GConfClient* client,
178 guint cnxn_id,
179 GConfEntry *entry,
180 gpointer user_data);
182 G_DEFINE_TYPE (RBPodcastManager, rb_podcast_manager, G_TYPE_OBJECT)
184 static void
185 rb_podcast_manager_class_init (RBPodcastManagerClass *klass)
187 GObjectClass *object_class = G_OBJECT_CLASS (klass);
189 object_class->constructor = rb_podcast_manager_constructor;
190 object_class->dispose = rb_podcast_manager_dispose;
191 object_class->finalize = rb_podcast_manager_finalize;
193 object_class->set_property = rb_podcast_manager_set_property;
194 object_class->get_property = rb_podcast_manager_get_property;
196 g_object_class_install_property (object_class,
197 PROP_DB,
198 g_param_spec_object ("db",
199 "db",
200 "database",
201 RHYTHMDB_TYPE,
202 G_PARAM_READWRITE));
204 rb_podcast_manager_signals[STATUS_CHANGED] =
205 g_signal_new ("status_changed",
206 G_OBJECT_CLASS_TYPE (object_class),
207 GTK_RUN_LAST,
208 G_STRUCT_OFFSET (RBPodcastManagerClass, status_changed),
209 NULL, NULL,
210 rb_marshal_VOID__BOXED_ULONG,
211 G_TYPE_NONE,
213 RHYTHMDB_TYPE_ENTRY,
214 G_TYPE_ULONG);
216 rb_podcast_manager_signals[START_DOWNLOAD] =
217 g_signal_new ("start_download",
218 G_OBJECT_CLASS_TYPE (object_class),
219 GTK_RUN_LAST,
220 G_STRUCT_OFFSET (RBPodcastManagerClass, start_download),
221 NULL, NULL,
222 g_cclosure_marshal_VOID__BOXED,
223 G_TYPE_NONE,
225 RHYTHMDB_TYPE_ENTRY);
227 rb_podcast_manager_signals[FINISH_DOWNLOAD] =
228 g_signal_new ("finish_download",
229 G_OBJECT_CLASS_TYPE (object_class),
230 GTK_RUN_LAST,
231 G_STRUCT_OFFSET (RBPodcastManagerClass, finish_download),
232 NULL, NULL,
233 g_cclosure_marshal_VOID__BOXED,
234 G_TYPE_NONE,
236 RHYTHMDB_TYPE_ENTRY);
238 rb_podcast_manager_signals[FEED_UPDATES_AVAILABLE] =
239 g_signal_new ("feed_updates_available",
240 G_OBJECT_CLASS_TYPE (object_class),
241 GTK_RUN_LAST,
242 G_STRUCT_OFFSET (RBPodcastManagerClass, feed_updates_available),
243 NULL, NULL,
244 g_cclosure_marshal_VOID__BOXED,
245 G_TYPE_NONE,
247 RHYTHMDB_TYPE_ENTRY);
249 rb_podcast_manager_signals[PROCESS_ERROR] =
250 g_signal_new ("process_error",
251 G_OBJECT_CLASS_TYPE (object_class),
252 GTK_RUN_LAST,
253 G_STRUCT_OFFSET (RBPodcastManagerClass, process_error),
254 NULL, NULL,
255 g_cclosure_marshal_VOID__STRING,
256 G_TYPE_NONE,
258 G_TYPE_STRING);
260 g_type_class_add_private (klass, sizeof (RBPodcastManagerPrivate));
263 static void
264 rb_podcast_manager_init (RBPodcastManager *pd)
266 pd->priv = RB_PODCAST_MANAGER_GET_PRIVATE (pd);
268 pd->priv->source_sync = 0;
269 pd->priv->db = NULL;
270 eel_gconf_monitor_add (CONF_STATE_PODCAST_PREFIX);
273 static GObject *
274 rb_podcast_manager_constructor (GType type, guint n_construct_properties,
275 GObjectConstructParam *construct_properties)
277 RBPodcastManager *pd;
279 pd = RB_PODCAST_MANAGER (G_OBJECT_CLASS (rb_podcast_manager_parent_class)
280 ->constructor (type, n_construct_properties, construct_properties));
282 pd->priv->update_interval_notify_id = eel_gconf_notification_add (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL,
283 rb_podcast_manager_config_changed,
284 pd);
286 return G_OBJECT (pd);
290 static void
291 rb_podcast_manager_dispose (GObject *object)
293 RBPodcastManager *pd;
294 g_return_if_fail (object != NULL);
295 g_return_if_fail (RB_IS_PODCAST_MANAGER (object));
297 pd = RB_PODCAST_MANAGER (object);
298 g_return_if_fail (pd->priv != NULL);
300 eel_gconf_monitor_remove (CONF_STATE_PODCAST_PREFIX);
302 if (pd->priv->source_sync != 0) {
303 g_source_remove (pd->priv->source_sync);
304 pd->priv->source_sync = 0;
307 if (pd->priv->update_interval_notify_id != 0) {
308 eel_gconf_notification_remove (pd->priv->update_interval_notify_id);
309 pd->priv->update_interval_notify_id = 0;
312 if (pd->priv->db != NULL) {
313 g_object_unref (pd->priv->db);
314 pd->priv->db = NULL;
317 G_OBJECT_CLASS (rb_podcast_manager_parent_class)->dispose (object);
320 static void
321 rb_podcast_manager_finalize (GObject *object)
323 RBPodcastManager *pd;
324 g_return_if_fail (object != NULL);
325 g_return_if_fail (RB_IS_PODCAST_MANAGER (object));
327 pd = RB_PODCAST_MANAGER(object);
329 g_return_if_fail (pd->priv != NULL);
331 if (pd->priv->download_list) {
332 g_list_foreach (pd->priv->download_list, (GFunc)g_free, NULL);
333 g_list_free (pd->priv->download_list);
336 G_OBJECT_CLASS (rb_podcast_manager_parent_class)->finalize (object);
339 static void
340 rb_podcast_manager_set_property (GObject *object,
341 guint prop_id,
342 const GValue *value,
343 GParamSpec *pspec)
345 RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
347 switch (prop_id) {
348 case PROP_DB:
349 if (pd->priv->db) {
350 g_signal_handlers_disconnect_by_func (pd->priv->db,
351 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
352 pd);
354 g_signal_handlers_disconnect_by_func (pd->priv->db,
355 G_CALLBACK (rb_podcast_manager_db_entry_deleted_cb),
356 pd);
357 g_object_unref (pd->priv->db);
360 pd->priv->db = g_value_get_object (value);
361 g_object_ref (pd->priv->db);
363 g_signal_connect_object (pd->priv->db,
364 "entry-added",
365 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
366 pd, G_CONNECT_SWAPPED);
368 g_signal_connect_object (pd->priv->db,
369 "entry_deleted",
370 G_CALLBACK (rb_podcast_manager_db_entry_deleted_cb),
371 pd, G_CONNECT_SWAPPED);
373 break;
374 default:
375 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
379 static void
380 rb_podcast_manager_get_property (GObject *object,
381 guint prop_id,
382 GValue *value,
383 GParamSpec *pspec)
385 RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
387 switch (prop_id) {
388 case PROP_DB:
389 g_value_set_object (value, pd->priv->db);
390 break;
391 default:
392 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
397 RBPodcastManager *
398 rb_podcast_manager_new (RhythmDB *db)
400 RBPodcastManager *pd;
402 pd = g_object_new (RB_TYPE_PODCAST_MANAGER, "db", db, NULL);
403 return pd;
406 void
407 rb_podcast_manager_download_entry (RBPodcastManager *pd,
408 RhythmDBEntry *entry)
410 gulong status;
411 g_assert (rb_is_main_thread ());
413 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
415 if (entry == NULL)
416 return;
418 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
419 if ((status < RHYTHMDB_PODCAST_STATUS_COMPLETE) ||
420 (status == RHYTHMDB_PODCAST_STATUS_WAITING)) {
421 RBPodcastManagerInfo *data;
422 if (status < RHYTHMDB_PODCAST_STATUS_COMPLETE) {
423 GValue status_val = { 0, };
424 g_value_init (&status_val, G_TYPE_ULONG);
425 g_value_set_ulong (&status_val, RHYTHMDB_PODCAST_STATUS_WAITING);
426 rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &status_val);
427 g_value_unset (&status_val);
429 rhythmdb_commit (pd->priv->db);
431 rb_debug ("Adding podcast episode %s to download list",
432 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
434 data = g_new0 (RBPodcastManagerInfo, 1);
435 data->pd = g_object_ref (pd);
436 data->entry = rhythmdb_entry_ref (entry);
438 pd->priv->download_list = g_list_append (pd->priv->download_list, data);
439 g_idle_add ((GSourceFunc) rb_podcast_manager_next_file, pd);
443 gboolean
444 rb_podcast_manager_entry_downloaded (RhythmDBEntry *entry)
446 gulong status;
447 const gchar *file_name;
448 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
450 g_assert (type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
452 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
453 file_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
455 return (status != RHYTHMDB_PODCAST_STATUS_ERROR && file_name != NULL);
458 void
459 rb_podcast_manager_start_sync (RBPodcastManager *pd)
461 gint next_time;
463 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
465 if (pd->priv->next_time > 0) {
466 next_time = pd->priv->next_time;
467 } else {
468 next_time = eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME);
471 if (next_time > 0) {
472 if (pd->priv->source_sync != 0) {
473 g_source_remove (pd->priv->source_sync);
474 pd->priv->source_sync = 0;
476 next_time = next_time - ((int)time (NULL));
477 if (next_time <= 0) {
478 rb_podcast_manager_update_feeds (pd);
479 pd->priv->next_time = 0;
480 rb_podcast_manager_update_synctime (pd);
481 return;
483 pd->priv->source_sync = g_timeout_add (next_time * 1000, (GSourceFunc) rb_podcast_manager_sync_head_cb, pd);
488 static gboolean
489 rb_podcast_manager_sync_head_cb (gpointer data)
491 RBPodcastManager *pd = RB_PODCAST_MANAGER (data);
493 g_assert (rb_is_main_thread ());
495 GDK_THREADS_ENTER ();
497 rb_podcast_manager_update_feeds (pd);
498 pd->priv->source_sync = 0;
499 pd->priv->next_time = 0;
500 rb_podcast_manager_update_synctime (RB_PODCAST_MANAGER (data));
501 GDK_THREADS_LEAVE ();
502 return FALSE;
505 void
506 rb_podcast_manager_update_feeds (RBPodcastManager *pd)
508 GtkTreeModel *query_model;
510 g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
512 query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (pd->priv->db));
514 rhythmdb_do_full_query (pd->priv->db,
515 RHYTHMDB_QUERY_RESULTS (query_model),
516 RHYTHMDB_QUERY_PROP_EQUALS,
517 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
518 RHYTHMDB_QUERY_END);
520 gtk_tree_model_foreach (query_model,
521 (GtkTreeModelForeachFunc) rb_podcast_manager_head_query_cb,
522 pd);
524 g_object_unref (query_model);
527 static gboolean
528 rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
529 GtkTreePath *path,
530 GtkTreeIter *iter,
531 RBPodcastManager *manager)
533 const char *uri;
534 RhythmDBEntry *entry;
535 guint status;
537 gtk_tree_model_get (query_model, iter, 0, &entry, -1);
538 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
539 status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
541 if (status == 1)
542 rb_podcast_manager_subscribe_feed (manager, uri);
544 rhythmdb_entry_unref (entry);
546 return FALSE;
549 static gboolean
550 rb_podcast_manager_next_file (RBPodcastManager * pd)
552 const char *location;
553 RBPodcastManagerInfo *data;
554 char *query_string;
555 GList *d;
557 g_assert (rb_is_main_thread ());
559 rb_debug ("looking for something to download");
561 GDK_THREADS_ENTER ();
563 if (pd->priv->active_download != NULL) {
564 rb_debug ("already downloading something");
565 GDK_THREADS_LEAVE ();
566 return FALSE;
569 d = g_list_first (pd->priv->download_list);
570 if (d == NULL) {
571 rb_debug ("download queue is empty");
572 GDK_THREADS_LEAVE ();
573 return FALSE;
576 data = (RBPodcastManagerInfo *) d->data;
577 g_assert (data != NULL);
578 g_assert (data->entry != NULL);
580 pd->priv->active_download = data;
582 location = rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION);
583 rb_debug ("processing %s", location);
585 /* gnome-vfs currently doesn't handle HTTP query strings correctly.
586 * so we do it ourselves.
588 query_string = strrchr (location, '?');
589 if (query_string != NULL) {
590 char *base_uri;
592 base_uri = g_strdup (location);
593 query_string = strrchr (base_uri, '?');
594 *query_string++ = '\0';
595 rb_debug ("hiding query string %s from gnome-vfs", query_string);
597 data->read_uri = gnome_vfs_uri_new (base_uri);
598 if (data->read_uri != NULL) {
599 char *full_uri;
601 full_uri = g_strdup_printf ("%s?%s",
602 data->read_uri->text,
603 query_string);
604 g_free (data->read_uri->text);
605 data->read_uri->text = full_uri;
607 /* include the question mark in data->query_string to make
608 * the later check easier.
610 query_string--;
611 *query_string = '?';
612 data->query_string = g_strdup (query_string);
614 g_free (base_uri);
615 } else {
616 data->read_uri = gnome_vfs_uri_new (location);
619 if (data->read_uri == NULL) {
620 rb_debug ("Error downloading podcast: could not create remote uri");
621 rb_podcast_manager_abort_download (data);
622 } else {
623 GList *l;
625 l = g_list_prepend (NULL, data->read_uri);
626 gnome_vfs_async_get_file_info (&data->read_handle,
628 GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
629 GNOME_VFS_PRIORITY_DEFAULT,
630 (GnomeVFSAsyncGetFileInfoCallback) rb_podcast_manager_download_file_info_cb,
631 data);
632 g_list_free (l);
635 GDK_THREADS_LEAVE ();
636 return FALSE;
639 static void
640 rb_podcast_manager_download_file_info_cb (GnomeVFSAsyncHandle *handle,
641 GList *results,
642 RBPodcastManagerInfo *data)
644 GnomeVFSGetFileInfoResult *result = results->data;
645 char *local_file_name;
646 char *local_file_path;
647 char *dir_name;
648 char *conf_dir_name;
650 g_assert (rb_is_main_thread ());
652 rb_debug ("got file info results for %s",
653 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION));
655 if (result->result != GNOME_VFS_OK) {
657 GValue val = {0,};
659 g_value_init (&val, G_TYPE_ULONG);
660 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
661 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
662 g_value_unset (&val);
664 g_value_init (&val, G_TYPE_STRING);
665 g_value_set_string (&val, gnome_vfs_result_to_string (result->result));
666 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
667 g_value_unset (&val);
669 rhythmdb_commit (data->pd->priv->db);
671 rb_debug ("get_file_info request failed");
672 rb_podcast_manager_abort_download (data);
673 return;
676 /* construct download directory */
677 conf_dir_name = rb_podcast_manager_get_podcast_dir (data->pd);
678 dir_name = g_build_filename (conf_dir_name,
679 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_ALBUM),
680 NULL);
681 g_free (conf_dir_name);
683 if (g_mkdir_with_parents (dir_name, 0750) == -1) {
684 rb_debug ("Could not create podcast download directory %s", dir_name);
685 /* FIXME: display error to user */
686 g_free (dir_name);
687 rb_podcast_manager_abort_download (data);
688 return;
691 /* if the filename ends with the query string from the original URI,
692 * remove it.
694 if (data->query_string &&
695 g_str_has_suffix (result->file_info->name, data->query_string)) {
696 local_file_name = g_strdup (result->file_info->name);
697 local_file_name[strlen (local_file_name) - strlen (data->query_string)] = '\0';
698 rb_debug ("removing query string \"%s\" -> local file name \"%s\"", data->query_string, local_file_name);
699 } else {
700 local_file_name = result->file_info->name;
703 /* construct local filename */
704 local_file_path = g_build_filename (dir_name,
705 local_file_name,
706 NULL);
708 if (local_file_name != result->file_info->name)
709 g_free (local_file_name);
711 g_free (dir_name);
712 rb_debug ("creating file %s", local_file_path);
714 data->write_uri = gnome_vfs_uri_new (local_file_path);
715 if (data->write_uri == NULL) {
716 g_warning ("Could not create local podcast URI for %s", local_file_path);
717 rb_podcast_manager_abort_download (data);
718 return;
721 if (g_file_test (local_file_path, G_FILE_TEST_EXISTS)) {
722 guint64 local_size;
723 GnomeVFSFileInfo *local_info;
724 GnomeVFSResult local_result;
726 local_info = gnome_vfs_file_info_new ();
727 local_result = gnome_vfs_get_file_info (local_file_path,
728 local_info,
729 GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
730 local_size = local_info->size;
731 gnome_vfs_file_info_unref (local_info);
733 if (local_result != GNOME_VFS_OK) {
734 g_warning ("Could not get info on downloaded podcast file %s",
735 local_file_path);
736 rb_podcast_manager_abort_download (data);
737 return;
738 } else if (result->file_info->size == local_size) {
739 GValue val = {0,};
740 char *uri;
741 char *canon_uri;
743 uri = gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE);
744 canon_uri = rb_canonicalise_uri (uri);
745 g_free (uri);
747 rb_debug ("podcast %s already downloaded",
748 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION));
750 g_value_init (&val, G_TYPE_ULONG);
751 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
752 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
753 g_value_unset (&val);
755 g_value_init (&val, G_TYPE_STRING);
756 g_value_set_string (&val, canon_uri);
757 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
758 g_value_unset (&val);
760 rb_podcast_manager_save_metadata (data->pd->priv->db, data->entry, canon_uri);
762 g_free (canon_uri);
764 rb_podcast_manager_abort_download (data);
765 return;
766 } else if (result->file_info->size > local_size) {
767 /* TODO: support resume file */
768 rb_debug ("podcast episode already partially downloaded, but we can't resume downloads");
769 } else {
770 /* the local file is larger. replace it */
774 g_free (local_file_path);
775 start_job (data);
779 static void
780 rb_podcast_manager_abort_download (RBPodcastManagerInfo *data)
782 RBPodcastManager *mgr = data->pd;
784 g_assert (rb_is_main_thread ());
786 mgr->priv->download_list = g_list_remove (mgr->priv->download_list, data);
787 download_info_free (data);
789 if (mgr->priv->active_download == data)
790 mgr->priv->active_download = NULL;
792 g_idle_add ((GSourceFunc) rb_podcast_manager_next_file, mgr);
795 gboolean
796 rb_podcast_manager_subscribe_feed (RBPodcastManager *pd, const char *url)
798 RBPodcastThreadInfo *info;
799 gchar *valid_url = gnome_vfs_make_uri_from_input (url);
801 if (valid_url == NULL) {
802 rb_error_dialog (NULL, _("Invalid URL"),
803 _("The URL \"%s\" is not valid, please check it."), url);
804 return FALSE;
807 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, valid_url);
808 if (entry) {
809 if (rhythmdb_entry_get_entry_type (entry) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
810 /* added as something else, probably iradio */
811 rb_error_dialog (NULL, _("URL already added"),
812 _("The URL \"%s\" has already been added as a radio station. "
813 "If this is a podcast feed, please remove the radio station."), url);
814 return FALSE;
818 info = g_new0 (RBPodcastThreadInfo, 1);
819 info->pd = g_object_ref (pd);
820 info->url = valid_url;
822 g_thread_create ((GThreadFunc) rb_podcast_manager_thread_parse_feed,
823 info, FALSE, NULL);
825 return TRUE;
828 static void
829 rb_podcast_manager_free_parse_result (RBPodcastManagerParseResult *result)
831 rb_podcast_parse_channel_free (result->channel);
832 g_object_unref (result->pd);
833 g_free (result);
836 static gboolean
837 rb_podcast_manager_parse_complete_cb (RBPodcastManagerParseResult *result)
839 GDK_THREADS_ENTER ();
840 if (result->pd->priv->shutdown) {
841 GDK_THREADS_LEAVE ();
842 return FALSE;
845 switch (result->result)
847 case RESULT_PARSE_OK:
848 rb_podcast_manager_insert_feed (result->pd, result->channel);
849 break;
850 case RESULT_PARSE_ERROR:
852 gchar *error_msg;
853 error_msg = g_strdup_printf (_("There was a problem adding this podcast. Please verify the URL: %s"),
854 (gchar *) result->channel->url);
855 g_signal_emit (result->pd,
856 rb_podcast_manager_signals[PROCESS_ERROR],
857 0, error_msg);
858 g_free (error_msg);
859 break;
863 GDK_THREADS_LEAVE ();
864 return FALSE;
868 static gpointer
869 rb_podcast_manager_thread_parse_feed (RBPodcastThreadInfo *info)
871 RBPodcastChannel *feed = g_new0 (RBPodcastChannel, 1);
873 if (rb_podcast_parse_load_feed (feed, info->url)) {
874 RBPodcastManagerParseResult *result;
876 result = g_new0 (RBPodcastManagerParseResult, 1);
877 result->channel = feed;
878 result->result = (feed->title == NULL) ? RESULT_PARSE_ERROR : RESULT_PARSE_OK;
879 result->pd = g_object_ref (info->pd);
881 g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
882 (GSourceFunc) rb_podcast_manager_parse_complete_cb,
883 result,
884 (GDestroyNotify) rb_podcast_manager_free_parse_result);
887 g_object_unref (info->pd);
888 g_free (info->url);
889 g_free (info);
890 return NULL;
893 RhythmDBEntry *
894 rb_podcast_manager_add_post (RhythmDB *db,
895 const char *name,
896 const char *title,
897 const char *subtitle,
898 const char *generator,
899 const char *uri,
900 const char *description,
901 gulong date,
902 gulong duration,
903 guint64 filesize)
905 RhythmDBEntry *entry;
906 GValue val = {0,};
907 GTimeVal time;
909 if (!uri || !name || !title || !g_utf8_validate(uri, -1, NULL)) {
910 return NULL;
912 entry = rhythmdb_entry_lookup_by_location (db, uri);
913 if (entry)
914 return NULL;
916 entry = rhythmdb_entry_new (db,
917 RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
918 uri);
919 if (entry == NULL)
920 return NULL;
922 g_get_current_time (&time);
923 if (date == 0)
924 date = time.tv_sec;
926 g_value_init (&val, G_TYPE_STRING);
927 g_value_set_string (&val, name);
928 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ALBUM, &val);
930 g_value_reset (&val);
931 g_value_set_static_string (&val, _("Podcast"));
932 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_GENRE, &val);
934 g_value_reset (&val);
935 g_value_set_string (&val, title);
936 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &val);
938 g_value_reset (&val);
939 if (subtitle)
940 g_value_set_string (&val, subtitle);
941 else
942 g_value_set_static_string (&val, "");
943 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUBTITLE, &val);
945 g_value_reset (&val);
946 if (description)
947 g_value_set_string (&val, description);
948 else
949 g_value_set_static_string (&val, "");
950 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DESCRIPTION, &val);
952 g_value_reset (&val);
953 if (generator)
954 g_value_set_string (&val, generator);
955 else
956 g_value_set_static_string (&val, "");
957 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &val);
958 g_value_unset (&val);
960 g_value_init (&val, G_TYPE_ULONG);
961 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_PAUSED);
962 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
964 g_value_reset (&val);
965 g_value_set_ulong (&val, date);
966 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &val);
968 g_value_reset (&val);
969 g_value_set_ulong (&val, duration);
970 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val);
972 g_value_reset (&val);
973 g_value_set_ulong (&val, 0);
974 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_PLAYED, &val);
976 /* first seen */
977 g_value_reset (&val);
978 g_value_set_ulong (&val, time.tv_sec);
979 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &val);
980 g_value_unset (&val);
982 /* initialize the rating */
983 g_value_init (&val, G_TYPE_DOUBLE);
984 g_value_set_double (&val, 2.5);
985 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &val);
986 g_value_unset (&val);
988 g_value_init (&val, G_TYPE_UINT64);
989 g_value_set_uint64 (&val, filesize);
990 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FILE_SIZE, &val);
991 g_value_unset (&val);
993 return entry;
996 static gboolean
997 rb_podcast_manager_save_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *uri)
999 RBMetaData *md = rb_metadata_new ();
1000 GError *error = NULL;
1001 GValue val = { 0, };
1002 const char *mime;
1004 rb_debug ("Loading podcast metadata from %s", uri);
1005 rb_metadata_load (md, uri, &error);
1007 if (error != NULL) {
1008 /* this probably isn't an audio enclosure. or some other error */
1009 g_value_init (&val, G_TYPE_ULONG);
1010 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
1011 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
1012 g_value_unset (&val);
1014 g_value_init (&val, G_TYPE_STRING);
1015 g_value_set_string (&val, error->message);
1016 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
1017 g_value_unset (&val);
1019 rhythmdb_commit (db);
1021 g_object_unref (md);
1022 g_error_free (error);
1024 return FALSE;
1027 mime = rb_metadata_get_mime (md);
1028 if (mime) {
1029 g_value_init (&val, G_TYPE_STRING);
1030 g_value_set_string (&val, mime);
1031 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MIMETYPE, &val);
1032 g_value_unset (&val);
1035 if (rb_metadata_get (md,
1036 RB_METADATA_FIELD_DURATION,
1037 &val)) {
1038 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val);
1039 g_value_unset (&val);
1042 if (rb_metadata_get (md,
1043 RB_METADATA_FIELD_BITRATE,
1044 &val)) {
1045 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_BITRATE, &val);
1046 g_value_unset (&val);
1049 rhythmdb_commit (db);
1051 g_object_unref (md);
1052 return TRUE;
1055 static void
1056 rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd, RhythmDBEntry *entry)
1058 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
1060 if (type != RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
1061 return;
1063 rb_podcast_manager_download_entry (pd, entry);
1066 static void
1067 download_info_free (RBPodcastManagerInfo *data)
1069 if (data->write_uri) {
1070 gnome_vfs_uri_unref (data->write_uri);
1071 data->write_uri = NULL;
1074 if (data->read_uri) {
1075 gnome_vfs_uri_unref (data->read_uri);
1076 data->read_uri = NULL;
1078 if (data->query_string) {
1079 g_free (data->query_string);
1080 data->query_string = NULL;
1083 if (data->entry) {
1084 rhythmdb_entry_unref (data->entry);
1087 g_free (data);
1090 static void
1091 start_job (RBPodcastManagerInfo *data)
1093 GList *source_uri_list;
1094 GList *target_uri_list;
1096 GDK_THREADS_ENTER ();
1097 g_signal_emit (data->pd, rb_podcast_manager_signals[START_DOWNLOAD],
1098 0, data->entry);
1099 GDK_THREADS_LEAVE ();
1101 source_uri_list = g_list_prepend (NULL, data->read_uri);
1102 target_uri_list = g_list_prepend (NULL, data->write_uri);
1104 gnome_vfs_async_xfer (&data->read_handle,
1105 source_uri_list,
1106 target_uri_list,
1107 GNOME_VFS_XFER_DEFAULT ,
1108 GNOME_VFS_XFER_ERROR_MODE_ABORT,
1109 GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
1110 GNOME_VFS_PRIORITY_DEFAULT,
1111 (GnomeVFSAsyncXferProgressCallback) download_progress_update_cb,
1112 data,
1113 (GnomeVFSXferProgressCallback) download_progress_cb,
1114 data);
1116 g_list_free (source_uri_list);
1117 g_list_free (target_uri_list);
1120 static gboolean
1121 end_job (RBPodcastManagerInfo *data)
1123 RBPodcastManager *pd = data->pd;
1125 g_assert (rb_is_main_thread ());
1127 rb_debug ("cleaning up download of %s",
1128 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION));
1130 data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, data);
1132 GDK_THREADS_ENTER ();
1133 g_signal_emit (data->pd, rb_podcast_manager_signals[FINISH_DOWNLOAD],
1134 0, data->entry);
1135 GDK_THREADS_LEAVE ();
1137 g_assert (pd->priv->active_download == data);
1138 pd->priv->active_download = NULL;
1140 download_info_free (data);
1142 g_idle_add ((GSourceFunc) rb_podcast_manager_next_file, pd);
1143 return FALSE;
1146 static void
1147 cancel_job (RBPodcastManagerInfo *data)
1149 g_assert (rb_is_main_thread ());
1150 rb_debug ("cancelling download of %s",
1151 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION));
1153 /* is this the active download? */
1154 if (data == data->pd->priv->active_download) {
1155 data->cancelled = TRUE;
1156 if (data->read_handle != NULL) {
1157 gnome_vfs_async_cancel (data->read_handle);
1158 data->read_handle = NULL;
1161 /* download data will be cleaned up after next progress callback */
1162 } else {
1163 /* destroy download data */
1164 data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, data);
1165 download_info_free (data);
1169 static guint
1170 download_progress_cb (GnomeVFSXferProgressInfo *info, gpointer cb_data)
1172 GValue val = {0, };
1173 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) cb_data;
1175 if (data == NULL) {
1176 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1179 if (info->status != GNOME_VFS_XFER_PROGRESS_STATUS_OK ||
1180 ((info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) && (info->file_size == 0))) {
1182 rb_debug ("error downloading %s",
1183 rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION));
1185 g_value_init (&val, G_TYPE_ULONG);
1186 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
1187 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1188 g_value_unset (&val);
1190 if (info->vfs_status != GNOME_VFS_OK) {
1191 g_value_init (&val, G_TYPE_STRING);
1192 g_value_set_string (&val, gnome_vfs_result_to_string (info->vfs_status));
1193 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
1194 g_value_unset (&val);
1197 rhythmdb_commit (data->pd->priv->db);
1198 g_idle_add ((GSourceFunc)end_job, data);
1199 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1202 if (rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_MOUNTPOINT) == NULL) {
1203 char *uri = gnome_vfs_uri_to_string (data->write_uri, GNOME_VFS_URI_HIDE_NONE);
1204 char *canon_uri = rb_canonicalise_uri (uri);
1205 g_free (uri);
1207 g_value_init (&val, G_TYPE_STRING);
1208 g_value_set_string (&val, canon_uri);
1209 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
1210 g_value_unset (&val);
1212 rhythmdb_commit (data->pd->priv->db);
1213 g_free (canon_uri);
1216 if (info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) {
1217 if (data->cancelled == FALSE) {
1218 char *uri;
1219 char *canon_uri;
1221 uri = gnome_vfs_uri_to_string (data->write_uri,
1222 GNOME_VFS_URI_HIDE_NONE);
1223 canon_uri = rb_canonicalise_uri (uri);
1224 g_free (uri);
1225 rb_debug ("download of %s completed", canon_uri);
1227 g_value_init (&val, G_TYPE_UINT64);
1228 g_value_set_uint64 (&val, info->file_size);
1229 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_FILE_SIZE, &val);
1230 g_value_unset (&val);
1232 g_value_init (&val, G_TYPE_ULONG);
1233 g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
1234 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1235 g_value_unset (&val);
1237 rb_podcast_manager_save_metadata (data->pd->priv->db,
1238 data->entry,
1239 canon_uri);
1240 g_free (canon_uri);
1242 g_idle_add ((GSourceFunc)end_job, data);
1243 return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
1246 return 1;
1249 static guint
1250 download_progress_update_cb (GnomeVFSAsyncHandle *handle, GnomeVFSXferProgressInfo *info, gpointer cb_data)
1252 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) cb_data;
1254 if (data == NULL) {
1255 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1258 if ((info->phase == GNOME_VFS_XFER_PHASE_COPYING) &&
1259 (data->entry != NULL)) {
1260 guint local_progress = 0;
1262 if (info->file_size > 0)
1263 local_progress = (gint) 100 * info->total_bytes_copied / info->file_size;
1265 if (local_progress != data->progress) {
1266 GValue val = {0,};
1268 GDK_THREADS_ENTER ();
1270 g_value_init (&val, G_TYPE_ULONG);
1271 g_value_set_ulong (&val, local_progress);
1272 rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1273 g_value_unset (&val);
1275 g_signal_emit (data->pd, rb_podcast_manager_signals[STATUS_CHANGED],
1276 0, data->entry, local_progress);
1278 GDK_THREADS_LEAVE ();
1280 data->progress = local_progress;
1284 return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
1287 void
1288 rb_podcast_manager_unsubscribe_feed (RhythmDB *db, const char *url)
1290 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (db, url);
1291 if (entry) {
1292 GValue val = {0, };
1293 g_value_init (&val, G_TYPE_ULONG);
1294 g_value_set_ulong (&val, 0);
1295 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
1296 g_value_unset (&val);
1301 gboolean
1302 rb_podcast_manager_remove_feed (RBPodcastManager *pd, const char *url, gboolean remove_files)
1304 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, url);
1306 if (entry) {
1307 rb_debug ("Removing podcast feed: %s remove_files: %d", url, remove_files);
1309 rb_podcast_manager_set_remove_files (pd, remove_files);
1310 rhythmdb_entry_delete (pd->priv->db, entry);
1311 rhythmdb_commit (pd->priv->db);
1312 return TRUE;
1315 return FALSE;
1318 static void
1319 rb_podcast_manager_db_entry_deleted_cb (RBPodcastManager *pd,
1320 RhythmDBEntry *entry)
1322 RhythmDBEntryType type = rhythmdb_entry_get_entry_type (entry);
1324 if ((type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) && (pd->priv->remove_files == TRUE)) {
1325 const char *file_name;
1326 const char *dir_name;
1327 const char *conf_dir_name;
1328 GnomeVFSResult result;
1330 rb_debug ("Handling entry deleted");
1332 /* make sure we're not downloading it */
1333 rb_podcast_manager_cancel_download (pd, entry);
1335 file_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
1336 if (file_name == NULL) {
1337 /* episode has not been downloaded */
1338 rb_debug ("Episode not downloaded, skipping.");
1339 return;
1342 result = gnome_vfs_unlink (file_name);
1343 if (result != GNOME_VFS_OK) {
1344 rb_debug ("Removing episode failed: %s", gnome_vfs_result_to_string (result));
1345 return;
1348 /* remove dir */
1349 rb_debug ("removing dir");
1350 conf_dir_name = eel_gconf_get_string (CONF_STATE_PODCAST_DOWNLOAD_DIR);
1352 dir_name = g_build_filename (conf_dir_name,
1353 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM),
1354 NULL);
1355 gnome_vfs_remove_directory (dir_name);
1357 } else if (type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
1358 GtkTreeModel *query_model;
1359 GtkTreeIter iter;
1361 query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (pd->priv->db));
1362 rhythmdb_do_full_query (pd->priv->db,
1363 RHYTHMDB_QUERY_RESULTS (query_model),
1364 RHYTHMDB_QUERY_PROP_EQUALS,
1365 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
1366 RHYTHMDB_QUERY_PROP_LIKE,
1367 RHYTHMDB_PROP_SUBTITLE, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
1368 RHYTHMDB_QUERY_END);
1370 if (gtk_tree_model_get_iter_first (query_model, &iter)) {
1371 gboolean has_next;
1372 do {
1373 RhythmDBEntry *entry;
1375 gtk_tree_model_get (query_model, &iter, 0, &entry, -1);
1376 has_next = gtk_tree_model_iter_next (query_model, &iter);
1378 /* make sure we're not downloading it */
1379 rb_podcast_manager_cancel_download (pd, entry);
1381 rhythmdb_entry_delete (pd->priv->db, entry);
1382 rhythmdb_entry_unref (entry);
1384 } while (has_next);
1386 rhythmdb_commit (pd->priv->db);
1389 g_object_unref (query_model);
1393 void
1394 rb_podcast_manager_cancel_download (RBPodcastManager *pd, RhythmDBEntry *entry)
1396 GList *lst;
1397 g_assert (rb_is_main_thread ());
1399 for (lst = pd->priv->download_list; lst != NULL; lst = lst->next) {
1400 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) lst->data;
1401 if (data->entry == entry) {
1402 cancel_job (data);
1403 return;
1408 static void
1409 rb_podcast_manager_update_synctime (RBPodcastManager *pd)
1411 gint value;
1412 gint index = eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL);
1414 switch (index)
1416 case UPDATE_EVERY_HOUR:
1417 value = time (NULL) + 3600;
1418 break;
1419 case UPDATE_EVERY_DAY:
1420 value = time (NULL) + (3600 * 24);
1421 break;
1422 case UPDATE_EVERY_WEEK:
1423 value = time (NULL) + (3600 * 24 * 7);
1424 break;
1425 case UPDATE_MANUALLY:
1426 value = 0;
1427 break;
1428 default:
1429 g_warning ("unknown download-inteval");
1430 value = 0;
1433 eel_gconf_set_integer (CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME, value);
1434 eel_gconf_suggest_sync ();
1435 pd->priv->next_time = value;
1436 rb_podcast_manager_start_sync (pd);
1439 static void
1440 rb_podcast_manager_config_changed (GConfClient* client,
1441 guint cnxn_id,
1442 GConfEntry *entry,
1443 gpointer user_data)
1445 rb_podcast_manager_update_synctime (RB_PODCAST_MANAGER (user_data));
1448 /* this bit really wants to die */
1450 void
1451 rb_podcast_manager_set_remove_files (RBPodcastManager *pd, gboolean flag)
1453 pd->priv->remove_files = flag;
1457 gboolean
1458 rb_podcast_manager_get_remove_files (RBPodcastManager *pd)
1460 return pd->priv->remove_files;
1463 static void
1464 rb_podcast_manager_insert_feed (RBPodcastManager *pd, RBPodcastChannel *data)
1466 GValue description_val = { 0, };
1467 GValue title_val = { 0, };
1468 GValue subtitle_val = { 0, };
1469 GValue summary_val = { 0, };
1470 GValue lang_val = { 0, };
1471 GValue copyright_val = { 0, };
1472 GValue image_val = { 0, };
1473 GValue author_val = { 0, };
1474 GValue status_val = { 0, };
1475 GValue last_post_val = { 0, };
1476 GValue last_update_val = { 0, };
1477 gulong last_post = 0;
1478 gulong new_last_post;
1479 GList *download_entries = NULL;
1480 gboolean new_feed, updated, download_last;
1481 RhythmDB *db = pd->priv->db;
1483 RhythmDBEntry *entry;
1485 GList *lst_songs;
1487 if (data->title == NULL) {
1488 g_list_free (data->posts);
1489 g_free (data);
1490 return;
1493 new_feed = TRUE;
1495 /* processing podcast head */
1496 entry = rhythmdb_entry_lookup_by_location (db, (gchar *)data->url);
1497 if (entry) {
1498 if (rhythmdb_entry_get_entry_type (entry) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
1499 return;
1501 rb_debug ("Podcast feed entry for %s found", data->url);
1502 g_value_init (&status_val, G_TYPE_ULONG);
1503 g_value_set_ulong (&status_val, 1);
1504 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1505 g_value_unset (&status_val);
1506 last_post = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_POST_TIME);
1507 new_feed = FALSE;
1508 } else {
1509 rb_debug ("Adding podcast feed: %s", data->url);
1510 entry = rhythmdb_entry_new (db,
1511 RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
1512 (gchar *) data->url);
1513 if (entry == NULL)
1514 return;
1516 g_value_init (&title_val, G_TYPE_STRING);
1517 g_value_set_string (&title_val, (gchar * ) data->title);
1518 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &title_val);
1519 g_value_unset (&title_val);
1521 g_value_init (&author_val, G_TYPE_STRING);
1522 if (data->author)
1523 g_value_set_string (&author_val, (gchar *) data->author);
1524 else
1525 g_value_set_static_string (&author_val, _("Unknown"));
1526 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &author_val);
1527 g_value_unset (&author_val);
1529 if (data->subtitle) {
1530 g_value_init (&subtitle_val, G_TYPE_STRING);
1531 g_value_set_string (&subtitle_val, (gchar *) data->subtitle);
1532 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUBTITLE, &subtitle_val);
1533 g_value_unset (&subtitle_val);
1536 if (data->description) {
1537 g_value_init (&description_val, G_TYPE_STRING);
1538 g_value_set_string (&description_val, (gchar *) data->description);
1539 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DESCRIPTION, &description_val);
1540 g_value_unset (&description_val);
1543 if (data->summary) {
1544 g_value_init (&summary_val, G_TYPE_STRING);
1545 g_value_set_string (&summary_val, (gchar *) data->summary);
1546 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUMMARY, &summary_val);
1547 g_value_unset (&summary_val);
1550 if (data->lang) {
1551 g_value_init (&lang_val, G_TYPE_STRING);
1552 g_value_set_string (&lang_val, (gchar *) data->lang);
1553 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LANG, &lang_val);
1554 g_value_unset (&lang_val);
1557 if (data->copyright) {
1558 g_value_init (&copyright_val, G_TYPE_STRING);
1559 g_value_set_string (&copyright_val, (gchar *) data->copyright);
1560 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_COPYRIGHT, &copyright_val);
1561 g_value_unset (&copyright_val);
1564 if (data->img) {
1565 g_value_init (&image_val, G_TYPE_STRING);
1566 g_value_set_string (&image_val, (gchar *) data->img);
1567 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_IMAGE, &image_val);
1568 g_value_unset (&image_val);
1571 g_value_init (&status_val, G_TYPE_ULONG);
1572 g_value_set_ulong (&status_val, 1);
1573 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1574 g_value_unset (&status_val);
1577 /* insert episodes */
1578 new_last_post = last_post;
1580 updated = FALSE;
1581 download_last = (eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL) != UPDATE_MANUALLY);
1582 for (lst_songs = data->posts; lst_songs != NULL; lst_songs = g_list_next (lst_songs)) {
1583 RBPodcastItem *item = (RBPodcastItem *) lst_songs->data;
1584 RhythmDBEntry *post_entry;
1586 if (item->pub_date > last_post || item->pub_date == 0) {
1587 updated = TRUE;
1589 post_entry =
1590 rb_podcast_manager_add_post (db,
1591 (gchar *) data->title,
1592 (gchar *) item->title,
1593 (gchar *) data->url,
1594 (gchar *) (item->author ? item->author : data->author),
1595 (gchar *) item->url,
1596 (gchar *) item->description,
1597 (gulong) (item->pub_date > 0 ? item->pub_date : data->pub_date),
1598 (gulong) item->duration,
1599 item->filesize);
1600 if (post_entry && item->pub_date >= new_last_post) {
1601 if (item->pub_date > new_last_post) {
1602 g_list_free (download_entries);
1603 download_entries = NULL;
1605 download_entries = g_list_prepend (download_entries, post_entry);
1606 new_last_post = item->pub_date;
1611 if (download_last) {
1612 GValue status = {0,};
1613 GList *t;
1615 g_value_init (&status, G_TYPE_ULONG);
1616 g_value_set_ulong (&status, RHYTHMDB_PODCAST_STATUS_WAITING);
1617 for (t = download_entries; t != NULL; t = g_list_next (t)) {
1618 rhythmdb_entry_set (db,
1619 (RhythmDBEntry*) t->data,
1620 RHYTHMDB_PROP_STATUS,
1621 &status);
1623 g_value_unset (&status);
1625 g_list_free (download_entries);
1627 if (updated)
1628 g_signal_emit (pd, rb_podcast_manager_signals[FEED_UPDATES_AVAILABLE],
1629 0, entry);
1631 if (data->pub_date > new_last_post)
1632 new_last_post = data->pub_date;
1634 g_value_init (&last_post_val, G_TYPE_ULONG);
1635 g_value_set_ulong (&last_post_val, new_last_post);
1637 if (new_feed)
1638 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
1639 else
1640 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
1641 g_value_unset (&last_post_val);
1643 g_value_init (&last_update_val, G_TYPE_ULONG);
1644 g_value_set_ulong (&last_update_val, time(NULL));
1646 if (new_feed)
1647 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1648 else
1649 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1650 g_value_unset (&last_update_val);
1652 rhythmdb_commit (db);
1655 void
1656 rb_podcast_manager_shutdown (RBPodcastManager *pd)
1658 GList *lst, *l;
1660 g_assert (rb_is_main_thread ());
1662 lst = g_list_reverse (pd->priv->download_list);
1663 for (l = lst; l != NULL; l = l->next) {
1664 RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) l->data;
1665 cancel_job (data);
1667 g_list_free (lst);
1669 pd->priv->shutdown = TRUE;
1672 gchar *
1673 rb_podcast_manager_get_podcast_dir (RBPodcastManager *pd)
1675 gchar *conf_dir_name = eel_gconf_get_string (CONF_STATE_PODCAST_DOWNLOAD_DIR);
1677 if (conf_dir_name == NULL || (strcmp (conf_dir_name, "") == 0)) {
1678 conf_dir_name = g_build_filename (g_get_home_dir (),
1679 "Podcasts",
1680 NULL);
1681 eel_gconf_set_string (CONF_STATE_PODCAST_DOWNLOAD_DIR, conf_dir_name);
1684 return conf_dir_name;