hythmbox-2.98/sources/rb-import-errors-source.c

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
  2 /*
  3  *  Copyright (C) 2006-2010  Jonathan Matthew <jonathan@d14n.org>
  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 #include <gtk/gtk.h>
 31 #include <glib/gi18n.h>
 32 
 33 #include "rb-entry-view.h"
 34 #include "rb-import-errors-source.h"
 35 #include "rb-util.h"
 36 #include "rb-debug.h"
 37 #include "rb-missing-plugins.h"
 38 
 39 static void rb_import_errors_source_class_init (RBImportErrorsSourceClass *klass);
 40 static void rb_import_errors_source_init (RBImportErrorsSource *source);
 41 static void rb_import_errors_source_constructed (GObject *object);
 42 static void rb_import_errors_source_dispose (GObject *object);
 43 
 44 static void impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
 45 static void impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
 46 
 47 static RBEntryView *impl_get_entry_view (RBSource *source);
 48 static void impl_delete (RBSource *source);
 49 static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
 50 
 51 static void rb_import_errors_source_songs_show_popup_cb (RBEntryView *view,
 52 							 gboolean over_entry,
 53 							 RBImportErrorsSource *source);
 54 static void infobar_response_cb (GtkInfoBar *bar, gint response, RBImportErrorsSource *source);
 55 
 56 static void missing_plugin_row_inserted_cb (GtkTreeModel *model,
 57 					    GtkTreePath *path,
 58 					    GtkTreeIter *iter,
 59 					    RBImportErrorsSource *source);
 60 static void missing_plugin_row_deleted_cb (GtkTreeModel *model,
 61 					   GtkTreePath *path,
 62 					   RBImportErrorsSource *source);
 63 
 64 enum {
 65 	PROP_0,
 66 	PROP_NORMAL_ENTRY_TYPE,
 67 	PROP_IGNORE_ENTRY_TYPE
 68 };
 69 
 70 struct _RBImportErrorsSourcePrivate
 71 {
 72 	RhythmDB *db;
 73 	RBEntryView *view;
 74 
 75 	RhythmDBQueryModel *missing_plugin_model;
 76 	GtkWidget *infobar;
 77 
 78 	RhythmDBEntryType *normal_entry_type;
 79 	RhythmDBEntryType *ignore_entry_type;
 80 };
 81 
 82 G_DEFINE_TYPE (RBImportErrorsSource, rb_import_errors_source, RB_TYPE_SOURCE);
 83 
 84 /**
 85  * SECTION:rb-import-errors-source
 86  * @short_description: source for displaying import errors
 87  *
 88  * This source is used to display the names of files that could not
 89  * be imported into the library, along with any error messages from
 90  * the import process.  When there are no import errors to display,
 91  * the source is hidden.
 92  *
 93  * The source allows the user to delete the import error entries,
 94  * and to move the files to the trash.
 95  *
 96  * When a file import fails, a #RhythmDBEntry is created with a
 97  * specific entry type for import errors.  This source uses a query
 98  * model that matches all such import error entries.
 99  *
100  * To keep import errors from removable devices separate from those
101  * from the main library, multiple import error sources can be created,
102  * with separate entry types.  The generic audio player plugin, for
103  * example, creates an import error source for each device and inserts
104  * it into the source list as a child of the main source for the device.
105  */
106 
107 static void
108 rb_import_errors_source_class_init (RBImportErrorsSourceClass *klass)
109 {
110 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
111 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
112 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
113 
114 	object_class->dispose = rb_import_errors_source_dispose;
115 	object_class->constructed = rb_import_errors_source_constructed;
116 	object_class->get_property = impl_get_property;
117 	object_class->set_property = impl_set_property;
118 
119 	page_class->get_status = impl_get_status;
120 
121 	source_class->impl_get_entry_view = impl_get_entry_view;
122 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_false_function;
123 
124 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
125 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
126 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_true_function;
127 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
128 	source_class->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_false_function;
129 
130 	source_class->impl_delete = impl_delete;
131 
132 	source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_false_function;
133 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
134 
135 	g_object_class_install_property (object_class,
136 					 PROP_NORMAL_ENTRY_TYPE,
137 					 g_param_spec_object ("normal-entry-type",
138 							      "Normal entry type",
139 							      "Entry type for successfully imported entries of this type",
140 							      RHYTHMDB_TYPE_ENTRY_TYPE,
141 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
142 	g_object_class_install_property (object_class,
143 					 PROP_IGNORE_ENTRY_TYPE,
144 					 g_param_spec_object ("ignore-entry-type",
145 							      "Ignore entry type",
146 							      "Entry type for entries of this type to be ignored",
147 							      RHYTHMDB_TYPE_ENTRY_TYPE,
148 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
149 
150 	g_type_class_add_private (klass, sizeof (RBImportErrorsSourcePrivate));
151 }
152 
153 static void
154 rb_import_errors_source_init (RBImportErrorsSource *source)
155 {
156 	gint size;
157 	GdkPixbuf *pixbuf;
158 
159 	source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source, RB_TYPE_IMPORT_ERRORS_SOURCE, RBImportErrorsSourcePrivate);
160 
161 	gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
162 	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
163 					   "dialog-error",
164 					   size,
165 					   0, NULL);
166 	g_object_set (source, "pixbuf", pixbuf, NULL);
167 	if (pixbuf != NULL) {
168 		g_object_unref (pixbuf);
169 	}
170 }
171 
172 static void
173 rb_import_errors_source_constructed (GObject *object)
174 {
175 	GObject *shell_player;
176 	RBImportErrorsSource *source;
177 	RBShell *shell;
178 	GPtrArray *query;
179 	RhythmDBQueryModel *model;
180 	RhythmDBEntryType *entry_type;
181 	GtkWidget *box;
182 	GtkWidget *label;
183 
184 	RB_CHAIN_GOBJECT_METHOD (rb_import_errors_source_parent_class, constructed, object);
185 
186 	source = RB_IMPORT_ERRORS_SOURCE (object);
187 
188 	g_object_get (source,
189 		      "shell", &shell,
190 		      "entry-type", &entry_type,
191 		      NULL);
192 	g_object_get (shell,
193 		      "db", &source->priv->db,
194 		      "shell-player", &shell_player,
195 		      NULL);
196 	g_object_unref (shell);
197 
198 	/* construct real query */
199 	query = rhythmdb_query_parse (source->priv->db,
200 				      RHYTHMDB_QUERY_PROP_EQUALS,
201 				      	RHYTHMDB_PROP_TYPE,
202 					entry_type,
203 				      RHYTHMDB_QUERY_END);
204 
205 	model = rhythmdb_query_model_new (source->priv->db, query,
206 					  (GCompareDataFunc) rhythmdb_query_model_string_sort_func,
207 					  GUINT_TO_POINTER (RHYTHMDB_PROP_LOCATION), NULL, FALSE);
208 	rhythmdb_query_free (query);
209 
210 	/* set up entry view */
211 	source->priv->view = rb_entry_view_new (source->priv->db, shell_player,
212 						FALSE, FALSE);
213 	g_object_unref (shell_player);
214 
215 	rb_entry_view_set_model (source->priv->view, model);
216 
217 	rb_entry_view_append_column (source->priv->view, RB_ENTRY_VIEW_COL_LOCATION, TRUE);
218 	rb_entry_view_append_column (source->priv->view, RB_ENTRY_VIEW_COL_ERROR, TRUE);
219 
220 	g_signal_connect_object (source->priv->view, "show_popup",
221 				 G_CALLBACK (rb_import_errors_source_songs_show_popup_cb), source, 0);
222 
223 	g_object_set (source, "query-model", model, NULL);
224 	g_object_unref (model);
225 
226 	/* set up query model for tracking missing plugin information */
227 	query = rhythmdb_query_parse (source->priv->db,
228 				      RHYTHMDB_QUERY_PROP_EQUALS,
229 				        RHYTHMDB_PROP_TYPE,
230 					entry_type,
231 				      RHYTHMDB_QUERY_PROP_NOT_EQUAL,
232 				        RHYTHMDB_PROP_COMMENT,
233 					"",
234 				      RHYTHMDB_QUERY_END);
235 
236 	source->priv->missing_plugin_model = rhythmdb_query_model_new_empty (source->priv->db);
237 	rhythmdb_do_full_query_async_parsed (source->priv->db,
238 					     RHYTHMDB_QUERY_RESULTS (source->priv->missing_plugin_model),
239 					     query);
240 	rhythmdb_query_free (query);
241 
242 	/* set up info bar for triggering codec installation */
243 	source->priv->infobar = gtk_info_bar_new_with_buttons (_("Install Additional Software"), GTK_RESPONSE_OK, NULL);
244 	g_signal_connect_object (source->priv->infobar,
245 				 "response",
246 				 G_CALLBACK (infobar_response_cb),
247 				 source, 0);
248 
249 	label = gtk_label_new (_("Additional software is required to play some of these files."));
250 	gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
251 	gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->infobar))),
252 			   label);
253 
254 	g_object_unref (entry_type);
255 
256 	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
257 	gtk_box_pack_start (GTK_BOX (box), GTK_WIDGET (source->priv->view), TRUE, TRUE, 0);
258 	gtk_box_pack_start (GTK_BOX (box), source->priv->infobar, FALSE, FALSE, 0);
259 
260 	gtk_container_add (GTK_CONTAINER (source), box);
261 	gtk_widget_show_all (GTK_WIDGET (source));
262 	gtk_widget_hide (source->priv->infobar);
263 
264 	/* show the info bar when there are missing plugin entries */
265 	g_signal_connect_object (source->priv->missing_plugin_model,
266 				 "row-inserted",
267 				 G_CALLBACK (missing_plugin_row_inserted_cb),
268 				 source, 0);
269 	g_signal_connect_object (source->priv->missing_plugin_model,
270 				 "row-deleted",
271 				 G_CALLBACK (missing_plugin_row_deleted_cb),
272 				 source, 0);
273 }
274 
275 static void
276 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
277 {
278 	RBImportErrorsSource *source = RB_IMPORT_ERRORS_SOURCE (object);
279 	switch (prop_id) {
280 	case PROP_NORMAL_ENTRY_TYPE:
281 		g_value_set_object (value, source->priv->normal_entry_type);
282 		break;
283 	case PROP_IGNORE_ENTRY_TYPE:
284 		g_value_set_object (value, source->priv->ignore_entry_type);
285 		break;
286 	default:
287 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
288 		break;
289 	}
290 }
291 
292 static void
293 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
294 {
295 	RBImportErrorsSource *source = RB_IMPORT_ERRORS_SOURCE (object);
296 	switch (prop_id) {
297 	case PROP_NORMAL_ENTRY_TYPE:
298 		source->priv->normal_entry_type = g_value_get_object (value);
299 		break;
300 	case PROP_IGNORE_ENTRY_TYPE:
301 		source->priv->ignore_entry_type = g_value_get_object (value);
302 		break;
303 	default:
304 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
305 		break;
306 	}
307 }
308 
309 static void
310 rb_import_errors_source_dispose (GObject *object)
311 {
312 	RBImportErrorsSource *source = RB_IMPORT_ERRORS_SOURCE (object);
313 
314 	if (source->priv->db) {
315 		g_object_unref (source->priv->db);
316 		source->priv->db = NULL;
317 	}
318 	if (source->priv->missing_plugin_model) {
319 		g_object_unref (source->priv->missing_plugin_model);
320 		source->priv->missing_plugin_model = NULL;
321 	}
322 
323 	G_OBJECT_CLASS (rb_import_errors_source_parent_class)->dispose (object);
324 }
325 
326 static RBEntryView *
327 impl_get_entry_view (RBSource *asource)
328 {
329 	RBImportErrorsSource *source = RB_IMPORT_ERRORS_SOURCE (asource);
330 	return source->priv->view;
331 }
332 
333 /**
334  * rb_import_errors_source_new:
335  * @shell: the #RBShell instance
336  * @entry_type: the entry type to display in the source
337  * @normal_entry_type: entry type for successfully imported entries of this type
338  * @ignore_entry_type: entry type for entries of this type to be ignored
339  *
340  * Creates a new source for displaying import errors of the
341  * specified type.
342  *
343  * Return value: a new import error source
344  */
345 RBSource *
346 rb_import_errors_source_new (RBShell *shell,
347 			     RhythmDBEntryType *entry_type,
348 			     RhythmDBEntryType *normal_entry_type,
349 			     RhythmDBEntryType *ignore_entry_type)
350 {
351 	RBSource *source;
352 
353 	source = RB_SOURCE (g_object_new (RB_TYPE_IMPORT_ERRORS_SOURCE,
354 					  "name", _("Import Errors"),
355 					  "shell", shell,
356 					  "visibility", FALSE,
357 					  "hidden-when-empty", TRUE,
358 					  "entry-type", entry_type,
359 					  "normal-entry-type", normal_entry_type,
360 					  "ignore-entry-type", ignore_entry_type,
361 					  NULL));
362 	return source;
363 }
364 
365 static void
366 impl_delete (RBSource *asource)
367 {
368 	RBImportErrorsSource *source = RB_IMPORT_ERRORS_SOURCE (asource);
369 	GList *sel, *tem;
370 
371 	sel = rb_entry_view_get_selected_entries (source->priv->view);
372 	for (tem = sel; tem != NULL; tem = tem->next) {
373 		rhythmdb_entry_delete (source->priv->db, tem->data);
374 		rhythmdb_commit (source->priv->db);
375 	}
376 
377 	g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
378 	g_list_free (sel);
379 }
380 
381 static void
382 impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress)
383 {
384 	RhythmDBQueryModel *model;
385 	gint count;
386 
387 	g_object_get (page, "query-model", &model, NULL);
388 	count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
389 	g_object_unref (model);
390 
391 	*text = g_strdup_printf (ngettext ("%d import error", "%d import errors", count),
392 				 count);
393 }
394 
395 static void
396 rb_import_errors_source_songs_show_popup_cb (RBEntryView *view,
397 					     gboolean over_entry,
398 					     RBImportErrorsSource *source)
399 {
400 	_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/ImportErrorsViewPopup");
401 }
402 
403 static void
404 missing_plugin_row_inserted_cb (GtkTreeModel *model,
405 				GtkTreePath *path,
406 				GtkTreeIter *iter,
407 				RBImportErrorsSource *source)
408 {
409 	gtk_widget_show (source->priv->infobar);
410 }
411 
412 static void
413 missing_plugin_row_deleted_cb (GtkTreeModel *model,
414 			       GtkTreePath *path,
415 			       RBImportErrorsSource *source)
416 {
417 	/* row hasn't been deleted from the model yet, so the count
418 	 * still includes it.
419 	 */
420 	if (gtk_tree_model_iter_n_children (model, NULL) == 1) {
421 		gtk_widget_hide (source->priv->infobar);
422 	}
423 }
424 
425 static void
426 missing_plugins_retry_cb (gpointer instance, gboolean installed, RBImportErrorsSource *source)
427 {
428 	GtkTreeIter iter;
429 	RhythmDBEntryType *error_entry_type;
430 
431 	gtk_info_bar_set_response_sensitive (GTK_INFO_BAR (source->priv->infobar),
432 					     GTK_RESPONSE_OK,
433 					     TRUE);
434 
435 	if (installed == FALSE) {
436 		rb_debug ("installer failed, not retrying imports");
437 		return;
438 	}
439 
440 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (source->priv->missing_plugin_model), &iter) == FALSE) {
441 		return;
442 	}
443 
444 	g_object_get (source, "entry-type", &error_entry_type, NULL);
445 	do {
446 		RhythmDBEntry *entry;
447 
448 		entry = rhythmdb_query_model_iter_to_entry (source->priv->missing_plugin_model, &iter);
449 		rhythmdb_add_uri_with_types (source->priv->db,
450 					     rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
451 					     source->priv->normal_entry_type,
452 					     source->priv->ignore_entry_type,
453 					     error_entry_type);
454 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->missing_plugin_model), &iter));
455 
456 	g_object_unref (error_entry_type);
457 }
458 
459 static void
460 missing_plugins_retry_cleanup (RBImportErrorsSource *source)
461 {
462 	g_object_unref (source);
463 }
464 
465 static void
466 infobar_response_cb (GtkInfoBar *infobar, gint response, RBImportErrorsSource *source)
467 {
468 	char **details = NULL;
469 	GtkTreeIter iter;
470 	GClosure *closure;
471 	int i;
472 
473 	/* gather plugin installer detail strings */
474 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (source->priv->missing_plugin_model), &iter) == FALSE) {
475 		return;
476 	}
477 
478 	i = 0;
479 	do {
480 		RhythmDBEntry *entry;
481 		char **bits;
482 		int j;
483 
484 		entry = rhythmdb_query_model_iter_to_entry (source->priv->missing_plugin_model, &iter);
485 		bits = g_strsplit (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_COMMENT), "\n", 0);
486 
487 		for (j = 0; bits[j] != NULL; j++) {
488 			if (rb_str_in_strv (bits[j], (const char **)details) == FALSE) {
489 				details = g_realloc (details, sizeof (char *) * i+2);
490 				details[i++] = g_strdup (bits[j]);
491 				details[i] = NULL;
492 			}
493 		}
494 
495 		g_strfreev (bits);
496 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->missing_plugin_model), &iter));
497 
498 	/* run the installer */
499 	closure = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
500 				  g_object_ref (source),
501 				  (GClosureNotify) missing_plugins_retry_cleanup);
502 	g_closure_set_marshal (closure, g_cclosure_marshal_VOID__BOOLEAN);
503 	if (rb_missing_plugins_install ((const char **)details, TRUE, closure) == TRUE) {
504 		/* disable the button while the installer is running */
505 		gtk_info_bar_set_response_sensitive (infobar, response, FALSE);
506 	}
507 	g_closure_sink (closure);
508 
509 	g_strfreev (details);
510 }