1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * Libbrasero-misc
4  * Copyright (C) Philippe Rouquier 2005-2009 <bonfire-app@wanadoo.fr>
5  *
6  * Libbrasero-misc is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * The Libbrasero-misc authors hereby grant permission for non-GPL compatible
12  * GStreamer plugins to be used and distributed together with GStreamer
13  * and Libbrasero-misc. This permission is above and beyond the permissions granted
14  * by the GPL license by which Libbrasero-burn is covered. If you modify this code
15  * you may extend this exception to your version of the code, but you are not
16  * obligated to do so. If you do not wish to do so, delete this exception
17  * statement from your version.
18  *
19  * Libbrasero-misc is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to:
26  * 	The Free Software Foundation, Inc.,
27  * 	51 Franklin Street, Fifth Floor
28  * 	Boston, MA  02110-1301, USA.
29  */
30 
31 #ifdef HAVE_CONFIG_H
32 #  include <config.h>
33 #endif
34 
35 #include <string.h>
36 #include <errno.h>
37 
38 #include <glib.h>
39 #include <glib-object.h>
40 #include <glib/gi18n-lib.h>
41 #include <glib/gstdio.h>
42 
43 #include <gio/gio.h>
44 
45 #include <gdk/gdkx.h>
46 
47 #include <gtk/gtk.h>
48 
49 #ifdef BUILD_PLAYLIST
50 #include <totem-pl-parser.h>
51 #endif
52 
53 #include "brasero-misc.h"
54 #include "brasero-io.h"
55 #include "brasero-metadata.h"
56 #include "brasero-async-task-manager.h"
57 
58 #define BRASERO_TYPE_IO             (brasero_io_get_type ())
59 #define BRASERO_IO(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), BRASERO_TYPE_IO, BraseroIO))
60 #define BRASERO_IO_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), BRASERO_TYPE_IO, BraseroIOClass))
61 #define BRASERO_IS_IO(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), BRASERO_TYPE_IO))
62 #define BRASERO_IS_IO_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), BRASERO_TYPE_IO))
63 #define BRASERO_IO_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), BRASERO_TYPE_IO, BraseroIOClass))
64 
65 typedef struct _BraseroIOClass BraseroIOClass;
66 typedef struct _BraseroIO BraseroIO;
67 
68 struct _BraseroIOClass
69 {
70 	BraseroAsyncTaskManagerClass parent_class;
71 };
72 
73 struct _BraseroIO
74 {
75 	BraseroAsyncTaskManager parent_instance;
76 };
77 
78 GType brasero_io_get_type (void) G_GNUC_CONST;
79 
80 typedef struct _BraseroIOPrivate BraseroIOPrivate;
81 struct _BraseroIOPrivate
82 {
83 	GMutex *lock;
84 
85 	GSList *mounted;
86 
87 	/* used for returning results */
88 	GSList *results;
89 	gint results_id;
90 
91 	/* used for metadata */
92 	GMutex *lock_metadata;
93 
94 	GSList *metadatas;
95 	GSList *metadata_running;
96 
97 	/* used to "buffer" some results returned by metadata.
98 	 * It takes time to return metadata and it's not unusual
99 	 * to fetch metadata three times in a row, once for size
100 	 * preview, once for preview, once adding to selection */
101 	GQueue *meta_buffer;
102 
103 	guint progress_id;
104 	GSList *progress;
105 
106 	BraseroIOGetParentWinCb win_callback;
107 	gpointer win_user_data;
108 };
109 
110 #define BRASERO_IO_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), BRASERO_TYPE_IO, BraseroIOPrivate))
111 
112 /* so far 2 metadata at a time has shown to be the best for performance */
113 #define MAX_CONCURENT_META 	2
114 #define MAX_BUFFERED_META	20
115 
116 struct _BraseroIOJobResult {
117 	const BraseroIOJobBase *base;
118 	BraseroIOResultCallbackData *callback_data;
119 
120 	GFileInfo *info;
121 	GError *error;
122 	gchar *uri;
123 };
124 typedef struct _BraseroIOJobResult BraseroIOJobResult;
125 
126 
127 typedef void	(*BraseroIOJobProgressCallback)	(BraseroIOJob *job,
128 						 BraseroIOJobProgress *progress);
129 
130 struct _BraseroIOJobProgress {
131 	BraseroIOJob *job;
132 	BraseroIOJobProgressCallback progress;
133 
134 	BraseroIOPhase phase;
135 
136 	guint files_num;
137 	guint files_invalid;
138 
139 	guint64 read_b;
140 	guint64 total_b;
141 
142 	guint64 current_read_b;
143 	guint64 current_total_b;
144 
145 	gchar *current;
146 };
147 
148 G_DEFINE_TYPE (BraseroIO, brasero_io, BRASERO_TYPE_ASYNC_TASK_MANAGER);
149 
150 static BraseroIO *singleton = NULL;
151 
152 static BraseroIO *
brasero_io_get_default()153 brasero_io_get_default ()
154 {
155 	if (singleton) {
156 		g_object_ref (singleton);
157 		return singleton;
158 	}
159 
160 	singleton = g_object_new (BRASERO_TYPE_IO, NULL);
161 	g_object_ref (singleton);
162 	return singleton;
163 }
164 
165 /**
166  * That's the structure to pass the progress on
167  */
168 
169 static gboolean
brasero_io_job_progress_report_cb(gpointer callback_data)170 brasero_io_job_progress_report_cb (gpointer callback_data)
171 {
172 	BraseroIOPrivate *priv;
173 	GSList *iter;
174 
175 	priv = BRASERO_IO_PRIVATE (callback_data);
176 
177 	g_mutex_lock (priv->lock);
178 	for (iter = priv->progress; iter; iter = iter->next) {
179 		BraseroIOJobProgress *progress;
180 		gpointer callback_data;
181 
182 		progress = iter->data;
183 
184 		callback_data = progress->job->callback_data?
185 				progress->job->callback_data->callback_data:
186 				NULL;
187 
188 		/* update our progress */
189 		progress->progress (progress->job, progress);
190 		progress->job->base->methods->progress (progress->job->base->object,
191 		                                        progress,
192 		                                        callback_data);
193 	}
194 	g_mutex_unlock (priv->lock);
195 
196 	return TRUE;
197 }
198 
199 static void
brasero_io_job_progress_report_start(BraseroIO * self,BraseroIOJob * job,BraseroIOJobProgressCallback callback)200 brasero_io_job_progress_report_start (BraseroIO *self,
201 				      BraseroIOJob *job,
202 				      BraseroIOJobProgressCallback callback)
203 {
204 	BraseroIOJobProgress *progress;
205 	BraseroIOPrivate *priv;
206 
207 	priv = BRASERO_IO_PRIVATE (self);
208 
209 	if (!job->base->methods->progress)
210 		return;
211 
212 	progress = g_new0 (BraseroIOJobProgress, 1);
213 	progress->job = job;
214 	progress->progress = callback;
215 
216 	g_mutex_lock (priv->lock);
217 	priv->progress = g_slist_prepend (priv->progress, progress);
218 	if (!priv->progress_id)
219 		priv->progress_id = g_timeout_add (500, brasero_io_job_progress_report_cb, self);
220 	g_mutex_unlock (priv->lock);
221 }
222 
223 static void
brasero_io_job_progress_report_stop(BraseroIO * self,BraseroIOJob * job)224 brasero_io_job_progress_report_stop (BraseroIO *self,
225 				     BraseroIOJob *job)
226 {
227 	BraseroIOPrivate *priv;
228 	GSList *iter;
229 
230 	priv = BRASERO_IO_PRIVATE (self);
231 	g_mutex_lock (priv->lock);
232 	for (iter = priv->progress; iter; iter = iter->next) {
233 		BraseroIOJobProgress *progress;
234 
235 		progress = iter->data;
236 		if (progress->job == job) {
237 			priv->progress = g_slist_remove (priv->progress, progress);
238 			if (progress->current)
239 				g_free (progress->current);
240 
241 			g_free (progress);
242 			break;
243 		}
244 	}
245 
246 	if (!priv->progress) {
247 		if (priv->progress_id) {
248 			g_source_remove (priv->progress_id);
249 			priv->progress_id = 0;
250 		}
251 	}
252 
253 	g_mutex_unlock (priv->lock);
254 }
255 
256 guint
brasero_io_job_progress_get_file_processed(BraseroIOJobProgress * progress)257 brasero_io_job_progress_get_file_processed (BraseroIOJobProgress *progress)
258 {
259 	return progress->files_num;
260 }
261 
262 guint64
brasero_io_job_progress_get_read(BraseroIOJobProgress * progress)263 brasero_io_job_progress_get_read (BraseroIOJobProgress *progress)
264 {
265 	return progress->current_read_b + progress->read_b;
266 }
267 
268 guint64
brasero_io_job_progress_get_total(BraseroIOJobProgress * progress)269 brasero_io_job_progress_get_total (BraseroIOJobProgress *progress)
270 {
271 	return progress->total_b;
272 }
273 
274 BraseroIOPhase
brasero_io_job_progress_get_phase(BraseroIOJobProgress * progress)275 brasero_io_job_progress_get_phase (BraseroIOJobProgress *progress)
276 {
277 	return progress->phase;
278 }
279 
280 static void
brasero_io_unref_result_callback_data(BraseroIOResultCallbackData * data,GObject * object,BraseroIODestroyCallback destroy,gboolean cancelled)281 brasero_io_unref_result_callback_data (BraseroIOResultCallbackData *data,
282 				       GObject *object,
283 				       BraseroIODestroyCallback destroy,
284 				       gboolean cancelled)
285 {
286 	if (!data)
287 		return;
288 
289 	/* see atomically if we are the last to hold a lock */
290 	if (!g_atomic_int_dec_and_test (&data->ref))
291 		return;
292 
293 	if (destroy)
294 		destroy (object,
295 			 cancelled,
296 			 data->callback_data);
297 	g_free (data);
298 }
299 
300 static void
brasero_io_job_result_free(BraseroIOJobResult * result)301 brasero_io_job_result_free (BraseroIOJobResult *result)
302 {
303 	if (result->info)
304 		g_object_unref (result->info);
305 
306 	if (result->error)
307 		g_error_free (result->error);
308 
309 	if (result->uri)
310 		g_free (result->uri);
311 
312 	g_free (result);
313 }
314 
315 /**
316  * Used to return the results
317  */
318 
319 #define NUMBER_OF_RESULTS	25
320 
321 static gboolean
brasero_io_return_result_idle(gpointer callback_data)322 brasero_io_return_result_idle (gpointer callback_data)
323 {
324 	BraseroIO *self = BRASERO_IO (callback_data);
325 	BraseroIOResultCallbackData *data;
326 	BraseroIOJobResult *result;
327 	BraseroIOPrivate *priv;
328 	guint results_id;
329 	int i;
330 
331 	priv = BRASERO_IO_PRIVATE (self);
332 
333 	g_mutex_lock (priv->lock);
334 
335 	/* Put that to 0 for now so that a new idle call will be scheduled while
336 	 * we are in the loop. That way if we block the other one will be able
337 	 * to deliver results. */
338 	results_id = priv->results_id;
339 	priv->results_id = 0;
340 
341 	/* Return several results at a time that can be a huge speed gain.
342 	 * What should be the value that provides speed and responsiveness? */
343 	for (i = 0; priv->results && i < NUMBER_OF_RESULTS;) {
344 		BraseroIOJobBase *base;
345 		GSList *iter;
346 
347 		/* Find the next result that can be returned */
348 		result = NULL;
349 		for (iter = priv->results; iter; iter = iter->next) {
350 			BraseroIOJobResult *tmp_result;
351 
352 			tmp_result = iter->data;
353 			if (!tmp_result->base->methods->in_use) {
354 				result = tmp_result;
355 				break;
356 			}
357 		}
358 
359 		if (!result)
360 			break;
361 
362 		/* Make sure another result is not returned for this base. This
363 		 * is to avoid BraseroDataDisc showing multiple dialogs for
364 		 * various problems; like one dialog for joliet, one for deep,
365 		 * and one for name collision. */
366 		base = (BraseroIOJobBase *) result->base;
367 		base->methods->in_use = TRUE;
368 
369 		priv->results = g_slist_remove (priv->results, result);
370 
371 		/* This is to make sure the object
372 		 *  lives as long as we need it. */
373 		g_object_ref (base->object);
374 
375 		g_mutex_unlock (priv->lock);
376 
377 		data = result->callback_data;
378 
379 		if (result->uri || result->info || result->error)
380 			base->methods->callback (base->object,
381 			                          result->error,
382 			                          result->uri,
383 			                          result->info,
384 			                          data? data->callback_data:NULL);
385 
386 		/* call destroy () for callback data */
387 		brasero_io_unref_result_callback_data (data,
388 						       base->object,
389 						       base->methods->destroy,
390 						       FALSE);
391 
392 		brasero_io_job_result_free (result);
393 
394 		g_mutex_lock (priv->lock);
395 
396 		i ++;
397 
398 		g_object_unref (base->object);
399 		base->methods->in_use = FALSE;
400 	}
401 
402 	if (!priv->results_id && priv->results && i >= NUMBER_OF_RESULTS) {
403 		/* There are still results and no idle call is scheduled so we
404 		 * have to restart ourselves to make sure we empty the queue */
405 		priv->results_id = results_id;
406 		g_mutex_unlock (priv->lock);
407 		return TRUE;
408 	}
409 
410 	g_mutex_unlock (priv->lock);
411 	return FALSE;
412 }
413 
414 static void
brasero_io_queue_result(BraseroIO * self,BraseroIOJobResult * result)415 brasero_io_queue_result (BraseroIO *self,
416 			 BraseroIOJobResult *result)
417 {
418 	BraseroIOPrivate *priv;
419 
420 	priv = BRASERO_IO_PRIVATE (self);
421 
422 	/* insert the task in the results queue */
423 	g_mutex_lock (priv->lock);
424 	priv->results = g_slist_append (priv->results, result);
425 	if (!priv->results_id)
426 		priv->results_id = g_idle_add ((GSourceFunc) brasero_io_return_result_idle, self);
427 	g_mutex_unlock (priv->lock);
428 }
429 
430 void
brasero_io_return_result(const BraseroIOJobBase * base,const gchar * uri,GFileInfo * info,GError * error,BraseroIOResultCallbackData * callback_data)431 brasero_io_return_result (const BraseroIOJobBase *base,
432 			  const gchar *uri,
433 			  GFileInfo *info,
434 			  GError *error,
435 			  BraseroIOResultCallbackData *callback_data)
436 {
437 	BraseroIO *self = brasero_io_get_default ();
438 	BraseroIOJobResult *result;
439 
440 	/* even if it is cancelled we let the result go through to be able to
441 	 * call its destroy callback in the main thread. */
442 
443 	result = g_new0 (BraseroIOJobResult, 1);
444 	result->base = base;
445 	result->info = info;
446 	result->error = error;
447 	result->uri = g_strdup (uri);
448 
449 	if (callback_data) {
450 		g_atomic_int_inc (&callback_data->ref);
451 		result->callback_data = callback_data;
452 	}
453 
454 	brasero_io_queue_result (self, result);
455 	g_object_unref (self);
456 }
457 
458 /**
459  * Used to push a job
460  */
461 
462 void
brasero_io_set_job(BraseroIOJob * job,const BraseroIOJobBase * base,const gchar * uri,BraseroIOFlags options,BraseroIOResultCallbackData * callback_data)463 brasero_io_set_job (BraseroIOJob *job,
464 		    const BraseroIOJobBase *base,
465 		    const gchar *uri,
466 		    BraseroIOFlags options,
467 		    BraseroIOResultCallbackData *callback_data)
468 {
469 	job->base = base;
470 	job->uri = g_strdup (uri);
471 	job->options = options;
472 	job->callback_data = callback_data;
473 
474 	if (callback_data)
475 		g_atomic_int_inc (&job->callback_data->ref);
476 }
477 
478 void
brasero_io_push_job(BraseroIOJob * job,const BraseroAsyncTaskType * type)479 brasero_io_push_job (BraseroIOJob *job,
480 		     const BraseroAsyncTaskType *type)
481 {
482 	BraseroIO *self = brasero_io_get_default ();
483 
484 	if (job->options & BRASERO_IO_INFO_URGENT)
485 		brasero_async_task_manager_queue (BRASERO_ASYNC_TASK_MANAGER (self),
486 						  BRASERO_ASYNC_URGENT,
487 						  type,
488 						  job);
489 	else if (job->options & BRASERO_IO_INFO_IDLE)
490 		brasero_async_task_manager_queue (BRASERO_ASYNC_TASK_MANAGER (self),
491 						  BRASERO_ASYNC_IDLE,
492 						  type,
493 						  job);
494 	else
495 		brasero_async_task_manager_queue (BRASERO_ASYNC_TASK_MANAGER (self),
496 						  BRASERO_ASYNC_NORMAL,
497 						  type,
498 						  job);
499 	g_object_unref (self);
500 }
501 
502 /**
503  * Job destruction
504  */
505 
506 void
brasero_io_job_free(gboolean cancelled,BraseroIOJob * job)507 brasero_io_job_free (gboolean cancelled,
508 		     BraseroIOJob *job)
509 {
510 	/* NOTE: the callback_data member is never destroyed here since it would
511 	 * be destroyed in a thread different from the main loop.
512 	 * Either it's destroyed in the thread that called brasero_io_cancel ()
513 	 * or after all results are returned (and therefore in main loop).
514 	 * As a special case, some jobs like read directory contents have to
515 	 * return a dummy result to destroy the callback_data if the directory
516 	 * is empty.
517 	 * If the job happens to be the last to carry a reference then destroy
518 	 * it but do it in the main loop by sending a dummy result. */
519 
520 	/* NOTE2: that's also used for directory loading when there aren't any
521 	 * result to notify the caller that the operation has finished but that
522 	 * there weren't any result to report. */
523 	if (job->callback_data) {
524 		/* see atomically if we are the last to hold a lock:
525 		 * If so, and if cancelled is TRUE destroy it now since we are
526 		 * always cancelled (and destroyed in the main loop). Otherwise
527 		 * add a dummy result to destroy callback_data. */
528 		if (g_atomic_int_dec_and_test (&job->callback_data->ref)) {
529 			if (cancelled) {
530 				if (job->base->methods->destroy)
531 					job->base->methods->destroy (job->base->object,
532 					                              TRUE,
533 					                              job->callback_data->callback_data);
534 
535 				g_free (job->callback_data);
536 			}
537 			else {
538 				BraseroIO *self = brasero_io_get_default ();
539 
540 				brasero_io_return_result (job->base,
541 							  NULL,
542 							  NULL,
543 							  NULL,
544 							  job->callback_data);
545 				g_object_unref (self);
546 			}
547 		}
548 	}
549 
550 	g_free (job->uri);
551 	g_free (job);
552 }
553 
554 static void
brasero_io_job_destroy(BraseroAsyncTaskManager * manager,gboolean cancelled,gpointer callback_data)555 brasero_io_job_destroy (BraseroAsyncTaskManager *manager,
556 			gboolean cancelled,
557 			gpointer callback_data)
558 {
559 	BraseroIOJob *job = callback_data;
560 
561 	/* If a job is destroyed we don't call the destroy callback since it
562 	 * otherwise it would be called in a different thread. All object that
563 	 * cancel io ops are doing it either in destroy () and therefore handle
564 	 * all destruction for callback_data or if they don't they usually don't
565 	 * pass any callback data anyway. */
566 	/* NOTE: usually threads are cancelled from the main thread/loop and
567 	 * block until the active task is removed which means that if we called
568 	 * the destroy () then the destruction would be done in the main loop */
569 	brasero_io_job_free (cancelled, job);
570 }
571 
572 /**
573  * That's when we need to mount a remote volume
574  */
575 
576 struct _BraseroIOMount {
577 	GError *error;
578 	gboolean result;
579 	gboolean finished;
580 };
581 typedef struct _BraseroIOMount BraseroIOMount;
582 
583 static void
brasero_io_mount_enclosing_volume_cb(GObject * source,GAsyncResult * result,gpointer callback_data)584 brasero_io_mount_enclosing_volume_cb (GObject *source,
585 				      GAsyncResult *result,
586 				      gpointer callback_data)
587 {
588 	BraseroIOMount *mount = callback_data;
589 
590 	mount->result = g_file_mount_enclosing_volume_finish (G_FILE (source),
591 							      result,
592 							      &mount->error);
593 	mount->finished = TRUE;
594 }
595 
596 static gboolean
brasero_io_mount_enclosing_volume(BraseroIO * self,GFile * file,GCancellable * cancel,GError ** error)597 brasero_io_mount_enclosing_volume (BraseroIO *self,
598 				   GFile *file,
599 				   GCancellable *cancel,
600 				   GError **error)
601 {
602 	GMount *mounted;
603 	GtkWindow *parent;
604 	BraseroIOPrivate *priv;
605 	BraseroIOMount mount = { NULL, };
606 	GMountOperation *operation = NULL;
607 
608 	priv = BRASERO_IO_PRIVATE (self);
609 
610 	if (priv->win_callback) {
611 		parent = priv->win_callback (priv->win_user_data);
612 
613 		if (parent)
614 			operation = gtk_mount_operation_new (parent);
615 	}
616 
617 	g_file_mount_enclosing_volume (file,
618 				       G_MOUNT_MOUNT_NONE,
619 				       operation,
620 				       cancel,
621 				       brasero_io_mount_enclosing_volume_cb,
622 				       &mount);
623 	if (operation)
624 		g_object_unref (operation);
625 
626 	/* sleep and wait operation end */
627 	while (!mount.finished && !g_cancellable_is_cancelled (cancel))
628 		sleep (1);
629 
630 	mounted = g_file_find_enclosing_mount (file, cancel, NULL);
631 	if (mounted) {
632 		BraseroIOPrivate *priv;
633 
634 		priv = BRASERO_IO_PRIVATE (self);
635 
636 		/* Keep these for later to unmount them */
637 		if (mount.result) {
638 			g_mutex_lock (priv->lock);
639 			priv->mounted = g_slist_prepend (priv->mounted, mounted);
640 			g_mutex_unlock (priv->lock);
641 		}
642 		else
643 			g_object_unref (mounted);
644 	}
645 
646 	if (!mounted
647 	&&   mount.error
648 	&&  !g_cancellable_is_cancelled (cancel))
649 		g_propagate_error (error, mount.error);
650 	else if (mount.error)
651 		g_error_free (mount.error);
652 
653 	BRASERO_UTILS_LOG ("Parent volume is %s",
654 			   (mounted != NULL && !g_cancellable_is_cancelled (cancel))?
655 			   "mounted":"not mounted");
656 
657 	return (mounted != NULL && !g_cancellable_is_cancelled (cancel));
658 }
659 
660 /**
661  * This part deals with symlinks, that allows to get unique filenames by
662  * replacing any parent symlink by its target and check for recursive
663  * symlinks
664  */
665 
666 static gchar *
brasero_io_check_for_parent_symlink(const gchar * escaped_uri,GCancellable * cancel)667 brasero_io_check_for_parent_symlink (const gchar *escaped_uri,
668 				     GCancellable *cancel)
669 {
670 	GFile *parent;
671 	GFile *file;
672     	gchar *uri;
673 
674 	/* don't check if the node itself is a symlink since that'll be done */
675 	file = g_file_new_for_uri (escaped_uri);
676     	uri = g_file_get_uri (file);
677 	parent = g_file_get_parent (file);
678 	g_object_unref (file);
679 
680 	while (parent) {
681 	    	GFile *tmp;
682 		GFileInfo *info;
683 
684 		info = g_file_query_info (parent,
685 					  G_FILE_ATTRIBUTE_STANDARD_TYPE ","
686 					  G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
687 					  G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
688 					  G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,	/* don't follow symlinks */
689 					  NULL,
690 					  NULL);
691 		if (!info)
692 		    	break;
693 
694 		/* NOTE: no need to check for broken symlinks since
695 		 * we wouldn't have reached this point otherwise */
696 		if (g_file_info_get_is_symlink (info)) {
697 			const gchar *target_path;
698 		    	gchar *parent_uri;
699 		    	gchar *new_root;
700 			gchar *newuri;
701 
702 		    	parent_uri = g_file_get_uri (parent);
703 			target_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
704 
705 			/* check if this is not a relative path */
706 			 if (!g_path_is_absolute (target_path)) {
707 				gchar *tmp;
708 
709 				tmp = g_path_get_dirname (parent_uri);
710 				new_root = g_build_path (G_DIR_SEPARATOR_S,
711 							 tmp,
712 							 target_path,
713 							 NULL);
714 				g_free (tmp);
715 			}
716 			else
717 				new_root = g_filename_to_uri (target_path, NULL, NULL);
718 
719 			newuri = g_build_path (G_DIR_SEPARATOR_S,
720 					       new_root,
721 					       uri + strlen (parent_uri),
722 					       NULL);
723 		    	g_free (uri);
724 		    	uri = newuri;
725 
726 		    	g_object_unref (parent);
727 		    	g_free (parent_uri);
728 
729 		    	parent = g_file_new_for_uri (new_root);
730 			g_free (new_root);
731 		}
732 
733 		tmp = parent;
734 		parent = g_file_get_parent (parent);
735 		g_object_unref (tmp);
736 
737 		g_object_unref (info);
738 	}
739 
740 	if (parent)
741 		g_object_unref (parent);
742 
743 	return uri;
744 }
745 
746 static gchar *
brasero_io_get_uri_from_path(GFile * file,const gchar * path)747 brasero_io_get_uri_from_path (GFile *file,
748 			      const gchar *path)
749 {
750 	gchar *uri;
751 
752 	if (!g_path_is_absolute (path))
753 		file = g_file_resolve_relative_path (file, path);
754 	else
755 		file = g_file_new_for_path (path);
756 
757 	if (!file)
758 		return NULL;
759 
760 	uri = g_file_get_uri (file);
761 	g_object_unref (file);
762 	return uri;
763 }
764 
765 static gboolean
brasero_io_check_symlink_target(GFile * parent,GFileInfo * info)766 brasero_io_check_symlink_target (GFile *parent,
767 				 GFileInfo *info)
768 {
769 	const gchar *target;
770 	gchar *target_uri;
771 	guint size;
772 	gchar *uri;
773 
774 	target = g_file_info_get_symlink_target (info);
775     	if (!target)
776 		return FALSE;
777 
778 	target_uri = brasero_io_get_uri_from_path (parent, target);
779 	if (!target_uri)
780 		return FALSE;
781 
782 	/* we check for circular dependency here :
783 	 * if the target is one of the parent of symlink */
784 	size = strlen (target_uri);
785 	uri = g_file_get_uri (parent);
786 
787 	if (!strncmp (target_uri, uri, size)
788 	&& (*(uri + size) == '/' || *(uri + size) == '\0')) {
789 		g_free (target_uri);
790 		g_free (uri);
791 		return FALSE;
792 	}
793 	g_free (uri);
794 
795 	g_file_info_set_symlink_target (info, target_uri);
796 	g_free (target_uri);
797 
798 	return TRUE;
799 }
800 
801 /**
802  * Used to retrieve metadata for audio files
803  */
804 
805 struct _BraserIOMetadataTask {
806 	gchar *uri;
807 	GSList *results;
808 };
809 typedef struct _BraseroIOMetadataTask BraseroIOMetadataTask;
810 
811 struct _BraseroIOMetadataCached {
812 	guint64 last_modified;
813 	BraseroMetadataInfo *info;
814 
815 	guint missing_codec_used:1;
816 };
817 typedef struct _BraseroIOMetadataCached BraseroIOMetadataCached;
818 
819 static gint
brasero_io_metadata_lookup_buffer(gconstpointer a,gconstpointer b)820 brasero_io_metadata_lookup_buffer (gconstpointer a, gconstpointer b)
821 {
822 	const BraseroIOMetadataCached *cached = a;
823 	const gchar *uri = b;
824 
825 	return strcmp (uri, cached->info->uri);
826 }
827 
828 static void
brasero_io_metadata_cached_free(BraseroIOMetadataCached * cached)829 brasero_io_metadata_cached_free (BraseroIOMetadataCached *cached)
830 {
831 	brasero_metadata_info_free (cached->info);
832 	g_free (cached);
833 }
834 
835 static void
brasero_io_set_metadata_attributes(GFileInfo * info,BraseroMetadataInfo * metadata)836 brasero_io_set_metadata_attributes (GFileInfo *info,
837 				    BraseroMetadataInfo *metadata)
838 {
839 	g_file_info_set_attribute_uint64 (info, BRASERO_IO_LEN, metadata->len);
840 
841 	if (metadata->type)
842 		g_file_info_set_content_type (info, metadata->type);
843 
844 	if (metadata->artist)
845 		g_file_info_set_attribute_string (info, BRASERO_IO_ARTIST, metadata->artist);
846 
847 	if (metadata->title)
848 		g_file_info_set_attribute_string (info, BRASERO_IO_TITLE, metadata->title);
849 
850 	if (metadata->album)
851 		g_file_info_set_attribute_string (info, BRASERO_IO_ALBUM, metadata->album);
852 
853 	if (metadata->genre)
854 		g_file_info_set_attribute_string (info, BRASERO_IO_GENRE, metadata->genre);
855 
856 	if (metadata->composer)
857 		g_file_info_set_attribute_string (info, BRASERO_IO_COMPOSER, metadata->composer);
858 
859 	if (metadata->isrc)
860 		g_file_info_set_attribute_string (info, BRASERO_IO_ISRC, metadata->isrc);
861 
862 	g_file_info_set_attribute_boolean (info, BRASERO_IO_HAS_AUDIO, metadata->has_audio);
863 	if (metadata->has_audio) {
864 		if (metadata->channels)
865 			g_file_info_set_attribute_int32 (info, BRASERO_IO_CHANNELS, metadata->channels);
866 
867 		if (metadata->rate)
868 			g_file_info_set_attribute_int32 (info, BRASERO_IO_RATE, metadata->rate);
869 
870 		if (metadata->has_dts)
871 			g_file_info_set_attribute_boolean (info, BRASERO_IO_HAS_DTS, TRUE);
872 	}
873 
874 	g_file_info_set_attribute_boolean (info, BRASERO_IO_HAS_VIDEO, metadata->has_video);
875 	g_file_info_set_attribute_boolean (info, BRASERO_IO_IS_SEEKABLE, metadata->is_seekable);
876 	if (metadata->snapshot)
877 		g_file_info_set_attribute_object (info, BRASERO_IO_THUMBNAIL, G_OBJECT (metadata->snapshot));
878 
879 	/* FIXME: what about silences */
880 }
881 
882 static BraseroMetadata *
brasero_io_find_metadata(BraseroIO * self,GCancellable * cancel,const gchar * uri,BraseroMetadataFlag flags,GError ** error)883 brasero_io_find_metadata (BraseroIO *self,
884 			  GCancellable *cancel,
885 			  const gchar *uri,
886 			  BraseroMetadataFlag flags,
887 			  GError **error)
888 {
889 	GSList *iter;
890 	BraseroIOPrivate *priv;
891 	BraseroMetadata *metadata;
892 
893 	priv = BRASERO_IO_PRIVATE (self);
894 
895 	BRASERO_UTILS_LOG ("Retrieving available metadata %s", uri);
896 
897 	/* First see if a metadata is running with the same uri and the same
898 	 * flags as us. In this case, acquire the lock and wait for the lock
899 	 * to become available which will mean that it has finished
900 	 * NOTE: since we will hold the lock another thread waiting to get
901 	 * an available metadata won't be able to have this one. */
902 	for (iter = priv->metadata_running; iter; iter = iter->next) {
903 		const gchar *metadata_uri;
904 		BraseroMetadataFlag metadata_flags;
905 
906 		metadata = iter->data;
907 		metadata_uri = brasero_metadata_get_uri (metadata);
908 		if (!metadata_uri) {
909 			/* It could a metadata that was running but failed to
910 			 * retrieve anything and is waiting to be inserted back
911 			 * in the available list. Ignore it. */
912 			continue;
913 		}
914 
915 		metadata_flags = brasero_metadata_get_flags (metadata);
916 
917 		if (((flags & metadata_flags) == flags)
918 		&&  !strcmp (uri, metadata_uri)) {
919 			/* Found one: release the IO lock to let other threads
920 			 * do what they need to do then lock the metadata lock
921 			 * Let the thread that got the lock first move it back
922 			 * to waiting queue */
923 			BRASERO_UTILS_LOG ("Already ongoing search for %s", uri);
924 			brasero_metadata_increase_listener_number (metadata);
925 			return metadata;
926 		}
927 	}
928 
929 	/* Grab an available metadata (NOTE: there should always be at least one
930 	 * since we run 2 threads at max and have two metadatas available) */
931 	while (!priv->metadatas) {
932 		if (g_cancellable_is_cancelled (cancel))
933 			return NULL;
934 
935 		g_mutex_unlock (priv->lock_metadata);
936 		g_usleep (250);
937 		g_mutex_lock (priv->lock_metadata);
938 	}
939 
940 	/* One metadata is finally available */
941 	metadata = priv->metadatas->data;
942 
943 	/* Try to set it up for running */
944 	if (!brasero_metadata_set_uri (metadata, flags, uri, error))
945 		return NULL;
946 
947 	/* The metadata is ready for running put it in right queue */
948 	brasero_metadata_increase_listener_number (metadata);
949 	priv->metadatas = g_slist_remove (priv->metadatas, metadata);
950 	priv->metadata_running = g_slist_prepend (priv->metadata_running, metadata);
951 
952 	return metadata;
953 }
954 
955 static gboolean
brasero_io_wait_for_metadata(BraseroIO * self,GCancellable * cancel,GFileInfo * info,BraseroMetadata * metadata,BraseroMetadataFlag flags,BraseroMetadataInfo * meta_info)956 brasero_io_wait_for_metadata (BraseroIO *self,
957 			      GCancellable *cancel,
958 			      GFileInfo *info,
959 			      BraseroMetadata *metadata,
960 			      BraseroMetadataFlag flags,
961 			      BraseroMetadataInfo *meta_info)
962 {
963 	gboolean result;
964 	gboolean is_last;
965 	BraseroIOPrivate *priv;
966 
967 	priv = BRASERO_IO_PRIVATE (self);
968 
969 	brasero_metadata_wait (metadata, cancel);
970 	g_mutex_lock (priv->lock_metadata);
971 
972 	is_last = brasero_metadata_decrease_listener_number (metadata);
973 
974 	if (!g_cancellable_is_cancelled (cancel))
975 		result = brasero_metadata_get_result (metadata, meta_info, NULL);
976 	else
977 		result = FALSE;
978 
979 	/* Only the last thread waiting for the result will put metadata back
980 	 * into the available queue and cache the results. */
981 	if (!is_last) {
982 		g_mutex_unlock (priv->lock_metadata);
983 		return result;
984 	}
985 
986 	if (result) {
987 		/* see if we should add it to the buffer */
988 		if (meta_info->has_audio || meta_info->has_video) {
989 			BraseroIOMetadataCached *cached;
990 
991 			cached = g_new0 (BraseroIOMetadataCached, 1);
992 			cached->last_modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
993 
994 			cached->info = g_new0 (BraseroMetadataInfo, 1);
995 			brasero_metadata_get_result (metadata, cached->info, NULL);
996 
997 			cached->missing_codec_used = (flags & BRASERO_METADATA_FLAG_MISSING) != 0;
998 
999 			g_queue_push_head (priv->meta_buffer, cached);
1000 			if (g_queue_get_length (priv->meta_buffer) > MAX_BUFFERED_META) {
1001 				cached = g_queue_pop_tail (priv->meta_buffer);
1002 				brasero_io_metadata_cached_free (cached);
1003 			}
1004 		}
1005 	}
1006 
1007 	/* Make sure it is stopped */
1008 	BRASERO_UTILS_LOG ("Stopping metadata information retrieval (%p)", metadata);
1009 	brasero_metadata_cancel (metadata);
1010 
1011 	priv->metadata_running = g_slist_remove (priv->metadata_running, metadata);
1012 	priv->metadatas = g_slist_append (priv->metadatas, metadata);
1013 
1014 	g_mutex_unlock (priv->lock_metadata);
1015 
1016 	return result;
1017 }
1018 
1019 static gboolean
brasero_io_get_metadata_info(BraseroIO * self,GCancellable * cancel,const gchar * uri,GFileInfo * info,BraseroMetadataFlag flags,BraseroMetadataInfo * meta_info)1020 brasero_io_get_metadata_info (BraseroIO *self,
1021 			      GCancellable *cancel,
1022 			      const gchar *uri,
1023 			      GFileInfo *info,
1024 			      BraseroMetadataFlag flags,
1025 			      BraseroMetadataInfo *meta_info)
1026 {
1027 	BraseroMetadata *metadata = NULL;
1028 	BraseroIOPrivate *priv;
1029 	const gchar *mime;
1030 	GList *node;
1031 
1032 	if (g_cancellable_is_cancelled (cancel))
1033 		return FALSE;
1034 
1035 	priv = BRASERO_IO_PRIVATE (self);
1036 
1037 	mime = g_file_info_get_content_type (info);
1038 	BRASERO_UTILS_LOG ("Found file with type %s", mime);
1039 
1040 	if (mime
1041 	&& (!strncmp (mime, "image/", 6)
1042 	||  !strcmp (mime, "text/plain")
1043 	||  !strcmp (mime, "application/x-cue") /* this one make gstreamer crash */
1044 	||  !strcmp (mime, "application/x-cd-image")))
1045 		return FALSE;
1046 
1047 	BRASERO_UTILS_LOG ("Retrieving metadata info");
1048 	g_mutex_lock (priv->lock_metadata);
1049 
1050 	/* Seek in the buffer if we have already explored these metadata. Check
1051 	 * the info last modified time in case a result should be updated. */
1052 	node = g_queue_find_custom (priv->meta_buffer,
1053 				    uri,
1054 				    brasero_io_metadata_lookup_buffer);
1055 	if (node) {
1056 		guint64 last_modified;
1057 		BraseroIOMetadataCached *cached;
1058 
1059 		cached = node->data;
1060 		last_modified = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
1061 		if (last_modified == cached->last_modified) {
1062 			gboolean refresh_cache = FALSE;
1063 
1064 			if (flags & BRASERO_METADATA_FLAG_MISSING) {
1065 				/* This cached result may indicate an error and
1066 				 * this error could be related to the fact that
1067 				 * it was not first looked for with missing
1068 				 * codec detection. */
1069 				if (!cached->missing_codec_used)
1070 					refresh_cache = TRUE;
1071 			}
1072 
1073 			if (flags & BRASERO_METADATA_FLAG_THUMBNAIL) {
1074 				/* If there isn't any snapshot retry */
1075 				if (!cached->info->snapshot)
1076 					refresh_cache = TRUE;
1077 			}
1078 
1079 			if (!refresh_cache) {
1080 				brasero_metadata_info_copy (meta_info, cached->info);
1081 				g_mutex_unlock (priv->lock_metadata);
1082 				return TRUE;
1083 			}
1084 		}
1085 
1086 		/* Found the same URI but it didn't have all required flags so
1087 		 * we'll get another metadata information; Remove it from the
1088 		 * queue => no same URI twice */
1089 		g_queue_remove (priv->meta_buffer, cached);
1090 		brasero_io_metadata_cached_free (cached);
1091 
1092 		BRASERO_UTILS_LOG ("Updating cache information for %s", uri);
1093 	}
1094 
1095 	/* Find a metadata */
1096 	metadata = brasero_io_find_metadata (self, cancel, uri, flags, NULL);
1097 	g_mutex_unlock (priv->lock_metadata);
1098 
1099 	if (!metadata)
1100 		return FALSE;
1101 
1102 	return brasero_io_wait_for_metadata (self,
1103 					     cancel,
1104 					     info,
1105 					     metadata,
1106 					     flags,
1107 					     meta_info);
1108 }
1109 
1110 /**
1111  * Used to get information about files
1112  */
1113 
1114 static GFileInfo *
brasero_io_get_file_info_thread_real(BraseroAsyncTaskManager * manager,GCancellable * cancel,GFile * file,BraseroIOFlags options,GError ** error)1115 brasero_io_get_file_info_thread_real (BraseroAsyncTaskManager *manager,
1116 				      GCancellable *cancel,
1117 				      GFile *file,
1118 				      BraseroIOFlags options,
1119 				      GError **error)
1120 {
1121 	gchar attributes [256] = {G_FILE_ATTRIBUTE_STANDARD_NAME ","
1122 				  G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1123 				  G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
1124 				  G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET ","
1125 				  G_FILE_ATTRIBUTE_STANDARD_TYPE};
1126 	GError *local_error = NULL;
1127 	gboolean should_thumbnail;
1128 	GFileInfo *info;
1129 
1130 	if (g_cancellable_is_cancelled (cancel))
1131 		return NULL;
1132 
1133 	if (options & BRASERO_IO_INFO_PERM)
1134 		strcat (attributes, "," G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
1135 	if (options & BRASERO_IO_INFO_MIME)
1136 		strcat (attributes, "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
1137 	if (options & BRASERO_IO_INFO_ICON)
1138 		strcat (attributes, "," G_FILE_ATTRIBUTE_STANDARD_ICON);
1139 	if (options & BRASERO_IO_INFO_METADATA_THUMBNAIL)
1140 		strcat (attributes, "," G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
1141 
1142 	/* if retrieving metadata we need this one to check if a possible result
1143 	 * in cache should be updated or used */
1144 	if (options & BRASERO_IO_INFO_METADATA)
1145 		strcat (attributes, "," G_FILE_ATTRIBUTE_STANDARD_SIZE);
1146 
1147 	info = g_file_query_info (file,
1148 				  attributes,
1149 				  (options & BRASERO_IO_INFO_FOLLOW_SYMLINK)?G_FILE_QUERY_INFO_NONE:G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,	/* follow symlinks by default*/
1150 				  cancel,
1151 				  &local_error);
1152 	if (!info) {
1153 		if (local_error && local_error->code == G_IO_ERROR_NOT_MOUNTED) {
1154 			gboolean res;
1155 
1156 			BRASERO_UTILS_LOG ("Starting to mount parent volume");
1157 			g_error_free (local_error);
1158 			local_error = NULL;
1159 
1160 			/* try to mount whatever has to be mounted */
1161 			/* NOTE: of course, we block a thread but one advantage
1162 			 * is that we won't have many queries to mount the same
1163 			 * remote volume at the same time. */
1164 			res = brasero_io_mount_enclosing_volume (BRASERO_IO (manager),
1165 								 file,
1166 								 cancel,
1167 								 error);
1168 			if (!res)
1169 				return NULL;
1170 
1171 			return brasero_io_get_file_info_thread_real (manager,
1172 								     cancel,
1173 								     file,
1174 								     options,
1175 								     error);
1176 		}
1177 
1178 		g_propagate_error (error, local_error);
1179 		return NULL;
1180 	}
1181 
1182 	if (g_file_info_get_is_symlink (info)) {
1183 		GFile *parent;
1184 
1185 		parent = g_file_get_parent (file);
1186 		if (!brasero_io_check_symlink_target (parent, info)) {
1187 			g_set_error (error,
1188 				     BRASERO_UTILS_ERROR,
1189 				     BRASERO_UTILS_ERROR_SYMLINK_LOOP,
1190 				     _("Recursive symbolic link"));
1191 
1192 			g_object_unref (info);
1193 			g_object_unref (file);
1194 			g_object_unref (parent);
1195 			return NULL;
1196 		}
1197 		g_object_unref (parent);
1198 	}
1199 
1200 	should_thumbnail = FALSE;
1201 	if (options & BRASERO_IO_INFO_METADATA_THUMBNAIL) {
1202 		const gchar *path;
1203 
1204 		path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
1205 		if (path) {
1206 			GdkPixbuf *pixbuf;
1207 
1208 			pixbuf = gdk_pixbuf_new_from_file (path, NULL);
1209 			if (pixbuf) {
1210 				g_file_info_set_attribute_object (info,
1211 								  BRASERO_IO_THUMBNAIL,
1212 								  G_OBJECT (pixbuf));
1213 				g_object_unref (pixbuf);
1214 			}
1215 			else
1216 				should_thumbnail = TRUE;
1217 		}
1218 		else if (!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED))
1219 			should_thumbnail = TRUE;
1220 	}
1221 
1222 	/* see if we are supposed to get metadata for this file (provided it's
1223 	 * an audio file of course). */
1224 	if ((g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR
1225 	||   g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK)
1226 	&&  (options & BRASERO_IO_INFO_METADATA)) {
1227 		BraseroMetadataInfo metadata = { NULL };
1228 		BraseroMetadataFlag flags;
1229 		gboolean result;
1230 		gchar *uri;
1231 
1232 		flags = ((options & BRASERO_IO_INFO_METADATA_MISSING_CODEC) ? BRASERO_METADATA_FLAG_MISSING : 0)|
1233 			((should_thumbnail) ? BRASERO_METADATA_FLAG_THUMBNAIL : 0);
1234 
1235 		uri = g_file_get_uri (file);
1236 		result = brasero_io_get_metadata_info (BRASERO_IO (manager),
1237 						       cancel,
1238 						       uri,
1239 						       info,
1240 						       flags,
1241 						       &metadata);
1242 		g_free (uri);
1243 
1244 		if (result)
1245 			brasero_io_set_metadata_attributes (info, &metadata);
1246 
1247 		brasero_metadata_info_clear (&metadata);
1248 	}
1249 
1250 	return info;
1251 }
1252 
1253 static BraseroAsyncTaskResult
brasero_io_get_file_info_thread(BraseroAsyncTaskManager * manager,GCancellable * cancel,gpointer callback_data)1254 brasero_io_get_file_info_thread (BraseroAsyncTaskManager *manager,
1255 				 GCancellable *cancel,
1256 				 gpointer callback_data)
1257 {
1258 	BraseroIOJob *job = callback_data;
1259 	gchar *file_uri = NULL;
1260 	GError *error = NULL;
1261 	GFileInfo *info;
1262 	GFile *file;
1263 
1264 	if (job->options & BRASERO_IO_INFO_CHECK_PARENT_SYMLINK) {
1265 		/* If we want to make sure a directory is not added twice we have to make sure
1266 		 * that it doesn't have a symlink as parent otherwise "/home/Foo/Bar" with Foo
1267 		 * as a symlink pointing to /tmp would be seen as a different file from /tmp/Bar
1268 		 * It would be much better if we could use the inode numbers provided by gnome_vfs
1269 		 * unfortunately they are guint64 and can't be used in hash tables as keys.
1270 		 * Therefore we check parents up to root to see if there are symlinks and if so
1271 		 * we get a path without symlinks in it. This is done only for local file */
1272 		file_uri = brasero_io_check_for_parent_symlink (job->uri, cancel);
1273 	}
1274 
1275 	if (g_cancellable_is_cancelled (cancel)) {
1276 		g_free (file_uri);
1277 		return BRASERO_ASYNC_TASK_FINISHED;
1278 	}
1279 
1280 	file = g_file_new_for_uri (file_uri?file_uri:job->uri);
1281 	info = brasero_io_get_file_info_thread_real (manager,
1282 						     cancel,
1283 						     file,
1284 						     job->options,
1285 						     &error);
1286 
1287 	/* do this to have a very nice URI:
1288 	 * for example: file://pouet instead of file://../directory/pouet */
1289 	g_free (file_uri);
1290 	file_uri = g_file_get_uri (file);
1291 	g_object_unref (file);
1292 
1293 	brasero_io_return_result (job->base,
1294 				  file_uri,
1295 				  info,
1296 				  error,
1297 				  job->callback_data);
1298 
1299 	g_free (file_uri);
1300 	return BRASERO_ASYNC_TASK_FINISHED;
1301 }
1302 
1303 static const BraseroAsyncTaskType info_type = {
1304 	brasero_io_get_file_info_thread,
1305 	brasero_io_job_destroy
1306 };
1307 
1308 static void
brasero_io_new_file_info_job(const gchar * uri,const BraseroIOJobBase * base,BraseroIOFlags options,BraseroIOResultCallbackData * callback_data)1309 brasero_io_new_file_info_job (const gchar *uri,
1310 			      const BraseroIOJobBase *base,
1311 			      BraseroIOFlags options,
1312 			      BraseroIOResultCallbackData *callback_data)
1313 {
1314 	BraseroIOJob *job;
1315 
1316 	job = g_new0 (BraseroIOJob, 1);
1317 	brasero_io_set_job (job,
1318 			    base,
1319 			    uri,
1320 			    options,
1321 			    callback_data);
1322 
1323 	brasero_io_push_job (job, &info_type);
1324 }
1325 
1326 void
brasero_io_get_file_info(const gchar * uri,const BraseroIOJobBase * base,BraseroIOFlags options,gpointer user_data)1327 brasero_io_get_file_info (const gchar *uri,
1328 			  const BraseroIOJobBase *base,
1329 			  BraseroIOFlags options,
1330 			  gpointer user_data)
1331 {
1332 	BraseroIOResultCallbackData *callback_data = NULL;
1333 	BraseroIO *self = brasero_io_get_default ();
1334 
1335 	if (user_data) {
1336 		callback_data = g_new0 (BraseroIOResultCallbackData, 1);
1337 		callback_data->callback_data = user_data;
1338 	}
1339 
1340 	brasero_io_new_file_info_job (uri, base, options, callback_data);
1341 	g_object_unref (self);
1342 }
1343 
1344 /**
1345  * Used to parse playlists
1346  */
1347 
1348 #ifdef BUILD_PLAYLIST
1349 
1350 struct _BraseroIOPlaylist {
1351 	gchar *title;
1352 	GSList *uris;
1353 };
1354 typedef struct _BraseroIOPlaylist BraseroIOPlaylist;
1355 
1356 static void
brasero_io_playlist_clear(BraseroIOPlaylist * data)1357 brasero_io_playlist_clear (BraseroIOPlaylist *data)
1358 {
1359 	g_slist_foreach (data->uris, (GFunc) g_free, NULL);
1360 	g_slist_free (data->uris);
1361 
1362 	g_free (data->title);
1363 }
1364 
1365 static void
brasero_io_add_playlist_entry_parsed_cb(TotemPlParser * parser,const gchar * uri,GHashTable * metadata,BraseroIOPlaylist * data)1366 brasero_io_add_playlist_entry_parsed_cb (TotemPlParser *parser,
1367 					 const gchar *uri,
1368 					 GHashTable *metadata,
1369 					 BraseroIOPlaylist *data)
1370 {
1371 	data->uris = g_slist_prepend (data->uris, g_strdup (uri));
1372 }
1373 
1374 static void
brasero_io_start_playlist_cb(TotemPlParser * parser,const gchar * uri,GHashTable * metadata,BraseroIOPlaylist * data)1375 brasero_io_start_playlist_cb (TotemPlParser *parser,
1376 				  const gchar *uri,
1377 				  GHashTable *metadata,
1378 				  BraseroIOPlaylist *data)
1379 {
1380 	const gchar *title;
1381 
1382 	title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE);
1383 	if (!title)
1384 		return;
1385 
1386 	if (!data->title)
1387 		data->title = g_strdup (title);
1388 }
1389 
1390 static gboolean
brasero_io_parse_playlist_get_uris(const gchar * uri,BraseroIOPlaylist * playlist,GError ** error)1391 brasero_io_parse_playlist_get_uris (const gchar *uri,
1392 				    BraseroIOPlaylist *playlist,
1393 				    GError **error)
1394 {
1395 	gboolean result;
1396 	TotemPlParser *parser;
1397 
1398 	parser = totem_pl_parser_new ();
1399 	g_signal_connect (parser,
1400 			  "playlist-started",
1401 			  G_CALLBACK (brasero_io_start_playlist_cb),
1402 			  playlist);
1403 	g_signal_connect (parser,
1404 			  "entry-parsed",
1405 			  G_CALLBACK (brasero_io_add_playlist_entry_parsed_cb),
1406 			  playlist);
1407 
1408 	if (g_object_class_find_property (G_OBJECT_GET_CLASS (parser), "recurse"))
1409 		g_object_set (G_OBJECT (parser), "recurse", FALSE, NULL);
1410 
1411 	result = totem_pl_parser_parse (parser, uri, TRUE);
1412 	g_object_unref (parser);
1413 
1414 	if (!result) {
1415 		g_set_error (error,
1416 			     BRASERO_UTILS_ERROR,
1417 			     BRASERO_UTILS_ERROR_GENERAL,
1418 			     _("The file does not appear to be a playlist"));
1419 
1420 		return FALSE;
1421 	}
1422 
1423 	return TRUE;
1424 }
1425 
1426 static BraseroAsyncTaskResult
brasero_io_parse_playlist_thread(BraseroAsyncTaskManager * manager,GCancellable * cancel,gpointer callback_data)1427 brasero_io_parse_playlist_thread (BraseroAsyncTaskManager *manager,
1428 				  GCancellable *cancel,
1429 				  gpointer callback_data)
1430 {
1431 	GSList *iter;
1432 	gboolean result;
1433 	GFileInfo *info;
1434 	GError *error = NULL;
1435 	BraseroIOJob *job = callback_data;
1436 	BraseroIOPlaylist data = { NULL, };
1437 
1438 	result = brasero_io_parse_playlist_get_uris (job->uri, &data, &error);
1439 	if (!result) {
1440 		brasero_io_return_result (job->base,
1441 					  job->uri,
1442 					  NULL,
1443 					  error,
1444 					  job->callback_data);
1445 		return BRASERO_ASYNC_TASK_FINISHED;
1446 	}
1447 
1448 	if (g_cancellable_is_cancelled (cancel))
1449 		return BRASERO_ASYNC_TASK_FINISHED;
1450 
1451 	/* that's finished; Send the title */
1452 	info = g_file_info_new ();
1453 	g_file_info_set_attribute_boolean (info,
1454 					   BRASERO_IO_IS_PLAYLIST,
1455 					   TRUE);
1456 	g_file_info_set_attribute_uint32 (info,
1457 					  BRASERO_IO_PLAYLIST_ENTRIES_NUM,
1458 					  g_slist_length (data.uris));
1459 	if (data.title)
1460 		g_file_info_set_attribute_string (info,
1461 						  BRASERO_IO_PLAYLIST_TITLE,
1462 						  data.title);
1463 
1464 	brasero_io_return_result (job->base,
1465 				  job->uri,
1466 				  info,
1467 				  NULL,
1468 				  job->callback_data);
1469 
1470 	/* Now get information about each file in the list.
1471 	 * Reverse order of list to get a correct order for entries. */
1472 	data.uris = g_slist_reverse (data.uris);
1473 	for (iter = data.uris; iter; iter = iter->next) {
1474 		GFile *file;
1475 		gchar *child;
1476 		GFileInfo *child_info;
1477 
1478 		child = iter->data;
1479 		if (g_cancellable_is_cancelled (cancel))
1480 			break;
1481 
1482 		file = g_file_new_for_uri (child);
1483 		child_info = brasero_io_get_file_info_thread_real (manager,
1484 								   cancel,
1485 								   file,
1486 								   job->options,
1487 								   NULL);
1488 		g_object_unref (file);
1489 
1490 		if (!child_info)
1491 			continue;
1492 
1493 		brasero_io_return_result (job->base,
1494 					  child,
1495 					  child_info,
1496 					  NULL,
1497 					  job->callback_data);
1498 	}
1499 
1500 	brasero_io_playlist_clear (&data);
1501 	return BRASERO_ASYNC_TASK_FINISHED;
1502 }
1503 
1504 static const BraseroAsyncTaskType playlist_type = {
1505 	brasero_io_parse_playlist_thread,
1506 	brasero_io_job_destroy
1507 };
1508 
1509 void
brasero_io_parse_playlist(const gchar * uri,const BraseroIOJobBase * base,BraseroIOFlags options,gpointer user_data)1510 brasero_io_parse_playlist (const gchar *uri,
1511 			   const BraseroIOJobBase *base,
1512 			   BraseroIOFlags options,
1513 			   gpointer user_data)
1514 {
1515 	BraseroIOJob *job;
1516 	BraseroIO *self = brasero_io_get_default ();
1517 	BraseroIOResultCallbackData *callback_data = NULL;
1518 
1519 	if (user_data) {
1520 		callback_data = g_new0 (BraseroIOResultCallbackData, 1);
1521 		callback_data->callback_data = user_data;
1522 	}
1523 
1524 	job = g_new0 (BraseroIOJob, 1);
1525 	brasero_io_set_job (job,
1526 			    base,
1527 			    uri,
1528 			    options,
1529 			    callback_data);
1530 
1531 	brasero_io_push_job (job, &playlist_type);
1532 	g_object_unref (self);
1533 }
1534 
1535 #endif
1536 
1537 /**
1538  * Used to count the number of files under a directory and the children size
1539  */
1540 
1541 struct _BraseroIOCountData {
1542 	BraseroIOJob job;
1543 
1544 	GSList *uris;
1545 	GSList *children;
1546 
1547 	guint files_num;
1548 	guint files_invalid;
1549 
1550 	guint64 total_b;
1551 	gboolean progress_started;
1552 };
1553 typedef struct _BraseroIOCountData BraseroIOCountData;
1554 
1555 static void
brasero_io_get_file_count_destroy(BraseroAsyncTaskManager * manager,gboolean cancelled,gpointer callback_data)1556 brasero_io_get_file_count_destroy (BraseroAsyncTaskManager *manager,
1557 				   gboolean cancelled,
1558 				   gpointer callback_data)
1559 {
1560 	BraseroIOCountData *data = callback_data;
1561 
1562 	g_slist_foreach (data->uris, (GFunc) g_free, NULL);
1563 	g_slist_free (data->uris);
1564 
1565 	g_slist_foreach (data->children, (GFunc) g_object_unref, NULL);
1566 	g_slist_free (data->children);
1567 
1568 	brasero_io_job_progress_report_stop (BRASERO_IO (manager), callback_data);
1569 
1570 	brasero_io_job_free (cancelled, callback_data);
1571 }
1572 
1573 #ifdef BUILD_PLAYLIST
1574 
1575 static gboolean
brasero_io_get_file_count_process_playlist(BraseroIO * self,GCancellable * cancel,BraseroIOCountData * data,const gchar * uri)1576 brasero_io_get_file_count_process_playlist (BraseroIO *self,
1577 					    GCancellable *cancel,
1578 					    BraseroIOCountData *data,
1579 					    const gchar *uri)
1580 {
1581 	BraseroIOPlaylist playlist = {NULL, };
1582 	GSList *iter;
1583 
1584 	if (!brasero_io_parse_playlist_get_uris (uri, &playlist, NULL))
1585 		return FALSE;
1586 
1587 	for (iter = playlist.uris; iter; iter = iter->next) {
1588 		gboolean result;
1589 		GFileInfo *info;
1590 		gchar *child_uri;
1591 		BraseroMetadataInfo metadata = { NULL, };
1592 
1593 		child_uri = iter->data;
1594 		data->files_num ++;
1595 
1596 		info = g_file_info_new ();
1597 		result = brasero_io_get_metadata_info (self,
1598 						       cancel,
1599 						       child_uri,
1600 						       info,
1601 						       ((data->job.options & BRASERO_IO_INFO_METADATA_MISSING_CODEC) ? BRASERO_METADATA_FLAG_MISSING : 0) |
1602 						       ((data->job.options & BRASERO_IO_INFO_METADATA_THUMBNAIL) ? BRASERO_METADATA_FLAG_THUMBNAIL : 0),
1603 						       &metadata);
1604 
1605 		if (result)
1606 			data->total_b += metadata.len;
1607 		else
1608 			data->files_invalid ++;
1609 
1610 		brasero_metadata_info_clear (&metadata);
1611 		g_object_unref (info);
1612 	}
1613 
1614 	brasero_io_playlist_clear (&playlist);
1615 	return TRUE;
1616 }
1617 
1618 #endif
1619 
1620 static void
brasero_io_get_file_count_process_file(BraseroIO * self,GCancellable * cancel,BraseroIOCountData * data,GFile * file,GFileInfo * info)1621 brasero_io_get_file_count_process_file (BraseroIO *self,
1622 					GCancellable *cancel,
1623 					BraseroIOCountData *data,
1624 					GFile *file,
1625 					GFileInfo *info)
1626 {
1627 	if (data->job.options & BRASERO_IO_INFO_METADATA) {
1628 		BraseroMetadataInfo metadata = { NULL, };
1629 		gboolean result = FALSE;
1630 		gchar *child_uri;
1631 
1632 		child_uri = g_file_get_uri (file);
1633 		result = brasero_io_get_metadata_info (self,
1634 						       cancel,
1635 						       child_uri,
1636 						       info,
1637 						       ((data->job.options & BRASERO_IO_INFO_METADATA_MISSING_CODEC) ? BRASERO_METADATA_FLAG_MISSING : 0) |
1638 						       ((data->job.options & BRASERO_IO_INFO_METADATA_THUMBNAIL) ? BRASERO_METADATA_FLAG_THUMBNAIL : 0),
1639 						       &metadata);
1640 		if (result)
1641 			data->total_b += metadata.len;
1642 
1643 #ifdef BUILD_PLAYLIST
1644 
1645 		/* see if that's a playlist (and if we have recursive on). */
1646 		else if (data->job.options & BRASERO_IO_INFO_RECURSIVE) {
1647 			const gchar *mime;
1648 
1649 			mime = g_file_info_get_content_type (info);
1650 			if (mime
1651 			&& (!strcmp (mime, "audio/x-scpls")
1652 			||  !strcmp (mime, "audio/x-ms-asx")
1653 			||  !strcmp (mime, "audio/x-mp3-playlist")
1654 			||  !strcmp (mime, "audio/x-mpegurl"))) {
1655 				if (!brasero_io_get_file_count_process_playlist (self, cancel, data, child_uri))
1656 					data->files_invalid ++;
1657 			}
1658 			else
1659 				data->files_invalid ++;
1660 		}
1661 
1662 #endif
1663 
1664 		else
1665 			data->files_invalid ++;
1666 
1667 		brasero_metadata_info_clear (&metadata);
1668 		g_free (child_uri);
1669 		return;
1670 	}
1671 
1672 	data->total_b += g_file_info_get_size (info);
1673 }
1674 
1675 static void
brasero_io_get_file_count_process_directory(BraseroIO * self,GCancellable * cancel,BraseroIOCountData * data)1676 brasero_io_get_file_count_process_directory (BraseroIO *self,
1677 					     GCancellable *cancel,
1678 					     BraseroIOCountData *data)
1679 {
1680 	GFile *file;
1681 	GFileInfo *info;
1682 	GError *error = NULL;
1683 	GFileEnumerator *enumerator;
1684 	gchar attributes [512] = {G_FILE_ATTRIBUTE_STANDARD_NAME ","
1685 				  G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1686 				  G_FILE_ATTRIBUTE_STANDARD_TYPE };
1687 
1688 	if ((data->job.options & BRASERO_IO_INFO_METADATA)
1689 	&&  (data->job.options & BRASERO_IO_INFO_RECURSIVE))
1690 		strcat (attributes, "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
1691 
1692 	file = data->children->data;
1693 	data->children = g_slist_remove (data->children, file);
1694 
1695 	enumerator = g_file_enumerate_children (file,
1696 						attributes,
1697 						(data->job.options & BRASERO_IO_INFO_FOLLOW_SYMLINK)?G_FILE_QUERY_INFO_NONE:G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,	/* follow symlinks by default*/
1698 						cancel,
1699 						NULL);
1700 	if (!enumerator) {
1701 		g_object_unref (file);
1702 		return;
1703 	}
1704 
1705 	while ((info = g_file_enumerator_next_file (enumerator, cancel, &error)) || error) {
1706 		GFile *child;
1707 
1708 		if (g_cancellable_is_cancelled (cancel)) {
1709 			g_object_unref (info);
1710 			break;
1711 		}
1712 
1713 		data->files_num ++;
1714 
1715 		if (error) {
1716 			g_error_free (error);
1717 			error = NULL;
1718 
1719 			data->files_invalid ++;
1720 			continue;
1721 		}
1722 
1723 		child = g_file_get_child (file, g_file_info_get_name (info));
1724 
1725 		if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR
1726 		||  g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK) {
1727 			brasero_io_get_file_count_process_file (self, cancel, data, child, info);
1728 			g_object_unref (child);
1729 		}
1730 		else if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
1731 			data->children = g_slist_prepend (data->children, child);
1732 		else
1733 			g_object_unref (child);
1734 
1735 		g_object_unref (info);
1736 	}
1737 
1738 	g_file_enumerator_close (enumerator, cancel, NULL);
1739 	g_object_unref (enumerator);
1740 	g_object_unref (file);
1741 }
1742 
1743 static gboolean
brasero_io_get_file_count_start(BraseroIO * self,GCancellable * cancel,BraseroIOCountData * data,const gchar * uri)1744 brasero_io_get_file_count_start (BraseroIO *self,
1745 				 GCancellable *cancel,
1746 				 BraseroIOCountData *data,
1747 				 const gchar *uri)
1748 {
1749 	GFile *file;
1750 	GFileInfo *info;
1751 	gchar attributes [512] = {G_FILE_ATTRIBUTE_STANDARD_NAME ","
1752 				  G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1753 				  G_FILE_ATTRIBUTE_STANDARD_TYPE };
1754 
1755 	if ((data->job.options & BRASERO_IO_INFO_METADATA)
1756 	&&  (data->job.options & BRASERO_IO_INFO_RECURSIVE))
1757 		strcat (attributes, "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
1758 
1759 	file = g_file_new_for_uri (uri);
1760 	info = g_file_query_info (file,
1761 				  attributes,
1762 				  (data->job.options & BRASERO_IO_INFO_FOLLOW_SYMLINK)?G_FILE_QUERY_INFO_NONE:G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,	/* follow symlinks by default*/
1763 				  cancel,
1764 				  NULL);
1765 	data->files_num ++;
1766 
1767 	if (!info) {
1768 		g_object_unref (file);
1769 		data->files_invalid ++;
1770 		return FALSE;
1771 	}
1772 
1773 	if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR
1774 	||  g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK) {
1775 		brasero_io_get_file_count_process_file (self, cancel, data, file, info);
1776 		g_object_unref (file);
1777 	}
1778 	else if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
1779 		if (data->job.options & BRASERO_IO_INFO_RECURSIVE)
1780 			data->children = g_slist_prepend (data->children, file);
1781 		else
1782 			g_object_unref (file);
1783 	}
1784 	else
1785 		g_object_unref (file);
1786 
1787 	g_object_unref (info);
1788 	return TRUE;
1789 }
1790 
1791 static void
brasero_io_get_file_count_progress_cb(BraseroIOJob * job,BraseroIOJobProgress * progress)1792 brasero_io_get_file_count_progress_cb (BraseroIOJob *job,
1793 				       BraseroIOJobProgress *progress)
1794 {
1795 	BraseroIOCountData *data = (BraseroIOCountData *) job;
1796 
1797 	progress->read_b = data->total_b;
1798 	progress->total_b = data->total_b;
1799 	progress->files_num = data->files_num;
1800 	progress->files_invalid = data->files_invalid;
1801 }
1802 
1803 static BraseroAsyncTaskResult
brasero_io_get_file_count_thread(BraseroAsyncTaskManager * manager,GCancellable * cancel,gpointer callback_data)1804 brasero_io_get_file_count_thread (BraseroAsyncTaskManager *manager,
1805 				  GCancellable *cancel,
1806 				  gpointer callback_data)
1807 {
1808 	BraseroIOCountData *data = callback_data;
1809 	GFileInfo *info;
1810 	gchar *uri;
1811 
1812 	if (data->children) {
1813 		brasero_io_get_file_count_process_directory (BRASERO_IO (manager), cancel, data);
1814 		return BRASERO_ASYNC_TASK_RESCHEDULE;
1815 	}
1816 
1817 	if (!data->uris) {
1818 		info = g_file_info_new ();
1819 
1820 		/* set GFileInfo information */
1821 		g_file_info_set_attribute_uint32 (info, BRASERO_IO_COUNT_INVALID, data->files_invalid);
1822 		g_file_info_set_attribute_uint64 (info, BRASERO_IO_COUNT_SIZE, data->total_b);
1823 		g_file_info_set_attribute_uint32 (info, BRASERO_IO_COUNT_NUM, data->files_num);
1824 
1825 		brasero_io_return_result (data->job.base,
1826 					  NULL,
1827 					  info,
1828 					  NULL,
1829 					  data->job.callback_data);
1830 
1831 		return BRASERO_ASYNC_TASK_FINISHED;
1832 	}
1833 
1834 	if (!data->progress_started) {
1835 		brasero_io_job_progress_report_start (BRASERO_IO (manager),
1836 						      &data->job,
1837 						      brasero_io_get_file_count_progress_cb);
1838 		data->progress_started = 1;
1839 	}
1840 
1841 	uri = data->uris->data;
1842 	data->uris = g_slist_remove (data->uris, uri);
1843 
1844 	brasero_io_get_file_count_start (BRASERO_IO (manager), cancel, data, uri);
1845 	g_free (uri);
1846 
1847 	return BRASERO_ASYNC_TASK_RESCHEDULE;
1848 }
1849 
1850 static const BraseroAsyncTaskType count_type = {
1851 	brasero_io_get_file_count_thread,
1852 	brasero_io_get_file_count_destroy
1853 };
1854 
1855 void
brasero_io_get_file_count(GSList * uris,const BraseroIOJobBase * base,BraseroIOFlags options,gpointer user_data)1856 brasero_io_get_file_count (GSList *uris,
1857 			   const BraseroIOJobBase *base,
1858 			   BraseroIOFlags options,
1859 			   gpointer user_data)
1860 {
1861 	BraseroIOCountData *data;
1862 	BraseroIO *self = brasero_io_get_default ();
1863 	BraseroIOResultCallbackData *callback_data = NULL;
1864 
1865 	if (user_data) {
1866 		callback_data = g_new0 (BraseroIOResultCallbackData, 1);
1867 		callback_data->callback_data = user_data;
1868 	}
1869 
1870 	data = g_new0 (BraseroIOCountData, 1);
1871 
1872 	for (; uris; uris = uris->next)
1873 		data->uris = g_slist_prepend (data->uris, g_strdup (uris->data));
1874 
1875 	brasero_io_set_job (BRASERO_IO_JOB (data),
1876 			    base,
1877 			    NULL,
1878 			    options,
1879 			    callback_data);
1880 
1881 	brasero_io_push_job (BRASERO_IO_JOB (data), &count_type);
1882 	g_object_unref (self);
1883 }
1884 
1885 /**
1886  * Used to explore directories
1887  */
1888 
1889 struct _BraseroIOContentsData {
1890 	BraseroIOJob job;
1891 	GSList *children;
1892 };
1893 typedef struct _BraseroIOContentsData BraseroIOContentsData;
1894 
1895 static void
brasero_io_load_directory_destroy(BraseroAsyncTaskManager * manager,gboolean cancelled,gpointer callback_data)1896 brasero_io_load_directory_destroy (BraseroAsyncTaskManager *manager,
1897 				   gboolean cancelled,
1898 				   gpointer callback_data)
1899 {
1900 	BraseroIOContentsData *data = callback_data;
1901 
1902 	g_slist_foreach (data->children, (GFunc) g_object_unref, NULL);
1903 	g_slist_free (data->children);
1904 
1905 	brasero_io_job_free (cancelled, BRASERO_IO_JOB (data));
1906 }
1907 
1908 #ifdef BUILD_PLAYLIST
1909 
1910 static gboolean
brasero_io_load_directory_playlist(BraseroIO * self,GCancellable * cancel,BraseroIOContentsData * data,const gchar * uri,const gchar * attributes)1911 brasero_io_load_directory_playlist (BraseroIO *self,
1912 				    GCancellable *cancel,
1913 				    BraseroIOContentsData *data,
1914 				    const gchar *uri,
1915 				    const gchar *attributes)
1916 {
1917 	BraseroIOPlaylist playlist = {NULL, };
1918 	GSList *iter;
1919 
1920 	if (!brasero_io_parse_playlist_get_uris (uri, &playlist, NULL))
1921 		return FALSE;
1922 
1923 	for (iter = playlist.uris; iter; iter = iter->next) {
1924 		GFile *file;
1925 		gboolean result;
1926 		GFileInfo *info;
1927 		gchar *child_uri;
1928 		BraseroMetadataInfo metadata = { NULL, };
1929 
1930 		child_uri = iter->data;
1931 
1932 		file = g_file_new_for_uri (child_uri);
1933 		info = g_file_query_info (file,
1934 					  attributes,
1935 					  G_FILE_QUERY_INFO_NONE,		/* follow symlinks */
1936 					  cancel,
1937 					  NULL);
1938 		if (!info) {
1939 			g_object_unref (file);
1940 			continue;
1941 		}
1942 
1943 		result = brasero_io_get_metadata_info (self,
1944 						       cancel,
1945 						       child_uri,
1946 						       info,
1947 						       ((data->job.options & BRASERO_IO_INFO_METADATA_MISSING_CODEC) ? BRASERO_METADATA_FLAG_MISSING : 0) |
1948 						       ((data->job.options & BRASERO_IO_INFO_METADATA_THUMBNAIL) ? BRASERO_METADATA_FLAG_THUMBNAIL : 0),
1949 						       &metadata);
1950 
1951 		if (result) {
1952 			brasero_io_set_metadata_attributes (info, &metadata);
1953 			brasero_io_return_result (data->job.base,
1954 						  child_uri,
1955 						  info,
1956 						  NULL,
1957 						  data->job.callback_data);
1958 		}
1959 		else
1960 			g_object_unref (info);
1961 
1962 		brasero_metadata_info_clear (&metadata);
1963 
1964 		g_object_unref (file);
1965 	}
1966 
1967 	brasero_io_playlist_clear (&playlist);
1968 	return TRUE;
1969 }
1970 
1971 #endif
1972 
1973 static BraseroAsyncTaskResult
brasero_io_load_directory_thread(BraseroAsyncTaskManager * manager,GCancellable * cancel,gpointer callback_data)1974 brasero_io_load_directory_thread (BraseroAsyncTaskManager *manager,
1975 				  GCancellable *cancel,
1976 				  gpointer callback_data)
1977 {
1978 	gchar attributes [512] = {G_FILE_ATTRIBUTE_STANDARD_NAME ","
1979 				  G_FILE_ATTRIBUTE_STANDARD_SIZE ","
1980 				  G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
1981 				  G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET ","
1982 				  G_FILE_ATTRIBUTE_STANDARD_TYPE };
1983 	BraseroIOContentsData *data = callback_data;
1984 	GFileEnumerator *enumerator;
1985 	GError *error = NULL;
1986 	GFileInfo *info;
1987 	GFile *file;
1988 
1989 	if (data->job.options & BRASERO_IO_INFO_PERM)
1990 		strcat (attributes, "," G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
1991 
1992 	if (data->job.options & BRASERO_IO_INFO_MIME)
1993 		strcat (attributes, "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
1994 	else if ((data->job.options & BRASERO_IO_INFO_METADATA)
1995 	     &&  (data->job.options & BRASERO_IO_INFO_RECURSIVE))
1996 		strcat (attributes, "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
1997 
1998 	if (data->job.options & BRASERO_IO_INFO_ICON)
1999 		strcat (attributes, "," G_FILE_ATTRIBUTE_STANDARD_ICON);
2000 
2001 	if (data->children) {
2002 		file = data->children->data;
2003 		data->children = g_slist_remove (data->children, file);
2004 	}
2005 	else
2006 		file = g_file_new_for_uri (data->job.uri);
2007 
2008 	enumerator = g_file_enumerate_children (file,
2009 						attributes,
2010 						(data->job.options & BRASERO_IO_INFO_FOLLOW_SYMLINK)?G_FILE_QUERY_INFO_NONE:G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,	/* follow symlinks by default*/
2011 						cancel,
2012 						&error);
2013 
2014 	if (!enumerator) {
2015 		gchar *directory_uri;
2016 
2017 		directory_uri = g_file_get_uri (file);
2018 		brasero_io_return_result (data->job.base,
2019 					  directory_uri,
2020 					  NULL,
2021 					  error,
2022 					  data->job.callback_data);
2023 		g_free (directory_uri);
2024 		g_object_unref (file);
2025 
2026 		if (data->children)
2027 			return BRASERO_ASYNC_TASK_RESCHEDULE;
2028 
2029 		return BRASERO_ASYNC_TASK_FINISHED;
2030 	}
2031 
2032 	while ((info = g_file_enumerator_next_file (enumerator, cancel, NULL))) {
2033 		const gchar *name;
2034 		gchar *child_uri;
2035 		GFile *child;
2036 
2037 		name = g_file_info_get_name (info);
2038 		if (g_cancellable_is_cancelled (cancel)) {
2039 			g_object_unref (info);
2040 			break;
2041 		}
2042 
2043 		if (name [0] == '.'
2044 		&& (name [1] == '\0'
2045 		|| (name [1] == '.' && name [2] == '\0'))) {
2046 			g_object_unref (info);
2047 			continue;
2048 		}
2049 
2050 		child = g_file_get_child (file, name);
2051 		if (!child)
2052 			continue;
2053 
2054 		child_uri = g_file_get_uri (child);
2055 
2056 		/* special case for symlinks */
2057 		if (g_file_info_get_is_symlink (info)) {
2058 			if (!brasero_io_check_symlink_target (file, info)) {
2059 				error = g_error_new (BRASERO_UTILS_ERROR,
2060 						     BRASERO_UTILS_ERROR_SYMLINK_LOOP,
2061 						     _("Recursive symbolic link"));
2062 
2063 				/* since we checked for the existence of the file
2064 				 * an error means a looping symbolic link */
2065 				brasero_io_return_result (data->job.base,
2066 							  child_uri,
2067 							  NULL,
2068 							  error,
2069 							  data->job.callback_data);
2070 
2071 				g_free (child_uri);
2072 				g_object_unref (info);
2073 				g_object_unref (child);
2074 				continue;
2075 			}
2076 		}
2077 
2078 		if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
2079 			brasero_io_return_result (data->job.base,
2080 						  child_uri,
2081 						  info,
2082 						  NULL,
2083 						  data->job.callback_data);
2084 
2085 			if (data->job.options & BRASERO_IO_INFO_RECURSIVE)
2086 				data->children = g_slist_prepend (data->children, child);
2087 			else
2088 				g_object_unref (child);
2089 
2090 			g_free (child_uri);
2091 			continue;
2092 		}
2093 
2094 		if (data->job.options & BRASERO_IO_INFO_METADATA) {
2095 			BraseroMetadataInfo metadata = {NULL, };
2096 			gboolean result;
2097 
2098 			/* add metadata information to this file */
2099 			result = brasero_io_get_metadata_info (BRASERO_IO (manager),
2100 							       cancel,
2101 							       child_uri,
2102 							       info,
2103 							       ((data->job.options & BRASERO_IO_INFO_METADATA_MISSING_CODEC) ? BRASERO_METADATA_FLAG_MISSING : 0) |
2104 							       ((data->job.options & BRASERO_IO_INFO_METADATA_THUMBNAIL) ? BRASERO_METADATA_FLAG_THUMBNAIL : 0),
2105 							       &metadata);
2106 
2107 			if (result)
2108 				brasero_io_set_metadata_attributes (info, &metadata);
2109 
2110 #ifdef BUILD_PLAYLIST
2111 
2112 			else if (data->job.options & BRASERO_IO_INFO_RECURSIVE) {
2113 				const gchar *mime;
2114 
2115 				mime = g_file_info_get_content_type (info);
2116 				if (mime
2117 				&& (!strcmp (mime, "audio/x-scpls")
2118 				||  !strcmp (mime, "audio/x-ms-asx")
2119 				||  !strcmp (mime, "audio/x-mp3-playlist")
2120 				||  !strcmp (mime, "audio/x-mpegurl")))
2121 					brasero_io_load_directory_playlist (BRASERO_IO (manager),
2122 									    cancel,
2123 									    data,
2124 									    child_uri,
2125 									    attributes);
2126 			}
2127 
2128 #endif
2129 
2130 			brasero_metadata_info_clear (&metadata);
2131 		}
2132 
2133 		brasero_io_return_result (data->job.base,
2134 					  child_uri,
2135 					  info,
2136 					  NULL,
2137 					  data->job.callback_data);
2138 		g_free (child_uri);
2139 		g_object_unref (child);
2140 	}
2141 
2142 	g_file_enumerator_close (enumerator, NULL, NULL);
2143 	g_object_unref (enumerator);
2144 	g_object_unref (file);
2145 
2146 	if (data->children)
2147 		return BRASERO_ASYNC_TASK_RESCHEDULE;
2148 
2149 	return BRASERO_ASYNC_TASK_FINISHED;
2150 }
2151 
2152 static const BraseroAsyncTaskType contents_type = {
2153 	brasero_io_load_directory_thread,
2154 	brasero_io_load_directory_destroy
2155 };
2156 
2157 void
brasero_io_load_directory(const gchar * uri,const BraseroIOJobBase * base,BraseroIOFlags options,gpointer user_data)2158 brasero_io_load_directory (const gchar *uri,
2159 			   const BraseroIOJobBase *base,
2160 			   BraseroIOFlags options,
2161 			   gpointer user_data)
2162 {
2163 	BraseroIOContentsData *data;
2164 	BraseroIO *self = brasero_io_get_default ();
2165 	BraseroIOResultCallbackData *callback_data = NULL;
2166 
2167 	if (user_data) {
2168 		callback_data = g_new0 (BraseroIOResultCallbackData, 1);
2169 		callback_data->callback_data = user_data;
2170 	}
2171 
2172 	data = g_new0 (BraseroIOContentsData, 1);
2173 	brasero_io_set_job (BRASERO_IO_JOB (data),
2174 			    base,
2175 			    uri,
2176 			    options,
2177 			    callback_data);
2178 
2179 	brasero_io_push_job (BRASERO_IO_JOB (data), &contents_type);
2180 	g_object_unref (self);
2181 }
2182 
2183 static void
brasero_io_cancel_result(BraseroIO * self,BraseroIOJobResult * result)2184 brasero_io_cancel_result (BraseroIO *self,
2185 			  BraseroIOJobResult *result)
2186 {
2187 	BraseroIOResultCallbackData *data;
2188 	BraseroIOPrivate *priv;
2189 
2190 	priv = BRASERO_IO_PRIVATE (self);
2191 
2192 	g_mutex_lock (priv->lock);
2193 	priv->results = g_slist_remove (priv->results, result);
2194 	g_mutex_unlock (priv->lock);
2195 
2196 	data = result->callback_data;
2197 	brasero_io_unref_result_callback_data (data,
2198 					       result->base->object,
2199 					       result->base->methods->destroy,
2200 					       TRUE);
2201 	brasero_io_job_result_free (result);
2202 }
2203 
2204 static gboolean
brasero_io_cancel_tasks_by_base_cb(BraseroAsyncTaskManager * manager,gpointer callback_data,gpointer user_data)2205 brasero_io_cancel_tasks_by_base_cb (BraseroAsyncTaskManager *manager,
2206 				    gpointer callback_data,
2207 				    gpointer user_data)
2208 {
2209 	BraseroIOJob *job = callback_data;
2210 	BraseroIOJobBase *base = user_data;
2211 
2212 	if (job->base != base)
2213 		return FALSE;
2214 
2215 	return TRUE;
2216 }
2217 
2218 void
brasero_io_cancel_by_base(BraseroIOJobBase * base)2219 brasero_io_cancel_by_base (BraseroIOJobBase *base)
2220 {
2221 	GSList *iter;
2222 	GSList *next;
2223 	BraseroIOPrivate *priv;
2224 	BraseroIO *self = brasero_io_get_default ();
2225 
2226 	priv = BRASERO_IO_PRIVATE (self);
2227 
2228 	brasero_async_task_manager_foreach_unprocessed_remove (BRASERO_ASYNC_TASK_MANAGER (self),
2229 							       brasero_io_cancel_tasks_by_base_cb,
2230 							       base);
2231 
2232 	brasero_async_task_manager_foreach_active_remove (BRASERO_ASYNC_TASK_MANAGER (self),
2233 							  brasero_io_cancel_tasks_by_base_cb,
2234 							  base);
2235 
2236 	/* do it afterwards in case some results slipped through */
2237 	for (iter = priv->results; iter; iter = next) {
2238 		BraseroIOJobResult *result;
2239 
2240 		result = iter->data;
2241 		next = iter->next;
2242 
2243 		if (result->base != base)
2244 			continue;
2245 
2246 		brasero_io_cancel_result (self, result);
2247 	}
2248 
2249 	g_object_unref (self);
2250 }
2251 
2252 struct _BraseroIOJobCompareData {
2253 	BraseroIOCompareCallback func;
2254 	const BraseroIOJobBase *base;
2255 	gpointer user_data;
2256 };
2257 typedef struct _BraseroIOJobCompareData BraseroIOJobCompareData;
2258 
2259 static gboolean
brasero_io_compare_unprocessed_task(BraseroAsyncTaskManager * manager,gpointer task,gpointer callback_data)2260 brasero_io_compare_unprocessed_task (BraseroAsyncTaskManager *manager,
2261 				     gpointer task,
2262 				     gpointer callback_data)
2263 {
2264 	BraseroIOJob *job = task;
2265 	BraseroIOJobCompareData *data = callback_data;
2266 
2267 	if (job->base == data->base)
2268 		return FALSE;
2269 
2270 	if (!job->callback_data)
2271 		return FALSE;
2272 
2273 	return data->func (job->callback_data->callback_data, data->user_data);
2274 }
2275 
2276 void
brasero_io_find_urgent(const BraseroIOJobBase * base,BraseroIOCompareCallback callback,gpointer user_data)2277 brasero_io_find_urgent (const BraseroIOJobBase *base,
2278 			BraseroIOCompareCallback callback,
2279 			gpointer user_data)
2280 {
2281 	BraseroIOJobCompareData callback_data;
2282 	BraseroIO *self = brasero_io_get_default ();
2283 
2284 	callback_data.func = callback;
2285 	callback_data.base = base;
2286 	callback_data.user_data = user_data;
2287 
2288 	brasero_async_task_manager_find_urgent_task (BRASERO_ASYNC_TASK_MANAGER (self),
2289 						     brasero_io_compare_unprocessed_task,
2290 						     &callback_data);
2291 	g_object_unref (self);
2292 
2293 }
2294 
2295 BraseroIOJobCallbacks *
brasero_io_register_job_methods(BraseroIOResultCallback callback,BraseroIODestroyCallback destroy,BraseroIOProgressCallback progress)2296 brasero_io_register_job_methods (BraseroIOResultCallback callback,
2297                                  BraseroIODestroyCallback destroy,
2298                                  BraseroIOProgressCallback progress)
2299 {
2300 	BraseroIOJobCallbacks *methods;
2301 
2302 	methods = g_new0 (BraseroIOJobCallbacks, 1);
2303 	methods->callback = callback;
2304 	methods->destroy = destroy;
2305 	methods->progress = progress;
2306 
2307 	return methods;
2308 }
2309 
2310 BraseroIOJobBase *
brasero_io_register_with_methods(GObject * object,BraseroIOJobCallbacks * methods)2311 brasero_io_register_with_methods (GObject *object,
2312                                   BraseroIOJobCallbacks *methods)
2313 {
2314 	BraseroIOJobBase *base;
2315 
2316 	base = g_new0 (BraseroIOJobBase, 1);
2317 	base->object = object;
2318 	base->methods = methods;
2319 	methods->ref ++;
2320 
2321 	return base;
2322 }
2323 
2324 BraseroIOJobBase *
brasero_io_register(GObject * object,BraseroIOResultCallback callback,BraseroIODestroyCallback destroy,BraseroIOProgressCallback progress)2325 brasero_io_register (GObject *object,
2326 		     BraseroIOResultCallback callback,
2327 		     BraseroIODestroyCallback destroy,
2328 		     BraseroIOProgressCallback progress)
2329 {
2330 	return brasero_io_register_with_methods (object, brasero_io_register_job_methods (callback, destroy, progress));
2331 }
2332 
2333 void
brasero_io_job_base_free(BraseroIOJobBase * base)2334 brasero_io_job_base_free (BraseroIOJobBase *base)
2335 {
2336 	BraseroIOJobCallbacks *methods;
2337 
2338 	if (!base)
2339 		return;
2340 
2341 	methods = base->methods;
2342 	g_free (base);
2343 
2344 	methods->ref --;
2345 	if (methods->ref <= 0)
2346 		g_free (methods);
2347 }
2348 
2349 static int
brasero_io_xid_for_metadata(gpointer user_data)2350  brasero_io_xid_for_metadata (gpointer user_data)
2351 {
2352 	BraseroIOPrivate *priv;
2353 
2354 	priv = BRASERO_IO_PRIVATE (user_data);
2355 	if (priv->win_callback) {
2356 		int xid;
2357 		GtkWindow *parent;
2358 
2359 		parent = priv->win_callback (priv->win_user_data);
2360 		xid = gdk_x11_window_get_xid (gtk_widget_get_window(GTK_WIDGET (parent)));
2361 		return xid;
2362 	}
2363 
2364 	return 0;
2365 }
2366 
2367 static void
brasero_io_init(BraseroIO * object)2368 brasero_io_init (BraseroIO *object)
2369 {
2370 	BraseroIOPrivate *priv;
2371 	BraseroMetadata *metadata;
2372 	priv = BRASERO_IO_PRIVATE (object);
2373 
2374 	priv->lock = g_mutex_new ();
2375 	priv->lock_metadata = g_mutex_new ();
2376 
2377 	priv->meta_buffer = g_queue_new ();
2378 
2379 	/* create metadatas now since it doesn't work well when it's created in
2380 	 * a thread. */
2381 	metadata = brasero_metadata_new ();
2382 	priv->metadatas = g_slist_prepend (priv->metadatas, metadata);
2383 	brasero_metadata_set_get_xid_callback (metadata, brasero_io_xid_for_metadata, object);
2384 	metadata = brasero_metadata_new ();
2385 	priv->metadatas = g_slist_prepend (priv->metadatas, metadata);
2386 	brasero_metadata_set_get_xid_callback (metadata, brasero_io_xid_for_metadata, object);
2387 }
2388 
2389 static gboolean
brasero_io_free_async_queue(BraseroAsyncTaskManager * manager,gpointer callback_data,gpointer NULL_data)2390 brasero_io_free_async_queue (BraseroAsyncTaskManager *manager,
2391 			     gpointer callback_data,
2392 			     gpointer NULL_data)
2393 {
2394 	/* don't do anything here, the async task manager
2395 	 * will destroy the job anyway.
2396 	 */
2397 	return TRUE;
2398 }
2399 
2400 static void
brasero_io_finalize(GObject * object)2401 brasero_io_finalize (GObject *object)
2402 {
2403 	BraseroIOPrivate *priv;
2404 	GSList *iter;
2405 
2406 	priv = BRASERO_IO_PRIVATE (object);
2407 
2408 	brasero_async_task_manager_foreach_unprocessed_remove (BRASERO_ASYNC_TASK_MANAGER (object),
2409 							       brasero_io_free_async_queue,
2410 							       NULL);
2411 
2412 	brasero_async_task_manager_foreach_active_remove (BRASERO_ASYNC_TASK_MANAGER (object),
2413 							  brasero_io_free_async_queue,
2414 							  NULL);
2415 
2416 	g_slist_foreach (priv->metadatas, (GFunc) g_object_unref, NULL);
2417 	g_slist_free (priv->metadatas);
2418 	priv->metadatas = NULL;
2419 
2420 	if (priv->meta_buffer) {
2421 		BraseroIOMetadataCached *cached;
2422 
2423 		while ((cached = g_queue_pop_head (priv->meta_buffer)) != NULL)
2424 			brasero_io_metadata_cached_free (cached);
2425 
2426 		g_queue_free (priv->meta_buffer);
2427 		priv->meta_buffer = NULL;
2428 	}
2429 
2430 	if (priv->results_id) {
2431 		g_source_remove (priv->results_id);
2432 		priv->results_id = 0;
2433 	}
2434 
2435 	for (iter = priv->results; iter; iter = iter->next) {
2436 		BraseroIOJobResult *result;
2437 
2438 		result = iter->data;
2439 		brasero_io_job_result_free (result);
2440 	}
2441 	g_slist_free (priv->results);
2442 	priv->results = NULL;
2443 
2444 	if (priv->progress_id) {
2445 		g_source_remove (priv->progress_id);
2446 		priv->progress_id = 0;
2447 	}
2448 
2449 	if (priv->progress) {
2450 		g_slist_foreach (priv->progress, (GFunc) g_free, NULL);
2451 		g_slist_free (priv->progress);
2452 		priv->progress = NULL;
2453 	}
2454 
2455 	if (priv->lock) {
2456 		g_mutex_free (priv->lock);
2457 		priv->lock = NULL;
2458 	}
2459 
2460 	if (priv->lock_metadata) {
2461 		g_mutex_free (priv->lock_metadata);
2462 		priv->lock_metadata = NULL;
2463 	}
2464 
2465 	if (priv->mounted) {
2466 		GSList *iter;
2467 
2468 		/* unmount all volumes we mounted ourselves */
2469 		for (iter = priv->mounted; iter; iter = iter->next) {
2470 			GMount *mount;
2471 
2472 			mount = iter->data;
2473 
2474 			BRASERO_UTILS_LOG ("Unmountin volume");
2475 			g_mount_unmount_with_operation (mount,
2476 					 		G_MOUNT_UNMOUNT_NONE,
2477 							NULL,
2478 					 		NULL,
2479 					 		NULL,
2480 					 		NULL);
2481 			g_object_unref (mount);
2482 		}
2483 	}
2484 
2485 	G_OBJECT_CLASS (brasero_io_parent_class)->finalize (object);
2486 }
2487 
2488 static void
brasero_io_class_init(BraseroIOClass * klass)2489 brasero_io_class_init (BraseroIOClass *klass)
2490 {
2491 	GObjectClass* object_class = G_OBJECT_CLASS (klass);
2492 
2493 	g_type_class_add_private (klass, sizeof (BraseroIOPrivate));
2494 
2495 	object_class->finalize = brasero_io_finalize;
2496 }
2497 
2498 static gboolean
brasero_io_cancel(BraseroAsyncTaskManager * manager,gpointer callback_data,gpointer user_data)2499 brasero_io_cancel (BraseroAsyncTaskManager *manager,
2500                    gpointer callback_data,
2501                    gpointer user_data)
2502 {
2503 	return TRUE;
2504 }
2505 
2506 void
brasero_io_shutdown(void)2507 brasero_io_shutdown (void)
2508 {
2509 	GSList *iter, *next;
2510 	BraseroIOPrivate *priv;
2511 
2512 	priv = BRASERO_IO_PRIVATE (singleton);
2513 
2514 	brasero_async_task_manager_foreach_unprocessed_remove (BRASERO_ASYNC_TASK_MANAGER (singleton),
2515 							       brasero_io_cancel,
2516 							       NULL);
2517 
2518 	brasero_async_task_manager_foreach_active_remove (BRASERO_ASYNC_TASK_MANAGER (singleton),
2519 							  brasero_io_cancel,
2520 							  NULL);
2521 
2522 	/* do it afterwards in case some results slipped through */
2523 	for (iter = priv->results; iter; iter = next) {
2524 		BraseroIOJobResult *result;
2525 
2526 		result = iter->data;
2527 		next = iter->next;
2528 		brasero_io_cancel_result (singleton, result);
2529 	}
2530 
2531 	if (singleton) {
2532 		g_object_unref (singleton);
2533 		singleton = NULL;
2534 	}
2535 }
2536 
2537 void
brasero_io_set_parent_window_callback(BraseroIOGetParentWinCb callback,gpointer user_data)2538 brasero_io_set_parent_window_callback (BraseroIOGetParentWinCb callback,
2539                                        gpointer user_data)
2540 {
2541 	BraseroIOPrivate *priv;
2542 	BraseroIO *self;
2543 
2544 	self = brasero_io_get_default ();
2545 	priv = BRASERO_IO_PRIVATE (self);
2546 	priv->win_callback = callback;
2547 	priv->win_user_data = user_data;
2548 	g_object_unref (self);
2549 }
2550