hythmbox-2.98/podcast/rb-podcast-manager.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found rb-podcast-manager.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
clang-analyzer no-output-found rb-podcast-manager.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2005 Renato Araujo Oliveira Filho - INdT <renato.filho@indt.org.br>
   4  *
   5  *  This program is free software; you can redistribute it and/or modify
   6  *  it under the terms of the GNU General Public License as published by
   7  *  the Free Software Foundation; either version 2 of the License, or
   8  *  (at your option) any later version.
   9  *
  10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  11  *  GStreamer plugins to be used and distributed together with GStreamer
  12  *  and Rhythmbox. This permission is above and beyond the permissions granted
  13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  14  *  you may extend this exception to your version of the code, but you are not
  15  *  obligated to do so. If you do not wish to do so, delete this exception
  16  *  statement from your version.
  17  *
  18  *  This program is distributed in the hope that it will be useful,
  19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21  *  GNU General Public License for more details.
  22  *
  23  *  You should have received a copy of the GNU General Public License
  24  *  along with this program; if not, write to the Free Software
  25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  26  *
  27  */
  28 
  29 #include "config.h"
  30 
  31 #include <string.h>
  32 #define __USE_XOPEN
  33 #include <time.h>
  34 
  35 #include <glib/gi18n.h>
  36 #include <glib/gstdio.h>
  37 #include <gio/gio.h>
  38 #include <gtk/gtk.h>
  39 
  40 #include "rb-podcast-settings.h"
  41 #include "rb-podcast-manager.h"
  42 #include "rb-podcast-entry-types.h"
  43 #include "rb-podcast-search.h"
  44 #include "rb-file-helpers.h"
  45 #include "rb-debug.h"
  46 #include "rb-marshal.h"
  47 #include "rhythmdb.h"
  48 #include "rhythmdb-query-model.h"
  49 #include "rb-podcast-parse.h"
  50 #include "rb-dialog.h"
  51 #include "rb-metadata.h"
  52 #include "rb-util.h"
  53 #include "rb-missing-plugins.h"
  54 #include "rb-ext-db.h"
  55 
  56 enum
  57 {
  58 	PROP_0,
  59 	PROP_DB
  60 };
  61 
  62 enum
  63 {
  64 	START_DOWNLOAD,
  65 	FINISH_DOWNLOAD,
  66 	PROCESS_ERROR,
  67 	FEED_UPDATES_AVAILABLE,
  68 	LAST_SIGNAL
  69 };
  70 
  71 /* passed from feed parsing threads back to main thread */
  72 typedef struct
  73 {
  74 	GError			*error;
  75 	RBPodcastChannel 	*channel;
  76 	RBPodcastManager	*pd;
  77 	gboolean		 automatic;
  78 } RBPodcastManagerParseResult;
  79 
  80 typedef struct
  81 {
  82 	RBPodcastManager *pd;
  83 
  84 	RhythmDBEntry *entry;
  85 	char *query_string;
  86 
  87 	GFile *source;
  88 	GFile *destination;
  89 	GFileInputStream *in_stream;
  90 	GFileOutputStream *out_stream;
  91 
  92 	guint64 download_offset;
  93 	guint64 download_size;
  94 	guint progress;
  95 
  96 	GCancellable *cancel;
  97 	GThread *thread;
  98 } RBPodcastManagerInfo;
  99 
 100 typedef struct
 101 {
 102 	RBPodcastManager *pd;
 103 	char *url;
 104 	gboolean automatic;
 105 	gboolean existing_feed;
 106 } RBPodcastThreadInfo;
 107 
 108 struct RBPodcastManagerPrivate
 109 {
 110 	RhythmDB *db;
 111 	GList *download_list;
 112 	RBPodcastManagerInfo *active_download;
 113 	guint source_sync;
 114 	guint next_file_id;
 115 	gboolean shutdown;
 116 	RBExtDB *art_store;
 117 
 118 	GList *searches;
 119 	GSettings *settings;
 120 	GFile *timestamp_file;
 121 };
 122 
 123 #define RB_PODCAST_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PODCAST_MANAGER, RBPodcastManagerPrivate))
 124 
 125 
 126 static guint rb_podcast_manager_signals[LAST_SIGNAL] = { 0 };
 127 
 128 /* functions */
 129 static void rb_podcast_manager_class_init 		(RBPodcastManagerClass *klass);
 130 static void rb_podcast_manager_init 			(RBPodcastManager *dp);
 131 static void rb_podcast_manager_constructed		(GObject *object);
 132 static void rb_podcast_manager_dispose 			(GObject *object);
 133 static void rb_podcast_manager_finalize 		(GObject *object);
 134 static void rb_podcast_manager_set_property 		(GObject *object,
 135                                    			 guint prop_id,
 136 		                                	 const GValue *value,
 137 		                                	 GParamSpec *pspec);
 138 static void rb_podcast_manager_get_property 		(GObject *object,
 139 							 guint prop_id,
 140 		                                	 GValue *value,
 141                 		                	 GParamSpec *pspec);
 142 static void read_file_cb				(GFile *source,
 143 							 GAsyncResult *result,
 144 							 RBPodcastManagerInfo *data);
 145 static void download_file_info_cb			(GFile *source,
 146 							 GAsyncResult *result,
 147 							 RBPodcastManagerInfo *data);
 148 static void download_podcast				(GFileInfo *src_info,
 149 							 RBPodcastManagerInfo *data);
 150 static void rb_podcast_manager_abort_download		(RBPodcastManagerInfo *data);
 151 static gboolean rb_podcast_manager_update_feeds_cb 	(gpointer data);
 152 static void rb_podcast_manager_save_metadata		(RBPodcastManager *pd,
 153 						  	 RhythmDBEntry *entry);
 154 static void rb_podcast_manager_db_entry_added_cb 	(RBPodcastManager *pd,
 155 							 RhythmDBEntry *entry);
 156 static gboolean rb_podcast_manager_next_file 		(RBPodcastManager * pd);
 157 static void rb_podcast_manager_handle_feed_error	(RBPodcastManager *mgr,
 158 							 const char *url,
 159 							 GError *error,
 160 							 gboolean emit);
 161 
 162 static gpointer rb_podcast_manager_thread_parse_feed	(RBPodcastThreadInfo *info);
 163 static void podcast_settings_changed_cb			(GSettings *settings,
 164 							 const char *key,
 165 							 RBPodcastManager *mgr);
 166 
 167 /* internal functions */
 168 static void download_info_free				(RBPodcastManagerInfo *data);
 169 static gpointer podcast_download_thread			(RBPodcastManagerInfo *data);
 170 static gboolean end_job					(RBPodcastManagerInfo *data);
 171 static void cancel_job					(RBPodcastManagerInfo *pd);
 172 static void rb_podcast_manager_start_update_timer 	(RBPodcastManager *pd);
 173 
 174 G_DEFINE_TYPE (RBPodcastManager, rb_podcast_manager, G_TYPE_OBJECT)
 175 
 176 static void
 177 rb_podcast_manager_class_init (RBPodcastManagerClass *klass)
 178 {
 179 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 180 
 181 	object_class->constructed = rb_podcast_manager_constructed;
 182 	object_class->dispose = rb_podcast_manager_dispose;
 183 	object_class->finalize = rb_podcast_manager_finalize;
 184 
 185 	object_class->set_property = rb_podcast_manager_set_property;
 186         object_class->get_property = rb_podcast_manager_get_property;
 187 
 188 	g_object_class_install_property (object_class,
 189                                          PROP_DB,
 190                                          g_param_spec_object ("db",
 191 							      "db",
 192 							      "database",
 193 							      RHYTHMDB_TYPE,
 194 							      G_PARAM_READWRITE));
 195 
 196 	rb_podcast_manager_signals[START_DOWNLOAD] =
 197 	       g_signal_new ("start_download",
 198 		       		G_OBJECT_CLASS_TYPE (object_class),
 199 		 		G_SIGNAL_RUN_LAST,
 200 				G_STRUCT_OFFSET (RBPodcastManagerClass, start_download),
 201 				NULL, NULL,
 202 				g_cclosure_marshal_VOID__BOXED,
 203 				G_TYPE_NONE,
 204 				1,
 205 				RHYTHMDB_TYPE_ENTRY);
 206 
 207 	rb_podcast_manager_signals[FINISH_DOWNLOAD] =
 208 	       g_signal_new ("finish_download",
 209 		       		G_OBJECT_CLASS_TYPE (object_class),
 210 		 		G_SIGNAL_RUN_LAST,
 211 				G_STRUCT_OFFSET (RBPodcastManagerClass, finish_download),
 212 				NULL, NULL,
 213 				g_cclosure_marshal_VOID__BOXED,
 214 				G_TYPE_NONE,
 215 				1,
 216 				RHYTHMDB_TYPE_ENTRY);
 217 
 218 	rb_podcast_manager_signals[FEED_UPDATES_AVAILABLE] =
 219 	       g_signal_new ("feed_updates_available",
 220 		       		G_OBJECT_CLASS_TYPE (object_class),
 221 		 		G_SIGNAL_RUN_LAST,
 222 				G_STRUCT_OFFSET (RBPodcastManagerClass, feed_updates_available),
 223 				NULL, NULL,
 224 				g_cclosure_marshal_VOID__BOXED,
 225 				G_TYPE_NONE,
 226 				1,
 227 				RHYTHMDB_TYPE_ENTRY);
 228 
 229 	rb_podcast_manager_signals[PROCESS_ERROR] =
 230 	       g_signal_new ("process_error",
 231 		       		G_OBJECT_CLASS_TYPE (object_class),
 232 				G_SIGNAL_RUN_LAST,
 233 				G_STRUCT_OFFSET (RBPodcastManagerClass, process_error),
 234 				NULL, NULL,
 235 				NULL,
 236 				G_TYPE_NONE,
 237 				3,
 238 				G_TYPE_STRING,
 239 				G_TYPE_STRING,
 240 				G_TYPE_BOOLEAN);
 241 
 242 	g_type_class_add_private (klass, sizeof (RBPodcastManagerPrivate));
 243 }
 244 
 245 static void
 246 rb_podcast_manager_init (RBPodcastManager *pd)
 247 {
 248 	pd->priv = RB_PODCAST_MANAGER_GET_PRIVATE (pd);
 249 
 250 	pd->priv->source_sync = 0;
 251 	pd->priv->db = NULL;
 252 
 253 }
 254 
 255 static void
 256 rb_podcast_manager_constructed (GObject *object)
 257 {
 258 	RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
 259 	GFileOutputStream *st;
 260 	char *ts_file_path;
 261 
 262 	RB_CHAIN_GOBJECT_METHOD (rb_podcast_manager_parent_class, constructed, object);
 263 
 264 	/* add built in search types */
 265 	rb_podcast_manager_add_search (pd, rb_podcast_search_itunes_get_type ());
 266 	rb_podcast_manager_add_search (pd, rb_podcast_search_miroguide_get_type ());
 267 
 268 	pd->priv->settings = g_settings_new (PODCAST_SETTINGS_SCHEMA);
 269 	g_signal_connect_object (pd->priv->settings,
 270 				 "changed",
 271 				 G_CALLBACK (podcast_settings_changed_cb),
 272 				 pd, 0);
 273 
 274 	ts_file_path = g_build_filename (rb_user_data_dir (), "podcast-timestamp", NULL);
 275 	pd->priv->timestamp_file = g_file_new_for_path (ts_file_path);
 276 	g_free (ts_file_path);
 277 
 278 	/* create it if it doesn't exist */
 279 	st = g_file_create (pd->priv->timestamp_file, G_FILE_CREATE_NONE, NULL, NULL);
 280 	if (st != NULL) {
 281 		g_output_stream_close (G_OUTPUT_STREAM (st), NULL, NULL);
 282 		g_object_unref (st);
 283 	}
 284 
 285 	pd->priv->art_store = rb_ext_db_new ("album-art");
 286 
 287 	rb_podcast_manager_start_update_timer (pd);
 288 }
 289 
 290 static void
 291 rb_podcast_manager_dispose (GObject *object)
 292 {
 293 	RBPodcastManager *pd;
 294 	g_return_if_fail (object != NULL);
 295         g_return_if_fail (RB_IS_PODCAST_MANAGER (object));
 296 
 297 	pd = RB_PODCAST_MANAGER (object);
 298 	g_return_if_fail (pd->priv != NULL);
 299 
 300 	if (pd->priv->next_file_id != 0) {
 301 		g_source_remove (pd->priv->next_file_id);
 302 		pd->priv->next_file_id = 0;
 303 	}
 304 
 305 	if (pd->priv->source_sync != 0) {
 306 		g_source_remove (pd->priv->source_sync);
 307 		pd->priv->source_sync = 0;
 308 	}
 309 
 310 	if (pd->priv->db != NULL) {
 311 		g_object_unref (pd->priv->db);
 312 		pd->priv->db = NULL;
 313 	}
 314 
 315 	if (pd->priv->settings != NULL) {
 316 		g_object_unref (pd->priv->settings);
 317 		pd->priv->settings = NULL;
 318 	}
 319 
 320 	if (pd->priv->timestamp_file != NULL) {
 321 		g_object_unref (pd->priv->timestamp_file);
 322 		pd->priv->timestamp_file = NULL;
 323 	}
 324 
 325 	if (pd->priv->art_store != NULL) {
 326 		g_object_unref (pd->priv->art_store);
 327 		pd->priv->art_store = NULL;
 328 	}
 329 
 330 	G_OBJECT_CLASS (rb_podcast_manager_parent_class)->dispose (object);
 331 }
 332 
 333 static void
 334 rb_podcast_manager_finalize (GObject *object)
 335 {
 336 	RBPodcastManager *pd;
 337 	g_return_if_fail (object != NULL);
 338         g_return_if_fail (RB_IS_PODCAST_MANAGER (object));
 339 
 340 	pd = RB_PODCAST_MANAGER(object);
 341 
 342 	g_return_if_fail (pd->priv != NULL);
 343 
 344 	if (pd->priv->download_list) {
 345 		g_list_foreach (pd->priv->download_list, (GFunc)g_free, NULL);
 346 		g_list_free (pd->priv->download_list);
 347 	}
 348 
 349 	g_list_free (pd->priv->searches);
 350 
 351 	G_OBJECT_CLASS (rb_podcast_manager_parent_class)->finalize (object);
 352 }
 353 
 354 static void
 355 rb_podcast_manager_set_property (GObject *object,
 356 				 guint prop_id,
 357 				 const GValue *value,
 358 				 GParamSpec *pspec)
 359 {
 360 	RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
 361 
 362 	switch (prop_id) {
 363 	case PROP_DB:
 364 		if (pd->priv->db) {
 365 			g_signal_handlers_disconnect_by_func (pd->priv->db,
 366 							      G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
 367 							      pd);
 368 			g_object_unref (pd->priv->db);
 369 		}
 370 
 371 		pd->priv->db = g_value_get_object (value);
 372 		g_object_ref (pd->priv->db);
 373 
 374 	        g_signal_connect_object (pd->priv->db,
 375 	                                 "entry-added",
 376 	                                 G_CALLBACK (rb_podcast_manager_db_entry_added_cb),
 377 	                                 pd, G_CONNECT_SWAPPED);
 378 		break;
 379 	default:
 380 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 381 	}
 382 }
 383 
 384 static void
 385 rb_podcast_manager_get_property (GObject *object,
 386 					guint prop_id,
 387 		                        GValue *value,
 388                 		        GParamSpec *pspec)
 389 {
 390 	RBPodcastManager *pd = RB_PODCAST_MANAGER (object);
 391 
 392 	switch (prop_id) {
 393 	case PROP_DB:
 394 		g_value_set_object (value, pd->priv->db);
 395 		break;
 396 	default:
 397 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 398 	}
 399 
 400 }
 401 
 402 RBPodcastManager *
 403 rb_podcast_manager_new (RhythmDB *db)
 404 {
 405 	RBPodcastManager *pd;
 406 
 407 	pd = g_object_new (RB_TYPE_PODCAST_MANAGER, "db", db, NULL);
 408 	return pd;
 409 }
 410 
 411 static const char *
 412 get_download_location (RhythmDBEntry *entry)
 413 {
 414 	/* We haven't tried to download the entry yet */
 415 	if (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT) == NULL)
 416 		return NULL;
 417 	return rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
 418 }
 419 
 420 static void
 421 set_download_location (RhythmDB *db, RhythmDBEntry *entry, GValue *value)
 422 {
 423 	char *remote_location;
 424 
 425 	if (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT) == NULL) {
 426 		/* If the download location was never set */
 427 		GValue val = {0, };
 428 
 429 		/* Save the remote location */
 430 		remote_location = g_strdup (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
 431 		/* Set the new download location */
 432 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LOCATION, value);
 433 		/* Set MOUNTPOINT to the remote location */
 434 		g_value_init (&val, G_TYPE_STRING);
 435 		g_value_take_string (&val, remote_location);
 436 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MOUNTPOINT, &val);
 437 		g_value_unset (&val);
 438 	} else {
 439 		/* Just update the location */
 440 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LOCATION, value);
 441 	}
 442 }
 443 
 444 static const char *
 445 get_remote_location (RhythmDBEntry *entry)
 446 {
 447 	const char *location;
 448 	location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
 449 	if (location == NULL)
 450 		location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
 451 	return location;
 452 }
 453 
 454 void
 455 rb_podcast_manager_download_entry (RBPodcastManager *pd,
 456 				   RhythmDBEntry *entry)
 457 {
 458 	gulong status;
 459 	g_assert (rb_is_main_thread ());
 460 
 461 	g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
 462 
 463 	if (entry == NULL)
 464 		return;
 465 
 466 	status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
 467 	if ((status < RHYTHMDB_PODCAST_STATUS_COMPLETE) ||
 468 	    (status == RHYTHMDB_PODCAST_STATUS_WAITING)) {
 469 		RBPodcastManagerInfo *data;
 470 		GValue val = { 0, };
 471 		GTimeVal now;
 472 
 473 		if (status < RHYTHMDB_PODCAST_STATUS_COMPLETE) {
 474 			g_value_init (&val, G_TYPE_ULONG);
 475 			g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_WAITING);
 476 			rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &val);
 477 			g_value_unset (&val);
 478 		}
 479 
 480 		/* set last seen time so it shows up in the 'new downloads' subsource */
 481 		g_value_init (&val, G_TYPE_ULONG);
 482 		g_get_current_time (&now);
 483 		g_value_set_ulong (&val, now.tv_sec);
 484 		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_LAST_SEEN, &val);
 485 		g_value_unset (&val);
 486 		rhythmdb_commit (pd->priv->db);
 487 
 488 		rb_debug ("Adding podcast episode %s to download list", get_remote_location (entry));
 489 
 490 		data = g_new0 (RBPodcastManagerInfo, 1);
 491 		data->pd = g_object_ref (pd);
 492 		data->entry = rhythmdb_entry_ref (entry);
 493 
 494 		pd->priv->download_list = g_list_append (pd->priv->download_list, data);
 495 		if (pd->priv->next_file_id == 0) {
 496 			pd->priv->next_file_id =
 497 				g_idle_add ((GSourceFunc) rb_podcast_manager_next_file, pd);
 498 		}
 499 	}
 500 }
 501 
 502 gboolean
 503 rb_podcast_manager_entry_downloaded (RhythmDBEntry *entry)
 504 {
 505 	gulong status;
 506 	const gchar *file_name;
 507 	RhythmDBEntryType *type = rhythmdb_entry_get_entry_type (entry);
 508 
 509 	g_assert (type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
 510 
 511 	status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
 512 	file_name = get_download_location (entry);
 513 
 514 	return (status != RHYTHMDB_PODCAST_STATUS_ERROR && file_name != NULL);
 515 }
 516 
 517 gboolean
 518 rb_podcast_manager_entry_in_download_queue (RBPodcastManager *pd, RhythmDBEntry *entry)
 519 {
 520 	RBPodcastManagerInfo *info;
 521 	GList *l;
 522 
 523 	for (l = pd->priv->download_list; l != NULL; l = l->next) {
 524 		info = l->data;
 525 		if (info->entry == entry) {
 526 			return TRUE;
 527 		}
 528 	}
 529 
 530 	return FALSE;
 531 }
 532 
 533 static void
 534 rb_podcast_manager_start_update_timer (RBPodcastManager *pd)
 535 {
 536 	guint64 last_time;
 537 	guint64 interval_sec;
 538 	guint64 now;
 539 	GFileInfo *fi;
 540 	RBPodcastInterval interval;
 541 
 542 	g_return_if_fail (RB_IS_PODCAST_MANAGER (pd));
 543 
 544 	if (pd->priv->source_sync != 0) {
 545 		g_source_remove (pd->priv->source_sync);
 546 		pd->priv->source_sync = 0;
 547 	}
 548 
 549 	interval = g_settings_get_enum (pd->priv->settings,
 550 					PODCAST_DOWNLOAD_INTERVAL);
 551 	if (interval == PODCAST_INTERVAL_MANUAL) {
 552 		rb_debug ("periodic podcast updates disabled");
 553 		return;
 554 	}
 555 
 556 	/* get last update time */
 557 	fi = g_file_query_info (pd->priv->timestamp_file,
 558 				G_FILE_ATTRIBUTE_TIME_MODIFIED,
 559 				G_FILE_QUERY_INFO_NONE,
 560 				NULL,
 561 				NULL);
 562 	if (fi != NULL) {
 563 		last_time = g_file_info_get_attribute_uint64 (fi, G_FILE_ATTRIBUTE_TIME_MODIFIED);
 564 	} else {
 565 		last_time = 0;
 566 	}
 567 
 568 	switch (interval) {
 569 	case PODCAST_INTERVAL_HOURLY:
 570 		interval_sec = 3600;
 571 		break;
 572 	case PODCAST_INTERVAL_DAILY:
 573 		interval_sec = (3600 * 24);
 574 		break;
 575 	case PODCAST_INTERVAL_WEEKLY:
 576 		interval_sec = (3600 * 24 * 7);
 577 		break;
 578 	default:
 579 		g_assert_not_reached ();
 580 		return;
 581 	}
 582 
 583 	/* wait until next update time */
 584 	now = time (NULL);
 585 	rb_debug ("last periodic update at %" G_GUINT64_FORMAT ", interval %" G_GUINT64_FORMAT ", time is now %" G_GUINT64_FORMAT,
 586 		  last_time, interval_sec, now);
 587 
 588 	if (last_time + interval_sec < now) {
 589 		rb_debug ("periodic update should already have happened");
 590 		pd->priv->source_sync = g_idle_add ((GSourceFunc) rb_podcast_manager_update_feeds_cb,
 591 						    pd);
 592 	} else {
 593 		rb_debug ("next periodic update in %" G_GUINT64_FORMAT " seconds", (last_time + interval_sec) - now);
 594 		pd->priv->source_sync = g_timeout_add_seconds ((last_time + interval_sec) - now,
 595 							       (GSourceFunc) rb_podcast_manager_update_feeds_cb,
 596 							       pd);
 597 	}
 598 }
 599 
 600 static gboolean
 601 rb_podcast_manager_head_query_cb (GtkTreeModel *query_model,
 602 				  GtkTreePath *path,
 603 				  GtkTreeIter *iter,
 604 				  RBPodcastManager *manager)
 605 {
 606         const char *uri;
 607         RhythmDBEntry *entry;
 608 	guint status;
 609 
 610         gtk_tree_model_get (query_model, iter, 0, &entry, -1);
 611         uri = get_remote_location (entry);
 612 	status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
 613 
 614 	if (status == 1)
 615 		rb_podcast_manager_subscribe_feed (manager, uri, TRUE);
 616 
 617 	rhythmdb_entry_unref (entry);
 618 
 619         return FALSE;
 620 }
 621 
 622 void
 623 rb_podcast_manager_update_feeds (RBPodcastManager *pd)
 624 {
 625 	GtkTreeModel *query_model;
 626 
 627 	query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (pd->priv->db));
 628 
 629 	rhythmdb_do_full_query (pd->priv->db,
 630 				RHYTHMDB_QUERY_RESULTS (query_model),
 631                                 RHYTHMDB_QUERY_PROP_EQUALS,
 632                                 RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
 633                                 RHYTHMDB_QUERY_END);
 634 
 635  	gtk_tree_model_foreach (query_model,
 636 		                (GtkTreeModelForeachFunc) rb_podcast_manager_head_query_cb,
 637                                 pd);
 638 
 639 	g_object_unref (query_model);
 640 }
 641 
 642 static gboolean
 643 rb_podcast_manager_update_feeds_cb (gpointer data)
 644 {
 645 	RBPodcastManager *pd = RB_PODCAST_MANAGER (data);
 646 
 647 	g_assert (rb_is_main_thread ());
 648 
 649 	GDK_THREADS_ENTER ();
 650 
 651 	pd->priv->source_sync = 0;
 652 
 653 	g_file_set_attribute_uint64 (pd->priv->timestamp_file,
 654 				     G_FILE_ATTRIBUTE_TIME_MODIFIED,
 655 				     (guint64) time (NULL),
 656 				     G_FILE_QUERY_INFO_NONE,
 657 				     NULL,
 658 				     NULL);
 659 
 660 	rb_podcast_manager_update_feeds (pd);
 661 
 662 	rb_podcast_manager_start_update_timer (pd);
 663 
 664 	GDK_THREADS_LEAVE ();
 665 	return FALSE;
 666 }
 667 
 668 static void
 669 download_error (RBPodcastManagerInfo *data, GError *error)
 670 {
 671 	GValue val = {0,};
 672 
 673 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) == FALSE) {
 674 		rb_debug ("error downloading %s: %s",
 675 			  get_remote_location (data->entry),
 676 			  error->message);
 677 
 678 		g_value_init (&val, G_TYPE_ULONG);
 679 		g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
 680 		rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
 681 		g_value_unset (&val);
 682 
 683 		g_value_init (&val, G_TYPE_STRING);
 684 		g_value_set_string (&val, error->message);
 685 		rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
 686 		g_value_unset (&val);
 687 	} else {
 688 		rb_debug ("download of %s was cancelled", get_remote_location (data->entry));
 689 	}
 690 
 691 	rhythmdb_commit (data->pd->priv->db);
 692 
 693 	if (rb_is_main_thread() == FALSE) {
 694 		g_idle_add ((GSourceFunc)end_job, data);
 695 	} else {
 696 		rb_podcast_manager_abort_download (data);
 697 	}
 698 }
 699 
 700 static gboolean
 701 rb_podcast_manager_next_file (RBPodcastManager * pd)
 702 {
 703 	const char *location;
 704 	RBPodcastManagerInfo *data;
 705 	char *query_string;
 706 	GList *d;
 707 
 708 	g_assert (rb_is_main_thread ());
 709 
 710 	rb_debug ("looking for something to download");
 711 
 712 	GDK_THREADS_ENTER ();
 713 
 714 	pd->priv->next_file_id = 0;
 715 
 716 	if (pd->priv->active_download != NULL) {
 717 		rb_debug ("already downloading something");
 718 		GDK_THREADS_LEAVE ();
 719 		return FALSE;
 720 	}
 721 
 722 	d = g_list_first (pd->priv->download_list);
 723 	if (d == NULL) {
 724 		rb_debug ("download queue is empty");
 725 		GDK_THREADS_LEAVE ();
 726 		return FALSE;
 727 	}
 728 
 729 	data = (RBPodcastManagerInfo *) d->data;
 730 	g_assert (data != NULL);
 731 	g_assert (data->entry != NULL);
 732 
 733 	pd->priv->active_download = data;
 734 
 735 	location = get_remote_location (data->entry);
 736 	rb_debug ("processing %s", location);
 737 
 738 	/* extract the query string so we can remove it later if it appears
 739 	 * in download URLs
 740 	 */
 741 	query_string = strchr (location, '?');
 742 	if (query_string != NULL) {
 743 		query_string--;
 744 		data->query_string = g_strdup (query_string);
 745 	}
 746 
 747 	data->source = g_file_new_for_uri (location);
 748 
 749 	g_file_read_async (data->source,
 750 	                   0,
 751 	                   data->cancel,
 752 	                   (GAsyncReadyCallback) read_file_cb,
 753 	                   data);
 754 
 755 	GDK_THREADS_LEAVE ();
 756 	return FALSE;
 757 }
 758 
 759 static void
 760 read_file_cb (GFile *source,
 761               GAsyncResult *result,
 762               RBPodcastManagerInfo *data)
 763 {
 764 	GError *error = NULL;
 765 	GFileInfo *src_info;
 766 
 767 	g_assert (rb_is_main_thread ());
 768 
 769 	rb_debug ("started read for %s",
 770 		  get_remote_location (data->entry));
 771 
 772 	data->in_stream = g_file_read_finish (data->source,
 773 	                                      result,
 774 	                                      &error);
 775 	if (error != NULL) {
 776 		download_error (data, error);
 777 		g_error_free (error);
 778 		return;
 779 	}
 780 
 781 	src_info = g_file_input_stream_query_info (data->in_stream,
 782 	                                           G_FILE_ATTRIBUTE_STANDARD_SIZE ","
 783 	                                           G_FILE_ATTRIBUTE_STANDARD_COPY_NAME ","
 784 	                                           G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
 785 	                                           G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
 786 	                                           NULL,
 787 	                                           &error);
 788 
 789 	/* If no stream information then probably using an old version of gvfs, fall back
 790 	 * to getting the stream information from the GFile.
 791 	 */
 792 	if (error != NULL) {
 793 		rb_debug ("file info query from input failed, trying query on file: %s", error->message);
 794 		g_error_free (error);
 795 
 796 		g_file_query_info_async (data->source,
 797 		                         G_FILE_ATTRIBUTE_STANDARD_SIZE ","
 798 		                         G_FILE_ATTRIBUTE_STANDARD_COPY_NAME ","
 799 	                                 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
 800 		                         G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
 801 		                         G_FILE_QUERY_INFO_NONE,
 802 		                         0,
 803 		                         data->cancel,
 804 		                         (GAsyncReadyCallback) download_file_info_cb,
 805 		                         data);
 806 		return;
 807 	}
 808 
 809 	rb_debug ("got file info results for %s",
 810 		  get_remote_location (data->entry));
 811 
 812 	download_podcast (src_info, data);
 813 }
 814 
 815 static void
 816 download_file_info_cb (GFile *source,
 817                        GAsyncResult *result,
 818                        RBPodcastManagerInfo *data)
 819 {
 820 	GError *error = NULL;
 821 	GFileInfo *src_info;
 822 
 823 	src_info = g_file_query_info_finish (source, result, &error);
 824 
 825 	if (error != NULL) {
 826 		download_error (data, error);
 827 		g_error_free (error);
 828 	} else {
 829 		rb_debug ("got file info results for %s",
 830 			  get_remote_location (data->entry));
 831 
 832 		download_podcast (src_info, data);
 833 	}
 834 }
 835 
 836 static void
 837 download_podcast (GFileInfo *src_info, RBPodcastManagerInfo *data)
 838 {
 839 	GError *error = NULL;
 840 	char *local_file_name = NULL;
 841 	char *feed_folder;
 842 	char *esc_local_file_name;
 843 	char *local_file_uri;
 844 	char *sane_local_file_uri;
 845 	char *conf_dir_uri;
 846 
 847 	if (src_info != NULL) {
 848 		data->download_size = g_file_info_get_attribute_uint64 (src_info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
 849 
 850 		local_file_name = g_file_info_get_attribute_as_string (src_info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME);
 851 		if (local_file_name == NULL) {
 852 			/* probably shouldn't be using this, but the gvfs http backend doesn't
 853 			 * set the copy name (yet)
 854 			 */
 855 			local_file_name = g_strdup (g_file_info_get_edit_name (src_info));
 856 		}
 857 
 858 		g_object_unref (src_info);
 859 	}
 860 
 861 	if (local_file_name == NULL) {
 862 		/* fall back to the basename from the original URI */
 863 		local_file_name = g_file_get_basename (data->source);
 864 		rb_debug ("didn't get a filename from the file info request; using basename %s", local_file_name);
 865 	}
 866 
 867 	/* if the filename ends with the query string from the original URI,
 868 	 * remove it.
 869 	 */
 870 	if (data->query_string &&
 871 	    g_str_has_suffix (local_file_name, data->query_string)) {
 872 		local_file_name[strlen (local_file_name) - strlen (data->query_string)] = '\0';
 873 		rb_debug ("removing query string \"%s\" -> local file name \"%s\"", data->query_string, local_file_name);
 874 	}
 875 
 876 	esc_local_file_name = g_uri_escape_string (local_file_name,
 877 						   G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
 878 						   TRUE);
 879 	feed_folder = g_uri_escape_string (rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_ALBUM),
 880 					   G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
 881 					   TRUE);
 882 	g_strdelimit (feed_folder, "/", '_');
 883 	g_strdelimit (esc_local_file_name, "/", '_');
 884 
 885 	/* construct local filename */
 886 	conf_dir_uri = rb_podcast_manager_get_podcast_dir (data->pd);
 887 	local_file_uri = g_build_filename (conf_dir_uri,
 888 					   feed_folder,
 889 					   local_file_name,
 890 					   NULL);
 891 
 892 	g_free (local_file_name);
 893 	g_free (feed_folder);
 894 	g_free (esc_local_file_name);
 895 
 896 	sane_local_file_uri = rb_sanitize_uri_for_filesystem (local_file_uri);
 897 	g_free (local_file_uri);
 898 
 899 	rb_debug ("download URI: %s", sane_local_file_uri);
 900 
 901 	if (rb_uri_create_parent_dirs (sane_local_file_uri, &error) == FALSE) {
 902 		rb_debug ("error creating parent dirs: %s", error->message);
 903 
 904 		rb_error_dialog (NULL, _("Error creating podcast download directory"),
 905 				 _("Unable to create the download directory for %s: %s"),
 906 				 sane_local_file_uri, error->message);
 907 
 908 		g_error_free (error);
 909 		rb_podcast_manager_abort_download (data);
 910 		return;
 911 	}
 912 
 913 	data->destination = g_file_new_for_uri (sane_local_file_uri);
 914 	if (g_file_query_exists (data->destination, NULL)) {
 915 		GFileInfo *dest_info;
 916 		guint64 local_size;
 917 
 918 		dest_info = g_file_query_info (data->destination,
 919 					       G_FILE_ATTRIBUTE_STANDARD_SIZE,
 920 					       G_FILE_QUERY_INFO_NONE,
 921 					       NULL,
 922 					       &error);
 923 		if (error != NULL) {
 924 			/* hrm */
 925 			g_warning ("Looking at downloaded podcast file %s: %s",
 926 				   sane_local_file_uri, error->message);
 927 			g_error_free (error);
 928 			rb_podcast_manager_abort_download (data);
 929 			return;
 930 		}
 931 
 932 		/* check size */
 933 		local_size = g_file_info_get_attribute_uint64 (dest_info,
 934 							       G_FILE_ATTRIBUTE_STANDARD_SIZE);
 935 		g_object_unref (dest_info);
 936 		if (local_size == data->download_size) {
 937 			GValue val = {0,};
 938 
 939 			rb_debug ("local file is the same size as the download (%" G_GUINT64_FORMAT ")",
 940 				  local_size);
 941 
 942 			g_value_init (&val, G_TYPE_ULONG);
 943 			g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
 944 			rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
 945 			g_value_unset (&val);
 946 
 947 			g_value_init (&val, G_TYPE_STRING);
 948 			g_value_take_string (&val, g_file_get_uri (data->destination));
 949 			set_download_location (data->pd->priv->db, data->entry, &val);
 950 			g_value_unset (&val);
 951 
 952 			rb_podcast_manager_save_metadata (data->pd, data->entry);
 953 
 954 			rb_podcast_manager_abort_download (data);
 955 			return;
 956 		} else if (local_size < data->download_size) {
 957 			rb_debug ("podcast partly downloaded (%" G_GUINT64_FORMAT " of %" G_GUINT64_FORMAT ")",
 958 				  local_size, data->download_size);
 959 			data->download_offset = local_size;
 960 		} else {
 961 			rb_debug ("replacing local file as it's larger than the download");
 962 			g_file_delete (data->destination, NULL, &error);
 963 			if (error != NULL) {
 964 				g_warning ("Removing existing download: %s", error->message);
 965 				g_error_free (error);
 966 				rb_podcast_manager_abort_download (data);
 967 				return;
 968 			}
 969 		}
 970 	}
 971 
 972 	g_free (sane_local_file_uri);
 973 
 974 	GDK_THREADS_ENTER ();
 975 	g_signal_emit (data->pd, rb_podcast_manager_signals[START_DOWNLOAD],
 976 		       0, data->entry);
 977 	GDK_THREADS_LEAVE ();
 978 
 979 	data->cancel = g_cancellable_new ();
 980 	data->thread = g_thread_new ("podcast-download",
 981 				     (GThreadFunc) podcast_download_thread,
 982 				     data);
 983 }
 984 
 985 static void
 986 rb_podcast_manager_abort_download (RBPodcastManagerInfo *data)
 987 {
 988 	RBPodcastManager *mgr = data->pd;
 989 
 990 	g_assert (rb_is_main_thread ());
 991 
 992 	mgr->priv->download_list = g_list_remove (mgr->priv->download_list, data);
 993 	download_info_free (data);
 994 
 995 	if (mgr->priv->active_download == data)
 996 		mgr->priv->active_download = NULL;
 997 
 998 	if (mgr->priv->next_file_id == 0) {
 999 		mgr->priv->next_file_id =
1000 			g_idle_add ((GSourceFunc) rb_podcast_manager_next_file, mgr);
1001 	}
1002 }
1003 
1004 gboolean
1005 rb_podcast_manager_subscribe_feed (RBPodcastManager *pd, const char *url, gboolean automatic)
1006 {
1007 	RBPodcastThreadInfo *info;
1008 	GFile *feed;
1009 	char *feed_url;
1010 	gboolean existing_feed;
1011 
1012 	if (g_str_has_prefix (url, "feed://") || g_str_has_prefix (url, "itpc://")) {
1013 		char *tmp;
1014 
1015 		tmp = g_strdup_printf ("http://%s", url + strlen ("feed://"));
1016 		feed = g_file_new_for_uri (tmp);
1017 		g_free (tmp);
1018 	} else {
1019 		feed = g_file_new_for_uri (url);
1020 	}
1021 
1022 	/* hmm.  can we check if the GFile we got is something useful? */
1023 #if 0
1024 	if (valid_url == NULL) {
1025 		rb_error_dialog (NULL, _("Invalid URL"),
1026 				 _("The URL \"%s\" is not valid, please check it."), url);
1027 		return FALSE;
1028 	}
1029 #endif
1030 
1031 	feed_url = g_file_get_uri (feed);		/* not sure this buys us anything at all */
1032 	RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (pd->priv->db, feed_url);
1033 	if (entry) {
1034 		if (rhythmdb_entry_get_entry_type (entry) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
1035 			/* added as something else, probably iradio */
1036 			rb_error_dialog (NULL, _("URL already added"),
1037 					 _("The URL \"%s\" has already been added as a radio station. "
1038 					 "If this is a podcast feed, please remove the radio station."), url);
1039 			return FALSE;
1040 		}
1041 		existing_feed = TRUE;
1042 	} else {
1043 		existing_feed = FALSE;
1044 	}
1045 
1046 	info = g_new0 (RBPodcastThreadInfo, 1);
1047 	info->pd = g_object_ref (pd);
1048 	info->url = feed_url;
1049 	info->automatic = automatic;
1050 	info->existing_feed = existing_feed;
1051 
1052 	g_thread_new ("podcast-parse",
1053 		      (GThreadFunc) rb_podcast_manager_thread_parse_feed,
1054 		      info);
1055 
1056 	return TRUE;
1057 }
1058 
1059 static void
1060 rb_podcast_manager_free_parse_result (RBPodcastManagerParseResult *result)
1061 {
1062 	rb_podcast_parse_channel_free (result->channel);
1063 	g_object_unref (result->pd);
1064 	g_clear_error (&result->error);
1065 	g_free (result);
1066 }
1067 
1068 static gboolean
1069 rb_podcast_manager_parse_complete_cb (RBPodcastManagerParseResult *result)
1070 {
1071 	GDK_THREADS_ENTER ();
1072 	if (result->pd->priv->shutdown) {
1073 		GDK_THREADS_LEAVE ();
1074 		return FALSE;
1075 	}
1076 
1077 	if (result->error) {
1078 		rb_podcast_manager_handle_feed_error (result->pd,
1079 						      (char *)result->channel->url,
1080 						      result->error,
1081 						      (result->automatic == FALSE));
1082 	} else {
1083 		rb_podcast_manager_add_parsed_feed (result->pd, result->channel);
1084 	}
1085 
1086 	GDK_THREADS_LEAVE ();
1087 	return FALSE;
1088 }
1089 
1090 static void
1091 confirm_bad_mime_type_response_cb (GtkDialog *dialog, int response, RBPodcastThreadInfo *info)
1092 {
1093 	if (response == GTK_RESPONSE_YES) {
1094 		/* set the 'existing feed' flag to avoid the mime type check */
1095 		info->existing_feed = TRUE;
1096 		rb_podcast_manager_thread_parse_feed (info);
1097 	} else {
1098 		g_free (info->url);
1099 		g_free (info);
1100 	}
1101 
1102 	gtk_widget_destroy (GTK_WIDGET (dialog));
1103 }
1104 
1105 static void
1106 confirm_bad_mime_type (RBPodcastThreadInfo *info)
1107 {
1108 	GtkWidget *dialog;
1109 
1110 	GDK_THREADS_ENTER ();
1111 	dialog = gtk_message_dialog_new (NULL, 0,
1112 					 GTK_MESSAGE_QUESTION,
1113 					 GTK_BUTTONS_YES_NO,
1114 					 _("The URL '%s' does not appear to be a podcast feed. "
1115 					 "It may be the wrong URL, or the feed may be broken. "
1116 					 "Would you like Rhythmbox to attempt to use it anyway?"),
1117 					 info->url);
1118 	gtk_widget_show_all (dialog);
1119 	g_signal_connect (dialog, "response", G_CALLBACK (confirm_bad_mime_type_response_cb), info);
1120 	GDK_THREADS_LEAVE ();
1121 }
1122 
1123 static gpointer
1124 rb_podcast_manager_thread_parse_feed (RBPodcastThreadInfo *info)
1125 {
1126 	RBPodcastChannel *feed = g_new0 (RBPodcastChannel, 1);
1127 	RBPodcastManagerParseResult *result;
1128 
1129 	result = g_new0 (RBPodcastManagerParseResult, 1);
1130 	result->channel = feed;
1131 	result->pd = info->pd;		/* adopts our reference */
1132 	result->automatic = info->automatic;
1133 
1134 	g_clear_error (&result->error);
1135 
1136 	rb_debug ("attempting to parse feed %s", info->url);
1137 	if (rb_podcast_parse_load_feed (feed, info->url, info->existing_feed, &result->error) == FALSE) {
1138 		if (g_error_matches (result->error,
1139 				     RB_PODCAST_PARSE_ERROR,
1140 				     RB_PODCAST_PARSE_ERROR_MIME_TYPE)) {
1141 			confirm_bad_mime_type (info);
1142 			return NULL;
1143 		}
1144 	}
1145 
1146 	if (feed->is_opml) {
1147 		GList *l;
1148 
1149 		rb_debug ("Loading OPML feeds from %s", info->url);
1150 
1151 		for (l = feed->posts; l != NULL; l = l->next) {
1152 			RBPodcastItem *item = l->data;
1153 			/* assume the feeds don't already exist */
1154 			rb_podcast_manager_subscribe_feed (info->pd, item->url, FALSE);
1155 		}
1156 
1157 		rb_podcast_manager_free_parse_result (result);
1158 	} else {
1159 		g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
1160 				 (GSourceFunc) rb_podcast_manager_parse_complete_cb,
1161 				 result,
1162 				 (GDestroyNotify) rb_podcast_manager_free_parse_result);
1163 	}
1164 
1165 	g_free (info->url);
1166 	g_free (info);
1167 	return NULL;
1168 }
1169 
1170 RhythmDBEntry *
1171 rb_podcast_manager_add_post (RhythmDB *db,
1172 			     gboolean search_result,
1173 			     const char *name,
1174 			     const char *title,
1175 			     const char *subtitle,
1176 			     const char *generator,
1177 			     const char *uri,
1178 			     const char *description,
1179 			     gulong date,
1180 			     gulong duration,
1181 			     guint64 filesize)
1182 {
1183 	RhythmDBEntry *entry;
1184 	RhythmDBEntryType *entry_type;
1185 	GValue val = {0,};
1186 	GTimeVal time;
1187 
1188 	if (!uri || !name || !title || !g_utf8_validate(uri, -1, NULL)) {
1189 		return NULL;
1190 	}
1191 	entry = rhythmdb_entry_lookup_by_location (db, uri);
1192 	if (entry)
1193 		return NULL;
1194 
1195 	if (search_result == FALSE) {
1196 		RhythmDBQueryModel *mountpoint_entries;
1197 		GtkTreeIter iter;
1198 
1199 		/*
1200 		 * Does the uri exist as the mount-point?
1201 		 * This check is necessary since after an entry's file is downloaded,
1202 		 * the location stored in the db changes to the local file path
1203 		 * instead of the uri. The uri moves to the mount-point attribute.
1204 		 * Consequently, without this check, every downloaded entry will be
1205 		 * re-added to the db.
1206 		 */
1207 		mountpoint_entries = rhythmdb_query_model_new_empty (db);
1208 		g_object_set (mountpoint_entries, "show-hidden", TRUE, NULL);
1209 		rhythmdb_do_full_query (db, RHYTHMDB_QUERY_RESULTS (mountpoint_entries),
1210 			RHYTHMDB_QUERY_PROP_EQUALS,
1211 			RHYTHMDB_PROP_TYPE,
1212 			RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
1213 			RHYTHMDB_QUERY_PROP_EQUALS,
1214 			RHYTHMDB_PROP_MOUNTPOINT,
1215 			uri,
1216 			RHYTHMDB_QUERY_END);
1217 
1218 		if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (mountpoint_entries), &iter)) {
1219 			g_object_unref (mountpoint_entries);
1220 			return NULL;
1221 		}
1222 		g_object_unref (mountpoint_entries);
1223 	}
1224 
1225 	if (search_result)
1226 		entry_type = RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH;
1227 	else
1228 		entry_type = RHYTHMDB_ENTRY_TYPE_PODCAST_POST;
1229 
1230 	entry = rhythmdb_entry_new (db,
1231 				    entry_type,
1232 				    uri);
1233 	if (entry == NULL)
1234 		return NULL;
1235 
1236 	g_get_current_time (&time);
1237 	if (date == 0)
1238 		date = time.tv_sec;
1239 
1240 	g_value_init (&val, G_TYPE_STRING);
1241 	g_value_set_string (&val, name);
1242 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ALBUM, &val);
1243 
1244 	g_value_reset (&val);
1245 	g_value_set_static_string (&val, _("Podcast"));
1246 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_GENRE, &val);
1247 
1248 	g_value_reset (&val);
1249 	g_value_set_string (&val, title);
1250 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &val);
1251 
1252 	g_value_reset (&val);
1253 	if (subtitle)
1254 		g_value_set_string (&val, subtitle);
1255 	else
1256 		g_value_set_static_string (&val, "");
1257 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUBTITLE, &val);
1258 
1259 	g_value_reset (&val);
1260 	if (description)
1261 		g_value_set_string (&val, description);
1262 	else
1263 		g_value_set_static_string (&val, "");
1264 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DESCRIPTION, &val);
1265 
1266 	g_value_reset (&val);
1267 	if (generator)
1268 		g_value_set_string (&val, generator);
1269 	else
1270 		g_value_set_static_string (&val, "");
1271 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &val);
1272 	g_value_unset (&val);
1273 
1274 	g_value_init (&val, G_TYPE_ULONG);
1275 	g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_PAUSED);
1276 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
1277 
1278 	g_value_reset (&val);
1279 	g_value_set_ulong (&val, date);
1280 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &val);
1281 
1282 	g_value_reset (&val);
1283 	g_value_set_ulong (&val, duration);
1284 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val);
1285 
1286 	g_value_reset (&val);
1287 	g_value_set_ulong (&val, 0);
1288 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_PLAYED, &val);
1289 
1290 	/* first seen */
1291 	g_value_reset (&val);
1292 	g_value_set_ulong (&val, time.tv_sec);
1293 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &val);
1294 	g_value_unset (&val);
1295 
1296 	/* initialize the rating */
1297 	g_value_init (&val, G_TYPE_DOUBLE);
1298 	g_value_set_double (&val, 0.0);
1299 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &val);
1300 	g_value_unset (&val);
1301 
1302 	g_value_init (&val, G_TYPE_UINT64);
1303 	g_value_set_uint64 (&val, filesize);
1304 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FILE_SIZE, &val);
1305 	g_value_unset (&val);
1306 
1307 	return entry;
1308 }
1309 
1310 typedef struct {
1311 	RhythmDBEntry *entry;
1312 	RBPodcastManager *mgr;
1313 } MissingPluginRetryData;
1314 
1315 static void
1316 missing_plugins_retry_cb (gpointer inst, gboolean retry, MissingPluginRetryData *retry_data)
1317 {
1318 	if (retry == FALSE)
1319 		return;
1320 
1321 	rb_podcast_manager_save_metadata (retry_data->mgr, retry_data->entry);
1322 }
1323 
1324 static void
1325 missing_plugins_retry_cleanup (MissingPluginRetryData *retry)
1326 {
1327 	g_object_unref (retry->mgr);
1328 	rhythmdb_entry_unref (retry->entry);
1329 	g_free (retry);
1330 }
1331 
1332 
1333 static void
1334 rb_podcast_manager_save_metadata (RBPodcastManager *pd, RhythmDBEntry *entry)
1335 {
1336 	RBMetaData *md = rb_metadata_new ();
1337 	GError *error = NULL;
1338 	GValue val = { 0, };
1339 	const char *media_type;
1340 	const char *uri;
1341 	char **missing_plugins;
1342 	char **plugin_descriptions;
1343 
1344 	uri = get_download_location (entry);
1345 	rb_debug ("loading podcast metadata from %s", uri);
1346         rb_metadata_load (md, uri, &error);
1347 
1348 	if (rb_metadata_get_missing_plugins (md, &missing_plugins, &plugin_descriptions)) {
1349 		GClosure *closure;
1350 		gboolean processing;
1351 		MissingPluginRetryData *data;
1352 
1353 		rb_debug ("missing plugins during podcast metadata load for %s", uri);
1354 		data = g_new0 (MissingPluginRetryData, 1);
1355 		data->mgr = g_object_ref (pd);
1356 		data->entry = rhythmdb_entry_ref (entry);
1357 
1358 		closure = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
1359 					  data,
1360 					  (GClosureNotify) missing_plugins_retry_cleanup);
1361 		g_closure_set_marshal (closure, g_cclosure_marshal_VOID__BOOLEAN);
1362 
1363 		processing = rb_missing_plugins_install ((const char **)missing_plugins, FALSE, closure);
1364 		g_closure_sink (closure);
1365 
1366 		if (processing) {
1367 			/* when processing is complete, we'll retry */
1368 			return;
1369 		}
1370 	}
1371 
1372 	if (error != NULL) {
1373 		/* this probably isn't an audio enclosure. or some other error */
1374 		g_value_init (&val, G_TYPE_ULONG);
1375 		g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR);
1376 		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &val);
1377 		g_value_unset (&val);
1378 
1379 		g_value_init (&val, G_TYPE_STRING);
1380 		g_value_set_string (&val, error->message);
1381 		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
1382 		g_value_unset (&val);
1383 
1384 		rhythmdb_commit (pd->priv->db);
1385 
1386 		g_object_unref (md);
1387 		g_error_free (error);
1388 
1389 		return;
1390 	}
1391 
1392 	media_type = rb_metadata_get_media_type (md);
1393 	if (media_type) {
1394 		g_value_init (&val, G_TYPE_STRING);
1395 		g_value_set_string (&val, media_type);
1396 		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_MEDIA_TYPE, &val);
1397 		g_value_unset (&val);
1398 	}
1399 
1400 	if (rb_metadata_get (md,
1401 			     RB_METADATA_FIELD_DURATION,
1402 			     &val)) {
1403 		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_DURATION, &val);
1404 		g_value_unset (&val);
1405 	}
1406 
1407 	if (rb_metadata_get (md,
1408 			     RB_METADATA_FIELD_BITRATE,
1409 			     &val)) {
1410 		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_BITRATE, &val);
1411 		g_value_unset (&val);
1412 	}
1413 
1414 	rhythmdb_commit (pd->priv->db);
1415 
1416 	g_object_unref (md);
1417 }
1418 
1419 static void
1420 rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd, RhythmDBEntry *entry)
1421 {
1422 	RhythmDBEntryType *type = rhythmdb_entry_get_entry_type (entry);
1423 
1424 	if (type != RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
1425 		return;
1426 
1427         rb_podcast_manager_download_entry (pd, entry);
1428 }
1429 
1430 static void
1431 download_info_free (RBPodcastManagerInfo *data)
1432 {
1433 	/* what should this do about the thread and etc.? */
1434 
1435 	if (data->cancel != NULL) {
1436 		g_object_unref (data->cancel);
1437 		data->cancel = NULL;
1438 	}
1439 
1440 	if (data->source) {
1441 		g_object_unref (data->source);
1442 		data->source = NULL;
1443 	}
1444 
1445 	if (data->destination) {
1446 		g_object_unref (data->destination);
1447 		data->destination = NULL;
1448 	}
1449 
1450 	if (data->query_string) {
1451 		g_free (data->query_string);
1452 		data->query_string = NULL;
1453 	}
1454 
1455 	if (data->entry) {
1456 		rhythmdb_entry_unref (data->entry);
1457 	}
1458 
1459 	g_free (data);
1460 }
1461 
1462 static void
1463 download_progress (RBPodcastManagerInfo *data, guint64 downloaded, guint64 total, gboolean complete)
1464 {
1465 	guint local_progress = 0;
1466 
1467 	if (downloaded > 0 && total > 0)
1468 		local_progress = (100 * downloaded) / total;
1469 
1470 	if (local_progress != data->progress) {
1471 		GValue val = {0,};
1472 
1473 		rb_debug ("%s: %" G_GUINT64_FORMAT "/ %" G_GUINT64_FORMAT,
1474 			  rhythmdb_entry_get_string (data->entry, RHYTHMDB_PROP_LOCATION),
1475 			  downloaded, total);
1476 
1477 		GDK_THREADS_ENTER ();
1478 
1479 		g_value_init (&val, G_TYPE_ULONG);
1480 		g_value_set_ulong (&val, local_progress);
1481 		rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1482 		g_value_unset (&val);
1483 
1484 		rhythmdb_commit (data->pd->priv->db);
1485 
1486 		GDK_THREADS_LEAVE ();
1487 
1488 		data->progress = local_progress;
1489 	}
1490 
1491 	if (complete) {
1492 		if (g_cancellable_is_cancelled (data->cancel) == FALSE) {
1493 			GValue val = {0,};
1494 			rb_debug ("download of %s completed",
1495 				  get_remote_location (data->entry));
1496 
1497 			g_value_init (&val, G_TYPE_UINT64);
1498 			g_value_set_uint64 (&val, downloaded);
1499 			rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_FILE_SIZE, &val);
1500 			g_value_unset (&val);
1501 
1502 			if (total == 0 || downloaded >= total) {
1503 				g_value_init (&val, G_TYPE_ULONG);
1504 				g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_COMPLETE);
1505 				rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val);
1506 				g_value_unset (&val);
1507 			}
1508 
1509 			rb_podcast_manager_save_metadata (data->pd,
1510 							  data->entry);
1511 		}
1512 		g_idle_add ((GSourceFunc)end_job, data);
1513 	}
1514 }
1515 
1516 static gpointer
1517 podcast_download_thread (RBPodcastManagerInfo *data)
1518 {
1519 	GError *error = NULL;
1520 	char buf[8192];
1521 	gssize n_read;
1522 	gssize n_written;
1523 	guint64 downloaded;
1524 
1525 	/* if we have an offset to download from, try the seek
1526 	 * before anything else.  if we can't seek, we'll have to
1527 	 * grab the whole thing.
1528 	 */
1529 	downloaded = 0;
1530 	if (data->download_offset != 0) {
1531 		g_seekable_seek (G_SEEKABLE (data->in_stream),
1532 				 data->download_offset,
1533 				 G_SEEK_SET,
1534 				 data->cancel,
1535 				 &error);
1536 		if (error == NULL) {
1537 			/* ok, now we can open the output file for appending */
1538 			rb_debug ("seek to offset %" G_GUINT64_FORMAT " successful", data->download_offset);
1539 			data->out_stream = g_file_append_to (data->destination,
1540 							     G_FILE_CREATE_NONE,
1541 							     data->cancel,
1542 							     &error);
1543 			downloaded = data->download_offset;
1544 		} else if (error->domain == G_IO_ERROR &&
1545 			   error->code == G_IO_ERROR_NOT_SUPPORTED) {
1546 			/* can't seek, download the whole thing */
1547 			rb_debug ("seeking failed: %s", error->message);
1548 			g_clear_error (&error);
1549 		}
1550 	}
1551 	if (error != NULL) {
1552 		download_error (data, error);
1553 		g_error_free (error);
1554 		return NULL;
1555 	}
1556 
1557 	/* set the downloaded location for the episode
1558 	 * and do it before opening the file, so that the monitor
1559 	 * doesn't add a normal entry for us
1560 	 */
1561 	if (get_download_location (data->entry) == NULL) {
1562 		GValue val = {0,};
1563 		char *uri = g_file_get_uri (data->destination);
1564 
1565 		g_value_init (&val, G_TYPE_STRING);
1566 		g_value_set_string (&val, uri);
1567 		set_download_location (data->pd->priv->db, data->entry, &val);
1568 		g_value_unset (&val);
1569 
1570 		rhythmdb_commit (data->pd->priv->db);
1571 		g_free (uri);
1572 	}
1573 
1574 	/* gvfs doesn't do file info queries from streams, so this doesn't help, but
1575 	 * maybe some day it will..
1576 	 */
1577 	if (data->download_size == 0) {
1578 		GFileInfo *info;
1579 
1580 		info = g_file_input_stream_query_info (data->in_stream,
1581 						       G_FILE_ATTRIBUTE_STANDARD_SIZE,
1582 						       NULL,
1583 						       &error);
1584 		if (error != NULL) {
1585 			rb_debug ("stream info query failed: %s", error->message);
1586 			g_clear_error (&error);
1587 		} else {
1588 			data->download_size = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
1589 			rb_debug ("got file size from stream: %" G_GINT64_FORMAT, data->download_size);
1590 			g_object_unref (info);
1591 		}
1592 	}
1593 
1594 	/* open local file */
1595 	if (data->out_stream == NULL) {
1596 		data->out_stream = g_file_create (data->destination,
1597 						  G_FILE_CREATE_NONE,
1598 						  data->cancel,
1599 						  &error);
1600 		if (error != NULL) {
1601 			download_error (data, error);
1602 			g_error_free (error);
1603 			return NULL;
1604 		}
1605 	}
1606 
1607 	/* loop, copying from input stream to output stream */
1608 	while (TRUE) {
1609 		char *p;
1610 		n_read = g_input_stream_read (G_INPUT_STREAM (data->in_stream),
1611 					      buf, sizeof (buf),
1612 					      data->cancel,
1613 					      &error);
1614 		if (n_read < 1) {
1615 			break;
1616 		}
1617 
1618 		p = buf;
1619 		while (n_read > 0) {
1620 			n_written = g_output_stream_write (G_OUTPUT_STREAM (data->out_stream),
1621 							   p, n_read,
1622 							   data->cancel,
1623 							   &error);
1624 			if (n_written == -1) {
1625 				break;
1626 			}
1627 			p += n_written;
1628 			n_read -= n_written;
1629 			downloaded += n_written;
1630 		}
1631 		if (n_written == -1)
1632 			break;
1633 
1634 		download_progress (data, downloaded, data->download_size, FALSE);
1635 	}
1636 
1637 	/* close everything - don't allow these operations to be cancelled */
1638 	g_input_stream_close (G_INPUT_STREAM (data->in_stream), NULL, NULL);
1639 	g_object_unref (data->in_stream);
1640 
1641 	g_output_stream_close (G_OUTPUT_STREAM (data->out_stream), NULL, &error);
1642 	g_object_unref (data->out_stream);
1643 
1644 	if (error != NULL) {
1645 		download_error (data, error);
1646 		g_error_free (error);
1647 	} else {
1648 		download_progress (data, downloaded, data->download_size, TRUE);
1649 	}
1650 
1651 	rb_debug ("exiting download thread");
1652 	return NULL;
1653 }
1654 
1655 static gboolean
1656 end_job	(RBPodcastManagerInfo *data)
1657 {
1658 	RBPodcastManager *pd = data->pd;
1659 
1660 	g_assert (rb_is_main_thread ());
1661 
1662 	rb_debug ("cleaning up download of %s",
1663 		  get_remote_location (data->entry));
1664 
1665 	data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, data);
1666 
1667 	GDK_THREADS_ENTER ();
1668 	g_signal_emit (data->pd, rb_podcast_manager_signals[FINISH_DOWNLOAD],
1669 		       0, data->entry);
1670 	GDK_THREADS_LEAVE ();
1671 
1672 	g_assert (pd->priv->active_download == data);
1673 	pd->priv->active_download = NULL;
1674 
1675 	download_info_free (data);
1676 
1677 	if (pd->priv->next_file_id == 0) {
1678 		pd->priv->next_file_id =
1679 			g_idle_add ((GSourceFunc) rb_podcast_manager_next_file, pd);
1680 	}
1681 	return FALSE;
1682 }
1683 
1684 static void
1685 cancel_job (RBPodcastManagerInfo *data)
1686 {
1687 	g_assert (rb_is_main_thread ());
1688 	rb_debug ("cancelling download of %s", get_remote_location (data->entry));
1689 
1690 	/* is this the active download? */
1691 	if (data == data->pd->priv->active_download) {
1692 		g_cancellable_cancel (data->cancel);
1693 
1694 		/* download data will be cleaned up after next progress callback */
1695 	} else {
1696 		/* destroy download data */
1697 		data->pd->priv->download_list = g_list_remove (data->pd->priv->download_list, data);
1698 		download_info_free (data);
1699 	}
1700 }
1701 
1702 
1703 void
1704 rb_podcast_manager_unsubscribe_feed (RhythmDB *db, const char *url)
1705 {
1706 	RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location (db, url);
1707 	if (entry) {
1708 		GValue val = {0, };
1709 		g_value_init (&val, G_TYPE_ULONG);
1710 		g_value_set_ulong (&val, 0);
1711 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val);
1712 		g_value_unset (&val);
1713 	}
1714 
1715 }
1716 
1717 gboolean
1718 rb_podcast_manager_remove_feed (RBPodcastManager *pd, const char *url, gboolean remove_files)
1719 {
1720 	RhythmDBQueryModel *query;
1721 	GtkTreeModel *query_model;
1722 	GtkTreeIter iter;
1723 	RhythmDBEntry *entry;
1724 
1725 	entry = rhythmdb_entry_lookup_by_location (pd->priv->db, url);
1726 	if (entry == NULL) {
1727 		rb_debug ("unable to find entry for podcast feed %s", url);
1728 		return FALSE;
1729 	}
1730 
1731 	rb_debug ("removing podcast feed: %s remove_files: %d", url, remove_files);
1732 
1733 	/* first remove the posts from the feed. include deleted posts (which will be hidden).
1734 	 * these need to be deleted so they will properly be readded should the feed be readded.
1735 	 */
1736 	query = rhythmdb_query_model_new_empty (pd->priv->db);
1737 	g_object_set (query, "show-hidden", TRUE, NULL);
1738 	query_model = GTK_TREE_MODEL (query);
1739 	rhythmdb_do_full_query (pd->priv->db,
1740 				RHYTHMDB_QUERY_RESULTS (query_model),
1741 				RHYTHMDB_QUERY_PROP_EQUALS,
1742 				RHYTHMDB_PROP_TYPE, RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
1743 				RHYTHMDB_QUERY_PROP_LIKE,
1744 				RHYTHMDB_PROP_SUBTITLE, get_remote_location (entry),
1745 				RHYTHMDB_QUERY_END);
1746 
1747 	if (gtk_tree_model_get_iter_first (query_model, &iter)) {
1748 		gboolean has_next;
1749 		do {
1750 			RhythmDBEntry *entry;
1751 
1752 			gtk_tree_model_get (query_model, &iter, 0, &entry, -1);
1753 			has_next = gtk_tree_model_iter_next (query_model, &iter);
1754 
1755 			/* make sure we're not downloading it */
1756 			rb_podcast_manager_cancel_download (pd, entry);
1757 			if (remove_files) {
1758 				rb_podcast_manager_delete_download (pd, entry);
1759 			}
1760 
1761 			rhythmdb_entry_delete (pd->priv->db, entry);
1762 			rhythmdb_entry_unref (entry);
1763 
1764 		} while (has_next);
1765 
1766 		rhythmdb_commit (pd->priv->db);
1767 	}
1768 
1769 	g_object_unref (query_model);
1770 
1771 	/* now delete the feed */
1772 	rhythmdb_entry_delete (pd->priv->db, entry);
1773 	rhythmdb_commit (pd->priv->db);
1774 	return TRUE;
1775 }
1776 
1777 void
1778 rb_podcast_manager_delete_download (RBPodcastManager *pd, RhythmDBEntry *entry)
1779 {
1780 	const char *file_name;
1781 	GFile *file;
1782 	GError *error = NULL;
1783 	RhythmDBEntryType *type = rhythmdb_entry_get_entry_type (entry);
1784 
1785 	/* make sure it's a podcast post */
1786 	g_assert (type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
1787 
1788 	file_name = get_download_location (entry);
1789 	if (file_name == NULL) {
1790 		/* episode has not been downloaded */
1791 		rb_debug ("Episode %s not downloaded",
1792 			  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1793 		return;
1794 	}
1795 
1796 	rb_debug ("deleting downloaded episode %s", file_name);
1797 	file = g_file_new_for_uri (file_name);
1798 	g_file_delete (file, NULL, &error);
1799 
1800 	if (error != NULL) {
1801 		rb_debug ("Removing episode failed: %s", error->message);
1802 		g_clear_error (&error);
1803 	} else {
1804 		GFile *feed_dir;
1805 		/* try to remove the directory
1806 		 * (will only work once it's empty)
1807 		 */
1808 		feed_dir = g_file_get_parent (file);
1809 		g_file_delete (feed_dir, NULL, &error);
1810 		if (error != NULL) {
1811 			rb_debug ("couldn't remove podcast feed directory: %s",
1812 				  error->message);
1813 			g_clear_error (&error);
1814 		}
1815 		g_object_unref (feed_dir);
1816 	}
1817 	g_object_unref (file);
1818 }
1819 
1820 void
1821 rb_podcast_manager_cancel_download (RBPodcastManager *pd, RhythmDBEntry *entry)
1822 {
1823 	GList *lst;
1824 	g_assert (rb_is_main_thread ());
1825 
1826 	for (lst = pd->priv->download_list; lst != NULL; lst = lst->next) {
1827 		RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) lst->data;
1828 		if (data->entry == entry) {
1829 			cancel_job (data);
1830 			return;
1831 		}
1832 	}
1833 }
1834 
1835 static void
1836 podcast_settings_changed_cb (GSettings *settings, const char *key, RBPodcastManager *mgr)
1837 {
1838 	if (g_strcmp0 (key, PODCAST_DOWNLOAD_INTERVAL) == 0) {
1839 		rb_podcast_manager_start_update_timer (mgr);
1840 	}
1841 }
1842 
1843 /* this bit really wants to die */
1844 
1845 static gboolean
1846 remove_if_not_downloaded (GtkTreeModel *model,
1847 			  GtkTreePath *path,
1848 			  GtkTreeIter *iter,
1849 			  GList **remove)
1850 {
1851 	RhythmDBEntry *entry;
1852 
1853 	entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model),
1854 						    iter);
1855 	if (entry != NULL) {
1856 		if (rb_podcast_manager_entry_downloaded (entry) == FALSE) {
1857 			rb_debug ("entry %s is no longer present in the feed and has not been downloaded",
1858 				  get_remote_location (entry));
1859 			*remove = g_list_prepend (*remove, entry);
1860 		} else {
1861 			rhythmdb_entry_unref (entry);
1862 		}
1863 	}
1864 
1865 	return FALSE;
1866 }
1867 
1868 void
1869 rb_podcast_manager_insert_feed_url (RBPodcastManager *pd, const char *url)
1870 {
1871 	RhythmDBEntry *entry;
1872 	GValue status_val = { 0, };
1873 	GValue title_val = { 0, };
1874 	GValue author_val = { 0, };
1875 	GValue last_update_val = { 0, };
1876 
1877 	entry = rhythmdb_entry_lookup_by_location (pd->priv->db, url);
1878 	if (entry) {
1879 		rb_debug ("podcast feed entry for %s found", url);
1880 		return;
1881 	}
1882 	rb_debug ("adding podcast feed %s with no entries", url);
1883 	entry = rhythmdb_entry_new (pd->priv->db,
1884 				    RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
1885 				    url);
1886 	if (entry == NULL)
1887 		return;
1888 
1889 	g_value_init (&status_val, G_TYPE_ULONG);
1890 	g_value_set_ulong (&status_val, 1);
1891 	rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1892 	g_value_unset (&status_val);
1893 
1894 	g_value_init (&title_val, G_TYPE_STRING);
1895 	g_value_set_string (&title_val, url);
1896 	rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_TITLE, &title_val);
1897 	g_value_unset (&title_val);
1898 
1899 	g_value_init (&author_val, G_TYPE_STRING);
1900 	g_value_set_static_string (&author_val, _("Unknown"));
1901 	rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_ARTIST, &author_val);
1902 	g_value_unset (&author_val);
1903 
1904 	g_value_init (&last_update_val, G_TYPE_ULONG);
1905 	g_value_set_ulong (&last_update_val, time(NULL));
1906 	rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
1907 	g_value_unset (&last_update_val);
1908 }
1909 
1910 void
1911 rb_podcast_manager_add_parsed_feed (RBPodcastManager *pd, RBPodcastChannel *data)
1912 {
1913 	GValue description_val = { 0, };
1914 	GValue title_val = { 0, };
1915 	GValue lang_val = { 0, };
1916 	GValue copyright_val = { 0, };
1917 	GValue image_val = { 0, };
1918 	GValue author_val = { 0, };
1919 	GValue status_val = { 0, };
1920 	GValue last_post_val = { 0, };
1921 	GValue last_update_val = { 0, };
1922 	GValue error_val = { 0, };
1923 	gulong last_post = 0;
1924 	gulong new_last_post;
1925 	const char *title;
1926 	GList *download_entries = NULL;
1927 	gboolean new_feed, updated, download_last;
1928 	RhythmDB *db = pd->priv->db;
1929 	RhythmDBQueryModel *existing_entries = NULL;
1930 
1931 	RhythmDBEntry *entry;
1932 
1933 	GList *lst_songs;
1934 
1935 	new_feed = TRUE;
1936 
1937 	/* processing podcast head */
1938 	entry = rhythmdb_entry_lookup_by_location (db, (gchar *)data->url);
1939 	if (entry) {
1940 		if (rhythmdb_entry_get_entry_type (entry) != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
1941 			return;
1942 
1943 		rb_debug ("Podcast feed entry for %s found", data->url);
1944 		g_value_init (&status_val, G_TYPE_ULONG);
1945 		g_value_set_ulong (&status_val, 1);
1946 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1947 		g_value_unset (&status_val);
1948 		last_post = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_POST_TIME);
1949 		new_feed = FALSE;
1950 
1951 		/* find all the existing entries in this feed, so we can cull those
1952 		 * that haven't been downloaded and are no longer present in the feed.
1953 		 */
1954 		existing_entries = rhythmdb_query_model_new_empty (db);
1955 		g_object_set (existing_entries, "show-hidden", TRUE, NULL);
1956 		rhythmdb_do_full_query (db, RHYTHMDB_QUERY_RESULTS (existing_entries),
1957 				 	RHYTHMDB_QUERY_PROP_EQUALS,
1958 					  RHYTHMDB_PROP_TYPE,
1959 					  RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
1960 					RHYTHMDB_QUERY_PROP_EQUALS,
1961 					  RHYTHMDB_PROP_SUBTITLE,
1962 					  data->url,
1963 					RHYTHMDB_QUERY_END);
1964 	} else {
1965 		rb_debug ("Adding podcast feed: %s", data->url);
1966 		entry = rhythmdb_entry_new (db,
1967 					    RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
1968 				    	    (gchar *) data->url);
1969 		if (entry == NULL)
1970 			return;
1971 
1972 		g_value_init (&status_val, G_TYPE_ULONG);
1973 		g_value_set_ulong (&status_val, 1);
1974 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &status_val);
1975 		g_value_unset (&status_val);
1976 	}
1977 
1978 	/* if the feed does not contain a title, use the URL instead */
1979 	g_value_init (&title_val, G_TYPE_STRING);
1980 	if (data->title == NULL || strlen ((gchar *)data->title) == 0) {
1981 		g_value_set_string (&title_val, (gchar *) data->url);
1982 		title = data->url;
1983 	} else {
1984 		g_value_set_string (&title_val, (gchar *) data->title);
1985 		title = data->title;
1986 	}
1987 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &title_val);
1988 	g_value_unset (&title_val);
1989 
1990 	g_value_init (&author_val, G_TYPE_STRING);
1991 	if (data->author)
1992 		g_value_set_string (&author_val, (gchar *) data->author);
1993 	else
1994 		g_value_set_static_string (&author_val, _("Unknown"));
1995 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &author_val);
1996 	g_value_unset (&author_val);
1997 
1998 	if (data->description) {
1999 		g_value_init (&description_val, G_TYPE_STRING);
2000 		g_value_set_string (&description_val, (gchar *) data->description);
2001 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DESCRIPTION, &description_val);
2002 		g_value_unset (&description_val);
2003 	}
2004 
2005 	if (data->lang) {
2006 		g_value_init (&lang_val, G_TYPE_STRING);
2007 		g_value_set_string (&lang_val, (gchar *) data->lang);
2008 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LANG, &lang_val);
2009 		g_value_unset (&lang_val);
2010 	}
2011 
2012 	if (data->copyright) {
2013 		g_value_init (&copyright_val, G_TYPE_STRING);
2014 		g_value_set_string (&copyright_val, (gchar *) data->copyright);
2015 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_COPYRIGHT, &copyright_val);
2016 		g_value_unset (&copyright_val);
2017 	}
2018 
2019 	if (data->img) {
2020 		RBExtDBKey *key;
2021 
2022 		g_value_init (&image_val, G_TYPE_STRING);
2023 		g_value_set_string (&image_val, (gchar *) data->img);
2024 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_IMAGE, &image_val);
2025 		g_value_unset (&image_val);
2026 
2027 		key = rb_ext_db_key_create_storage ("album", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
2028 		rb_ext_db_store_uri (pd->priv->art_store,
2029 				     key,
2030 				     RB_EXT_DB_SOURCE_SEARCH,	/* sort of */
2031 				     data->img);
2032 	}
2033 
2034 	/* clear any error that might have been set earlier */
2035 	g_value_init (&error_val, G_TYPE_STRING);
2036 	g_value_set_string (&error_val, NULL);
2037 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &error_val);
2038 	g_value_unset (&error_val);
2039 
2040 	/* insert episodes */
2041 	new_last_post = last_post;
2042 
2043 	updated = FALSE;
2044 	download_last = (g_settings_get_enum (pd->priv->settings, PODCAST_DOWNLOAD_INTERVAL) != PODCAST_INTERVAL_MANUAL);
2045 	for (lst_songs = data->posts; lst_songs != NULL; lst_songs = g_list_next (lst_songs)) {
2046 		RBPodcastItem *item = (RBPodcastItem *) lst_songs->data;
2047 		RhythmDBEntry *post_entry;
2048 
2049 		if (existing_entries != NULL) {
2050 			GtkTreeIter iter;
2051 			RhythmDBEntry *entry = NULL;
2052 
2053 			/* look for an existing entry with this remote location */
2054 			if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (existing_entries), &iter)) {
2055 				do {
2056 					entry = rhythmdb_query_model_iter_to_entry (existing_entries, &iter);
2057 					if (strcmp (get_remote_location (entry), item->url) == 0) {
2058 						rhythmdb_entry_unref (entry);
2059 						break;
2060 					}
2061 					rhythmdb_entry_unref (entry);
2062 					entry = NULL;
2063 
2064 				} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (existing_entries), &iter));
2065 			}
2066 
2067 			if (entry != NULL) {
2068 				/* mark this entry as still being available */
2069 				rhythmdb_query_model_remove_entry (existing_entries, entry);
2070 			}
2071 		}
2072 
2073 
2074 		post_entry =
2075 		    rb_podcast_manager_add_post (db,
2076 			    FALSE,
2077 			    title,
2078 			    (gchar *) item->title,
2079 			    (gchar *) data->url,
2080 			    (gchar *) (item->author ? item->author : data->author),
2081 			    (gchar *) item->url,
2082 			    (gchar *) item->description,
2083 			    (gulong) (item->pub_date > 0 ? item->pub_date : data->pub_date),
2084 			    (gulong) item->duration,
2085 			    item->filesize);
2086 
2087 		if (post_entry)
2088 			updated = TRUE;
2089 
2090                 if (post_entry && item->pub_date >= new_last_post) {
2091 			if (item->pub_date > new_last_post) {
2092 				g_list_free (download_entries);
2093 				download_entries = NULL;
2094 			}
2095 			download_entries = g_list_prepend (download_entries, post_entry);
2096 			new_last_post = item->pub_date;
2097                 }
2098 	}
2099 
2100 	if (download_last) {
2101 		GValue status = {0,};
2102 		GList *t;
2103 
2104 		g_value_init (&status, G_TYPE_ULONG);
2105 		g_value_set_ulong (&status, RHYTHMDB_PODCAST_STATUS_WAITING);
2106 		for (t = download_entries; t != NULL; t = g_list_next (t)) {
2107 			rhythmdb_entry_set (db,
2108 					    (RhythmDBEntry*) t->data,
2109 					    RHYTHMDB_PROP_STATUS,
2110 					    &status);
2111 		}
2112 		g_value_unset (&status);
2113 	}
2114 	g_list_free (download_entries);
2115 
2116 	if (updated)
2117 		g_signal_emit (pd, rb_podcast_manager_signals[FEED_UPDATES_AVAILABLE],
2118 			       0, entry);
2119 
2120 	if (data->pub_date > new_last_post)
2121 		new_last_post = data->pub_date;
2122 
2123 	g_value_init (&last_post_val, G_TYPE_ULONG);
2124 	g_value_set_ulong (&last_post_val, new_last_post);
2125 
2126 	if (new_feed)
2127 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
2128 	else
2129 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_POST_TIME, &last_post_val);
2130 	g_value_unset (&last_post_val);
2131 
2132 	g_value_init (&last_update_val, G_TYPE_ULONG);
2133 	g_value_set_ulong (&last_update_val, time(NULL));
2134 
2135 	if (new_feed)
2136 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
2137 	else
2138 		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_LAST_SEEN, &last_update_val);
2139 	g_value_unset (&last_update_val);
2140 
2141 	if (existing_entries != NULL) {
2142 		GList *remove = NULL;
2143 		GList *i;
2144 
2145 		/* look for expired entries to remove */
2146 		gtk_tree_model_foreach (GTK_TREE_MODEL (existing_entries),
2147 					(GtkTreeModelForeachFunc) remove_if_not_downloaded,
2148 					&remove);
2149 		for (i = remove; i != NULL; i = i->next) {
2150 			rhythmdb_entry_delete (db, (RhythmDBEntry *)i->data);
2151 		}
2152 		g_list_free (remove);
2153 
2154 		g_object_unref (existing_entries);
2155 	}
2156 
2157 	rhythmdb_commit (db);
2158 }
2159 
2160 static void
2161 rb_podcast_manager_handle_feed_error (RBPodcastManager *mgr,
2162 				      const char *url,
2163 				      GError *error,
2164 				      gboolean emit)
2165 {
2166 	RhythmDBEntry *entry;
2167 	GValue v = {0,};
2168 	gboolean existing = FALSE;
2169 
2170 	/* set the error in the feed entry, if one exists */
2171 	entry = rhythmdb_entry_lookup_by_location (mgr->priv->db, url);
2172 	if (entry != NULL && rhythmdb_entry_get_entry_type (entry) == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
2173 		g_value_init (&v, G_TYPE_STRING);
2174 		g_value_set_string (&v, error->message);
2175 		rhythmdb_entry_set (mgr->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &v);
2176 		g_value_unset (&v);
2177 
2178 		rhythmdb_commit (mgr->priv->db);
2179 		existing = TRUE;
2180 	}
2181 
2182 	/* if this was a result of a direct user action, emit the error signal too */
2183 	if (emit) {
2184 		gchar *error_msg;
2185 		error_msg = g_strdup_printf (_("There was a problem adding this podcast: %s.  Please verify the URL: %s"),
2186 					     error->message, url);
2187 		g_signal_emit (mgr,
2188 			       rb_podcast_manager_signals[PROCESS_ERROR],
2189 			       0, url, error_msg, existing);
2190 		g_free (error_msg);
2191 	}
2192 }
2193 
2194 void
2195 rb_podcast_manager_shutdown (RBPodcastManager *pd)
2196 {
2197 	GList *lst, *l;
2198 
2199 	g_assert (rb_is_main_thread ());
2200 
2201 	lst = g_list_reverse (g_list_copy (pd->priv->download_list));
2202 	for (l = lst; l != NULL; l = l->next) {
2203 		RBPodcastManagerInfo *data = (RBPodcastManagerInfo *) l->data;
2204 		cancel_job (data);
2205 	}
2206 	g_list_free (lst);
2207 
2208 	pd->priv->shutdown = TRUE;
2209 }
2210 
2211 char *
2212 rb_podcast_manager_get_podcast_dir (RBPodcastManager *pd)
2213 {
2214 	char *conf_dir_uri = g_settings_get_string (pd->priv->settings, PODCAST_DOWNLOAD_DIR_KEY);
2215 
2216 	/* if we don't have a download directory yet, use the music dir,
2217 	 * or the home dir if we can't find that.
2218 	 */
2219 	if (conf_dir_uri == NULL || (strcmp (conf_dir_uri, "") == 0)) {
2220 		const char *conf_dir_name;
2221 
2222 		conf_dir_name = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC);
2223 		if (!conf_dir_name)
2224 			conf_dir_name = g_get_home_dir ();
2225 
2226 		conf_dir_uri = g_filename_to_uri (conf_dir_name, NULL, NULL);
2227 		g_settings_set_string (pd->priv->settings, PODCAST_DOWNLOAD_DIR_KEY, conf_dir_uri);
2228 	}
2229 
2230 	return conf_dir_uri;
2231 }
2232 
2233 void
2234 rb_podcast_manager_add_search (RBPodcastManager *pd, GType search_type)
2235 {
2236 	pd->priv->searches = g_list_append (pd->priv->searches, GUINT_TO_POINTER (search_type));
2237 }
2238 
2239 GList *
2240 rb_podcast_manager_get_searches (RBPodcastManager *pd)
2241 {
2242 	GList *searches = NULL;
2243 	GList *i;
2244 
2245 	for (i = pd->priv->searches; i != NULL; i = i->next) {
2246 		RBPodcastSearch *search;
2247 		GType search_type;
2248 
2249 		search_type = GPOINTER_TO_UINT (i->data);
2250 		search = RB_PODCAST_SEARCH (g_object_new (search_type, NULL));
2251 		searches = g_list_append (searches, search);
2252 	}
2253 
2254 	return searches;
2255 }