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 # Resources
qt6_add_resources(WinoMailQtResources qt6_add_resources(WinoMailQtResources
PREFIX
/
FILES FILES
resources/qml/main.qml resources/qml/main.qml
resources/qml/Shell.qml
) )
# Source files # Source files
@@ -32,10 +31,18 @@ set(SRC_FILES
src/core/models/folder.cpp src/core/models/folder.cpp
src/services/synchronizer.cpp src/services/synchronizer.cpp
src/services/imap/imapsynchronizer.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/databasemanager.cpp
src/db/dao/accountdao.cpp src/db/dao/accountdao.cpp
src/db/dao/folderdao.cpp src/db/dao/folderdao.cpp
src/db/dao/mailitemdao.cpp src/db/dao/mailitemdao.cpp
src/db/dbchangeprocessor.cpp
) )
# Executable # Executable
+1
View File
@@ -1,5 +1,6 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>resources/qml/main.qml</file> <file>resources/qml/main.qml</file>
<file>resources/qml/Shell.qml</file>
</qresource> </qresource>
</RCC> </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.Window 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
Window { // We'll load the Shell component from Shell.qml
width: 1024 Shell {
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"
}
}
} }
+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 <QString>
#include <QDateTime> #include <QDateTime>
#include <QVector> #include <QVector>
#include "../models/account.h" #include "models/account.h"
#include "../models/folder.h" #include "models/folder.h"
#include "../core/mailitem.h" #include "mailitem.h"
namespace WinoMail { namespace WinoMail {
namespace Events { namespace Events {
@@ -38,7 +38,7 @@ struct FolderAddedEvent : public BaseEvent {
}; };
struct FolderRemovedEvent : public BaseEvent { struct FolderRemovedEvent : public BaseEvent {
String folderId; QString folderId;
}; };
struct FolderUpdatedEvent : public BaseEvent { struct FolderUpdatedEvent : public BaseEvent {
@@ -63,12 +63,12 @@ struct AccountUpdatedEvent : public BaseEvent {
// Sync events // Sync events
struct SyncStartedEvent : public BaseEvent { struct SyncStartedEvent : public BaseEvent {
int accountId; int accountId;
String folderId; // Optional, empty for full account sync QString folderId; // Optional, empty for full account sync
}; };
struct SyncFinishedEvent : public BaseEvent { struct SyncFinishedEvent : public BaseEvent {
int accountId; int accountId;
String folderId; // Optional, empty for full account sync QString folderId; // Optional, empty for full account sync
bool success; bool success;
QString errorMessage; // If success is false QString errorMessage; // If success is false
}; };
+3 -1
View File
@@ -3,9 +3,11 @@
#include <QString> #include <QString>
#include <QMap> #include <QMap>
#include <QMutex> #include <QMutex>
#include <QObject>
class Translator class Translator : public QObject
{ {
Q_OBJECT
public: public:
static Translator& instance(); static Translator& instance();
~Translator() = default; ~Translator() = default;
+24 -24
View File
@@ -1,5 +1,4 @@
#include "accountdao.h" #include "accountdao.h"
#include <optional>
bool AccountDao::insert(const Account& account) bool AccountDao::insert(const Account& account)
{ {
@@ -69,7 +68,7 @@ bool AccountDao::remove(int id)
return true; return true;
} }
std::optional<Account> AccountDao::findById(int id) Account* AccountDao::findById(int id)
{ {
QSqlDatabase& db = DatabaseManager::instance().database(); QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db); QSqlQuery query(db);
@@ -78,21 +77,22 @@ std::optional<Account> AccountDao::findById(int id)
if (!query.exec()) { if (!query.exec()) {
qWarning() << "Failed to find account by id:" << query.lastError().text(); qWarning() << "Failed to find account by id:" << query.lastError().text();
return std::nullopt; return nullptr;
} }
if (query.next()) { if (query.next()) {
Account acc; Account* account = new Account();
acc.setId(query.value(0).toInt()); account->setId(query.value("id").toLongLong());
acc.setEmail(query.value(1).toString()); account->setEmail(query.value("email").toString());
acc.setDisplayName(query.value(2).toString()); account->setDisplayName(query.value("displayName").toString());
acc.setType(static_cast<AccountType>(query.value(3).toInt())); account->setType(static_cast<AccountType>(query.value("type").toInt()));
acc.setAccessToken(query.value(4).toString()); account->setAccessToken(query.value("accessToken").toString());
acc.setRefreshToken(query.value(5).toString()); account->setRefreshToken(query.value("refreshToken").toString());
acc.setTokenExpires(query.value(6).toDateTime()); account->setTokenExpires(query.value("tokenExpires").toDateTime());
return acc; return account;
} }
return std::nullopt;
return nullptr;
} }
QVector<Account> AccountDao::findAll() QVector<Account> AccountDao::findAll()
@@ -119,7 +119,7 @@ QVector<Account> AccountDao::findAll()
return accounts; return accounts;
} }
std::optional<Account> AccountDao::findByEmail(const QString& email) Account* AccountDao::findByEmail(const QString& email)
{ {
QSqlDatabase& db = DatabaseManager::instance().database(); QSqlDatabase& db = DatabaseManager::instance().database();
QSqlQuery query(db); QSqlQuery query(db);
@@ -128,19 +128,19 @@ std::optional<Account> AccountDao::findByEmail(const QString& email)
if (!query.exec()) { if (!query.exec()) {
qWarning() << "Failed to find account by email:" << query.lastError().text(); qWarning() << "Failed to find account by email:" << query.lastError().text();
return std::nullopt; return nullptr;
} }
if (query.next()) { if (query.next()) {
Account acc; Account* acc = new Account();
acc.setId(query.value(0).toInt()); acc->setId(query.value(0).toLongLong());
acc.setEmail(query.value(1).toString()); acc->setEmail(query.value(1).toString());
acc.setDisplayName(query.value(2).toString()); acc->setDisplayName(query.value(2).toString());
acc.setType(static_cast<AccountType>(query.value(3).toInt())); acc->setType(static_cast<AccountType>(query.value(3).toInt()));
acc.setAccessToken(query.value(4).toString()); acc->setAccessToken(query.value(4).toString());
acc.setRefreshToken(query.value(5).toString()); acc->setRefreshToken(query.value(5).toString());
acc.setTokenExpires(query.value(6).toDateTime()); acc->setTokenExpires(query.value(6).toDateTime());
return acc; return acc;
} }
return std::nullopt; return nullptr;
} }
+3
View File
@@ -1,4 +1,7 @@
#include <QSqlQuery>
#include "folderdao.h" #include "folderdao.h"
#include <QSqlError>
#include <QSqlError>
#include <optional> #include <optional>
bool FolderDao::insert(const Folder& folder) bool FolderDao::insert(const Folder& folder)
+2
View File
@@ -1,3 +1,5 @@
#include <QSqlQuery>
#include <QSqlError>
#include "mailitemdao.h" #include "mailitemdao.h"
#include <optional> #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 <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include "core/translator.h" #include "core/translator.h"
#include "db/dbchangeprocessor.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@@ -13,8 +14,11 @@ int main(int argc, char *argv[])
qWarning() << "Failed to load translation"; qWarning() << "Failed to load translation";
} }
// Initialize the DbChangeProcessor to start processing events in batches
DbChangeProcessor dbChangeProcessor(&app);
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("translator", &translator); engine.rootContext()->setContextProperty("translator", static_cast<QObject*>(&translator));
const QUrl url(QStringLiteral("qrc:/main.qml")); const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
+7 -11
View File
@@ -7,6 +7,7 @@
#include <QJsonObject> #include <QJsonObject>
#include <QJsonArray> #include <QJsonArray>
#include <QDebug> #include <QDebug>
#include <QUrlQuery>
#include <QDateTime> #include <QDateTime>
#include <QTimer> #include <QTimer>
@@ -25,11 +26,6 @@ GmailSynchronizer::GmailSynchronizer(QObject* parent)
m_historyTimer->setInterval(2 * 60 * 1000); // 2 minutos m_historyTimer->setInterval(2 * 60 * 1000); // 2 minutos
} }
GmailSynchronizer::~GmailSynchronizer()
{
m_historyTimer->stop();
}
bool GmailSynchronizer::initialize(const Account& account) bool GmailSynchronizer::initialize(const Account& account)
{ {
// Guardar información de la cuenta // 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(); 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 // 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 // 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 // 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; return true;
} }
@@ -113,7 +109,7 @@ QVector<MailItem> GmailSynchronizer::fetchMailItems(const QString& folderId,
// Construir la URL para Gmail API // Construir la URL para Gmail API
// Nota: En Gmail, las carpetas se identifican por su nombre de label (ej: INBOX, DRAFTS, etc.) // 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); QString url = buildGmailUrl(endpoint);
// Parámetros de consulta // Parámetros de consulta
@@ -257,7 +253,7 @@ void GmailSynchronizer::onHistoryTimer()
// de todas las etiquetas (esto sería ineficiente en producción) // de todas las etiquetas (esto sería ineficiente en producción)
QVector<Folder> folders = getFolders(); QVector<Folder> folders = getFolders();
for (const Folder& folder : folders) { 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 GmailSynchronizer::createAuthRequest(const QString& url) const
{ {
QNetworkRequest request(QUrl(url)); QNetworkRequest request{QUrl(url)};
request.setRawHeader("Authorization", request.setRawHeader("Authorization",
QString("Bearer %1").arg(m_accessToken).toUtf8()); QString("Bearer %1").arg(m_accessToken).toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader, request.setHeader(QNetworkRequest::ContentTypeHeader,
+75 -3
View File
@@ -1,5 +1,6 @@
#include "imapsynchronizer.h" #include "imapsynchronizer.h"
#include <QDebug> #include <QDebug>
#include <cstdlib>
ImapSynchronizer::ImapSynchronizer(QObject* parent) ImapSynchronizer::ImapSynchronizer(QObject* parent)
: Synchronizer(parent) : Synchronizer(parent)
@@ -11,6 +12,15 @@ bool ImapSynchronizer::initialize(const Account& account)
Q_UNUSED(account); Q_UNUSED(account);
qDebug() << "IMAP Synchronizer initialize (stub)"; qDebug() << "IMAP Synchronizer initialize (stub)";
m_connected = true; // pretend success 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; return true;
} }
@@ -18,9 +28,36 @@ bool ImapSynchronizer::syncFolder(const Folder& folder)
{ {
Q_UNUSED(folder); Q_UNUSED(folder);
qDebug() << "IMAP Synchronizer syncFolder (stub)"; 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. // 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; return true;
} }
@@ -41,7 +78,7 @@ QVector<MailItem> ImapSynchronizer::fetchMailItems(const QString& folderId,
qDebug() << "IMAP Synchronizer fetchMailItems (stub)"; qDebug() << "IMAP Synchronizer fetchMailItems (stub)";
// Return a dummy mail item for testing // Return a dummy mail item for testing
QVector<MailItem> items; 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)); QDateTime::currentDateTime(), false, false));
return items; return items;
} }
@@ -51,6 +88,13 @@ bool ImapSynchronizer::appendMailItem(const QString& folderId, const MailItem& i
Q_UNUSED(folderId); Q_UNUSED(folderId);
Q_UNUSED(item); Q_UNUSED(item);
qDebug() << "IMAP Synchronizer appendMailItem (stub)"; 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; return true;
} }
@@ -63,6 +107,18 @@ bool ImapSynchronizer::updateMailItemFlags(const QString& folderId,
Q_UNUSED(read); Q_UNUSED(read);
Q_UNUSED(flagged); Q_UNUSED(flagged);
qDebug() << "IMAP Synchronizer updateMailItemFlags (stub)"; 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; return true;
} }
@@ -72,5 +128,21 @@ bool ImapSynchronizer::deleteMailItem(const QString& folderId,
Q_UNUSED(folderId); Q_UNUSED(folderId);
Q_UNUSED(itemUid); Q_UNUSED(itemUid);
qDebug() << "IMAP Synchronizer deleteMailItem (stub)"; 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; 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 "../synchronizer.h"
#include <QObject> #include <QObject>
#include "../../core/eventbus.h"
#include "../../core/models/account.h"
#include "../../core/models/folder.h"
#include "../../core/mailitem.h"
class ImapSynchronizer : public Synchronizer class ImapSynchronizer : public Synchronizer
{ {
@@ -31,4 +35,7 @@ private:
QString m_username; QString m_username;
QString m_password; QString m_password;
bool m_useSsl{true}; 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 <QJsonObject>
#include <QJsonArray> #include <QJsonArray>
#include <QDebug> #include <QDebug>
#include <QUrlQuery>
#include <QDateTime> #include <QDateTime>
#include <QTimer> #include <QTimer>
@@ -25,11 +26,6 @@ OutlookSynchronizer::OutlookSynchronizer(QObject* parent)
m_deltaTimer->setInterval(5 * 60 * 1000); // 5 minutos m_deltaTimer->setInterval(5 * 60 * 1000); // 5 minutos
} }
OutlookSynchronizer::~OutlookSynchronizer()
{
m_deltaTimer->stop();
}
bool OutlookSynchronizer::initialize(const Account& account) bool OutlookSynchronizer::initialize(const Account& account)
{ {
// Guardar información de la cuenta // Guardar información de la cuenta
@@ -60,10 +56,10 @@ bool OutlookSynchronizer::syncFolder(const Folder& folder)
} }
qDebug() << "Starting Outlook sync for folder:" << folder.name(); 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 // 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 // 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 // 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; return true;
} }
@@ -264,7 +260,7 @@ QString OutlookSynchronizer::buildGraphUrl(const QString& endpoint) const
QNetworkRequest OutlookSynchronizer::createAuthRequest(const QString& url) const QNetworkRequest OutlookSynchronizer::createAuthRequest(const QString& url) const
{ {
QNetworkRequest request(QUrl(url)); QNetworkRequest request{QUrl(url)};
request.setRawHeader("Authorization", request.setRawHeader("Authorization",
QString("Bearer %1").arg(m_accessToken).toUtf8()); QString("Bearer %1").arg(m_accessToken).toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader, 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