[evolution-data-server/openismus-work: 3/3] Make local addressbook backend handle shared photo uris.



commit 5d1cd0c398be5adeea4f6a43e18389aaffcd1d7e
Author: Tristan Van Berkom <tristan van berkom gmail com>
Date:   Thu Jul 14 16:53:09 2011 -0400

    Make local addressbook backend handle shared photo uris.
    
    When shared uris specified in contacts belong to an outside
    source, the addressbook is not concerned, however when you
    try to share a uri that belongs to the addressbook, the
    addressbook is responsible for the refcounting involved on
    that uri (so it does not get prematurely deleted).
    
    This patch handles this case by creating a hard-link on disk
    and changing any incomming "shared uris" in the contact to
    point to the new hardlinks.
    
    Additionally, test-ebook-photo-is-uri.c has been extended to
    assert that after sharing an addressbook owned uri and deleting
    the original contact, the second contact's uri is still on disk.

 addressbook/backends/file/e-book-backend-file.c   |  193 ++++++++++++++++++++-
 addressbook/tests/ebook/test-ebook-photo-is-uri.c |  103 ++++++++++--
 2 files changed, 275 insertions(+), 21 deletions(-)
---
diff --git a/addressbook/backends/file/e-book-backend-file.c b/addressbook/backends/file/e-book-backend-file.c
index f6c7fac..dd39e8c 100644
--- a/addressbook/backends/file/e-book-backend-file.c
+++ b/addressbook/backends/file/e-book-backend-file.c
@@ -272,6 +272,9 @@ maybe_delete_unused_uris (EBookBackendFile *bf,
 	if (uri_photo)
 		maybe_delete_uri (bf, uri_photo);
 
+	if (uri_logo)
+		maybe_delete_uri (bf, uri_logo);
+
 	g_object_unref (old_contact);
 }
 
