hythmbox-2.98/rhythmdb/rhythmdb-import-job.c

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 }