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