0.8.11claws26
[claws.git] / src / imap.c
index 4a72d8817927569266dcd350e0ab9731127ff60c..cc877b3b74a728d36c161948f6b51f9fb97dc613 100644 (file)
@@ -74,6 +74,7 @@ struct _IMAPFolderItem
        FolderItem item;
 
        guint lastuid;
+       guint uid_next;
        GSList *uid_list;
 };
 
@@ -241,9 +242,10 @@ static gint imap_cmd_search        (SockInfo       *sock,
 static gint imap_cmd_fetch     (SockInfo       *sock,
                                 guint32         uid,
                                 const gchar    *filename);
-static gint imap_cmd_append    (SockInfo       *sock,
+static gint imap_cmd_append    (IMAPSession    *session,
                                 const gchar    *destfolder,
-                                const gchar    *file);
+                                const gchar    *file,
+                                gint32         *newuid);
 static gint imap_cmd_copy      (IMAPSession    *session,
                                 gint32         msgnum,
                                 const gchar    *destfolder,
@@ -293,17 +295,39 @@ MsgInfo *imap_get_msginfo                         (Folder         *folder,
                                                 gint            num);
 gboolean imap_check_msgnum_validity            (Folder         *folder,
                                                 FolderItem     *item);
+void imap_change_flags                         (Folder         *folder,
+                                                FolderItem     *item,
+                                                MsgInfo        *msginfo,
+                                                MsgPermFlags   newflags);
 
 FolderClass imap_class =
 {
        F_IMAP,
        "imap",
+       "IMAP4",
+
+       /* Folder functions */
+       imap_folder_new,
+       imap_folder_destroy,
+       imap_scan_tree,
+       imap_create_tree,
 
+       /* FolderItem functions */
        imap_folder_item_new,
        imap_folder_item_destroy,
-       imap_fetch_msg,
+       imap_create_folder,
+       imap_rename_folder,
+       imap_remove_folder,
+       imap_get_num_list,
+       NULL,
+       NULL,
+       NULL,
+       imap_check_msgnum_validity,
+
+       /* Message functions */
        imap_get_msginfo,
        imap_get_msginfos,
+       imap_fetch_msg,
        imap_add_msg,
        imap_move_msg,
        NULL,
@@ -313,19 +337,7 @@ FolderClass imap_class =
        imap_remove_msgs,
        imap_remove_all_msg,
        imap_is_msg_changed,
-       NULL,
-       imap_get_num_list,
-       imap_scan_tree,
-       imap_create_tree,
-       imap_create_folder,
-       imap_rename_folder,
-       imap_remove_folder,
-       imap_folder_destroy,
-       NULL,
-       NULL,
-       NULL,
-       NULL,
-       imap_check_msgnum_validity,
+       imap_change_flags,
 };
 
 FolderClass *imap_get_class()
@@ -368,6 +380,7 @@ static FolderItem *imap_folder_item_new(Folder *folder)
        
        item = g_new0(IMAPFolderItem, 1);
        item->lastuid = 0;
+       item->uid_next = 0;
        item->uid_list = NULL;
 
        return (FolderItem *)item;
@@ -388,6 +401,7 @@ static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
        IMAPFolderItem *item = (IMAPFolderItem *)node->data;
        
        item->lastuid = 0;
+       item->uid_next = 0;
        g_slist_free(item->uid_list);
        item->uid_list = NULL;
        
@@ -410,7 +424,7 @@ static IMAPSession *imap_session_get(Folder *folder)
        gushort port;
 
        g_return_val_if_fail(folder != NULL, NULL);
-       g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, NULL);
+       g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
        g_return_val_if_fail(folder->account != NULL, NULL);
 
 #if USE_OPENSSL
@@ -659,7 +673,7 @@ gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
 {
        gchar *destdir;
        IMAPSession *session;
-       gint ok;
+       gint ok, newuid;
 
        g_return_val_if_fail(folder != NULL, -1);
        g_return_val_if_fail(dest != NULL, -1);
@@ -669,7 +683,7 @@ gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
        if (!session) return -1;
 
        destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
-       ok = imap_cmd_append(SESSION(session)->sock, destdir, file);
+       ok = imap_cmd_append(session, destdir, file, &newuid);
        g_free(destdir);
 
        if (ok != IMAP_SUCCESS) {
@@ -682,7 +696,7 @@ gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
                        FILE_OP_ERROR(file, "unlink");
        }
 
-       return 0;
+       return newuid;
 }
 
 static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
@@ -691,12 +705,11 @@ static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
        gchar *destdir;
        IMAPSession *session;
        IMAPFlags iflags = 0;
-       gint messages, recent, unseen;
-       guint32 newuid = 0, uid_validity;
+       guint32 newuid = 0;
        gint ok;
     
        g_return_val_if_fail(folder != NULL, -1);
-       g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
+       g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
        g_return_val_if_fail(dest != NULL, -1);
        g_return_val_if_fail(msginfo != NULL, -1);
 
@@ -708,17 +721,6 @@ static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
                return -1;
        }
 
