[epiphany/wip/sync: 10/22] sync: Implement history sync
- From: Gabriel Ivașcu <gabrielivascu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/wip/sync: 10/22] sync: Implement history sync
- Date: Sat, 10 Jun 2017 17:30:41 +0000 (UTC)
commit 2fe2e37093f7f7b5cb07a442d954ed89304a1815
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date: Wed May 24 00:20:07 2017 +0300
sync: Implement history sync
data/org.gnome.epiphany.gschema.xml | 15 +
embed/ephy-embed-shell.c | 4 +-
embed/ephy-web-view.c | 5 +-
lib/ephy-prefs.h | 3 +
lib/ephy-sync-utils.c | 185 ++++++++
lib/ephy-sync-utils.h | 41 ++
lib/history/ephy-history-service-urls-table.c | 17 +-
lib/history/ephy-history-service.c | 50 ++-
lib/history/ephy-history-service.h | 2 +-
lib/history/ephy-history-types.c | 7 +
lib/history/ephy-history-types.h | 3 +
lib/meson.build | 1 +
lib/sync/ephy-history-manager.c | 563 +++++++++++++++++++++++++
lib/sync/ephy-history-manager.h | 36 ++
lib/sync/ephy-history-record.c | 404 ++++++++++++++++++
lib/sync/ephy-history-record.h | 44 ++
lib/sync/ephy-password-manager.c | 6 +-
lib/sync/ephy-sync-crypto.c | 215 ++--------
lib/sync/ephy-sync-crypto.h | 10 -
lib/sync/ephy-sync-service.c | 54 ++-
lib/sync/ephy-synchronizable-manager.c | 5 +-
lib/sync/ephy-synchronizable-manager.h | 2 +-
lib/sync/meson.build | 2 +
src/bookmarks/ephy-add-bookmark-popover.c | 3 +-
src/bookmarks/ephy-bookmark-properties-grid.c | 2 +-
src/bookmarks/ephy-bookmarks-manager.c | 9 +-
src/ephy-history-dialog.c | 5 +-
src/ephy-shell.c | 30 ++
src/ephy-shell.h | 3 +
src/prefs-dialog.c | 17 +
src/profile-migrator/ephy-profile-migrator.c | 6 +-
src/resources/gtk/history-dialog.ui | 2 +
src/resources/gtk/prefs-dialog.ui | 7 +
tests/ephy-web-view-test.c | 7 +-
34 files changed, 1494 insertions(+), 271 deletions(-)
---
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index 3635eed..e354664 100644
--- a/data/org.gnome.epiphany.gschema.xml
+++ b/data/org.gnome.epiphany.gschema.xml
@@ -322,6 +322,21 @@
<summary>Initial sync or normal sync</summary>
<description>TRUE if passwords collection needs to be synced for the first time,
FALSE otherwise.</description>
</key>
+ <key type="b" name="sync-history-enabled">
+ <default>false</default>
+ <summary>Enable history sync</summary>
+ <description>TRUE if history collection should be synced, FALSE
otherwise.</description>
+ </key>
+ <key type="d" name="sync-history-time">
+ <default>0</default>
+ <summary>History sync timestamp</summary>
+ <description>The timestamp at which last history sync was made.</description>
+ </key>
+ <key type="b" name="sync-history-initial">
+ <default>true</default>
+ <summary>Initial sync or normal sync</summary>
+ <description>TRUE if history collection needs to be synced for the first time, FALSE
otherwise.</description>
+ </key>
</schema>
<enum id="org.gnome.Epiphany.Permission">
<value nick="undecided" value="-1"/>
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index bb5572d..1e3292e 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -311,7 +311,7 @@ history_service_url_title_changed_cb (EphyHistoryService *service,
static void
history_service_url_deleted_cb (EphyHistoryService *service,
- const char *url,
+ EphyHistoryURL *url,
EphyEmbedShell *shell)
{
EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell);
@@ -320,7 +320,7 @@ history_service_url_deleted_cb (EphyHistoryService *service,
for (l = priv->web_extensions; l; l = g_list_next (l)) {
EphyWebExtensionProxy *web_extension = (EphyWebExtensionProxy *)l->data;
- ephy_web_extension_proxy_history_delete_url (web_extension, url);
+ ephy_web_extension_proxy_history_delete_url (web_extension, url->url);
}
}
diff --git a/embed/ephy-web-view.c b/embed/ephy-web-view.c
index 4129100..13dbec6 100644
--- a/embed/ephy-web-view.c
+++ b/embed/ephy-web-view.c
@@ -1736,7 +1736,10 @@ load_changed_cb (WebKitWebView *web_view,
ephy_history_service_visit_url (view->history_service,
history_uri,
- view->visit_type);
+ NULL,
+ g_get_real_time (),
+ view->visit_type,
+ TRUE);
g_free (history_uri);
}
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
index b6be084..fde68f9 100644
--- a/lib/ephy-prefs.h
+++ b/lib/ephy-prefs.h
@@ -162,6 +162,9 @@ static const char * const ephy_prefs_web_schema[] = {
#define EPHY_PREFS_SYNC_PASSWORDS_ENABLED "sync-passwords-enabled"
#define EPHY_PREFS_SYNC_PASSWORDS_TIME "sync-passwords-time"
#define EPHY_PREFS_SYNC_PASSWORDS_INITIAL "sync-passwords-initial"
+#define EPHY_PREFS_SYNC_HISTORY_ENABLED "sync-history-enabled"
+#define EPHY_PREFS_SYNC_HISTORY_TIME "sync-history-time"
+#define EPHY_PREFS_SYNC_HISTORY_INITIAL "sync-history-initial"
static struct {
const char *schema;
diff --git a/lib/ephy-sync-utils.c b/lib/ephy-sync-utils.c
new file mode 100644
index 0000000..b97fc54
--- /dev/null
+++ b/lib/ephy-sync-utils.c
@@ -0,0 +1,185 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-sync-utils.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#define SYNC_ID_LEN 12
+
+static const char hex_digits[] = "0123456789abcdef";
+
+char *
+ephy_sync_utils_encode_hex (const guint8 *data,
+ gsize data_len)
+{
+ char *encoded;
+
+ g_return_val_if_fail (data, NULL);
+
+ encoded = g_malloc (data_len * 2 + 1);
+ for (gsize i = 0; i < data_len; i++) {
+ guint8 byte = data[i];
+
+ encoded[2 * i] = hex_digits[byte >> 4];
+ encoded[2 * i + 1] = hex_digits[byte & 0xf];
+ }
+ encoded[data_len * 2] = 0;
+
+ return encoded;
+}
+
+guint8 *
+ephy_sync_utils_decode_hex (const char *hex)
+{
+ guint8 *decoded;
+
+ g_return_val_if_fail (hex, NULL);
+
+ decoded = g_malloc (strlen (hex) / 2);
+ for (gsize i = 0, j = 0; i < strlen (hex); i += 2, j++)
+ sscanf (hex + i, "%2hhx", decoded + j);
+
+ return decoded;
+}
+
+static void
+base64_to_base64_urlsafe (char *text)
+{
+ g_assert (text);
+
+ /* Replace '+' with '-' and '/' with '_' */
+ g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=/", '-');
+ g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=-", '_');
+}
+
+char *
+ephy_sync_utils_base64_urlsafe_encode (const guint8 *data,
+ gsize data_len,
+ gboolean should_strip)
+{
+ char *base64;
+ char *out;
+ gsize start = 0;
+ gssize end;
+
+ g_return_val_if_fail (data, NULL);
+
+ base64 = g_base64_encode (data, data_len);
+ end = strlen (base64) - 1;
+
+ /* Strip the data of any leading or trailing '=' characters. */
+ if (should_strip) {
+ while (start < strlen (base64) && base64[start] == '=')
+ start++;
+
+ while (end >= 0 && base64[end] == '=')
+ end--;
+ }
+
+ out = g_strndup (base64 + start, end - start + 1);
+ base64_to_base64_urlsafe (out);
+
+ g_free (base64);
+
+ return out;
+}
+
+static void
+base64_urlsafe_to_base64 (char *text)
+{
+ g_assert (text);
+
+ /* Replace '-' with '+' and '_' with '/' */
+ g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=_", '+');
+ g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=+", '/');
+}
+
+guint8 *
+ephy_sync_utils_base64_urlsafe_decode (const char *text,
+ gsize *out_len,
+ gboolean should_fill)
+{
+ guint8 *out;
+ char *to_decode;
+ char *suffix = NULL;
+
+ g_return_val_if_fail (text, NULL);
+ g_return_val_if_fail (out_len, NULL);
+
+ /* Fill the text with trailing '=' characters up to the proper length. */
+ if (should_fill)
+ suffix = g_strnfill ((4 - strlen (text) % 4) % 4, '=');
+
+ to_decode = g_strconcat (text, suffix, NULL);
+ base64_urlsafe_to_base64 (to_decode);
+ out = g_base64_decode (to_decode, out_len);
+
+ g_free (suffix);
+ g_free (to_decode);
+
+ return out;
+}
+
+/*
+ * This is mainly required by Nettle's RSA support.
+ * From Nettle's documentation: random_ctx and random is a randomness generator.
+ * random(random_ctx, length, dst) should generate length random octets and store them at dst.
+ * We don't really use random_ctx, since we have /dev/urandom available.
+ */
+void
+ephy_sync_utils_generate_random_bytes (void *random_ctx,
+ gsize num_bytes,
+ guint8 *out)
+{
+ FILE *fp;
+
+ g_assert (num_bytes > 0);
+ g_assert (out);
+
+ fp = fopen ("/dev/urandom", "r");
+ fread (out, sizeof (guint8), num_bytes, fp);
+ fclose (fp);
+}
+
+char *
+ephy_sync_utils_get_random_sync_id (void)
+{
+ char *id;
+ char *base64;
+ guint8 *bytes;
+ gsize bytes_len;
+
+ /* The sync id is a base64-urlsafe string. Base64 uses 4 chars to represent 3 bytes,
+ * therefore we need ceil(len * 3 / 4) bytes to cover the requested length. */
+ bytes_len = (SYNC_ID_LEN + 3) / 4 * 3;
+ bytes = g_malloc (bytes_len);
+
+ ephy_sync_utils_generate_random_bytes (NULL, bytes_len, bytes);
+ base64 = ephy_sync_utils_base64_urlsafe_encode (bytes, bytes_len, FALSE);
+ id = g_strndup (base64, SYNC_ID_LEN);
+
+ g_free (base64);
+ g_free (bytes);
+
+ return id;
+}
diff --git a/lib/ephy-sync-utils.h b/lib/ephy-sync-utils.h
new file mode 100644
index 0000000..d459535
--- /dev/null
+++ b/lib/ephy-sync-utils.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+char *ephy_sync_utils_encode_hex (const guint8 *data,
+ gsize data_len);
+guint8 *ephy_sync_utils_decode_hex (const char *hex);
+char *ephy_sync_utils_base64_urlsafe_encode (const guint8 *data,
+ gsize data_len,
+ gboolean should_strip);
+guint8 *ephy_sync_utils_base64_urlsafe_decode (const char *text,
+ gsize *out_len,
+ gboolean should_fill);
+void ephy_sync_utils_generate_random_bytes (void *random_ctx,
+ gsize num_bytes,
+ guint8 *out);
+char *ephy_sync_utils_get_random_sync_id (void);
+
+G_END_DECLS
diff --git a/lib/history/ephy-history-service-urls-table.c b/lib/history/ephy-history-service-urls-table.c
index fd69bca..447e5b6 100644
--- a/lib/history/ephy-history-service-urls-table.c
+++ b/lib/history/ephy-history-service-urls-table.c
@@ -37,6 +37,7 @@ ephy_history_service_initialize_urls_table (EphyHistoryService *self)
"host INTEGER NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,"
"url LONGVARCAR,"
"title LONGVARCAR,"
+ "sync_id LONGVARCAR,"
"visit_count INTEGER DEFAULT 0 NOT NULL,"
"typed_count INTEGER DEFAULT 0 NOT NULL,"
"last_visit_time INTEGER,"
@@ -67,11 +68,11 @@ ephy_history_service_get_url_row (EphyHistoryService *self, const char *url_stri
if (url != NULL && url->id != -1) {
statement = ephy_sqlite_connection_create_statement (self->history_database,
- "SELECT id, url, title, visit_count, typed_count,
last_visit_time, hidden_from_overview, thumbnail_update_time FROM urls "
+ "SELECT id, url, title, visit_count, typed_count,
last_visit_time, hidden_from_overview, thumbnail_update_time, sync_id FROM urls "
"WHERE id=?", &error);
} else {
statement = ephy_sqlite_connection_create_statement (self->history_database,
- "SELECT id, url, title, visit_count, typed_count,
last_visit_time, hidden_from_overview, thumbnail_update_time FROM urls "
+ "SELECT id, url, title, visit_count, typed_count,
last_visit_time, hidden_from_overview, thumbnail_update_time, sync_id FROM urls "
"WHERE url=?", &error);
}
@@ -116,6 +117,7 @@ ephy_history_service_get_url_row (EphyHistoryService *self, const char *url_stri
url->last_visit_time = ephy_sqlite_statement_get_column_as_int64 (statement, 5);
url->hidden = ephy_sqlite_statement_get_column_as_int (statement, 6);
url->thumbnail_time = ephy_sqlite_statement_get_column_as_int64 (statement, 7);
+ url->sync_id = g_strdup (ephy_sqlite_statement_get_column_as_string (statement, 8));
g_object_unref (statement);
return url;
@@ -131,8 +133,8 @@ ephy_history_service_add_url_row (EphyHistoryService *self, EphyHistoryURL *url)
g_assert (self->history_database != NULL);
statement = ephy_sqlite_connection_create_statement (self->history_database,
- "INSERT INTO urls (url, title, visit_count,
typed_count, last_visit_time, host) "
- " VALUES (?, ?, ?, ?, ?, ?)", &error);
+ "INSERT INTO urls (url, title, visit_count,
typed_count, last_visit_time, host, sync_id) "
+ " VALUES (?, ?, ?, ?, ?, ?, ?)", &error);
if (error) {
g_warning ("Could not build urls table addition statement: %s", error->message);
g_error_free (error);
@@ -144,7 +146,8 @@ ephy_history_service_add_url_row (EphyHistoryService *self, EphyHistoryURL *url)
ephy_sqlite_statement_bind_int (statement, 2, url->visit_count, &error) == FALSE ||
ephy_sqlite_statement_bind_int (statement, 3, url->typed_count, &error) == FALSE ||
ephy_sqlite_statement_bind_int64 (statement, 4, url->last_visit_time, &error) == FALSE ||
- ephy_sqlite_statement_bind_int (statement, 5, url->host->id, &error) == FALSE) {
+ ephy_sqlite_statement_bind_int (statement, 5, url->host->id, &error) == FALSE ||
+ ephy_sqlite_statement_bind_string (statement, 6, url->sync_id, &error) == FALSE) {
g_warning ("Could not insert URL into urls table: %s", error->message);
g_error_free (error);
g_object_unref (statement);
@@ -215,6 +218,7 @@ create_url_from_statement (EphySQLiteStatement *statement)
url->hidden = ephy_sqlite_statement_get_column_as_int (statement, 6);
url->thumbnail_time = ephy_sqlite_statement_get_column_as_int64 (statement, 7);
url->host->id = ephy_sqlite_statement_get_column_as_int (statement, 8);
+ url->sync_id = g_strdup (ephy_sqlite_statement_get_column_as_string (statement, 9));
return url;
}
@@ -237,7 +241,8 @@ ephy_history_service_find_url_rows (EphyHistoryService *self, EphyHistoryQuery *
"urls.last_visit_time, "
"urls.hidden_from_overview, "
"urls.thumbnail_update_time, "
- "urls.host "
+ "urls.host, "
+ "urls.sync_id "
"FROM "
"urls ";
diff --git a/lib/history/ephy-history-service.c b/lib/history/ephy-history-service.c
index 5c92776..7b95b37 100644
--- a/lib/history/ephy-history-service.c
+++ b/lib/history/ephy-history-service.c
@@ -25,6 +25,7 @@
#include "ephy-history-types.h"
#include "ephy-lib-type-builtins.h"
#include "ephy-sqlite-connection.h"
+#include "ephy-sync-utils.h"
#include <errno.h>
#include <glib.h>
@@ -221,9 +222,8 @@ ephy_history_service_class_init (EphyHistoryServiceClass *klass)
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
- 2,
- G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
- EPHY_TYPE_HISTORY_PAGE_VISIT_TYPE);
+ 1,
+ G_TYPE_POINTER | G_SIGNAL_TYPE_STATIC_SCOPE);
/**
* EphyHistoryService::urls-visited:
@@ -268,7 +268,7 @@ ephy_history_service_class_init (EphyHistoryServiceClass *klass)
0, NULL, NULL, NULL,
G_TYPE_NONE,
1,
- G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
+ G_TYPE_POINTER | G_SIGNAL_TYPE_STATIC_SCOPE);
signals[HOST_DELETED] =
g_signal_new ("host-deleted",
@@ -579,6 +579,8 @@ ephy_history_service_execute_add_visit_helper (EphyHistoryService *self, EphyHis
if (ephy_history_service_get_url_row (self, visit->url->url, visit->url) == NULL) {
visit->url->last_visit_time = visit->visit_time;
visit->url->visit_count = 1;
+ if (!visit->url->sync_id)
+ visit->url->sync_id = ephy_sync_utils_get_random_sync_id ();
ephy_history_service_add_url_row (self, visit->url);
@@ -595,6 +597,9 @@ ephy_history_service_execute_add_visit_helper (EphyHistoryService *self, EphyHis
ephy_history_service_update_url_row (self, visit->url);
}
+ if (visit->url->notify_visit)
+ g_signal_emit (self, signals[VISIT_URL], 0, visit->url);
+
ephy_history_service_add_visit_row (self, visit);
return visit->id != -1;
}
@@ -1058,7 +1063,7 @@ ephy_history_service_get_host_for_url (EphyHistoryService *self,
static gboolean
delete_urls_signal_emit (SignalEmissionContext *ctx)
{
- char *url = (char *)ctx->user_data;
+ EphyHistoryURL *url = (EphyHistoryURL *)ctx->user_data;
g_signal_emit (ctx->service, signals[URL_DELETED], 0, url);
@@ -1081,12 +1086,14 @@ ephy_history_service_execute_delete_urls (EphyHistoryService *self,
url = l->data;
ephy_history_service_delete_url (self, url);
- ctx = signal_emission_context_new (self, g_strdup (url->url),
- (GDestroyNotify)g_free);
- g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
- (GSourceFunc)delete_urls_signal_emit,
- ctx,
- (GDestroyNotify)signal_emission_context_free);
+ if (url->notify_delete) {
+ ctx = signal_emission_context_new (self, ephy_history_url_copy (url),
+ (GDestroyNotify)ephy_history_url_free);
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc)delete_urls_signal_emit,
+ ctx,
+ (GDestroyNotify)signal_emission_context_free);
+ }
}
ephy_history_service_delete_orphan_hosts (self);
@@ -1305,22 +1312,23 @@ ephy_history_service_find_urls (EphyHistoryService *self,
}
void
-ephy_history_service_visit_url (EphyHistoryService *self,
- const char *url,
- EphyHistoryPageVisitType visit_type)
+ephy_history_service_visit_url (EphyHistoryService *self,
+ const char *url,
+ const char *sync_id,
+ gint64 visit_time,
+ EphyHistoryPageVisitType visit_type,
+ gboolean should_notify)
{
EphyHistoryPageVisit *visit;
g_return_if_fail (EPHY_IS_HISTORY_SERVICE (self));
g_return_if_fail (url != NULL);
+ g_return_if_fail (visit_time > 0);
- g_signal_emit (self, signals[VISIT_URL], 0, url, visit_type);
-
- visit = ephy_history_page_visit_new (url,
- g_get_real_time (),
- visit_type);
- ephy_history_service_add_visit (self,
- visit, NULL, NULL, NULL);
+ visit = ephy_history_page_visit_new (url, visit_time, visit_type);
+ visit->url->sync_id = g_strdup (sync_id);
+ visit->url->notify_visit = should_notify;
+ ephy_history_service_add_visit (self, visit, NULL, NULL, NULL);
ephy_history_page_visit_free (visit);
ephy_history_service_queue_urls_visited (self);
diff --git a/lib/history/ephy-history-service.h b/lib/history/ephy-history-service.h
index 61b0e89..82dbf59 100644
--- a/lib/history/ephy-history-service.h
+++ b/lib/history/ephy-history-service.h
@@ -52,7 +52,7 @@ void ephy_history_service_delete_host (EphyHisto
void ephy_history_service_get_url (EphyHistoryService *self, const char
*url, GCancellable *cancellable, EphyHistoryJobCallback callback, gpointer user_data);
void ephy_history_service_delete_urls (EphyHistoryService *self, GList
*urls, GCancellable *cancellable, EphyHistoryJobCallback callback, gpointer user_data);
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);
-void ephy_history_service_visit_url (EphyHistoryService *self, const char
*orig_url, EphyHistoryPageVisitType visit_type);
+void ephy_history_service_visit_url (EphyHistoryService *self, const char
*url, const char *sync_id, gint64 visit_time, EphyHistoryPageVisitType visit_type, gboolean should_notify);
void ephy_history_service_clear (EphyHistoryService *self,
GCancellable *cancellable, EphyHistoryJobCallback callback, gpointer user_data);
void ephy_history_service_find_hosts (EphyHistoryService *self, gint64
from, gint64 to, GCancellable *cancellable, EphyHistoryJobCallback callback, gpointer user_data);
diff --git a/lib/history/ephy-history-types.c b/lib/history/ephy-history-types.c
index b72a44b..088d1cd 100644
--- a/lib/history/ephy-history-types.c
+++ b/lib/history/ephy-history-types.c
@@ -127,10 +127,13 @@ ephy_history_url_new (const char *url, const char *title, int visit_count, int t
history_url->id = -1;
history_url->url = g_strdup (url);
history_url->title = g_strdup (title);
+ history_url->sync_id = NULL;
history_url->visit_count = visit_count;
history_url->typed_count = typed_count;
history_url->last_visit_time = last_visit_time;
history_url->host = NULL;
+ history_url->notify_visit = TRUE;
+ history_url->notify_delete = TRUE;
return history_url;
}
@@ -147,9 +150,12 @@ ephy_history_url_copy (EphyHistoryURL *url)
url->typed_count,
url->last_visit_time);
copy->id = url->id;
+ copy->sync_id = g_strdup (url->sync_id);
copy->hidden = url->hidden;
copy->host = ephy_history_host_copy (url->host);
copy->thumbnail_time = url->thumbnail_time;
+ copy->notify_visit = url->notify_visit;
+ copy->notify_delete = url->notify_delete;
return copy;
}
@@ -162,6 +168,7 @@ ephy_history_url_free (EphyHistoryURL *url)
g_free (url->url);
g_free (url->title);
+ g_free (url->sync_id);
ephy_history_host_free (url->host);
g_slice_free1 (sizeof (EphyHistoryURL), url);
}
diff --git a/lib/history/ephy-history-types.h b/lib/history/ephy-history-types.h
index 1a45e99..d9d1688 100644
--- a/lib/history/ephy-history-types.h
+++ b/lib/history/ephy-history-types.h
@@ -71,12 +71,15 @@ typedef struct _EphyHistoryURL
int id;
char* url;
char* title;
+ char *sync_id;
int visit_count;
int typed_count;
gint64 last_visit_time; /* Microseconds */
gint64 thumbnail_time; /* Seconds */
gboolean hidden;
EphyHistoryHost *host;
+ gboolean notify_visit;
+ gboolean notify_delete;
} EphyHistoryURL;
typedef struct _EphyHistoryPageVisit
diff --git a/lib/meson.build b/lib/meson.build
index b81c721..9b68094 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -33,6 +33,7 @@ libephymisc_sources = [
'ephy-sqlite-connection.c',
'ephy-sqlite-statement.c',
'ephy-string.c',
+ 'ephy-sync-utils.c',
'ephy-time-helpers.c',
'ephy-uri-helpers.c',
'ephy-uri-tester-shared.c',
diff --git a/lib/sync/ephy-history-manager.c b/lib/sync/ephy-history-manager.c
new file mode 100644
index 0000000..6ca033b
--- /dev/null
+++ b/lib/sync/ephy-history-manager.c
@@ -0,0 +1,563 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-history-manager.h"
+
+#include "ephy-settings.h"
+#include "ephy-synchronizable-manager.h"
+
+struct _EphyHistoryManager {
+ GObject parent_instance;
+
+ EphyHistoryService *service;
+};
+
+static void ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphyHistoryManager, ephy_history_manager, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+ ephy_synchronizable_manager_iface_init))
+
+enum {
+ PROP_0,
+ PROP_HISTORY_SERVICE,
+ LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+typedef struct {
+ EphyHistoryManager *manager;
+ gboolean is_initial;
+ GSList *remotes_deleted;
+ GSList *remotes_updated;
+ EphySynchronizableManagerMergeCallback callback;
+ gpointer user_data;
+} MergeHistoryAsyncData;
+
+static MergeHistoryAsyncData *
+merge_history_async_data_new (EphyHistoryManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated,
+ EphySynchronizableManagerMergeCallback callback,
+ gpointer user_data)
+{
+ MergeHistoryAsyncData *data;
+
+ data = g_slice_new (MergeHistoryAsyncData);
+ data->manager = g_object_ref (manager);
+ data->is_initial = is_initial;
+ data->remotes_deleted = remotes_deleted;
+ data->remotes_updated = remotes_updated;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ return data;
+}
+
+static void
+merge_history_async_data_free (MergeHistoryAsyncData *data)
+{
+ g_assert (data);
+
+ g_object_unref (data->manager);
+ g_slice_free (MergeHistoryAsyncData, data);
+}
+
+static void
+url_visited_cb (EphyHistoryService *service,
+ EphyHistoryURL *url,
+ EphyHistoryManager *self)
+{
+ EphyHistoryRecord *record;
+
+ record = ephy_history_record_new (url->sync_id, url->title, url->url, url->last_visit_time);
+ g_signal_emit_by_name (self, "synchronizable-modified", record, TRUE);
+ g_object_unref (record);
+}
+
+static void
+url_deleted_cb (EphyHistoryService *service,
+ EphyHistoryURL *url,
+ EphyHistoryManager *self)
+{
+ EphyHistoryRecord *record;
+
+ record = ephy_history_record_new (url->sync_id, url->title, url->url, url->last_visit_time);
+ g_signal_emit_by_name (self, "synchronizable-deleted", record);
+ g_object_unref (record);
+}
+
+static void
+ephy_history_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphyHistoryManager *self = EPHY_HISTORY_MANAGER (object);
+
+ switch (prop_id) {
+ case PROP_HISTORY_SERVICE:
+ g_clear_object (&self->service);
+ self->service = g_object_ref (g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ephy_history_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphyHistoryManager *self = EPHY_HISTORY_MANAGER (object);
+
+ switch (prop_id) {
+ case PROP_HISTORY_SERVICE:
+ g_value_set_object (value, self->service);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ephy_history_manager_dispose (GObject *object)
+{
+ EphyHistoryManager *self = EPHY_HISTORY_MANAGER (object);
+
+ if (self->service) {
+ g_signal_handlers_disconnect_by_func (self->service, url_visited_cb, self);
+ g_signal_handlers_disconnect_by_func (self->service, url_deleted_cb, self);
+ }
+
+ g_clear_object (&self->service);
+
+ G_OBJECT_CLASS (ephy_history_manager_parent_class)->dispose (object);
+}
+
+static void
+ephy_history_manager_constructed (GObject *object)
+{
+ EphyHistoryManager *self = EPHY_HISTORY_MANAGER (object);
+
+ G_OBJECT_CLASS (ephy_history_manager_parent_class)->constructed (object);
+
+ g_signal_connect (self->service, "visit-url", G_CALLBACK (url_visited_cb), self);
+ g_signal_connect (self->service, "url-deleted", G_CALLBACK (url_deleted_cb), self);
+}
+
+static void
+ephy_history_manager_class_init (EphyHistoryManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = ephy_history_manager_set_property;
+ object_class->get_property = ephy_history_manager_get_property;
+ object_class->constructed = ephy_history_manager_constructed;
+ object_class->dispose = ephy_history_manager_dispose;
+
+ obj_properties[PROP_HISTORY_SERVICE] =
+ g_param_spec_object ("history-service",
+ "History service",
+ "History Service",
+ EPHY_TYPE_HISTORY_SERVICE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+}
+
+static void
+ephy_history_manager_init (EphyHistoryManager *self)
+{
+}
+
+EphyHistoryManager *
+ephy_history_manager_new (EphyHistoryService *service)
+{
+ return EPHY_HISTORY_MANAGER (g_object_new (EPHY_TYPE_HISTORY_MANAGER,
+ "history-service", service,
+ NULL));
+}
+
+static const char *
+synchronizable_manager_get_collection_name (EphySynchronizableManager *manager)
+{
+ gboolean sync_with_firefox = g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_WITH_FIREFOX);
+
+ return sync_with_firefox ? "history" : "ephy-history";
+}
+
+static GType
+synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager)
+{
+ return EPHY_TYPE_HISTORY_RECORD;
+}
+
+static gboolean
+synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager)
+{
+ return g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_HISTORY_INITIAL);
+}
+
+static void
+synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
+ gboolean is_initial)
+{
+ g_settings_set_boolean (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_HISTORY_INITIAL,
+ is_initial);
+}
+
+static double
+synchronizable_manager_get_sync_time (EphySynchronizableManager *manager)
+{
+ return g_settings_get_double (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_HISTORY_TIME);
+}
+
+static void
+synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
+ double sync_time)
+{
+ g_settings_set_double (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_HISTORY_TIME,
+ sync_time);
+}
+
+static void
+synchronizable_manager_add (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ EphyHistoryManager *self = EPHY_HISTORY_MANAGER (manager);
+ EphyHistoryRecord *record = EPHY_HISTORY_RECORD (synchronizable);
+
+ if (ephy_history_record_get_last_visit_time (record) > 0)
+ ephy_history_service_visit_url (self->service,
+ ephy_history_record_get_uri (record),
+ ephy_history_record_get_id (record),
+ ephy_history_record_get_last_visit_time (record),
+ EPHY_PAGE_VISIT_LINK,
+ FALSE);
+}
+
+static void
+synchronizable_manager_remove (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ EphyHistoryManager *self = EPHY_HISTORY_MANAGER (manager);
+ EphyHistoryRecord *record = EPHY_HISTORY_RECORD (synchronizable);
+ EphyHistoryURL *url;
+ GList *to_delete = NULL;
+
+ url = ephy_history_url_new (ephy_history_record_get_uri (record),
+ ephy_history_record_get_title (record),
+ 0, 0,
+ ephy_history_record_get_last_visit_time (record));
+ url->notify_delete = FALSE;
+ to_delete = g_list_prepend (to_delete, url);
+ ephy_history_service_delete_urls (self->service, to_delete, NULL, NULL, NULL);
+
+ g_list_free_full (to_delete, (GDestroyNotify)ephy_history_url_free);
+}
+
+static void
+synchronizable_manager_save (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ /* No implementation.
+ * We don't care about the server time modified of history records.
+ */
+}
+
+static EphyHistoryRecord *
+get_record_by_id (GSList *records,
+ const char *id)
+{
+ g_assert (id);
+
+ for (GSList *l = records; l && l->data; l = l->next) {
+ if (!g_strcmp0 (ephy_history_record_get_id (l->data), id))
+ return l->data;
+ }
+
+ return NULL;
+}
+
+static EphyHistoryRecord *
+get_record_by_url (GSList *records,
+ const char *url)
+{
+ g_assert (url);
+
+ for (GSList *l = records; l && l->data; l = l->next) {
+ if (!g_strcmp0 (ephy_history_record_get_uri (l->data), url))
+ return l->data;
+ }
+
+ return NULL;
+}
+
+static void
+ephy_history_manager_handle_different_id_same_url (EphyHistoryManager *self,
+ EphyHistoryRecord *local,
+ EphyHistoryRecord *remote)
+{
+ gint64 local_last_visit_time;
+ gint64 remote_last_visit_time;
+
+ g_assert (EPHY_IS_HISTORY_MANAGER (self));
+ g_assert (EPHY_HISTORY_RECORD (local));
+ g_assert (EPHY_HISTORY_RECORD (remote));
+
+ local_last_visit_time = ephy_history_record_get_last_visit_time (local);
+ remote_last_visit_time = ephy_history_record_get_last_visit_time (remote);
+
+ if (remote_last_visit_time > local_last_visit_time)
+ ephy_history_service_visit_url (self->service,
+ ephy_history_record_get_uri (local),
+ ephy_history_record_get_id (local),
+ local_last_visit_time,
+ EPHY_PAGE_VISIT_LINK, FALSE);
+
+ ephy_history_record_set_id (remote, ephy_history_record_get_id (local));
+ ephy_history_record_add_visit_time (remote, local_last_visit_time);
+}
+
+static GSList *
+ephy_history_manager_handle_initial_merge (EphyHistoryManager *self,
+ GSList *local_records,
+ GSList *remote_records)
+{
+ EphyHistoryRecord *record;
+ GHashTable *dont_upload;
+ GSList *to_upload = NULL;
+ const char *remote_id;
+ const char *remote_url;
+ gint64 remote_last_visit_time;
+ gint64 local_last_visit_time;
+
+ g_assert (EPHY_IS_HISTORY_MANAGER (self));
+
+ /* A history record is uniquely identified by its sync ID or by its URL. When
+ * importing history records from server, we may encounter duplicates either
+ * by ID or by URL. We start from the assumption that same ID means same URL
+ * but same URL does not necessarily mean same ID. This is what our merge
+ * logic is based on.
+ */
+ dont_upload = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ for (GSList *l = remote_records; l && l->data; l = l->next) {
+ remote_id = ephy_history_record_get_id (l->data);
+ remote_url = ephy_history_record_get_uri (l->data);
+ remote_last_visit_time = ephy_history_record_get_last_visit_time (l->data);
+
+ record = get_record_by_id (local_records, remote_id);
+ if (record) {
+ /* Same ID, same URL. Update last visit time for the local record and add
+ * the local last visit time to the remote one. */
+ local_last_visit_time = ephy_history_record_get_last_visit_time (record);
+ if (remote_last_visit_time > local_last_visit_time)
+ ephy_history_service_visit_url (self->service, remote_url,
+ remote_id, remote_last_visit_time,
+ EPHY_PAGE_VISIT_LINK, FALSE);
+
+ if (ephy_history_record_add_visit_time (l->data, local_last_visit_time))
+ to_upload = g_slist_prepend (to_upload, g_object_ref (l->data));
+
+ g_hash_table_add (dont_upload, g_strdup (remote_id));
+ } else {
+ record = get_record_by_url (local_records, remote_url);
+ if (record) {
+ /* Different ID, same URL. Keep local ID. */
+ g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
+ ephy_history_manager_handle_different_id_same_url (self, record, l->data);
+ to_upload = g_slist_prepend (to_upload, g_object_ref (l->data));
+ g_hash_table_add (dont_upload, g_strdup (ephy_history_record_get_id (record)));
+ } else {
+ /* Different ID, different URL. This is a new record. */
+ if (remote_last_visit_time > 0)
+ ephy_history_service_visit_url (self->service, remote_url,
+ remote_id, remote_last_visit_time,
+ EPHY_PAGE_VISIT_LINK, FALSE);
+ }
+ }
+ }
+
+ /* Set the remaining local records to be uploaded to server. */
+ for (GSList *l = local_records; l && l->data; l = l->next) {
+ record = EPHY_HISTORY_RECORD (l->data);
+ if (!g_hash_table_contains (dont_upload, ephy_history_record_get_id (record))) {
+ to_upload = g_slist_prepend (to_upload, g_object_ref (record));
+ }
+ }
+
+ g_hash_table_unref (dont_upload);
+
+ return to_upload;
+}
+
+static GSList *
+ephy_history_manager_handle_regular_merge (EphyHistoryManager *self,
+ GSList *local_records,
+ GSList *deleted_records,
+ GSList *updated_records)
+{
+ EphyHistoryRecord *record;
+ GSList *to_upload = NULL;
+ const char *remote_id;
+ const char *remote_url;
+ gint64 remote_last_visit_time;
+ gint64 local_last_visit_time;
+
+ g_assert (EPHY_IS_HISTORY_MANAGER (self));
+
+ for (GSList *l = deleted_records; l && l->data; l = l->next) {
+ record = get_record_by_id (local_records, ephy_history_record_get_id (l->data));
+ if (record)
+ ephy_synchronizable_manager_remove (EPHY_SYNCHRONIZABLE_MANAGER (self),
+ EPHY_SYNCHRONIZABLE (record));
+ }
+
+ /* See comment in ephy_history_manager_handle_initial_merge. */
+ for (GSList *l = updated_records; l && l->data; l = l->next) {
+ remote_id = ephy_history_record_get_id (l->data);
+ remote_url = ephy_history_record_get_uri (l->data);
+ remote_last_visit_time = ephy_history_record_get_last_visit_time (l->data);
+
+ record = get_record_by_id (local_records, remote_id);
+ if (record) {
+ /* Same ID, same URL. Update last visit time for the local record. */
+ local_last_visit_time = ephy_history_record_get_last_visit_time (record);
+
+ /* Firefox offers the option to "forget about this site" which means that
+ * the record is not deleted from server but only has its visit times
+ * deleted. Having no visit times translates to a negative last visit time
+ * in Epiphany. Since Epiphany does not support having a history record
+ * with no visit time, we delete it for good from the local database.
+ */
+ if (remote_last_visit_time <= 0)
+ ephy_synchronizable_manager_remove (EPHY_SYNCHRONIZABLE_MANAGER (self),
+ EPHY_SYNCHRONIZABLE (record));
+ else if (remote_last_visit_time > local_last_visit_time)
+ ephy_history_service_visit_url (self->service, remote_url,
+ remote_id, remote_last_visit_time,
+ EPHY_PAGE_VISIT_LINK, FALSE);
+ } else {
+ record = get_record_by_url (local_records, remote_url);
+ if (record) {
+ /* Different ID, same URL. Keep local ID. */
+ g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
+ ephy_history_manager_handle_different_id_same_url (self, record, l->data);
+ to_upload = g_slist_prepend (to_upload, g_object_ref (l->data));
+ } else {
+ /* Different ID, different URL. This is a new record. */
+ if (remote_last_visit_time > 0)
+ ephy_history_service_visit_url (self->service, remote_url,
+ remote_id, remote_last_visit_time,
+ EPHY_PAGE_VISIT_LINK, FALSE);
+ }
+ }
+ }
+
+ return to_upload;
+}
+
+static void
+merge_history_cb (EphyHistoryService *service,
+ gboolean success,
+ GList *urls,
+ MergeHistoryAsyncData *data)
+{
+ GSList *records = NULL;
+ GSList *to_upload = NULL;
+
+ if (!success) {
+ g_warning ("Failed to retrieve URLs in history");
+ goto out;
+ }
+
+ for (GList *l = urls; l && l->data; l = l->next) {
+ EphyHistoryURL *url = (EphyHistoryURL *)l->data;
+ records = g_slist_prepend (records, ephy_history_record_new (url->sync_id,
+ url->title,
+ url->url,
+ url->last_visit_time));
+ }
+
+ if (data->is_initial)
+ to_upload = ephy_history_manager_handle_initial_merge (data->manager,
+ records,
+ data->remotes_updated);
+ else
+ to_upload = ephy_history_manager_handle_regular_merge (data->manager,
+ records,
+ data->remotes_deleted,
+ data->remotes_updated);
+
+out:
+ data->callback (to_upload, TRUE, data->user_data);
+
+ g_list_free_full (urls, (GDestroyNotify)ephy_history_url_free);
+ g_slist_free_full (records, g_object_unref);
+ merge_history_async_data_free (data);
+}
+
+static void
+synchronizable_manager_merge (EphySynchronizableManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated,
+ EphySynchronizableManagerMergeCallback callback,
+ gpointer user_data)
+{
+ EphyHistoryManager *self = EPHY_HISTORY_MANAGER (manager);
+
+ ephy_history_service_find_urls (self->service, -1, -1, -1, 0, NULL,
+ EPHY_HISTORY_SORT_MOST_RECENTLY_VISITED, NULL,
+ (EphyHistoryJobCallback)merge_history_cb,
+ merge_history_async_data_new (self,
+ is_initial,
+ remotes_deleted,
+ remotes_updated,
+ callback,
+ user_data));
+}
+
+static void
+ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface)
+{
+ iface->get_collection_name = synchronizable_manager_get_collection_name;
+ iface->get_synchronizable_type = synchronizable_manager_get_synchronizable_type;
+ iface->is_initial_sync = synchronizable_manager_is_initial_sync;
+ iface->set_is_initial_sync = synchronizable_manager_set_is_initial_sync;
+ iface->get_sync_time = synchronizable_manager_get_sync_time;
+ iface->set_sync_time = synchronizable_manager_set_sync_time;
+ iface->add = synchronizable_manager_add;
+ iface->remove = synchronizable_manager_remove;
+ iface->save = synchronizable_manager_save;
+ iface->merge = synchronizable_manager_merge;
+}
diff --git a/lib/sync/ephy-history-manager.h b/lib/sync/ephy-history-manager.h
new file mode 100644
index 0000000..30f688c
--- /dev/null
+++ b/lib/sync/ephy-history-manager.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-history-record.h"
+#include "ephy-history-service.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_HISTORY_MANAGER (ephy_history_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyHistoryManager, ephy_history_manager, EPHY, HISTORY_MANAGER, GObject)
+
+EphyHistoryManager *ephy_history_manager_new (EphyHistoryService *service);
+
+G_END_DECLS
diff --git a/lib/sync/ephy-history-record.c b/lib/sync/ephy-history-record.c
new file mode 100644
index 0000000..fc80392
--- /dev/null
+++ b/lib/sync/ephy-history-record.c
@@ -0,0 +1,404 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-history-record.h"
+
+#include "ephy-history-types.h"
+#include "ephy-synchronizable.h"
+
+struct _EphyHistoryRecord {
+ GObject parent_instance;
+
+ char *id;
+ char *title;
+ char *uri;
+ GSequence *visits;
+};
+
+static void json_serializable_iface_init (JsonSerializableIface *iface);
+static void ephy_synchronizable_iface_init (EphySynchronizableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphyHistoryRecord, ephy_history_record, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
+ json_serializable_iface_init)
+ G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE,
+ ephy_synchronizable_iface_init))
+
+enum {
+ PROP_0,
+ PROP_ID,
+ PROP_TITLE,
+ PROP_URI,
+ PROP_VISITS,
+ LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+typedef struct {
+ gint64 timestamp; /* UNIX time in microseconds. */
+ guint type; /* Transition type.*/
+} EphyHistoryRecordVisit;
+
+static EphyHistoryRecordVisit *
+ephy_history_record_visit_new (gint64 timestamp,
+ guint type)
+{
+ EphyHistoryRecordVisit *visit;
+
+ visit = g_slice_new (EphyHistoryRecordVisit);
+ visit->timestamp = timestamp;
+ visit->type = type;
+
+ return visit;
+}
+
+static void
+ephy_history_record_visit_free (EphyHistoryRecordVisit *visit)
+{
+ g_assert (visit);
+
+ g_slice_free (EphyHistoryRecordVisit, visit);
+}
+
+static int
+ephy_history_record_visit_compare (EphyHistoryRecordVisit *visit1,
+ EphyHistoryRecordVisit *visit2,
+ gpointer user_data)
+{
+ g_assert (visit1);
+ g_assert (visit2);
+
+ /* We keep visits sorted in descending order by timestamp. */
+ return visit2->timestamp - visit1->timestamp;
+}
+
+static void
+ephy_history_record_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphyHistoryRecord *self = EPHY_HISTORY_RECORD (object);
+
+ switch (prop_id) {
+ case PROP_ID:
+ g_free (self->id);
+ self->id = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_TITLE:
+ g_free (self->title);
+ self->title = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_URI:
+ g_free (self->uri);
+ self->uri = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_VISITS:
+ if (self->visits)
+ g_sequence_free (self->visits);
+ self->visits = g_value_get_pointer (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ephy_history_record_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphyHistoryRecord *self = EPHY_HISTORY_RECORD (object);
+
+ switch (prop_id) {
+ case PROP_ID:
+ g_value_set_string (value, self->id);
+ break;
+ case PROP_TITLE:
+ g_value_set_string (value, self->title);
+ break;
+ case PROP_URI:
+ g_value_set_string (value, self->uri);
+ break;
+ case PROP_VISITS:
+ g_value_set_pointer (value, self->visits);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ephy_history_record_dispose (GObject *object)
+{
+ EphyHistoryRecord *self = EPHY_HISTORY_RECORD (object);
+
+ g_clear_pointer (&self->id, g_free);
+ g_clear_pointer (&self->title, g_free);
+ g_clear_pointer (&self->uri, g_free);
+ g_clear_pointer (&self->visits, g_sequence_free);
+
+ G_OBJECT_CLASS (ephy_history_record_parent_class)->dispose (object);
+}
+
+static void
+ephy_history_record_class_init (EphyHistoryRecordClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = ephy_history_record_set_property;
+ object_class->get_property = ephy_history_record_get_property;
+ object_class->dispose = ephy_history_record_dispose;
+
+ obj_properties[PROP_ID] =
+ g_param_spec_string ("id",
+ "Id",
+ "Id of the history record",
+ "Default id",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+ obj_properties[PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title of the history record",
+ "Default title",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+ obj_properties[PROP_URI] =
+ g_param_spec_string ("histUri",
+ "History URI",
+ "URI of the history record",
+ "Default history uri",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+ obj_properties[PROP_VISITS] =
+ g_param_spec_pointer ("visits",
+ "Visits",
+ "An array of how and when URI of the history record was visited",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+}
+
+static void
+ephy_history_record_init (EphyHistoryRecord *self)
+{
+}
+
+EphyHistoryRecord *
+ephy_history_record_new (const char *id,
+ const char *title,
+ const char *uri,
+ gint64 last_visit_time)
+{
+ EphyHistoryRecordVisit *visit;
+ GSequence *visits;
+
+ /* We only use link transition for now. */
+ visit = ephy_history_record_visit_new (last_visit_time, EPHY_PAGE_VISIT_LINK);
+ visits = g_sequence_new ((GDestroyNotify)ephy_history_record_visit_free);
+ g_sequence_prepend (visits, visit);
+
+ return EPHY_HISTORY_RECORD (g_object_new (EPHY_TYPE_HISTORY_RECORD,
+ "id", id,
+ "title", title,
+ "histUri", uri,
+ "visits", visits,
+ NULL));
+}
+
+void
+ephy_history_record_set_id (EphyHistoryRecord *self,
+ const char *id)
+{
+ g_return_if_fail (EPHY_IS_HISTORY_RECORD (self));
+ g_return_if_fail (id);
+
+ g_free (self->id);
+ self->id = g_strdup (id);
+}
+
+const char *
+ephy_history_record_get_id (EphyHistoryRecord *self)
+{
+ g_return_val_if_fail (EPHY_IS_HISTORY_RECORD (self), NULL);
+
+ return self->id;
+}
+
+const char *
+ephy_history_record_get_title (EphyHistoryRecord *self)
+{
+ g_return_val_if_fail (EPHY_IS_HISTORY_RECORD (self), NULL);
+
+ return self->title;
+}
+
+const char *
+ephy_history_record_get_uri (EphyHistoryRecord *self)
+{
+ g_return_val_if_fail (EPHY_IS_HISTORY_RECORD (self), NULL);
+
+ return self->uri;
+}
+
+gint64
+ephy_history_record_get_last_visit_time (EphyHistoryRecord *self)
+{
+ EphyHistoryRecordVisit *visit;
+
+ g_return_val_if_fail (EPHY_IS_HISTORY_RECORD (self), -1);
+ g_return_val_if_fail (self->visits, -1);
+
+ if (g_sequence_is_empty (self->visits))
+ return -1;
+
+ /* Visits are sorted in descending order by date. */
+ visit = (EphyHistoryRecordVisit *)g_sequence_get (g_sequence_get_begin_iter (self->visits));
+
+ return visit->timestamp;
+}
+
+gboolean
+ephy_history_record_add_visit_time (EphyHistoryRecord *self,
+ gint64 visit_time)
+{
+ EphyHistoryRecordVisit *visit;
+
+ g_return_val_if_fail (EPHY_IS_HISTORY_RECORD (self), FALSE);
+
+ visit = ephy_history_record_visit_new (visit_time, EPHY_PAGE_VISIT_LINK);
+ if (g_sequence_lookup (self->visits, visit,
+ (GCompareDataFunc)ephy_history_record_visit_compare,
+ NULL)) {
+ ephy_history_record_visit_free (visit);
+ return FALSE;
+ }
+
+ g_sequence_insert_sorted (self->visits, visit,
+ (GCompareDataFunc)ephy_history_record_visit_compare,
+ NULL);
+
+ return TRUE;
+}
+
+static JsonNode *
+serializable_serialize_property (JsonSerializable *serializable,
+ const char *name,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ if (!g_strcmp0 (name, "visits")) {
+ JsonNode *node;
+ JsonArray *array;
+ GSequence *visits;
+ GSequenceIter *it;
+
+ node = json_node_new (JSON_NODE_ARRAY);
+ array = json_array_new ();
+ visits = g_value_get_pointer (value);
+
+ if (visits != NULL) {
+ for (it = g_sequence_get_begin_iter (visits); !g_sequence_iter_is_end (it); it = g_sequence_iter_next
(it)) {
+ EphyHistoryRecordVisit *visit = g_sequence_get (it);
+ JsonObject *object = json_object_new ();
+ json_object_set_int_member (object, "date", visit->timestamp);
+ json_object_set_int_member (object, "type", visit->type);
+ json_array_add_object_element (array, object);
+ }
+ }
+
+ json_node_set_array (node, array);
+
+ return node;
+ }
+
+ return json_serializable_default_serialize_property (serializable, name, value, pspec);
+}
+
+static gboolean
+serializable_deserialize_property (JsonSerializable *serializable,
+ const char *name,
+ GValue *value,
+ GParamSpec *pspec,
+ JsonNode *node)
+{
+ if (!g_strcmp0 (name, "visits")) {
+ JsonArray *array;
+ GSequence *visits;
+
+ array = json_node_get_array (node);
+ visits = g_sequence_new ((GDestroyNotify)ephy_history_record_visit_free);
+
+ for (guint i = 0; i < json_array_get_length (array); i++) {
+ JsonObject *object = json_node_get_object (json_array_get_element (array, i));
+ gint64 timestamp = json_object_get_int_member (object, "date");
+ guint type = json_object_get_int_member (object, "type");
+ EphyHistoryRecordVisit *visit = ephy_history_record_visit_new (timestamp, type);
+ g_sequence_insert_sorted (visits, visit, (GCompareDataFunc)ephy_history_record_visit_compare, NULL);
+ }
+
+ g_value_set_pointer (value, visits);
+
+ return TRUE;
+ }
+
+ return json_serializable_default_deserialize_property (serializable, name, value, pspec, node);
+}
+
+static void
+json_serializable_iface_init (JsonSerializableIface *iface)
+{
+ iface->serialize_property = serializable_serialize_property;
+ iface->deserialize_property = serializable_deserialize_property;
+}
+
+static const char *
+synchronizable_get_id (EphySynchronizable *synchronizable)
+{
+ return ephy_history_record_get_id (EPHY_HISTORY_RECORD (synchronizable));
+}
+
+static double
+synchronizable_get_server_time_modified (EphySynchronizable *synchronizable)
+{
+ /* No implementation.
+ * We don't care about the server time modified of history records.
+ */
+ return 0;
+}
+
+static void
+synchronizable_set_server_time_modified (EphySynchronizable *synchronizable,
+ double server_time_modified)
+{
+ /* No implementation.
+ * We don't care about the server time modified of history records.
+ */
+}
+
+static void
+ephy_synchronizable_iface_init (EphySynchronizableInterface *iface)
+{
+ iface->get_id = synchronizable_get_id;
+ iface->get_server_time_modified = synchronizable_get_server_time_modified;
+ iface->set_server_time_modified = synchronizable_set_server_time_modified;
+ iface->to_bso = ephy_synchronizable_default_to_bso;
+}
diff --git a/lib/sync/ephy-history-record.h b/lib/sync/ephy-history-record.h
new file mode 100644
index 0000000..37e64a6
--- /dev/null
+++ b/lib/sync/ephy-history-record.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_HISTORY_RECORD (ephy_history_record_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyHistoryRecord, ephy_history_record, EPHY, HISTORY_RECORD, GObject)
+
+EphyHistoryRecord *ephy_history_record_new (const char *id,
+ const char *title,
+ const char *history_uri,
+ gint64 last_visit_time);
+void ephy_history_record_set_id (EphyHistoryRecord *self,
+ const char *id);
+const char *ephy_history_record_get_id (EphyHistoryRecord *self);
+const char *ephy_history_record_get_title (EphyHistoryRecord *self);
+const char *ephy_history_record_get_uri (EphyHistoryRecord *self);
+gint64 ephy_history_record_get_last_visit_time (EphyHistoryRecord *self);
+gboolean ephy_history_record_add_visit_time (EphyHistoryRecord *self,
+ gint64 visit_time);
+
+G_END_DECLS
diff --git a/lib/sync/ephy-password-manager.c b/lib/sync/ephy-password-manager.c
index a84384a..3f33c5d 100644
--- a/lib/sync/ephy-password-manager.c
+++ b/lib/sync/ephy-password-manager.c
@@ -446,7 +446,7 @@ update_password_cb (GSList *records,
record = EPHY_PASSWORD_RECORD (records->data);
ephy_password_record_set_password (record, data->password);
ephy_password_manger_store_record (data->manager, record);
- g_signal_emit_by_name (data->manager, "synchronizable-modified", record);
+ g_signal_emit_by_name (data->manager, "synchronizable-modified", record, FALSE);
g_slist_free_full (records, g_object_unref);
update_password_async_data_free (data);
@@ -492,7 +492,7 @@ ephy_password_manager_save (EphyPasswordManager *self,
username_field, password_field,
timestamp, timestamp);
ephy_password_manger_store_record (self, record);
- g_signal_emit_by_name (self, "synchronizable-modified", record);
+ g_signal_emit_by_name (self, "synchronizable-modified", record, FALSE);
g_free (hostname);
g_free (uuid);
@@ -1063,7 +1063,7 @@ merge_cb (GSList *records,
data->remotes_deleted,
data->remotes_updated);
- data->callback (to_upload, data->user_data);
+ data->callback (to_upload, FALSE, data->user_data);
g_slist_free_full (records, g_object_unref);
merge_passwords_async_data_free (data);
diff --git a/lib/sync/ephy-sync-crypto.c b/lib/sync/ephy-sync-crypto.c
index 7607053..d7b6a33 100644
--- a/lib/sync/ephy-sync-crypto.c
+++ b/lib/sync/ephy-sync-crypto.c
@@ -21,6 +21,8 @@
#include "config.h"
#include "ephy-sync-crypto.h"
+#include "ephy-sync-utils.h"
+
#include <glib/gstdio.h>
#include <inttypes.h>
#include <libsoup/soup.h>
@@ -31,9 +33,6 @@
#define HAWK_VERSION 1
#define NONCE_LEN 6
#define IV_LEN 16
-#define SYNC_ID_LEN 12
-
-static const char hex_digits[] = "0123456789abcdef";
SyncCryptoHawkOptions *
ephy_sync_crypto_hawk_options_new (const char *app,
@@ -204,8 +203,8 @@ ephy_sync_crypto_key_bundle_from_array (JsonArray *array)
aes_key = g_base64_decode (json_array_get_string_element (array, 0), &len);
hmac_key = g_base64_decode (json_array_get_string_element (array, 1), &len);
- aes_key_hex = ephy_sync_crypto_encode_hex (aes_key, 32);
- hmac_key_hex = ephy_sync_crypto_encode_hex (hmac_key, 32);
+ aes_key_hex = ephy_sync_utils_encode_hex (aes_key, 32);
+ hmac_key_hex = ephy_sync_utils_encode_hex (hmac_key, 32);
bundle = ephy_sync_crypto_key_bundle_new (aes_key_hex, hmac_key_hex);
g_free (aes_key);
@@ -388,7 +387,7 @@ ephy_sync_crypto_calculate_payload_hash (const char *payload,
HAWK_VERSION, content, payload);
digest_hex = g_compute_checksum_for_string (G_CHECKSUM_SHA256, update, -1);
- digest = ephy_sync_crypto_decode_hex (digest_hex);
+ digest = ephy_sync_utils_decode_hex (digest_hex);
hash = g_base64_encode (digest, g_checksum_type_get_length (G_CHECKSUM_SHA256));
g_free (content);
@@ -419,7 +418,7 @@ ephy_sync_crypto_calculate_mac (const char *type,
digest_hex = g_compute_hmac_for_string (G_CHECKSUM_SHA256,
key, key_len,
normalized, -1);
- digest = ephy_sync_crypto_decode_hex (digest_hex);
+ digest = ephy_sync_utils_decode_hex (digest_hex);
mac = g_base64_encode (digest, g_checksum_type_get_length (G_CHECKSUM_SHA256));
g_free (normalized);
@@ -517,7 +516,7 @@ ephy_sync_crypto_hkdf (const guint8 *in,
prk_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
salt, salt_len,
in, in_len);
- prk = ephy_sync_crypto_decode_hex (prk_hex);
+ prk = ephy_sync_utils_decode_hex (prk_hex);
/* Step 2: Expand */
counter = 1;
@@ -539,7 +538,7 @@ ephy_sync_crypto_hkdf (const guint8 *in,
tmp_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
prk, hash_len,
data, data_len);
- tmp = ephy_sync_crypto_decode_hex (tmp_hex);
+ tmp = ephy_sync_utils_decode_hex (tmp_hex);
memcpy (out_full + i * hash_len, tmp, hash_len);
g_free (data);
@@ -555,26 +554,6 @@ ephy_sync_crypto_hkdf (const guint8 *in,
g_free (out_full);
}
-static void
-ephy_sync_crypto_b64_to_b64_urlsafe (char *text)
-{
- g_assert (text);
-
- /* Replace '+' with '-' and '/' with '_' */
- g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=/", '-');
- g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=-", '_');
-}
-
-static void
-ephy_sync_crypto_b64_urlsafe_to_b64 (char *text)
-{
- g_assert (text);
-
- /* Replace '-' with '+' and '_' with '/' */
- g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=_", '+');
- g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=+", '/');
-}
-
static guint8 *
ephy_sync_crypto_pad (const char *text,
gsize block_len,
@@ -698,27 +677,6 @@ ephy_sync_crypto_hmac_is_valid (const char *text,
return retval;
}
-/*
- * This function is required by Nettle's RSA support.
- * From Nettle's documentation: random_ctx and random is a randomness generator.
- * random(random_ctx, length, dst) should generate length random octets and store them at dst.
- * We don't really use random_ctx, since we have /dev/urandom available.
- */
-static void
-ephy_sync_crypto_random_bytes_gen (void *random_ctx,
- gsize length,
- guint8 *dst)
-{
- FILE *fp;
-
- g_assert (length > 0);
- g_assert (dst);
-
- fp = fopen ("/dev/urandom", "r");
- fread (dst, sizeof (guint8), length, fp);
- fclose (fp);
-}
-
void
ephy_sync_crypto_process_key_fetch_token (const char *key_fetch_token,
guint8 **token_id,
@@ -740,7 +698,7 @@ ephy_sync_crypto_process_key_fetch_token (const char *key_fetch_token,
g_return_if_fail (resp_hmac_key);
g_return_if_fail (resp_xor_key);
- kft = ephy_sync_crypto_decode_hex (key_fetch_token);
+ kft = ephy_sync_utils_decode_hex (key_fetch_token);
info_kft = ephy_sync_crypto_kw ("keyFetchToken");
info_keys = ephy_sync_crypto_kw ("account/keys");
out1 = g_malloc (3 * token_len);
@@ -794,7 +752,7 @@ ephy_sync_crypto_process_session_token (const char *session_token,
g_return_if_fail (req_hmac_key);
g_return_if_fail (request_key);
- st = ephy_sync_crypto_decode_hex (session_token);
+ st = ephy_sync_utils_decode_hex (session_token);
info = ephy_sync_crypto_kw ("sessionToken");
out = g_malloc (3 * token_len);
@@ -841,7 +799,7 @@ ephy_sync_crypto_compute_sync_keys (const char *bundle_hex,
g_return_val_if_fail (kA, FALSE);
g_return_val_if_fail (kB, FALSE);
- bundle = ephy_sync_crypto_decode_hex (bundle_hex);
+ bundle = ephy_sync_utils_decode_hex (bundle_hex);
ciphertext = g_malloc (2 * key_len);
resp_hmac = g_malloc (key_len);
@@ -851,7 +809,7 @@ ephy_sync_crypto_compute_sync_keys (const char *bundle_hex,
resp_hmac_2_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
resp_hmac_key, key_len,
ciphertext, 2 * key_len);
- resp_hmac_2 = ephy_sync_crypto_decode_hex (resp_hmac_2_hex);
+ resp_hmac_2 = ephy_sync_utils_decode_hex (resp_hmac_2_hex);
if (!ephy_sync_crypto_equals (resp_hmac, resp_hmac_2, key_len)) {
g_warning ("HMAC values differs from the one expected");
retval = FALSE;
@@ -904,14 +862,14 @@ ephy_sync_crypto_derive_key_bundle (const guint8 *key,
prk_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
salt, key_len,
key, key_len);
- prk = ephy_sync_crypto_decode_hex (prk_hex);
+ prk = ephy_sync_utils_decode_hex (prk_hex);
tmp = ephy_sync_crypto_concat_bytes ((guint8 *)info, strlen (info),
"\x01", 1,
NULL);
aes_key_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
prk, key_len,
tmp, strlen (info) + 1);
- aes_key = ephy_sync_crypto_decode_hex (aes_key_hex);
+ aes_key = ephy_sync_utils_decode_hex (aes_key_hex);
g_free (tmp);
tmp = ephy_sync_crypto_concat_bytes (aes_key, key_len,
(guint8 *)info, strlen (info),
@@ -945,10 +903,10 @@ ephy_sync_crypto_generate_crypto_keys (gsize key_len)
char *payload;
aes_key = g_malloc (key_len);
- ephy_sync_crypto_random_bytes_gen (NULL, key_len, aes_key);
+ ephy_sync_utils_generate_random_bytes (NULL, key_len, aes_key);
aes_key_b64 = g_base64_encode (aes_key, key_len);
hmac_key = g_malloc (key_len);
- ephy_sync_crypto_random_bytes_gen (NULL, key_len, hmac_key);
+ ephy_sync_utils_generate_random_bytes (NULL, key_len, hmac_key);
hmac_key_b64 = g_base64_encode (hmac_key, key_len);
node = json_node_new (JSON_NODE_OBJECT);
@@ -1014,8 +972,8 @@ ephy_sync_crypto_decrypt_record (const char *payload,
}
/* Get the encryption key and the HMAC key. */
- aes_key = ephy_sync_crypto_decode_hex (bundle->aes_key_hex);
- hmac_key = ephy_sync_crypto_decode_hex (bundle->hmac_key_hex);
+ aes_key = ephy_sync_utils_decode_hex (bundle->aes_key_hex);
+ hmac_key = ephy_sync_utils_decode_hex (bundle->hmac_key_hex);
/* Under no circumstances should a client try to decrypt a record
* if the HMAC verification fails. */
@@ -1063,12 +1021,12 @@ ephy_sync_crypto_encrypt_record (const char *cleartext,
g_return_val_if_fail (bundle, NULL);
/* Get the encryption key and the HMAC key. */
- aes_key = ephy_sync_crypto_decode_hex (bundle->aes_key_hex);
- hmac_key = ephy_sync_crypto_decode_hex (bundle->hmac_key_hex);
+ aes_key = ephy_sync_utils_decode_hex (bundle->aes_key_hex);
+ hmac_key = ephy_sync_utils_decode_hex (bundle->hmac_key_hex);
/* Generate a random 16 bytes initialization vector. */
iv = g_malloc (IV_LEN);
- ephy_sync_crypto_random_bytes_gen (NULL, IV_LEN, iv);
+ ephy_sync_utils_generate_random_bytes (NULL, IV_LEN, iv);
/* Encrypt the record using the AES key. */
ciphertext = ephy_sync_crypto_aes_256_encrypt (cleartext, aes_key,
@@ -1142,8 +1100,8 @@ ephy_sync_crypto_compute_hawk_header (const char *url,
nonce = g_strdup (options->nonce);
} else {
bytes = g_malloc (NONCE_LEN / 2);
- ephy_sync_crypto_random_bytes_gen (NULL, NONCE_LEN / 2, bytes);
- nonce = ephy_sync_crypto_encode_hex (bytes, NONCE_LEN / 2);
+ ephy_sync_utils_generate_random_bytes (NULL, NONCE_LEN / 2, bytes);
+ nonce = ephy_sync_utils_encode_hex (bytes, NONCE_LEN / 2);
g_free (bytes);
}
@@ -1237,7 +1195,7 @@ ephy_sync_crypto_generate_rsa_key_pair (void)
/* Key sizes below 2048 are considered breakable and should not be used. */
success = rsa_generate_keypair (&public, &private,
- NULL, ephy_sync_crypto_random_bytes_gen,
+ NULL, ephy_sync_utils_generate_random_bytes,
NULL, NULL, 2048, 0);
/* Given correct parameters, this never fails. */
g_assert (success);
@@ -1274,18 +1232,18 @@ ephy_sync_crypto_create_assertion (const char *certificate,
/* Encode the header and body to base64 url safe and join them. */
expires_at = g_get_real_time () / 1000 + seconds * 1000;
body = g_strdup_printf ("{\"exp\": %lu, \"aud\": \"%s\"}", expires_at, audience);
- body_b64 = ephy_sync_crypto_base64_urlsafe_encode ((guint8 *)body, strlen (body), TRUE);
- header_b64 = ephy_sync_crypto_base64_urlsafe_encode ((guint8 *)header, strlen (header), TRUE);
+ body_b64 = ephy_sync_utils_base64_urlsafe_encode ((guint8 *)body, strlen (body), TRUE);
+ header_b64 = ephy_sync_utils_base64_urlsafe_encode ((guint8 *)header, strlen (header), TRUE);
to_sign = g_strdup_printf ("%s.%s", header_b64, body_b64);
/* Compute the SHA256 hash of the message to be signed. */
digest_hex = g_compute_checksum_for_string (G_CHECKSUM_SHA256, to_sign, -1);
- digest = ephy_sync_crypto_decode_hex (digest_hex);
+ digest = ephy_sync_utils_decode_hex (digest_hex);
/* Use the provided key pair to RSA sign the message. */
mpz_init (signature);
success = rsa_sha256_sign_digest_tr (&rsa_key_pair->public, &rsa_key_pair->private,
- NULL, ephy_sync_crypto_random_bytes_gen,
+ NULL, ephy_sync_utils_generate_random_bytes,
digest, signature);
/* Given correct parameters, this never fails. */
g_assert (success);
@@ -1297,7 +1255,7 @@ ephy_sync_crypto_create_assertion (const char *certificate,
g_assert (count == expected_size);
/* Join certificate, header, body and signed message to create the assertion. */
- sig_b64 = ephy_sync_crypto_base64_urlsafe_encode (sig, count, TRUE);
+ sig_b64 = ephy_sync_utils_base64_urlsafe_encode (sig, count, TRUE);
assertion = g_strdup_printf ("%s~%s.%s.%s", certificate, header_b64, body_b64, sig_b64);
g_free (body);
@@ -1312,118 +1270,3 @@ ephy_sync_crypto_create_assertion (const char *certificate,
return assertion;
}
-
-char *
-ephy_sync_crypto_base64_urlsafe_encode (const guint8 *data,
- gsize data_len,
- gboolean strip)
-{
- char *base64;
- char *out;
- gsize start = 0;
- gssize end;
-
- g_return_val_if_fail (data, NULL);
-
- base64 = g_base64_encode (data, data_len);
- end = strlen (base64) - 1;
-
- /* Strip the data of any leading or trailing '=' characters. */
- if (strip) {
- while (start < strlen (base64) && base64[start] == '=')
- start++;
-
- while (end >= 0 && base64[end] == '=')
- end--;
- }
-
- out = g_strndup (base64 + start, end - start + 1);
- ephy_sync_crypto_b64_to_b64_urlsafe (out);
-
- g_free (base64);
-
- return out;
-}
-
-guint8 *
-ephy_sync_crypto_base64_urlsafe_decode (const char *text,
- gsize *out_len,
- gboolean fill)
-{
- guint8 *out;
- char *to_decode;
- char *suffix = NULL;
-
- g_return_val_if_fail (text, NULL);
- g_return_val_if_fail (out_len, NULL);
-
- /* Fill the text with trailing '=' characters up to the proper length. */
- if (fill)
- suffix = g_strnfill ((4 - strlen (text) % 4) % 4, '=');
-
- to_decode = g_strconcat (text, suffix, NULL);
- ephy_sync_crypto_b64_urlsafe_to_b64 (to_decode);
- out = g_base64_decode (to_decode, out_len);
-
- g_free (suffix);
- g_free (to_decode);
-
- return out;
-}
-
-char *
-ephy_sync_crypto_encode_hex (const guint8 *data,
- gsize data_len)
-{
- char *retval;
-
- g_return_val_if_fail (data, NULL);
-
- retval = g_malloc (data_len * 2 + 1);
- for (gsize i = 0; i < data_len; i++) {
- guint8 byte = data[i];
-
- retval[2 * i] = hex_digits[byte >> 4];
- retval[2 * i + 1] = hex_digits[byte & 0xf];
- }
- retval[data_len * 2] = 0;
-
- return retval;
-}
-
-guint8 *
-ephy_sync_crypto_decode_hex (const char *hex)
-{
- guint8 *retval;
-
- g_return_val_if_fail (hex, NULL);
-
- retval = g_malloc (strlen (hex) / 2);
- for (gsize i = 0, j = 0; i < strlen (hex); i += 2, j++)
- sscanf (hex + i, "%2hhx", retval + j);
-
- return retval;
-}
-
-char *
-ephy_sync_crypto_get_random_sync_id (void)
-{
- char *id;
- char *base64;
- guint8 *bytes;
- gsize bytes_len;
-
- /* The sync id is a base64-urlsafe string. Base64 uses 4 chars to represent 3 bytes,
- * therefore we need ceil(len * 3 / 4) bytes to cover the requested length. */
- bytes_len = (SYNC_ID_LEN + 3) / 4 * 3;
- bytes = g_malloc (bytes_len);
-
- ephy_sync_crypto_random_bytes_gen (NULL, bytes_len, bytes);
- base64 = ephy_sync_crypto_base64_urlsafe_encode (bytes, bytes_len, FALSE);
- id = g_strndup (base64, SYNC_ID_LEN);
-
- g_free (base64);
- g_free (bytes);
-
- return id;
-}
diff --git a/lib/sync/ephy-sync-crypto.h b/lib/sync/ephy-sync-crypto.h
index f753e3d..3b275ce 100644
--- a/lib/sync/ephy-sync-crypto.h
+++ b/lib/sync/ephy-sync-crypto.h
@@ -116,15 +116,5 @@ char *ephy_sync_crypto_create_assertion (const char
const char *audience,
guint64 duration,
SyncCryptoRSAKeyPair *rsa_key_pair);
-char *ephy_sync_crypto_base64_urlsafe_encode (const guint8 *data,
- gsize data_len,
- gboolean strip);
-guint8 *ephy_sync_crypto_base64_urlsafe_decode (const char *text,
- gsize *out_len,
- gboolean fill);
-char *ephy_sync_crypto_encode_hex (const guint8 *data,
- gsize data_len);
-guint8 *ephy_sync_crypto_decode_hex (const char *hex);
-char *ephy_sync_crypto_get_random_sync_id (void);
G_END_DECLS
diff --git a/lib/sync/ephy-sync-service.c b/lib/sync/ephy-sync-service.c
index 34b5703..bb56707 100644
--- a/lib/sync/ephy-sync-service.c
+++ b/lib/sync/ephy-sync-service.c
@@ -26,6 +26,7 @@
#include "ephy-notification.h"
#include "ephy-settings.h"
#include "ephy-sync-crypto.h"
+#include "ephy-sync-utils.h"
#include <glib/gi18n.h>
#include <json-glib/json-glib.h>
@@ -564,8 +565,8 @@ ephy_sync_service_certificate_is_valid (EphySyncService *self,
g_assert (certificate);
pieces = g_strsplit (certificate, ".", 0);
- header = (char *)ephy_sync_crypto_base64_urlsafe_decode (pieces[0], &len, TRUE);
- payload = (char *)ephy_sync_crypto_base64_urlsafe_decode (pieces[1], &len, TRUE);
+ header = (char *)ephy_sync_utils_base64_urlsafe_decode (pieces[0], &len, TRUE);
+ payload = (char *)ephy_sync_utils_base64_urlsafe_decode (pieces[1], &len, TRUE);
parser = json_parser_new ();
json_parser_load_from_data (parser, header, -1, &error);
@@ -662,7 +663,7 @@ ephy_sync_service_destroy_session (EphySyncService *self,
url = g_strdup_printf ("%ssession/destroy", FIREFOX_ACCOUNTS_SERVER_URL);
ephy_sync_crypto_process_session_token (session_token, &token_id,
&req_hmac_key, &request_key, 32);
- token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+ token_id_hex = ephy_sync_utils_encode_hex (token_id, 32);
msg = soup_message_new (SOUP_METHOD_POST, url);
soup_message_set_request (msg, content_type, SOUP_MEMORY_STATIC,
@@ -820,7 +821,7 @@ ephy_sync_service_obtain_storage_credentials (EphySyncService *self)
audience = get_audience (TOKEN_SERVER_URL);
assertion = ephy_sync_crypto_create_assertion (self->certificate, audience,
300, self->rsa_key_pair);
- key_b = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+ key_b = ephy_sync_utils_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
hashed_key_b = g_compute_checksum_for_data (G_CHECKSUM_SHA256, key_b, 32);
client_state = g_strndup (hashed_key_b, 32);
authorization = g_strdup_printf ("BrowserID %s", assertion);
@@ -934,7 +935,7 @@ ephy_sync_service_obtain_signed_certificate (EphySyncService *self)
session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
ephy_sync_crypto_process_session_token (session_token, &token_id,
&req_hmac_key, &request_key, 32);
- token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+ token_id_hex = ephy_sync_utils_encode_hex (token_id, 32);
n = mpz_get_str (NULL, 10, self->rsa_key_pair->public.n);
e = mpz_get_str (NULL, 10, self->rsa_key_pair->public.e);
@@ -1050,7 +1051,6 @@ ephy_sync_service_delete_synchronizable (EphySyncService *self,
record = json_to_string (node, FALSE);
bundle = ephy_sync_service_get_key_bundle (self, collection);
payload = ephy_sync_crypto_encrypt_record (record, bundle);
- json_object_remove_member (object, "type");
json_object_remove_member (object, "deleted");
json_object_set_string_member (object, "payload", payload);
body = json_to_string (node, FALSE);
@@ -1185,7 +1185,8 @@ upload_synchronizable_cb (SoupSession *session,
static void
ephy_sync_service_upload_synchronizable (EphySyncService *self,
EphySynchronizableManager *manager,
- EphySynchronizable *synchronizable)
+ EphySynchronizable *synchronizable,
+ gboolean should_force)
{
SyncCryptoKeyBundle *bundle;
SyncAsyncData *data;
@@ -1195,6 +1196,7 @@ ephy_sync_service_upload_synchronizable (EphySyncService *self,
char *id_safe;
const char *collection;
const char *id;
+ double time_modified;
g_assert (EPHY_IS_SYNC_SERVICE (self));
g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
@@ -1213,8 +1215,9 @@ ephy_sync_service_upload_synchronizable (EphySyncService *self,
body = json_to_string (bso, FALSE);
LOG ("Uploading object with id %s...", id);
- ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_PUT, body, -1,
- ephy_synchronizable_get_server_time_modified (synchronizable),
+ time_modified = ephy_synchronizable_get_server_time_modified (synchronizable);
+ ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_PUT, body,
+ -1, should_force ? -1 : time_modified,
upload_synchronizable_cb, data);
g_free (id_safe);
@@ -1225,13 +1228,15 @@ ephy_sync_service_upload_synchronizable (EphySyncService *self,
}
static void
-merge_finished_cb (GSList *to_upload,
- gpointer user_data)
+merge_collection_finished_cb (GSList *to_upload,
+ gboolean should_force,
+ gpointer user_data)
{
SyncCollectionAsyncData *data = (SyncCollectionAsyncData *)user_data;
for (GSList *l = to_upload; l && l->data; l = l->next)
- ephy_sync_service_upload_synchronizable (data->service, data->manager, l->data);
+ ephy_sync_service_upload_synchronizable (data->service, data->manager,
+ l->data, should_force);
if (data->is_last)
g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
@@ -1302,7 +1307,7 @@ sync_collection_cb (SoupSession *session,
ephy_synchronizable_manager_merge (data->manager, data->is_initial,
data->remotes_deleted, data->remotes_updated,
- merge_finished_cb, data);
+ merge_collection_finished_cb, data);
goto out_no_error;
out_error:
@@ -1414,7 +1419,7 @@ ephy_sync_service_register_client_id (EphySyncService *self)
protocol = g_strdup_printf ("1.%d", STORAGE_VERSION);
json_array_add_string_element (array, protocol);
json_object_set_array_member (payload, "protocols", array);
- client_id = ephy_sync_crypto_get_random_sync_id ();
+ client_id = ephy_sync_utils_get_random_sync_id ();
json_object_set_string_member (payload, "id", client_id);
name = g_strdup_printf ("%s on Epiphany", client_id);
json_object_set_string_member (payload, "name", name);
@@ -1803,7 +1808,7 @@ ephy_sync_service_upload_crypto_keys_record (EphySyncService *self)
node = json_node_new (JSON_NODE_OBJECT);
record = json_object_new ();
payload_clear = ephy_sync_crypto_generate_crypto_keys (32);
- master_key = ephy_sync_crypto_decode_hex (master_key_hex);
+ master_key = ephy_sync_utils_decode_hex (master_key_hex);
bundle = ephy_sync_crypto_derive_key_bundle (master_key, 32);
payload_cipher = ephy_sync_crypto_encrypt_record (payload_clear, bundle);
json_object_set_string_member (record, "payload", payload_cipher);
@@ -1868,7 +1873,7 @@ obtain_crypto_keys_cb (SoupSession *session,
/* Derive the Sync Key bundle from kB. The bundle consists of two 32 bytes keys:
* the first one used as a symmetric encryption key (AES) and the second one
* used as a HMAC key. */
- key_b = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+ key_b = ephy_sync_utils_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
bundle = ephy_sync_crypto_derive_key_bundle (key_b, 32);
crypto_keys = ephy_sync_crypto_decrypt_record (payload, bundle);
if (!crypto_keys) {
@@ -1911,7 +1916,7 @@ make_engine_object (int version)
char *sync_id;
object = json_object_new ();
- sync_id = ephy_sync_crypto_get_random_sync_id ();
+ sync_id = ephy_sync_utils_get_random_sync_id ();
json_object_set_int_member (object, "version", version);
json_object_set_string_member (object, "syncID", sync_id);
@@ -1950,7 +1955,7 @@ ephy_sync_service_upload_meta_global_record (EphySyncService *self)
json_object_set_object_member (engines, "forms", make_engine_object (1));
json_object_set_object_member (payload, "engines", engines);
json_object_set_int_member (payload, "storageVersion", STORAGE_VERSION);
- sync_id = ephy_sync_crypto_get_random_sync_id ();
+ sync_id = ephy_sync_utils_get_random_sync_id ();
json_object_set_string_member (payload, "syncID", sync_id);
json_node_set_object (node, payload);
payload_str = json_to_string (node, FALSE);
@@ -2075,7 +2080,7 @@ ephy_sync_service_conclude_sign_in (EphySyncService *self,
g_assert (bundle);
/* Derive the master sync keys form the key bundle. */
- unwrap_key_b = ephy_sync_crypto_decode_hex (data->unwrap_b_key);
+ unwrap_key_b = ephy_sync_utils_decode_hex (data->unwrap_b_key);
if (!ephy_sync_crypto_compute_sync_keys (bundle, data->resp_hmac_key,
data->resp_xor_key, unwrap_key_b,
&key_a, &key_b, 32)) {
@@ -2088,7 +2093,7 @@ ephy_sync_service_conclude_sign_in (EphySyncService *self,
self->account = g_strdup (data->email);
ephy_sync_service_set_secret (self, secrets[UID], data->uid);
ephy_sync_service_set_secret (self, secrets[SESSION_TOKEN], data->session_token);
- key_b_hex = ephy_sync_crypto_encode_hex (key_b, 32);
+ key_b_hex = ephy_sync_utils_encode_hex (key_b, 32);
ephy_sync_service_set_secret (self, secrets[MASTER_KEY], key_b_hex);
ephy_sync_service_check_storage_version (self);
@@ -2190,7 +2195,7 @@ ephy_sync_service_do_sign_in (EphySyncService *self,
* See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#fetching-sync-keys */
ephy_sync_crypto_process_key_fetch_token (key_fetch_token, &token_id, &req_hmac_key,
&resp_hmac_key, &resp_xor_key, 32);
- token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+ token_id_hex = ephy_sync_utils_encode_hex (token_id, 32);
/* Get the master sync key bundle from the /account/keys endpoint. */
data = sign_in_async_data_new (self, email, uid,
@@ -2226,6 +2231,7 @@ synchronizable_deleted_cb (EphySynchronizableManager *manager,
static void
synchronizable_modified_cb (EphySynchronizableManager *manager,
EphySynchronizable *synchronizable,
+ gboolean should_force,
EphySyncService *self)
{
g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
@@ -2235,7 +2241,7 @@ synchronizable_modified_cb (EphySynchronizableManager *manager,
if (!ephy_sync_service_is_signed_in (self))
return;
- ephy_sync_service_upload_synchronizable (self, manager, synchronizable);
+ ephy_sync_service_upload_synchronizable (self, manager, synchronizable, should_force);
}
void
@@ -2290,12 +2296,12 @@ ephy_sync_service_do_sign_out (EphySyncService *self)
g_signal_handlers_disconnect_by_func (l->data, synchronizable_deleted_cb, self);
g_signal_handlers_disconnect_by_func (l->data, synchronizable_modified_cb, self);
}
- g_slist_free (self->managers);
- self->managers = NULL;
+ g_clear_pointer (&self->managers, g_slist_free);
g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER, "");
g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_INITIAL, TRUE);
g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_INITIAL, TRUE);
+ g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_HISTORY_INITIAL, TRUE);
}
void
diff --git a/lib/sync/ephy-synchronizable-manager.c b/lib/sync/ephy-synchronizable-manager.c
index 60212b7..5e2c70e 100644
--- a/lib/sync/ephy-synchronizable-manager.c
+++ b/lib/sync/ephy-synchronizable-manager.c
@@ -58,8 +58,9 @@ ephy_synchronizable_manager_default_init (EphySynchronizableManagerInterface *if
EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
- G_TYPE_NONE, 1,
- EPHY_TYPE_SYNCHRONIZABLE);
+ G_TYPE_NONE, 2,
+ EPHY_TYPE_SYNCHRONIZABLE,
+ G_TYPE_BOOLEAN);
}
/**
diff --git a/lib/sync/ephy-synchronizable-manager.h b/lib/sync/ephy-synchronizable-manager.h
index 8c36332..046c340 100644
--- a/lib/sync/ephy-synchronizable-manager.h
+++ b/lib/sync/ephy-synchronizable-manager.h
@@ -30,7 +30,7 @@ G_BEGIN_DECLS
G_DECLARE_INTERFACE (EphySynchronizableManager, ephy_synchronizable_manager, EPHY, SYNCHRONIZABLE_MANAGER,
GObject)
-typedef void (*EphySynchronizableManagerMergeCallback) (GSList *to_upload, gpointer user_data);
+typedef void (*EphySynchronizableManagerMergeCallback) (GSList *to_upload, gboolean should_force, gpointer
user_data);
struct _EphySynchronizableManagerInterface {
GTypeInterface parent_iface;
diff --git a/lib/sync/meson.build b/lib/sync/meson.build
index 658c17c..b728c39 100644
--- a/lib/sync/meson.build
+++ b/lib/sync/meson.build
@@ -1,4 +1,6 @@
libephysync_sources = [
+ 'ephy-history-manager.c',
+ 'ephy-history-record.c',
'ephy-password-manager.c',
'ephy-password-record.c',
'ephy-sync-crypto.c',
diff --git a/src/bookmarks/ephy-add-bookmark-popover.c b/src/bookmarks/ephy-add-bookmark-popover.c
index 76019ee..2bef611 100644
--- a/src/bookmarks/ephy-add-bookmark-popover.c
+++ b/src/bookmarks/ephy-add-bookmark-popover.c
@@ -27,6 +27,7 @@
#include "ephy-embed-container.h"
#include "ephy-location-entry.h"
#include "ephy-shell.h"
+#include "ephy-sync-utils.h"
struct _EphyAddBookmarkPopover {
GtkPopover parent_instance;
@@ -207,7 +208,7 @@ ephy_add_bookmark_popover_show (EphyAddBookmarkPopover *self)
bookmark = ephy_bookmarks_manager_get_bookmark_by_url (manager, address);
if (!bookmark) {
- char *id = ephy_sync_crypto_get_random_sync_id ();
+ char *id = ephy_sync_utils_get_random_sync_id ();
bookmark = ephy_bookmark_new (address,
ephy_embed_get_title (embed),
g_sequence_new (g_free),
diff --git a/src/bookmarks/ephy-bookmark-properties-grid.c b/src/bookmarks/ephy-bookmark-properties-grid.c
index 2a4d9ce..eedb1f1 100644
--- a/src/bookmarks/ephy-bookmark-properties-grid.c
+++ b/src/bookmarks/ephy-bookmark-properties-grid.c
@@ -419,7 +419,7 @@ ephy_bookmark_properties_grid_finalize (GObject *object)
EphyBookmarkPropertiesGrid *self = EPHY_BOOKMARK_PROPERTIES_GRID (object);
if (self->bookmark_is_modified && !self->bookmark_is_removed)
- g_signal_emit_by_name (self->manager, "synchronizable-modified", self->bookmark);
+ g_signal_emit_by_name (self->manager, "synchronizable-modified", self->bookmark, FALSE);
ephy_bookmarks_manager_save_to_file_async (self->manager, NULL,
ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
diff --git a/src/bookmarks/ephy-bookmarks-manager.c b/src/bookmarks/ephy-bookmarks-manager.c
index 7df400b..d6d2518 100644
--- a/src/bookmarks/ephy-bookmarks-manager.c
+++ b/src/bookmarks/ephy-bookmarks-manager.c
@@ -26,6 +26,7 @@
#include "ephy-debug.h"
#include "ephy-file-helpers.h"
#include "ephy-settings.h"
+#include "ephy-sync-utils.h"
#include "ephy-synchronizable-manager.h"
#include <string.h>
@@ -341,7 +342,7 @@ ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
ephy_bookmarks_manager_add_bookmark_internal (self, bookmark, TRUE);
- g_signal_emit_by_name (self, "synchronizable-modified", bookmark);
+ g_signal_emit_by_name (self, "synchronizable-modified", bookmark, FALSE);
}
void
@@ -358,7 +359,7 @@ ephy_bookmarks_manager_add_bookmarks (EphyBookmarksManager *self,
EphyBookmark *bookmark = g_sequence_get (iter);
ephy_bookmarks_manager_add_bookmark_internal (self, bookmark, FALSE);
- g_signal_emit_by_name (self, "synchronizable-modified", bookmark);
+ g_signal_emit_by_name (self, "synchronizable-modified", bookmark, FALSE);
}
ephy_bookmarks_manager_save_to_file_async (self, NULL,
@@ -781,7 +782,7 @@ ephy_bookmarks_manager_handle_initial_merge (EphyBookmarksManager *self,
ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), timestamp);
} else {
/* Same id, different url. Keep both and upload local one with new id. */
- char *new_id = ephy_sync_crypto_get_random_sync_id ();
+ char *new_id = ephy_sync_utils_get_random_sync_id ();
ephy_bookmark_set_id (bookmark, new_id);
ephy_bookmarks_manager_add_bookmark_internal (self, l->data, FALSE);
g_hash_table_add (dont_upload, g_strdup (id));
@@ -918,7 +919,7 @@ synchronizable_manager_merge (EphySynchronizableManager *manager,
remotes_updated,
remotes_deleted);
- callback (to_upload, user_data);
+ callback (to_upload, FALSE, user_data);
}
static void
diff --git a/src/ephy-history-dialog.c b/src/ephy-history-dialog.c
index 38f029b..e103f57 100644
--- a/src/ephy-history-dialog.c
+++ b/src/ephy-history-dialog.c
@@ -86,7 +86,8 @@ static GParamSpec *obj_properties[LAST_PROP];
typedef enum {
COLUMN_DATE,
COLUMN_NAME,
- COLUMN_LOCATION
+ COLUMN_LOCATION,
+ COLUMN_SYNC_ID
} EphyHistoryDialogColumns;
static gboolean
@@ -112,6 +113,7 @@ add_urls_source (EphyHistoryDialog *self)
COLUMN_DATE, url->last_visit_time,
COLUMN_NAME, url->title,
COLUMN_LOCATION, url->url,
+ COLUMN_SYNC_ID, url->sync_id,
-1);
self->urls = g_list_remove_link (self->urls, element);
ephy_history_url_free (url);
@@ -318,6 +320,7 @@ get_url_from_path (GtkTreeModel *model,
gtk_tree_model_get (model, &iter,
COLUMN_NAME, &url->title,
COLUMN_LOCATION, &url->url,
+ COLUMN_SYNC_ID, &url->sync_id,
-1);
return url;
}
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 0310705..894a017 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -57,6 +57,7 @@ struct _EphyShell {
GObject *lockdown;
EphyBookmarksManager *bookmarks_manager;
EphyPasswordManager *password_manager;
+ EphyHistoryManager *history_manager;
GNetworkMonitor *network_monitor;
GtkWidget *history_dialog;
GObject *prefs_dialog;
@@ -354,6 +355,9 @@ ephy_shell_startup (GApplication *application)
if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_ENABLED))
ephy_sync_service_register_manager (ephy_shell->sync_service,
EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_password_manager
(ephy_shell)));
+ if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_HISTORY_ENABLED))
+ ephy_sync_service_register_manager (ephy_shell->sync_service,
+ EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_history_manager
(ephy_shell)));
gtk_application_set_app_menu (GTK_APPLICATION (application),
G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu")));
@@ -624,6 +628,7 @@ ephy_shell_dispose (GObject *object)
g_clear_object (&shell->sync_service);
g_clear_object (&shell->bookmarks_manager);
g_clear_object (&shell->password_manager);
+ g_clear_object (&shell->history_manager);
g_slist_free_full (shell->open_uris_idle_ids, remove_open_uris_idle_cb);
shell->open_uris_idle_ids = NULL;
@@ -830,6 +835,31 @@ ephy_shell_get_password_manager (EphyShell *shell)
}
/**
+ * ephy_shell_get_history_manager:
+ * @shell: the #EphyShell
+ *
+ * Returns the history manager.
+ *
+ * Return value: (transfer none): An #EphyHistoryManager.
+ */
+EphyHistoryManager *
+ephy_shell_get_history_manager (EphyShell *shell)
+{
+ EphyEmbedShell *embed_shell;
+ EphyHistoryService *service;
+
+ g_return_val_if_fail (EPHY_IS_SHELL (shell), NULL);
+
+ if (shell->history_manager == NULL) {
+ embed_shell = ephy_embed_shell_get_default ();
+ service = ephy_embed_shell_get_global_history_service (embed_shell);
+ shell->history_manager = ephy_history_manager_new (service);
+ }
+
+ return shell->history_manager;
+}
+
+/**
* ephy_shell_get_net_monitor:
*
* Return value: (transfer none):
diff --git a/src/ephy-shell.h b/src/ephy-shell.h
index 6734d82..b7d4800 100644
--- a/src/ephy-shell.h
+++ b/src/ephy-shell.h
@@ -25,6 +25,7 @@
#include "ephy-bookmarks-manager.h"
#include "ephy-embed-shell.h"
#include "ephy-embed.h"
+#include "ephy-history-manager.h"
#include "ephy-password-manager.h"
#include "ephy-session.h"
#include "ephy-sync-service.h"
@@ -105,6 +106,8 @@ EphyBookmarksManager *ephy_shell_get_bookmarks_manager (EphyShell *shell);
EphyPasswordManager *ephy_shell_get_password_manager (EphyShell *shell);
+EphyHistoryManager *ephy_shell_get_history_manager (EphyShell *shell);
+
EphySyncService *ephy_shell_get_sync_service (EphyShell *shell);
GtkWidget *ephy_shell_get_history_dialog (EphyShell *shell);
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index 69e9557..e2984b0 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -122,6 +122,7 @@ struct _PrefsDialog {
GtkWidget *sync_with_firefox_checkbutton;
GtkWidget *sync_bookmarks_checkbutton;
GtkWidget *sync_passwords_checkbutton;
+ GtkWidget *sync_history_checkbutton;
GtkWidget *sync_frequency_5_min_radiobutton;
GtkWidget *sync_frequency_15_min_radiobutton;
GtkWidget *sync_frequency_30_min_radiobutton;
@@ -181,6 +182,8 @@ sync_collection_toggled_cb (GtkToggleButton *button,
manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_bookmarks_manager (ephy_shell_get_default ()));
else if (GTK_WIDGET (button) == dialog->sync_passwords_checkbutton)
manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_password_manager (ephy_shell_get_default ()));
+ else if (GTK_WIDGET (button) == dialog->sync_history_checkbutton)
+ manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_history_manager (ephy_shell_get_default ()));
if (gtk_toggle_button_get_active (button))
ephy_sync_service_register_manager (dialog->sync_service, manager);
@@ -233,6 +236,7 @@ sync_secrets_store_finished_cb (EphySyncService *service,
{
EphyBookmarksManager *bookmarks_manager;
EphyPasswordManager *password_manager;
+ EphyHistoryManager *history_manager;
g_assert (EPHY_IS_SYNC_SERVICE (service));
g_assert (EPHY_IS_PREFS_DIALOG (dialog));
@@ -267,6 +271,10 @@ sync_secrets_store_finished_cb (EphySyncService *service,
password_manager = ephy_shell_get_password_manager (ephy_shell_get_default ());
ephy_sync_service_register_manager (service, EPHY_SYNCHRONIZABLE_MANAGER (password_manager));
}
+ if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_HISTORY_ENABLED)) {
+ history_manager = ephy_shell_get_history_manager (ephy_shell_get_default ());
+ ephy_sync_service_register_manager (service, EPHY_SYNCHRONIZABLE_MANAGER (history_manager));
+ }
g_free (text);
g_free (user);
@@ -621,6 +629,7 @@ prefs_dialog_class_init (PrefsDialogClass *klass)
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_with_firefox_checkbutton);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_bookmarks_checkbutton);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_passwords_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_history_checkbutton);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_5_min_radiobutton);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_15_min_radiobutton);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_30_min_radiobutton);
@@ -1680,6 +1689,9 @@ setup_sync_page (PrefsDialog *dialog)
g_signal_connect_object (dialog->sync_passwords_checkbutton, "toggled",
G_CALLBACK (sync_collection_toggled_cb),
dialog, 0);
+ g_signal_connect_object (dialog->sync_history_checkbutton, "toggled",
+ G_CALLBACK (sync_collection_toggled_cb),
+ dialog, 0);
g_settings_bind (sync_settings,
EPHY_PREFS_SYNC_WITH_FIREFOX,
@@ -1696,6 +1708,11 @@ setup_sync_page (PrefsDialog *dialog)
dialog->sync_passwords_checkbutton,
"active",
G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (sync_settings,
+ EPHY_PREFS_SYNC_HISTORY_ENABLED,
+ dialog->sync_history_checkbutton,
+ "active",
+ G_SETTINGS_BIND_DEFAULT);
g_settings_bind_with_mapping (sync_settings,
EPHY_PREFS_SYNC_FREQUENCY,
dialog->sync_frequency_5_min_radiobutton,
diff --git a/src/profile-migrator/ephy-profile-migrator.c b/src/profile-migrator/ephy-profile-migrator.c
index effd7cc..d0591eb 100644
--- a/src/profile-migrator/ephy-profile-migrator.c
+++ b/src/profile-migrator/ephy-profile-migrator.c
@@ -41,7 +41,7 @@
#include "ephy-search-engine-manager.h"
#include "ephy-settings.h"
#include "ephy-sqlite-connection.h"
-#include "ephy-sync-crypto.h"
+#include "ephy-sync-utils.h"
#include "ephy-uri-tester-shared.h"
#include "ephy-web-app-utils.h"
@@ -648,7 +648,7 @@ parse_rdf_item (EphyBookmarksManager *manager,
char *id;
g_sequence_sort (tags, (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
- id = ephy_sync_crypto_get_random_sync_id ();
+ id = ephy_sync_utils_get_random_sync_id ();
bookmark = ephy_bookmark_new ((const char *)link, (const char *)title, tags, id);
ephy_bookmarks_manager_add_bookmark (manager, bookmark);
@@ -1102,7 +1102,7 @@ migrate_history_to_firefox_sync_history (void)
/* Set sync_id for each row. */
for (GSList *l = ids; l && l->data; l = l->next) {
- char *sync_id = ephy_sync_crypto_get_random_sync_id ();
+ char *sync_id = ephy_sync_utils_get_random_sync_id ();
char *sql = g_strdup_printf ("UPDATE urls SET sync_id = \"%s\" WHERE id=%d",
sync_id, GPOINTER_TO_INT (l->data));
diff --git a/src/resources/gtk/history-dialog.ui b/src/resources/gtk/history-dialog.ui
index b8a4b15..d86534d 100644
--- a/src/resources/gtk/history-dialog.ui
+++ b/src/resources/gtk/history-dialog.ui
@@ -9,6 +9,8 @@
<column type="gchararray"/>
<!-- column-name LOCATION -->
<column type="gchararray"/>
+ <!-- column-name SYNC_ID -->
+ <column type="gchararray"/>
</columns>
</object>
<menu id="treeview_popup_menu_model">
diff --git a/src/resources/gtk/prefs-dialog.ui b/src/resources/gtk/prefs-dialog.ui
index a1f619e..db6911f 100644
--- a/src/resources/gtk/prefs-dialog.ui
+++ b/src/resources/gtk/prefs-dialog.ui
@@ -922,6 +922,13 @@
<property name="use-underline">True</property>
</object>
</child>
+ <child>
+ <object class="GtkCheckButton" id="sync_history_checkbutton">
+ <property name="label" translatable="yes">_History</property>
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
</object>
</child>
<child>
diff --git a/tests/ephy-web-view-test.c b/tests/ephy-web-view-test.c
index b6b53aa..f94ddfc 100644
--- a/tests/ephy-web-view-test.c
+++ b/tests/ephy-web-view-test.c
@@ -406,10 +406,9 @@ test_ephy_web_view_provisional_load_failure_updates_back_forward_list (void)
}
static void
-visit_url_cb (EphyHistoryService *service,
- const char *url,
- EphyHistoryPageVisit visit_type,
- gpointer user_data)
+visit_url_cb (EphyHistoryService *service,
+ EphyHistoryURL *url,
+ gpointer user_data)
{
/* We are only loading an error page, this code should never be
* reached. */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]