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