From 2d27c7da0ac6c853a8e75574534e4e905e2c0bbd Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Thu, 23 Jul 2026 15:08:46 +0200 Subject: [PATCH] Restore requested IMAP UIDs through Store WRITE --- src/agents/store/command.c | 13 +++++++++---- src/agents/store/command.h | 1 + src/agents/store/object-model.c | 26 ++++++++++++++++++++------ src/agents/store/object-model.h | 2 ++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/agents/store/command.c b/src/agents/store/command.c index 6fde372..6ef8e11 100644 --- a/src/agents/store/command.c +++ b/src/agents/store/command.c @@ -1341,12 +1341,12 @@ StoreCommandLoop(StoreClient *client) case STORE_COMMAND_WRITE: /* WRITE - [F] [G] [T] + [I] [F] [G] [T] [Z] [L] [N] */ if (TOKEN_OK != (ccode = RequireStore(client)) || - TOKEN_OK != (ccode = CheckTokC(client, n, 4, 9)) || + TOKEN_OK != (ccode = CheckTokC(client, n, 4, 10)) || TOKEN_OK != (ccode = ParseCollection(client, tokens[1], &object)) || TOKEN_OK != (ccode = ParseDocType(client, tokens[2], &doctype)) || TOKEN_OK != (ccode = ParseStreamLength(client, tokens[3], &int1))) @@ -1357,6 +1357,7 @@ StoreCommandLoop(StoreClient *client) filename = NULL; timestamp = 0; ulong = 0; /* addflags */ + ulong2 = 0; /* requested IMAP UID */ guid = 0; guid2 = 0; /* link guid */ int2 = 0; /* whether or not to process the document */ @@ -1370,6 +1371,8 @@ StoreCommandLoop(StoreClient *client) filename = tokens[i] + 1; } else if ('N' == *tokens[i] && tokens[i][1] == '\0') { int2 = 1; + } else if ('I' == *tokens[i] && !ulong2) { + ccode = ParseUnsignedLong(client, tokens[i] + 1, &ulong2); } else if ('T' == *tokens[i] && !timestamp) { ccode = ParseDateTimeToUint64(client, 1 + tokens[i], ×tamp); } else if ('Z' == *tokens[i] && !ulong) { @@ -1386,7 +1389,8 @@ StoreCommandLoop(StoreClient *client) } ccode = StoreCommandWRITE(client, &object, doctype, int1, - (uint32_t) ulong, guid, filename, + (uint32_t) ulong, (uint32_t) ulong2, + guid, filename, timestamp, guid2, int2); break; @@ -3254,6 +3258,7 @@ StoreCommandWRITE(StoreClient *client, int doctype, uint64_t size, uint32_t addflags, + uint32_t requested_imapuid, uint64_t guid, const char *filename, uint64_t timestamp, @@ -3354,7 +3359,7 @@ StoreCommandWRITE(StoreClient *client, ccode = ConnWriteStr(client->conn, MSG4224CANTWRITE); goto finish; } - if (StoreObjectUpdateImapUID(client, &newdocument)) { + if (StoreObjectSetImapUID(client, &newdocument, requested_imapuid)) { LogicalLockRelease(client, collection, LLOCK_READWRITE, "StoreCommandWRITE"); ccode = ConnWriteStr(client->conn, MSG5005DBLIBERR); goto finish; diff --git a/src/agents/store/command.h b/src/agents/store/command.h index 1c5bb8c..1b1ae26 100644 --- a/src/agents/store/command.h +++ b/src/agents/store/command.h @@ -272,6 +272,7 @@ CCode StoreCommandWRITE(StoreClient *client, int doctype, uint64_t size, uint32_t addflags, + uint32_t requested_imapuid, uint64_t guid, const char *filename, uint64_t timestamp, diff --git a/src/agents/store/object-model.c b/src/agents/store/object-model.c index b7ff089..53f17d9 100644 --- a/src/agents/store/object-model.c +++ b/src/agents/store/object-model.c @@ -1105,12 +1105,20 @@ abort: // correct value :( int StoreObjectUpdateImapUID(StoreClient *client, StoreObject *object) +{ + return StoreObjectSetImapUID(client, object, 0); +} + +int +StoreObjectSetImapUID(StoreClient *client, StoreObject *object, + uint32_t requested_imapuid) { MsgSQLStatement stmt; MsgSQLStatement *ret; StoreObject collection; char *query; int new_imapuid; + int collection_imapuid; int status; if (StoreObjectFind(client, object->collection_guid, &collection)) { @@ -1121,16 +1129,22 @@ StoreObjectUpdateImapUID(StoreClient *client, StoreObject *object) memset(&stmt, 0, sizeof(MsgSQLStatement)); MsgSQLBeginTransaction(client->storedb); - // update the imap uid in a transaction to assure atomicity - new_imapuid = ++collection.imap_uid; - query = "UPDATE storeobject SET imap_uid = ?1 WHERE guid IN (?2, ?3);"; + // Update the object and collection counter in one transaction. Restore + // operations may request an existing UID, including values with gaps. + new_imapuid = requested_imapuid ? + (int)requested_imapuid : (int)collection.imap_uid + 1; + collection_imapuid = MAX((int)collection.imap_uid, new_imapuid); + query = "UPDATE storeobject SET imap_uid = " + "CASE WHEN guid = ?1 THEN ?2 ELSE ?3 END " + "WHERE guid IN (?1, ?4);"; ret = MsgSQLPrepare(client->storedb, query, &stmt); if (ret == NULL) goto abort; - MsgSQLBindInt(&stmt, 1, new_imapuid); - MsgSQLBindInt64(&stmt, 2, object->guid); - MsgSQLBindInt64(&stmt, 3, collection.guid); + MsgSQLBindInt64(&stmt, 1, object->guid); + MsgSQLBindInt(&stmt, 2, new_imapuid); + MsgSQLBindInt(&stmt, 3, collection_imapuid); + MsgSQLBindInt64(&stmt, 4, collection.guid); status = MsgSQLExecute(client->storedb, &stmt); if (status != 0) { diff --git a/src/agents/store/object-model.h b/src/agents/store/object-model.h index 2919575..5dfbd27 100644 --- a/src/agents/store/object-model.h +++ b/src/agents/store/object-model.h @@ -107,6 +107,8 @@ void StoreOutputProperty(StoreClient *client, StorePropertyType type, const char // "dangerous" functions which hit the DB directly int StoreObjectCopyInfo(StoreClient *client, StoreObject *old, StoreObject *newobj); int StoreObjectUpdateImapUID(StoreClient *client, StoreObject *object); +int StoreObjectSetImapUID(StoreClient *client, StoreObject *object, + uint32_t requested_imapuid); int StoreObjectRenameSubobjects(StoreClient *client, StoreObject *container, const char *new_root); int StoreObjectRemoveCollection(StoreClient *client, StoreObject *collection);