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:
2026-08-17 15:34:49 +02:00
parent 364624aada
commit a004bdd60f
94 changed files with 19408 additions and 1047 deletions
+12 -9
View File
@@ -2,7 +2,7 @@
qint64 AccountDao::insert(const Account& account)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO Account (email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
@@ -39,12 +39,13 @@ qint64 AccountDao::insert(const Account& account)
bool AccountDao::update(const Account& account)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"UPDATE Account SET "
"email = :email, "
"displayName = :displayName, "
"signature = :signature, "
"type = :type, "
"accessToken = :accessToken, "
"refreshToken = :refreshToken, "
@@ -66,6 +67,7 @@ bool AccountDao::update(const Account& account)
query.bindValue(":type", static_cast<int>(account.type()));
query.bindValue(":accessToken", account.accessToken());
query.bindValue(":refreshToken", account.refreshToken());
query.bindValue(":tokenExpires", account.tokenExpires());
const auto& settings = account.connectionSettings();
query.bindValue(":incomingHost", settings.incomingHost);
query.bindValue(":incomingPort", settings.incomingPort);
@@ -86,7 +88,7 @@ bool AccountDao::update(const Account& account)
bool AccountDao::remove(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Account WHERE id = :id");
query.bindValue(":id", id);
@@ -100,10 +102,10 @@ bool AccountDao::remove(int id)
Account* AccountDao::findById(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"SELECT id, email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account WHERE id = :id");
@@ -144,10 +146,10 @@ Account* AccountDao::findById(int id)
QVector<Account> AccountDao::findAll()
{
QVector<Account> accounts;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
if (!query.exec(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"SELECT id, email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account")) {
@@ -160,6 +162,7 @@ QVector<Account> AccountDao::findAll()
acc.setId(query.value("id").toInt());
acc.setEmail(query.value("email").toString());
acc.setDisplayName(query.value("displayName").toString());
acc.setSignature(query.value("signature").toString());
acc.setType(static_cast<AccountType>(query.value("type").toInt()));
acc.setAccessToken(query.value("accessToken").toString());
acc.setRefreshToken(query.value("refreshToken").toString());
@@ -182,10 +185,10 @@ QVector<Account> AccountDao::findAll()
Account* AccountDao::findByEmail(const QString& email)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"SELECT id, email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account WHERE email = :email");
+221
View File
@@ -0,0 +1,221 @@
#include "accountdao.h"
bool AccountDao::insert(const Account& account)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO Account (email, displayName, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod) "
"VALUES (:email, :displayName, :type, :accessToken, :refreshToken, :tokenExpires, "
":incomingHost, :incomingPort, :incomingSsl, :outgoingHost, :outgoingPort, :outgoingSsl, "
":connUsername, :connPassword, :authMethod)");
query.bindValue(":email", account.email());
query.bindValue(":displayName", account.displayName());
query.bindValue(":type", static_cast<int>(account.type()));
query.bindValue(":accessToken", account.accessToken());
query.bindValue(":refreshToken", account.refreshToken());
query.bindValue(":tokenExpires", account.tokenExpires());
const auto& settings = account.connectionSettings();
query.bindValue(":incomingHost", settings.incomingHost);
query.bindValue(":incomingPort", settings.incomingPort);
query.bindValue(":incomingSsl", settings.incomingSsl ? 1 : 0);
query.bindValue(":outgoingHost", settings.outgoingHost);
query.bindValue(":outgoingPort", settings.outgoingPort);
query.bindValue(":outgoingSsl", settings.outgoingSsl ? 1 : 0);
query.bindValue(":connUsername", settings.username);
query.bindValue(":connPassword", Account::encryptPassword(settings.password));
query.bindValue(":authMethod", settings.authMethod);
if (!query.exec()) {
qWarning() << "Failed to insert account:" << query.lastError().text();
return false;
}
return true;
}
bool AccountDao::update(const Account& account)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"UPDATE Account SET "
"email = :email, "
"displayName = :displayName, "
"type = :type, "
"accessToken = :accessToken, "
"refreshToken = :refreshToken, "
"tokenExpires = :tokenExpires, "
"incomingHost = :incomingHost, "
"incomingPort = :incomingPort, "
"incomingSsl = :incomingSsl, "
"outgoingHost = :outgoingHost, "
"outgoingPort = :outgoingPort, "
"outgoingSsl = :outgoingSsl, "
"connUsername = :connUsername, "
"connPassword = :connPassword, "
"authMethod = :authMethod "
"WHERE id = :id");
query.bindValue(":id", account.id());
query.bindValue(":email", account.email());
query.bindValue(":displayName", account.displayName());
query.bindValue(":type", static_cast<int>(account.type()));
query.bindValue(":accessToken", account.accessToken());
query.bindValue(":refreshToken", account.refreshToken());
query.bindValue(":tokenExpires", account.tokenExpires());
const auto& settings = account.connectionSettings();
query.bindValue(":incomingHost", settings.incomingHost);
query.bindValue(":incomingPort", settings.incomingPort);
query.bindValue(":incomingSsl", settings.incomingSsl ? 1 : 0);
query.bindValue(":outgoingHost", settings.outgoingHost);
query.bindValue(":outgoingPort", settings.outgoingPort);
query.bindValue(":outgoingSsl", settings.outgoingSsl ? 1 : 0);
query.bindValue(":connUsername", settings.username);
query.bindValue(":connPassword", Account::encryptPassword(settings.password));
query.bindValue(":authMethod", settings.authMethod);
if (!query.exec()) {
qWarning() << "Failed to update account:" << query.lastError().text();
return false;
}
return true;
}
bool AccountDao::remove(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Account WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to delete account:" << query.lastError().text();
return false;
}
return true;
}
Account* AccountDao::findById(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to find account by id:" << query.lastError().text();
return nullptr;
}
if (query.next()) {
Account* account = new Account();
account->setId(query.value("id").toInt());
account->setEmail(query.value("email").toString());
account->setDisplayName(query.value("displayName").toString());
account->setType(static_cast<AccountType>(query.value("type").toInt()));
account->setAccessToken(query.value("accessToken").toString());
account->setRefreshToken(query.value("refreshToken").toString());
account->setTokenExpires(query.value("tokenExpires").toDateTime());
Account::ConnectionSettings settings;
settings.incomingHost = query.value("incomingHost").toString();
settings.incomingPort = query.value("incomingPort").toInt();
settings.incomingSsl = query.value("incomingSsl").toBool();
settings.outgoingHost = query.value("outgoingHost").toString();
settings.outgoingPort = query.value("outgoingPort").toInt();
settings.outgoingSsl = query.value("outgoingSsl").toBool();
settings.username = query.value("connUsername").toString();
settings.password = Account::decryptPassword(query.value("connPassword").toString());
settings.authMethod = query.value("authMethod").toString();
account->setConnectionSettings(settings);
return account;
}
return nullptr;
}
QVector<Account> AccountDao::findAll()
{
QVector<Account> accounts;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
if (!query.exec(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account")) {
qWarning() << "Failed to fetch all accounts:" << query.lastError().text();
return accounts;
}
while (query.next()) {
Account acc;
acc.setId(query.value("id").toInt());
acc.setEmail(query.value("email").toString());
acc.setDisplayName(query.value("displayName").toString());
acc.setType(static_cast<AccountType>(query.value("type").toInt()));
acc.setAccessToken(query.value("accessToken").toString());
acc.setRefreshToken(query.value("refreshToken").toString());
acc.setTokenExpires(query.value("tokenExpires").toDateTime());
Account::ConnectionSettings settings;
settings.incomingHost = query.value("incomingHost").toString();
settings.incomingPort = query.value("incomingPort").toInt();
settings.incomingSsl = query.value("incomingSsl").toBool();
settings.outgoingHost = query.value("outgoingHost").toString();
settings.outgoingPort = query.value("outgoingPort").toInt();
settings.outgoingSsl = query.value("outgoingSsl").toBool();
settings.username = query.value("connUsername").toString();
settings.password = Account::decryptPassword(query.value("connPassword").toString());
settings.authMethod = query.value("authMethod").toString();
acc.setConnectionSettings(settings);
accounts.append(acc);
}
return accounts;
}
Account* AccountDao::findByEmail(const QString& email)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account WHERE email = :email");
query.bindValue(":email", email);
if (!query.exec()) {
qWarning() << "Failed to find account by email:" << query.lastError().text();
return nullptr;
}
if (query.next()) {
Account* acc = new Account();
acc->setId(query.value("id").toInt());
acc->setEmail(query.value("email").toString());
acc->setDisplayName(query.value("displayName").toString());
acc->setType(static_cast<AccountType>(query.value("type").toInt()));
acc->setAccessToken(query.value("accessToken").toString());
acc->setRefreshToken(query.value("refreshToken").toString());
acc->setTokenExpires(query.value("tokenExpires").toDateTime());
Account::ConnectionSettings settings;
settings.incomingHost = query.value("incomingHost").toString();
settings.incomingPort = query.value("incomingPort").toInt();
settings.incomingSsl = query.value("incomingSsl").toBool();
settings.outgoingHost = query.value("outgoingHost").toString();
settings.outgoingPort = query.value("outgoingPort").toInt();
settings.outgoingSsl = query.value("outgoingSsl").toBool();
settings.username = query.value("connUsername").toString();
settings.password = Account::decryptPassword(query.value("connPassword").toString());
settings.authMethod = query.value("authMethod").toString();
acc->setConnectionSettings(settings);
return acc;
}
return nullptr;
}
+224
View File
@@ -0,0 +1,224 @@
#include "accountdao.h"
qint64 AccountDao::insert(const Account& account)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO Account (email, displayName, signature, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod) "
"VALUES (:email, :displayName, :signature, :type, :accessToken, :refreshToken, :tokenExpires, "
":incomingHost, :incomingPort, :incomingSsl, :outgoingHost, :outgoingPort, :outgoingSsl, "
":connUsername, :connPassword, :authMethod)");
query.bindValue(":email", account.email());
query.bindValue(":displayName", account.displayName());
query.bindValue(":signature", account.signature());
query.bindValue(":type", static_cast<int>(account.type()));
query.bindValue(":accessToken", account.accessToken());
query.bindValue(":refreshToken", account.refreshToken());
query.bindValue(":tokenExpires", account.tokenExpires());
const auto& settings = account.connectionSettings();
query.bindValue(":incomingHost", settings.incomingHost);
query.bindValue(":incomingPort", settings.incomingPort);
query.bindValue(":incomingSsl", settings.incomingSsl ? 1 : 0);
query.bindValue(":outgoingHost", settings.outgoingHost);
query.bindValue(":outgoingPort", settings.outgoingPort);
query.bindValue(":outgoingSsl", settings.outgoingSsl ? 1 : 0);
query.bindValue(":connUsername", settings.username);
query.bindValue(":connPassword", Account::encryptPassword(settings.password));
query.bindValue(":authMethod", settings.authMethod);
if (!query.exec()) {
qWarning() << "Failed to insert account:" << query.lastError().text();
return -1;
}
return query.lastInsertId().toLongLong();
}
bool AccountDao::update(const Account& account)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"UPDATE Account SET "
"email = :email, "
"displayName = :displayName, "
"type = :type, "
"accessToken = :accessToken, "
"refreshToken = :refreshToken, "
"tokenExpires = :tokenExpires, "
"incomingHost = :incomingHost, "
"incomingPort = :incomingPort, "
"incomingSsl = :incomingSsl, "
"outgoingHost = :outgoingHost, "
"outgoingPort = :outgoingPort, "
"outgoingSsl = :outgoingSsl, "
"connUsername = :connUsername, "
"connPassword = :connPassword, "
"authMethod = :authMethod "
"WHERE id = :id");
query.bindValue(":id", account.id());
query.bindValue(":email", account.email());
query.bindValue(":displayName", account.displayName());
query.bindValue(":signature", account.signature());
query.bindValue(":type", static_cast<int>(account.type()));
query.bindValue(":accessToken", account.accessToken());
query.bindValue(":refreshToken", account.refreshToken());
const auto& settings = account.connectionSettings();
query.bindValue(":incomingHost", settings.incomingHost);
query.bindValue(":incomingPort", settings.incomingPort);
query.bindValue(":incomingSsl", settings.incomingSsl ? 1 : 0);
query.bindValue(":outgoingHost", settings.outgoingHost);
query.bindValue(":outgoingPort", settings.outgoingPort);
query.bindValue(":outgoingSsl", settings.outgoingSsl ? 1 : 0);
query.bindValue(":connUsername", settings.username);
query.bindValue(":connPassword", Account::encryptPassword(settings.password));
query.bindValue(":authMethod", settings.authMethod);
if (!query.exec()) {
qWarning() << "Failed to update account:" << query.lastError().text();
return false;
}
return true;
}
bool AccountDao::remove(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Account WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to delete account:" << query.lastError().text();
return false;
}
return true;
}
Account* AccountDao::findById(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to find account by id:" << query.lastError().text();
return nullptr;
}
if (query.next()) {
Account* account = new Account();
account->setId(query.value("id").toInt());
account->setEmail(query.value("email").toString());
account->setDisplayName(query.value("displayName").toString());
account->setSignature(query.value("signature").toString());
account->setType(static_cast<AccountType>(query.value("type").toInt()));
account->setAccessToken(query.value("accessToken").toString());
account->setRefreshToken(query.value("refreshToken").toString());
account->setTokenExpires(query.value("tokenExpires").toDateTime());
Account::ConnectionSettings settings;
settings.incomingHost = query.value("incomingHost").toString();
settings.incomingPort = query.value("incomingPort").toInt();
settings.incomingSsl = query.value("incomingSsl").toBool();
settings.outgoingHost = query.value("outgoingHost").toString();
settings.outgoingPort = query.value("outgoingPort").toInt();
settings.outgoingSsl = query.value("outgoingSsl").toBool();
settings.username = query.value("connUsername").toString();
settings.password = Account::decryptPassword(query.value("connPassword").toString());
settings.authMethod = query.value("authMethod").toString();
account->setConnectionSettings(settings);
return account;
}
return nullptr;
}
QVector<Account> AccountDao::findAll()
{
QVector<Account> accounts;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
if (!query.exec(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account")) {
qWarning() << "Failed to fetch all accounts:" << query.lastError().text();
return accounts;
}
while (query.next()) {
Account acc;
acc.setId(query.value("id").toInt());
acc.setEmail(query.value("email").toString());
acc.setDisplayName(query.value("displayName").toString());
acc.setType(static_cast<AccountType>(query.value("type").toInt()));
acc.setAccessToken(query.value("accessToken").toString());
acc.setRefreshToken(query.value("refreshToken").toString());
acc.setTokenExpires(query.value("tokenExpires").toDateTime());
Account::ConnectionSettings settings;
settings.incomingHost = query.value("incomingHost").toString();
settings.incomingPort = query.value("incomingPort").toInt();
settings.incomingSsl = query.value("incomingSsl").toBool();
settings.outgoingHost = query.value("outgoingHost").toString();
settings.outgoingPort = query.value("outgoingPort").toInt();
settings.outgoingSsl = query.value("outgoingSsl").toBool();
settings.username = query.value("connUsername").toString();
settings.password = Account::decryptPassword(query.value("connPassword").toString());
settings.authMethod = query.value("authMethod").toString();
acc.setConnectionSettings(settings);
accounts.append(acc);
}
return accounts;
}
Account* AccountDao::findByEmail(const QString& email)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"SELECT id, email, displayName, type, accessToken, refreshToken, tokenExpires, "
"incomingHost, incomingPort, incomingSsl, outgoingHost, outgoingPort, outgoingSsl, "
"connUsername, connPassword, authMethod "
"FROM Account WHERE email = :email");
query.bindValue(":email", email);
if (!query.exec()) {
qWarning() << "Failed to find account by email:" << query.lastError().text();
return nullptr;
}
if (query.next()) {
Account* acc = new Account();
acc->setId(query.value("id").toInt());
acc->setEmail(query.value("email").toString());
acc->setDisplayName(query.value("displayName").toString());
acc->setSignature(query.value("signature").toString());
acc->setType(static_cast<AccountType>(query.value("type").toInt()));
acc->setAccessToken(query.value("accessToken").toString());
acc->setRefreshToken(query.value("refreshToken").toString());
acc->setTokenExpires(query.value("tokenExpires").toDateTime());
Account::ConnectionSettings settings;
settings.incomingHost = query.value("incomingHost").toString();
settings.incomingPort = query.value("incomingPort").toInt();
settings.incomingSsl = query.value("incomingSsl").toBool();
settings.outgoingHost = query.value("outgoingHost").toString();
settings.outgoingPort = query.value("outgoingPort").toInt();
settings.outgoingSsl = query.value("outgoingSsl").toBool();
settings.username = query.value("connUsername").toString();
settings.password = Account::decryptPassword(query.value("connPassword").toString());
settings.authMethod = query.value("authMethod").toString();
acc->setConnectionSettings(settings);
return acc;
}
return nullptr;
}
+32 -32
View File
@@ -4,34 +4,9 @@
#include <QSqlError>
#include <optional>
bool FolderDao::insert(const Folder& folder)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO Folder (accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced) "
"VALUES (:accountId, :name, :parentFolderId, :isInbox, :isSent, :isDrafts, :isTrash, :unreadCount, :lastSynced)"
);
query.bindValue(":accountId", folder.accountId());
query.bindValue(":name", folder.name());
query.bindValue(":parentFolderId", folder.parentFolderId());
query.bindValue(":isInbox", folder.isInbox() ? 1 : 0);
query.bindValue(":isSent", folder.isSent() ? 1 : 0);
query.bindValue(":isDrafts", folder.isDrafts() ? 1 : 0);
query.bindValue(":isTrash", folder.isTrash() ? 1 : 0);
query.bindValue(":unreadCount", folder.unreadCount());
query.bindValue(":lastSynced", folder.lastSynced());
if (!query.exec()) {
qWarning() << "Failed to insert folder:" << query.lastError().text();
return false;
}
return true;
}
bool FolderDao::update(const Folder& folder)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"UPDATE Folder SET "
@@ -66,7 +41,7 @@ bool FolderDao::update(const Folder& folder)
bool FolderDao::remove(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Folder WHERE id = :id");
query.bindValue(":id", id);
@@ -80,7 +55,7 @@ bool FolderDao::remove(int id)
std::optional<Folder> FolderDao::findById(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder WHERE id = :id");
query.bindValue(":id", id);
@@ -110,7 +85,7 @@ std::optional<Folder> FolderDao::findById(int id)
QVector<Folder> FolderDao::findAll()
{
QVector<Folder> folders;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
if (!query.exec("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder")) {
qWarning() << "Failed to fetch all folders:" << query.lastError().text();
@@ -137,7 +112,7 @@ QVector<Folder> FolderDao::findAll()
QVector<Folder> FolderDao::findByAccountId(int accountId)
{
QVector<Folder> folders;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder WHERE accountId = :accountId");
query.bindValue(":accountId", accountId);
@@ -166,7 +141,7 @@ QVector<Folder> FolderDao::findByAccountId(int accountId)
bool FolderDao::removeByAccountId(int accountId)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Folder WHERE accountId = :accountId");
query.bindValue(":accountId", accountId);
@@ -176,4 +151,29 @@ bool FolderDao::removeByAccountId(int accountId)
return false;
}
return true;
}
}
qint64 FolderDao::insert(const Folder& folder)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO Folder (accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced) "
"VALUES (:accountId, :name, :parentFolderId, :isInbox, :isSent, :isDrafts, :isTrash, :unreadCount, :lastSynced)"
);
query.bindValue(":accountId", folder.accountId());
query.bindValue(":name", folder.name());
query.bindValue(":parentFolderId", folder.parentFolderId());
query.bindValue(":isInbox", folder.isInbox() ? 1 : 0);
query.bindValue(":isSent", folder.isSent() ? 1 : 0);
query.bindValue(":isDrafts", folder.isDrafts() ? 1 : 0);
query.bindValue(":isTrash", folder.isTrash() ? 1 : 0);
query.bindValue(":unreadCount", folder.unreadCount());
query.bindValue(":lastSynced", folder.lastSynced());
if (!query.exec()) {
qWarning() << "Failed to insert folder:" << query.lastError().text();
return -1;
}
return query.lastInsertId().toLongLong();
}
+179
View File
@@ -0,0 +1,179 @@
#include <QSqlQuery>
#include "folderdao.h"
#include <QSqlError>
#include <QSqlError>
#include <optional>
bool FolderDao::insert(const Folder& folder)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO Folder (accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced) "
"VALUES (:accountId, :name, :parentFolderId, :isInbox, :isSent, :isDrafts, :isTrash, :unreadCount, :lastSynced)"
);
query.bindValue(":accountId", folder.accountId());
query.bindValue(":name", folder.name());
query.bindValue(":parentFolderId", folder.parentFolderId());
query.bindValue(":isInbox", folder.isInbox() ? 1 : 0);
query.bindValue(":isSent", folder.isSent() ? 1 : 0);
query.bindValue(":isDrafts", folder.isDrafts() ? 1 : 0);
query.bindValue(":isTrash", folder.isTrash() ? 1 : 0);
query.bindValue(":unreadCount", folder.unreadCount());
query.bindValue(":lastSynced", folder.lastSynced());
if (!query.exec()) {
qWarning() << "Failed to insert folder:" << query.lastError().text();
return false;
}
return true;
}
bool FolderDao::update(const Folder& folder)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"UPDATE Folder SET "
"accountId = :accountId, "
"name = :name, "
"parentFolderId = :parentFolderId, "
"isInbox = :isInbox, "
"isSent = :isSent, "
"isDrafts = :isDrafts, "
"isTrash = :isTrash, "
"unreadCount = :unreadCount, "
"lastSynced = :lastSynced "
"WHERE id = :id"
);
query.bindValue(":id", folder.id());
query.bindValue(":accountId", folder.accountId());
query.bindValue(":name", folder.name());
query.bindValue(":parentFolderId", folder.parentFolderId());
query.bindValue(":isInbox", folder.isInbox() ? 1 : 0);
query.bindValue(":isSent", folder.isSent() ? 1 : 0);
query.bindValue(":isDrafts", folder.isDrafts() ? 1 : 0);
query.bindValue(":isTrash", folder.isTrash() ? 1 : 0);
query.bindValue(":unreadCount", folder.unreadCount());
query.bindValue(":lastSynced", folder.lastSynced());
if (!query.exec()) {
qWarning() << "Failed to update folder:" << query.lastError().text();
return false;
}
return true;
}
bool FolderDao::remove(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Folder WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to delete folder:" << query.lastError().text();
return false;
}
return true;
}
std::optional<Folder> FolderDao::findById(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to find folder by id:" << query.lastError().text();
return std::nullopt;
}
if (query.next()) {
Folder fld;
fld.setId(query.value(0).toInt());
fld.setAccountId(query.value(1).toInt());
fld.setName(query.value(2).toString());
fld.setParentFolderId(query.value(3).toString());
fld.setInbox(query.value(4).toBool());
fld.setSent(query.value(5).toBool());
fld.setDrafts(query.value(6).toBool());
fld.setTrash(query.value(7).toBool());
fld.setUnreadCount(query.value(8).toInt());
fld.setLastSynced(query.value(9).toDateTime());
return fld;
}
return std::nullopt;
}
QVector<Folder> FolderDao::findAll()
{
QVector<Folder> folders;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
if (!query.exec("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder")) {
qWarning() << "Failed to fetch all folders:" << query.lastError().text();
return folders;
}
while (query.next()) {
Folder fld;
fld.setId(query.value(0).toInt());
fld.setAccountId(query.value(1).toInt());
fld.setName(query.value(2).toString());
fld.setParentFolderId(query.value(3).toString());
fld.setInbox(query.value(4).toBool());
fld.setSent(query.value(5).toBool());
fld.setDrafts(query.value(6).toBool());
fld.setTrash(query.value(7).toBool());
fld.setUnreadCount(query.value(8).toInt());
fld.setLastSynced(query.value(9).toDateTime());
folders.push_back(fld);
}
return folders;
}
QVector<Folder> FolderDao::findByAccountId(int accountId)
{
QVector<Folder> folders;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder WHERE accountId = :accountId");
query.bindValue(":accountId", accountId);
if (!query.exec()) {
qWarning() << "Failed to fetch folders by account id:" << query.lastError().text();
return folders;
}
while (query.next()) {
Folder fld;
fld.setId(query.value(0).toInt());
fld.setAccountId(query.value(1).toInt());
fld.setName(query.value(2).toString());
fld.setParentFolderId(query.value(3).toString());
fld.setInbox(query.value(4).toBool());
fld.setSent(query.value(5).toBool());
fld.setDrafts(query.value(6).toBool());
fld.setTrash(query.value(7).toBool());
fld.setUnreadCount(query.value(8).toInt());
fld.setLastSynced(query.value(9).toDateTime());
folders.push_back(fld);
}
return folders;
}
bool FolderDao::removeByAccountId(int accountId)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Folder WHERE accountId = :accountId");
query.bindValue(":accountId", accountId);
if (!query.exec()) {
qWarning() << "Failed to delete folders by account id:" << query.lastError().text();
return false;
}
return true;
}
+180
View File
@@ -0,0 +1,180 @@
#include <QSqlQuery>
#include "folderdao.h"
#include <QSqlError>
#include <QSqlError>
#include <optional>
qint64 FolderDao::insert(Folder& folder)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO Folder (accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced) "
"VALUES (:accountId, :name, :parentFolderId, :isInbox, :isSent, :isDrafts, :isTrash, :unreadCount, :lastSynced)"
);
query.bindValue(":accountId", folder.accountId());
query.bindValue(":name", folder.name());
query.bindValue(":parentFolderId", folder.parentFolderId());
query.bindValue(":isInbox", folder.isInbox() ? 1 : 0);
query.bindValue(":isSent", folder.isSent() ? 1 : 0);
query.bindValue(":isDrafts", folder.isDrafts() ? 1 : 0);
query.bindValue(":isTrash", folder.isTrash() ? 1 : 0);
query.bindValue(":unreadCount", folder.unreadCount());
query.bindValue(":lastSynced", folder.lastSynced());
if (!query.exec()) {
qWarning() << "Failed to insert folder:" << query.lastError().text();
return -1;
}
folder.setId(query.lastInsertId().toLongLong());
return folder.id();
}
bool FolderDao::update(const Folder& folder)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"UPDATE Folder SET "
"accountId = :accountId, "
"name = :name, "
"parentFolderId = :parentFolderId, "
"isInbox = :isInbox, "
"isSent = :isSent, "
"isDrafts = :isDrafts, "
"isTrash = :isTrash, "
"unreadCount = :unreadCount, "
"lastSynced = :lastSynced "
"WHERE id = :id"
);
query.bindValue(":id", folder.id());
query.bindValue(":accountId", folder.accountId());
query.bindValue(":name", folder.name());
query.bindValue(":parentFolderId", folder.parentFolderId());
query.bindValue(":isInbox", folder.isInbox() ? 1 : 0);
query.bindValue(":isSent", folder.isSent() ? 1 : 0);
query.bindValue(":isDrafts", folder.isDrafts() ? 1 : 0);
query.bindValue(":isTrash", folder.isTrash() ? 1 : 0);
query.bindValue(":unreadCount", folder.unreadCount());
query.bindValue(":lastSynced", folder.lastSynced());
if (!query.exec()) {
qWarning() << "Failed to update folder:" << query.lastError().text();
return false;
}
return true;
}
bool FolderDao::remove(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Folder WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to delete folder:" << query.lastError().text();
return false;
}
return true;
}
std::optional<Folder> FolderDao::findById(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to find folder by id:" << query.lastError().text();
return std::nullopt;
}
if (query.next()) {
Folder fld;
fld.setId(query.value(0).toInt());
fld.setAccountId(query.value(1).toInt());
fld.setName(query.value(2).toString());
fld.setParentFolderId(query.value(3).toString());
fld.setInbox(query.value(4).toBool());
fld.setSent(query.value(5).toBool());
fld.setDrafts(query.value(6).toBool());
fld.setTrash(query.value(7).toBool());
fld.setUnreadCount(query.value(8).toInt());
fld.setLastSynced(query.value(9).toDateTime());
return fld;
}
return std::nullopt;
}
QVector<Folder> FolderDao::findAll()
{
QVector<Folder> folders;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
if (!query.exec("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder")) {
qWarning() << "Failed to fetch all folders:" << query.lastError().text();
return folders;
}
while (query.next()) {
Folder fld;
fld.setId(query.value(0).toInt());
fld.setAccountId(query.value(1).toInt());
fld.setName(query.value(2).toString());
fld.setParentFolderId(query.value(3).toString());
fld.setInbox(query.value(4).toBool());
fld.setSent(query.value(5).toBool());
fld.setDrafts(query.value(6).toBool());
fld.setTrash(query.value(7).toBool());
fld.setUnreadCount(query.value(8).toInt());
fld.setLastSynced(query.value(9).toDateTime());
folders.push_back(fld);
}
return folders;
}
QVector<Folder> FolderDao::findByAccountId(int accountId)
{
QVector<Folder> folders;
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, parentFolderId, isInbox, isSent, isDrafts, isTrash, unreadCount, lastSynced FROM Folder WHERE accountId = :accountId");
query.bindValue(":accountId", accountId);
if (!query.exec()) {
qWarning() << "Failed to fetch folders by account id:" << query.lastError().text();
return folders;
}
while (query.next()) {
Folder fld;
fld.setId(query.value(0).toInt());
fld.setAccountId(query.value(1).toInt());
fld.setName(query.value(2).toString());
fld.setParentFolderId(query.value(3).toString());
fld.setInbox(query.value(4).toBool());
fld.setSent(query.value(5).toBool());
fld.setDrafts(query.value(6).toBool());
fld.setTrash(query.value(7).toBool());
fld.setUnreadCount(query.value(8).toInt());
fld.setLastSynced(query.value(9).toDateTime());
folders.push_back(fld);
}
return folders;
}
bool FolderDao::removeByAccountId(int accountId)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Folder WHERE accountId = :accountId");
query.bindValue(":accountId", accountId);
if (!query.exec()) {
qWarning() << "Failed to delete folders by account id:" << query.lastError().text();
return false;
}
return true;
}
+1 -1
View File
@@ -8,7 +8,7 @@
class FolderDao
{
public:
static bool insert(const Folder& folder);
static qint64 insert(const Folder& folder);
static bool update(const Folder& folder);
static bool remove(int id);
static bool removeByAccountId(int accountId);
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "../databasemanager.h"
#include "../../core/models/folder.h"
#include <QVector>
#include <optional>
class FolderDao
{
public:
static bool insert(const Folder& folder);
static bool update(const Folder& folder);
static bool remove(int id);
static bool removeByAccountId(int accountId);
static std::optional<Folder> findById(int id);
static QVector<Folder> findAll();
static QVector<Folder> findByAccountId(int accountId);
};
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "../databasemanager.h"
#include "../../core/models/folder.h"
#include <QVector>
#include <optional>
class FolderDao
{
public:
static qint64 insert(const Folder& folder);
static bool update(const Folder& folder);
static bool remove(int id);
static bool removeByAccountId(int accountId);
static std::optional<Folder> findById(int id);
static QVector<Folder> findAll();
static QVector<Folder> findByAccountId(int accountId);
};
+204 -20
View File
@@ -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;
}
+254
View File
@@ -0,0 +1,254 @@
#include <QSqlQuery>
#include <QSqlError>
#include "mailitemdao.h"
#include <optional>
bool MailItemDao::insert(const MailItem& item)
{
QSqlDatabase& db = DatabaseManager::instance().database();
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)"
);
query.bindValue(":folderId", item.folderId());
query.bindValue(":messageId", 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());
if (!query.exec()) {
qWarning() << "Failed to insert mail item:" << query.lastError().text();
return false;
}
return true;
}
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 "
"WHERE id = :id"
);
query.bindValue(":id", item.id());
query.bindValue(":folderId", item.folderId());
query.bindValue(":messageId", 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());
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<MailItem> 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 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());
return item;
}
return std::nullopt;
}
QVector<MailItem> MailItemDao::findAll()
{
QVector<MailItem> 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 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());
items.append(item);
}
return items;
}
QVector<MailItem> MailItemDao::findByFolderId(int folderId)
{
QVector<MailItem> 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 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());
items.append(item);
}
return items;
}
QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 sinceUid)
{
QVector<MailItem> 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 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());
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::uidsForFolder(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();
if (uid > 0) {
uids.append(uid);
}
}
return uids;
}
+285
View File
@@ -0,0 +1,285 @@
#include <QSqlQuery>
#include <QSqlError>
#include "mailitemdao.h"
#include <QDebug>
#include <optional>
bool MailItemDao::insert(MailItem& item)
{
QSqlDatabase& db = DatabaseManager::instance().database();
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());
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;
}
qInfo() << "Last insert id:" << query.lastInsertId();
item.setId(query.lastInsertId().toLongLong());
return true;
}
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 "
"WHERE id = :id"
);
query.bindValue(":id", item.id());
query.bindValue(":folderId", item.folderId());
query.bindValue(":messageId", 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());
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<MailItem> 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());
qDebug() << "Debug findById: toAddr=" << query.value(14).toString() << " ccAddr=" << query.value(15).toString() << " bccAddr=" << query.value(16).toString();
return item;
}
return std::nullopt;
}
QVector<MailItem> MailItemDao::findAll()
{
QVector<MailItem> 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());
items.append(item);
}
return items;
}
QVector<MailItem> MailItemDao::findByFolderId(int folderId)
{
QVector<MailItem> 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());
items.append(item);
}
return items;
}
QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 sinceUid)
{
QVector<MailItem> 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());
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;
}
+260
View File
@@ -0,0 +1,260 @@
#include <QSqlQuery>
#include <QSqlError>
#include "mailitemdao.h"
#include <optional>
bool MailItemDao::insert(const MailItem& item)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO MailCopy (folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml) "
"VALUES (:folderId, :messageId, :subject, :sender, :recipient, :date, :read, :flagged, :hasAttachment, :size, :fileId, :uid, :bodyHtml)"
);
query.bindValue(":folderId", item.folderId());
query.bindValue(":messageId", 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());
if (!query.exec()) {
qWarning() << "Failed to insert mail item:" << query.lastError().text();
return false;
}
return true;
}
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 "
"WHERE id = :id"
);
query.bindValue(":id", item.id());
query.bindValue(":folderId", item.folderId());
query.bindValue(":messageId", 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());
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<MailItem> 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 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());
return item;
}
return std::nullopt;
}
QVector<MailItem> MailItemDao::findAll()
{
QVector<MailItem> 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 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());
items.append(item);
}
return items;
}
QVector<MailItem> MailItemDao::findByFolderId(int folderId)
{
QVector<MailItem> 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 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());
items.append(item);
}
return items;
}
QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 sinceUid)
{
QVector<MailItem> 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 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());
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;
}
+263
View File
@@ -0,0 +1,263 @@
#include <QSqlQuery>
#include <QSqlError>
#include "mailitemdao.h"
#include <optional>
bool MailItemDao::insert(const MailItem& item)
{
QSqlDatabase& db = DatabaseManager::instance().database();
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());
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;
}
return true;
}
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 "
"WHERE id = :id"
);
query.bindValue(":id", item.id());
query.bindValue(":folderId", item.folderId());
query.bindValue(":messageId", 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());
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<MailItem> 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 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());
return item;
}
return std::nullopt;
}
QVector<MailItem> MailItemDao::findAll()
{
QVector<MailItem> 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 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());
items.append(item);
}
return items;
}
QVector<MailItem> MailItemDao::findByFolderId(int folderId)
{
QVector<MailItem> 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 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());
items.append(item);
}
return items;
}
QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 sinceUid)
{
QVector<MailItem> 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 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());
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;
}
+19 -2
View File
@@ -5,14 +5,31 @@
#include <QVector>
#include <optional>
struct StoredAttachmentRecord
{
QString fileName;
QString mimeType;
QString contentId;
qint64 size{0};
QString storedPath;
};
class MailItemDao
{
public:
static bool insert(const MailItem& item);
static bool insert(MailItem& item);
static bool upsert(MailItem& item);
static bool update(const MailItem& item);
static bool remove(qint64 id);
static std::optional<MailItem> findById(qint64 id);
static QVector<MailItem> findAll();
static QVector<MailItem> findByFolderId(int folderId);
static QVector<MailItem> findByFolderIdSinceUid(int folderId, qint64 sinceUid);
};
static std::optional<qint64> maxUidForFolder(int folderId);
static QVector<qint64> getUidsForFolder(int folderId);
static QVector<qint64> uidsForFolder(int folderId);
static std::optional<MailItem> findByUid(int folderId, qint64 uid);
static bool removeByUid(int folderId, qint64 uid);
static bool replaceAttachments(qint64 mailItemId, const QVector<StoredAttachmentRecord>& attachments);
static QVector<StoredAttachmentRecord> attachmentsForMail(qint64 mailItemId);
};
+59
View File
@@ -0,0 +1,59 @@
import sys
import re
filename = sys.argv[1]
with open(filename, 'r') as f:
content = f.read()
# Helper to replace SELECT clause and add mapping after setBodyHtml
def replace_select_and_add_mapping(content, func_name):
# Pattern to find the function (simplistic but works for our file)
# We'll replace the SELECT string inside the function.
# Since each function has a unique SELECT, we can replace directly.
if func_name == 'findById':
# Replace SELECT line
pattern = r'(query\.prepare\\(\s*"SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml FROM MailCopy WHERE id = :id"\s*\\);)'
replacement = '''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"
);'''
content = re.sub(pattern, replacement, content)
# Add mapping after setBodyHtml line
# Find line with item.setBodyHtml(query.value(13).toString());
pattern2 = r'(item\.setBodyHtml\(query\.value\(13\)\.toString\(\);\s*\n\s*)'
replacement2 = r'\1 item.setTo(query.value(14).toString());\n item.setCc(query.value(15).toString());\n item.setBcc(query.value(16).toString());\n'
content = re.sub(pattern2, replacement2, content)
elif func_name == 'findAll':
# SELECT line inside exec
pattern = r'(query\.exec\(\s*"SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml FROM MailCopy"\s*\)\s*)'
# We need to replace the whole argument; easier: replace the string inside.
# We'll replace the quoted string.
pattern = r'"SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml FROM MailCopy"'
replacement = '"SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy"'
content = re.sub(pattern, replacement, content)
# Add mapping after setBodyHtml line in the while loop
pattern2 = r'(item\.setBodyHtml\(query\.value\(13\)\.toString\(\);\s*\n\s*)'
replacement2 = r'\1 item.setTo(query.value(14).toString());\n item.setCc(query.value(15).toString());\n item.setBcc(query.value(16).toString());\n'
content = re.sub(pattern2, replacement2, content)
elif func_name == 'findByFolderId':
pattern = r'"SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml FROM MailCopy WHERE folderId = :folderId"'
replacement = '"SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy WHERE folderId = :folderId"'
content = re.sub(pattern, replacement, content)
pattern2 = r'(item\.setBodyHtml\(query\.value\(13\)\.toString\(\);\s*\n\s*)'
replacement2 = r'\1 item.setTo(query.value(14).toString());\n item.setCc(query.value(15).toString());\n item.setBcc(query.value(16).toString());\n'
content = re.sub(pattern2, replacement2, content)
elif func_name == 'findByFolderIdSinceUid':
pattern = r'"SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml FROM MailCopy WHERE folderId = :folderId AND uid > :sinceUid"'
replacement = '"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"'
content = re.sub(pattern, replacement, content)
pattern2 = r'(item\.setBodyHtml\(query\.value\(13\)\.toString\(\);\s*\n\s*)'
replacement2 = r'\1 item.setTo(query.value(14).toString());\n item.setCc(query.value(15).toString());\n item.setBcc(query.value(16).toString());\n'
content = re.sub(pattern2, replacement2, content)
return content
# Apply to each function
for func in ['findById', 'findAll', 'findByFolderId', 'findByFolderIdSinceUid']:
content = replace_select_and_add_mapping(content, func)
with open(filename, 'w') as f:
f.write(content)
print('Modified', filename)
+52 -4
View File
@@ -37,6 +37,11 @@ bool DatabaseManager::initialize(const QString& databasePath)
dbPath = dir.filePath("wino-mail.sqlite");
}
m_databasePath = dbPath;
m_ownerThread = QThread::currentThreadId();
qDebug() << "[DatabaseManager] Opening database at:" << dbPath;
m_db = QSqlDatabase::addDatabase("QSQLITE", "wino_mail_connection");
m_db.setDatabaseName(dbPath);
@@ -45,18 +50,22 @@ bool DatabaseManager::initialize(const QString& databasePath)
return false;
}
qDebug() << "[DatabaseManager] Database opened successfully";
QSqlQuery query(m_db);
// Enable foreign keys
qDebug() << "[DatabaseManager] Enabling foreign keys";
query.exec("PRAGMA foreign_keys = ON;");
// Create tables if they don't exist
qDebug() << "[DatabaseManager] Creating Account table";
// Account table
if (!query.exec(
"CREATE TABLE IF NOT EXISTS Account ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"email TEXT NOT NULL, "
"displayName TEXT, ""signature TEXT, "
"displayName TEXT, signature TEXT, "
"type INTEGER NOT NULL, " // 0=Outlook,1=Gmail,2=IMAP
"accessToken TEXT, "
"refreshToken TEXT, "
@@ -74,6 +83,7 @@ bool DatabaseManager::initialize(const QString& databasePath)
qWarning() << "Failed to create Account table:" << query.lastError().text();
return false;
}
qDebug() << "[DatabaseManager] Account table created successfully";
// Folder table
if (!query.exec(
@@ -110,6 +120,10 @@ bool DatabaseManager::initialize(const QString& databasePath)
"size INTEGER, "
"uid INTEGER, "
"fileId TEXT, " // references the .eml file in storage
"bodyHtml TEXT, "
"toAddr TEXT, "
"ccAddr TEXT, "
"bccAddr TEXT, "
"FOREIGN KEY(folderId) REFERENCES Folder(id) ON DELETE CASCADE"
");")) {
qWarning() << "Failed to create MailCopy table:" << query.lastError().text();
@@ -125,18 +139,52 @@ bool DatabaseManager::initialize(const QString& databasePath)
"mimeType TEXT, "
"size INTEGER, "
"contentId TEXT, "
"storedPath TEXT, "
"FOREIGN KEY(mailCopyId) REFERENCES MailCopy(id) ON DELETE CASCADE"
");")) {
qWarning() << "Failed to create Attachment table:" << query.lastError().text();
return false;
}
// Existing installations were created before storedPath was introduced.
// SQLite reports an error when the column already exists, which is safe to
// ignore here because the CREATE TABLE above covers new databases.
query.exec("ALTER TABLE Account ADD COLUMN signature TEXT");
query.exec("ALTER TABLE MailCopy ADD COLUMN toAddr TEXT");
query.exec("ALTER TABLE MailCopy ADD COLUMN ccAddr TEXT");
query.exec("ALTER TABLE MailCopy ADD COLUMN bccAddr TEXT");
query.exec("ALTER TABLE MailCopy ADD COLUMN bodyHtml TEXT");
query.exec("ALTER TABLE MailCopy ADD COLUMN uid INTEGER");
query.exec("ALTER TABLE MailCopy ADD COLUMN fileId TEXT");
query.exec("ALTER TABLE Attachment ADD COLUMN storedPath TEXT");
query.exec("CREATE INDEX IF NOT EXISTS idx_mailcopy_folder_uid ON MailCopy(folderId, uid)");
query.exec("CREATE INDEX IF NOT EXISTS idx_attachment_mailcopy ON Attachment(mailCopyId)");
m_initialized = true;
qDebug() << "Database initialized at:" << dbPath;
return true;
}
QSqlDatabase& DatabaseManager::database()
QSqlDatabase DatabaseManager::database()
{
return m_db;
}
if (QThread::currentThreadId() == m_ownerThread)
return m_db;
const QString connectionName = QStringLiteral("wino_mail_connection_%1")
.arg(reinterpret_cast<quintptr>(QThread::currentThreadId()), 0, 16);
if (QSqlDatabase::contains(connectionName))
return QSqlDatabase::database(connectionName);
QMutexLocker locker(&m_mutex);
if (QSqlDatabase::contains(connectionName))
return QSqlDatabase::database(connectionName);
QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), connectionName);
db.setDatabaseName(m_databasePath);
if (!db.open()) {
qWarning() << "Failed to open thread-local database:" << db.lastError().text();
return db;
}
QSqlQuery pragma(db);
pragma.exec(QStringLiteral("PRAGMA foreign_keys = ON"));
return db;
}
+142
View File
@@ -0,0 +1,142 @@
#include "databasemanager.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QStandardPaths>
#include <QDir>
DatabaseManager& DatabaseManager::instance()
{
static DatabaseManager instance;
return instance;
}
DatabaseManager::DatabaseManager() = default;
DatabaseManager::~DatabaseManager()
{
if (m_db.isOpen()) {
m_db.close();
}
}
bool DatabaseManager::initialize(const QString& databasePath)
{
QMutexLocker locker(&m_mutex);
if (m_initialized) {
return true;
}
QString dbPath = databasePath;
if (dbPath.isEmpty()) {
// Use application data location
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
if (!dir.exists()) {
dir.mkpath(".");
}
dbPath = dir.filePath("wino-mail.sqlite");
}
m_db = QSqlDatabase::addDatabase("QSQLITE", "wino_mail_connection");
m_db.setDatabaseName(dbPath);
if (!m_db.open()) {
qWarning() << "Failed to open database:" << m_db.lastError().text();
return false;
}
QSqlQuery query(m_db);
// Enable foreign keys
query.exec("PRAGMA foreign_keys = ON;");
// Create tables if they don't exist
// Account table
if (!query.exec(
"CREATE TABLE IF NOT EXISTS Account ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"email TEXT NOT NULL, "
"displayName TEXT, signature TEXT, "
"type INTEGER NOT NULL, " // 0=Outlook,1=Gmail,2=IMAP
"accessToken TEXT, "
"refreshToken TEXT, "
"tokenExpires DATETIME, "
"incomingHost TEXT, "
"incomingPort INTEGER, "
"incomingSsl INTEGER, "
"outgoingHost TEXT, "
"outgoingPort INTEGER, "
"outgoingSsl INTEGER, "
"connUsername TEXT, "
"connPassword TEXT, "
"authMethod TEXT"
");")) {
qWarning() << "Failed to create Account table:" << query.lastError().text();
return false;
}
// Folder table
if (!query.exec(
"CREATE TABLE IF NOT EXISTS Folder ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"accountId INTEGER NOT NULL, "
"name TEXT NOT NULL, "
"parentFolderId TEXT, "
"isInbox BOOLEAN DEFAULT 0, "
"isSent BOOLEAN DEFAULT 0, "
"isDrafts BOOLEAN DEFAULT 0, "
"isTrash BOOLEAN DEFAULT 0, "
"unreadCount INTEGER DEFAULT 0, "
"lastSynced DATETIME, "
"FOREIGN KEY(accountId) REFERENCES Account(id) ON DELETE CASCADE"
");")) {
qWarning() << "Failed to create Folder table:" << query.lastError().text();
return false;
}
// MailCopy table (simplified)
if (!query.exec(
"CREATE TABLE IF NOT EXISTS MailCopy ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"folderId INTEGER NOT NULL, "
"messageId TEXT UNIQUE, "
"subject TEXT, "
"sender TEXT, "
"recipient TEXT, "
"date DATETIME, "
"read BOOLEAN DEFAULT 0, "
"flagged BOOLEAN DEFAULT 0, "
"hasAttachment BOOLEAN DEFAULT 0, "
"size INTEGER, "
"uid INTEGER, "
"fileId TEXT, " // references the .eml file in storage
"FOREIGN KEY(folderId) REFERENCES Folder(id) ON DELETE CASCADE"
");")) {
qWarning() << "Failed to create MailCopy table:" << query.lastError().text();
return false;
}
// Attachments table (optional)
if (!query.exec(
"CREATE TABLE IF NOT EXISTS Attachment ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"mailCopyId INTEGER NOT NULL, "
"filename TEXT, "
"mimeType TEXT, "
"size INTEGER, "
"contentId TEXT, "
"FOREIGN KEY(mailCopyId) REFERENCES MailCopy(id) ON DELETE CASCADE"
");")) {
qWarning() << "Failed to create Attachment table:" << query.lastError().text();
return false;
}
m_initialized = true;
qDebug() << "Database initialized at:" << dbPath;
return true;
}
QSqlDatabase& DatabaseManager::database()
{
return m_db;
}
+8 -3
View File
@@ -3,6 +3,7 @@
#include <QSqlDatabase>
#include <QString>
#include <QMutex>
#include <QThread>
class DatabaseManager
{
@@ -10,8 +11,10 @@ public:
static DatabaseManager& instance();
~DatabaseManager();
bool initialize(const QString& databasePath = QStringLiteral("wino-mail.sqlite"));
QSqlDatabase& database();
bool initialize(const QString& databasePath = QString());
// Returns a connection owned by the calling thread. SQLite/Qt database
// connections must never be shared across threads.
QSqlDatabase database();
private:
DatabaseManager();
@@ -19,5 +22,7 @@ private:
QSqlDatabase m_db;
QMutex m_mutex;
QString m_databasePath;
Qt::HANDLE m_ownerThread{nullptr};
bool m_initialized{false};
};
};
+2 -1
View File
@@ -123,7 +123,8 @@ void DbChangeProcessor::processBatch()
if (!m_mailItemAddedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_mailItemAddedQueue.size() << "MailItemAdded events";
for (const auto& event : m_mailItemAddedQueue) {
MailItemDao::insert(event.item);
MailItem item = event.item;
MailItemDao::insert(item);
}
m_mailItemAddedQueue.clear();
}
+219
View File
@@ -0,0 +1,219 @@
#include "dbchangeprocessor.h"
#include <QDebug>
#include <QDateTime>
#include "../db/dao/mailitemdao.h"
#include "../db/dao/folderdao.h"
#include "../db/dao/accountdao.h"
DbChangeProcessor::DbChangeProcessor(QObject* parent)
: QObject(parent),
m_batchTimer(new QTimer(this))
{
// Configurar el timer para procesar batch cada 5 segundos
m_batchTimer->setInterval(5000); // 5 segundos
connect(m_batchTimer, &QTimer::timeout, this, &DbChangeProcessor::processBatch);
m_batchTimer->start();
// Suscribirse a todos los eventos relevantes
SUBSCRIBE(WinoMail::Events::MailItemAddedEvent,
[this](const WinoMail::Events::MailItemAddedEvent& event) {
handleMailItemAdded(event);
});
SUBSCRIBE(WinoMail::Events::MailItemRemovedEvent,
[this](const WinoMail::Events::MailItemRemovedEvent& event) {
handleMailItemRemoved(event);
});
SUBSCRIBE(WinoMail::Events::MailItemUpdatedEvent,
[this](const WinoMail::Events::MailItemUpdatedEvent& event) {
handleMailItemUpdated(event);
});
SUBSCRIBE(WinoMail::Events::FolderAddedEvent,
[this](const WinoMail::Events::FolderAddedEvent& event) {
handleFolderAdded(event);
});
SUBSCRIBE(WinoMail::Events::FolderRemovedEvent,
[this](const WinoMail::Events::FolderRemovedEvent& event) {
handleFolderRemoved(event);
});
SUBSCRIBE(WinoMail::Events::FolderUpdatedEvent,
[this](const WinoMail::Events::FolderUpdatedEvent& event) {
handleFolderUpdated(event);
});
SUBSCRIBE(WinoMail::Events::AccountAddedEvent,
[this](const WinoMail::Events::AccountAddedEvent& event) {
handleAccountAdded(event);
});
SUBSCRIBE(WinoMail::Events::AccountRemovedEvent,
[this](const WinoMail::Events::AccountRemovedEvent& event) {
handleAccountRemoved(event);
});
SUBSCRIBE(WinoMail::Events::AccountUpdatedEvent,
[this](const WinoMail::Events::AccountUpdatedEvent& event) {
handleAccountUpdated(event);
});
}
void DbChangeProcessor::handleMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing MailItemAddedEvent for ID:" << event.item.id();
m_mailItemAddedQueue.append(event);
}
void DbChangeProcessor::handleMailItemRemoved(const WinoMail::Events::MailItemRemovedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing MailItemRemovedEvent for UID:" << event.itemUid;
m_mailItemRemovedQueue.append(event);
}
void DbChangeProcessor::handleMailItemUpdated(const WinoMail::Events::MailItemUpdatedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing MailItemUpdatedEvent for ID:" << event.item.id();
m_mailItemUpdatedQueue.append(event);
}
void DbChangeProcessor::handleFolderAdded(const WinoMail::Events::FolderAddedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing FolderAddedEvent for folder:" << event.folder.name();
m_folderAddedQueue.append(event);
}
void DbChangeProcessor::handleFolderRemoved(const WinoMail::Events::FolderRemovedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing FolderRemovedEvent for folder ID:" << event.folderId;
m_folderRemovedQueue.append(event);
}
void DbChangeProcessor::handleFolderUpdated(const WinoMail::Events::FolderUpdatedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing FolderUpdatedEvent for folder ID:" << event.folder.id();
m_folderUpdatedQueue.append(event);
}
void DbChangeProcessor::handleAccountAdded(const WinoMail::Events::AccountAddedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing AccountAddedEvent for account:" << event.account.email();
m_accountAddedQueue.append(event);
}
void DbChangeProcessor::handleAccountRemoved(const WinoMail::Events::AccountRemovedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing AccountRemovedEvent for account ID:" << event.accountId;
m_accountRemovedQueue.append(event);
}
void DbChangeProcessor::handleAccountUpdated(const WinoMail::Events::AccountUpdatedEvent& event)
{
qDebug() << "DbChangeProcessor: Queuing AccountUpdatedEvent for account ID:" << event.account.id();
m_accountUpdatedQueue.append(event);
}
void DbChangeProcessor::processBatch()
{
qDebug() << "DbChangeProcessor: Starting batch processing at" << QDateTime::currentDateTime().toString();
// Procesar eventos de MailItem añadidos
if (!m_mailItemAddedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_mailItemAddedQueue.size() << "MailItemAdded events";
for (auto for (autoconst auto& event event : m_mailItemAddedQueue) { event : m_mailItemAddedQueue) {
MailItemDao::insert(event.item);
}
m_mailItemAddedQueue.clear();
}
// Procesar eventos de MailItem eliminados
if (!m_mailItemRemovedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_mailItemRemovedQueue.size() << "MailItemRemoved events";
for (const auto& event : m_mailItemRemovedQueue) {
// We don't have removeByUid in MailItemDao, so we skip and log a warning.
// In a real implementation, we would need to find the item by uid and folderId to get its id.
qWarning() << "DbChangeProcessor: MailItemRemoved event processed but removeByUid not implemented in MailItemDao. Skipping removal for UID:" << event.itemUid;
}
m_mailItemRemovedQueue.clear();
}
// Procesar eventos de MailItem actualizados
if (!m_mailItemUpdatedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_mailItemUpdatedQueue.size() << "MailItemUpdated events";
for (const auto& event : m_mailItemUpdatedQueue) {
// We ignore changedFields and update the whole item for simplicity.
MailItemDao::update(event.item);
}
m_mailItemUpdatedQueue.clear();
}
// Procesar eventos de carpetas añadidas
if (!m_folderAddedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_folderAddedQueue.size() << "FolderAdded events";
for (const auto& event : m_folderAddedQueue) {
FolderDao::insert(event.folder);
}
m_folderAddedQueue.clear();
}
// Procesar eventos de carpetas eliminadas
if (!m_folderRemovedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_folderRemovedQueue.size() << "FolderRemoved events";
for (const auto& event : m_folderRemovedQueue) {
// We don't have a way to get the folder id from the event without a query, so we skip and log a warning.
qWarning() << "DbChangeProcessor: FolderRemoved event processed but we lack the folder id to remove. Skipping removal for folder ID:" << event.folderId;
}
m_folderRemovedQueue.clear();
}
// Procesar eventos de carpetas actualizadas
if (!m_folderUpdatedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_folderUpdatedQueue.size() << "FolderUpdated events";
for (const auto& event : m_folderUpdatedQueue) {
// We ignore changedFields and update the whole folder for simplicity.
FolderDao::update(event.folder);
}
m_folderUpdatedQueue.clear();
}
// Procesar eventos de cuentas añadidas
if (!m_accountAddedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_accountAddedQueue.size() << "AccountAdded events";
for (const auto& event : m_accountAddedQueue) {
AccountDao::insert(event.account);
}
m_accountAddedQueue.clear();
}
// Procesar eventos de cuentas eliminadas
if (!m_accountRemovedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_accountRemovedQueue.size() << "AccountRemoved events";
for (const auto& event : m_accountRemovedQueue) {
AccountDao::remove(event.accountId);
}
m_accountRemovedQueue.clear();
}
// Procesar eventos de cuentas actualizadas
if (!m_accountUpdatedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_accountUpdatedQueue.size() << "AccountUpdated events";
for (const auto& event : m_accountUpdatedQueue) {
// We ignore changedFields and update the whole account for simplicity.
AccountDao::update(event.account);
}
m_accountUpdatedQueue.clear();
}
qDebug() << "DbChangeProcessor: Batch processing completed at" << QDateTime::currentDateTime().toString();
}
DbChangeProcessor::~DbChangeProcessor()
{
if (m_batchTimer->isActive()) {
m_batchTimer->stop();
}
// Procesar cualquier evento restante antes de destruir
processBatch();
}