#include #include #include "mailitemdao.h" #include #include bool MailItemDao::insert(MailItem& item) { 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, 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().isEmpty() ? QVariant() : QVariant(item.messageId())); query.bindValue(":subject", item.subject()); query.bindValue(":sender", item.sender()); query.bindValue(":recipient", item.recipient()); query.bindValue(":date", item.date()); query.bindValue(":read", item.isRead() ? 1 : 0); query.bindValue(":flagged", item.isFlagged() ? 1 : 0); query.bindValue(":hasAttachment", !item.attachments().isEmpty() ? 1 : 0); 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(); QSqlQuery query(db); query.prepare( "UPDATE MailCopy SET " "folderId = :folderId, " "messageId = :messageId, " "subject = :subject, " "sender = :sender, " "recipient = :recipient, " "date = :date, " "read = :read, " "flagged = :flagged, " "hasAttachment = :hasAttachment, " "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().isEmpty() ? QVariant() : QVariant(item.messageId())); query.bindValue(":subject", item.subject()); query.bindValue(":sender", item.sender()); query.bindValue(":recipient", item.recipient()); query.bindValue(":date", item.date()); query.bindValue(":read", item.isRead() ? 1 : 0); query.bindValue(":flagged", item.isFlagged() ? 1 : 0); query.bindValue(":hasAttachment", !item.attachments().isEmpty() ? 1 : 0); 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(); return false; } return true; } bool MailItemDao::remove(qint64 id) { QSqlDatabase db = DatabaseManager::instance().database(); QSqlQuery query(db); query.prepare("DELETE FROM MailCopy WHERE id = :id"); query.bindValue(":id", id); if (!query.exec()) { qWarning() << "Failed to delete mail item:" << query.lastError().text(); return false; } return true; } std::optional MailItemDao::findById(qint64 id) { QSqlDatabase db = DatabaseManager::instance().database(); QSqlQuery query(db); 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()) { qWarning() << "Failed to find mail item by id:" << query.lastError().text(); return std::nullopt; } if (query.next()) { MailItem item; item.setId(query.value(0).toInt()); item.setFolderId(query.value(1).toInt()); item.setMessageId(query.value(2).toString()); item.setSubject(query.value(3).toString()); item.setSender(query.value(4).toString()); item.setRecipient(query.value(5).toString()); item.setDate(query.value(6).toDateTime()); item.setRead(query.value(7).toBool()); item.setFlagged(query.value(8).toBool()); 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 names; for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName); item.setAttachments(names); return item; } return std::nullopt; } QVector MailItemDao::findAll() { QVector items; 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, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy")) { qWarning() << "Failed to fetch all mail items:" << query.lastError().text(); return items; } while (query.next()) { MailItem item; item.setId(query.value(0).toInt()); item.setFolderId(query.value(1).toInt()); item.setMessageId(query.value(2).toString()); item.setSubject(query.value(3).toString()); item.setSender(query.value(4).toString()); item.setRecipient(query.value(5).toString()); item.setDate(query.value(6).toDateTime()); item.setRead(query.value(7).toBool()); item.setFlagged(query.value(8).toBool()); 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 names; for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName); item.setAttachments(names); items.append(item); } return items; } QVector MailItemDao::findByFolderId(int folderId) { QVector items; QSqlDatabase db = DatabaseManager::instance().database(); QSqlQuery query(db); 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()) { qWarning() << "Failed to fetch mail items by folder id:" << query.lastError().text(); return items; } while (query.next()) { MailItem item; item.setId(query.value(0).toInt()); item.setFolderId(query.value(1).toInt()); item.setMessageId(query.value(2).toString()); item.setSubject(query.value(3).toString()); item.setSender(query.value(4).toString()); item.setRecipient(query.value(5).toString()); item.setDate(query.value(6).toDateTime()); item.setRead(query.value(7).toBool()); item.setFlagged(query.value(8).toBool()); 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 names; for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName); item.setAttachments(names); items.append(item); } return items; } QVector MailItemDao::findByFolderIdSinceUid(int folderId, qint64 sinceUid) { QVector items; QSqlDatabase db = DatabaseManager::instance().database(); QSqlQuery query(db); 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); if (!query.exec()) { qWarning() << "Failed to fetch mail items by folder id since uid:" << query.lastError().text(); return items; } while (query.next()) { MailItem item; item.setId(query.value(0).toInt()); item.setFolderId(query.value(1).toInt()); item.setMessageId(query.value(2).toString()); item.setSubject(query.value(3).toString()); item.setSender(query.value(4).toString()); item.setRecipient(query.value(5).toString()); item.setDate(query.value(6).toDateTime()); item.setRead(query.value(7).toBool()); item.setFlagged(query.value(8).toBool()); 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 names; for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName); item.setAttachments(names); items.append(item); } return items; } std::optional 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 MailItemDao::getUidsForFolder(int folderId) { QVector 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 MailItemDao::uidsForFolder(int folderId) { return getUidsForFolder(folderId); } std::optional 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& 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 MailItemDao::attachmentsForMail(qint64 mailItemId) { QVector 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; } int MailItemDao::countUnreadByFolderId(int folderId) { QSqlDatabase db = DatabaseManager::instance().database(); QSqlQuery query(db); query.prepare("SELECT COUNT(*) FROM MailCopy WHERE folderId = :folderId AND read = 0"); query.bindValue(":folderId", folderId); if (!query.exec()) { qWarning() << "Failed to count unread mails:" << query.lastError().text(); return 0; } if (query.next()) { return query.value(0).toInt(); } return 0; }