1 /*
2  * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA  02110-1301, USA.
18  */
19 
20 #include "config.h"
21 
22 #include <math.h>
23 
24 #include <glib/gi18n.h>
25 
26 #include <libtracker-common/tracker-dbus.h>
27 #include <libtracker-common/tracker-type-utils.h>
28 
29 #include "tracker-miner-object.h"
30 
31 /* Here we use ceil() to eliminate decimal points beyond what we're
32  * interested in, which is 2 decimal places for the progress. The
33  * ceil() call will also round up the last decimal place.
34  *
35  * The 0.49 value is used for rounding correctness, because ceil()
36  * rounds up if the number is > 0.0.
37  */
38 #define PROGRESS_ROUNDED(x) ((x) < 0.01 ? 0.00 : (ceil (((x) * 100) - 0.49) / 100))
39 
40 #ifdef MINER_STATUS_ENABLE_TRACE
41 #warning Miner status traces are enabled
42 #define trace(message, ...) g_debug (message, ##__VA_ARGS__)
43 #else
44 #define trace(...)
45 #endif /* MINER_STATUS_ENABLE_TRACE */
46 
47 /**
48  * SECTION:tracker-miner-object
49  * @short_description: Abstract base class for data miners
50  * @include: libtracker-miner/tracker-miner.h
51  *
52  * #TrackerMiner is an abstract base class to help developing data miners
53  * for tracker-store, being an abstract class it doesn't do much by itself,
54  * but provides the basic signaling and control over the actual indexing
55  * task.
56  *
57  * #TrackerMiner implements the #GInitable interface, and thus, all objects of
58  * types inheriting from #TrackerMiner must be initialized with g_initable_init()
59  * just after creation (or directly created with g_initable_new()).
60  **/
61 
62 struct _TrackerMinerPrivate {
63 	TrackerSparqlConnection *connection;
64 	gboolean started;
65 	gint n_pauses;
66 	gchar *status;
67 	gdouble progress;
68 	gint remaining_time;
69 	gint availability_cookie;
70 	guint update_id;
71 };
72 
73 enum {
74 	PROP_0,
75 	PROP_STATUS,
76 	PROP_PROGRESS,
77 	PROP_REMAINING_TIME,
78 	PROP_CONNECTION
79 };
80 
81 enum {
82 	STARTED,
83 	STOPPED,
84 	PAUSED,
85 	RESUMED,
86 	PROGRESS,
87 	LAST_SIGNAL
88 };
89 
90 static guint signals[LAST_SIGNAL] = { 0 };
91 
92 static void       miner_set_property           (GObject                *object,
93                                                 guint                   param_id,
94                                                 const GValue           *value,
95                                                 GParamSpec             *pspec);
96 static void       miner_get_property           (GObject                *object,
97                                                 guint                   param_id,
98                                                 GValue                 *value,
99                                                 GParamSpec             *pspec);
100 static void       miner_finalize               (GObject                *object);
101 static void       miner_initable_iface_init    (GInitableIface         *iface);
102 static gboolean   miner_initable_init          (GInitable              *initable,
103                                                 GCancellable           *cancellable,
104                                                 GError                **error);
105 
106 /**
107  * tracker_miner_error_quark:
108  *
109  * Gives the caller the #GQuark used to identify #TrackerMiner errors
110  * in #GError structures. The #GQuark is used as the domain for the error.
111  *
112  * Returns: the #GQuark used for the domain of a #GError.
113  *
114  * Since: 0.8
115  **/
116 G_DEFINE_QUARK (TrackerMinerError, tracker_miner_error)
117 
118 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TrackerMiner, tracker_miner, G_TYPE_OBJECT,
119                                   G_ADD_PRIVATE (TrackerMiner)
120                                   G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
121                                                          miner_initable_iface_init));
122 
123 static void
tracker_miner_class_init(TrackerMinerClass * klass)124 tracker_miner_class_init (TrackerMinerClass *klass)
125 {
126 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
127 
128 	object_class->set_property = miner_set_property;
129 	object_class->get_property = miner_get_property;
130 	object_class->finalize     = miner_finalize;
131 
132 	/**
133 	 * TrackerMiner::started:
134 	 * @miner: the #TrackerMiner
135 	 *
136 	 * the ::started signal is emitted in the miner
137 	 * right after it has been started through
138 	 * tracker_miner_start().
139 	 *
140 	 * Since: 0.8
141 	 **/
142 	signals[STARTED] =
143 		g_signal_new ("started",
144 		              G_OBJECT_CLASS_TYPE (object_class),
145 		              G_SIGNAL_RUN_LAST,
146 		              G_STRUCT_OFFSET (TrackerMinerClass, started),
147 		              NULL, NULL,
148 		              NULL,
149 		              G_TYPE_NONE, 0);
150 	/**
151 	 * TrackerMiner::stopped:
152 	 * @miner: the #TrackerMiner
153 	 *
154 	 * the ::stopped signal is emitted in the miner
155 	 * right after it has been stopped through
156 	 * tracker_miner_stop().
157 	 *
158 	 * Since: 0.8
159 	 **/
160 	signals[STOPPED] =
161 		g_signal_new ("stopped",
162 		              G_OBJECT_CLASS_TYPE (object_class),
163 		              G_SIGNAL_RUN_LAST,
164 		              G_STRUCT_OFFSET (TrackerMinerClass, stopped),
165 		              NULL, NULL,
166 		              NULL,
167 		              G_TYPE_NONE, 0);
168 	/**
169 	 * TrackerMiner::paused:
170 	 * @miner: the #TrackerMiner
171 	 *
172 	 * the ::paused signal is emitted whenever
173 	 * there is any reason to pause, either
174 	 * internal (through tracker_miner_pause()) or
175 	 * external (through DBus, see #TrackerMinerManager).
176 	 *
177 	 * Since: 0.8
178 	 **/
179 	signals[PAUSED] =
180 		g_signal_new ("paused",
181 		              G_OBJECT_CLASS_TYPE (object_class),
182 		              G_SIGNAL_RUN_LAST,
183 		              G_STRUCT_OFFSET (TrackerMinerClass, paused),
184 		              NULL, NULL,
185 		              NULL,
186 		              G_TYPE_NONE, 0);
187 	/**
188 	 * TrackerMiner::resumed:
189 	 * @miner: the #TrackerMiner
190 	 *
191 	 * the ::resumed signal is emitted whenever
192 	 * all reasons to pause have disappeared, see
193 	 * tracker_miner_resume() and #TrackerMinerManager.
194 	 *
195 	 * Since: 0.8
196 	 **/
197 	signals[RESUMED] =
198 		g_signal_new ("resumed",
199 		              G_OBJECT_CLASS_TYPE (object_class),
200 		              G_SIGNAL_RUN_LAST,
201 		              G_STRUCT_OFFSET (TrackerMinerClass, resumed),
202 		              NULL, NULL,
203 		              NULL,
204 		              G_TYPE_NONE, 0);
205 	/**
206 	 * TrackerMiner::progress:
207 	 * @miner: the #TrackerMiner
208 	 * @status: miner status
209 	 * @progress: a #gdouble indicating miner progress, from 0 to 1.
210 	 * @remaining_time: a #gint indicating the reamaining processing time, in
211 	 * seconds.
212 	 *
213 	 * the ::progress signal will be emitted by TrackerMiner implementations
214 	 * to indicate progress about the data mining process. @status will
215 	 * contain a translated string with the current miner status and @progress
216 	 * will indicate how much has been processed so far. @remaining_time will
217 	 * give the number expected of seconds to finish processing, 0 if the
218 	 * value cannot be estimated, and -1 if its not applicable.
219 	 *
220 	 * Since: 0.12
221 	 **/
222 	signals[PROGRESS] =
223 		g_signal_new ("progress",
224 		              G_OBJECT_CLASS_TYPE (object_class),
225 		              G_SIGNAL_RUN_LAST,
226 		              G_STRUCT_OFFSET (TrackerMinerClass, progress),
227 		              NULL, NULL,
228 		              NULL,
229 		              G_TYPE_NONE, 3,
230 		              G_TYPE_STRING,
231 		              G_TYPE_DOUBLE,
232 		              G_TYPE_INT);
233 
234 	g_object_class_install_property (object_class,
235 	                                 PROP_STATUS,
236 	                                 g_param_spec_string ("status",
237 	                                                      "Status",
238 	                                                      "Translatable string with status description",
239 	                                                      "Idle",
240 	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
241 	g_object_class_install_property (object_class,
242 	                                 PROP_PROGRESS,
243 	                                 g_param_spec_double ("progress",
244 	                                                      "Progress",
245 	                                                      "Miner progress",
246 	                                                      0.0,
247 	                                                      1.0,
248 	                                                      0.0,
249 	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
250 
251 	g_object_class_install_property (object_class,
252 	                                 PROP_REMAINING_TIME,
253 	                                 g_param_spec_int ("remaining-time",
254 	                                                   "Remaining time",
255 	                                                   "Estimated remaining time to finish processing",
256 	                                                   -1,
257 	                                                   G_MAXINT,
258 	                                                   -1,
259 	                                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
260 	/**
261 	 * TrackerMiner:connection:
262 	 *
263 	 * The SPARQL connection to use. For compatibility reasons, if not set
264 	 * at construct time, one shall be obtained through
265 	 * tracker_sparql_connection_get().
266 	 *
267 	 * Since: 2.0
268 	 **/
269 	g_object_class_install_property (object_class,
270 	                                 PROP_CONNECTION,
271 	                                 g_param_spec_object ("connection",
272 	                                                      "Connection",
273 	                                                      "SPARQL Connection",
274 	                                                      TRACKER_SPARQL_TYPE_CONNECTION,
275 	                                                      G_PARAM_READWRITE |
276 	                                                      G_PARAM_CONSTRUCT_ONLY));
277 }
278 
279 static void
miner_initable_iface_init(GInitableIface * iface)280 miner_initable_iface_init (GInitableIface *iface)
281 {
282 	iface->init = miner_initable_init;
283 }
284 
285 static gboolean
miner_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)286 miner_initable_init (GInitable     *initable,
287                      GCancellable  *cancellable,
288                      GError       **error)
289 {
290 	TrackerMiner *miner = TRACKER_MINER (initable);
291 	GError *inner_error = NULL;
292 
293 	if (!miner->priv->connection) {
294 		/* Try to get SPARQL connection... */
295 		miner->priv->connection = tracker_sparql_connection_get (NULL, &inner_error);
296 	}
297 
298 	if (!miner->priv->connection) {
299 		g_propagate_error (error, inner_error);
300 		return FALSE;
301 	}
302 
303 	return TRUE;
304 }
305 
306 static void
tracker_miner_init(TrackerMiner * miner)307 tracker_miner_init (TrackerMiner *miner)
308 {
309 	miner->priv = tracker_miner_get_instance_private (miner);
310 }
311 
312 static gboolean
miner_update_progress_cb(gpointer data)313 miner_update_progress_cb (gpointer data)
314 {
315 	TrackerMiner *miner = data;
316 
317 	trace ("(Miner:'%s') UPDATE PROGRESS SIGNAL", G_OBJECT_TYPE_NAME (miner));
318 
319 	g_signal_emit (miner, signals[PROGRESS], 0,
320 	               miner->priv->status,
321 	               miner->priv->progress,
322 	               miner->priv->remaining_time);
323 
324 	miner->priv->update_id = 0;
325 
326 	return FALSE;
327 }
328 
329 static void
miner_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)330 miner_set_property (GObject      *object,
331                     guint         prop_id,
332                     const GValue *value,
333                     GParamSpec   *pspec)
334 {
335 	TrackerMiner *miner = TRACKER_MINER (object);
336 
337 	/* Quite often, we see status of 100% and still have
338 	 * status messages saying Processing... which is not
339 	 * true. So we use an idle timeout to help that situation.
340 	 * Additionally we can't force both properties are correct
341 	 * with the GObject API, so we have to do some checks our
342 	 * selves. The g_object_bind_property() API also isn't
343 	 * sufficient here.
344 	 */
345 
346 	switch (prop_id) {
347 	case PROP_STATUS: {
348 		const gchar *new_status;
349 
350 		new_status = g_value_get_string (value);
351 
352 		trace ("(Miner:'%s') Set property:'status' to '%s'",
353 		       G_OBJECT_TYPE_NAME (miner),
354 		       new_status);
355 
356 		if (miner->priv->status && new_status &&
357 		    strcmp (miner->priv->status, new_status) == 0) {
358 			/* Same, do nothing */
359 			break;
360 		}
361 
362 		g_free (miner->priv->status);
363 		miner->priv->status = g_strdup (new_status);
364 
365 		/* Check progress matches special statuses */
366 		if (new_status != NULL) {
367 			if (g_ascii_strcasecmp (new_status, "Initializing") == 0 &&
368 			    miner->priv->progress != 0.0) {
369 				trace ("(Miner:'%s') Set progress to 0.0 from status:'Initializing'",
370 				       G_OBJECT_TYPE_NAME (miner));
371 				miner->priv->progress = 0.0;
372 			} else if (g_ascii_strcasecmp (new_status, "Idle") == 0 &&
373 			           miner->priv->progress != 1.0) {
374 				trace ("(Miner:'%s') Set progress to 1.0 from status:'Idle'",
375 				       G_OBJECT_TYPE_NAME (miner));
376 				miner->priv->progress = 1.0;
377 			}
378 		}
379 
380 		if (miner->priv->update_id == 0) {
381 			miner->priv->update_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
382 			                                          miner_update_progress_cb,
383 			                                          miner,
384 			                                          NULL);
385 		}
386 
387 		break;
388 	}
389 	case PROP_PROGRESS: {
390 		gdouble new_progress;
391 
392 		new_progress = PROGRESS_ROUNDED (g_value_get_double (value));
393 		trace ("(Miner:'%s') Set property:'progress' to '%2.2f' (%2.2f before rounded)",
394 		         G_OBJECT_TYPE_NAME (miner),
395 		         new_progress,
396 		         g_value_get_double (value));
397 
398 		/* NOTE: We don't round the current progress before
399 		 * comparison because we use the rounded value when
400 		 * we set it last.
401 		 *
402 		 * Only notify 1% changes
403 		 */
404 		if (new_progress == miner->priv->progress) {
405 			/* Same, do nothing */
406 			break;
407 		}
408 
409 		miner->priv->progress = new_progress;
410 
411 		/* Check status matches special progress values */
412 		if (new_progress == 0.0) {
413 			if (miner->priv->status == NULL ||
414 			    g_ascii_strcasecmp (miner->priv->status, "Initializing") != 0) {
415 				trace ("(Miner:'%s') Set status:'Initializing' from progress:0.0",
416 				       G_OBJECT_TYPE_NAME (miner));
417 				g_free (miner->priv->status);
418 				miner->priv->status = g_strdup ("Initializing");
419 			}
420 		} else if (new_progress == 1.0) {
421 			if (miner->priv->status == NULL ||
422 			    g_ascii_strcasecmp (miner->priv->status, "Idle") != 0) {
423 				trace ("(Miner:'%s') Set status:'Idle' from progress:1.0",
424 				       G_OBJECT_TYPE_NAME (miner));
425 				g_free (miner->priv->status);
426 				miner->priv->status = g_strdup ("Idle");
427 			}
428 		}
429 
430 		if (miner->priv->update_id == 0) {
431 			miner->priv->update_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
432 			                                          miner_update_progress_cb,
433 			                                          miner,
434 			                                          NULL);
435 		}
436 
437 		break;
438 	}
439 	case PROP_REMAINING_TIME: {
440 		gint new_remaining_time;
441 
442 		new_remaining_time = g_value_get_int (value);
443 		if (new_remaining_time != miner->priv->remaining_time) {
444 			/* Just set the new remaining time, don't notify it */
445 			miner->priv->remaining_time = new_remaining_time;
446 		}
447 		break;
448 	}
449 	case PROP_CONNECTION: {
450 		miner->priv->connection = g_value_dup_object (value);
451 		break;
452 	}
453 	default:
454 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
455 		break;
456 	}
457 }
458 
459 static void
miner_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)460 miner_get_property (GObject    *object,
461                     guint       prop_id,
462                     GValue     *value,
463                     GParamSpec *pspec)
464 {
465 	TrackerMiner *miner = TRACKER_MINER (object);
466 
467 	switch (prop_id) {
468 	case PROP_STATUS:
469 		g_value_set_string (value, miner->priv->status);
470 		break;
471 	case PROP_PROGRESS:
472 		g_value_set_double (value, miner->priv->progress);
473 		break;
474 	case PROP_REMAINING_TIME:
475 		g_value_set_int (value, miner->priv->remaining_time);
476 		break;
477 	case PROP_CONNECTION:
478 		g_value_set_object (value, miner->priv->connection);
479 		break;
480 	default:
481 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
482 		break;
483 	}
484 }
485 
486 /**
487  * tracker_miner_start:
488  * @miner: a #TrackerMiner
489  *
490  * Tells the miner to start processing data.
491  *
492  * Since: 0.8
493  **/
494 void
tracker_miner_start(TrackerMiner * miner)495 tracker_miner_start (TrackerMiner *miner)
496 {
497 	g_return_if_fail (TRACKER_IS_MINER (miner));
498 	g_return_if_fail (miner->priv->started == FALSE);
499 
500 	miner->priv->started = TRUE;
501 	g_signal_emit (miner, signals[STARTED], 0);
502 }
503 
504 /**
505  * tracker_miner_stop:
506  * @miner: a #TrackerMiner
507  *
508  * Tells the miner to stop processing data.
509  *
510  * Since: 0.8
511  **/
512 void
tracker_miner_stop(TrackerMiner * miner)513 tracker_miner_stop (TrackerMiner *miner)
514 {
515 	g_return_if_fail (TRACKER_IS_MINER (miner));
516 	g_return_if_fail (miner->priv->started == TRUE);
517 
518 	miner->priv->started = FALSE;
519 	g_signal_emit (miner, signals[STOPPED], 0);
520 }
521 
522 /**
523  * tracker_miner_is_started:
524  * @miner: a #TrackerMiner
525  *
526  * Returns #TRUE if the miner has been started.
527  *
528  * Returns: #TRUE if the miner is already started.
529  *
530  * Since: 0.8
531  **/
532 gboolean
tracker_miner_is_started(TrackerMiner * miner)533 tracker_miner_is_started (TrackerMiner *miner)
534 {
535 	g_return_val_if_fail (TRACKER_IS_MINER (miner), TRUE);
536 
537 	return miner->priv->started;
538 }
539 
540 /**
541  * tracker_miner_is_paused:
542  * @miner: a #TrackerMiner
543  *
544  * Returns #TRUE if the miner is paused.
545  *
546  * Returns: #TRUE if the miner is paused.
547  *
548  * Since: 0.10
549  **/
550 gboolean
tracker_miner_is_paused(TrackerMiner * miner)551 tracker_miner_is_paused (TrackerMiner *miner)
552 {
553 	g_return_val_if_fail (TRACKER_IS_MINER (miner), TRUE);
554 
555 	return miner->priv->n_pauses > 0;
556 }
557 
558 /**
559  * tracker_miner_pause:
560  * @miner: a #TrackerMiner
561  *
562  * Asks @miner to pause. This call may be called multiple times,
563  * but #TrackerMiner::paused will only be emitted the first time.
564  * The same number of tracker_miner_resume() calls are expected
565  * in order to resume operations.
566  **/
567 void
tracker_miner_pause(TrackerMiner * miner)568 tracker_miner_pause (TrackerMiner *miner)
569 {
570 	gint previous;
571 
572 	g_return_if_fail (TRACKER_IS_MINER (miner));
573 
574 	previous = g_atomic_int_add (&miner->priv->n_pauses, 1);
575 
576 	if (previous == 0)
577 		g_signal_emit (miner, signals[PAUSED], 0);
578 }
579 
580 /**
581  * tracker_miner_resume:
582  * @miner: a #TrackerMiner
583  *
584  * Asks the miner to resume processing. This needs to be called
585  * as many times as tracker_miner_pause() calls were done
586  * previously. This function will return #TRUE when the miner
587  * is actually resumed.
588  *
589  * Returns: #TRUE if the miner resumed its operations.
590  **/
591 gboolean
tracker_miner_resume(TrackerMiner * miner)592 tracker_miner_resume (TrackerMiner *miner)
593 {
594 	g_return_val_if_fail (TRACKER_IS_MINER (miner), FALSE);
595 	g_return_val_if_fail (miner->priv->n_pauses > 0, FALSE);
596 
597 	if (g_atomic_int_dec_and_test (&miner->priv->n_pauses)) {
598 		g_signal_emit (miner, signals[RESUMED], 0);
599 		return TRUE;
600 	}
601 
602 	return FALSE;
603 }
604 
605 /**
606  * tracker_miner_get_connection:
607  * @miner: a #TrackerMiner
608  *
609  * Gets the #TrackerSparqlConnection initialized by @miner
610  *
611  * Returns: (transfer none): a #TrackerSparqlConnection.
612  *
613  * Since: 0.10
614  **/
615 TrackerSparqlConnection *
tracker_miner_get_connection(TrackerMiner * miner)616 tracker_miner_get_connection (TrackerMiner *miner)
617 {
618 	return miner->priv->connection;
619 }
620 
621 static void
miner_finalize(GObject * object)622 miner_finalize (GObject *object)
623 {
624 	TrackerMiner *miner = TRACKER_MINER (object);
625 
626 	if (miner->priv->update_id != 0) {
627 		g_source_remove (miner->priv->update_id);
628 	}
629 
630 	g_free (miner->priv->status);
631 
632 	if (miner->priv->connection) {
633 		g_object_unref (miner->priv->connection);
634 	}
635 
636 	G_OBJECT_CLASS (tracker_miner_parent_class)->finalize (object);
637 }
638