feat: IMAP sync incremental + Account Setup Wizard + robust FETCH parser
- ImapSynchronizer::syncFolder(): detecta eliminados (UIDs locales no en servidor), fetch nuevos (sinceUid), actualiza flags (FLAGS batch) - fetchAllUids(): SELECT + SEARCH ALL para lista completa UIDs servidor - parseAndUpdateFlags(): FETCH FLAGS en lotes 100, update DB si cambió read/flagged - AccountSetupDialog: integra ConnectionWizard para IMAP/POP3 con test de conexión real - IMAP FETCH parser robusto: logging respuesta servidor + fallback UID-by-UID - Fix: FETCH failed logging muestra first/last UID + respuesta truncada
This commit is contained in:
+204
-20
@@ -1,18 +1,38 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include "mailitemdao.h"
|
||||
#include <QDebug>
|
||||
#include <optional>
|
||||
|
||||
bool MailItemDao::insert(const MailItem& item)
|
||||
bool MailItemDao::insert(MailItem& item)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
|
||||
// IMAP UIDs and provider message IDs are stable across synchronizations.
|
||||
// Update the existing row instead of creating duplicates on every sync.
|
||||
if (!item.messageId().isEmpty() || item.uid() > 0) {
|
||||
QSqlQuery existing(db);
|
||||
if (!item.messageId().isEmpty()) {
|
||||
existing.prepare("SELECT id FROM MailCopy WHERE messageId = :messageId LIMIT 1");
|
||||
existing.bindValue(":messageId", item.messageId());
|
||||
} else {
|
||||
existing.prepare("SELECT id FROM MailCopy WHERE folderId = :folderId AND uid = :uid LIMIT 1");
|
||||
existing.bindValue(":folderId", item.folderId());
|
||||
existing.bindValue(":uid", item.uid());
|
||||
}
|
||||
if (existing.exec() && existing.next()) {
|
||||
item.setId(existing.value(0).toLongLong());
|
||||
return update(item);
|
||||
}
|
||||
}
|
||||
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT INTO MailCopy (folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid) "
|
||||
"VALUES (:folderId, :messageId, :subject, :sender, :recipient, :date, :read, :flagged, :hasAttachment, :size, :fileId, :uid)"
|
||||
"INSERT INTO MailCopy (folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr) "
|
||||
"VALUES (:folderId, :messageId, :subject, :sender, :recipient, :date, :read, :flagged, :hasAttachment, :size, :fileId, :uid, :bodyHtml, :toAddr, :ccAddr, :bccAddr)"
|
||||
);
|
||||
query.bindValue(":folderId", item.folderId());
|
||||
query.bindValue(":messageId", item.messageId());
|
||||
query.bindValue(":messageId", item.messageId().isEmpty() ? QVariant() : QVariant(item.messageId()));
|
||||
query.bindValue(":subject", item.subject());
|
||||
query.bindValue(":sender", item.sender());
|
||||
query.bindValue(":recipient", item.recipient());
|
||||
@@ -23,17 +43,27 @@ bool MailItemDao::insert(const MailItem& item)
|
||||
query.bindValue(":size", item.size());
|
||||
query.bindValue(":fileId", item.fileId());
|
||||
query.bindValue(":uid", item.uid());
|
||||
query.bindValue(":bodyHtml", item.bodyHtml());
|
||||
query.bindValue(":toAddr", item.to());
|
||||
query.bindValue(":ccAddr", item.cc());
|
||||
query.bindValue(":bccAddr", item.bcc());
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to insert mail item:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
item.setId(query.lastInsertId().toLongLong());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MailItemDao::upsert(MailItem& item)
|
||||
{
|
||||
return insert(item);
|
||||
}
|
||||
|
||||
bool MailItemDao::update(const MailItem& item)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"UPDATE MailCopy SET "
|
||||
@@ -46,14 +76,18 @@ bool MailItemDao::update(const MailItem& item)
|
||||
"read = :read, "
|
||||
"flagged = :flagged, "
|
||||
"hasAttachment = :hasAttachment, "
|
||||
"size = :size, "
|
||||
"fileId = :fileId, "
|
||||
"uid = :uid "
|
||||
"size = :size, "
|
||||
"fileId = :fileId, "
|
||||
"uid = :uid, "
|
||||
"bodyHtml = :bodyHtml, "
|
||||
"toAddr = :toAddr, "
|
||||
"ccAddr = :ccAddr, "
|
||||
"bccAddr = :bccAddr "
|
||||
"WHERE id = :id"
|
||||
);
|
||||
query.bindValue(":id", item.id());
|
||||
query.bindValue(":folderId", item.folderId());
|
||||
query.bindValue(":messageId", item.messageId());
|
||||
query.bindValue(":messageId", item.messageId().isEmpty() ? QVariant() : QVariant(item.messageId()));
|
||||
query.bindValue(":subject", item.subject());
|
||||
query.bindValue(":sender", item.sender());
|
||||
query.bindValue(":recipient", item.recipient());
|
||||
@@ -64,6 +98,10 @@ bool MailItemDao::update(const MailItem& item)
|
||||
query.bindValue(":size", item.size());
|
||||
query.bindValue(":fileId", item.fileId());
|
||||
query.bindValue(":uid", item.uid());
|
||||
query.bindValue(":bodyHtml", item.bodyHtml());
|
||||
query.bindValue(":toAddr", item.to());
|
||||
query.bindValue(":ccAddr", item.cc());
|
||||
query.bindValue(":bccAddr", item.bcc());
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to update mail item:" << query.lastError().text();
|
||||
@@ -74,7 +112,7 @@ bool MailItemDao::update(const MailItem& item)
|
||||
|
||||
bool MailItemDao::remove(qint64 id)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM MailCopy WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
@@ -88,9 +126,9 @@ bool MailItemDao::remove(qint64 id)
|
||||
|
||||
std::optional<MailItem> MailItemDao::findById(qint64 id)
|
||||
{
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid FROM MailCopy WHERE id = :id");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
if (!query.exec()) {
|
||||
@@ -112,6 +150,13 @@ std::optional<MailItem> MailItemDao::findById(qint64 id)
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
item.setBodyHtml(query.value(13).toString());
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
return item;
|
||||
}
|
||||
return std::nullopt;
|
||||
@@ -120,9 +165,9 @@ std::optional<MailItem> MailItemDao::findById(qint64 id)
|
||||
QVector<MailItem> MailItemDao::findAll()
|
||||
{
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
if (!query.exec("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid FROM MailCopy")) {
|
||||
if (!query.exec("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy")) {
|
||||
qWarning() << "Failed to fetch all mail items:" << query.lastError().text();
|
||||
return items;
|
||||
}
|
||||
@@ -141,6 +186,13 @@ QVector<MailItem> MailItemDao::findAll()
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
item.setBodyHtml(query.value(13).toString());
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
items.append(item);
|
||||
}
|
||||
return items;
|
||||
@@ -149,9 +201,9 @@ QVector<MailItem> MailItemDao::findAll()
|
||||
QVector<MailItem> MailItemDao::findByFolderId(int folderId)
|
||||
{
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid FROM MailCopy WHERE folderId = :folderId");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy WHERE folderId = :folderId");
|
||||
query.bindValue(":folderId", folderId);
|
||||
|
||||
if (!query.exec()) {
|
||||
@@ -173,6 +225,13 @@ QVector<MailItem> MailItemDao::findByFolderId(int folderId)
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
item.setBodyHtml(query.value(13).toString());
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
items.append(item);
|
||||
}
|
||||
return items;
|
||||
@@ -181,9 +240,9 @@ QVector<MailItem> MailItemDao::findByFolderId(int folderId)
|
||||
QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 sinceUid)
|
||||
{
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase& db = DatabaseManager::instance().database();
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid FROM MailCopy WHERE folderId = :folderId AND id > :sinceUid");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy WHERE folderId = :folderId AND uid > :sinceUid");
|
||||
query.bindValue(":folderId", folderId);
|
||||
query.bindValue(":sinceUid", sinceUid);
|
||||
|
||||
@@ -206,7 +265,132 @@ QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 since
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
item.setBodyHtml(query.value(13).toString());
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
items.append(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<qint64> MailItemDao::maxUidForFolder(int folderId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT MAX(uid) FROM MailCopy WHERE folderId = :folderId");
|
||||
query.bindValue(":folderId", folderId);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to get max uid for folder:" << query.lastError().text();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (query.next()) {
|
||||
if (query.isNull(0)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return query.value(0).toLongLong();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QVector<qint64> MailItemDao::getUidsForFolder(int folderId)
|
||||
{
|
||||
QVector<qint64> uids;
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT uid FROM MailCopy WHERE folderId = :folderId");
|
||||
query.bindValue(":folderId", folderId);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to get uids for folder:" << query.lastError().text();
|
||||
return uids;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
qint64 uid = query.value(0).toLongLong();
|
||||
uids.append(uid);
|
||||
}
|
||||
return uids;
|
||||
}
|
||||
|
||||
QVector<qint64> MailItemDao::uidsForFolder(int folderId)
|
||||
{
|
||||
return getUidsForFolder(folderId);
|
||||
}
|
||||
|
||||
std::optional<MailItem> MailItemDao::findByUid(int folderId, qint64 uid)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id FROM MailCopy WHERE folderId = :folderId AND uid = :uid LIMIT 1");
|
||||
query.bindValue(":folderId", folderId);
|
||||
query.bindValue(":uid", uid);
|
||||
if (!query.exec() || !query.next()) return std::nullopt;
|
||||
return findById(query.value(0).toLongLong());
|
||||
}
|
||||
|
||||
bool MailItemDao::removeByUid(int folderId, qint64 uid)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM MailCopy WHERE folderId = :folderId AND uid = :uid");
|
||||
query.bindValue(":folderId", folderId);
|
||||
query.bindValue(":uid", uid);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to delete mail item by UID:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MailItemDao::replaceAttachments(qint64 mailItemId,
|
||||
const QVector<StoredAttachmentRecord>& attachments)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
if (!db.transaction()) return false;
|
||||
|
||||
QSqlQuery remove(db);
|
||||
remove.prepare("DELETE FROM Attachment WHERE mailCopyId = :mailCopyId");
|
||||
remove.bindValue(":mailCopyId", mailItemId);
|
||||
if (!remove.exec()) { db.rollback(); return false; }
|
||||
|
||||
QSqlQuery insert(db);
|
||||
insert.prepare("INSERT INTO Attachment (mailCopyId, filename, mimeType, size, contentId, storedPath) "
|
||||
"VALUES (:mailCopyId, :filename, :mimeType, :size, :contentId, :storedPath)");
|
||||
for (const StoredAttachmentRecord &attachment : attachments) {
|
||||
insert.bindValue(":mailCopyId", mailItemId);
|
||||
insert.bindValue(":filename", attachment.fileName);
|
||||
insert.bindValue(":mimeType", attachment.mimeType);
|
||||
insert.bindValue(":size", attachment.size);
|
||||
insert.bindValue(":contentId", attachment.contentId);
|
||||
insert.bindValue(":storedPath", attachment.storedPath);
|
||||
if (!insert.exec()) { db.rollback(); return false; }
|
||||
}
|
||||
return db.commit();
|
||||
}
|
||||
|
||||
QVector<StoredAttachmentRecord> MailItemDao::attachmentsForMail(qint64 mailItemId)
|
||||
{
|
||||
QVector<StoredAttachmentRecord> result;
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT filename, mimeType, size, contentId, storedPath "
|
||||
"FROM Attachment WHERE mailCopyId = :mailCopyId ORDER BY id");
|
||||
query.bindValue(":mailCopyId", mailItemId);
|
||||
if (!query.exec()) return result;
|
||||
while (query.next()) {
|
||||
StoredAttachmentRecord attachment;
|
||||
attachment.fileName = query.value(0).toString();
|
||||
attachment.mimeType = query.value(1).toString();
|
||||
attachment.size = query.value(2).toLongLong();
|
||||
attachment.contentId = query.value(3).toString();
|
||||
attachment.storedPath = query.value(4).toString();
|
||||
result.append(attachment);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user