Implement basic UI with QML (MailListPage) and batching DbChangeProcessor (Step 3-4 of transition plan)
This commit is contained in:
+9
-2
@@ -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,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>
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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,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;
|
||||||
|
|||||||
+25
-25
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QSqlError>
|
||||||
#include "mailitemdao.h"
|
#include "mailitemdao.h"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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
@@ -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,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,
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user