Implement basic UI with QML (MailListPage) and batching DbChangeProcessor (Step 3-4 of transition plan)

This commit is contained in:
Padrino
2026-05-17 01:09:01 +02:00
parent e3071a23e0
commit acec320222
20 changed files with 829 additions and 82 deletions
+9 -2
View File
@@ -17,10 +17,9 @@ include_directories(${PROJECT_SOURCE_DIR}/src)
# Resources
qt6_add_resources(WinoMailQtResources
PREFIX
/
FILES
resources/qml/main.qml
resources/qml/Shell.qml
)
# Source files
@@ -32,10 +31,18 @@ set(SRC_FILES
src/core/models/folder.cpp
src/services/synchronizer.cpp
src/services/imap/imapsynchronizer.cpp
src/services/outlook/outlooksynchronizer.cpp
src/services/gmail/gmailsynchronizer.cpp
src/services/request.cpp
src/services/concreterequests.cpp
src/services/requestprocessor.cpp
src/services/changetype.cpp
src/services/changprocessor.cpp
src/db/databasemanager.cpp
src/db/dao/accountdao.cpp
src/db/dao/folderdao.cpp
src/db/dao/mailitemdao.cpp
src/db/dbchangeprocessor.cpp
)
# Executable
+1
View File
@@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/">
<file>resources/qml/main.qml</file>
<file>resources/qml/Shell.qml</file>
</qresource>
</RCC>
+136
View File
@@ -0,0 +1,136 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Item {
id: mailListPage
anchors.fill: parent
// Layout: SplitView for resizable panes
SplitView {
anchors.fill: parent
// Left pane: Folder list
Rectangle {
width: 200
color: "#fafafa"
ListView {
id: folderListView
anchors.fill: parent
clip: true
model: FolderModel {}
delegate: Item {
height: 40
Layout.fillWidth: true
Rectangle {
anchors.fill: parent
color: ListView.isCurrentItem ? "#e3f2fd" : "transparent"
Text {
text: folderName
anchors.left: parent.left
anchors.leftMargin: 16
anchors.verticalCenter: parent.verticalCenter
font.pointSize: 14
color: ListView.isCurrentItem ? "#1976d2" : "#666"
}
}
MouseArea {
anchors.fill: parent
onClicked: {
folderListView.currentIndex = index
// TODO: Load emails for selected folder
}
}
}
}
}
// Right pane: Email list and preview
Rectangle {
color: "#fafafa"
SplitView {
orientation: Qt.Vertical
anchors.fill: parent
// Email list (top)
Rectangle {
Layout.minimumHeight: 200
color: "#fafafa"
ListView {
id: emailListView
anchors.fill: parent
clip: true
model: EmailModel {}
delegate: Item {
height: 60
Layout.fillWidth: true
Rectangle {
anchors.fill: parent
color: ListView.isCurrentItem ? "#e3f2fd" : "transparent"
RowLayout {
anchors.fill: parent
anchors.margins: 8
// Sender avatar/initial
Rectangle {
width: 40
height: 40
color: "#cccccc"
Text {
text: senderInitial
anchors.centerIn: parent
font.pointSize: 16
color: "#666"
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: senderName
font.pointSize: 14
color: "#333"
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: subject
font.pointSize: 13
color: "#666"
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: time
font.pointSize: 12
color: "#999"
}
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
emailListView.currentIndex = index
// TODO: Show email preview in the bottom pane
}
}
}
}
}
// Email preview (bottom)
Rectangle {
Layout.minimumHeight: 200
color: "#ffffff"
Text {
id: emailPreview
text: "Select an email to preview"
anchors.centerIn: parent
font.pointSize: 14
color: "#666"
}
}
}
}
}
}
+174
View File
@@ -0,0 +1,174 @@
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
id: shell
visible: true
width: 1024
height: 768
title: qsTr("Wino Mail Qt")
// Set the title from translator in Component.onCompleted
Component.onCompleted: {
title = translator.tr("appName")
}
// Drawer for navigation
Drawer {
id: drawer
width: 200
height: shell.height
Container {
anchors.fill: parent
Layout.alignment: Qt.AlignTop
ScrollView {
anchors.fill: parent
ColumnLayout {
spacing: 0
// App logo/name
Rectangle {
Layout.fillWidth: true
height: 60
color: "#1976D2"
Text {
text: qsTr("Wino Mail")
color: "white"
font.pointSize: 18
anchors.centerIn: parent
}
}
// Navigation items
NavigationItem {
text: qsTr("Mail")
icon: "mail"
isChecked: stackView.currentIndex === 0
onClicked: {
drawer.close()
stackView.currentIndex = 0
}
}
NavigationItem {
text: qsTr("Calendar")
icon: "calendar"
isChecked: stackView.currentIndex === 1
onClicked: {
drawer.close()
stackView.currentIndex = 1
}
}
NavigationItem {
text: qsTr("Contacts")
icon: "contacts"
isChecked: stackView.currentIndex === 2
onClicked: {
drawer.close()
stackView.currentIndex = 2
}
}
NavigationItem {
text: qsTr("Settings")
icon: "settings"
isChecked: stackView.currentIndex === 3
onClicked: {
drawer.close()
stackView.currentIndex = 3
}
}
}
}
}
}
// Main content area with StackView for pages
StackView {
id: stackView
anchors.fill: parent
initialItem: mailListPage
}
// Define pages
MailListPage {
id: mailListPage
}
// Placeholder for other pages
Rectangle {
color: "#f0f0f0"
Text {
text: qsTr("Calendar Page")
anchors.centerIn: parent
}
}
Rectangle {
color: "#f0f0f0"
Text {
text: qsTr("Contacts Page")
anchors.centerIn: parent
}
}
Rectangle {
color: "#f0f0f0"
Text {
text: qsTr("Settings Page")
anchors.centerIn: parent
}
}
// Hamburger button to open drawer
MenuButton {
icon: "menu"
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: 10
onClicked: drawer.open()
}
}
// Helper components
component NavigationItem: Button {
Layout.fillWidth: true
height: 48
padding: 0
contentItem: RowLayout {
spacing: 16
anchors.fill: parent
anchors.margins: 24
Icon {
source: icon
width: 24
height: 24
color: isChecked ? "#1976D2" : "#666"
}
Text {
text: text
font.pointSize: 14
verticalAlignment: Text.AlignVCenter
color: isChecked ? "#1976D2" : "#666"
}
}
background: Rectangle {
color: isChecked ? "#E3F2FD" : "transparent"
radius: 0
}
}
component MenuButton: IconButton {
iconSource: icon
}
// Placeholder for MailListPage - we'll replace this with a real component later
component MailListPage: Item {
id: mailListPage
anchors.fill: parent
Rectangle {
anchors.fill: parent
color: "#fafafa"
Text {
text: qsTr("Mail List Placeholder")
anchors.centerIn: parent
font.pointSize: 16
color: "#666"
}
}
}
+2 -24
View File
@@ -2,28 +2,6 @@ import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 1024
height: 768
visible: true
title: qsTr("Wino Mail Qt")
// Use the translator to set the title
// Note: we can't directly call translator.tr here because it's a C++ object.
// We'll set the title in Component.onCompleted by accessing the context property.
Component.onCompleted: {
title = translator.tr("appName")
}
Rectangle {
anchors.fill: parent
color: "#f0f0f0"
Text {
text: qsTr("Welcome to Wino Mail Qt!")
anchors.centerIn: parent
font.pointSize: 24
color: "#333"
}
}
// We'll load the Shell component from Shell.qml
Shell {
}
+24
View File
@@ -0,0 +1,24 @@
import QtQuick 2.15
ListModel {
ListElement {
folderName: "Inbox"
unreadCount: 5
}
ListElement {
folderName: "Sent"
unreadCount: 0
}
ListElement {
folderName: "Drafts"
unreadCount: 0
}
ListElement {
folderName: "Trash"
unreadCount: 0
}
ListElement {
folderName: "Spam"
unreadCount: 0
}
}
+6 -6
View File
@@ -3,9 +3,9 @@
#include <QString>
#include <QDateTime>
#include <QVector>
#include "../models/account.h"
#include "../models/folder.h"
#include "../core/mailitem.h"
#include "models/account.h"
#include "models/folder.h"
#include "mailitem.h"
namespace WinoMail {
namespace Events {
@@ -38,7 +38,7 @@ struct FolderAddedEvent : public BaseEvent {
};
struct FolderRemovedEvent : public BaseEvent {
String folderId;
QString folderId;
};
struct FolderUpdatedEvent : public BaseEvent {
@@ -63,12 +63,12 @@ struct AccountUpdatedEvent : public BaseEvent {
// Sync events
struct SyncStartedEvent : public BaseEvent {
int accountId;
String folderId; // Optional, empty for full account sync
QString folderId; // Optional, empty for full account sync
};
struct SyncFinishedEvent : public BaseEvent {
int accountId;
String folderId; // Optional, empty for full account sync
QString folderId; // Optional, empty for full account sync
bool success;
QString errorMessage; // If success is false
};
+3 -1
View File
@@ -3,9 +3,11 @@
#include <QString>
#include <QMap>
#include <QMutex>
#include <QObject>
class Translator
class Translator : public QObject
{
Q_OBJECT
public:
static Translator& instance();
~Translator() = default;
+24 -24
View File
@@ -1,5 +1,4 @@
#include "accountdao.h"
#include <optional>
bool AccountDao::insert(const Account& account)
{
@@ -69,7 +68,7 @@ bool AccountDao::remove(int id)
return true;
}
std::optional<Account> AccountDao::findById(int id)
Account* AccountDao::findById(int id)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
@@ -78,21 +77,22 @@ std::optional<Account> AccountDao::findById(int id)
if (!query.exec()) {
qWarning() << "Failed to find account by id:" << query.lastError().text();
return std::nullopt;
return nullptr;
}
if (query.next()) {
Account acc;
acc.setId(query.value(0).toInt());
acc.setEmail(query.value(1).toString());
acc.setDisplayName(query.value(2).toString());
acc.setType(static_cast<AccountType>(query.value(3).toInt()));
acc.setAccessToken(query.value(4).toString());
acc.setRefreshToken(query.value(5).toString());
acc.setTokenExpires(query.value(6).toDateTime());
return acc;
Account* account = new Account();
account->setId(query.value("id").toLongLong());
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());
return account;
}
return std::nullopt;
return nullptr;
}
QVector<Account> AccountDao::findAll()
@@ -119,7 +119,7 @@ QVector<Account> AccountDao::findAll()
return accounts;
}
std::optional<Account> AccountDao::findByEmail(const QString& email)
Account* AccountDao::findByEmail(const QString& email)
{
QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db);
@@ -128,19 +128,19 @@ std::optional<Account> AccountDao::findByEmail(const QString& email)
if (!query.exec()) {
qWarning() << "Failed to find account by email:" << query.lastError().text();
return std::nullopt;
return nullptr;
}
if (query.next()) {
Account acc;
acc.setId(query.value(0).toInt());
acc.setEmail(query.value(1).toString());
acc.setDisplayName(query.value(2).toString());
acc.setType(static_cast<AccountType>(query.value(3).toInt()));
acc.setAccessToken(query.value(4).toString());
acc.setRefreshToken(query.value(5).toString());
acc.setTokenExpires(query.value(6).toDateTime());
Account* acc = new Account();
acc->setId(query.value(0).toLongLong());
acc->setEmail(query.value(1).toString());
acc->setDisplayName(query.value(2).toString());
acc->setType(static_cast<AccountType>(query.value(3).toInt()));
acc->setAccessToken(query.value(4).toString());
acc->setRefreshToken(query.value(5).toString());
acc->setTokenExpires(query.value(6).toDateTime());
return acc;
}
return std::nullopt;
return nullptr;
}
+3
View File
@@ -1,4 +1,7 @@
#include <QSqlQuery>
#include "folderdao.h"
#include <QSqlError>
#include <QSqlError>
#include <optional>
bool FolderDao::insert(const Folder& folder)
+2
View File
@@ -1,3 +1,5 @@
#include <QSqlQuery>
#include <QSqlError>
#include "mailitemdao.h"
#include <optional>
+214
View File
@@ -0,0 +1,214 @@
#include "dbchangeprocessor.h"
#include <QDebug>
#include <QDateTime>
DbChangeProcessor::DbChangeProcessor(QObject* parent)
: QObject(parent),
m_db(DatabaseManager::instance()),
m_mailItemDao(MailItemDao::instance()),
m_folderDao(FolderDao::instance()),
m_accountDao(AccountDao::instance()),
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 UID:" << event.item.uid;
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 UID:" << event.item.uid;
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 (const auto& event : m_mailItemAddedQueue) {
m_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) {
m_mailItemDao.removeByUid(event.itemUid, event.folderId);
}
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) {
m_mailItemDao.update(event.item, event.changedFields);
}
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) {
m_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) {
m_folderDao.remove(event.folderId.toInt());
}
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) {
m_folderDao.update(event.folder, event.changedFields);
}
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) {
m_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) {
m_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) {
m_accountDao.update(event.account, event.changedFields);
}
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();
}
+51
View File
@@ -0,0 +1,51 @@
#ifndef DBCHANGEPROCESSOR_H
#define DBCHANGEPROCESSOR_H
#include <QObject>
#include <QTimer>
#include <QVector>
#include "core/eventbus.h"
#include "../core/events.h"
#include "../db/databasemanager.h"
#include "../db/dao/accountdao.h"
#include "../db/dao/folderdao.h"
#include "../db/dao/mailitemdao.h"
class DbChangeProcessor : public QObject
{
Q_OBJECT
public:
explicit DbChangeProcessor(QObject* parent = nullptr);
~DbChangeProcessor() override = default;
private slots:
void handleMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event);
void handleMailItemRemoved(const WinoMail::Events::MailItemRemovedEvent& event);
void handleMailItemUpdated(const WinoMail::Events::MailItemUpdatedEvent& event);
void handleFolderAdded(const WinoMail::Events::FolderAddedEvent& event);
void handleFolderRemoved(const WinoMail::Events::FolderRemovedEvent& event);
void handleFolderUpdated(const WinoMail::Events::FolderUpdatedEvent& event);
void handleAccountAdded(const WinoMail::Events::AccountAddedEvent& event);
void handleAccountRemoved(const WinoMail::Events::AccountRemovedEvent& event);
void handleAccountUpdated(const WinoMail::Events::AccountUpdatedEvent& event);
void processBatch();
private:
DatabaseManager& m_db;
MailItemDao& m_mailItemDao;
FolderDao& m_folderDao;
AccountDao& m_accountDao;
QTimer* m_batchTimer;
QVector<WinoMail::Events::MailItemAddedEvent> m_mailItemAddedQueue;
QVector<WinoMail::Events::MailItemRemovedEvent> m_mailItemRemovedQueue;
QVector<WinoMail::Events::MailItemUpdatedEvent> m_mailItemUpdatedQueue;
QVector<WinoMail::Events::FolderAddedEvent> m_folderAddedQueue;
QVector<WinoMail::Events::FolderRemovedEvent> m_folderRemovedQueue;
QVector<WinoMail::Events::FolderUpdatedEvent> m_folderUpdatedQueue;
QVector<WinoMail::Events::AccountAddedEvent> m_accountAddedQueue;
QVector<WinoMail::Events::AccountRemovedEvent> m_accountRemovedQueue;
QVector<WinoMail::Events::AccountUpdatedEvent> m_accountUpdatedQueue;
};
#endif // DBCHANGEPROCESSOR_H
+5 -1
View File
@@ -2,6 +2,7 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "core/translator.h"
#include "db/dbchangeprocessor.h"
int main(int argc, char *argv[])
{
@@ -13,8 +14,11 @@ int main(int argc, char *argv[])
qWarning() << "Failed to load translation";
}
// Initialize the DbChangeProcessor to start processing events in batches
DbChangeProcessor dbChangeProcessor(&app);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("translator", &translator);
engine.rootContext()->setContextProperty("translator", static_cast<QObject*>(&translator));
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
+7 -11
View File
@@ -7,6 +7,7 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <QUrlQuery>
#include <QDateTime>
#include <QTimer>
@@ -25,11 +26,6 @@ GmailSynchronizer::GmailSynchronizer(QObject* parent)
m_historyTimer->setInterval(2 * 60 * 1000); // 2 minutos
}
GmailSynchronizer::~GmailSynchronizer()
{
m_historyTimer->stop();
}
bool GmailSynchronizer::initialize(const Account& account)
{
// Guardar información de la cuenta
@@ -60,10 +56,10 @@ bool GmailSynchronizer::syncFolder(const Folder& folder)
}
qDebug() << "Starting Gmail sync for folder (label):" << folder.name();
emit folderSyncStarted(folder.id());
emit folderSyncStarted(QString::number(folder.id()));
// Obtener elementos de correo desde Gmail API
QVector<MailItem> items = fetchMailItems(folder.id());
QVector<MailItem> items = fetchMailItems(QString::number(folder.id()));
// En una implementación real, aquí compararíamos con la base de datos local
// y emitiríamos las señales apropiadas para elementos nuevos/actualizados/eliminados
@@ -75,7 +71,7 @@ bool GmailSynchronizer::syncFolder(const Folder& folder)
}
}
emit folderSyncFinished(folder.id(), true);
emit folderSyncFinished(QString::number(folder.id()), true);
return true;
}
@@ -113,7 +109,7 @@ QVector<MailItem> GmailSynchronizer::fetchMailItems(const QString& folderId,
// Construir la URL para Gmail API
// Nota: En Gmail, las carpetas se identifican por su nombre de label (ej: INBOX, DRAFTS, etc.)
QString endpoint = String.format("/me/mailFolders/%1/messages", folderId);
QString endpoint = QString("/me/mailFolders/%1/messages").arg(folderId);
QString url = buildGmailUrl(endpoint);
// Parámetros de consulta
@@ -257,7 +253,7 @@ void GmailSynchronizer::onHistoryTimer()
// de todas las etiquetas (esto sería ineficiente en producción)
QVector<Folder> folders = getFolders();
for (const Folder& folder : folders) {
syncFolder(folder.id());
syncFolder(Folder(folder.id(), 0, QString()));
}
}
@@ -270,7 +266,7 @@ QString GmailSynchronizer::buildGmailUrl(const QString& endpoint) const
QNetworkRequest GmailSynchronizer::createAuthRequest(const QString& url) const
{
QNetworkRequest request(QUrl(url));
QNetworkRequest request{QUrl(url)};
request.setRawHeader("Authorization",
QString("Bearer %1").arg(m_accessToken).toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader,
+75 -3
View File
@@ -1,5 +1,6 @@
#include "imapsynchronizer.h"
#include <QDebug>
#include <cstdlib>
ImapSynchronizer::ImapSynchronizer(QObject* parent)
: Synchronizer(parent)
@@ -11,6 +12,15 @@ bool ImapSynchronizer::initialize(const Account& account)
Q_UNUSED(account);
qDebug() << "IMAP Synchronizer initialize (stub)";
m_connected = true; // pretend success
// Publish AccountConnectedEvent
WinoMail::Events::AccountConnectedEvent event;
event.eventId = generateEventId();
event.timestamp = QDateTime::currentDateTimeUtc();
event.accountId = account.id();
event.provider = "imap";
PUBLISH(event);
return true;
}
@@ -18,9 +28,36 @@ bool ImapSynchronizer::syncFolder(const Folder& folder)
{
Q_UNUSED(folder);
qDebug() << "IMAP Synchronizer syncFolder (stub)";
emit folderSyncStarted(folder.name());
// Publish SyncStartedEvent
WinoMail::Events::SyncStartedEvent event;
event.eventId = generateEventId();
event.timestamp = QDateTime::currentDateTimeUtc();
event.accountId = m_account.id();
event.folderId = QString::number(folder.id()); // Assuming folder.id() returns int
PUBLISH(event);
// In reality, we would connect to IMAP, list messages, etc.
emit folderSyncFinished(folder.name(), true);
// For now, simulate some mail items being added
QVector<MailItem> items = fetchMailItems(QString::number(folder.id()));
for (const MailItem& item : items) {
WinoMail::Events::MailItemAddedEvent mailEvent;
mailEvent.eventId = generateEventId();
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
mailEvent.item = item;
PUBLISH(mailEvent);
}
// Publish SyncFinishedEvent
WinoMail::Events::SyncFinishedEvent finishEvent;
finishEvent.eventId = generateEventId();
finishEvent.timestamp = QDateTime::currentDateTimeUtc();
finishEvent.accountId = m_account.id();
finishEvent.folderId = QString::number(folder.id());
finishEvent.success = true;
finishEvent.errorMessage = "";
PUBLISH(finishEvent);
return true;
}
@@ -41,7 +78,7 @@ QVector<MailItem> ImapSynchronizer::fetchMailItems(const QString& folderId,
qDebug() << "IMAP Synchronizer fetchMailItems (stub)";
// Return a dummy mail item for testing
QVector<MailItem> items;
items.append(MailItem(1, "Test Subject", "sender@example.com", "me@example.com",
items.append(MailItem(1, 1, "Test Subject", "sender@example.com", "me@example.com",
QDateTime::currentDateTime(), false, false));
return items;
}
@@ -51,6 +88,13 @@ bool ImapSynchronizer::appendMailItem(const QString& folderId, const MailItem& i
Q_UNUSED(folderId);
Q_UNUSED(item);
qDebug() << "IMAP Synchronizer appendMailItem (stub)";
// In a real implementation, this would send the mail via IMAP APPEND
// For now, we'll simulate success and publish an event
WinoMail::Events::MailItemAddedEvent event;
event.eventId = generateEventId();
event.timestamp = QDateTime::currentDateTimeUtc();
event.item = item;
PUBLISH(event);
return true;
}
@@ -63,6 +107,18 @@ bool ImapSynchronizer::updateMailItemFlags(const QString& folderId,
Q_UNUSED(read);
Q_UNUSED(flagged);
qDebug() << "IMAP Synchronizer updateMailItemFlags (stub)";
// In a real implementation, this would update flags via IMAP STORE
// For now, we'll simulate success and publish an update event
// We need to fetch the item first to know what changed
MailItem item; // This would be fetched from storage
item.setId(itemUid.toLongLong()); // Assuming there's a setter
WinoMail::Events::MailItemUpdatedEvent event;
event.eventId = generateEventId();
event.timestamp = QDateTime::currentDateTimeUtc();
event.item = item;
event.changedFields = QStringList() << "read" << "flagged"; // Simplified
PUBLISH(event);
return true;
}
@@ -72,5 +128,21 @@ bool ImapSynchronizer::deleteMailItem(const QString& folderId,
Q_UNUSED(folderId);
Q_UNUSED(itemUid);
qDebug() << "IMAP Synchronizer deleteMailItem (stub)";
// In a real implementation, this would delete the mail via IMAP STORE +FLAGS or EXPUNGE
// For now, we'll simulate success and publish a removal event
WinoMail::Events::MailItemRemovedEvent event;
event.eventId = generateEventId();
event.timestamp = QDateTime::currentDateTimeUtc();
event.itemUid = itemUid;
event.folderId = folderId.toInt(); // Assuming folderId is numeric
PUBLISH(event);
return true;
}
// Helper para generar IDs únicos de eventos
QString ImapSynchronizer::generateEventId() const
{
// Simple implementation using timestamp and random component
return QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" +
QString::number(std::rand());
}
+7
View File
@@ -2,6 +2,10 @@
#include "../synchronizer.h"
#include <QObject>
#include "../../core/eventbus.h"
#include "../../core/models/account.h"
#include "../../core/models/folder.h"
#include "../../core/mailitem.h"
class ImapSynchronizer : public Synchronizer
{
@@ -31,4 +35,7 @@ private:
QString m_username;
QString m_password;
bool m_useSsl{true};
// Helper para generar IDs únicos de eventos
QString generateEventId() const;
};
+5 -9
View File
@@ -7,6 +7,7 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <QUrlQuery>
#include <QDateTime>
#include <QTimer>
@@ -25,11 +26,6 @@ OutlookSynchronizer::OutlookSynchronizer(QObject* parent)
m_deltaTimer->setInterval(5 * 60 * 1000); // 5 minutos
}
OutlookSynchronizer::~OutlookSynchronizer()
{
m_deltaTimer->stop();
}
bool OutlookSynchronizer::initialize(const Account& account)
{
// Guardar información de la cuenta
@@ -60,10 +56,10 @@ bool OutlookSynchronizer::syncFolder(const Folder& folder)
}
qDebug() << "Starting Outlook sync for folder:" << folder.name();
emit folderSyncStarted(folder.id());
emit folderSyncStarted(QString::number(folder.id()));
// Obtener elementos de correo desde Microsoft Graph
QVector<MailItem> items = fetchMailItems(folder.id());
QVector<MailItem> items = fetchMailItems(QString::number(folder.id()));
// En una implementación real, aquí compararíamos con la base de datos local
// y emitiríamos las señales apropiadas para elementos nuevos/actualizados/eliminados
@@ -75,7 +71,7 @@ bool OutlookSynchronizer::syncFolder(const Folder& folder)
}
}
emit folderSyncFinished(folder.id(), true);
emit folderSyncFinished(QString::number(folder.id()), true);
return true;
}
@@ -264,7 +260,7 @@ QString OutlookSynchronizer::buildGraphUrl(const QString& endpoint) const
QNetworkRequest OutlookSynchronizer::createAuthRequest(const QString& url) const
{
QNetworkRequest request(QUrl(url));
QNetworkRequest request{QUrl(url)};
request.setRawHeader("Authorization",
QString("Bearer %1").arg(m_accessToken).toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader,
+46
View File
@@ -0,0 +1,46 @@
#include "FolderListModel.h"
#include <QDebug>
FolderListModel::FolderListModel(QObject *parent)
: QAbstractListModel(parent),
m_folderDao(FolderDao::instance())
{
refresh();
}
int FolderListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_folderNames.size();
}
QVariant FolderListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= m_folderNames.size())
return QVariant();
if (role == FolderNameRole)
return m_folderNames.at(index.row());
else if (role == UnreadCountRole)
return m_unreadCounts.at(index.row());
return QVariant();
}
QHash<int, QByteArray> FolderListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[FolderNameRole] = "folderName";
roles[UnreadCountRole] = "unreadCount";
return roles;
}
void FolderListModel::refresh()
{
beginResetModel();
// For now, we'll just use hardcoded folders until we implement the DAO properly
m_folderNames = {"Inbox", "Sent", "Drafts", "Trash", "Spam"};
m_unreadCounts = {5, 0, 0, 0, 0};
endResetModel();
qDebug() << "FolderListModel refreshed with" << m_folderNames.size() << "folders";
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef FOLDERLISTMODEL_H
#define FOLDERLISTMODEL_H
#include <QAbstractListModel>
#include <QHash>
#include <QByteArray>
#include "../db/dao/folderdao.h"
class FolderListModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit FolderListModel(QObject *parent = nullptr);
~FolderListModel() override = default;
enum FolderRoles {
FolderNameRole = Qt::UserRole + 1,
UnreadCountRole
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
// Optional: method to refresh the model from the database
void refresh();
private:
QVector<QString> m_folderNames;
QVector<int> m_unreadCounts;
FolderDao& m_folderDao;
};
#endif // FOLDERLISTMODEL_H