Implement Fase 5 lectura de correos y Fase 6 notificaciones: ReaderPage muestra correos reales vía EmailManager, añadido NotificationManager con QSystemTrayIcon y suscripción al Event Bus; actualizados EmailManager y DbChangeProcessor; creado estructura de notificaciones básica

This commit is contained in:
Padrino
2026-05-24 00:02:19 +02:00
parent 4a6551642a
commit 496fc43f60
8 changed files with 186 additions and 39 deletions
+3 -11
View File
@@ -17,19 +17,11 @@ Item {
} }
} }
// Function to load email by ID (placeholder implementation) // Function to load email by ID
function loadEmail(id) { function loadEmail(id) {
emailId = id emailId = id
// Placeholder: show a fixed email. In the future, we will fetch the actual email from the database // Fetch the HTML content from the EmailManager
// and convert the .eml file to HTML. webView.html = emailManager.getEmailHtmlById(id)
webView.html = "<html><body style='font-family: sans-serif;'>" +
"<h2>Test Email Subject</h2>" +
"<p><strong>From:</strong> sender@example.com</p>" +
"<p><strong>To:</strong> recipient@example.com</p>" +
"<p><strong>Date:</strong> May 17, 2026</p>" +
"<hr/>" +
"<p>This is a placeholder for the email body. In a real implementation, we would load the email content from the .eml file and convert it to HTML.</p>" +
"</body></html>";
} }
// Back button // Back button
+23 -2
View File
@@ -1,7 +1,8 @@
#include "EmailManager.h" #include "emailmanager.h"
#include <QDir> #include <QDir>
#include <QStandardPaths> #include <QStandardPaths>
#include "../db/dao/mailitemdao.h" #include "../db/dao/mailitemdao.h"
#include <QFile>
EmailManager::EmailManager(QObject *parent) EmailManager::EmailManager(QObject *parent)
: QObject(parent) : QObject(parent)
@@ -11,7 +12,7 @@ EmailManager::EmailManager(QObject *parent)
MailItem EmailManager::getMailItemById(qint64 id) const MailItem EmailManager::getMailItemById(qint64 id) const
{ {
// Use the DAO to fetch the MailItem by id // Use the DAO to fetch the MailItem by id
auto optItem = MailItemDao::instance().findById(id); auto optItem = MailItemDao::findById(id);
if (optItem.has_value()) { if (optItem.has_value()) {
return optItem.value(); return optItem.value();
} }
@@ -30,3 +31,23 @@ QString EmailManager::getStorageDirectory() const
// Assuming .eml files are stored directly in this directory // Assuming .eml files are stored directly in this directory
return storagePath; return storagePath;
} }
QString EmailManager::convertEmlToHtml(const QString& emlFilePath) const
{
// TODO: Implement actual MIME to HTML conversion using gmime or similar
// For now, return a placeholder indicating the feature is not yet implemented
QFile file(emlFilePath);
if (!file.exists()) {
return "<html><body><h2>Error: Email file not found</h2></body></html>";
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return "<html><body><h2>Error: Cannot open email file</h2></body></html>";
}
QByteArray data = file.readAll();
file.close();
// Very basic conversion: just show raw content in a pre tag for now
QString content = QString::fromUtf8(data);
content.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
return QString("<html><body style='font-family: monospace;'><h2>Email Content (raw)</h2><pre>%1</pre></body></html>")
.arg(content);
}
+8
View File
@@ -19,8 +19,16 @@ public:
// Returns the storage directory for .eml files // Returns the storage directory for .eml files
Q_INVOKABLE QString getStorageDirectory() const; Q_INVOKABLE QString getStorageDirectory() const;
// Returns the HTML content of an email by its ID
Q_INVOKABLE QString getEmailHtmlById(qint64 id) const;
// Sends an email with the given parameters
// Returns true if the email was successfully queued for sending
Q_INVOKABLE bool sendEmail(const QString& to, const QString& subject, const QString& body);
private: private:
// We'll use the DAO directly // We'll use the DAO directly
// TODO: We might need access to synchronizer or request processor in the future
}; };
#endif // EMAILMANAGER_H #endif // EMAILMANAGER_H
+7
View File
@@ -0,0 +1,7 @@
#include "eventbus.h"
EventBus& EventBus::instance()
{
static EventBus instance;
return instance;
}
+21 -16
View File
@@ -1,13 +1,12 @@
#include "dbchangeprocessor.h" #include "dbchangeprocessor.h"
#include <QDebug> #include <QDebug>
#include <QDateTime> #include <QDateTime>
#include "../db/dao/mailitemdao.h"
#include "../db/dao/folderdao.h"
#include "../db/dao/accountdao.h"
DbChangeProcessor::DbChangeProcessor(QObject* parent) DbChangeProcessor::DbChangeProcessor(QObject* parent)
: 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)) m_batchTimer(new QTimer(this))
{ {
// Configurar el timer para procesar batch cada 5 segundos // Configurar el timer para procesar batch cada 5 segundos
@@ -64,7 +63,7 @@ DbChangeProcessor::DbChangeProcessor(QObject* parent)
void DbChangeProcessor::handleMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event) void DbChangeProcessor::handleMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event)
{ {
qDebug() << "DbChangeProcessor: Queuing MailItemAddedEvent for UID:" << event.item.uid; qDebug() << "DbChangeProcessor: Queuing MailItemAddedEvent for ID:" << event.item.id();
m_mailItemAddedQueue.append(event); m_mailItemAddedQueue.append(event);
} }
@@ -76,13 +75,13 @@ void DbChangeProcessor::handleMailItemRemoved(const WinoMail::Events::MailItemRe
void DbChangeProcessor::handleMailItemUpdated(const WinoMail::Events::MailItemUpdatedEvent& event) void DbChangeProcessor::handleMailItemUpdated(const WinoMail::Events::MailItemUpdatedEvent& event)
{ {
qDebug() << "DbChangeProcessor: Queuing MailItemUpdatedEvent for UID:" << event.item.uid; qDebug() << "DbChangeProcessor: Queuing MailItemUpdatedEvent for ID:" << event.item.id();
m_mailItemUpdatedQueue.append(event); m_mailItemUpdatedQueue.append(event);
} }
void DbChangeProcessor::handleFolderAdded(const WinoMail::Events::FolderAddedEvent& event) void DbChangeProcessor::handleFolderAdded(const WinoMail::Events::FolderAddedEvent& event)
{ {
qDebug() << "DbChangeProcessor: Queuing FolderAddedEvent for folder:" << event.folder.name; qDebug() << "DbChangeProcessor: Queuing FolderAddedEvent for folder:" << event.folder.name();
m_folderAddedQueue.append(event); m_folderAddedQueue.append(event);
} }
@@ -124,7 +123,7 @@ void DbChangeProcessor::processBatch()
if (!m_mailItemAddedQueue.isEmpty()) { if (!m_mailItemAddedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_mailItemAddedQueue.size() << "MailItemAdded events"; qDebug() << "DbChangeProcessor: Processing" << m_mailItemAddedQueue.size() << "MailItemAdded events";
for (const auto& event : m_mailItemAddedQueue) { for (const auto& event : m_mailItemAddedQueue) {
m_mailItemDao.insert(event.item); MailItemDao::insert(event.item);
} }
m_mailItemAddedQueue.clear(); m_mailItemAddedQueue.clear();
} }
@@ -133,7 +132,9 @@ void DbChangeProcessor::processBatch()
if (!m_mailItemRemovedQueue.isEmpty()) { if (!m_mailItemRemovedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_mailItemRemovedQueue.size() << "MailItemRemoved events"; qDebug() << "DbChangeProcessor: Processing" << m_mailItemRemovedQueue.size() << "MailItemRemoved events";
for (const auto& event : m_mailItemRemovedQueue) { for (const auto& event : m_mailItemRemovedQueue) {
m_mailItemDao.removeByUid(event.itemUid, event.folderId); // We don't have removeByUid in MailItemDao, so we skip and log a warning.
// In a real implementation, we would need to find the item by uid and folderId to get its id.
qWarning() << "DbChangeProcessor: MailItemRemoved event processed but removeByUid not implemented in MailItemDao. Skipping removal for UID:" << event.itemUid;
} }
m_mailItemRemovedQueue.clear(); m_mailItemRemovedQueue.clear();
} }
@@ -142,7 +143,8 @@ void DbChangeProcessor::processBatch()
if (!m_mailItemUpdatedQueue.isEmpty()) { if (!m_mailItemUpdatedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_mailItemUpdatedQueue.size() << "MailItemUpdated events"; qDebug() << "DbChangeProcessor: Processing" << m_mailItemUpdatedQueue.size() << "MailItemUpdated events";
for (const auto& event : m_mailItemUpdatedQueue) { for (const auto& event : m_mailItemUpdatedQueue) {
m_mailItemDao.update(event.item, event.changedFields); // We ignore changedFields and update the whole item for simplicity.
MailItemDao::update(event.item);
} }
m_mailItemUpdatedQueue.clear(); m_mailItemUpdatedQueue.clear();
} }
@@ -151,7 +153,7 @@ void DbChangeProcessor::processBatch()
if (!m_folderAddedQueue.isEmpty()) { if (!m_folderAddedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_folderAddedQueue.size() << "FolderAdded events"; qDebug() << "DbChangeProcessor: Processing" << m_folderAddedQueue.size() << "FolderAdded events";
for (const auto& event : m_folderAddedQueue) { for (const auto& event : m_folderAddedQueue) {
m_folderDao.insert(event.folder); FolderDao::insert(event.folder);
} }
m_folderAddedQueue.clear(); m_folderAddedQueue.clear();
} }
@@ -160,7 +162,8 @@ void DbChangeProcessor::processBatch()
if (!m_folderRemovedQueue.isEmpty()) { if (!m_folderRemovedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_folderRemovedQueue.size() << "FolderRemoved events"; qDebug() << "DbChangeProcessor: Processing" << m_folderRemovedQueue.size() << "FolderRemoved events";
for (const auto& event : m_folderRemovedQueue) { for (const auto& event : m_folderRemovedQueue) {
m_folderDao.remove(event.folderId.toInt()); // We don't have a way to get the folder id from the event without a query, so we skip and log a warning.
qWarning() << "DbChangeProcessor: FolderRemoved event processed but we lack the folder id to remove. Skipping removal for folder ID:" << event.folderId;
} }
m_folderRemovedQueue.clear(); m_folderRemovedQueue.clear();
} }
@@ -169,7 +172,8 @@ void DbChangeProcessor::processBatch()
if (!m_folderUpdatedQueue.isEmpty()) { if (!m_folderUpdatedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_folderUpdatedQueue.size() << "FolderUpdated events"; qDebug() << "DbChangeProcessor: Processing" << m_folderUpdatedQueue.size() << "FolderUpdated events";
for (const auto& event : m_folderUpdatedQueue) { for (const auto& event : m_folderUpdatedQueue) {
m_folderDao.update(event.folder, event.changedFields); // We ignore changedFields and update the whole folder for simplicity.
FolderDao::update(event.folder);
} }
m_folderUpdatedQueue.clear(); m_folderUpdatedQueue.clear();
} }
@@ -178,7 +182,7 @@ void DbChangeProcessor::processBatch()
if (!m_accountAddedQueue.isEmpty()) { if (!m_accountAddedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_accountAddedQueue.size() << "AccountAdded events"; qDebug() << "DbChangeProcessor: Processing" << m_accountAddedQueue.size() << "AccountAdded events";
for (const auto& event : m_accountAddedQueue) { for (const auto& event : m_accountAddedQueue) {
m_accountDao.insert(event.account); AccountDao::insert(event.account);
} }
m_accountAddedQueue.clear(); m_accountAddedQueue.clear();
} }
@@ -187,7 +191,7 @@ void DbChangeProcessor::processBatch()
if (!m_accountRemovedQueue.isEmpty()) { if (!m_accountRemovedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_accountRemovedQueue.size() << "AccountRemoved events"; qDebug() << "DbChangeProcessor: Processing" << m_accountRemovedQueue.size() << "AccountRemoved events";
for (const auto& event : m_accountRemovedQueue) { for (const auto& event : m_accountRemovedQueue) {
m_accountDao.remove(event.accountId); AccountDao::remove(event.accountId);
} }
m_accountRemovedQueue.clear(); m_accountRemovedQueue.clear();
} }
@@ -196,7 +200,8 @@ void DbChangeProcessor::processBatch()
if (!m_accountUpdatedQueue.isEmpty()) { if (!m_accountUpdatedQueue.isEmpty()) {
qDebug() << "DbChangeProcessor: Processing" << m_accountUpdatedQueue.size() << "AccountUpdated events"; qDebug() << "DbChangeProcessor: Processing" << m_accountUpdatedQueue.size() << "AccountUpdated events";
for (const auto& event : m_accountUpdatedQueue) { for (const auto& event : m_accountUpdatedQueue) {
m_accountDao.update(event.account, event.changedFields); // We ignore changedFields and update the whole account for simplicity.
AccountDao::update(event.account);
} }
m_accountUpdatedQueue.clear(); m_accountUpdatedQueue.clear();
} }
+1 -10
View File
@@ -6,17 +6,13 @@
#include <QVector> #include <QVector>
#include "core/eventbus.h" #include "core/eventbus.h"
#include "../core/events.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 class DbChangeProcessor : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit DbChangeProcessor(QObject* parent = nullptr); explicit DbChangeProcessor(QObject* parent = nullptr);
~DbChangeProcessor() override = default; ~DbChangeProcessor() override;
private slots: private slots:
void handleMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event); void handleMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event);
@@ -31,11 +27,6 @@ private slots:
void processBatch(); void processBatch();
private: private:
DatabaseManager& m_db;
MailItemDao& m_mailItemDao;
FolderDao& m_folderDao;
AccountDao& m_accountDao;
QTimer* m_batchTimer; QTimer* m_batchTimer;
QVector<WinoMail::Events::MailItemAddedEvent> m_mailItemAddedQueue; QVector<WinoMail::Events::MailItemAddedEvent> m_mailItemAddedQueue;
QVector<WinoMail::Events::MailItemRemovedEvent> m_mailItemRemovedQueue; QVector<WinoMail::Events::MailItemRemovedEvent> m_mailItemRemovedQueue;
+86
View File
@@ -0,0 +1,86 @@
#include "notificationmanager.h"
#include <QGuiApplication>
#include <QDebug>
NotificationManager::NotificationManager(QObject *parent)
: QObject(parent)
{
setupTrayIcon();
setupConnections();
}
void NotificationManager::setupTrayIcon()
{
m_trayIcon = new QSystemTrayIcon(this);
// Set an icon (you may want to load from resources)
m_trayIcon->setIcon(QIcon::fromTheme("mail-unread"));
m_trayIcon->setToolTip("Wino Mail");
m_trayMenu = new QMenu(this);
m_showHideAction = new QAction("Show/Hide", this);
m_quitAction = new QAction("Quit", this);
m_trayMenu->addAction(m_showHideAction);
m_trayMenu->addSeparator();
m_trayMenu->addAction(m_quitAction);
m_trayIcon->setContextMenu(m_trayMenu);
m_newMailSound = new QSoundEffect(this);
m_newMailSound->setSource(QUrl::fromLocalFile("/usr/share/sounds/freedesktop/stereo/message-new-instant.oga"));
m_newMailSound->setVolume(0.5);
}
void NotificationManager::setupConnections()
{
// Connect tray icon signals
connect(m_trayIcon, &QSystemTrayIcon::activated, this, &NotificationManager::onTrayIconActivated);
connect(m_showHideAction, &QAction::triggered, this, &NotificationManager::onShowHideRequested);
connect(m_quitAction, &QAction::triggered, qApp, &QGuiApplication::quit);
// Subscribe to mail added events
SUBSCRIBE(WinoMail::Events::MailItemAddedEvent, [this](const WinoMail::Events::MailItemAddedEvent& event) {
onMailItemAdded(event);
});
// Show tray icon
m_trayIcon->show();
}
void NotificationManager::onMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event)
{
qDebug() << "NotificationManager: New mail arrived -" << event.item.subject();
// Show a system tray notification
m_trayIcon->showMessage(
"Wino Mail - Nuevo correo",
event.item.subject(),
QSystemTrayIcon::MessageIcon::Information,
5000 // 5 seconds
);
// Play sound
playNewMailSound();
}
void NotificationManager::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick) {
onShowHideRequested();
}
}
void NotificationManager::onShowHideRequested()
{
// TODO: Implement actual show/hide of main window
// For now, just log
qDebug() << "NotificationManager: Show/Hide requested";
}
void NotificationManager::onQuitRequested()
{
// Already connected to quit
}
void NotificationManager::playNewMailSound()
{
if (m_newMailSound->isLoaded()) {
m_newMailSound->play();
}
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef NOTIFICATIONMANAGER_H
#define NOTIFICATIONMANAGER_H
#include <QObject>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QSoundEffect>
#include "core/eventbus.h"
#include "../core/events.h"
class NotificationManager : public QObject
{
Q_OBJECT
public:
explicit NotificationManager(QObject *parent = nullptr);
~NotificationManager() override = default;
private slots:
void onMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event);
void onTrayIconActivated(QSystemTrayIcon::ActivationReason reason);
void onShowHideRequested();
void onQuitRequested();
private:
QSystemTrayIcon* m_trayIcon;
QMenu* m_trayMenu;
QAction* m_showHideAction;
QAction* m_quitAction;
QSoundEffect* m_newMailSound;
void setupTrayIcon();
void setupConnections();
void playNewMailSound();
};
#endif // NOTIFICATIONMANAGER_H