1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /*
3  *  Copyright © 2011 Igalia S.L.
4  *
5  *  This file is part of Epiphany.
6  *
7  *  Epiphany is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  Epiphany is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 #include "ephy-history-service.h"
23 
24 #include "ephy-history-service-private.h"
25 #include "ephy-history-types.h"
26 #include "ephy-lib-type-builtins.h"
27 #include "ephy-prefs.h"
28 #include "ephy-settings.h"
29 #include "ephy-sqlite-connection.h"
30 #include "ephy-sync-utils.h"
31 
32 #include <errno.h>
33 #include <glib.h>
34 #include <glib/gstdio.h>
35 
36 typedef gboolean (*EphyHistoryServiceMethod)      (EphyHistoryService *self,
37                                                    gpointer            data,
38                                                    gpointer           *result);
39 
40 typedef enum {
41   /* WRITE */
42   SET_URL_TITLE,
43   SET_URL_ZOOM_LEVEL,
44   SET_URL_HIDDEN,
45   ADD_VISIT,
46   ADD_VISITS,
47   DELETE_URLS,
48   DELETE_HOST,
49   CLEAR,
50   /* QUIT */
51   QUIT,
52   /* READ */
53   GET_URL,
54   GET_HOST_FOR_URL,
55   QUERY_URLS,
56   QUERY_VISITS,
57   GET_HOSTS,
58   QUERY_HOSTS
59 } EphyHistoryServiceMessageType;
60 
61 enum {
62   VISIT_URL,
63   URLS_VISITED,
64   CLEARED,
65   URL_TITLE_CHANGED,
66   URL_DELETED,
67   HOST_DELETED,
68   LAST_SIGNAL
69 };
70 
71 static guint signals[LAST_SIGNAL];
72 
73 typedef struct _EphyHistoryServiceMessage {
74   EphyHistoryService *service;
75   EphyHistoryServiceMessageType type;
76   gpointer *method_argument;
77   gboolean success;
78   gpointer result;
79   gpointer user_data;
80   GCancellable *cancellable;
81   GDestroyNotify method_argument_cleanup;
82   GDestroyNotify result_cleanup;
83   EphyHistoryJobCallback callback;
84 } EphyHistoryServiceMessage;
85 
86 static gpointer run_history_service_thread (EphyHistoryService *self);
87 static void ephy_history_service_process_message (EphyHistoryService        *self,
88                                                   EphyHistoryServiceMessage *message);
89 static gboolean ephy_history_service_execute_quit (EphyHistoryService *self,
90                                                    gpointer            data,
91                                                    gpointer           *result);
92 static void ephy_history_service_quit (EphyHistoryService    *self,
93                                        EphyHistoryJobCallback callback,
94                                        gpointer               user_data);
95 
96 enum {
97   PROP_0,
98   PROP_HISTORY_FILENAME,
99   PROP_MEMORY,
100   LAST_PROP
101 };
102 
103 static GParamSpec *obj_properties[LAST_PROP];
104 
105 G_DEFINE_TYPE (EphyHistoryService, ephy_history_service, G_TYPE_OBJECT);
106 
107 static void
ephy_history_service_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)108 ephy_history_service_set_property (GObject      *object,
109                                    guint         property_id,
110                                    const GValue *value,
111                                    GParamSpec   *pspec)
112 {
113   EphyHistoryService *self = EPHY_HISTORY_SERVICE (object);
114 
115   switch (property_id) {
116     case PROP_HISTORY_FILENAME:
117       g_free (self->history_filename);
118       self->history_filename = g_value_dup_string (value);
119       break;
120     case PROP_MEMORY:
121       self->in_memory = g_value_get_boolean (value);
122       break;
123     default:
124       G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
125       break;
126   }
127 }
128 
129 static void
ephy_history_service_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)130 ephy_history_service_get_property (GObject    *object,
131                                    guint       property_id,
132                                    GValue     *value,
133                                    GParamSpec *pspec)
134 {
135   EphyHistoryService *self = EPHY_HISTORY_SERVICE (object);
136   switch (property_id) {
137     case PROP_HISTORY_FILENAME:
138       g_value_set_string (value, self->history_filename);
139       break;
140     default:
141       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
142       break;
143   }
144 }
145 
146 static void
ephy_history_service_finalize(GObject * object)147 ephy_history_service_finalize (GObject *object)
148 {
149   EphyHistoryService *self = EPHY_HISTORY_SERVICE (object);
150 
151   ephy_history_service_quit (self, NULL, NULL);
152 
153   if (self->history_thread)
154     g_thread_join (self->history_thread);
155 
156   g_free (self->history_filename);
157 
158   G_OBJECT_CLASS (ephy_history_service_parent_class)->finalize (object);
159 }
160 
161 static void
ephy_history_service_dispose(GObject * object)162 ephy_history_service_dispose (GObject *object)
163 {
164   EphyHistoryService *self = EPHY_HISTORY_SERVICE (object);
165 
166   g_clear_handle_id (&self->queue_urls_visited_id, g_source_remove);
167 
168   G_OBJECT_CLASS (ephy_history_service_parent_class)->dispose (object);
169 }
170 
171 static void
ephy_history_service_constructed(GObject * object)172 ephy_history_service_constructed (GObject *object)
173 {
174   EphyHistoryService *self = EPHY_HISTORY_SERVICE (object);
175 
176   G_OBJECT_CLASS (ephy_history_service_parent_class)->constructed (object);
177 
178   self->queue = g_async_queue_new ();
179 
180   /* This value is checked in several functions to verify that they are only
181    * ever run on the history thread. Accordingly, we'd better be sure it's set
182    * before it is checked for the first time. That requires a lock here. */
183   g_mutex_lock (&self->history_thread_mutex);
184   self->history_thread = g_thread_new ("EphyHistoryService", (GThreadFunc)run_history_service_thread, self);
185 
186   /* Additionally, make sure the SQLite connection has really been opened before
187    * returning. We need this so that we can test that using a read-only service
188    * at the same time as a read/write service does not cause the read/write
189    * service to break. This delay is required because we need to be sure the
190    * read/write service has completed initialization before attempting to open
191    * the read-only service, or initializing the read-only service will fail.
192    * This isn't needed except in test mode, because only tests might run
193    * multiple history services, but it's harmless and cleaner to do always.
194    */
195   while (!self->history_thread_initialized)
196     g_cond_wait (&self->history_thread_initialized_condition, &self->history_thread_mutex);
197 
198   g_mutex_unlock (&self->history_thread_mutex);
199 }
200 
201 static gboolean
emit_urls_visited(EphyHistoryService * self)202 emit_urls_visited (EphyHistoryService *self)
203 {
204   g_signal_emit (self, signals[URLS_VISITED], 0);
205   self->queue_urls_visited_id = 0;
206 
207   return FALSE;
208 }
209 
210 static void
ephy_history_service_queue_urls_visited(EphyHistoryService * self)211 ephy_history_service_queue_urls_visited (EphyHistoryService *self)
212 {
213   if (self->queue_urls_visited_id)
214     return;
215 
216   self->queue_urls_visited_id =
217     g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)emit_urls_visited, self, NULL);
218 }
219 
220 static void
ephy_history_service_class_init(EphyHistoryServiceClass * klass)221 ephy_history_service_class_init (EphyHistoryServiceClass *klass)
222 {
223   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
224 
225   gobject_class->finalize = ephy_history_service_finalize;
226   gobject_class->dispose = ephy_history_service_dispose;
227   gobject_class->constructed = ephy_history_service_constructed;
228   gobject_class->get_property = ephy_history_service_get_property;
229   gobject_class->set_property = ephy_history_service_set_property;
230 
231   signals[VISIT_URL] =
232     g_signal_new ("visit-url",
233                   G_OBJECT_CLASS_TYPE (gobject_class),
234                   G_SIGNAL_RUN_LAST,
235                   0, NULL, NULL, NULL,
236                   G_TYPE_NONE,
237                   1,
238                   G_TYPE_POINTER | G_SIGNAL_TYPE_STATIC_SCOPE);
239 
240 /**
241  * EphyHistoryService::urls-visited:
242  * @service: the #EphyHistoryService that received the signal
243  *
244  * The ::urls-visited signal is emitted after one or more visits to
245  * URLS have taken place. This signal is intended for use-cases when
246  * precise information of the actual URLS visited is not important and
247  * there is only interest in the fact that there have been changes in
248  * the history. For more precise information, you can use ::visit-url
249  **/
250   signals[URLS_VISITED] =
251     g_signal_new ("urls-visited",
252                   G_OBJECT_CLASS_TYPE (gobject_class),
253                   G_SIGNAL_RUN_LAST,
254                   0, NULL, NULL, NULL,
255                   G_TYPE_NONE,
256                   0);
257 
258   signals[CLEARED] =
259     g_signal_new ("cleared",
260                   G_OBJECT_CLASS_TYPE (gobject_class),
261                   G_SIGNAL_RUN_LAST,
262                   0, NULL, NULL, NULL,
263                   G_TYPE_NONE,
264                   0);
265 
266   signals[URL_TITLE_CHANGED] =
267     g_signal_new ("url-title-changed",
268                   G_OBJECT_CLASS_TYPE (gobject_class),
269                   G_SIGNAL_RUN_LAST,
270                   0, NULL, NULL, NULL,
271                   G_TYPE_NONE,
272                   2,
273                   G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
274                   G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
275 
276   signals[URL_DELETED] =
277     g_signal_new ("url-deleted",
278                   G_OBJECT_CLASS_TYPE (gobject_class),
279                   G_SIGNAL_RUN_LAST,
280                   0, NULL, NULL, NULL,
281                   G_TYPE_NONE,
282                   1,
283                   G_TYPE_POINTER | G_SIGNAL_TYPE_STATIC_SCOPE);
284 
285   signals[HOST_DELETED] =
286     g_signal_new ("host-deleted",
287                   G_OBJECT_CLASS_TYPE (gobject_class),
288                   G_SIGNAL_RUN_LAST,
289                   0, NULL, NULL, NULL,
290                   G_TYPE_NONE,
291                   1,
292                   G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
293 
294   obj_properties[PROP_HISTORY_FILENAME] =
295     g_param_spec_string ("history-filename",
296                          "History filename",
297                          "The filename of the SQLite file holding containing history",
298                          NULL,
299                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
300 
301   obj_properties[PROP_MEMORY] =
302     g_param_spec_boolean ("memory",
303                           "In memory mode",
304                           "Whether the history service works in memory mode",
305                           FALSE,
306                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
307 
308   g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
309 }
310 
311 static void
ephy_history_service_init(EphyHistoryService * self)312 ephy_history_service_init (EphyHistoryService *self)
313 {
314 }
315 
316 EphyHistoryService *
ephy_history_service_new(const char * history_filename,EphySQLiteConnectionMode mode)317 ephy_history_service_new (const char               *history_filename,
318                           EphySQLiteConnectionMode  mode)
319 {
320   return EPHY_HISTORY_SERVICE (g_object_new (EPHY_TYPE_HISTORY_SERVICE,
321                                              "history-filename", history_filename,
322                                              "memory", mode == EPHY_SQLITE_CONNECTION_MODE_MEMORY,
323                                              NULL));
324 }
325 
326 static gint
sort_messages(EphyHistoryServiceMessage * a,EphyHistoryServiceMessage * b,gpointer user_data)327 sort_messages (EphyHistoryServiceMessage *a,
328                EphyHistoryServiceMessage *b,
329                gpointer                   user_data)
330 {
331   return a->type > b->type ? 1 : a->type == b->type ? 0 : -1;
332 }
333 
334 static EphyHistoryServiceMessage *
ephy_history_service_message_new(EphyHistoryService * service,EphyHistoryServiceMessageType type,gpointer method_argument,GDestroyNotify method_argument_cleanup,GDestroyNotify result_cleanup,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)335 ephy_history_service_message_new (EphyHistoryService            *service,
336                                   EphyHistoryServiceMessageType  type,
337                                   gpointer                       method_argument,
338                                   GDestroyNotify                 method_argument_cleanup,
339                                   GDestroyNotify                 result_cleanup,
340                                   GCancellable                  *cancellable,
341                                   EphyHistoryJobCallback         callback,
342                                   gpointer                       user_data)
343 {
344   EphyHistoryServiceMessage *message = g_new0 (EphyHistoryServiceMessage, 1);
345 
346   message->service = service;
347   message->type = type;
348   message->method_argument = method_argument;
349   message->method_argument_cleanup = method_argument_cleanup;
350   message->result_cleanup = result_cleanup;
351   message->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
352   message->callback = callback;
353   message->user_data = user_data;
354 
355   return message;
356 }
357 
358 static void
ephy_history_service_message_free(EphyHistoryServiceMessage * message)359 ephy_history_service_message_free (EphyHistoryServiceMessage *message)
360 {
361   if (message->method_argument_cleanup)
362     message->method_argument_cleanup (message->method_argument);
363 
364   if (message->result_cleanup)
365     message->result_cleanup (message->result);
366 
367   if (message->cancellable)
368     g_object_unref (message->cancellable);
369 
370   g_free (message);
371 }
372 
373 static void
ephy_history_service_send_message(EphyHistoryService * self,EphyHistoryServiceMessage * message)374 ephy_history_service_send_message (EphyHistoryService        *self,
375                                    EphyHistoryServiceMessage *message)
376 {
377   g_async_queue_push_sorted (self->queue, message, (GCompareDataFunc)sort_messages, NULL);
378 }
379 
380 static void
ephy_history_service_open_transaction(EphyHistoryService * self)381 ephy_history_service_open_transaction (EphyHistoryService *self)
382 {
383   GError *error = NULL;
384   g_assert (self->history_thread == g_thread_self ());
385 
386   if (self->history_database == NULL)
387     return;
388 
389   ephy_sqlite_connection_begin_transaction (self->history_database, &error);
390   if (error != NULL) {
391     g_warning ("Could not open history database transaction: %s", error->message);
392     g_error_free (error);
393   }
394 }
395 
396 static void
ephy_history_service_commit_transaction(EphyHistoryService * self)397 ephy_history_service_commit_transaction (EphyHistoryService *self)
398 {
399   GError *error = NULL;
400   g_assert (self->history_thread == g_thread_self ());
401 
402   if (self->history_database == NULL)
403     return;
404 
405   ephy_sqlite_connection_commit_transaction (self->history_database, &error);
406   if (error != NULL) {
407     g_warning ("Could not commit history database transaction: %s", error->message);
408     g_error_free (error);
409   }
410 }
411 
412 static gboolean
ephy_history_service_open_database_connections(EphyHistoryService * self)413 ephy_history_service_open_database_connections (EphyHistoryService *self)
414 {
415   GError *error = NULL;
416 
417   g_assert (self->history_thread == g_thread_self ());
418 
419   if (self->history_database != NULL)
420     g_object_unref (self->history_database);
421 
422   self->history_database = ephy_sqlite_connection_new (self->in_memory ? EPHY_SQLITE_CONNECTION_MODE_MEMORY
423                                                                        : EPHY_SQLITE_CONNECTION_MODE_READWRITE,
424                                                        self->history_filename);
425   ephy_sqlite_connection_open (self->history_database, &error);
426   if (error) {
427     g_object_unref (self->history_database);
428     self->history_database = NULL;
429 
430     if (!g_error_matches (error, EPHY_SQLITE_ERROR, SQLITE_CANTOPEN) ||
431         g_file_test (self->history_filename, G_FILE_TEST_EXISTS)) {
432       g_warning ("Could not open history database at %s: %s", self->history_filename, error->message);
433     }
434     g_error_free (error);
435     return FALSE;
436   } else {
437     ephy_sqlite_connection_enable_foreign_keys (self->history_database);
438   }
439 
440   return (ephy_history_service_initialize_hosts_table (self) &&
441           ephy_history_service_initialize_urls_table (self) &&
442           ephy_history_service_initialize_visits_table (self));
443 }
444 
445 static void
ephy_history_service_close_database_connections(EphyHistoryService * self)446 ephy_history_service_close_database_connections (EphyHistoryService *self)
447 {
448   g_assert (self->history_thread == g_thread_self ());
449 
450   ephy_sqlite_connection_close (self->history_database);
451   g_object_unref (self->history_database);
452   self->history_database = NULL;
453 }
454 
455 static gboolean
ephy_history_service_execute_quit(EphyHistoryService * self,gpointer data,gpointer * result)456 ephy_history_service_execute_quit (EphyHistoryService *self,
457                                    gpointer            data,
458                                    gpointer           *result)
459 {
460   g_assert (self->history_thread == g_thread_self ());
461 
462   g_async_queue_unref (self->queue);
463 
464   self->scheduled_to_quit = TRUE;
465 
466   return FALSE;
467 }
468 
469 static gpointer
run_history_service_thread(EphyHistoryService * self)470 run_history_service_thread (EphyHistoryService *self)
471 {
472   EphyHistoryServiceMessage *message;
473   gboolean success;
474 
475   /* Note that self->history_thread is only written once, and that's guaranteed
476    * to have occurred before we enter this critical section due to this mutex.
477    * Accordingly, we do not need to use the mutex when performing these
478    * assertions in other functions.
479    */
480   g_mutex_lock (&self->history_thread_mutex);
481   g_assert (self->history_thread == g_thread_self ());
482 
483   success = ephy_history_service_open_database_connections (self);
484 
485   self->history_thread_initialized = TRUE;
486   g_cond_signal (&self->history_thread_initialized_condition);
487   g_mutex_unlock (&self->history_thread_mutex);
488 
489   if (!success)
490     return NULL;
491 
492   do {
493     message = g_async_queue_try_pop (self->queue);
494     if (!message) {
495       /* Block the thread until there's data in the queue. */
496       message = g_async_queue_pop (self->queue);
497     }
498 
499     /* Process item. */
500     ephy_history_service_process_message (self, message);
501   } while (!self->scheduled_to_quit);
502 
503   ephy_history_service_close_database_connections (self);
504 
505   return NULL;
506 }
507 
508 static gboolean
ephy_history_service_execute_job_callback(gpointer data)509 ephy_history_service_execute_job_callback (gpointer data)
510 {
511   EphyHistoryServiceMessage *message = (EphyHistoryServiceMessage *)data;
512 
513   g_assert (message->callback || message->type == CLEAR);
514 
515   if (g_cancellable_is_cancelled (message->cancellable)) {
516     ephy_history_service_message_free (message);
517     return FALSE;
518   }
519 
520   if (message->callback)
521     message->callback (message->service, message->success, message->result, message->user_data);
522 
523   if (message->type == CLEAR)
524     g_signal_emit (message->service, signals[CLEARED], 0);
525 
526   ephy_history_service_message_free (message);
527 
528   return FALSE;
529 }
530 
531 typedef struct {
532   EphyHistoryService *service;
533   gpointer user_data;
534   GDestroyNotify destroy_func;
535 } SignalEmissionContext;
536 
537 static void
signal_emission_context_free(SignalEmissionContext * ctx)538 signal_emission_context_free (SignalEmissionContext *ctx)
539 {
540   g_object_unref (ctx->service);
541   if (ctx->destroy_func && ctx->user_data)
542     ctx->destroy_func (ctx->user_data);
543   g_free (ctx);
544 }
545 
546 static SignalEmissionContext *
signal_emission_context_new(EphyHistoryService * service,gpointer user_data,GDestroyNotify destroy_func)547 signal_emission_context_new (EphyHistoryService *service,
548                              gpointer            user_data,
549                              GDestroyNotify      destroy_func)
550 {
551   SignalEmissionContext *ctx = g_new0 (SignalEmissionContext, 1);
552 
553   ctx->service = g_object_ref (service);
554   ctx->user_data = user_data;
555   ctx->destroy_func = destroy_func;
556 
557   return ctx;
558 }
559 
560 static gboolean
ephy_history_service_execute_add_visit_helper(EphyHistoryService * self,EphyHistoryPageVisit * visit)561 ephy_history_service_execute_add_visit_helper (EphyHistoryService   *self,
562                                                EphyHistoryPageVisit *visit)
563 {
564   if (visit->url->host == NULL)
565     visit->url->host = ephy_history_service_get_host_row_from_url (self, visit->url->url);
566   else if (visit->url->host->id == -1) {
567     /* This will happen when we migrate the old history to the new
568      * format. We need to store a zoom level for a not-yet-created
569      * host, so we'll end up here. Ugly, but it works. */
570     double zoom_level = visit->url->host->zoom_level;
571     ephy_history_host_free (visit->url->host);
572     visit->url->host = ephy_history_service_get_host_row_from_url (self, visit->url->url);
573     visit->url->host->zoom_level = zoom_level;
574   }
575 
576   visit->url->host->visit_count++;
577   ephy_history_service_update_host_row (self, visit->url->host);
578 
579   /* A NULL return here means that the URL does not yet exist in the database.
580    * This overwrites visit->url so we have to test the sync id against NULL on
581    * both branches. */
582   if (ephy_history_service_get_url_row (self, visit->url->url, visit->url) == NULL) {
583     visit->url->last_visit_time = visit->visit_time;
584     visit->url->visit_count = 1;
585 
586     if (!visit->url->sync_id)
587       visit->url->sync_id = ephy_sync_utils_get_random_sync_id ();
588 
589     ephy_history_service_add_url_row (self, visit->url);
590 
591     if (!self->in_memory && visit->url->id == -1) {
592       g_warning ("Adding visit failed after failed URL addition.");
593       return FALSE;
594     }
595   } else {
596     visit->url->visit_count++;
597 
598     if (visit->visit_time > visit->url->last_visit_time)
599       visit->url->last_visit_time = visit->visit_time;
600 
601     if (!visit->url->sync_id)
602       visit->url->sync_id = ephy_sync_utils_get_random_sync_id ();
603 
604     ephy_history_service_update_url_row (self, visit->url);
605   }
606 
607   if (visit->url->notify_visit)
608     g_signal_emit (self, signals[VISIT_URL], 0, visit->url);
609 
610   ephy_history_service_add_visit_row (self, visit);
611   return visit->id != -1;
612 }
613 
614 static gboolean
ephy_history_service_execute_add_visit(EphyHistoryService * self,EphyHistoryPageVisit * visit,gpointer * result)615 ephy_history_service_execute_add_visit (EphyHistoryService   *self,
616                                         EphyHistoryPageVisit *visit,
617                                         gpointer             *result)
618 {
619   gboolean success;
620   g_assert (self->history_thread == g_thread_self ());
621 
622   success = ephy_history_service_execute_add_visit_helper (self, visit);
623   return success;
624 }
625 
626 static gboolean
ephy_history_service_execute_add_visits(EphyHistoryService * self,GList * visits,gpointer * result)627 ephy_history_service_execute_add_visits (EphyHistoryService *self,
628                                          GList              *visits,
629                                          gpointer           *result)
630 {
631   gboolean success = TRUE;
632   g_assert (self->history_thread == g_thread_self ());
633 
634   while (visits) {
635     success = success && ephy_history_service_execute_add_visit_helper (self, (EphyHistoryPageVisit *)visits->data);
636     visits = visits->next;
637   }
638 
639   return success;
640 }
641 
642 static gboolean
ephy_history_service_execute_find_visits(EphyHistoryService * self,EphyHistoryQuery * query,gpointer * result)643 ephy_history_service_execute_find_visits (EphyHistoryService *self,
644                                           EphyHistoryQuery   *query,
645                                           gpointer           *result)
646 {
647   GList *visits = ephy_history_service_find_visit_rows (self, query);
648   GList *current = visits;
649 
650   /* FIXME: We don't have a good way to tell the difference between failures and empty returns */
651   while (current) {
652     EphyHistoryPageVisit *visit = (EphyHistoryPageVisit *)current->data;
653     if (ephy_history_service_get_url_row (self, NULL, visit->url) == NULL) {
654       ephy_history_page_visit_list_free (visits);
655       g_warning ("Tried to process an orphaned page visit");
656       return FALSE;
657     }
658 
659     current = current->next;
660   }
661 
662   *result = visits;
663   return TRUE;
664 }
665 
666 static gboolean
ephy_history_service_execute_get_hosts(EphyHistoryService * self,gpointer pointer,gpointer * results)667 ephy_history_service_execute_get_hosts (EphyHistoryService *self,
668                                         gpointer            pointer,
669                                         gpointer           *results)
670 {
671   GList *hosts;
672 
673   hosts = ephy_history_service_get_all_hosts (self);
674   *results = hosts;
675 
676   return TRUE;
677 }
678 
679 static gboolean
ephy_history_service_execute_query_hosts(EphyHistoryService * self,EphyHistoryQuery * query,gpointer * results)680 ephy_history_service_execute_query_hosts (EphyHistoryService *self,
681                                           EphyHistoryQuery   *query,
682                                           gpointer           *results)
683 {
684   GList *hosts;
685 
686   hosts = ephy_history_service_find_host_rows (self, query);
687   *results = hosts;
688 
689   return TRUE;
690 }
691 
692 void
ephy_history_service_add_visit(EphyHistoryService * self,EphyHistoryPageVisit * visit,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)693 ephy_history_service_add_visit (EphyHistoryService     *self,
694                                 EphyHistoryPageVisit   *visit,
695                                 GCancellable           *cancellable,
696                                 EphyHistoryJobCallback  callback,
697                                 gpointer                user_data)
698 {
699   EphyHistoryServiceMessage *message;
700 
701   g_assert (EPHY_IS_HISTORY_SERVICE (self));
702   g_assert (visit != NULL);
703 
704   message = ephy_history_service_message_new (self, ADD_VISIT,
705                                               ephy_history_page_visit_copy (visit),
706                                               (GDestroyNotify)ephy_history_page_visit_free,
707                                               NULL,
708                                               cancellable, callback, user_data);
709   ephy_history_service_send_message (self, message);
710 }
711 
712 void
ephy_history_service_add_visits(EphyHistoryService * self,GList * visits,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)713 ephy_history_service_add_visits (EphyHistoryService     *self,
714                                  GList                  *visits,
715                                  GCancellable           *cancellable,
716                                  EphyHistoryJobCallback  callback,
717                                  gpointer                user_data)
718 {
719   EphyHistoryServiceMessage *message;
720 
721   g_assert (EPHY_IS_HISTORY_SERVICE (self));
722   g_assert (visits != NULL);
723 
724   message = ephy_history_service_message_new (self, ADD_VISITS,
725                                               ephy_history_page_visit_list_copy (visits),
726                                               (GDestroyNotify)ephy_history_page_visit_list_free,
727                                               NULL,
728                                               cancellable, callback, user_data);
729   ephy_history_service_send_message (self, message);
730 }
731 
732 void
ephy_history_service_find_visits_in_time(EphyHistoryService * self,gint64 from,gint64 to,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)733 ephy_history_service_find_visits_in_time (EphyHistoryService     *self,
734                                           gint64                  from,
735                                           gint64                  to,
736                                           GCancellable           *cancellable,
737                                           EphyHistoryJobCallback  callback,
738                                           gpointer                user_data)
739 {
740   EphyHistoryQuery *query;
741 
742   g_assert (EPHY_IS_HISTORY_SERVICE (self));
743 
744   query = ephy_history_query_new ();
745   query->from = from;
746   query->to = to;
747 
748   ephy_history_service_query_visits (self, query, cancellable, callback, user_data);
749   ephy_history_query_free (query);
750 }
751 
752 void
ephy_history_service_query_visits(EphyHistoryService * self,EphyHistoryQuery * query,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)753 ephy_history_service_query_visits (EphyHistoryService     *self,
754                                    EphyHistoryQuery       *query,
755                                    GCancellable           *cancellable,
756                                    EphyHistoryJobCallback  callback,
757                                    gpointer                user_data)
758 {
759   EphyHistoryServiceMessage *message;
760 
761   g_assert (EPHY_IS_HISTORY_SERVICE (self));
762   g_assert (query != NULL);
763 
764   message = ephy_history_service_message_new (self, QUERY_VISITS,
765                                               ephy_history_query_copy (query),
766                                               (GDestroyNotify)ephy_history_query_free,
767                                               (GDestroyNotify)ephy_history_page_visit_list_free,
768                                               cancellable, callback, user_data);
769   ephy_history_service_send_message (self, message);
770 }
771 
772 static gboolean
ephy_history_service_execute_query_urls(EphyHistoryService * self,EphyHistoryQuery * query,gpointer * result)773 ephy_history_service_execute_query_urls (EphyHistoryService *self,
774                                          EphyHistoryQuery   *query,
775                                          gpointer           *result)
776 {
777   GList *urls = ephy_history_service_find_url_rows (self, query);
778 
779   *result = urls;
780 
781   return TRUE;
782 }
783 
784 void
ephy_history_service_query_urls(EphyHistoryService * self,EphyHistoryQuery * query,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)785 ephy_history_service_query_urls (EphyHistoryService     *self,
786                                  EphyHistoryQuery       *query,
787                                  GCancellable           *cancellable,
788                                  EphyHistoryJobCallback  callback,
789                                  gpointer                user_data)
790 {
791   EphyHistoryServiceMessage *message;
792 
793   g_assert (EPHY_IS_HISTORY_SERVICE (self));
794   g_assert (query != NULL);
795 
796   message = ephy_history_service_message_new (self, QUERY_URLS,
797                                               ephy_history_query_copy (query),
798                                               (GDestroyNotify)ephy_history_query_free,
799                                               (GDestroyNotify)ephy_history_url_list_free,
800                                               cancellable, callback, user_data);
801   ephy_history_service_send_message (self, message);
802 }
803 
804 void
ephy_history_service_get_hosts(EphyHistoryService * self,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)805 ephy_history_service_get_hosts (EphyHistoryService     *self,
806                                 GCancellable           *cancellable,
807                                 EphyHistoryJobCallback  callback,
808                                 gpointer                user_data)
809 {
810   EphyHistoryServiceMessage *message;
811 
812   g_assert (EPHY_IS_HISTORY_SERVICE (self));
813 
814   message = ephy_history_service_message_new (self, GET_HOSTS,
815                                               NULL, NULL,
816                                               (GDestroyNotify)ephy_history_host_list_free,
817                                               cancellable, callback, user_data);
818   ephy_history_service_send_message (self, message);
819 }
820 
821 void
ephy_history_service_query_hosts(EphyHistoryService * self,EphyHistoryQuery * query,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)822 ephy_history_service_query_hosts (EphyHistoryService     *self,
823                                   EphyHistoryQuery       *query,
824                                   GCancellable           *cancellable,
825                                   EphyHistoryJobCallback  callback,
826                                   gpointer                user_data)
827 {
828   EphyHistoryServiceMessage *message;
829 
830   g_assert (EPHY_IS_HISTORY_SERVICE (self));
831 
832   message = ephy_history_service_message_new (self, QUERY_HOSTS,
833                                               ephy_history_query_copy (query),
834                                               (GDestroyNotify)ephy_history_query_free,
835                                               (GDestroyNotify)ephy_history_host_list_free,
836                                               cancellable, callback, user_data);
837   ephy_history_service_send_message (self, message);
838 }
839 
840 static gboolean
set_url_title_signal_emit(SignalEmissionContext * ctx)841 set_url_title_signal_emit (SignalEmissionContext *ctx)
842 {
843   EphyHistoryURL *url = (EphyHistoryURL *)ctx->user_data;
844 
845   g_signal_emit (ctx->service, signals[URL_TITLE_CHANGED], 0, url->url, url->title);
846 
847   return FALSE;
848 }
849 
850 static gboolean
ephy_history_service_execute_set_url_title(EphyHistoryService * self,EphyHistoryURL * url,gpointer * result)851 ephy_history_service_execute_set_url_title (EphyHistoryService *self,
852                                             EphyHistoryURL     *url,
853                                             gpointer           *result)
854 {
855   char *title = g_strdup (url->title);
856 
857   if (ephy_history_service_get_url_row (self, NULL, url) == NULL) {
858     /* The URL is not yet in the database, so we can't update it.. */
859     g_free (title);
860     return FALSE;
861   } else {
862     SignalEmissionContext *ctx;
863 
864     g_free (url->title);
865     url->title = title;
866     ephy_history_service_update_url_row (self, url);
867 
868     ctx = signal_emission_context_new (self,
869                                        ephy_history_url_copy (url),
870                                        (GDestroyNotify)ephy_history_url_free);
871     g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
872                      (GSourceFunc)set_url_title_signal_emit,
873                      ctx, (GDestroyNotify)signal_emission_context_free);
874     return TRUE;
875   }
876 }
877 
878 void
ephy_history_service_set_url_title(EphyHistoryService * self,const char * orig_url,const char * title,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)879 ephy_history_service_set_url_title (EphyHistoryService     *self,
880                                     const char             *orig_url,
881                                     const char             *title,
882                                     GCancellable           *cancellable,
883                                     EphyHistoryJobCallback  callback,
884                                     gpointer                user_data)
885 {
886   EphyHistoryURL *url;
887   EphyHistoryServiceMessage *message;
888 
889   g_assert (EPHY_IS_HISTORY_SERVICE (self));
890   g_assert (orig_url != NULL);
891   g_assert (title != NULL);
892   g_assert (*title != '\0');
893 
894   url = ephy_history_url_new (orig_url, title, 0, 0, 0);
895   message = ephy_history_service_message_new (self, SET_URL_TITLE,
896                                               url, (GDestroyNotify)ephy_history_url_free,
897                                               NULL, cancellable, callback, user_data);
898   ephy_history_service_send_message (self, message);
899 }
900 
901 static gboolean
ephy_history_service_execute_set_url_zoom_level(EphyHistoryService * self,GVariant * variant,gpointer * result)902 ephy_history_service_execute_set_url_zoom_level (EphyHistoryService *self,
903                                                  GVariant           *variant,
904                                                  gpointer           *result)
905 {
906   char *url_string;
907   double zoom_level;
908   EphyHistoryHost *host;
909 
910   g_variant_get (variant, "(sd)", &url_string, &zoom_level);
911 
912   host = ephy_history_service_get_host_row_from_url (self, url_string);
913   g_free (url_string);
914 
915   g_assert (host != NULL);
916 
917   host->zoom_level = zoom_level;
918   ephy_history_service_update_host_row (self, host);
919 
920   return TRUE;
921 }
922 
923 void
ephy_history_service_set_url_zoom_level(EphyHistoryService * self,const char * url,double zoom_level,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)924 ephy_history_service_set_url_zoom_level (EphyHistoryService     *self,
925                                          const char             *url,
926                                          double                  zoom_level,
927                                          GCancellable           *cancellable,
928                                          EphyHistoryJobCallback  callback,
929                                          gpointer                user_data)
930 {
931   EphyHistoryServiceMessage *message;
932   GVariant *variant;
933 
934   g_assert (EPHY_IS_HISTORY_SERVICE (self));
935   g_assert (url != NULL);
936 
937   /* Ensure that a change value which equals default zoom level is stored as 0.0 */
938   if (zoom_level == g_settings_get_double (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_DEFAULT_ZOOM_LEVEL))
939     zoom_level = 0.0f;
940 
941   variant = g_variant_new ("(sd)", url, zoom_level);
942 
943   message = ephy_history_service_message_new (self, SET_URL_ZOOM_LEVEL,
944                                               variant, (GDestroyNotify)g_variant_unref,
945                                               NULL, cancellable, callback, user_data);
946   ephy_history_service_send_message (self, message);
947 }
948 
949 static gboolean
ephy_history_service_execute_set_url_hidden(EphyHistoryService * self,EphyHistoryURL * url,gpointer * result)950 ephy_history_service_execute_set_url_hidden (EphyHistoryService *self,
951                                              EphyHistoryURL     *url,
952                                              gpointer           *result)
953 {
954   gboolean hidden;
955 
956   hidden = url->hidden;
957 
958   if (ephy_history_service_get_url_row (self, NULL, url) == NULL) {
959     /* The URL is not yet in the database, so we can't update it.. */
960     return FALSE;
961   } else {
962     url->hidden = hidden;
963     ephy_history_service_update_url_row (self, url);
964     return TRUE;
965   }
966 }
967 
968 void
ephy_history_service_set_url_hidden(EphyHistoryService * self,const char * orig_url,gboolean hidden,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)969 ephy_history_service_set_url_hidden (EphyHistoryService     *self,
970                                      const char             *orig_url,
971                                      gboolean                hidden,
972                                      GCancellable           *cancellable,
973                                      EphyHistoryJobCallback  callback,
974                                      gpointer                user_data)
975 {
976   EphyHistoryServiceMessage *message;
977   EphyHistoryURL *url;
978 
979   g_assert (EPHY_IS_HISTORY_SERVICE (self));
980   g_assert (orig_url != NULL);
981 
982   url = ephy_history_url_new (orig_url, NULL, 0, 0, 0);
983   url->hidden = hidden;
984 
985   message = ephy_history_service_message_new (self, SET_URL_HIDDEN,
986                                               url, (GDestroyNotify)ephy_history_url_free,
987                                               NULL, cancellable, callback, user_data);
988   ephy_history_service_send_message (self, message);
989 }
990 
991 static gboolean
ephy_history_service_execute_get_url(EphyHistoryService * self,const gchar * orig_url,gpointer * result)992 ephy_history_service_execute_get_url (EphyHistoryService *self,
993                                       const gchar        *orig_url,
994                                       gpointer           *result)
995 {
996   EphyHistoryURL *url;
997 
998   url = ephy_history_service_get_url_row (self, orig_url, NULL);
999 
1000   *result = url;
1001 
1002   return url != NULL;
1003 }
1004 
1005 void
ephy_history_service_get_url(EphyHistoryService * self,const char * url,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)1006 ephy_history_service_get_url (EphyHistoryService     *self,
1007                               const char             *url,
1008                               GCancellable           *cancellable,
1009                               EphyHistoryJobCallback  callback,
1010                               gpointer                user_data)
1011 {
1012   EphyHistoryServiceMessage *message;
1013 
1014   g_assert (EPHY_IS_HISTORY_SERVICE (self));
1015   g_assert (url != NULL);
1016 
1017   message = ephy_history_service_message_new (self, GET_URL,
1018                                               g_strdup (url), g_free, (GDestroyNotify)ephy_history_url_free,
1019                                               cancellable, callback, user_data);
1020   ephy_history_service_send_message (self, message);
1021 }
1022 
1023 static gboolean
ephy_history_service_execute_get_host_for_url(EphyHistoryService * self,const gchar * url,gpointer * result)1024 ephy_history_service_execute_get_host_for_url (EphyHistoryService *self,
1025                                                const gchar        *url,
1026                                                gpointer           *result)
1027 {
1028   EphyHistoryHost *host;
1029 
1030   host = ephy_history_service_get_host_row_from_url (self, url);
1031   g_assert (host != NULL);
1032 
1033   *result = host;
1034 
1035   return host != NULL;
1036 }
1037 
1038 void
ephy_history_service_get_host_for_url(EphyHistoryService * self,const char * url,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)1039 ephy_history_service_get_host_for_url (EphyHistoryService     *self,
1040                                        const char             *url,
1041                                        GCancellable           *cancellable,
1042                                        EphyHistoryJobCallback  callback,
1043                                        gpointer                user_data)
1044 {
1045   EphyHistoryServiceMessage *message;
1046 
1047   g_assert (EPHY_IS_HISTORY_SERVICE (self));
1048   g_assert (url != NULL);
1049 
1050   message = ephy_history_service_message_new (self, GET_HOST_FOR_URL,
1051                                               g_strdup (url), g_free, (GDestroyNotify)ephy_history_host_free,
1052                                               cancellable, callback, user_data);
1053   ephy_history_service_send_message (self, message);
1054 }
1055 
1056 static gboolean
delete_urls_signal_emit(SignalEmissionContext * ctx)1057 delete_urls_signal_emit (SignalEmissionContext *ctx)
1058 {
1059   EphyHistoryURL *url = (EphyHistoryURL *)ctx->user_data;
1060 
1061   g_signal_emit (ctx->service, signals[URL_DELETED], 0, url);
1062 
1063   return FALSE;
1064 }
1065 
1066 static gboolean
ephy_history_service_execute_delete_urls(EphyHistoryService * self,GList * urls,gpointer * result)1067 ephy_history_service_execute_delete_urls (EphyHistoryService *self,
1068                                           GList              *urls,
1069                                           gpointer           *result)
1070 {
1071   GList *l;
1072   EphyHistoryURL *url;
1073   SignalEmissionContext *ctx;
1074 
1075   for (l = urls; l != NULL; l = l->next) {
1076     url = l->data;
1077     ephy_history_service_delete_url (self, url);
1078 
1079     if (url->notify_delete) {
1080       ctx = signal_emission_context_new (self, ephy_history_url_copy (url),
1081                                          (GDestroyNotify)ephy_history_url_free);
1082       g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
1083                        (GSourceFunc)delete_urls_signal_emit,
1084                        ctx,
1085                        (GDestroyNotify)signal_emission_context_free);
1086     }
1087   }
1088 
1089   ephy_history_service_delete_orphan_hosts (self);
1090 
1091   return TRUE;
1092 }
1093 
1094 static gboolean
delete_host_signal_emit(SignalEmissionContext * ctx)1095 delete_host_signal_emit (SignalEmissionContext *ctx)
1096 {
1097   char *host = (char *)ctx->user_data;
1098 
1099   g_signal_emit (ctx->service, signals[HOST_DELETED], 0, host);
1100 
1101   return FALSE;
1102 }
1103 
1104 static gboolean
ephy_history_service_execute_delete_host(EphyHistoryService * self,EphyHistoryHost * host,EphyHistoryJobCallback callback,gpointer user_data)1105 ephy_history_service_execute_delete_host (EphyHistoryService     *self,
1106                                           EphyHistoryHost        *host,
1107                                           EphyHistoryJobCallback  callback,
1108                                           gpointer                user_data)
1109 {
1110   SignalEmissionContext *ctx;
1111 
1112   ephy_history_service_delete_host_row (self, host);
1113 
1114   ctx = signal_emission_context_new (self, g_strdup (host->url),
1115                                      (GDestroyNotify)g_free);
1116   g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
1117                    (GSourceFunc)delete_host_signal_emit,
1118                    ctx,
1119                    (GDestroyNotify)signal_emission_context_free);
1120 
1121   return TRUE;
1122 }
1123 
1124 static gboolean
ephy_history_service_execute_clear(EphyHistoryService * self,gpointer pointer,gpointer * result)1125 ephy_history_service_execute_clear (EphyHistoryService *self,
1126                                     gpointer            pointer,
1127                                     gpointer           *result)
1128 {
1129   if (self->history_database == NULL)
1130     return FALSE;
1131 
1132   ephy_history_service_commit_transaction (self);
1133   ephy_sqlite_connection_close (self->history_database);
1134   ephy_sqlite_connection_delete_database (self->history_database);
1135 
1136   ephy_history_service_open_database_connections (self);
1137   ephy_history_service_open_transaction (self);
1138 
1139   return TRUE;
1140 }
1141 
1142 void
ephy_history_service_delete_urls(EphyHistoryService * self,GList * urls,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)1143 ephy_history_service_delete_urls (EphyHistoryService     *self,
1144                                   GList                  *urls,
1145                                   GCancellable           *cancellable,
1146                                   EphyHistoryJobCallback  callback,
1147                                   gpointer                user_data)
1148 {
1149   EphyHistoryServiceMessage *message;
1150 
1151   g_assert (EPHY_IS_HISTORY_SERVICE (self));
1152   g_assert (urls != NULL);
1153 
1154   message = ephy_history_service_message_new (self, DELETE_URLS,
1155                                               ephy_history_url_list_copy (urls), (GDestroyNotify)ephy_history_url_list_free,
1156                                               NULL, cancellable, callback, user_data);
1157   ephy_history_service_send_message (self, message);
1158 }
1159 
1160 void
ephy_history_service_delete_host(EphyHistoryService * self,EphyHistoryHost * host,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)1161 ephy_history_service_delete_host (EphyHistoryService     *self,
1162                                   EphyHistoryHost        *host,
1163                                   GCancellable           *cancellable,
1164                                   EphyHistoryJobCallback  callback,
1165                                   gpointer                user_data)
1166 {
1167   EphyHistoryServiceMessage *message =
1168     ephy_history_service_message_new (self, DELETE_HOST,
1169                                       ephy_history_host_copy (host), (GDestroyNotify)ephy_history_host_free,
1170                                       NULL, cancellable, callback, user_data);
1171   ephy_history_service_send_message (self, message);
1172 }
1173 
1174 void
ephy_history_service_clear(EphyHistoryService * self,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)1175 ephy_history_service_clear (EphyHistoryService     *self,
1176                             GCancellable           *cancellable,
1177                             EphyHistoryJobCallback  callback,
1178                             gpointer                user_data)
1179 {
1180   EphyHistoryServiceMessage *message;
1181 
1182   g_assert (EPHY_IS_HISTORY_SERVICE (self));
1183 
1184   message = ephy_history_service_message_new (self, CLEAR,
1185                                               NULL, NULL, NULL,
1186                                               cancellable, callback, user_data);
1187   ephy_history_service_send_message (self, message);
1188 }
1189 
1190 static void
ephy_history_service_quit(EphyHistoryService * self,EphyHistoryJobCallback callback,gpointer user_data)1191 ephy_history_service_quit (EphyHistoryService     *self,
1192                            EphyHistoryJobCallback  callback,
1193                            gpointer                user_data)
1194 {
1195   EphyHistoryServiceMessage *message =
1196     ephy_history_service_message_new (self, QUIT,
1197                                       NULL, NULL, NULL, NULL,
1198                                       callback, user_data);
1199   ephy_history_service_send_message (self, message);
1200 }
1201 
1202 static EphyHistoryServiceMethod methods[] = {
1203   (EphyHistoryServiceMethod)ephy_history_service_execute_set_url_title,
1204   (EphyHistoryServiceMethod)ephy_history_service_execute_set_url_zoom_level,
1205   (EphyHistoryServiceMethod)ephy_history_service_execute_set_url_hidden,
1206   (EphyHistoryServiceMethod)ephy_history_service_execute_add_visit,
1207   (EphyHistoryServiceMethod)ephy_history_service_execute_add_visits,
1208   (EphyHistoryServiceMethod)ephy_history_service_execute_delete_urls,
1209   (EphyHistoryServiceMethod)ephy_history_service_execute_delete_host,
1210   (EphyHistoryServiceMethod)ephy_history_service_execute_clear,
1211   (EphyHistoryServiceMethod)ephy_history_service_execute_quit,
1212   (EphyHistoryServiceMethod)ephy_history_service_execute_get_url,
1213   (EphyHistoryServiceMethod)ephy_history_service_execute_get_host_for_url,
1214   (EphyHistoryServiceMethod)ephy_history_service_execute_query_urls,
1215   (EphyHistoryServiceMethod)ephy_history_service_execute_find_visits,
1216   (EphyHistoryServiceMethod)ephy_history_service_execute_get_hosts,
1217   (EphyHistoryServiceMethod)ephy_history_service_execute_query_hosts
1218 };
1219 
1220 static gboolean
ephy_history_service_message_is_write(EphyHistoryServiceMessage * message)1221 ephy_history_service_message_is_write (EphyHistoryServiceMessage *message)
1222 {
1223   return message->type < QUIT;
1224 }
1225 
1226 static void
ephy_history_service_process_message(EphyHistoryService * self,EphyHistoryServiceMessage * message)1227 ephy_history_service_process_message (EphyHistoryService        *self,
1228                                       EphyHistoryServiceMessage *message)
1229 {
1230   EphyHistoryServiceMethod method;
1231 
1232   g_assert (self->history_thread == g_thread_self ());
1233 
1234   if (g_cancellable_is_cancelled (message->cancellable) &&
1235       !ephy_history_service_message_is_write (message)) {
1236     ephy_history_service_message_free (message);
1237     return;
1238   }
1239 
1240   method = methods[message->type];
1241   message->result = NULL;
1242   if (message->service->history_database) {
1243     ephy_history_service_open_transaction (self);
1244     message->success = method (message->service, message->method_argument, &message->result);
1245     ephy_history_service_commit_transaction (self);
1246   } else {
1247     message->success = FALSE;
1248   }
1249 
1250   if (message->callback || message->type == CLEAR)
1251     g_idle_add ((GSourceFunc)ephy_history_service_execute_job_callback, message);
1252   else
1253     ephy_history_service_message_free (message);
1254 
1255   return;
1256 }
1257 
1258 /* Public API. */
1259 
1260 void
ephy_history_service_find_urls(EphyHistoryService * self,gint64 from,gint64 to,guint limit,gint host,GList * substring_list,EphyHistorySortType sort_type,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)1261 ephy_history_service_find_urls (EphyHistoryService     *self,
1262                                 gint64                  from,
1263                                 gint64                  to,
1264                                 guint                   limit,
1265                                 gint                    host,
1266                                 GList                  *substring_list,
1267                                 EphyHistorySortType     sort_type,
1268                                 GCancellable           *cancellable,
1269                                 EphyHistoryJobCallback  callback,
1270                                 gpointer                user_data)
1271 {
1272   EphyHistoryQuery *query;
1273 
1274   g_assert (EPHY_IS_HISTORY_SERVICE (self));
1275 
1276   query = ephy_history_query_new ();
1277   query->from = from;
1278   query->to = to;
1279   query->substring_list = substring_list;
1280   query->sort_type = sort_type;
1281   query->host = host;
1282 
1283   if (limit != 0)
1284     query->limit = limit;
1285 
1286   ephy_history_service_query_urls (self,
1287                                    query, cancellable,
1288                                    callback, user_data);
1289   ephy_history_query_free (query);
1290 }
1291 
1292 void
ephy_history_service_visit_url(EphyHistoryService * self,const char * url,const char * sync_id,gint64 visit_time,EphyHistoryPageVisitType visit_type,gboolean should_notify)1293 ephy_history_service_visit_url (EphyHistoryService       *self,
1294                                 const char               *url,
1295                                 const char               *sync_id,
1296                                 gint64                    visit_time,
1297                                 EphyHistoryPageVisitType  visit_type,
1298                                 gboolean                  should_notify)
1299 {
1300   EphyHistoryPageVisit *visit;
1301 
1302   g_assert (EPHY_IS_HISTORY_SERVICE (self));
1303   g_assert (url != NULL);
1304   g_assert (visit_time > 0);
1305 
1306   visit = ephy_history_page_visit_new (url, visit_time, visit_type);
1307   visit->url->sync_id = g_strdup (sync_id);
1308   visit->url->notify_visit = should_notify;
1309   ephy_history_service_add_visit (self, visit, NULL, NULL, NULL);
1310   ephy_history_page_visit_free (visit);
1311 
1312   ephy_history_service_queue_urls_visited (self);
1313 }
1314 
1315 void
ephy_history_service_find_hosts(EphyHistoryService * self,gint64 from,gint64 to,GCancellable * cancellable,EphyHistoryJobCallback callback,gpointer user_data)1316 ephy_history_service_find_hosts (EphyHistoryService     *self,
1317                                  gint64                  from,
1318                                  gint64                  to,
1319                                  GCancellable           *cancellable,
1320                                  EphyHistoryJobCallback  callback,
1321                                  gpointer                user_data)
1322 {
1323   EphyHistoryQuery *query;
1324 
1325   g_assert (EPHY_IS_HISTORY_SERVICE (self));
1326 
1327   query = ephy_history_query_new ();
1328 
1329   query->from = from;
1330   query->to = to;
1331 
1332   ephy_history_service_query_hosts (self, query,
1333                                     cancellable, callback, user_data);
1334   ephy_history_query_free (query);
1335 }
1336