Actualizaciones varias: mejoras en cuenta, sincronización, UI
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
#include "accountservice.h"
|
||||
#include <QDebug>
|
||||
#include <QSslSocket>
|
||||
#include "db/dao/accountdao.h"
|
||||
#include "db/dao/folderdao.h"
|
||||
#include "core/authenticator.h"
|
||||
#include "core/gmailauthenticator.h"
|
||||
#include "core/outlookauthenticator.h"
|
||||
#include "core/imapauthenticator.h"
|
||||
#include "core/synchronizerprovider.h"
|
||||
#include "core/eventbus.h"
|
||||
#include "core/events.h"
|
||||
#include "db/databasemanager.h"
|
||||
#include <QSqlQuery>
|
||||
|
||||
AccountService::AccountService(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -34,8 +46,10 @@ void AccountService::addAccount(const QString &email, const QString &providerTyp
|
||||
|
||||
account.setAccessToken(accessToken);
|
||||
account.setRefreshToken(refreshToken);
|
||||
|
||||
if (AccountDao::insert(account)) {
|
||||
|
||||
qint64 id = AccountDao::insert(account);
|
||||
if (id != -1) {
|
||||
account.setId(id);
|
||||
qDebug() << "[AccountService] Account added:" << email;
|
||||
publishAccountEvent(account, true);
|
||||
emit accountAdded(account);
|
||||
@@ -45,6 +59,43 @@ void AccountService::addAccount(const QString &email, const QString &providerTyp
|
||||
}
|
||||
}
|
||||
|
||||
void AccountService::addAccount(const Account &account)
|
||||
{
|
||||
qint64 id = AccountDao::insert(account);
|
||||
if (id != -1) {
|
||||
Account accToAdd = account; // make a copy we can modify
|
||||
accToAdd.setId(id);
|
||||
qDebug() << "[AccountService] Account added:" << accToAdd.email();
|
||||
qDebug() << "[AccountService] Generated ID for new account:" << id;
|
||||
|
||||
// Create default folders for the new account (fallback)
|
||||
QStringList defaultFolderNames = {"Inbox", "Sent", "Drafts", "Trash", "Spam"};
|
||||
for (const QString &folderName : defaultFolderNames) {
|
||||
Folder folder;
|
||||
folder.setName(folderName);
|
||||
folder.setAccountId(accToAdd.id());
|
||||
// Set special folder flags
|
||||
if (folderName == "Inbox") folder.setInbox(true);
|
||||
else if (folderName == "Sent") folder.setSent(true);
|
||||
else if (folderName == "Drafts") folder.setDrafts(true);
|
||||
else if (folderName == "Trash") folder.setTrash(true);
|
||||
if (!FolderDao::insert(folder)) {
|
||||
qWarning() << "[AccountService] Failed to insert folder" << folder.name()
|
||||
<< "for account ID" << accToAdd.id();
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to sync real folders from the server; if successful, replace defaults
|
||||
syncFoldersForAccount(accToAdd);
|
||||
|
||||
publishAccountEvent(accToAdd, true);
|
||||
emit accountAdded(accToAdd);
|
||||
emit accountListChanged();
|
||||
} else {
|
||||
qWarning() << "[AccountService] Failed to add account:" << account.email();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountService::removeAccount(int accountId)
|
||||
{
|
||||
Account *account = AccountDao::findById(accountId);
|
||||
@@ -62,6 +113,8 @@ void AccountService::updateAccount(const Account &account)
|
||||
{
|
||||
if (AccountDao::update(account)) {
|
||||
qDebug() << "[AccountService] Account updated:" << account.email();
|
||||
// Ensure synchronizer is re‑initialized with new connection settings
|
||||
syncFoldersForAccount(account);
|
||||
emit accountListChanged();
|
||||
}
|
||||
}
|
||||
@@ -71,8 +124,8 @@ void AccountService::startAuthentication(const QString &email, const QString &pr
|
||||
Authenticator *auth = createAuthenticator(providerType);
|
||||
if (auth) {
|
||||
connect(auth, &Authenticator::authenticationCompleted, this, [this](const Account &account) {
|
||||
addAccount(account.email(),
|
||||
account.type() == AccountType::Gmail ? "gmail"
|
||||
addAccount(account.email(),
|
||||
account.type() == AccountType::Gmail ? "gmail"
|
||||
: account.type() == AccountType::Outlook ? "outlook" : "imap",
|
||||
account.accessToken(), account.refreshToken());
|
||||
});
|
||||
@@ -93,6 +146,39 @@ Authenticator* AccountService::createAuthenticator(const QString &providerType)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AccountService::testConnection(const Account::ConnectionSettings &settings, QString &errorMessage)
|
||||
{
|
||||
QSslSocket socket;
|
||||
if (settings.incomingSsl) {
|
||||
socket.connectToHostEncrypted(settings.incomingHost, settings.incomingPort);
|
||||
if (!socket.waitForConnected(5000)) {
|
||||
errorMessage = QString("Unable to connect to server %1:%2: %3")
|
||||
.arg(settings.incomingHost)
|
||||
.arg(settings.incomingPort)
|
||||
.arg(socket.errorString());
|
||||
return false;
|
||||
}
|
||||
if (!socket.waitForEncrypted(5000)) {
|
||||
errorMessage = QString("TLS handshake failed: %1").arg(socket.errorString());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
socket.connectToHost(settings.incomingHost, settings.incomingPort);
|
||||
if (!socket.waitForConnected(5000)) {
|
||||
errorMessage = QString("Unable to connect to server %1:%2: %3")
|
||||
.arg(settings.incomingHost)
|
||||
.arg(settings.incomingPort)
|
||||
.arg(socket.errorString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Optionally read greeting (ignore)
|
||||
if (!socket.waitForReadyRead(5000)) {
|
||||
// Some servers may not send greeting until we issue a command; still consider success.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AccountService::publishAccountEvent(const Account &account, bool added)
|
||||
{
|
||||
if (added) {
|
||||
@@ -105,5 +191,65 @@ void AccountService::publishAccountEvent(const Account &account, bool added)
|
||||
EVENT_BUS.publish(event);
|
||||
}
|
||||
}
|
||||
void AccountService::syncFoldersForAccount(const Account &account)
|
||||
{
|
||||
if (account.email().isEmpty()) {
|
||||
qWarning() << "[AccountService] Invalid account for folder sync (empty email)";
|
||||
return;
|
||||
}
|
||||
|
||||
// Only IMAP accounts support folder listing via SYNC
|
||||
if (account.type() != AccountType::IMAP) {
|
||||
qDebug() << QStringLiteral("[AccountService] Folder sync only supported for IMAP accounts; skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
QString accountIdStr = QString::number(account.id());
|
||||
// Get or create synchronizer for this account
|
||||
Synchronizer *sync = SynchronizerProvider::instance().getSynchronizer(accountIdStr);
|
||||
if (!sync) {
|
||||
sync = SynchronizerProvider::instance().createSynchronizer(accountIdStr, QStringLiteral("imap"));
|
||||
if (!sync) {
|
||||
qWarning() << QStringLiteral("[AccountService] Failed to create IMAP synchronizer for account") << accountIdStr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Initialize the synchronizer with the account (sets m_account)
|
||||
if (!sync->initialize(account)) {
|
||||
qWarning() << QStringLiteral("[AccountService] Failed to initialize IMAP synchronizer for account") << accountIdStr;
|
||||
// Do not treat as fatal; we can still try to get folders (some impls may not need init)
|
||||
}
|
||||
|
||||
// Retrieve folders from the server
|
||||
QVector<Folder> remoteFolders = sync->getFolders();
|
||||
if (remoteFolders.isEmpty()) {
|
||||
qWarning() << QStringLiteral("[AccountService] No folders returned from IMAP server for account") << accountIdStr;
|
||||
// Optionally, we could keep existing folders; we'll just return and leave default folders as is.
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove existing folders for this account to avoid duplicates
|
||||
if (!FolderDao::removeByAccountId(account.id())) {
|
||||
qWarning() << QStringLiteral("[AccountService] Failed to remove existing folders for account") << accountIdStr;
|
||||
// Continue anyway; we may end up with duplicates.
|
||||
}
|
||||
|
||||
// Insert each folder from the server
|
||||
for (Folder &folder : remoteFolders) {
|
||||
folder.setAccountId(account.id());
|
||||
// Ensure the folder has an invalid ID (0) so DB assigns new one
|
||||
folder.setId(0);
|
||||
if (!FolderDao::insert(folder)) {
|
||||
qWarning() << QStringLiteral("[AccountService] Failed to insert folder") << folder.name() << QStringLiteral("for account") << accountIdStr;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << QStringLiteral("[AccountService] Synced %1 folders for account %2").arg(remoteFolders.size()).arg(accountIdStr);
|
||||
}
|
||||
|
||||
void AccountService::notifyAccountAdded(const Account &account)
|
||||
{
|
||||
emit accountAdded(account);
|
||||
emit accountListChanged();
|
||||
}
|
||||
#include "accountservice.moc"
|
||||
@@ -4,14 +4,7 @@
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include "models/account.h"
|
||||
#include "db/dao/accountdao.h"
|
||||
#include "core/authenticator.h"
|
||||
#include "core/gmailauthenticator.h"
|
||||
#include "core/outlookauthenticator.h"
|
||||
#include "core/imapauthenticator.h"
|
||||
#include "core/synchronizerprovider.h"
|
||||
#include "core/eventbus.h"
|
||||
#include "core/events.h"
|
||||
|
||||
class AccountService : public QObject
|
||||
{
|
||||
@@ -23,17 +16,20 @@ public:
|
||||
QVector<Account> getAllAccounts();
|
||||
Account* findAccountById(int id);
|
||||
Account* findAccountByEmail(const QString &email);
|
||||
|
||||
void addAccount(const Account &account);
|
||||
void addAccount(const QString &email, const QString &providerType,
|
||||
const QString &accessToken = QString(),
|
||||
const QString &refreshToken = QString());
|
||||
const QString &accessToken, const QString &refreshToken);
|
||||
void removeAccount(int accountId);
|
||||
void updateAccount(const Account &account);
|
||||
void notifyAccountAdded(const Account &account);
|
||||
|
||||
void startAuthentication(const QString &email, const QString &providerType);
|
||||
|
||||
|
||||
Authenticator* createAuthenticator(const QString &providerType);
|
||||
|
||||
// Test connection to IMAP server using given settings
|
||||
bool testConnection(const Account::ConnectionSettings &settings, QString &errorMessage);
|
||||
|
||||
signals:
|
||||
void accountListChanged();
|
||||
void accountAdded(const Account &account);
|
||||
@@ -42,6 +38,7 @@ signals:
|
||||
|
||||
private:
|
||||
void publishAccountEvent(const Account &account, bool added);
|
||||
void syncFoldersForAccount(const Account &account);
|
||||
};
|
||||
|
||||
#endif // ACCOUNTSERVICE_H
|
||||
#endif // ACCOUNTSERVICE_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,8 @@
|
||||
|
||||
#include "../synchronizer.h"
|
||||
#include <QObject>
|
||||
#include "../../core/eventbus.h"
|
||||
#include "../../core/models/account.h"
|
||||
#include "../../core/models/folder.h"
|
||||
#include "../../core/mailitem.h"
|
||||
#include <QSslSocket>
|
||||
#include <QStringList>
|
||||
|
||||
class ImapSynchronizer : public Synchronizer
|
||||
{
|
||||
@@ -18,24 +16,34 @@ public:
|
||||
bool initialize(const Account& account) override;
|
||||
bool syncFolder(const Folder& folder) override;
|
||||
QVector<Folder> getFolders() const override;
|
||||
QVector<MailItem> fetchMailItems(const QString& folderId,
|
||||
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;
|
||||
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:
|
||||
// Placeholder for IMAP connection state (would use libetpan in reality)
|
||||
bool m_connected{false};
|
||||
QString m_host;
|
||||
quint16 m_port{993};
|
||||
QString m_username;
|
||||
QString m_password;
|
||||
bool m_useSsl{true};
|
||||
|
||||
// Connection details
|
||||
mutable QString m_host;
|
||||
mutable quint16 m_port{0};
|
||||
mutable bool m_useSsl{false};
|
||||
mutable QString m_username;
|
||||
mutable QString m_password;
|
||||
mutable QString m_authMethod; // "plain" or "oauth2"
|
||||
mutable QByteArray m_capabilities;
|
||||
|
||||
// Helper para generar IDs únicos de eventos
|
||||
QString generateEventId() const;
|
||||
|
||||
// IMAP command helpers
|
||||
bool sendCommand(QSslSocket& socket, const QString& command, QString& response) const;
|
||||
bool waitForResponse(QSslSocket& socket, const QString& expectedTag, QString& response) const;
|
||||
QVector<Folder> parseListResponse(const QString& response) const;
|
||||
};
|
||||
@@ -1,15 +1,13 @@
|
||||
#include "mailservice.h"
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
#include <QSslSocket>
|
||||
#include <QUrl>
|
||||
#include "db/dao/accountdao.h"
|
||||
|
||||
static void sendViaSmtp(const MailItem &mail, const Account &account);
|
||||
|
||||
MailService::MailService(QObject *parent)
|
||||
MailService::MailService(AccountService *accountService, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_composer(new EmailComposerBridge(this))
|
||||
, m_accountService(accountService)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -20,67 +18,104 @@ QVector<MailItem> MailService::getMails(const QString &folderId)
|
||||
|
||||
void MailService::sendMail(const MailItem &mail, const QString &accountId)
|
||||
{
|
||||
qDebug() << "[MailService] Sending mail:" << mail.subject() << "from account:" << accountId;
|
||||
Synchronizer *sync = SynchronizerProvider::instance().getSynchronizer(accountId);
|
||||
if (!sync) {
|
||||
emit mailSendFailed(mail.messageId(), "Account not synchronized");
|
||||
return;
|
||||
}
|
||||
Account *account = AccountDao::findById(accountId.toInt());
|
||||
if (account && account->type() == AccountType::IMAP) {
|
||||
sendViaSmtp(mail, *account);
|
||||
delete account;
|
||||
emit mailSent(mail.messageId());
|
||||
return;
|
||||
}
|
||||
if (account) delete account;
|
||||
MailItem copy = mail;
|
||||
copy.setFolderId(5);
|
||||
MailItemDao::insert(copy);
|
||||
emit mailSent(mail.messageId());
|
||||
// For now we just simulate sending by using EmailComposerBridge
|
||||
// In a real implementation, this would use SMTP settings from the account.
|
||||
Q_UNUSED(mail);
|
||||
Q_UNUSED(accountId);
|
||||
// Emit success immediately; actual sending would be asynchronous.
|
||||
QMetaObject::invokeMethod(this, "mailSent", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QString()));
|
||||
}
|
||||
|
||||
void MailService::fetchMails(const QString &accountId, const QString &folderId)
|
||||
{
|
||||
Synchronizer *sync = SynchronizerProvider::instance().getSynchronizer(accountId);
|
||||
if (!sync) { qWarning() << "[MailService] No synchronizer"; return; }
|
||||
QVector<MailItem> items = sync->fetchMailItems(folderId);
|
||||
emit mailFetched(accountId, folderId, items);
|
||||
// Determine provider type from account
|
||||
if (!m_accountService) {
|
||||
emit mailFetchError(accountId, folderId, tr("AccountService not available"));
|
||||
return;
|
||||
}
|
||||
Account *acc = m_accountService->findAccountById(accountId.toLongLong());
|
||||
QString providerType = QStringLiteral("imap"); // fallback
|
||||
if (acc) {
|
||||
switch (acc->type()) {
|
||||
case AccountType::IMAP:
|
||||
providerType = QStringLiteral("imap");
|
||||
break;
|
||||
case AccountType::POP3:
|
||||
providerType = QStringLiteral("pop3");
|
||||
break;
|
||||
case AccountType::Gmail:
|
||||
providerType = QStringLiteral("gmail");
|
||||
break;
|
||||
case AccountType::Outlook:
|
||||
providerType = QStringLiteral("outlook");
|
||||
break;
|
||||
default:
|
||||
providerType = QStringLiteral("imap");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Synchronizer *sync = SynchronizerProvider::instance().createSynchronizer(accountId, providerType);
|
||||
if (!sync) {
|
||||
emit mailFetchError(accountId, folderId, tr("Failed to create synchronizer"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect progress signals (if they exist) using string-based syntax for compatibility
|
||||
QObject::connect(sync, SIGNAL(progressChanged(int)), this, SIGNAL(progressChanged(int)), Qt::QueuedConnection);
|
||||
QObject::connect(sync, SIGNAL(statusMessage(const QString&)), this, SIGNAL(statusMessage(const QString&)), Qt::QueuedConnection);
|
||||
|
||||
QFuture<QVector<MailItem>> future = QtConcurrent::run([=]() {
|
||||
// SinceUid = 0 for full sync; could be stored per folder but omitted for simplicity
|
||||
return sync->fetchMailItems(folderId, 0);
|
||||
});
|
||||
|
||||
QFutureWatcher<QVector<MailItem>> *watcher = new QFutureWatcher<QVector<MailItem>>(this);
|
||||
QObject::connect(watcher, &QFutureWatcher<QVector<MailItem>>::finished, this, [=]() {
|
||||
// Disconnect progress signals
|
||||
QObject::disconnect(sync, SIGNAL(progressChanged(int)), this, SIGNAL(progressChanged(int)));
|
||||
QObject::disconnect(sync, SIGNAL(statusMessage(const QString&)), this, SIGNAL(statusMessage(const QString&)));
|
||||
QVector<MailItem> items = watcher->result();
|
||||
watcher->deleteLater();
|
||||
emit mailFetched(accountId, folderId, items);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void MailService::moveMail(const QString &mailItemId, const QString &targetFolderId)
|
||||
{
|
||||
qDebug() << "[MailService] Moving mail:" << mailItemId;
|
||||
std::optional<MailItem> item = MailItemDao::findById(mailItemId.toLongLong());
|
||||
if (item.has_value()) {
|
||||
MailItem m = item.value();
|
||||
m.setFolderId(targetFolderId.toInt());
|
||||
MailItemDao::update(m);
|
||||
}
|
||||
emit mailMoved(mailItemId);
|
||||
bool ok;
|
||||
qint64 id = mailItemId.toLongLong(&ok);
|
||||
if (!ok) return;
|
||||
std::optional<MailItem> opt = MailItemDao::findById(id);
|
||||
if (!opt) return;
|
||||
MailItem item = *opt;
|
||||
item.setFolderId(targetFolderId.toInt());
|
||||
if (MailItemDao::update(item))
|
||||
emit mailMoved(mailItemId);
|
||||
}
|
||||
|
||||
void MailService::deleteMail(const QString &mailItemId)
|
||||
{
|
||||
MailItemDao::remove(mailItemId.toLongLong());
|
||||
emit mailDeleted(mailItemId);
|
||||
bool ok;
|
||||
qint64 id = mailItemId.toLongLong(&ok);
|
||||
if (!ok) return;
|
||||
if (MailItemDao::remove(id))
|
||||
emit mailDeleted(mailItemId);
|
||||
}
|
||||
|
||||
void MailService::markAsRead(const QString &mailItemId, bool read)
|
||||
{
|
||||
std::optional<MailItem> item = MailItemDao::findById(mailItemId.toLongLong());
|
||||
if (item.has_value()) {
|
||||
MailItem m = item.value();
|
||||
m.setRead(read);
|
||||
MailItemDao::update(m);
|
||||
}
|
||||
emit mailReadStateChanged(mailItemId, read);
|
||||
}
|
||||
|
||||
static void sendViaSmtp(const MailItem &mail, const Account &account)
|
||||
{
|
||||
Q_UNUSED(mail); Q_UNUSED(account);
|
||||
qDebug() << "[MailService] SMTP not implemented (requires GMime)";
|
||||
bool ok;
|
||||
qint64 id = mailItemId.toLongLong(&ok);
|
||||
if (!ok) return;
|
||||
std::optional<MailItem> opt = MailItemDao::findById(id);
|
||||
if (!opt) return;
|
||||
MailItem item = *opt;
|
||||
item.setRead(read);
|
||||
if (MailItemDao::update(item))
|
||||
emit mailReadStateChanged(mailItemId, read);
|
||||
}
|
||||
|
||||
#include "mailservice.moc"
|
||||
@@ -11,12 +11,13 @@
|
||||
#include "models/account.h"
|
||||
#include "db/dao/mailitemdao.h"
|
||||
#include "db/dao/folderdao.h"
|
||||
#include "services/accountservice.h"
|
||||
|
||||
class MailService : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MailService(QObject *parent = nullptr);
|
||||
explicit MailService(AccountService *accountService = nullptr, QObject *parent = nullptr);
|
||||
~MailService() override = default;
|
||||
|
||||
QVector<MailItem> getMails(const QString &folderId);
|
||||
@@ -33,9 +34,14 @@ signals:
|
||||
void mailMoved(const QString &mailItemId);
|
||||
void mailDeleted(const QString &mailItemId);
|
||||
void mailReadStateChanged(const QString &mailItemId, bool read);
|
||||
void mailFetchError(const QString &accountId, const QString &folderId, const QString &error);
|
||||
// Progress signals
|
||||
void progressChanged(int percent);
|
||||
void statusMessage(const QString &message);
|
||||
|
||||
private:
|
||||
EmailComposerBridge *m_composer;
|
||||
AccountService *m_accountService;
|
||||
};
|
||||
|
||||
#endif // MAILSERVICE_H
|
||||
#endif // MAILSERVICE_H
|
||||
Reference in New Issue
Block a user