-       /* if we don't have UIDPLUS we get UID-NEXT and hope that the
-          message really gets this uid */
-       if (!imap_has_capability(session, "UIDPLUS")) {
-               ok = imap_status(session, IMAP_FOLDER(folder), dest->path,
-                                &messages, &recent, &newuid, &uid_validity, &unseen);
-               if (ok != IMAP_SUCCESS) {
-                       g_warning("can't copy message\n");
-                       return -1;
-               }
-       }
-
        destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
 
        /* ensure source folder selected */
@@ -940,7 +942,7 @@ gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
        gchar *dir;
 
        g_return_val_if_fail(folder != NULL, -1);
-       g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
+       g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
        g_return_val_if_fail(item != NULL, -1);
 
        session = imap_session_get(folder);
@@ -983,7 +985,7 @@ gint imap_remove_msgs(Folder *folder, FolderItem *item, GSList *msglist)
        guint32 uid;
 
        g_return_val_if_fail(folder != NULL, -1);
-       g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
+       g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
        g_return_val_if_fail(item != NULL, -1);
        g_return_val_if_fail(msglist != NULL, -1);
 
@@ -1610,7 +1612,7 @@ static GSList *imap_get_uncached_messages(IMAPSession *session,
        g_return_val_if_fail(session != NULL, NULL);
        g_return_val_if_fail(item != NULL, NULL);
        g_return_val_if_fail(item->folder != NULL, NULL);
-       g_return_val_if_fail(FOLDER_TYPE(item->folder) == F_IMAP, NULL);
+       g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
        g_return_val_if_fail(first_uid <= last_uid, NULL);
 
        if (imap_cmd_envelope(SESSION(session)->sock, first_uid, last_uid)
@@ -1675,7 +1677,7 @@ static void imap_delete_all_cached_messages(FolderItem *item)
 
        g_return_if_fail(item != NULL);
        g_return_if_fail(item->folder != NULL);
-       g_return_if_fail(FOLDER_TYPE(item->folder) == F_IMAP);
+       g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
 
        debug_print("Deleting all cached messages...\n");
 
@@ -2209,7 +2211,7 @@ gint imap_msg_set_perm_flags(MsgInfo *msginfo, MsgPermFlags flags)
        g_return_val_if_fail(msginfo->folder->folder != NULL, -1);
 
        folder = msginfo->folder->folder;
-       g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
+       g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
 
        session = imap_session_get(folder);
        if (!session) return -1;
@@ -2245,7 +2247,7 @@ gint imap_msg_unset_perm_flags(MsgInfo *msginfo, MsgPermFlags flags)
        g_return_val_if_fail(msginfo->folder->folder != NULL, -1);
 
        folder = msginfo->folder->folder;
-       g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
+       g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
 
        session = imap_session_get(folder);
        if (!session) return -1;
@@ -2747,14 +2749,16 @@ static gint imap_cmd_fetch(SockInfo *sock, guint32 uid, const gchar *filename)
        return ok;
 }
 
-static gint imap_cmd_append(SockInfo *sock, const gchar *destfolder,
-                           const gchar *file)
+static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
+                           const gchar *file, gint32 *new_uid)
 {
        gint ok;
-       gint size;
+       gint size, newuid;
        gchar *destfolder_;
        gchar buf[BUFFSIZE], *imapbuf;
        FILE *fp;
+       GPtrArray *reply;
+       gchar *okmsginfo;
 
        g_return_val_if_fail(file != NULL, IMAP_ERROR);
 
@@ -2764,9 +2768,9 @@ static gint imap_cmd_append(SockInfo *sock, const gchar *destfolder,
                return -1;
        }
        QUOTE_IF_REQUIRED(destfolder_, destfolder);
-       imap_cmd_gen_send(sock, "APPEND %s (\\Seen) {%d}", destfolder_, size);
+       imap_cmd_gen_send(SESSION(session)->sock, "APPEND %s (\\Seen) {%d}", destfolder_, size);
 
-       ok = imap_cmd_gen_recv(sock, &imapbuf);
+       ok = imap_cmd_gen_recv(SESSION(session)->sock, &imapbuf);
        if (ok != IMAP_SUCCESS || imapbuf[0] != '+' || imapbuf[1] != ' ') {
                log_warning(_("can't append %s to %s\n"), file, destfolder_);
                g_free(imapbuf);
@@ -2779,9 +2783,9 @@ static gint imap_cmd_append(SockInfo *sock, const gchar *destfolder,
 
        while (fgets(buf, sizeof(buf), fp) != NULL) {
                strretchomp(buf);
-               if (sock_puts(sock, buf) < 0) {
+               if (sock_puts(SESSION(session)->sock, buf) < 0) {
                        fclose(fp);
-                       sock_close(sock);
+                       sock_close(SESSION(session)->sock);
                        return -1;
                }
        }
@@ -2789,51 +2793,37 @@ static gint imap_cmd_append(SockInfo *sock, const gchar *destfolder,
        if (ferror(fp)) {
                FILE_OP_ERROR(file, "fgets");
                fclose(fp);
-               sock_close(sock);
+               sock_close(SESSION(session)->sock);
                return -1;
        }
 
-       sock_puts(sock, "");
+       sock_puts(SESSION(session)->sock, "");
 
        fclose(fp);
-       return imap_cmd_ok(sock, NULL);
-}
-
-#if 0
-static void imap_uidplus_copy(IMAPSession *session, MsgInfo *msginfo,
-                               const gchar *destfolder, const gchar *okmsginfo)
-{
-       unsigned int olduid, newuid;
-       IMAPFlags iflags = 0;
-
-       if (okmsginfo == NULL)
-               return;
 
-       if (sscanf(okmsginfo, "%*u OK [COPYUID %*llu %u %u]", &olduid, &newuid) != 2)
-               return;
-
-       if (olduid != msginfo->msgnum)  /* this should NEVER happen */
-               return;
-
-       if (imap_select(session, IMAP_FOLDER(msginfo->folder->folder), destfolder,
-                       NULL, NULL, NULL, NULL) != IMAP_SUCCESS)
-               return;
-
-       if (msginfo->flags.perm_flags & MSG_MARKED)  iflags |= IMAP_FLAG_FLAGGED;
-       if (msginfo->flags.perm_flags & MSG_REPLIED) iflags |= IMAP_FLAG_ANSWERED;
-       if (iflags)
-               imap_set_message_flags(session, newuid, newuid, iflags, TRUE);
+       reply = g_ptr_array_new();
 
-       if (msginfo->flags.perm_flags & MSG_UNREAD)
-               imap_set_message_flags(session, newuid, newuid, IMAP_FLAG_SEEN, FALSE);
+       *new_uid = 0;
+       ok = imap_cmd_ok(SESSION(session)->sock, reply);
+       if (ok != IMAP_SUCCESS)
+               log_warning(_("can't append message to %s\n"), destfolder_);
+       else if (
+           (new_uid != NULL) && 
+           (imap_has_capability(session, "UIDPLUS") && reply->len > 0) &&
+           ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL) &&
+           (sscanf(okmsginfo, "%*u OK [APPENDUID %*u %u]", &newuid) == 1)) {
+               *new_uid = newuid;
+       }
 
-       imap_select(session, IMAP_FOLDER(msginfo->folder->folder), msginfo->folder->path,
-                   NULL, NULL, NULL, NULL);
+       ptr_array_free_strings(reply);
+       g_ptr_array_free(reply, TRUE);
+       return ok;
 }
-#endif
 
-static gint imap_cmd_copy(IMAPSession *session,
-    gint32 msgnum, const gchar *destfolder, gint32 *new_uid)
+
+static gint imap_cmd_copy(IMAPSession * session,
+                         gint32 msgnum,
+                         const gchar * destfolder, gint32 * new_uid)
 {
        gint ok;
        gint32 olduid, newuid;
@@ -2850,6 +2840,7 @@ static gint imap_cmd_copy(IMAPSession *session,
 
        reply = g_ptr_array_new();
 
+       *new_uid = 0;
        ok = imap_cmd_ok(SESSION(session)->sock, reply);
        if (ok != IMAP_SUCCESS)
                log_warning(_("can't copy %d to %s\n"), msgnum, destfolder_);
@@ -3291,31 +3282,21 @@ static gboolean imap_rename_folder_func(GNode *node, gpointer data)
        return FALSE;
 }
 
-gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
+static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
 {
-       IMAPFolderItem *item = (IMAPFolderItem *)_item;
+       gint ok, nummsgs = 0, lastuid_old;
        IMAPSession *session;
-       gint ok, lastuid_old, nummsgs = 0, exists, resent, unseen, uid_val;
        GSList *uidlist, *elem;
-       gchar *dir, *cmd_buf;
-
-       g_return_val_if_fail(folder != NULL, -1);
-       g_return_val_if_fail(item != NULL, -1);
-       g_return_val_if_fail(item->item.path != NULL, -1);
-       g_return_val_if_fail(FOLDER_TYPE(folder) == F_IMAP, -1);
-       g_return_val_if_fail(folder->account != NULL, -1);
+       gchar *cmd_buf;
 
        session = imap_session_get(folder);
        g_return_val_if_fail(session != NULL, -1);
 
        ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
-                        &exists, &resent, &unseen, &uid_val);
+                        NULL, NULL, NULL, NULL);
        if (ok != IMAP_SUCCESS)
                return -1;
 
-       if (exists == 0)
-               return 0;
-
        cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
        ok = imap_cmd_search(SESSION(session)->sock, cmd_buf, &uidlist);
        g_free(cmd_buf);
@@ -3355,6 +3336,7 @@ gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
 
        lastuid_old = item->lastuid;
        *msgnum_list = g_slist_copy(item->uid_list);
+       nummsgs = g_slist_length(*msgnum_list);
        debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
 
        for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
@@ -3372,9 +3354,65 @@ gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
        }
        g_slist_free(uidlist);
 
-       if (g_slist_length(item->uid_list) != exists) {
+       return nummsgs;
+}
+
+gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
+{
+       IMAPFolderItem *item = (IMAPFolderItem *)_item;
+       IMAPSession *session;
+       gint ok, nummsgs = 0, exists, recent, unseen, uid_val, uid_next;
+       GSList *uidlist;
+       gchar *dir;
+
+       g_return_val_if_fail(folder != NULL, -1);
+       g_return_val_if_fail(item != NULL, -1);
+       g_return_val_if_fail(item->item.path != NULL, -1);
+       g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
+       g_return_val_if_fail(folder->account != NULL, -1);
+
+       session = imap_session_get(folder);
+       g_return_val_if_fail(session != NULL, -1);
+
+       ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
+                        &exists, &recent, &uid_next, &uid_val, &unseen);
+       if (ok != IMAP_SUCCESS)
+               return -1;
+
+       /* If old uid_next matches new uid_next we can be sure no message
+          was added to the folder */
+       if (uid_next == item->uid_next) {
+               nummsgs = g_slist_length(item->uid_list);
+               
+               /* If number of messages is still the same we
+                   know our caches message numbers are still valid,
+                   otherwise if the number of messages has decrease
+                  we discard our cache to start a new scan to find
+                  out which numbers have been removed */
+               if (exists == nummsgs) {
+                       *msgnum_list = g_slist_copy(item->uid_list);
+                       return nummsgs;
+               } else if (exists < nummsgs) {
+                       debug_print("Freeing imap uid cache");
+                       item->lastuid = 0;
+                       g_slist_free(item->uid_list);
+                       item->uid_list = NULL;
+               }
+       }
+       item->uid_next = uid_next;
+
+       if (exists == 0) {
+               *msgnum_list = NULL;
+               return 0;
+       }
+
+       nummsgs = get_list_of_uids(folder, item, &uidlist);
+
+       if (nummsgs != exists) {
                /* Cache contains more messages then folder, we have cached
-                   an old UID of a message that was removed */
+                   an old UID of a message that was removed and new messages
+                   have been added too, otherwise the uid_next check would
+                  not have failed */
                debug_print("Freeing imap uid cache");
                item->lastuid = 0;
                g_slist_free(item->uid_list);
@@ -3382,9 +3420,11 @@ gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
 
                g_slist_free(*msgnum_list);
 
-               return imap_get_num_list(folder, _item, msgnum_list);
+               nummsgs = get_list_of_uids(folder, item, &uidlist);
        }
 
+       *msgnum_list = uidlist;
+
        dir = folder_item_get_path((FolderItem *)item);
        debug_print("removing old messages from %s\n", dir);
        remove_numbered_files_not_in_list(dir, *msgnum_list);
@@ -3537,7 +3577,7 @@ gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
        g_return_val_if_fail(folder != NULL, FALSE);
        g_return_val_if_fail(item != NULL, FALSE);
        g_return_val_if_fail(item->item.folder != NULL, FALSE);
-       g_return_val_if_fail(FOLDER_TYPE(item->item.folder) == F_IMAP, FALSE);
+       g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
 
        session = imap_session_get(folder);
        g_return_val_if_fail(session != NULL, FALSE);
@@ -3561,3 +3601,55 @@ gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
 
        return FALSE;
 }
+
+void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
+{
+       IMAPSession *session;
+       IMAPFlags flags_set = 0, flags_unset = 0;
+       gint ok = IMAP_SUCCESS;
+
+       g_return_if_fail(folder != NULL);
+       g_return_if_fail(folder->class == &imap_class);
+       g_return_if_fail(item != NULL);
+       g_return_if_fail(item->folder == folder);
+       g_return_if_fail(msginfo != NULL);
+       g_return_if_fail(msginfo->folder == item);
+
+       session = imap_session_get(folder);
+       if (!session) return;
+
+       if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
+           NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
+               return;
+
+       if (!MSG_IS_MARKED(msginfo->flags) &&  (newflags & MSG_MARKED))
+               flags_set |= IMAP_FLAG_FLAGGED;
+       if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
+               flags_unset |= IMAP_FLAG_FLAGGED;
+
+       if (!MSG_IS_UNREAD(msginfo->flags) &&  (newflags & MSG_UNREAD))
+               flags_unset |= IMAP_FLAG_SEEN;
+       if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
+               flags_set |= IMAP_FLAG_SEEN;
+
+       if (!MSG_IS_REPLIED(msginfo->flags) &&  (newflags & MSG_REPLIED))
+               flags_set |= IMAP_FLAG_ANSWERED;
+       if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
+               flags_set |= IMAP_FLAG_ANSWERED;
+
+       if (flags_set) {
+               ok = imap_set_message_flags(session, msginfo->msgnum,
+                                           msginfo->msgnum, flags_set, TRUE);
+               if (ok != IMAP_SUCCESS) return;
+       }
+
+       if (flags_unset) {
+               ok = imap_set_message_flags(session, msginfo->msgnum,
+                                           msginfo->msgnum, flags_unset, FALSE);
+               if (ok != IMAP_SUCCESS) return;
+       }
+
+       msginfo->flags.perm_flags = newflags;
+
+       return;
+}