@@ -312,7 +315,6 @@ e_book_backend_file_extract_path_from_source (ESource      *source,
 
 static gchar *
 safe_name_for_photo (EBookBackendFile *bf,
-		     ESource          *source,
 		     EContact         *contact,
 		     EContactField     field)
 {
@@ -346,9 +348,87 @@ safe_name_for_photo (EBookBackendFile *bf,
 	return fullname;
 }
 
+static gchar *
+hard_link_photo (EBookBackendFile *bf,
+		 EContact         *contact,
+		 EContactField     field,
+		 const gchar      *src_filename,
+		 GError          **error)
+{
+	gchar *fullname = NULL, *uid, *str, *final;
+	gint   i = 0, ret;
+
+	uid = g_strdup (e_contact_get_const (contact, E_CONTACT_UID));
+	uid = g_strdelimit (uid, NULL, '_');
+
+	str = g_strdup (e_contact_field_name (field));
+	str = g_strdelimit (str, NULL, '_');
+
+	final = g_strdup_printf ("%s_%s", uid, str);
+
+	do {
+		gchar *filename = NULL;
+
+		g_free (fullname);
+
+		filename = g_strdup_printf ("%s_%02d.data", final, i);
+		fullname = g_build_filename (bf->priv->photo_dirname, filename, NULL);
+		g_free (filename);
+
+		i++;
+
+		ret = link (src_filename, fullname);
+
+	} while (ret < 0 && errno == EEXIST);
+
+	if (ret < 0) {
+		if (errno == EACCES || errno == EPERM) {
+			g_propagate_error (error, EDB_ERROR (PERMISSION_DENIED));
+		} else {
+			g_propagate_error (error, e_data_book_create_error_fmt
+					   (E_DATA_BOOK_STATUS_OTHER_ERROR, 
+					    "Failed to create hardlink for resource '%s': %s", 
+					    src_filename, g_strerror (errno)));
+		}
+		g_free (fullname);
+		fullname = NULL;
+	}
+
+	g_free (str);
+	g_free (uid);
+	g_free (final);
+
+	return fullname;
+}
+
+
+static gboolean 
+is_backend_owned_uri (EBookBackendFile *bf, 
+		      const gchar      *uri)
+{
+	gchar     *filename;
+	gchar     *dirname;
+	gboolean   owned_uri;
+
+	/* Errors converting from uri definitily indicate it was
+	 * not our uri to begin with, so just disregard this error. */
+	filename = g_filename_from_uri (uri, NULL, NULL);
+	if (!filename)
+		return FALSE;
+
+	dirname = g_path_get_dirname (filename);
+
+	owned_uri = (strcmp (dirname, bf->priv->photo_dirname) == 0);
+
+	g_free (filename);
+	g_free (dirname);
+
+	return owned_uri;
+}
+
+
 static PhotoModifiedStatus
 maybe_transform_vcard_field_for_photo (EBookBackendFile *bf,
-				       ESource          *source,
 				       EContact         *contact,
 				       EContactField     field,
 				       GError          **error)
@@ -369,7 +449,7 @@ maybe_transform_vcard_field_for_photo (EBookBackendFile *bf,
 		gchar         *uri;
 
 		/* XXX Create a good file extension based on mime type */
-		new_photo_path = safe_name_for_photo (bf, source, contact, field);
+		new_photo_path = safe_name_for_photo (bf, contact, field);
 
 		if ((uri = 
 		     g_filename_to_uri (new_photo_path, NULL, error)) == NULL) {
@@ -388,11 +468,109 @@ maybe_transform_vcard_field_for_photo (EBookBackendFile *bf,
 
 			e_contact_set (contact, field, new_photo);
 
+			d(g_print ("Backend modified incomming binary blob to be %s:\n", uri));
+
 			status = STATUS_MODIFIED;
 		}
 
 		g_free (uri);
 		g_free (new_photo_path);
+
+	} else { /* E_CONTACT_PHOTO_TYPE_URI */
+
+		DB                *db = bf->priv->file_db;
+		DBT                id_dbt, vcard_dbt;
+		gchar             *db_vcard = NULL;
+		gint               db_error;
+		const gchar       *uid;
+
+ 		EContact          *old_contact;
+		EContactPhoto     *old_photo, *new_photo;
+
+		/* First determine that the new contact uri points to our 'photos' directory,
+		 * if not then we do nothing
+		 */
+		if (!is_backend_owned_uri (bf, photo->data.uri))
+			return status;
+
+		/* Now check if the uri is changed from the BDB copy
+		 */
+		uid = e_contact_get_const (contact, E_CONTACT_UID);
+		if (uid == NULL) {
+			g_propagate_error (error, EDB_ERROR_EX (OTHER_ERROR, "No UID in the contact"));
+			return STATUS_ERROR;
+		}
+
+		string_to_dbt (uid, &id_dbt);
+		memset (&vcard_dbt, 0, sizeof (vcard_dbt));
+		vcard_dbt.flags = DB_DBT_MALLOC;
+		
+		db_error = db->get (db, NULL, &id_dbt, &vcard_dbt, 0);
+
+		if (db_error == 0) {
+			db_vcard = vcard_dbt.data;
+		} else {
+			g_warning (G_STRLOC ": db->get failed with %s", db_strerror (db_error));
+
+			db_error_to_gerror (db_error, error);
+			return STATUS_ERROR;
+		}
+
+		old_contact = create_contact (uid, db_vcard);
+		old_photo   = e_contact_get (old_contact, field);
+		g_free (db_vcard);
+
+		/* Unless we are receiving the same uri that we already have
+		 * stored in the BDB... */
+		if (!old_photo || old_photo->type == E_CONTACT_PHOTO_TYPE_INLINED ||
+		    strcmp (old_photo->data.uri, photo->data.uri) != 0) {
+			gchar *filename;
+			gchar *new_filename;
+			gchar *new_uri = NULL;
+
+			/* ... Assume that the incomming uri belongs to another contact
+			 * still in the BDB. Lets go ahead and create a hard link to the 
+			 * photo file and create a new name for the incomming uri, and
+			 * use that in the incomming contact to save in place.
+			 *
+			 * This piece of code is here to ensure there are no problems if
+			 * the libebook user decides to cross-reference and start "sharing"
+			 * uris that we've previously stored in the photo directory.
+			 *
+			 * We use the hard-link here to off-load the necessary ref-counting
+			 * logic to the file-system.
+			 */
+			filename = g_filename_from_uri (photo->data.uri, NULL, NULL);
+			g_assert (filename); /* we already checked this with 'is_backend_owned_uri()' */
+
+			new_filename = hard_link_photo (bf, contact, field, filename, error);
+
+			if (!new_filename)
+				status = STATUS_ERROR;
+			else if ((new_uri = g_filename_to_uri (new_filename, NULL, error)) == NULL) {
+				/* If we fail here... we need to clean up the hardlink we just created */
+				GError *local_err = NULL;
+				if (!remove_file (new_filename, &local_err)) {
+					g_warning ("Unable to cleanup photo uri: %s", local_err->message);
+					g_error_free (error);
+				}
+				status = STATUS_ERROR;
+			} else {
+
+				new_photo           = g_new (EContactPhoto, 1);
+				new_photo->type     = E_CONTACT_PHOTO_TYPE_URI;
+				new_photo->data.uri = new_uri;
+
+				e_contact_set (contact, field, new_photo);
+
+				d(g_print ("Backend modified incomming shared uri to be %s:\n", new_uri));
+
+				status = STATUS_MODIFIED;
+			}
+			g_free (new_uri);
+			g_free (new_filename);
+			g_free (filename);
+		}
 	}
 
 	return status;
@@ -411,22 +589,19 @@ maybe_transform_vcard_for_photo (EBookBackendFile *bf,
 				 gchar           **vcard_ret,
 				 GError          **error)
 {
-	EBookBackend *backend = E_BOOK_BACKEND (bf);
-	ESource      *source  = e_book_backend_get_source (backend);
-	gboolean      modified = FALSE;
 	PhotoModifiedStatus status;
+	gboolean            modified = FALSE;
 
-	status   = maybe_transform_vcard_field_for_photo (bf, source, contact, E_CONTACT_PHOTO, error);
+	status   = maybe_transform_vcard_field_for_photo (bf, contact, E_CONTACT_PHOTO, error);
 	modified = (status == STATUS_MODIFIED);
 
 	if (status != STATUS_ERROR) {
-		status   = maybe_transform_vcard_field_for_photo (bf, source, contact, E_CONTACT_LOGO, error);
+		status   = maybe_transform_vcard_field_for_photo (bf, contact, E_CONTACT_LOGO, error);
 		modified = modified || (status == STATUS_MODIFIED);
 	}
 
 	if (status != STATUS_ERROR) {
 		if (modified) {
-
 			if (vcard_ret)
 				*vcard_ret = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
 			status = STATUS_MODIFIED;
diff --git a/addressbook/tests/ebook/test-ebook-photo-is-uri.c b/addressbook/tests/ebook/test-ebook-photo-is-uri.c
index b5ae3df..3809618 100644
--- a/addressbook/tests/ebook/test-ebook-photo-is-uri.c
+++ b/addressbook/tests/ebook/test-ebook-photo-is-uri.c
@@ -31,6 +31,18 @@ static GMainLoop *loop = NULL;
 static gchar     *micheal_jackson_uid = NULL;
 static gchar     *james_brown_uid = NULL;
 
+
+/* Decide what to do with every "view-completed" signal */
+enum {
+	ITERATION_SWAP_FACE = 0,
+	ITERATION_DELETE_JAMES,
+	ITERATION_UPDATE_MICHEAL,
+	ITERATION_DELETE_MICHEAL,
+	ITERATION_FINISH
+};
+static gint       iteration = ITERATION_SWAP_FACE;
+
+
 static void
 print_contact (EContact *contact)
 {
@@ -70,16 +82,38 @@ contacts_removed (EBookView *book_view, const GList *ids)
 	}
 }
 
-static gboolean
-quit_timeout (EBookView *book_view)
+
+/* This provokes the backend to handle a cross-referenced photo
+ * between contacts, how the backend handles this is it's choice,
+ * we should test that when deleting one of the contacts, the other
+ * contact does not lose it's photo on disk as a result.
+ */
+static void
+give_james_brown_micheal_jacksons_face (EBook *book)
 {
-	e_book_view_stop (book_view);
-	g_object_unref (book_view);
-	g_main_loop_quit (loop);
+	EContact       *micheal, *james;
+	EContactPhoto  *micheal_face;
+	EContactPhoto  *james_face;
+	GError         *error = NULL;
 
-	return FALSE;
-}
+	micheal = ebook_test_utils_book_get_contact (book, micheal_jackson_uid);
+	james   = ebook_test_utils_book_get_contact (book, james_brown_uid);
+
+	micheal_face = e_contact_get (micheal, E_CONTACT_PHOTO);
+	g_assert (micheal_face->type == E_CONTACT_PHOTO_TYPE_URI);
 
+	james_face  = g_new (EContactPhoto, 1);
+	james_face->type     = E_CONTACT_PHOTO_TYPE_URI;
+	james_face->data.uri = g_strdup (micheal_face->data.uri);
+	
+	e_contact_set (james, E_CONTACT_PHOTO, james_face);
+
+	if (!e_book_commit_contact (book, james, &error))
+		g_error ("Failed to modify contact with cross referenced photo: %s", error->message);
+
+	g_print ("Giving james brown micheal jacksons face: %s\n", micheal_face->data.uri);
+
+}
 
 static void
 update_contact_inline (EBook       *book, 
@@ -109,33 +143,78 @@ update_contact_inline (EBook       *book,
 		g_error ("Failed to modify contact with inline photo data: %s", error->message);
 }
 
+/* This assertion is made a couple of times in the view-complete
+ * handler, we run it to ensure that binary blobs and cross-referenced
+ * photo uris exist on disk while they should */
+static void
+assert_uri_exists (EBook       *book,
+		   const gchar *uid)
+{
+	EContact      *contact;
+	EContactPhoto *photo;
+	const gchar   *filename;
+
+	contact = ebook_test_utils_book_get_contact (book, uid);
+	g_assert (contact);
+
+	photo = e_contact_get (contact, E_CONTACT_PHOTO);
+	g_assert (photo);
+	g_assert (photo->type == E_CONTACT_PHOTO_TYPE_URI);
+
+	filename = g_filename_from_uri (photo->data.uri, NULL, NULL);
+	g_assert (filename);
+
+	/* The file should absolutely exist at this point */
+	g_assert (g_file_test (filename, G_FILE_TEST_EXISTS));
+}
+
 static void
 view_complete (EBookView *book_view, EBookViewStatus status, const gchar *error_msg)
 {
 	EBook *book = e_book_view_get_book (book_view);
 	GError *error = NULL;
 
+	g_print ("View complete, iteration %d\n", iteration);
+
 	/* We get another "complete" notification after removing or modifying a contact */
-	if (james_brown_uid) {
+	switch (iteration) {
+	case ITERATION_SWAP_FACE:
+		give_james_brown_micheal_jacksons_face (book);
+		break;
+	case ITERATION_DELETE_JAMES:
+		assert_uri_exists (book, james_brown_uid);
 
 		if (!e_book_remove_contact (book, james_brown_uid, &error))
 			g_error ("Error removing contact: %s", error->message);
 
 		g_free (james_brown_uid);
 		james_brown_uid = NULL;
+		break;
+	case ITERATION_UPDATE_MICHEAL:
+		assert_uri_exists (book, micheal_jackson_uid);
 
 		update_contact_inline (book, micheal_jackson_uid);
-
-	} else if (micheal_jackson_uid) {
+		break;
+	case ITERATION_DELETE_MICHEAL:
+		assert_uri_exists (book, micheal_jackson_uid);
 
 		if (!e_book_remove_contact (book, micheal_jackson_uid, &error))
 			g_error ("Error removing contact: %s", error->message);
 
 		g_free (micheal_jackson_uid);
 		micheal_jackson_uid = NULL;
+		break;
+	case ITERATION_FINISH:
+		e_book_view_stop (book_view);
+		g_object_unref (book_view);
+		g_main_loop_quit (loop);
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
 
-		g_timeout_add (500, (GSourceFunc)quit_timeout, book_view);
-	} 
+	iteration++;
 }
 
 static void



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]