[Evolution-hackers] [PATCH] offline improvements/features
- From: Not Zed <notzed ximian com>
- To: evolution-hackers ximian com
- Cc: evolution ximian com
- Subject: [Evolution-hackers] [PATCH] offline improvements/features
- Date: 31 Jul 2003 15:28:40 -0400
Hi guys,
I've come up with a patch which changes the way imap offline works.
Basically it does two things:
- instead of syncing only read messages, it always syncs all messages
when you go offline
- adds a server option ("receiving options") to enable automatic,
background downloading of new messages as they arrive.
It's still a little bit 'work in progress', and I think most of the
logic should probably lie in the back-end instead, but it should be
fully functional.
Some issues:
- clicking on an unread message wont force a check for new messages
anymore, as the message is already local and no chance to get updates
from the server (if you're using the automatic option).
- does the sync code conflict/interact badly with filtering?
- does the sync code interact badly with reading mail?
I'm cross-posting this to evolution@ in case people are building from
source, and aren't on evolution-hackers@, but might want to try this
patch. I probably wont do that again, so join -hackers@ if you want to
get involved.
It's against the (roughly) 1.4.3 source tree.
Michael
Index: camel/ChangeLog
===================================================================
RCS file: /cvs/gnome/evolution/camel/ChangeLog,v
retrieving revision 1.1836.2.6
diff -u -3 -r1.1836.2.6 ChangeLog
--- camel/ChangeLog 29 Jul 2003 13:53:28 -0000 1.1836.2.6
+++ camel/ChangeLog 31 Jul 2003 19:13:53 -0000
@@ -1,3 +1,9 @@
+2003-07-30 Not Zed <NotZed Ximian com>
+
+ * providers/imap/camel-imap-provider.c: Added offline_sync, to
+ automatically sync offline folders. Currently used by the mailer,
+ but will move here eventually.
+
2003-07-25 Jeffrey Stedfast <fejj ximian com>
* camel-mime-utils.c (header_decode_word): Revert NotZed's fix for
Index: camel/providers/imap/camel-imap-provider.c
===================================================================
RCS file: /cvs/gnome/evolution/camel/providers/imap/camel-imap-provider.c,v
retrieving revision 1.24
diff -u -3 -r1.24 camel-imap-provider.c
--- camel/providers/imap/camel-imap-provider.c 22 May 2002 20:17:48 -0000 1.24
+++ camel/providers/imap/camel-imap-provider.c 31 Jul 2003 19:13:53 -0000
@@ -55,6 +55,8 @@
{ CAMEL_PROVIDER_CONF_SECTION_END },
{ CAMEL_PROVIDER_CONF_CHECKBOX, "filter", NULL,
N_("Apply filters to new messages in INBOX on this server"), "0" },
+ { CAMEL_PROVIDER_CONF_CHECKBOX, "offline_sync", NULL,
+ N_("Automatically synchronize offline mailboxes to local storage"), "0" },
{ CAMEL_PROVIDER_CONF_END }
};
Index: mail/ChangeLog
===================================================================
RCS file: /cvs/gnome/evolution/mail/ChangeLog,v
retrieving revision 1.2761.2.7
diff -u -3 -r1.2761.2.7 ChangeLog
--- mail/ChangeLog 28 Jul 2003 21:01:33 -0000 1.2761.2.7
+++ mail/ChangeLog 31 Jul 2003 19:13:54 -0000
@@ -1,3 +1,17 @@
+2003-07-30 Not Zed <NotZed Ximian com>
+
+ * mail-folder-cache.c: Added handler for automatically
+ synchronising offline folders in the background.
+
+ * mail-folder-cache.c (setup_folder): tell the offline code a new
+ folder is available.
+ (mail_note_store_remove): tell offline code the store has gone.
+ (mail_note_get_store_by_account): function to find a camelstore
+ based on an evolution account name.
+
+ * mail-ops.c (prep_offline_do): change to sync all messages, not
+ just unread ones, by default.
+
2003-07-25 Jeffrey Stedfast <fejj ximian com>
* message-browser.c (on_key_press): New callback function to
Index: mail/component-factory.c
===================================================================
RCS file: /cvs/gnome/evolution/mail/component-factory.c,v
retrieving revision 1.328.4.1
diff -u -3 -r1.328.4.1 component-factory.c
--- mail/component-factory.c 15 Jul 2003 19:47:57 -0000 1.328.4.1
+++ mail/component-factory.c 31 Jul 2003 19:13:54 -0000
@@ -782,7 +782,9 @@
for (i = 0; i < sizeof (standard_folders) / sizeof (standard_folders[0]); i++)
*standard_folders[i].uri = g_strdup_printf ("file://%s/local/%s", evolution_dir, standard_folders[i].name);
-
+
+ em_offline_init();
+
vfolder_load_storage(corba_shell);
accounts = mail_config_get_accounts ();
@@ -815,7 +817,7 @@
rule_context_load (search_context, system, user);
}
-
+
if (mail_config_is_corrupt ()) {
GtkWidget *dialog;
Index: mail/mail-folder-cache.c
===================================================================
RCS file: /cvs/gnome/evolution/mail/mail-folder-cache.c,v
retrieving revision 1.69
diff -u -3 -r1.69 mail-folder-cache.c
--- mail/mail-folder-cache.c 2 Jun 2003 17:50:25 -0000 1.69
+++ mail/mail-folder-cache.c 31 Jul 2003 19:13:54 -0000
@@ -51,6 +51,10 @@
#define w(x)
#define d(x) /*(printf("%s(%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__), (x))*/
+#define o(x) /* offline debug */
+
+static void em_offline_check_folder(CamelStore *store, const char *account_name, const char *folder_name);
+static void em_offline_remove_store(const char *account_name);
/* note that many things are effectively serialised by having them run in
the main loop thread which they need to do because of corba/gtk calls */
@@ -358,6 +362,7 @@
up->path = g_strdup(mfi->path);
if (si->storage != NULL) {
up->name = g_strdup(fi->name);
+ em_offline_check_folder(si->store, evolution_storage_get_name(si->storage), fi->full_name);
}
up->uri = g_strdup(fi->url);
up->unread = (fi->unread_message_count==-1)?0:fi->unread_message_count;
@@ -665,6 +670,7 @@
{
struct _update_data *ud;
struct _store_info *si;
+ char *name = NULL;
g_assert(CAMEL_IS_STORE(store));
@@ -677,6 +683,9 @@
if (si) {
g_hash_table_remove(stores, store);
+ if (si->storage)
+ name = g_strdup(evolution_storage_get_name(si->storage));
+
camel_object_unhook_event(store, "folder_created", store_folder_created, NULL);
camel_object_unhook_event(store, "folder_deleted", store_folder_deleted, NULL);
camel_object_unhook_event(store, "folder_renamed", store_folder_renamed, NULL);
@@ -701,6 +710,11 @@
g_free(si);
}
UNLOCK(info_lock);
+
+ if (name) {
+ em_offline_remove_store(name);
+ g_free(name);
+ }
}
static void
@@ -930,3 +944,410 @@
return fi.fi != NULL;
}
+
+/* could just maintain another hashtable ... */
+struct _get_store_info {
+ const char *name;
+ CamelStore *store;
+};
+
+static void
+emfc_get_store_cb(void *key, struct _store_info *si, struct _get_store_info *gsi)
+{
+ const char *name;
+
+ if (gsi->store == NULL
+ && si->storage != NULL
+ && (name = evolution_storage_get_name(si->storage)) != NULL
+ && strcmp(name, gsi->name) == 0) {
+ gsi->store = si->store;
+ camel_object_ref(si->store);
+ }
+}
+
+/* by account name */
+static CamelStore *
+mail_note_get_store_by_account(const char *name)
+{
+ struct _get_store_info gsi = { name, NULL };
+
+ /* this could cause a race with opening of stores vs folders (?) */
+ if (stores == NULL)
+ return NULL;
+
+ LOCK(info_lock);
+ g_hash_table_foreach(stores, (GHFunc)emfc_get_store_cb, &gsi);
+ UNLOCK(info_lock);
+
+ return gsi.store;
+}
+
+/* stuff for offline mode handling */
+
+/* FIXME: should it just be a list/flags stored on and handled by the store? */
+static pthread_mutex_t offline_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static GSList *offline_paths;
+
+/* the store for these folders isn't active yet, so wait till it is */
+static EDList offline_pending_folders = E_DLIST_INITIALISER(offline_pending_folders);
+/* the store for these folders is active, these folders are to be synced */
+static EDList offline_sync_folders = E_DLIST_INITIALISER(offline_sync_folders);
+/* used for all other folders, so each folder info is always in some list */
+static EDList offline_idle_folders = E_DLIST_INITIALISER(offline_idle_folders);
+
+static EDList * const offline_lists[] = { &offline_pending_folders, &offline_sync_folders, &offline_idle_folders };
+
+static volatile int offline_sync_id = -1;
+
+struct _offline_folder_info {
+ struct _offline_folder_info *next, *prev;
+
+ char *shell_path;
+ const char *folder_path; /* note: folder_path points inside of shell_path */
+ CamelStore *store;
+ CamelFolder *folder;
+ int changed_id;
+
+ CamelFolderChangeInfo *changes;
+};
+
+static void emo_flush(void);
+static void emo_free_folder_info(struct _offline_folder_info *ofi);
+
+static char *emo_sync_desc(struct _mail_msg *mm, int done)
+{
+ return g_strdup(_("Retrieving remote mail"));
+}
+
+static void
+emo_folder_changed(CamelFolder *folder, CamelFolderChangeInfo *changes, struct _offline_folder_info *ofi)
+{
+ o(printf("folder changed '%s'\n", ofi->shell_path));
+
+ if (changes->uid_added->len > 0) {
+ LOCK(offline_lock);
+ camel_folder_change_info_cat(ofi->changes, changes);
+ e_dlist_remove((EDListNode *)ofi);
+ e_dlist_addtail(&offline_sync_folders, (EDListNode *)ofi);
+ UNLOCK(offline_lock);
+ emo_flush();
+ }
+}
+
+static void
+emo_sync_do(struct _mail_msg *mm)
+{
+ struct _offline_folder_info *ofi;
+
+ LOCK(offline_lock);
+
+ while ((ofi = (struct _offline_folder_info *)e_dlist_remhead(&offline_sync_folders))) {
+ CamelFolder *folder = ofi->folder;
+
+ e_dlist_addtail(&offline_idle_folders, (EDListNode *)ofi);
+
+ o(printf("offline syncing folder '%s'\n", ofi->folder_path));
+ if (folder == NULL) {
+ /* This could be slow ... deadlocks? */
+ folder = camel_store_get_folder(ofi->store, ofi->folder_path, 0, &mm->ex);
+ if (folder) {
+ if (!CAMEL_IS_DISCO_FOLDER(folder)) {
+ g_warning("folder '%s' is not able to work in offline mode, ignored", ofi->shell_path);
+ emo_free_folder_info(ofi);
+ continue;
+ }
+ ofi->folder = folder;
+ ofi->changed_id = camel_object_hook_event(folder, "folder_changed", (CamelObjectEventHookFunc)emo_folder_changed, ofi);
+ }
+ }
+
+ if (folder != NULL) {
+ camel_object_ref(folder);
+ if (ofi->changes->uid_added->len == 0) {
+ UNLOCK(offline_lock);
+ camel_disco_folder_prepare_for_offline((CamelDiscoFolder *)folder,
+ "(match-all)",
+ &mm->ex);
+ camel_object_unref(folder);
+ } else {
+ int i;
+ GPtrArray *uid_list = g_ptr_array_new();
+
+ for (i=0;i<ofi->changes->uid_added->len;i++)
+ g_ptr_array_add(uid_list, g_strdup(ofi->changes->uid_added->pdata[i]));
+ camel_folder_change_info_clear(ofi->changes);
+ UNLOCK(offline_lock);
+
+ for (i=0;camel_exception_get_id(&mm->ex) == 0 && i<uid_list->len;i++) {
+ char *uid = uid_list->pdata[i];
+
+ o(printf("syncing uid '%s'\n", uid));
+ camel_disco_folder_cache_message((CamelDiscoFolder *)ofi->folder,
+ uid,
+ &mm->ex);
+ g_free(uid);
+ }
+ g_ptr_array_free(uid_list, TRUE);
+ camel_object_unref(folder);
+ }
+ } else {
+ g_warning("cannot open offline folder '%s'\n", ofi->shell_path);
+ }
+
+ if (camel_exception_get_id(&mm->ex) == CAMEL_EXCEPTION_USER_CANCEL)
+ break;
+ camel_exception_clear(&mm->ex);
+ LOCK(offline_lock);
+ }
+
+ UNLOCK(offline_lock);
+
+ offline_sync_id = -1;
+}
+
+/* we use this to get the cancellation stuff for 'free' */
+static struct _mail_msg_op emo_sync_op = {
+ emo_sync_desc,
+ emo_sync_do,
+ NULL,
+ NULL,
+};
+
+static void
+emo_flush(void)
+{
+ if (offline_sync_id == -1 && !e_dlist_empty(&offline_sync_folders)) {
+ struct _mail_msg *m;
+
+ o(printf("launching thread to sync offline folders\n"));
+ m = mail_msg_new(&emo_sync_op, NULL, sizeof(*m));
+ offline_sync_id = m->seq;
+ e_thread_put(mail_thread_new, (EMsg *)m);
+ }
+}
+
+/* must have info_lock */
+static void
+emo_add_path(const char *path)
+{
+ struct _offline_folder_info *ofi;
+ char *store_name, *folder_name;
+
+ if (path[0] == 0)
+ return;
+
+ store_name = alloca(strlen(path)+1);
+ strcpy(store_name, path);
+ folder_name = strchr(store_name+1, '/');
+ if (folder_name == NULL)
+ return;
+ *folder_name++ = 0;
+ store_name++;
+
+ o(printf("Adding offline check path '%s'\n", path));
+
+ ofi = g_malloc0(sizeof(*ofi));
+ ofi->changes = camel_folder_change_info_new();
+ ofi->shell_path = g_strdup(path);
+ ofi->folder_path = ofi->shell_path + (folder_name - store_name + 1);
+ ofi->store = mail_note_get_store_by_account(store_name);
+ if (ofi->store == NULL)
+ e_dlist_addtail(&offline_pending_folders, (EDListNode *)ofi);
+ else
+ e_dlist_addtail(&offline_sync_folders, (EDListNode *)ofi);
+}
+
+static void
+em_offline_check_folder(CamelStore *store, const char *account_name, const char *folder_name)
+{
+ struct _offline_folder_info *ofw, *ofn;
+ char *path;
+
+ if (!CAMEL_IS_DISCO_STORE(store)
+ || camel_url_get_param(((CamelService *)store)->url, "offline_sync") == NULL)
+ return;
+
+ LOCK(offline_lock);
+
+ path = g_strdup_printf("/%s/%s", account_name, folder_name);
+ o(printf("new folder available '%s'\n", path));
+ ofw = (struct _offline_folder_info *)offline_pending_folders.head;
+ ofn = ofw->next;
+ while (ofn) {
+ o(printf("path '%s'\n", ofw->shell_path));
+ if (!strcmp(ofw->shell_path, path)) {
+ o(printf("This is an offline folder ... processing it\n"));
+ ofw->store = store;
+ camel_object_ref(store);
+ e_dlist_remove((EDListNode *)ofw);
+ e_dlist_addtail(&offline_sync_folders, (EDListNode *)ofw);
+ }
+
+ ofw = ofn;
+ ofn = ofn->next;
+ }
+
+ g_free(path);
+
+ UNLOCK(offline_lock);
+
+ emo_flush();
+}
+
+static void
+emo_free_folder_info(struct _offline_folder_info *ofi)
+{
+ g_free(ofi->shell_path);
+ if (ofi->store)
+ camel_object_unref(ofi->store);
+ if (ofi->folder) {
+ camel_object_remove_event(ofi->folder, ofi->changed_id);
+ camel_object_unref(ofi->folder);
+ }
+ e_dlist_remove((EDListNode *)ofi);
+ g_free(ofi);
+}
+
+static void
+emo_remove_old(char *key, struct _offline_folder_info *ofi, void *data)
+{
+ o(printf("path removed, removing '%s'\n", ofi->shell_path));
+
+ emo_free_folder_info(ofi);
+}
+
+static void
+em_offline_remove_store(const char *account_name)
+{
+ char *path;
+ struct _offline_folder_info *ofw, *ofn;
+ int i;
+
+ /* store goes inactive, move any matching folders back to pending w/o a store/folder set */
+
+ LOCK(offline_lock);
+ path = g_strdup_printf("/%s/", account_name);
+ for (i=1;i<sizeof(offline_lists)/sizeof(offline_lists[0]);i++) {
+ ofw = (struct _offline_folder_info *)offline_lists[i]->head;
+ ofn = ofw->next;
+ while (ofn) {
+ if (!strncmp(ofw->shell_path, path, strlen(path))) {
+ o(printf("Removing folder '%s'\n", ofw->shell_path));
+ if (ofw->store) {
+ camel_object_unref(ofw->store);
+ ofw->store = NULL;
+ }
+ if (ofw->folder) {
+ camel_object_remove_event(ofw->folder, ofw->changed_id);
+ camel_object_unref(ofw->folder);
+ ofw->folder = NULL;
+ }
+ e_dlist_remove((EDListNode *)ofw);
+ e_dlist_addtail(&offline_pending_folders, (EDListNode *)ofw);
+ }
+ ofw = ofn;
+ ofn = ofn->next;
+ }
+ }
+ UNLOCK(offline_lock);
+
+ emo_flush();
+}
+
+static void
+emo_path_changed_cb(GConfClient *gconf, guint cnxn_id, GConfEntry *entry, gpointer user_data)
+{
+ GSList *l;
+ GHashTable *all_paths;
+ int i;
+
+ if (strcmp(entry->key, "/apps/evolution/shell/offline/folder_paths") != 0)
+ return;
+
+ o(printf("paths have changed, rechecking\n"));
+
+ g_slist_foreach(offline_paths, (GFunc)g_free, NULL);
+ g_slist_free(offline_paths);
+ offline_paths = gconf_client_get_list(gconf, "/apps/evolution/shell/offline/folder_paths", GCONF_VALUE_STRING, NULL);
+
+ LOCK(offline_lock);
+
+ all_paths = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<sizeof(offline_lists)/sizeof(offline_lists[0]);i++) {
+ struct _offline_folder_info *ofw, *ofn;
+
+ ofw = (struct _offline_folder_info *)offline_lists[i]->head;
+ ofn = ofw->next;
+ while (ofn) {
+ g_hash_table_insert(all_paths, ofw->shell_path, ofw);
+ ofw = ofn;
+ ofn = ofn->next;
+ }
+ }
+
+ for (l = offline_paths; l != NULL; l = l->next) {
+ char *path = l->data;
+
+ o(printf("path '%s'\n", path));
+ if (g_hash_table_lookup(all_paths, path) == NULL)
+ emo_add_path(path);
+ else
+ g_hash_table_remove(all_paths, path);
+ }
+
+ g_hash_table_foreach(all_paths, (GHFunc)emo_remove_old, NULL);
+ g_hash_table_destroy(all_paths);
+
+ UNLOCK(offline_lock);
+
+ emo_flush();
+}
+
+static void
+emo_account_changed_cb(EAccountList *eal, EAccount *ea)
+{
+ /* HACK: the way account_changed is called, it will automatically
+ drop/restart the connection, and re-match any folders implicitly */
+ /* TODO: This doesn't actually work anyway since we end up just
+ getting the same (old) store again afterwards */
+ em_offline_remove_store(ea->name);
+}
+
+static void
+emo_account_removed_cb(EAccountList *eal, EAccount *ea)
+{
+ em_offline_remove_store(ea->name);
+}
+
+void
+em_offline_init(void)
+{
+ GSList *l;
+ GConfClient *gconf = mail_config_get_gconf_client();
+ EAccountList *eal;
+
+ eal = mail_config_get_accounts();
+ g_signal_connect(eal, "account_changed", G_CALLBACK(emo_account_changed_cb), NULL);
+ g_signal_connect(eal, "account_removed", G_CALLBACK(emo_account_removed_cb), NULL);
+
+ /*offline_notify_id = */
+ gconf_client_add_dir(gconf, "/apps/evolution/shell/offline", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
+ gconf_client_notify_add(gconf, "/apps/evolution/shell/offline", emo_path_changed_cb, NULL, NULL, NULL);
+ offline_paths = gconf_client_get_list(gconf, "/apps/evolution/shell/offline/folder_paths", GCONF_VALUE_STRING, NULL);
+ for (l = offline_paths; l != NULL; l = l->next)
+ emo_add_path((char *)l->data);
+
+ emo_flush();
+}
+
+#if 0
+/* tells the offline code what the currently displayed folder is, so it
+ can ensure that folder is downloaded first? */
+void
+em_offline_current_folder(CamelFolder *folder)
+{
+ /* could take move any sync job for that folder to the front of the list */
+ /* noop */
+}
+#endif
Index: mail/mail-folder-cache.h
===================================================================
RCS file: /cvs/gnome/evolution/mail/mail-folder-cache.h,v
retrieving revision 1.11
diff -u -3 -r1.11 mail-folder-cache.h
--- mail/mail-folder-cache.h 27 Oct 2001 00:47:21 -0000 1.11
+++ mail/mail-folder-cache.h 31 Jul 2003 19:13:54 -0000
@@ -47,4 +47,7 @@
to a (referenced) copy of the folder if it has already been opened */
int mail_note_get_folder_from_uri(const char *uri, CamelFolder **folderp);
+/* auto-download offline code */
+void em_offline_init(void);
+
#endif
Index: mail/mail-ops.c
===================================================================
RCS file: /cvs/gnome/evolution/mail/mail-ops.c,v
retrieving revision 1.392
diff -u -3 -r1.392 mail-ops.c
--- mail/mail-ops.c 2 Jun 2003 17:50:25 -0000 1.392
+++ mail/mail-ops.c 31 Jul 2003 19:13:54 -0000
@@ -2145,7 +2145,7 @@
if (folder) {
if (CAMEL_IS_DISCO_FOLDER(folder)) {
camel_disco_folder_prepare_for_offline((CamelDiscoFolder *)folder,
- "(match-all (or (not (system-flag \"Seen\")) (system-flag \"Flagged\")))",
+ "(match-all)",
&mm->ex);
}
/* prepare_for_offline should do this? */
Index: shell/evolution-storage.c
===================================================================
RCS file: /cvs/gnome/evolution/shell/evolution-storage.c,v
retrieving revision 1.95
diff -u -3 -r1.95 evolution-storage.c
--- shell/evolution-storage.c 28 Apr 2003 20:22:12 -0000 1.95
+++ shell/evolution-storage.c 31 Jul 2003 19:13:54 -0000
@@ -872,6 +872,14 @@
/* FIXME: Implement me */
}
+const char *
+evolution_storage_get_name(EvolutionStorage *storage)
+{
+ EvolutionStoragePrivate *priv = storage->priv;
+
+ return priv->name;
+}
+
EvolutionStorageResult
evolution_storage_register (EvolutionStorage *evolution_storage,
GNOME_Evolution_StorageRegistry corba_storage_registry)
Index: shell/evolution-storage.h
===================================================================
RCS file: /cvs/gnome/evolution/shell/evolution-storage.h,v
retrieving revision 1.36
diff -u -3 -r1.36 evolution-storage.h
--- shell/evolution-storage.h 14 Mar 2003 18:13:58 -0000 1.36
+++ shell/evolution-storage.h 31 Jul 2003 19:13:54 -0000
@@ -143,6 +143,8 @@
void evolution_storage_rename (EvolutionStorage *storage,
const char *new_name);
+const char *evolution_storage_get_name (EvolutionStorage *storage);
+
EvolutionStorageResult evolution_storage_register (EvolutionStorage *storage,
GNOME_Evolution_StorageRegistry corba_registry);
EvolutionStorageResult evolution_storage_register_on_shell (EvolutionStorage *evolution_storage,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]