No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2007 Jonathan Matthew <jonathan@d14n.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include "rhythmdb-import-job.h"
32 #include "rhythmdb-entry-type.h"
33 #include "rb-util.h"
34 #include "rb-file-helpers.h"
35 #include "rb-marshal.h"
36 #include "rb-debug.h"
37 #include "rb-missing-plugins.h"
38
39 /* maximum number of new URIs in the rhythmdb action queue.
40 * entries bounce around between different threads and processes a bit,
41 * so having multiple in flight should help. we also want to be able to
42 * cancel import jobs quickly. since we can't remove things from the
43 * action queue, having fewer entries helps.
44 */
45 #define PROCESSING_LIMIT 20
46
47 enum
48 {
49 PROP_0,
50 PROP_DB,
51 PROP_ENTRY_TYPE,
52 PROP_IGNORE_TYPE,
53 PROP_ERROR_TYPE
54 };
55
56 enum
57 {
58 ENTRY_ADDED,
59 STATUS_CHANGED,
60 SCAN_COMPLETE,
61 COMPLETE,
62 LAST_SIGNAL
63 };
64
65 static void rhythmdb_import_job_class_init (RhythmDBImportJobClass *klass);
66 static void rhythmdb_import_job_init (RhythmDBImportJob *job);
67
68 static guint signals[LAST_SIGNAL] = { 0 };
69
70 struct _RhythmDBImportJobPrivate
71 {
72 int total;
73 int imported;
74 int processed;
75 GQueue *outstanding;
76 GQueue *processing;
77 RhythmDB *db;
78 RhythmDBEntryType *entry_type;
79 RhythmDBEntryType *ignore_type;
80 RhythmDBEntryType *error_type;
81 GMutex lock;
82 GSList *uri_list;
83 gboolean started;
84 GCancellable *cancel;
85
86 GSList *retry_entries;
87 gboolean retried;
88
89 int status_changed_id;
90 gboolean scan_complete;
91 gboolean complete;
92 };
93
94 G_DEFINE_TYPE (RhythmDBImportJob, rhythmdb_import_job, G_TYPE_OBJECT)
95
96 /**
97 * SECTION:rhythmdb-import-job
98 * @short_description: batch import job
99 *
100 * Tracks the addition to the database of files under a set of
101 * directories, providing status information.
102 *
103 * The entry types to use for the database entries added by the import
104 * job are specified on creation.
105 */
106
107 /**
108 * rhythmdb_import_job_new:
109 * @db: the #RhythmDB object
110 * @entry_type: the #RhythmDBEntryType to use for normal entries
111 * @ignore_type: the #RhythmDBEntryType to use for ignored files
112 * (or NULL to not create ignore entries)
113 * @error_type: the #RhythmDBEntryType to use for import error
114 * entries (or NULL for none)
115 *
116 * Creates a new import job with the specified entry types.
117 * Before starting the job, the caller must add one or more
118 * paths to import.
119 *
120 * Return value: new #RhythmDBImportJob object.
121 */
122 RhythmDBImportJob *
123 rhythmdb_import_job_new (RhythmDB *db,
124 RhythmDBEntryType *entry_type,
125 RhythmDBEntryType *ignore_type,
126 RhythmDBEntryType *error_type)
127 {
128 GObject *obj;
129
130 obj = g_object_new (RHYTHMDB_TYPE_IMPORT_JOB,
131 "db", db,
132 "entry-type", entry_type,
133 "ignore-type", ignore_type,
134 "error-type", error_type,
135 NULL);
136 return RHYTHMDB_IMPORT_JOB (obj);
137 }
138
139 /**
140 * rhythmdb_import_job_add_uri:
141 * @job: a #RhythmDBImportJob
142 * @uri: the URI to import
143 *
144 * Adds a URI to import. All files under the specified
145 * URI will be imported.
146 */
147 void
148 rhythmdb_import_job_add_uri (RhythmDBImportJob *job, const char *uri)
149 {
150 g_assert (job->priv->started == FALSE);
151
152 g_mutex_lock (&job->priv->lock);
153 job->priv->uri_list = g_slist_prepend (job->priv->uri_list, g_strdup (uri));
154 g_mutex_unlock (&job->priv->lock);
155 }
156
157 /* must be called with lock held */
158 static void
159 maybe_start_more (RhythmDBImportJob *job)
160 {
161 if (g_cancellable_is_cancelled (job->priv->cancel)) {
162 return;
163 }
164
165 while (g_queue_get_length (job->priv->processing) < PROCESSING_LIMIT) {
166 char *uri;
167
168 uri = g_queue_pop_head (job->priv->outstanding);
169 if (uri == NULL) {
170 return;
171 }
172
173 g_queue_push_tail (job->priv->processing, uri);
174
175 rhythmdb_add_uri_with_types (job->priv->db,
176 uri,
177 job->priv->entry_type,
178 job->priv->ignore_type,
179 job->priv->error_type);
180 }
181 }
182
183 static void
184 missing_plugins_retry_cb (gpointer instance, gboolean installed, RhythmDBImportJob *job)
185 {
186 GSList *i;
187
188 g_mutex_lock (&job->priv->lock);
189 g_assert (job->priv->retried == FALSE);
190 if (installed == FALSE) {
191 rb_debug ("plugin installation was not successful; job complete");
192 g_signal_emit (job, signals[COMPLETE], 0, job->priv->total);
193 } else {
194 job->priv->retried = TRUE;
195
196 /* reset the job state to just show the retry information */
197 job->priv->total = g_slist_length (job->priv->retry_entries);
198 rb_debug ("plugin installation was successful, retrying %d entries", job->priv->total);
199 job->priv->processed = 0;
200
201 /* remove the import error entries and build the list of URIs to retry */
202 for (i = job->priv->retry_entries; i != NULL; i = i->next) {
203 RhythmDBEntry *entry = (RhythmDBEntry *)i->data;
204 char *uri;
205
206 uri = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_LOCATION);
207 rhythmdb_entry_delete (job->priv->db, entry);
208
209 g_queue_push_tail (job->priv->outstanding, g_strdup (uri));
210 }
211 rhythmdb_commit (job->priv->db);
212 }
213
214 maybe_start_more (job);
215
216 g_mutex_unlock (&job->priv->lock);
217 }
218
219 static gboolean
220 emit_status_changed (RhythmDBImportJob *job)
221 {
222 g_mutex_lock (&job->priv->lock);
223 job->priv->status_changed_id = 0;
224
225 rb_debug ("emitting status changed: %d/%d", job->priv->processed, job->priv->total);
226 g_signal_emit (job, signals[STATUS_CHANGED], 0, job->priv->total, job->priv->processed);
227
228 /* temporary ref while emitting this signal as we're expecting the caller
229 * to release the final reference there.
230 */
231 g_object_ref (job);
232 if (job->priv->scan_complete && job->priv->processed >= job->priv->total) {
233
234 if (job->priv->retry_entries != NULL && job->priv->retried == FALSE) {
235 gboolean processing = FALSE;
236 char **details = NULL;
237 GClosure *retry;
238 GSList *l;
239 int i;
240
241 /* gather missing plugin details etc. */
242 i = 0;
243 for (l = job->priv->retry_entries; l != NULL; l = l->next) {
244 RhythmDBEntry *entry;
245 char **bits;
246 int j;
247
248 entry = (RhythmDBEntry *)l->data;
249 bits = g_strsplit (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_COMMENT), "\n", 0);
250 for (j = 0; bits[j] != NULL; j++) {
251 if (rb_str_in_strv (bits[j], (const char **)details) == FALSE) {
252 details = g_realloc (details, sizeof (char *) * (i+2));
253 details[i++] = g_strdup (bits[j]);
254 details[i] = NULL;
255 }
256 }
257 g_strfreev (bits);
258 }
259
260 retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
261 g_object_ref (job),
262 (GClosureNotify)g_object_unref);
263 g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN);
264
265 processing = rb_missing_plugins_install ((const char **)details, FALSE, retry);
266 g_strfreev (details);
267 if (processing) {
268 rb_debug ("plugin installation is in progress");
269 } else {
270 rb_debug ("no plugin installation attempted; job complete");
271 g_signal_emit (job, signals[COMPLETE], 0, job->priv->total);
272 }
273 g_closure_sink (retry);
274 } else {
275 rb_debug ("emitting job complete");
276 g_signal_emit (job, signals[COMPLETE], 0, job->priv->total);
277 }
278 } else if (g_cancellable_is_cancelled (job->priv->cancel) &&
279 g_queue_is_empty (job->priv->processing)) {
280 rb_debug ("cancelled job has no processing entries, emitting complete");
281 g_signal_emit (job, signals[COMPLETE], 0, job->priv->total);
282 }
283 g_mutex_unlock (&job->priv->lock);
284 g_object_unref (job);
285
286 return FALSE;
287 }
288
289 static void
290 uri_recurse_func (GFile *file, gboolean dir, RhythmDBImportJob *job)
291 {
292 RhythmDBEntry *entry;
293 char *uri;
294
295 if (dir) {
296 return;
297 }
298
299 if (g_cancellable_is_cancelled (job->priv->cancel))
300 return;
301
302 uri = g_file_get_uri (file);
303
304 /* if it's not already in the db, add it to the list of things to process */
305 entry = rhythmdb_entry_lookup_by_location (job->priv->db, uri);
306 if (entry == NULL) {
307 rb_debug ("waiting for entry %s", uri);
308 g_mutex_lock (&job->priv->lock);
309 job->priv->total++;
310 g_queue_push_tail (job->priv->outstanding, g_strdup (uri));
311
312 if (job->priv->status_changed_id == 0) {
313 job->priv->status_changed_id = g_idle_add ((GSourceFunc) emit_status_changed, job);
314 }
315
316 maybe_start_more (job);
317
318 g_mutex_unlock (&job->priv->lock);
319 } else {
320 /* skip it if it's a different entry type */
321 RhythmDBEntryType *et;
322 et = rhythmdb_entry_get_entry_type (entry);
323 if (et == job->priv->entry_type ||
324 et == job->priv->ignore_type ||
325 et == job->priv->error_type) {
326 rhythmdb_add_uri_with_types (job->priv->db,
327 uri,
328 job->priv->entry_type,
329 job->priv->ignore_type,
330 job->priv->error_type);
331 }
332 }
333
334 g_free (uri);
335 }
336
337 static gboolean
338 emit_scan_complete_idle (RhythmDBImportJob *job)
339 {
340 rb_debug ("emitting scan complete");
341 g_signal_emit (job, signals[SCAN_COMPLETE], 0, job->priv->total);
342 emit_status_changed (job);
343 g_object_unref (job);
344 return FALSE;
345 }
346
347 static void
348 next_uri (RhythmDBImportJob *job)
349 {
350 g_mutex_lock (&job->priv->lock);
351 if (job->priv->uri_list == NULL) {
352 rb_debug ("no more uris to scan");
353 job->priv->scan_complete = TRUE;
354 g_idle_add ((GSourceFunc)emit_scan_complete_idle, job);
355 } else {
356 char *uri = job->priv->uri_list->data;
357 job->priv->uri_list = g_slist_delete_link (job->priv->uri_list,
358 job->priv->uri_list);
359
360 rb_debug ("scanning uri %s", uri);
361 rb_uri_handle_recursively_async (uri,
362 job->priv->cancel,
363 (RBUriRecurseFunc) uri_recurse_func,
364 job,
365 (GDestroyNotify) next_uri);
366
367 g_free (uri);
368 }
369 g_mutex_unlock (&job->priv->lock);
370 }
371
372 /**
373 * rhythmdb_import_job_start:
374 * @job: the #RhythmDBImportJob
375 *
376 * Starts the import job. After this method has been called,
377 * no more URIs may be added to the import job. May only be
378 * called once for a given import job.
379 */
380 void
381 rhythmdb_import_job_start (RhythmDBImportJob *job)
382 {
383 g_assert (job->priv->started == FALSE);
384
385 rb_debug ("starting");
386 g_mutex_lock (&job->priv->lock);
387 job->priv->started = TRUE;
388 job->priv->uri_list = g_slist_reverse (job->priv->uri_list);
389 g_mutex_unlock (&job->priv->lock);
390
391 /* reference is released in emit_scan_complete_idle */
392 next_uri (g_object_ref (job));
393 }
394
395 /**
396 * rhythmdb_import_job_get_total:
397 * @job: the #RhythmDBImportJob
398 *
399 * Returns the total number of files that will be processed by
400 * this import job. This increases as the import directories are
401 * scanned.
402 *
403 * Return value: the total number of files to be processed
404 */
405 int
406 rhythmdb_import_job_get_total (RhythmDBImportJob *job)
407 {
408 return job->priv->total;
409 }
410
411 /**
412 * rhythmdb_import_job_get_imported:
413 * @job: the #RhythmDBImportJob
414 *
415 * Returns the number of files successfully imported by the import job so far.
416 *
417 * Return value: file count
418 */
419 int
420 rhythmdb_import_job_get_imported (RhythmDBImportJob *job)
421 {
422 return job->priv->imported;
423 }
424
425 /**
426 * rhythmdb_import_job_get_processed:
427 * @job: the #RhythmDBImportJob
428 *
429 * Returns the number of files processed by the import job so far.
430 *
431 * Return value: file count
432 */
433 int
434 rhythmdb_import_job_get_processed (RhythmDBImportJob *job)
435 {
436 return job->priv->processed;
437 }
438
439 /**
440 * rhythmdb_import_job_scan_complete:
441 * @job: the #RhythmDBImportJob
442 *
443 * Returns whether the directory scan phase of the import job is complete.
444 *
445 * Return value: TRUE if complete
446 */
447 gboolean
448 rhythmdb_import_job_scan_complete (RhythmDBImportJob *job)
449 {
450 return job->priv->scan_complete;
451 }
452
453 /**
454 * rhythmdb_import_job_complete:
455 * @job: the #RhythmDBImportJob
456 *
457 * Returns whether the import job is complete.
458 *
459 * Return value: TRUE if complete.
460 */
461 gboolean
462 rhythmdb_import_job_complete (RhythmDBImportJob *job)
463 {
464 return job->priv->complete;
465 }
466
467 /**
468 * rhythmdb_import_job_cancel:
469 * @job: the #RhythmDBImportJob
470 *
471 * Cancels the import job. The job will cease as soon
472 * as possible. More directories may be scanned and
473 * more files may be imported before the job actually
474 * ceases.
475 */
476 void
477 rhythmdb_import_job_cancel (RhythmDBImportJob *job)
478 {
479 g_mutex_lock (&job->priv->lock);
480 g_cancellable_cancel (job->priv->cancel);
481 g_mutex_unlock (&job->priv->lock);
482 }
483
484 static void
485 entry_added_cb (RhythmDB *db,
486 RhythmDBEntry *entry,
487 RhythmDBImportJob *job)
488 {
489 const char *uri;
490 GList *link;
491
492 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
493
494 g_mutex_lock (&job->priv->lock);
495 link = g_queue_find_custom (job->priv->processing, uri, (GCompareFunc) g_strcmp0);
496
497 if (link != NULL) {
498 const char *details;
499 RhythmDBEntryType *entry_type;
500
501 entry_type = rhythmdb_entry_get_entry_type (entry);
502
503 job->priv->processed++;
504
505 if (entry_type == job->priv->entry_type) {
506 job->priv->imported++;
507 g_signal_emit (job, signals[ENTRY_ADDED], 0, entry);
508 }
509 rb_debug ("got entry %s; %d imported, %d processed", uri, job->priv->imported, job->priv->processed);
510
511 /* if it's an import error with missing plugins, add it to the retry list */
512 details = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_COMMENT);
513 if (entry_type == job->priv->error_type &&
514 (details != NULL && details[0] != '\0')) {
515 rb_debug ("entry %s is an import error with missing plugin details: %s", uri, details);
516 job->priv->retry_entries = g_slist_prepend (job->priv->retry_entries, rhythmdb_entry_ref (entry));
517 }
518
519 if (job->priv->status_changed_id == 0) {
520 job->priv->status_changed_id = g_idle_add ((GSourceFunc) emit_status_changed, job);
521 }
522
523 g_queue_delete_link (job->priv->processing, link);
524 maybe_start_more (job);
525 }
526 g_mutex_unlock (&job->priv->lock);
527 }
528
529 static void
530 rhythmdb_import_job_init (RhythmDBImportJob *job)
531 {
532 job->priv = G_TYPE_INSTANCE_GET_PRIVATE (job,
533 RHYTHMDB_TYPE_IMPORT_JOB,
534 RhythmDBImportJobPrivate);
535
536 g_mutex_init (&job->priv->lock);
537 job->priv->outstanding = g_queue_new ();
538 job->priv->processing = g_queue_new ();
539
540 job->priv->cancel = g_cancellable_new ();
541 }
542
543 static void
544 impl_set_property (GObject *object,
545 guint prop_id,
546 const GValue *value,
547 GParamSpec *pspec)
548 {
549 RhythmDBImportJob *job = RHYTHMDB_IMPORT_JOB (object);
550
551 switch (prop_id) {
552 case PROP_DB:
553 job->priv->db = RHYTHMDB (g_value_dup_object (value));
554 g_signal_connect_object (job->priv->db,
555 "entry-added",
556 G_CALLBACK (entry_added_cb),
557 job, 0);
558 break;
559 case PROP_ENTRY_TYPE:
560 job->priv->entry_type = g_value_get_object (value);
561 break;
562 case PROP_IGNORE_TYPE:
563 job->priv->ignore_type = g_value_get_object (value);
564 break;
565 case PROP_ERROR_TYPE:
566 job->priv->error_type = g_value_get_object (value);
567 break;
568 default:
569 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
570 break;
571 }
572 }
573
574 static void
575 impl_get_property (GObject *object,
576 guint prop_id,
577 GValue *value,
578 GParamSpec *pspec)
579 {
580 RhythmDBImportJob *job = RHYTHMDB_IMPORT_JOB (object);
581
582 switch (prop_id) {
583 case PROP_DB:
584 g_value_set_object (value, job->priv->db);
585 break;
586 case PROP_ENTRY_TYPE:
587 g_value_set_object (value, job->priv->entry_type);
588 break;
589 case PROP_IGNORE_TYPE:
590 g_value_set_object (value, job->priv->ignore_type);
591 break;
592 case PROP_ERROR_TYPE:
593 g_value_set_object (value, job->priv->error_type);
594 break;
595 default:
596 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
597 break;
598 }
599 }
600 static void
601 impl_dispose (GObject *object)
602 {
603 RhythmDBImportJob *job = RHYTHMDB_IMPORT_JOB (object);
604
605 if (job->priv->db != NULL) {
606 g_object_unref (job->priv->db);
607 job->priv->db = NULL;
608 }
609
610 if (job->priv->cancel != NULL) {
611 g_object_unref (job->priv->cancel);
612 job->priv->cancel = NULL;
613 }
614
615 G_OBJECT_CLASS (rhythmdb_import_job_parent_class)->dispose (object);
616 }
617
618 static void
619 impl_finalize (GObject *object)
620 {
621 RhythmDBImportJob *job = RHYTHMDB_IMPORT_JOB (object);
622
623 g_queue_free_full (job->priv->outstanding, g_free);
624 g_queue_free_full (job->priv->processing, g_free);
625
626 rb_slist_deep_free (job->priv->uri_list);
627
628 G_OBJECT_CLASS (rhythmdb_import_job_parent_class)->finalize (object);
629 }
630
631 static void
632 rhythmdb_import_job_class_init (RhythmDBImportJobClass *klass)
633 {
634 GObjectClass *object_class = G_OBJECT_CLASS (klass);
635
636 object_class->set_property = impl_set_property;
637 object_class->get_property = impl_get_property;
638 object_class->dispose = impl_dispose;
639 object_class->finalize = impl_finalize;
640
641 g_object_class_install_property (object_class,
642 PROP_DB,
643 g_param_spec_object ("db",
644 "db",
645 "RhythmDB object",
646 RHYTHMDB_TYPE,
647 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
648
649 g_object_class_install_property (object_class,
650 PROP_ENTRY_TYPE,
651 g_param_spec_object ("entry-type",
652 "Entry type",
653 "Entry type to use for entries added by this job",
654 RHYTHMDB_TYPE_ENTRY_TYPE,
655 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
656 g_object_class_install_property (object_class,
657 PROP_IGNORE_TYPE,
658 g_param_spec_object ("ignore-type",
659 "Ignored entry type",
660 "Entry type to use for ignored entries added by this job",
661 RHYTHMDB_TYPE_ENTRY_TYPE,
662 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
663 g_object_class_install_property (object_class,
664 PROP_ERROR_TYPE,
665 g_param_spec_object ("error-type",
666 "Error entry type",
667 "Entry type to use for import error entries added by this job",
668 RHYTHMDB_TYPE_ENTRY_TYPE,
669 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
670
671 /**
672 * RhythmDBImportJob::entry-added:
673 * @job: the #RhythmDBImportJob
674 * @entry: the newly added #RhythmDBEntry
675 *
676 * Emitted when an entry has been added to the database by the
677 * import job.
678 */
679 signals [ENTRY_ADDED] =
680 g_signal_new ("entry-added",
681 G_OBJECT_CLASS_TYPE (object_class),
682 G_SIGNAL_RUN_LAST,
683 G_STRUCT_OFFSET (RhythmDBImportJobClass, entry_added),
684 NULL, NULL,
685 g_cclosure_marshal_VOID__BOXED,
686 G_TYPE_NONE,
687 1, RHYTHMDB_TYPE_ENTRY);
688 /**
689 * RhythmDBImportJob::status-changed:
690 * @job: the #RhythmDBImportJob
691 * @total: the current total number of files to process
692 * @imported: the current count of files imported
693 *
694 * Emitted when the status of the import job has changed.
695 */
696 signals [STATUS_CHANGED] =
697 g_signal_new ("status-changed",
698 G_OBJECT_CLASS_TYPE (object_class),
699 G_SIGNAL_RUN_LAST,
700 G_STRUCT_OFFSET (RhythmDBImportJobClass, status_changed),
701 NULL, NULL,
702 rb_marshal_VOID__INT_INT,
703 G_TYPE_NONE,
704 2, G_TYPE_INT, G_TYPE_INT);
705 /**
706 * RhythmDBImportJob::scan-complete:
707 * @job: the #RhythmDBImportJob
708 *
709 * Emitted when the directory scan is complete. Once
710 * the scan is complete, the total number of files to
711 * be processed will not change.
712 */
713 signals[SCAN_COMPLETE] =
714 g_signal_new ("scan-complete",
715 G_OBJECT_CLASS_TYPE (object_class),
716 G_SIGNAL_RUN_LAST,
717 G_STRUCT_OFFSET (RhythmDBImportJobClass, scan_complete),
718 NULL, NULL,
719 g_cclosure_marshal_VOID__INT,
720 G_TYPE_NONE,
721 1, G_TYPE_INT);
722 /**
723 * RhythmDBImportJob::complete:
724 * @job: the #RhythmDBImportJob
725 *
726 * Emitted when the whole import job is complete.
727 */
728 signals[COMPLETE] =
729 g_signal_new ("complete",
730 G_OBJECT_CLASS_TYPE (object_class),
731 G_SIGNAL_RUN_LAST,
732 G_STRUCT_OFFSET (RhythmDBImportJobClass, complete),
733 NULL, NULL,
734 g_cclosure_marshal_VOID__INT,
735 G_TYPE_NONE,
736 1, G_TYPE_INT);
737
738 g_type_class_add_private (klass, sizeof (RhythmDBImportJobPrivate));
739 }