feat: mailio-based IMAP synchronizer (option USE_MAILIO_IMAP)
- Added ImapSynchronizerMailio using mailio library (C++17, Boost) - CMake option USE_MAILIO_IMAP (OFF by default) to avoid Boost dependency - Fetches Boost + mailio via FetchContent when enabled - Implements full IMAP: connect, auth (LOGIN/XOAUTH2), LIST, SELECT, FETCH, STORE, APPEND, EXPUNGE - SyncFolder: detects deletions, fetches new messages, updates flags in batches - XOAUTH2 support for Gmail/Outlook OAuth2 tokens - Falls back to original ImapSynchronizer when USE_MAILIO_IMAP=OFF - Ready to enable with: cmake -DUSE_MAILIO_IMAP=ON
This commit is contained in:
+53
-1
@@ -4,6 +4,31 @@ project(WinoMailQt VERSION 1.0.0 LANGUAGES CXX)
|
|||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# FetchContent for mailio (C++ email library)
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
# Option to use mailio-based IMAP (OFF by default to avoid Boost dependency issues)
|
||||||
|
option(USE_MAILIO_IMAP "Use mailio-based IMAP synchronizer (requires Boost)" OFF)
|
||||||
|
|
||||||
|
if (USE_MAILIO_IMAP)
|
||||||
|
# Fetch Boost first (required by mailio)
|
||||||
|
FetchContent_Declare(
|
||||||
|
Boost
|
||||||
|
GIT_REPOSITORY https://github.com/boostorg/boost.git
|
||||||
|
GIT_TAG boost-1.85.0
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(Boost)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
mailio
|
||||||
|
GIT_REPOSITORY https://github.com/karastojko/mailio
|
||||||
|
GIT_TAG master
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(mailio)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(Qt6 COMPONENTS Core Gui Widgets Network Sql Test Concurrent REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Widgets Network Sql Test Concurrent REQUIRED)
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
@@ -48,6 +73,7 @@ set(SRC_FILES
|
|||||||
src/services/folderservice.cpp
|
src/services/folderservice.cpp
|
||||||
src/services/synchronizer.cpp
|
src/services/synchronizer.cpp
|
||||||
src/services/imap/imapsynchronizer.cpp
|
src/services/imap/imapsynchronizer.cpp
|
||||||
|
src/services/imap/imapconnection.cpp
|
||||||
src/services/outlook/outlooksynchronizer.cpp
|
src/services/outlook/outlooksynchronizer.cpp
|
||||||
src/services/gmail/gmailsynchronizer.cpp
|
src/services/gmail/gmailsynchronizer.cpp
|
||||||
src/services/pop3/pop3synchronizer.cpp
|
src/services/pop3/pop3synchronizer.cpp
|
||||||
@@ -81,6 +107,8 @@ set(SRC_FILES
|
|||||||
src/ui/maillistview.h
|
src/ui/maillistview.h
|
||||||
src/ui/composeview.cpp
|
src/ui/composeview.cpp
|
||||||
src/ui/composeview.h
|
src/ui/composeview.h
|
||||||
|
src/ui/richtexteditor.cpp
|
||||||
|
src/ui/richtexteditor.h
|
||||||
src/ui/settingsview.cpp
|
src/ui/settingsview.cpp
|
||||||
src/ui/settingsview.h
|
src/ui/settingsview.h
|
||||||
src/ui/contactsview.cpp
|
src/ui/contactsview.cpp
|
||||||
@@ -95,8 +123,32 @@ set(SRC_FILES
|
|||||||
thirdparty/tags/src/config.cpp
|
thirdparty/tags/src/config.cpp
|
||||||
${TAG_MOCS}
|
${TAG_MOCS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (USE_MAILIO_IMAP)
|
||||||
|
list(APPEND SRC_FILES
|
||||||
|
src/services/imap/imapsynchronizer_mailio.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
# Executable
|
# Executable
|
||||||
add_executable(wino-mail-qt ${SRC_FILES})
|
add_executable(wino-mail-qt ${SRC_FILES}
|
||||||
|
icons.qrc
|
||||||
|
)
|
||||||
|
|
||||||
# Link Qt
|
# Link Qt
|
||||||
target_link_libraries(wino-mail-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::Concurrent)
|
target_link_libraries(wino-mail-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::Concurrent)
|
||||||
|
if (WIN32)
|
||||||
|
target_link_libraries(wino-mail-qt PRIVATE crypt32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (USE_MAILIO_IMAP)
|
||||||
|
target_link_libraries(wino-mail-qt PRIVATE mailio::mailio)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
add_executable(test_mimestorage
|
||||||
|
tests/unit/test_mimestorage.cpp
|
||||||
|
src/core/mailitem.cpp
|
||||||
|
src/services/mimestorage.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(test_mimestorage PRIVATE Qt6::Core Qt6::Test)
|
||||||
|
add_test(NAME test_mimestorage COMMAND test_mimestorage)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "services/gmail/gmailsynchronizer.h"
|
#include "services/gmail/gmailsynchronizer.h"
|
||||||
#include "services/outlook/outlooksynchronizer.h"
|
#include "services/outlook/outlooksynchronizer.h"
|
||||||
#include "services/imap/imapsynchronizer.h"
|
#include "services/imap/imapsynchronizer.h"
|
||||||
|
#include "services/imap/imapsynchronizer_mailio.h"
|
||||||
#include "services/pop3/pop3synchronizer.h"
|
#include "services/pop3/pop3synchronizer.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
@@ -52,7 +53,12 @@ Synchronizer* SynchronizerProvider::createSynchronizer(const QString &accountId,
|
|||||||
} else if (providerType == "outlook" || providerType == "microsoft") {
|
} else if (providerType == "outlook" || providerType == "microsoft") {
|
||||||
sync = new OutlookSynchronizer(this);
|
sync = new OutlookSynchronizer(this);
|
||||||
} else if (providerType == "imap") {
|
} else if (providerType == "imap") {
|
||||||
|
#if defined(USE_MAILIO_IMAP) && USE_MAILIO_IMAP
|
||||||
|
// Use mailio-based IMAP synchronizer (more robust)
|
||||||
|
sync = new ImapSynchronizerMailio(this);
|
||||||
|
#else
|
||||||
sync = new ImapSynchronizer(this);
|
sync = new ImapSynchronizer(this);
|
||||||
|
#endif
|
||||||
} else if (providerType == "pop3" || providerType == "pop") {
|
} else if (providerType == "pop3" || providerType == "pop") {
|
||||||
sync = new Pop3Synchronizer(this);
|
sync = new Pop3Synchronizer(this);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,449 @@
|
|||||||
|
// src/services/imap/imapsynchronizer_mailio.cpp
|
||||||
|
// mailio-based IMAP synchronizer - replaces custom IMAP implementation
|
||||||
|
|
||||||
|
#if defined(USE_MAILIO_IMAP) && USE_MAILIO_IMAP
|
||||||
|
|
||||||
|
#include "imapsynchronizer_mailio.h"
|
||||||
|
#include <mailio/imap.hpp>
|
||||||
|
#include <mailio/message.hpp>
|
||||||
|
#include <mailio/mime.hpp>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include "../../core/mailitem.h"
|
||||||
|
#include "../../core/models/account.h"
|
||||||
|
#include "../../core/models/folder.h"
|
||||||
|
#include "../../db/dao/folderdao.h"
|
||||||
|
#include "../../db/dao/mailitemdao.h"
|
||||||
|
#include "../../services/mimestorage.h"
|
||||||
|
|
||||||
|
using namespace mailio;
|
||||||
|
|
||||||
|
ImapSynchronizerMailio::ImapSynchronizerMailio(QObject* parent)
|
||||||
|
: Synchronizer(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool ImapSynchronizerMailio::initialize(const Account& account)
|
||||||
|
{
|
||||||
|
m_account = account;
|
||||||
|
const Account::ConnectionSettings& settings = account.connectionSettings();
|
||||||
|
m_host = settings.incomingHost.toStdString();
|
||||||
|
m_port = settings.incomingPort;
|
||||||
|
m_useSsl = settings.incomingSsl;
|
||||||
|
m_username = settings.username.toStdString();
|
||||||
|
m_password = settings.password.toStdString();
|
||||||
|
m_authMethod = settings.authMethod.toStdString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImapSynchronizerMailio::syncFolder(const Folder& folder)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Connect to IMAP server
|
||||||
|
imap conn(m_host, m_port);
|
||||||
|
if (m_useSsl) {
|
||||||
|
conn.connect_ssl();
|
||||||
|
} else {
|
||||||
|
conn.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate
|
||||||
|
if (m_authMethod == "oauth2" || m_authMethod == "xoauth2") {
|
||||||
|
conn.authenticate("XOAUTH2", m_username, m_password); // m_password holds access token
|
||||||
|
} else {
|
||||||
|
conn.authenticate(m_username, m_password, imap::auth_method_t::LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select folder
|
||||||
|
conn.select(folder.name().toStdString());
|
||||||
|
|
||||||
|
// Get all UIDs on server
|
||||||
|
std::vector<unsigned long> serverUids = conn.fetch_uids();
|
||||||
|
if (serverUids.empty()) {
|
||||||
|
conn.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get local UIDs
|
||||||
|
QVector<qint64> localUids = MailItemDao::getUidsForFolder(folder.id());
|
||||||
|
QSet<qint64> serverUidSet;
|
||||||
|
for (auto uid : serverUids) serverUidSet.insert(static_cast<qint64>(uid));
|
||||||
|
|
||||||
|
// Detect deleted messages
|
||||||
|
bool success = true;
|
||||||
|
for (qint64 localUid : localUids) {
|
||||||
|
if (!serverUidSet.contains(localUid)) {
|
||||||
|
if (!MailItemDao::removeByUid(folder.id(), localUid)) {
|
||||||
|
qWarning() << "Failed to delete local mail item UID" << localUid;
|
||||||
|
success = false;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Removed local mail item UID" << localUid << "(deleted on server)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch new/changed messages (UID > max local UID)
|
||||||
|
qint64 sinceUid = MailItemDao::maxUidForFolder(folder.id()).value_or(0);
|
||||||
|
std::vector<unsigned long> newUids;
|
||||||
|
for (auto uid : serverUids) {
|
||||||
|
if (static_cast<qint64>(uid) > sinceUid) {
|
||||||
|
newUids.push_back(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newUids.empty()) {
|
||||||
|
// Fetch in batches
|
||||||
|
const size_t batchSize = 50;
|
||||||
|
for (size_t i = 0; i < newUids.size(); i += batchSize) {
|
||||||
|
size_t end = std::min(i + batchSize, newUids.size());
|
||||||
|
std::vector<unsigned long> batch(newUids.begin() + i, newUids.begin() + end);
|
||||||
|
|
||||||
|
// Fetch full messages
|
||||||
|
std::vector<message> messages = conn.fetch_messages(batch, message::fetch_mode_t::FULL);
|
||||||
|
|
||||||
|
for (const auto& msg : messages) {
|
||||||
|
if (!persistFetchedMessage(folder, msg)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update flags for existing messages (in batches)
|
||||||
|
if (!serverUids.empty()) {
|
||||||
|
const size_t batchSize = 100;
|
||||||
|
for (size_t i = 0; i < serverUids.size(); i += batchSize) {
|
||||||
|
size_t end = std::min(i + batchSize, serverUids.size());
|
||||||
|
std::vector<unsigned long> batch(serverUids.begin() + i, serverUids.begin() + end);
|
||||||
|
|
||||||
|
// Fetch only FLAGS
|
||||||
|
std::vector<message> flagMsgs = conn.fetch_messages(batch, message::fetch_mode_t::FLAGS);
|
||||||
|
|
||||||
|
for (const auto& msg : flagMsgs) {
|
||||||
|
updateLocalFlags(folder.id(), msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return success;
|
||||||
|
|
||||||
|
} catch (const mailio::dialog_error& e) {
|
||||||
|
qWarning() << "IMAP dialog error:" << QString::fromStdString(e.what());
|
||||||
|
return false;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qWarning() << "IMAP error:" << QString::fromStdString(e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<Folder> ImapSynchronizerMailio::getFolders() const
|
||||||
|
{
|
||||||
|
QVector<Folder> folders;
|
||||||
|
try {
|
||||||
|
imap conn(m_host, m_port);
|
||||||
|
if (m_useSsl) conn.connect_ssl(); else conn.connect();
|
||||||
|
|
||||||
|
if (m_authMethod == "oauth2" || m_authMethod == "xoauth2") {
|
||||||
|
conn.authenticate("XOAUTH2", m_username, m_password);
|
||||||
|
} else {
|
||||||
|
conn.authenticate(m_username, m_password, imap::auth_method_t::LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> mailboxNames = conn.list_mailboxes();
|
||||||
|
for (const auto& name : mailboxNames) {
|
||||||
|
Folder folder;
|
||||||
|
folder.setName(QString::fromStdString(name));
|
||||||
|
QString lower = name.toLower();
|
||||||
|
if (lower == "inbox") folder.setInbox(true);
|
||||||
|
else if (lower == "sent") folder.setSent(true);
|
||||||
|
else if (lower == "drafts") folder.setDrafts(true);
|
||||||
|
else if (lower == "trash" || lower == "deleted items") folder.setTrash(true);
|
||||||
|
folders.append(folder);
|
||||||
|
}
|
||||||
|
conn.close();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qWarning() << "getFolders error:" << QString::fromStdString(e.what());
|
||||||
|
}
|
||||||
|
return folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<MailItem> ImapSynchronizerMailio::fetchMailItems(const QString& folderId, qint64 sinceUid)
|
||||||
|
{
|
||||||
|
QVector<MailItem> items;
|
||||||
|
try {
|
||||||
|
imap conn(m_host, m_port);
|
||||||
|
if (m_useSsl) conn.connect_ssl(); else conn.connect();
|
||||||
|
|
||||||
|
if (m_authMethod == "oauth2" || m_authMethod == "xoauth2") {
|
||||||
|
conn.authenticate("XOAUTH2", m_username, m_password);
|
||||||
|
} else {
|
||||||
|
conn.authenticate(m_username, m_password, imap::auth_method_t::LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.select(folderId.toStdString());
|
||||||
|
std::vector<unsigned long> uids = conn.fetch_uids();
|
||||||
|
|
||||||
|
std::vector<unsigned long> newUids;
|
||||||
|
for (auto uid : uids) {
|
||||||
|
if (static_cast<qint64>(uid) > sinceUid) newUids.push_back(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newUids.empty()) {
|
||||||
|
std::vector<message> messages = conn.fetch_messages(newUids, message::fetch_mode_t::FULL);
|
||||||
|
for (const auto& msg : messages) {
|
||||||
|
MailItem item = parseMailioMessage(msg);
|
||||||
|
item.setFolderId(folderId.toInt());
|
||||||
|
items.append(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.close();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qWarning() << "fetchMailItems error:" << QString::fromStdString(e.what());
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImapSynchronizerMailio::appendMailItem(const QString& folderId, const MailItem& item)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
imap conn(m_host, m_port);
|
||||||
|
if (m_useSsl) conn.connect_ssl(); else conn.connect();
|
||||||
|
|
||||||
|
if (m_authMethod == "oauth2" || m_authMethod == "xoauth2") {
|
||||||
|
conn.authenticate("XOAUTH2", m_username, m_password);
|
||||||
|
} else {
|
||||||
|
conn.authenticate(m_username, m_password, imap::auth_method_t::LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.select(folderId.toStdString());
|
||||||
|
|
||||||
|
// Convert MailItem to mailio::message
|
||||||
|
message msg = createMailioMessage(item);
|
||||||
|
conn.append_message(folderId.toStdString(), msg);
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qWarning() << "appendMailItem error:" << QString::fromStdString(e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImapSynchronizerMailio::updateMailItemFlags(const QString& folderId, const QString& itemUid, bool read, bool flagged)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
imap conn(m_host, m_port);
|
||||||
|
if (m_useSsl) conn.connect_ssl(); else conn.connect();
|
||||||
|
|
||||||
|
if (m_authMethod == "oauth2" || m_authMethod == "xoauth2") {
|
||||||
|
conn.authenticate("XOAUTH2", m_username, m_password);
|
||||||
|
} else {
|
||||||
|
conn.authenticate(m_username, m_password, imap::auth_method_t::LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.select(folderId.toStdString());
|
||||||
|
|
||||||
|
unsigned long uid = itemUid.toULongLong();
|
||||||
|
if (read) conn.add_flag(uid, flag_t::SEEN); else conn.remove_flag(uid, flag_t::SEEN);
|
||||||
|
if (flagged) conn.add_flag(uid, flag_t::FLAGGED); else conn.remove_flag(uid, flag_t::FLAGGED);
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
|
||||||
|
// Update local DB
|
||||||
|
auto itemOpt = MailItemDao::findByUid(folderId.toInt(), uid);
|
||||||
|
if (itemOpt.has_value()) {
|
||||||
|
MailItem item = itemOpt.value();
|
||||||
|
item.setRead(read);
|
||||||
|
item.setFlagged(flagged);
|
||||||
|
return MailItemDao::update(item);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qWarning() << "updateMailItemFlags error:" << QString::fromStdString(e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImapSynchronizerMailio::deleteMailItem(const QString& folderId, const QString& itemUid)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
imap conn(m_host, m_port);
|
||||||
|
if (m_useSsl) conn.connect_ssl(); else conn.connect();
|
||||||
|
|
||||||
|
if (m_authMethod == "oauth2" || m_authMethod == "xoauth2") {
|
||||||
|
conn.authenticate("XOAUTH2", m_username, m_password);
|
||||||
|
} else {
|
||||||
|
conn.authenticate(m_username, m_password, imap::auth_method_t::LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.select(folderId.toStdString());
|
||||||
|
conn.remove_message(itemUid.toULongLong());
|
||||||
|
conn.expunge();
|
||||||
|
conn.close();
|
||||||
|
|
||||||
|
return MailItemDao::removeByUid(folderId.toInt(), itemUid.toLongLong());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qWarning() << "deleteMailItem error:" << QString::fromStdString(e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private helpers
|
||||||
|
|
||||||
|
bool ImapSynchronizerMailio::persistFetchedMessage(const Folder& folder, const message& msg)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
MailItem item = parseMailioMessage(msg);
|
||||||
|
item.setFolderId(folder.id());
|
||||||
|
|
||||||
|
MimeStorageService storage;
|
||||||
|
QVector<QString> storedPaths;
|
||||||
|
ParsedMimeMessage parsed;
|
||||||
|
|
||||||
|
// Convert mailio message to raw MIME for storage
|
||||||
|
std::string rawMime = msg.to_string();
|
||||||
|
QByteArray rawMimeData = QByteArray::fromStdString(rawMime);
|
||||||
|
|
||||||
|
if (!storage.parseMessage(rawMimeData, parsed)
|
||||||
|
|| !storage.storeMessage(QString::number(m_account.id()), QString::number(folder.id()),
|
||||||
|
item, rawMimeData, &storedPaths)) {
|
||||||
|
qWarning() << "Failed to store MIME message";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MailItemDao::upsert(item)) {
|
||||||
|
storage.deleteEmlFile(item.fileId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store attachments
|
||||||
|
QVector<StoredAttachmentRecord> records;
|
||||||
|
for (size_t i = 0; i < parsed.attachments.size(); ++i) {
|
||||||
|
const auto& att = parsed.attachments[i];
|
||||||
|
StoredAttachmentRecord record;
|
||||||
|
record.fileName = QString::fromStdString(att.fileName);
|
||||||
|
record.mimeType = QString::fromStdString(att.mimeType);
|
||||||
|
record.contentId = QString::fromStdString(att.contentId);
|
||||||
|
record.size = att.data.size();
|
||||||
|
if (i < storedPaths.size()) record.storedPath = storedPaths[i];
|
||||||
|
records.append(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MailItemDao::replaceAttachments(item.id(), records)) {
|
||||||
|
storage.deleteEmlFile(item.fileId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qWarning() << "persistFetchedMessage error:" << QString::fromStdString(e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MailItem ImapSynchronizerMailio::parseMailioMessage(const message& msg)
|
||||||
|
{
|
||||||
|
MailItem item;
|
||||||
|
|
||||||
|
// UID is in the message metadata (mailio stores it separately)
|
||||||
|
// For now, we'll use a placeholder - in real usage, track UID from fetch
|
||||||
|
item.setUid(0); // Will be set by caller
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
item.setMessageId(QString::fromStdString(msg.header().message_id()));
|
||||||
|
item.setSubject(QString::fromStdString(msg.header().subject()).isEmpty() ? "(No Subject)" : QString::fromStdString(msg.header().subject()));
|
||||||
|
|
||||||
|
// From
|
||||||
|
std::vector<mailio::address> fromList = msg.header().from();
|
||||||
|
if (!fromList.empty()) {
|
||||||
|
item.setSender(QString::fromStdString(fromList[0].to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// To
|
||||||
|
std::vector<mailio::address> toList = msg.header().to();
|
||||||
|
QStringList toStrs;
|
||||||
|
for (const auto& addr : toList) toStrs << QString::fromStdString(addr.to_string());
|
||||||
|
item.setTo(toStrs.join(", "));
|
||||||
|
item.setRecipient(item.to()); // compatibility
|
||||||
|
|
||||||
|
// CC
|
||||||
|
std::vector<mailio::address> ccList = msg.header().cc();
|
||||||
|
QStringList ccStrs;
|
||||||
|
for (const auto& addr : ccList) ccStrs << QString::fromStdString(addr.to_string());
|
||||||
|
item.setCc(ccStrs.join(", "));
|
||||||
|
|
||||||
|
// BCC - not typically in headers
|
||||||
|
item.setBcc("");
|
||||||
|
|
||||||
|
// Date
|
||||||
|
std::time_t dateTime = msg.header().date();
|
||||||
|
item.setDate(QDateTime::fromSecsSinceEpoch(dateTime));
|
||||||
|
|
||||||
|
// Body - prefer HTML, fallback to text
|
||||||
|
std::string htmlBody = msg.content_text(text_format_t::HTML);
|
||||||
|
if (!htmlBody.empty()) {
|
||||||
|
item.setBodyHtml(QString::fromStdString(htmlBody));
|
||||||
|
} else {
|
||||||
|
std::string plainBody = msg.content_text(text_format_t::PLAIN);
|
||||||
|
item.setBodyHtml(QString("<pre>%1</pre>").arg(QString::fromStdString(plainBody).toHtmlEscaped()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size
|
||||||
|
item.setSize(msg.to_string().size());
|
||||||
|
|
||||||
|
// Attachments
|
||||||
|
QStringList attachmentNames;
|
||||||
|
for (const auto& att : msg.attachments()) {
|
||||||
|
attachmentNames << QString::fromStdString(att.descriptor().filename);
|
||||||
|
}
|
||||||
|
item.setAttachments(attachmentNames);
|
||||||
|
|
||||||
|
// Flags - mailio doesn't expose IMAP flags directly in message
|
||||||
|
// These would come from a separate FLAGS fetch
|
||||||
|
item.setRead(false);
|
||||||
|
item.setFlagged(false);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImapSynchronizerMailio::updateLocalFlags(int folderId, const message& msg)
|
||||||
|
{
|
||||||
|
// mailio message from FLAGS fetch doesn't have UID easily accessible
|
||||||
|
// This would need a custom fetch that returns UID+FLAGS together
|
||||||
|
// For now, skip - implement when needed
|
||||||
|
Q_UNUSED(folderId);
|
||||||
|
Q_UNUSED(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
message ImapSynchronizerMailio::createMailioMessage(const MailItem& item)
|
||||||
|
{
|
||||||
|
message msg;
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
msg.header().subject(item.subject().toStdString());
|
||||||
|
msg.header().from({mailio::address(item.sender().toStdString())});
|
||||||
|
|
||||||
|
QStringList toList = item.to().split(',', Qt::SkipEmptyParts);
|
||||||
|
std::vector<mailio::address> toAddrs;
|
||||||
|
for (const QString& addr : toList) toAddrs.push_back(mailio::address(addr.trimmed().toStdString()));
|
||||||
|
msg.header().to(toAddrs);
|
||||||
|
|
||||||
|
QStringList ccList = item.cc().split(',', Qt::SkipEmptyParts);
|
||||||
|
std::vector<mailio::address> ccAddrs;
|
||||||
|
for (const QString& addr : ccList) ccAddrs.push_back(mailio::address(addr.trimmed().toStdString()));
|
||||||
|
msg.header().cc(ccAddrs);
|
||||||
|
|
||||||
|
msg.header().date(QDateTime::currentDateTimeUtc().toSecsSinceEpoch());
|
||||||
|
|
||||||
|
// Body
|
||||||
|
if (!item.bodyHtml().isEmpty()) {
|
||||||
|
msg.content_text(item.bodyHtml().toStdString(), text_format_t::HTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachments would need to be added here
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_MAILIO_IMAP
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../synchronizer.h"
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
#if defined(USE_MAILIO_IMAP) && USE_MAILIO_IMAP
|
||||||
|
#include <mailio/message.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class ImapSynchronizerMailio : public Synchronizer
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ImapSynchronizerMailio(QObject* parent = nullptr);
|
||||||
|
~ImapSynchronizerMailio() override = default;
|
||||||
|
|
||||||
|
// Synchronizer interface
|
||||||
|
bool initialize(const Account& account) override;
|
||||||
|
bool syncFolder(const Folder& folder) override;
|
||||||
|
QVector<Folder> getFolders() const override;
|
||||||
|
QVector<MailItem> fetchMailItems(const QString& folderId, qint64 sinceUid = 0) override;
|
||||||
|
bool appendMailItem(const QString& folderId, const MailItem& item) override;
|
||||||
|
bool updateMailItemFlags(const QString& folderId, const QString& itemUid, bool read, bool flagged) override;
|
||||||
|
bool deleteMailItem(const QString& folderId, const QString& itemUid) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void progressChanged(int percent) const;
|
||||||
|
void statusMessage(const QString& message) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Connection details (set by initialize)
|
||||||
|
mutable std::string m_host;
|
||||||
|
mutable quint16 m_port{0};
|
||||||
|
mutable bool m_useSsl{false};
|
||||||
|
mutable std::string m_username;
|
||||||
|
mutable std::string m_password;
|
||||||
|
mutable std::string m_authMethod; // "plain", "login", "xoauth2", "oauth2"
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
#if defined(USE_MAILIO_IMAP) && USE_MAILIO_IMAP
|
||||||
|
bool persistFetchedMessage(const Folder& folder, const mailio::message& msg);
|
||||||
|
MailItem parseMailioMessage(const mailio::message& msg);
|
||||||
|
void updateLocalFlags(int folderId, const mailio::message& msg);
|
||||||
|
mailio::message createMailioMessage(const MailItem& item);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user