Compare commits
5 Commits
2821647574
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| dcb7c52269 | |||
| c5704b78a4 | |||
| acaf741eb9 | |||
| 4fdd95c979 | |||
| c3567f93d5 |
+123
-7
@@ -7,13 +7,34 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# Find Qt components
|
||||
find_package(Qt6 COMPONENTS Core Gui Widgets Network Sql WebEngineWidgets Qml Multimedia REQUIRED)
|
||||
|
||||
# Optional: DTK Widgets for Deepin look
|
||||
option(USE_DTKWIDGET "Use DTK Widgets for Deepin look" ON)
|
||||
if(USE_DTKWIDGET)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DTKWIDGET dtkwidget)
|
||||
if(DTKWIDGET_FOUND)
|
||||
message(STATUS "Found DTKWidget: ${DTKWIDGET_VERSION}")
|
||||
add_definitions(-DDTKWIDGET_FOUND)
|
||||
include_directories(${DTKWIDGET_INCLUDE_DIRS})
|
||||
set(DTKWIDGET_LIBS ${DTKWIDGET_LIBRARIES})
|
||||
else()
|
||||
message(WARNING "DTKWidget not found, using fallback Qt widgets")
|
||||
set(USE_DTKWIDGET OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Find optional libraries (example: libetpan, gmime)
|
||||
# find_package(PkgConfig REQUIRED)
|
||||
# pkg_check_modules(LIBETPAN REQUIRED libetpan)
|
||||
# pkg_check_modules(GMIME REQUIRED gmime-2.6)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(GMIME gmime-3.0)
|
||||
|
||||
# Include directories
|
||||
include_directories(${PROJECT_SOURCE_DIR}/src)
|
||||
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
|
||||
include_directories(${DTKWIDGET_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(GMIME_FOUND)
|
||||
include_directories(${GMIME_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
# Enable automatic moc, uic, rcc
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
@@ -44,18 +65,113 @@ set(SRC_FILES
|
||||
src/core/eventbus.cpp
|
||||
src/utils/notificationmanager.cpp
|
||||
src/core/emailmanager.cpp
|
||||
src/core/emailcomposerbridge.cpp
|
||||
src/core/synchronizerprovider.cpp
|
||||
src/syncscheduler.cpp
|
||||
src/core/accountsetupdialoglauncher.cpp
|
||||
src/ui/dialogs/accountsetupdialog.cpp
|
||||
resources.qrc
|
||||
)
|
||||
|
||||
# Executable
|
||||
add_executable(wino-mail-qt ${SRC_FILES})
|
||||
|
||||
# Link Qt
|
||||
target_link_libraries(wino-mail-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::WebEngineWidgets Qt6::Qml Qt6::Multimedia)
|
||||
target_link_libraries(wino-mail-qt PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::WebEngineWidgets Qt6::Qml Qt6::Quick Qt6::Multimedia)
|
||||
|
||||
# Link DTKWidget if found
|
||||
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
|
||||
target_link_libraries(wino-mail-qt PRIVATE ${DTKWIDGET_LIBS})
|
||||
endif()
|
||||
|
||||
# If using external libs
|
||||
# target_link_libraries(wino-mail-qt PRIVATE ${LIBETPAN_LIBRARIES} ${GMIME_LIBRARIES})
|
||||
# target_include_directories(wino-mail-qt PRIVATE ${LIBETPAN_INCLUDE_DIRS} ${GMIME_INCLUDE_DIRS})
|
||||
target_link_libraries(wino-mail-qt PRIVATE ${GMIME_LIBRARIES})
|
||||
target_include_directories(wino-mail-qt PRIVATE ${GMIME_INCLUDE_DIRS})
|
||||
|
||||
# Install (optional)
|
||||
install(TARGETS wino-mail-qt DESTINATION bin)
|
||||
install(TARGETS wino-mail-qt DESTINATION bin)
|
||||
|
||||
# Enable testing
|
||||
enable_testing()
|
||||
|
||||
# Find Qt Test
|
||||
find_package(Qt6 COMPONENTS Test REQUIRED)
|
||||
|
||||
# Unit tests for DAOs
|
||||
add_executable(tests_dao_unit
|
||||
tests/unit/test_accountdao.cpp
|
||||
tests/unit/test_folderdao.cpp
|
||||
tests/unit/test_mailitemdao.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests_dao_unit
|
||||
PRIVATE Qt6::Core Qt6::Test
|
||||
)
|
||||
|
||||
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
|
||||
target_link_libraries(tests_dao_unit PRIVATE ${DTKWIDGET_LIBS})
|
||||
endif()
|
||||
if(GMIME_FOUND)
|
||||
target_link_libraries(tests_dao_unit PRIVATE ${GMIME_LIBRARIES})
|
||||
target_include_directories(tests_dao_unit PRIVATE ${GMIME_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
add_test(NAME dao_unit COMMAND tests_dao_unit)
|
||||
|
||||
# Unit tests for Translator
|
||||
add_executable(tests_translator_unit
|
||||
tests/unit/test_translator.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests_translator_unit
|
||||
PRIVATE Qt6::Core Qt6::Test
|
||||
)
|
||||
|
||||
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
|
||||
target_link_libraries(tests_translator_unit PRIVATE ${DTKWIDGET_LIBS})
|
||||
endif()
|
||||
if(GMIME_FOUND)
|
||||
target_link_libraries(tests_translator_unit PRIVATE ${GMIME_LIBRARIES})
|
||||
target_include_directories(tests_translator_unit PRIVATE ${GMIME_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
add_test(NAME translator_unit COMMAND tests_translator_unit)
|
||||
|
||||
# Unit tests for EventBus
|
||||
add_executable(tests_eventbus_unit
|
||||
tests/unit/test_eventbus.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests_eventbus_unit
|
||||
PRIVATE Qt6::Core Qt6::Test
|
||||
)
|
||||
|
||||
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
|
||||
target_link_libraries(tests_eventbus_unit PRIVATE ${DTKWIDGET_LIBS})
|
||||
endif()
|
||||
if(GMIME_FOUND)
|
||||
target_link_libraries(tests_eventbus_unit PRIVATE ${GMIME_LIBRARIES})
|
||||
target_include_directories(tests_eventbus_unit PRIVATE ${GMIME_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
add_test(NAME eventbus_unit COMMAND tests_eventbus_unit)
|
||||
|
||||
# Integration tests (require full application context)
|
||||
add_executable(tests_sync_integration
|
||||
tests/integration/test_syncscheduler.cpp
|
||||
tests/integration/test_notificationmanager.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests_sync_integration
|
||||
PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Sql Qt6::Test
|
||||
)
|
||||
|
||||
if(USE_DTKWIDGET AND DTKWIDGET_FOUND)
|
||||
target_link_libraries(tests_sync_integration PRIVATE ${DTKWIDGET_LIBS})
|
||||
endif()
|
||||
if(GMIME_FOUND)
|
||||
target_link_libraries(tests_sync_integration PRIVATE ${GMIME_LIBRARIES})
|
||||
target_include_directories(tests_sync_integration PRIVATE ${GMIME_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
add_test(NAME sync_integration COMMAND tests_sync_integration)
|
||||
|
||||
+9
-1
@@ -2,5 +2,13 @@
|
||||
<qresource prefix="/">
|
||||
<file>resources/qml/main.qml</file>
|
||||
<file>resources/qml/Shell.qml</file>
|
||||
<file>resources/qml/MailListPage.qml</file>
|
||||
<file>resources/qml/ReaderPage.qml</file>
|
||||
<file>resources/qml/ComposePage.qml</file>
|
||||
<file>resources/qml/ContactsPage.qml</file>
|
||||
<file>resources/qml/SettingsPage.qml</file>
|
||||
<file>resources/qml/CalendarPage.qml</file>
|
||||
<file>resources/qml/models/FolderModel.qml</file>
|
||||
<file>resources/translations/en_US/resources.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
</RCC>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtWebEngine 1.15
|
||||
import QtQml 2.15
|
||||
|
||||
Item {
|
||||
id: composePage
|
||||
anchors.fill: parent
|
||||
|
||||
// Expose a bridge to C++ for sending email and getting Quill content
|
||||
property var emailBridge: emailComposerBridge
|
||||
|
||||
// Back button
|
||||
Rectangle {
|
||||
anchors {
|
||||
@@ -69,17 +74,47 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Body
|
||||
// Body - WebEngineView loading editor.html with Quill
|
||||
Text {
|
||||
text: qsTr("Body:")
|
||||
font.pointSize: 14
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
}
|
||||
TextArea {
|
||||
id: bodyArea
|
||||
placeholderText: qsTr("Write your message...")
|
||||
WebEngineView {
|
||||
id: bodyWebView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
url: "qrc:/resources/web/editor.html"
|
||||
// Settings to enable webchannel
|
||||
settings.javascriptEnabled: true
|
||||
settings.localContentCanAccessRemoteUrls: true
|
||||
settings.localContentCanAccessFileUrls: true
|
||||
}
|
||||
|
||||
// Attachments area (simple list placeholder)
|
||||
RowLayout {
|
||||
id: attachmentsRow
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
// Will be populated dynamically via C++ model or JS array
|
||||
// For now, placeholder
|
||||
Text {
|
||||
text: qsTr("Attachments: ")
|
||||
font.pointSize: 12
|
||||
}
|
||||
Text {
|
||||
id: attachmentsText
|
||||
text: qsTr("None")
|
||||
font.pointSize: 12
|
||||
color: "#666"
|
||||
}
|
||||
Button {
|
||||
text: qsTr("Attach")
|
||||
onClicked: {
|
||||
// Call C++ to open file dialog and get selected files
|
||||
emailBridge.openAttachmentDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send button
|
||||
@@ -93,10 +128,11 @@ Item {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
// TODO: Implement sending email
|
||||
console.log("Sending email to:", toField.text, "subject:", subjectField.text)
|
||||
// For now, just go back
|
||||
StackView.view.pop()
|
||||
// Trigger sending via bridge
|
||||
emailBridge.sendComposedEmail(
|
||||
toField.text,
|
||||
subjectField.text
|
||||
)
|
||||
}
|
||||
}
|
||||
Text {
|
||||
|
||||
@@ -9,11 +9,183 @@ Item {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#fafafa"
|
||||
Text {
|
||||
text: qsTr("Settings Page - Placeholder")
|
||||
anchors.centerIn: parent
|
||||
font.pointSize: 16
|
||||
color: "#666"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 20
|
||||
|
||||
// Accounts section
|
||||
GroupBox {
|
||||
title: qsTr("Email Accounts")
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 200
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 15
|
||||
spacing: 10
|
||||
|
||||
// Accounts list placeholder
|
||||
ListView {
|
||||
id: accountsListView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: 0 // Placeholder - would be connected to actual accounts model
|
||||
delegate: Item {
|
||||
height: 60
|
||||
width: parent.width
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#ffffff"
|
||||
radius: 4
|
||||
border.color: "#e0e0e0"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 10
|
||||
|
||||
Image {
|
||||
source: "account-outline.svg"
|
||||
width: 24
|
||||
height: 24
|
||||
color: "#1976D2"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
text: "example@email.com"
|
||||
font.bold: true
|
||||
color: "#333"
|
||||
}
|
||||
Label {
|
||||
text: "Outlook • john.doe@example.com"
|
||||
font.pointSize: 10
|
||||
color: "#666"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "edit"
|
||||
iconSize: 20
|
||||
color: "#666"
|
||||
onClicked: {
|
||||
// Would open edit account dialog
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "delete"
|
||||
iconSize: 20
|
||||
color: "#f44336"
|
||||
onClicked: {
|
||||
// Would delete account
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add account button
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Button {
|
||||
text: qsTr("Add Email Account")
|
||||
icon: "plus"
|
||||
Layout.fillWidth: true
|
||||
height: 40
|
||||
onClicked: {
|
||||
accountSetupDialogLauncher.showDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// General settings section
|
||||
GroupBox {
|
||||
title: qsTr("General Settings")
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 15
|
||||
spacing: 15
|
||||
|
||||
Switch {
|
||||
text: qsTr("Start application on login")
|
||||
checked: true
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: qsTr("Enable notifications")
|
||||
checked: true
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: qsTr("Minimize to tray on close")
|
||||
checked: true
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Sync Interval:")
|
||||
font.bold: true
|
||||
}
|
||||
RowLayout {
|
||||
ComboBox {
|
||||
width: 100
|
||||
model: [15, 30, 60, 120]
|
||||
textRole: "display"
|
||||
currentIndex: 1 // 30 minutes
|
||||
}
|
||||
Label {
|
||||
text: qsTr("minutes")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Appearance section
|
||||
GroupBox {
|
||||
title: qsTr("Appearance")
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 15
|
||||
spacing: 15
|
||||
|
||||
Label {
|
||||
text: qsTr("Theme:")
|
||||
font.bold: true
|
||||
}
|
||||
RowLayout {
|
||||
RadioButton {
|
||||
text: qsTr("Light")
|
||||
checked: true
|
||||
}
|
||||
RadioButton {
|
||||
text: qsTr("Dark")
|
||||
}
|
||||
RadioButton {
|
||||
text: qsTr("System")
|
||||
}
|
||||
}
|
||||
|
||||
Switch {
|
||||
text: qsTr("Use Deepin theme")
|
||||
checked: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
-34
@@ -1,3 +1,20 @@
|
||||
#include <gmime/gmime.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Undefine macros that conflict with Qt's signals/slots
|
||||
#ifdef public
|
||||
#undef public
|
||||
#endif
|
||||
#ifdef signals
|
||||
#undef signals
|
||||
#endif
|
||||
#ifdef slots
|
||||
#undef slots
|
||||
#endif
|
||||
#ifdef emit
|
||||
#undef emit
|
||||
#endif
|
||||
|
||||
#include "emailmanager.h"
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
@@ -5,6 +22,45 @@
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
|
||||
/* Helper: recursively find a part with given subtype */
|
||||
static GMimeObject *find_part_by_subtype(GMimeObject *obj, const char *subtype)
|
||||
{
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
GMimeContentType *ctype = g_mime_object_get_content_type(obj);
|
||||
if (ctype &&
|
||||
g_ascii_strcasecmp(g_mime_content_type_get_media_subtype(ctype), subtype) == 0)
|
||||
return obj;
|
||||
|
||||
if (GMIME_IS_MULTIPART(obj)) {
|
||||
GMimeMultipart *multipart = GMIME_MULTIPART(obj);
|
||||
guint count = g_mime_multipart_get_count(multipart);
|
||||
for (guint i = 0; i < count; ++i) {
|
||||
GMimeObject *part = g_mime_multipart_get_part(multipart, i);
|
||||
GMimeObject *found = find_part_by_subtype(part, subtype);
|
||||
if (found)
|
||||
return found;
|
||||
g_object_unref(part);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Helper: extract content as QString */
|
||||
static QString extract_content_as_string(GMimeObject *obj)
|
||||
{
|
||||
GMimeStream *stream = g_mime_stream_mem_new();
|
||||
g_mime_object_write_to_stream(obj, nullptr, stream);
|
||||
GMimeStreamMem *mem = GMIME_STREAM_MEM(stream);
|
||||
GByteArray *array = g_mime_stream_mem_get_byte_array(mem);
|
||||
const char *data = reinterpret_cast<const char*>(array->data);
|
||||
gsize size = array->len;
|
||||
QString result = QString::fromUtf8(data, size);
|
||||
g_object_unref(stream);
|
||||
return result;
|
||||
}
|
||||
|
||||
EmailManager::EmailManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@@ -12,70 +68,86 @@ EmailManager::EmailManager(QObject *parent)
|
||||
|
||||
MailItem EmailManager::getMailItemById(qint64 id) const
|
||||
{
|
||||
// Use the DAO to fetch the MailItem by id
|
||||
auto optItem = MailItemDao::findById(id);
|
||||
if (optItem.has_value()) {
|
||||
return optItem.value();
|
||||
}
|
||||
// Return an empty MailItem if not found
|
||||
return MailItem();
|
||||
return optItem.has_value() ? optItem.value() : MailItem();
|
||||
}
|
||||
|
||||
QString EmailManager::getStorageDirectory() const
|
||||
{
|
||||
// Define where .eml files are stored (e.g., in the application data directory)
|
||||
QString storagePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
QDir dir(storagePath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath("."); // create if it doesn't exist
|
||||
}
|
||||
// Assuming .eml files are stored directly in this directory
|
||||
if (!dir.exists())
|
||||
dir.mkpath(".");
|
||||
return storagePath;
|
||||
}
|
||||
|
||||
QString EmailManager::getEmailHtmlById(qint64 id) const
|
||||
{
|
||||
// Get the MailItem by id
|
||||
MailItem item = getMailItemById(id);
|
||||
if (item.id() == 0) {
|
||||
if (item.id() == 0)
|
||||
return "<html><body><h2>Error: Email not found</h2></body></html>";
|
||||
}
|
||||
// Construct the file path: storage directory + fileId + .eml
|
||||
|
||||
QString storageDir = getStorageDirectory();
|
||||
QString fileName = item.fileId();
|
||||
if (fileName.isEmpty()) {
|
||||
if (fileName.isEmpty())
|
||||
return "<html><body><h2>Error: Email file ID is empty</h2></body></html>";
|
||||
}
|
||||
|
||||
QString filePath = storageDir + QDir::separator() + fileName + ".eml";
|
||||
// Convert the .eml file to HTML
|
||||
return convertEmlToHtml(filePath);
|
||||
}
|
||||
|
||||
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>";
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
g_mime_init();
|
||||
initialized = true;
|
||||
}
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
|
||||
FILE *fp = fopen(emlFilePath.toLocal8Bit().constData(), "r");
|
||||
if (!fp)
|
||||
return "<html><body><h2>Error: Cannot open email file</h2></body></html>";
|
||||
|
||||
GMimeStream *istream = g_mime_stream_fs_new(fileno(fp));
|
||||
GMimeParser *parser = g_mime_parser_new_with_stream(istream);
|
||||
GMimeMessage *message = g_mime_parser_construct_message(parser, nullptr);
|
||||
g_object_unref(parser);
|
||||
g_object_unref(istream);
|
||||
fclose(fp);
|
||||
|
||||
if (!message)
|
||||
return "<html><body><h2>Error: Failed to parse email message</h2></body></html>";
|
||||
|
||||
/* Try to get HTML part */
|
||||
GMimeObject *htmlPart = find_part_by_subtype(GMIME_OBJECT(message), "html");
|
||||
if (htmlPart) {
|
||||
QString html = extract_content_as_string(htmlPart);
|
||||
g_object_unref(htmlPart);
|
||||
g_object_unref(message);
|
||||
return 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("&", "&").replace("<", "<").replace(">", ">");
|
||||
return QString("<html><body style='font-family: monospace;'><h2>Email Content (raw)</h2><pre>%1</pre></body></html>")
|
||||
.arg(content);
|
||||
|
||||
/* Fallback to plain text */
|
||||
GMimeObject *textPart = find_part_by_subtype(GMIME_OBJECT(message), "plain");
|
||||
if (textPart) {
|
||||
QString plain = extract_content_as_string(textPart);
|
||||
g_object_unref(textPart);
|
||||
g_object_unref(message);
|
||||
QString escaped = plain;
|
||||
escaped.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||
return QString("<html><body style='font-family:monospace;'><pre>%1</pre></body></html>")
|
||||
.arg(escaped);
|
||||
}
|
||||
|
||||
/* No displayable part */
|
||||
g_object_unref(message);
|
||||
return "<html><body><h2>Error: No displayable content in email</h2></body></html>";
|
||||
}
|
||||
|
||||
bool EmailManager::sendEmail(const QString& to, const QString& subject, const QString& body)
|
||||
{
|
||||
// TODO: Implement actual email sending
|
||||
// For now, just show a message and return true
|
||||
QMessageBox::information(nullptr, "Send Email",
|
||||
/* TODO: Implement real sending via RequestProcessor */
|
||||
QMessageBox::information(nullptr, "Send Email",
|
||||
QString("To: %1\nSubject: %2\nBody: %3").arg(to, subject, body));
|
||||
return true;
|
||||
}
|
||||
@@ -15,7 +15,7 @@ bool Translator::loadLanguage(const QString& langCode)
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_translations.clear();
|
||||
|
||||
QString filePath = QStringLiteral("resources/translations/%1/resources.json").arg(langCode);
|
||||
QString filePath = QStringLiteral(":/resources/translations/%1/resources.json").arg(langCode);
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qWarning() << "Failed to open translation file:" << filePath;
|
||||
|
||||
+26
-1
@@ -4,8 +4,10 @@
|
||||
#include "core/translator.h"
|
||||
#include "db/dbchangeprocessor.h"
|
||||
#include "core/emailmanager.h"
|
||||
#include "core/emailcomposerbridge.h"
|
||||
#include "syncscheduler.h"
|
||||
#include "utils/notificationmanager.h"
|
||||
#include "core/accountsetupdialoglauncher.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
@@ -23,6 +25,9 @@ int main(int argc, char *argv[])
|
||||
// Create EmailManager to expose to QML
|
||||
EmailManager emailManager(&app);
|
||||
|
||||
// Create EmailComposerBridge to expose to QML
|
||||
EmailComposerBridge emailComposerBridge(&app);
|
||||
|
||||
// Create and start the sync scheduler
|
||||
SyncScheduler syncScheduler(&app);
|
||||
syncScheduler.start();
|
||||
@@ -31,13 +36,18 @@ int main(int argc, char *argv[])
|
||||
NotificationManager notificationManager(&app);
|
||||
notificationManager.initialize(); // Initialize Qt components after QApplication is ready
|
||||
|
||||
// Create AccountSetupDialogLauncher to expose to QML
|
||||
AccountSetupDialogLauncher accountSetupDialogLauncher(&app);
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
engine.rootContext()->setContextProperty("translator", static_cast<QObject*>(&translator));
|
||||
engine.rootContext()->setContextProperty("emailManager", &emailManager);
|
||||
engine.rootContext()->setContextProperty("emailComposerBridge", &emailComposerBridge);
|
||||
engine.rootContext()->setContextProperty("syncScheduler", &syncScheduler);
|
||||
engine.rootContext()->setContextProperty("notificationManager", ¬ificationManager);
|
||||
engine.rootContext()->setContextProperty("accountSetupDialogLauncher", &accountSetupDialogLauncher);
|
||||
|
||||
const QUrl url(QStringLiteral("qrc:/main.qml"));
|
||||
const QUrl url(QStringLiteral("qrc:/resources/qml/main.qml"));
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
|
||||
&app, [url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl)
|
||||
@@ -45,5 +55,20 @@ int main(int argc, char *argv[])
|
||||
}, Qt::QueuedConnection);
|
||||
engine.load(url);
|
||||
|
||||
// After loading QML, get the root object to connect the notificationManager's showHideRequested signal
|
||||
QObject *rootObject = nullptr;
|
||||
if (!engine.rootObjects().isEmpty()) {
|
||||
rootObject = engine.rootObjects().first();
|
||||
}
|
||||
|
||||
// Connect the notificationManager's showHideRequested signal to toggle the root object's visibility
|
||||
QObject::connect(¬ificationManager, &NotificationManager::showHideRequested,
|
||||
[rootObject]() {
|
||||
if (rootObject) {
|
||||
bool visible = rootObject->property("visible").toBool();
|
||||
rootObject->setProperty("visible", !visible);
|
||||
}
|
||||
});
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <QUrlQuery>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
#include <QEventLoop>
|
||||
#include "../../core/events.h"
|
||||
#include "../../core/eventbus.h"
|
||||
|
||||
GmailSynchronizer::GmailSynchronizer(QObject* parent)
|
||||
: Synchronizer(parent),
|
||||
@@ -41,6 +44,14 @@ bool GmailSynchronizer::initialize(const Account& account)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Publish AccountConnectedEvent
|
||||
WinoMail::Events::AccountConnectedEvent event;
|
||||
event.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
event.timestamp = QDateTime::currentDateTimeUtc();
|
||||
event.accountId = account.id();
|
||||
event.provider = "gmail";
|
||||
PUBLISH(event);
|
||||
|
||||
qDebug() << "Gmail Synchronizer initialized for account:" << account.email();
|
||||
return true;
|
||||
}
|
||||
@@ -56,6 +67,15 @@ bool GmailSynchronizer::syncFolder(const Folder& folder)
|
||||
}
|
||||
|
||||
qDebug() << "Starting Gmail sync for folder (label):" << folder.name();
|
||||
|
||||
// Publish SyncStartedEvent
|
||||
WinoMail::Events::SyncStartedEvent syncStartEvent;
|
||||
syncStartEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
syncStartEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
syncStartEvent.accountId = m_account.id();
|
||||
syncStartEvent.folderId = QString::number(folder.id());
|
||||
PUBLISH(syncStartEvent);
|
||||
|
||||
emit folderSyncStarted(QString::number(folder.id()));
|
||||
|
||||
// Obtener elementos de correo desde Gmail API
|
||||
@@ -68,10 +88,27 @@ bool GmailSynchronizer::syncFolder(const Folder& folder)
|
||||
if (!items.isEmpty()) {
|
||||
for (const MailItem& item : items) {
|
||||
emit mailItemAdded(item);
|
||||
// Publish MailItemAddedEvent
|
||||
WinoMail::Events::MailItemAddedEvent mailEvent;
|
||||
mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
mailEvent.item = item;
|
||||
PUBLISH(mailEvent);
|
||||
}
|
||||
}
|
||||
|
||||
emit folderSyncFinished(QString::number(folder.id()), true);
|
||||
|
||||
// Publish SyncFinishedEvent
|
||||
WinoMail::Events::SyncFinishedEvent syncFinishEvent;
|
||||
syncFinishEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
syncFinishEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
syncFinishEvent.accountId = m_account.id();
|
||||
syncFinishEvent.folderId = QString::number(folder.id());
|
||||
syncFinishEvent.success = true;
|
||||
syncFinishEvent.errorMessage = "";
|
||||
PUBLISH(syncFinishEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -154,6 +191,14 @@ bool GmailSynchronizer::appendMailItem(const QString& folderId, const MailItem&
|
||||
|
||||
// Por ahora, simulamos éxito
|
||||
emit mailItemAdded(item);
|
||||
|
||||
// Publish MailItemAddedEvent
|
||||
WinoMail::Events::MailItemAddedEvent mailEvent;
|
||||
mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
mailEvent.item = item;
|
||||
PUBLISH(mailEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -182,6 +227,14 @@ bool GmailSynchronizer::updateMailItemFlags(const QString& folderId,
|
||||
updatedItem.setFlagged(flagged);
|
||||
emit mailItemUpdated(updatedItem);
|
||||
|
||||
// Publish MailItemUpdatedEvent
|
||||
WinoMail::Events::MailItemUpdatedEvent updateEvent;
|
||||
updateEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
updateEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
updateEvent.item = updatedItem;
|
||||
updateEvent.changedFields = QStringList() << "read" << "flagged"; // Simplified
|
||||
PUBLISH(updateEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -202,6 +255,15 @@ bool GmailSynchronizer::deleteMailItem(const QString& folderId,
|
||||
|
||||
// Por ahora, simulamos éxito
|
||||
emit mailItemRemoved(itemUid);
|
||||
|
||||
// Publish MailItemRemovedEvent
|
||||
WinoMail::Events::MailItemRemovedEvent removeEvent;
|
||||
removeEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
removeEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
removeEvent.itemUid = itemUid;
|
||||
removeEvent.folderId = folderId.toInt();
|
||||
PUBLISH(removeEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <QUrlQuery>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
#include <QEventLoop>
|
||||
#include "../../core/events.h"
|
||||
#include "../../core/eventbus.h"
|
||||
|
||||
OutlookSynchronizer::OutlookSynchronizer(QObject* parent)
|
||||
: Synchronizer(parent),
|
||||
@@ -41,6 +44,14 @@ bool OutlookSynchronizer::initialize(const Account& account)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Publish AccountConnectedEvent
|
||||
WinoMail::Events::AccountConnectedEvent event;
|
||||
event.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
event.timestamp = QDateTime::currentDateTimeUtc();
|
||||
event.accountId = account.id();
|
||||
event.provider = "outlook";
|
||||
PUBLISH(event);
|
||||
|
||||
qDebug() << "Outlook Synchronizer initialized for account:" << account.email();
|
||||
return true;
|
||||
}
|
||||
@@ -56,6 +67,15 @@ bool OutlookSynchronizer::syncFolder(const Folder& folder)
|
||||
}
|
||||
|
||||
qDebug() << "Starting Outlook sync for folder:" << folder.name();
|
||||
|
||||
// Publish SyncStartedEvent
|
||||
WinoMail::Events::SyncStartedEvent syncStartEvent;
|
||||
syncStartEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
syncStartEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
syncStartEvent.accountId = m_account.id();
|
||||
syncStartEvent.folderId = QString::number(folder.id());
|
||||
PUBLISH(syncStartEvent);
|
||||
|
||||
emit folderSyncStarted(QString::number(folder.id()));
|
||||
|
||||
// Obtener elementos de correo desde Microsoft Graph
|
||||
@@ -68,10 +88,27 @@ bool OutlookSynchronizer::syncFolder(const Folder& folder)
|
||||
if (!items.isEmpty()) {
|
||||
for (const MailItem& item : items) {
|
||||
emit mailItemAdded(item);
|
||||
// Publish MailItemAddedEvent
|
||||
WinoMail::Events::MailItemAddedEvent mailEvent;
|
||||
mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
mailEvent.item = item;
|
||||
PUBLISH(mailEvent);
|
||||
}
|
||||
}
|
||||
|
||||
emit folderSyncFinished(QString::number(folder.id()), true);
|
||||
|
||||
// Publish SyncFinishedEvent
|
||||
WinoMail::Events::SyncFinishedEvent syncFinishEvent;
|
||||
syncFinishEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
syncFinishEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
syncFinishEvent.accountId = m_account.id();
|
||||
syncFinishEvent.folderId = QString::number(folder.id());
|
||||
syncFinishEvent.success = true;
|
||||
syncFinishEvent.errorMessage = "";
|
||||
PUBLISH(syncFinishEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -150,6 +187,14 @@ bool OutlookSynchronizer::appendMailItem(const QString& folderId, const MailItem
|
||||
|
||||
// Por ahora, simulamos éxito
|
||||
emit mailItemAdded(item);
|
||||
|
||||
// Publish MailItemAddedEvent
|
||||
WinoMail::Events::MailItemAddedEvent mailEvent;
|
||||
mailEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
mailEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
mailEvent.item = item;
|
||||
PUBLISH(mailEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -178,6 +223,14 @@ bool OutlookSynchronizer::updateMailItemFlags(const QString& folderId,
|
||||
updatedItem.setFlagged(flagged);
|
||||
emit mailItemUpdated(updatedItem);
|
||||
|
||||
// Publish MailItemUpdatedEvent
|
||||
WinoMail::Events::MailItemUpdatedEvent updateEvent;
|
||||
updateEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
updateEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
updateEvent.item = updatedItem;
|
||||
updateEvent.changedFields = QStringList() << "read" << "flagged"; // Simplified
|
||||
PUBLISH(updateEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -198,6 +251,15 @@ bool OutlookSynchronizer::deleteMailItem(const QString& folderId,
|
||||
|
||||
// Por ahora, simulamos éxito
|
||||
emit mailItemRemoved(itemUid);
|
||||
|
||||
// Publish MailItemRemovedEvent
|
||||
WinoMail::Events::MailItemRemovedEvent removeEvent;
|
||||
removeEvent.eventId = QString::number(QDateTime::currentMSecsSinceEpoch()) + "_" + QString::number(rand());
|
||||
removeEvent.timestamp = QDateTime::currentDateTimeUtc();
|
||||
removeEvent.itemUid = itemUid;
|
||||
removeEvent.folderId = folderId.toInt();
|
||||
PUBLISH(removeEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -273,9 +335,66 @@ bool OutlookSynchronizer::refreshAccessToken()
|
||||
// En una implementación real, aquí usaríamos el refresh token
|
||||
// para obtener un nuevo access token desde Azure AD
|
||||
|
||||
qWarning() << "Token refresh not implemented - using stub";
|
||||
// Simulamos éxito por ahora
|
||||
return !m_refreshToken.isEmpty();
|
||||
if (m_refreshToken.isEmpty()) {
|
||||
qWarning() << "No refresh token available for Outlook";
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: These should come from secure configuration, not hardcoded
|
||||
const QString clientId = "YOUR_CLIENT_ID_HERE"; // Replace with actual client ID
|
||||
const QString clientSecret = "YOUR_CLIENT_SECRET_HERE"; // Replace with actual client secret
|
||||
const QString tokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
|
||||
|
||||
QNetworkRequest networkRequest{QUrl(tokenUrl)};
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QUrlQuery postData;
|
||||
postData.addQueryItem("client_id", clientId);
|
||||
postData.addQueryItem("scope", "Mail.Read Mail.ReadWrite Mail.Send offline_access");
|
||||
postData.addQueryItem("refresh_token", m_refreshToken);
|
||||
postData.addQueryItem("grant_type", "refresh_token");
|
||||
postData.addQueryItem("client_secret", clientSecret);
|
||||
|
||||
QNetworkReply* reply = m_networkManager->post(networkRequest, postData.toString(QUrl::FullyEncoded).toUtf8());
|
||||
|
||||
// Wait for reply synchronously for simplicity in this context
|
||||
QEventLoop loop;
|
||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "Outlook token refresh failed:" << reply->errorString();
|
||||
reply->deleteLater();
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray responseData = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(responseData, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Failed to parse Outlook token response:" << parseError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject obj = doc.object();
|
||||
if (!obj.contains("access_token")) {
|
||||
qWarning() << "No access_token in Outlook token response";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_accessToken = obj["access_token"].toString();
|
||||
int expiresIn = obj["expires_in"].toInt(3600); // default 1 hour
|
||||
m_tokenExpires = QDateTime::currentDateTimeUtc().addSecs(expiresIn);
|
||||
|
||||
// Update refresh token if provided (sometimes rotation is used)
|
||||
if (obj.contains("refresh_token")) {
|
||||
m_refreshToken = obj["refresh_token"].toString();
|
||||
}
|
||||
|
||||
qDebug() << "Outlook access token refreshed successfully, expires in" << expiresIn << "seconds";
|
||||
return true;
|
||||
}
|
||||
|
||||
QString OutlookSynchronizer::getDeltaLink(const QString& folderId) const
|
||||
|
||||
+48
-18
@@ -1,5 +1,11 @@
|
||||
#include "syncscheduler.h"
|
||||
#include <QDebug>
|
||||
#include <QSettings>
|
||||
#include "db/dao/accountdao.h"
|
||||
#include "core/synchronizerprovider.h"
|
||||
#include "services/synchronizer.h"
|
||||
#include "core/eventbus.h"
|
||||
#include "core/events.h"
|
||||
|
||||
SyncScheduler::SyncScheduler(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -47,21 +53,45 @@ void SyncScheduler::onTimerTick()
|
||||
void SyncScheduler::performSync()
|
||||
{
|
||||
qDebug() << "SyncScheduler: Performing synchronization...";
|
||||
// Trigger a manual sync by publishing a SyncRequestedEvent or calling synchronizers directly.
|
||||
// For now, we'll publish a generic event that synchronizers can listen to.
|
||||
// We'll create a simple event; but we don't have a specific event for manual sync trigger.
|
||||
// Instead, we can invoke each synchronizer's syncFolder for all folders? That would be heavy.
|
||||
// Following the plan, we want to trigger sync for all accounts.
|
||||
// We'll create a SyncRequestedEvent and publish it.
|
||||
// However, we don't have such event defined. We can extend events.h, but to keep it simple,
|
||||
// we can just call the synchronizers via a central place? Not ideal.
|
||||
// Given time, we'll just log and later implement.
|
||||
qDebug() << "SyncScheduler: Sync trigger would happen here.";
|
||||
// Update last sync timestamp
|
||||
QSettings settings;
|
||||
qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
|
||||
settings.setValue("lastSyncTimestamp", now);
|
||||
qDebug() << "SyncScheduler: Updated last sync timestamp to" << now;
|
||||
bool syncPerformed = false;
|
||||
|
||||
// Get all accounts
|
||||
QVector<Account> accounts = AccountDao::findAll();
|
||||
for (const Account& account : accounts) {
|
||||
// Get the synchronizer for this account
|
||||
Synchronizer* synchronizer = SynchronizerProvider::instance().getSynchronizer(QString::number(account.id()));
|
||||
if (!synchronizer) {
|
||||
qWarning() << "SyncScheduler: No synchronizer found for account ID" << account.id();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Optionally, we could initialize the synchronizer if not already initialized.
|
||||
// However, we assume it's already initialized and registered (e.g., when account was added).
|
||||
// If we want to be safe, we can try to initialize it here.
|
||||
// But note: the synchronizer might have been initialized with a different account instance?
|
||||
// We'll skip initialization for now and rely on the synchronizer being ready.
|
||||
|
||||
// Get folders for this account and sync each one
|
||||
QVector<Folder> folders = synchronizer->getFolders();
|
||||
for (const Folder& folder : folders) {
|
||||
qDebug() << "SyncScheduler: Syncing folder" << folder.name() << "for account ID" << account.id();
|
||||
bool success = synchronizer->syncFolder(folder);
|
||||
if (!success) {
|
||||
qWarning() << "SyncScheduler: Failed to sync folder" << folder.name() << "for account ID" << account.id();
|
||||
}
|
||||
syncPerformed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (syncPerformed) {
|
||||
// Update last sync timestamp only if we actually performed a sync
|
||||
QSettings settings;
|
||||
qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
|
||||
settings.setValue("lastSyncTimestamp", now);
|
||||
qDebug() << "SyncScheduler: Updated last sync timestamp to" << now;
|
||||
} else {
|
||||
qDebug() << "SyncScheduler: No accounts found or no synchronizers available.";
|
||||
}
|
||||
}
|
||||
|
||||
bool SyncScheduler::shouldRunSync() const
|
||||
@@ -75,9 +105,9 @@ bool SyncScheduler::shouldRunSync() const
|
||||
return false;
|
||||
}
|
||||
// Check if current hour >= m_runHour (UTC? we'll use local time for simplicity)
|
||||
QDateTime nowLocal = QDateTime::currentDateTime();
|
||||
if (nowLocal.time().hour() < m_runHour) {
|
||||
qDebug() << "SyncScheduler: Current hour" << nowLocal.time().hour() << "is less than run hour" << m_runHour;
|
||||
QDateTime nowUtc = QDateTime::currentDateTimeUtc();
|
||||
if (nowUtc.time().hour() < m_runHour) {
|
||||
qDebug() << "SyncScheduler: Current UTC hour" << nowUtc.time().hour() << "is less than run hour" << m_runHour;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -9,6 +9,7 @@ NotificationManager::NotificationManager(QObject *parent)
|
||||
, m_showHideAction(nullptr)
|
||||
, m_quitAction(nullptr)
|
||||
, m_newMailSound(nullptr)
|
||||
, m_errorSound(nullptr)
|
||||
{
|
||||
// Defer initialization to after QApplication is fully ready
|
||||
// We'll initialize in a separate method called from main after the event loop starts?
|
||||
@@ -22,8 +23,8 @@ NotificationManager::~NotificationManager()
|
||||
if (m_trayMenu) {
|
||||
delete m_trayMenu;
|
||||
}
|
||||
// Note: m_trayIcon, m_showHideAction, m_quitAction, m_newMailSound are children of this or m_trayMenu?
|
||||
// Actually, m_trayIcon and m_newMailSound have 'this' as parent, so they will be deleted automatically.
|
||||
// Note: m_trayIcon, m_showHideAction, m_quitAction, m_newMailSound, m_errorSound are children of this or m_trayMenu?
|
||||
// Actually, m_trayIcon and m_newMailSound, m_errorSound have 'this' as parent, so they will be deleted automatically.
|
||||
// m_showHideAction and m_quitAction have m_trayMenu as parent, so they will be deleted when m_trayMenu is deleted.
|
||||
}
|
||||
|
||||
@@ -31,10 +32,11 @@ void NotificationManager::initialize()
|
||||
{
|
||||
// Initialize the tray icon and related objects
|
||||
m_trayIcon = new QSystemTrayIcon(this);
|
||||
m_trayMenu = new QMenu(this); // parent to NotificationManager so it gets deleted with us
|
||||
m_trayMenu = new QMenu(); // parent to NotificationManager so it gets deleted with us
|
||||
m_showHideAction = new QAction("Show/Hide", m_trayMenu);
|
||||
m_quitAction = new QAction("Quit", m_trayMenu);
|
||||
m_newMailSound = new QSoundEffect(this);
|
||||
m_errorSound = new QSoundEffect(this);
|
||||
|
||||
setupTrayIcon();
|
||||
setupConnections();
|
||||
@@ -53,6 +55,9 @@ void NotificationManager::setupTrayIcon()
|
||||
|
||||
m_newMailSound->setSource(QUrl::fromLocalFile("/usr/share/sounds/freedesktop/stereo/message-new-instant.oga"));
|
||||
m_newMailSound->setVolume(0.5);
|
||||
|
||||
m_errorSound->setSource(QUrl::fromLocalFile("/usr/share/sounds/freedesktop/stereo/dialog-error.oga"));
|
||||
m_errorSound->setVolume(0.5);
|
||||
}
|
||||
|
||||
void NotificationManager::setupConnections()
|
||||
@@ -62,10 +67,31 @@ void NotificationManager::setupConnections()
|
||||
connect(m_showHideAction, &QAction::triggered, this, &NotificationManager::onShowHideRequested);
|
||||
connect(m_quitAction, &QAction::triggered, qApp, &QGuiApplication::quit);
|
||||
|
||||
// Subscribe to mail added events
|
||||
// Subscribe to events
|
||||
SUBSCRIBE(WinoMail::Events::MailItemAddedEvent, [this](const WinoMail::Events::MailItemAddedEvent& event) {
|
||||
onMailItemAdded(event);
|
||||
});
|
||||
SUBSCRIBE(WinoMail::Events::MailItemRemovedEvent, [this](const WinoMail::Events::MailItemRemovedEvent& event) {
|
||||
onMailItemRemoved(event);
|
||||
});
|
||||
SUBSCRIBE(WinoMail::Events::MailItemUpdatedEvent, [this](const WinoMail::Events::MailItemUpdatedEvent& event) {
|
||||
onMailItemUpdated(event);
|
||||
});
|
||||
SUBSCRIBE(WinoMail::Events::AccountConnectedEvent, [this](const WinoMail::Events::AccountConnectedEvent& event) {
|
||||
onAccountConnected(event);
|
||||
});
|
||||
SUBSCRIBE(WinoMail::Events::AccountDisconnectedEvent, [this](const WinoMail::Events::AccountDisconnectedEvent& event) {
|
||||
onAccountDisconnected(event);
|
||||
});
|
||||
SUBSCRIBE(WinoMail::Events::SyncStartedEvent, [this](const WinoMail::Events::SyncStartedEvent& event) {
|
||||
onSyncStarted(event);
|
||||
});
|
||||
SUBSCRIBE(WinoMail::Events::SyncFinishedEvent, [this](const WinoMail::Events::SyncFinishedEvent& event) {
|
||||
onSyncFinished(event);
|
||||
});
|
||||
SUBSCRIBE(WinoMail::Events::ErrorEvent, [this](const WinoMail::Events::ErrorEvent& event) {
|
||||
onErrorOccurred(event);
|
||||
});
|
||||
|
||||
// Show tray icon
|
||||
m_trayIcon->show();
|
||||
@@ -85,6 +111,98 @@ void NotificationManager::onMailItemAdded(const WinoMail::Events::MailItemAddedE
|
||||
playNewMailSound();
|
||||
}
|
||||
|
||||
void NotificationManager::onMailItemRemoved(const WinoMail::Events::MailItemRemovedEvent& event)
|
||||
{
|
||||
qDebug() << "NotificationManager: Mail removed - UID:" << event.itemUid;
|
||||
// Show a system tray notification
|
||||
m_trayIcon->showMessage(
|
||||
"Wino Mail - Correo eliminado",
|
||||
QString("Correo con UID %1 eliminado").arg(event.itemUid),
|
||||
QSystemTrayIcon::MessageIcon::Information,
|
||||
3000 // 3 seconds
|
||||
);
|
||||
}
|
||||
|
||||
void NotificationManager::onMailItemUpdated(const WinoMail::Events::MailItemUpdatedEvent& event)
|
||||
{
|
||||
qDebug() << "NotificationManager: Mail updated - UID:" << event.item.id() << "Changed fields:" << event.changedFields;
|
||||
// Show a system tray notification for significant changes (like read/unread)
|
||||
if (event.changedFields.contains("read") || event.changedFields.contains("flagged")) {
|
||||
m_trayIcon->showMessage(
|
||||
"Wino Mail - Correo actualizado",
|
||||
QString("Correo %1 actualizado").arg(event.item.subject()),
|
||||
QSystemTrayIcon::MessageIcon::Information,
|
||||
3000 // 3 seconds
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::onAccountConnected(const WinoMail::Events::AccountConnectedEvent& event)
|
||||
{
|
||||
qDebug() << "NotificationManager: Account connected - ID:" << event.accountId << "Provider:" << event.provider;
|
||||
// Show a system tray notification
|
||||
m_trayIcon->showMessage(
|
||||
"Wino Mail - Cuenta conectada",
|
||||
QString("Cuenta %1 conectada (%2)").arg(event.accountId).arg(event.provider),
|
||||
QSystemTrayIcon::MessageIcon::Information,
|
||||
3000 // 3 seconds
|
||||
);
|
||||
}
|
||||
|
||||
void NotificationManager::onAccountDisconnected(const WinoMail::Events::AccountDisconnectedEvent& event)
|
||||
{
|
||||
qDebug() << "NotificationManager: Account disconnected - ID:" << event.accountId << "Provider:" << event.provider << "Reason:" << event.reason;
|
||||
// Show a system tray notification
|
||||
m_trayIcon->showMessage(
|
||||
"Wino Mail - Cuenta desconectada",
|
||||
QString("Cuenta %1 desconectada (%2): %3").arg(event.accountId).arg(event.provider).arg(event.reason),
|
||||
QSystemTrayIcon::MessageIcon::Warning,
|
||||
5000 // 5 seconds
|
||||
);
|
||||
// Play error sound for disconnection
|
||||
playErrorSound();
|
||||
}
|
||||
|
||||
void NotificationManager::onSyncStarted(const WinoMail::Events::SyncStartedEvent& event)
|
||||
{
|
||||
qDebug() << "NotificationManager: Sync started - Account ID:" << event.accountId << "Folder ID:" << event.folderId;
|
||||
// Optionally show a subtle notification or just log
|
||||
// For now, we'll just log and not show a notification to avoid being too noisy
|
||||
}
|
||||
|
||||
void NotificationManager::onSyncFinished(const WinoMail::Events::SyncFinishedEvent& event)
|
||||
{
|
||||
qDebug() << "NotificationManager: Sync finished - Account ID:" << event.accountId << "Folder ID:" << event.folderId << "Success:" << event.success;
|
||||
if (!event.success) {
|
||||
// Show error notification and play sound
|
||||
m_trayIcon->showMessage(
|
||||
"Wino Mail - Error de sincronización",
|
||||
QString("Error al sincronizar cuenta %1, carpeta %2: %3").arg(event.accountId).arg(event.folderId).arg(event.errorMessage),
|
||||
QSystemTrayIcon::MessageIcon::Critical,
|
||||
5000 // 5 seconds
|
||||
);
|
||||
playErrorSound();
|
||||
} else {
|
||||
// Optionally show success notification
|
||||
// m_trayIcon->showMessage("Wino Mail - Sincronización completada", QString("Sincronización completada para cuenta %1, carpeta %2").arg(event.accountId).arg(event.folderId), QSystemTrayIcon::MessageIcon::Information, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::onErrorOccurred(const WinoMail::Events::ErrorEvent& event)
|
||||
{
|
||||
qDebug() << "NotificationManager: Error occurred - Source:" << event.source << "Message:" << event.message << "Critical:" << event.isCritical;
|
||||
// Show a system tray notification for critical errors
|
||||
if (event.isCritical) {
|
||||
m_trayIcon->showMessage(
|
||||
"Wino Mail - Error crítico",
|
||||
QString("Error en %1: %2").arg(event.source).arg(event.message),
|
||||
QSystemTrayIcon::MessageIcon::Critical,
|
||||
5000 // 5 seconds
|
||||
);
|
||||
playErrorSound();
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason)
|
||||
{
|
||||
if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick) {
|
||||
@@ -94,9 +212,8 @@ void NotificationManager::onTrayIconActivated(QSystemTrayIcon::ActivationReason
|
||||
|
||||
void NotificationManager::onShowHideRequested()
|
||||
{
|
||||
// TODO: Implement actual show/hide of main window
|
||||
// For now, just log
|
||||
qDebug() << "NotificationManager: Show/Hide requested";
|
||||
// Emit signal to toggle main window visibility
|
||||
Q_EMIT showHideRequested();
|
||||
}
|
||||
|
||||
void NotificationManager::onQuitRequested()
|
||||
@@ -109,4 +226,16 @@ void NotificationManager::playNewMailSound()
|
||||
if (m_newMailSound->isLoaded()) {
|
||||
m_newMailSound->play();
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::playErrorSound()
|
||||
{
|
||||
if (m_errorSound->isLoaded()) {
|
||||
m_errorSound->play();
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::showNotification(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon, int timeout)
|
||||
{
|
||||
m_trayIcon->showMessage(title, message, icon, timeout);
|
||||
}
|
||||
@@ -18,8 +18,18 @@ public:
|
||||
~NotificationManager() override;
|
||||
void initialize(); // Initialize Qt components after QApplication is ready
|
||||
|
||||
signals:
|
||||
void showHideRequested();
|
||||
|
||||
private slots:
|
||||
void onMailItemAdded(const WinoMail::Events::MailItemAddedEvent& event);
|
||||
void onMailItemRemoved(const WinoMail::Events::MailItemRemovedEvent& event);
|
||||
void onMailItemUpdated(const WinoMail::Events::MailItemUpdatedEvent& event);
|
||||
void onAccountConnected(const WinoMail::Events::AccountConnectedEvent& event);
|
||||
void onAccountDisconnected(const WinoMail::Events::AccountDisconnectedEvent& event);
|
||||
void onSyncStarted(const WinoMail::Events::SyncStartedEvent& event);
|
||||
void onSyncFinished(const WinoMail::Events::SyncFinishedEvent& event);
|
||||
void onErrorOccurred(const WinoMail::Events::ErrorEvent& event);
|
||||
void onTrayIconActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
void onShowHideRequested();
|
||||
void onQuitRequested();
|
||||
@@ -30,10 +40,13 @@ private:
|
||||
QAction* m_showHideAction;
|
||||
QAction* m_quitAction;
|
||||
QSoundEffect* m_newMailSound;
|
||||
QSoundEffect* m_errorSound;
|
||||
|
||||
void setupTrayIcon();
|
||||
void setupConnections();
|
||||
void playNewMailSound();
|
||||
void playErrorSound();
|
||||
void showNotification(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::Information, int timeout = 5000);
|
||||
};
|
||||
|
||||
#endif // NOTIFICATIONMANAGER_H
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <QtTest/QtTest>
|
||||
#include "../../src/syncscheduler.h"
|
||||
|
||||
class TestSyncScheduler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testShouldRunSync();
|
||||
};
|
||||
|
||||
void TestSyncScheduler::testShouldRunSync()
|
||||
{
|
||||
SyncScheduler scheduler;
|
||||
// This test would need to mock QSettings and QDateTime
|
||||
// For now, we just verify the object can be created
|
||||
QVERIFY(true);
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestSyncScheduler)
|
||||
#include "test_syncscheduler.moc"
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <QtTest/QtTest>
|
||||
#include "../../src/db/dao/accountdao.h"
|
||||
#include "../../src/db/databasemanager.h"
|
||||
#include "../../src/core/models/account.h"
|
||||
|
||||
class TestAccountDao : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void testInsertAndFind();
|
||||
void testUpdate();
|
||||
void testRemove();
|
||||
};
|
||||
|
||||
void TestAccountDao::initTestCase()
|
||||
{
|
||||
DatabaseManager::instance().openDatabase(":memory:");
|
||||
DatabaseManager::instance().createTables();
|
||||
}
|
||||
|
||||
void TestAccountDao::cleanupTestCase()
|
||||
{
|
||||
DatabaseManager::instance().closeDatabase();
|
||||
}
|
||||
|
||||
void TestAccountDao::testInsertAndFind()
|
||||
{
|
||||
Account account;
|
||||
account.setEmail("test@example.com");
|
||||
account.setProvider("IMAP");
|
||||
account.setHost("imap.example.com");
|
||||
account.setPort(993);
|
||||
account.setUsername("testuser");
|
||||
account.setPassword("testpass");
|
||||
account.setUseSsl(true);
|
||||
|
||||
bool inserted = AccountDao::insert(account);
|
||||
QVERIFY(inserted);
|
||||
QVERIFY(account.id() > 0);
|
||||
|
||||
Account found = AccountDao::findById(account.id());
|
||||
QVERIFY(found.isValid());
|
||||
QCOMPARE(found.email(), QString("test@example.com"));
|
||||
QCOMPARE(found.provider(), QString("IMAP"));
|
||||
QCOMPARE(found.host(), QString("imap.example.com"));
|
||||
QCOMPARE(found.port(), 993);
|
||||
QCOMPARE(found.username(), QString("testuser"));
|
||||
QCOMPARE(found.password(), QString("testpass"));
|
||||
QVERIFY(found.useSsl());
|
||||
}
|
||||
|
||||
void TestAccountDao::testUpdate()
|
||||
{
|
||||
Account account;
|
||||
account.setEmail("original@example.com");
|
||||
account.setProvider("IMAP");
|
||||
account.setHost("imap.example.com");
|
||||
account.setPort(993);
|
||||
account.setUsername("user");
|
||||
account.setPassword("pass");
|
||||
account.setUseSsl(true);
|
||||
|
||||
bool inserted = AccountDao::insert(account);
|
||||
QVERIFY(inserted);
|
||||
qint64 id = account.id();
|
||||
|
||||
account.setEmail("updated@example.com");
|
||||
account.setProvider("Gmail");
|
||||
account.setUseSsl(false);
|
||||
|
||||
bool updated = AccountDao::update(account);
|
||||
QVERIFY(updated);
|
||||
|
||||
Account found = AccountDao::findById(id);
|
||||
QVERIFY(found.isValid());
|
||||
QCOMPARE(found.email(), QString("updated@example.com"));
|
||||
QCOMPARE(found.provider(), QString("Gmail"));
|
||||
QVERIFY(!found.useSsl());
|
||||
}
|
||||
|
||||
void TestAccountDao::testRemove()
|
||||
{
|
||||
Account account;
|
||||
account.setEmail("todelete@example.com");
|
||||
account.setProvider("IMAP");
|
||||
account.setHost("imap.example.com");
|
||||
account.setPort(993);
|
||||
account.setUsername("user");
|
||||
account.setPassword("pass");
|
||||
account.setUseSsl(true);
|
||||
|
||||
bool inserted = AccountDao::insert(account);
|
||||
QVERIFY(inserted);
|
||||
qint64 id = account.id();
|
||||
|
||||
bool removed = AccountDao::remove(id);
|
||||
QVERIFY(removed);
|
||||
|
||||
Account found = AccountDao::findById(id);
|
||||
QVERIFY(!found.isValid());
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestAccountDao)
|
||||
#include "test_accountdao.moc"
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <QtTest/QtTest>
|
||||
#include "../../src/core/eventbus.h"
|
||||
#include "../../src/core/events.h"
|
||||
|
||||
class TestEventBus : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testPublishAndSubscribe();
|
||||
};
|
||||
|
||||
void TestEventBus::testPublishAndSubscribe()
|
||||
{
|
||||
bool received = false;
|
||||
int value = 0;
|
||||
|
||||
// Subscribe to an event of type int
|
||||
auto subscription = EventBus::instance().subscribe<int>([&](int data) {
|
||||
received = true;
|
||||
value = data;
|
||||
});
|
||||
|
||||
// Publish an event
|
||||
EventBus::instance().publish<int>(42);
|
||||
|
||||
// Check that we received the event
|
||||
QVERIFY(received);
|
||||
QCOMPARE(value, 42);
|
||||
|
||||
// Unsubscribe (optional, subscription goes out of scope)
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestEventBus)
|
||||
#include "test_eventbus.moc"
|
||||
@@ -0,0 +1,92 @@
|
||||
#include <QtTest/QtTest>
|
||||
#include "../../src/db/dao/folderdao.h"
|
||||
#include "../../src/db/databasemanager.h"
|
||||
#include "../../src/core/models/folder.h"
|
||||
|
||||
class TestFolderDao : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void testInsertAndFind();
|
||||
void testUpdate();
|
||||
void testRemove();
|
||||
};
|
||||
|
||||
void TestFolderDao::initTestCase()
|
||||
{
|
||||
DatabaseManager::instance().openDatabase(":memory:");
|
||||
DatabaseManager::instance().createTables();
|
||||
}
|
||||
|
||||
void TestFolderDao::cleanupTestCase()
|
||||
{
|
||||
DatabaseManager::instance().closeDatabase();
|
||||
}
|
||||
|
||||
void TestFolderDao::testInsertAndFind()
|
||||
{
|
||||
Folder folder;
|
||||
folder.setAccountId(1);
|
||||
folder.setName("Inbox");
|
||||
folder.setPath("INBOX");
|
||||
folder.setIsInbox(true);
|
||||
|
||||
bool inserted = FolderDao::insert(folder);
|
||||
QVERIFY(inserted);
|
||||
QVERIFY(folder.id() > 0);
|
||||
|
||||
Folder found = FolderDao::findById(folder.id());
|
||||
QVERIFY(found.isValid());
|
||||
QCOMPARE(found.accountId(), 1);
|
||||
QCOMPARE(found.name(), QString("Inbox"));
|
||||
QCOMPARE(found.path(), QString("INBOX"));
|
||||
QVERIFY(found.isInbox());
|
||||
}
|
||||
|
||||
void TestFolderDao::testUpdate()
|
||||
{
|
||||
Folder folder;
|
||||
folder.setAccountId(1);
|
||||
folder.setName("Drafts");
|
||||
folder.setPath("Drafts");
|
||||
folder.setIsInbox(false);
|
||||
|
||||
bool inserted = FolderDao::insert(folder);
|
||||
QVERIFY(inserted);
|
||||
qint64 id = folder.id();
|
||||
|
||||
folder.setName("Updated Drafts");
|
||||
folder.setPath("Updated/Drafts");
|
||||
|
||||
bool updated = FolderDao::update(folder);
|
||||
QVERIFY(updated);
|
||||
|
||||
Folder found = FolderDao::findById(id);
|
||||
QVERIFY(found.isValid());
|
||||
QCOMPARE(found.name(), QString("Updated Drafts"));
|
||||
QCOMPARE(found.path(), QString("Updated/Drafts"));
|
||||
}
|
||||
|
||||
void TestFolderDao::testRemove()
|
||||
{
|
||||
Folder folder;
|
||||
folder.setAccountId(1);
|
||||
folder.setName("ToDelete");
|
||||
folder.setPath("ToDelete");
|
||||
folder.setIsInbox(false);
|
||||
|
||||
bool inserted = FolderDao::insert(folder);
|
||||
QVERIFY(inserted);
|
||||
qint64 id = folder.id();
|
||||
|
||||
bool removed = FolderDao::remove(id);
|
||||
QVERIFY(removed);
|
||||
|
||||
Folder found = FolderDao::findById(id);
|
||||
QVERIFY(!found.isValid());
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestFolderDao)
|
||||
#include "test_folderdao.moc"
|
||||
@@ -0,0 +1,128 @@
|
||||
#include <QtTest/QtTest>
|
||||
#include "../../src/db/dao/mailitemdao.h"
|
||||
#include "../../src/db/databasemanager.h"
|
||||
#include "../../src/core/models/mailitem.h"
|
||||
#include "../../src/core/models/account.h"
|
||||
#include "../../src/core/models/folder.h"
|
||||
|
||||
class TestMailItemDao : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void testInsertAndFind();
|
||||
void testUpdate();
|
||||
void testRemove();
|
||||
};
|
||||
|
||||
void TestMailItemDao::initTestCase()
|
||||
{
|
||||
DatabaseManager::instance().openDatabase(":memory:");
|
||||
DatabaseManager::instance().createTables();
|
||||
// We need an account and folder for the mailitem
|
||||
Account account;
|
||||
account.setEmail("test@example.com");
|
||||
account.setProvider("IMAP");
|
||||
account.setHost("imap.example.com");
|
||||
account.setPort(993);
|
||||
account.setUsername("testuser");
|
||||
account.setPassword("testpass");
|
||||
account.setUseSsl(true);
|
||||
bool accountInserted = AccountDao::insert(account);
|
||||
QVERIFY(accountInserted);
|
||||
|
||||
Folder folder;
|
||||
folder.setAccountId(account.id());
|
||||
folder.setName("Inbox");
|
||||
folder.setPath("INBOX");
|
||||
folder.setIsInbox(true);
|
||||
bool folderInserted = FolderDao::insert(folder);
|
||||
QVERIFY(folderInserted);
|
||||
}
|
||||
|
||||
void TestMailItemDao::cleanupTestCase()
|
||||
{
|
||||
DatabaseManager::instance().closeDatabase();
|
||||
}
|
||||
|
||||
void TestMailItemDao::testInsertAndFind()
|
||||
{
|
||||
MailItem item;
|
||||
item.setAccountId(1); // from account inserted in init
|
||||
item.setFolderId(1); // from folder inserted in init
|
||||
item.setSubject("Test Subject");
|
||||
item.setFrom("sender@example.com");
|
||||
item.setTo("receiver@example.com");
|
||||
item.setDate(QDateTime::currentDateTimeUtc());
|
||||
item.setContent("Test content");
|
||||
item.setUid("12345");
|
||||
|
||||
bool inserted = MailItemDao::insert(item);
|
||||
QVERIFY(inserted);
|
||||
QVERIFY(item.id() > 0);
|
||||
|
||||
MailItem found = MailItemDao::findById(item.id());
|
||||
QVERIFY(found.isValid());
|
||||
QCOMPARE(found.accountId(), 1);
|
||||
QCOMPARE(found.folderId(), 1);
|
||||
QCOMPARE(found.subject(), QString("Test Subject"));
|
||||
QCOMPARE(found.from(), QString("sender@example.com"));
|
||||
QCOMPARE(found.to(), QString("receiver@example.com"));
|
||||
QCOMPARE(found.content(), QString("Test content"));
|
||||
QCOMPARE(found.uid(), QString("12345"));
|
||||
}
|
||||
|
||||
void TestMailItemDao::testUpdate()
|
||||
{
|
||||
MailItem item;
|
||||
item.setAccountId(1);
|
||||
item.setFolderId(1);
|
||||
item.setSubject("Original Subject");
|
||||
item.setFrom("sender@example.com");
|
||||
item.setTo("receiver@example.com");
|
||||
item.setDate(QDateTime::currentDateTimeUtc());
|
||||
item.setContent("Original content");
|
||||
item.setUid("12345");
|
||||
|
||||
bool inserted = MailItemDao::insert(item);
|
||||
QVERIFY(inserted);
|
||||
qint64 id = item.id();
|
||||
|
||||
item.setSubject("Updated Subject");
|
||||
item.setContent("Updated content");
|
||||
|
||||
bool updated = MailItemDao::update(item);
|
||||
QVERIFY(updated);
|
||||
|
||||
MailItem found = MailItemDao::findById(id);
|
||||
QVERIFY(found.isValid());
|
||||
QCOMPARE(found.subject(), QString("Updated Subject"));
|
||||
QCOMPARE(found.content(), QString("Updated content"));
|
||||
}
|
||||
|
||||
void TestMailItemDao::testRemove()
|
||||
{
|
||||
MailItem item;
|
||||
item.setAccountId(1);
|
||||
item.setFolderId(1);
|
||||
item.setSubject("To Delete");
|
||||
item.setFrom("sender@example.com");
|
||||
item.setTo("receiver@example.com");
|
||||
item.setDate(QDateTime::currentDateTimeUtc());
|
||||
item.setContent("To be deleted");
|
||||
item.setUid("12345");
|
||||
|
||||
bool inserted = MailItemDao::insert(item);
|
||||
QVERIFY(inserted);
|
||||
qint64 id = item.id();
|
||||
|
||||
bool removed = MailItemDao::remove(id);
|
||||
QVERIFY(removed);
|
||||
|
||||
MailItem found = MailItemDao::findById(id);
|
||||
QVERIFY(!found.isValid());
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestMailItemDao)
|
||||
#include "test_mailitemdao.moc"
|
||||
@@ -0,0 +1,47 @@
|
||||
#include <QtTest/QtTest>
|
||||
#include "../../src/core/translator.h"
|
||||
|
||||
class TestTranslator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testLoadLanguage();
|
||||
void testTranslation();
|
||||
void testCleanup();
|
||||
};
|
||||
|
||||
void TestTranslator::initTestCase()
|
||||
{
|
||||
// Ensure we start with a clean state (singleton may persist)
|
||||
// Since Translator is a singleton, we can reload language.
|
||||
Translator& translator = Translator::instance();
|
||||
translator.loadLanguage("en_US");
|
||||
}
|
||||
|
||||
void TestTranslator::testLoadLanguage()
|
||||
{
|
||||
Translator& translator = Translator::instance();
|
||||
QVERIFY(translator.loadLanguage("en_US"));
|
||||
QCOMPARE(translator.currentLanguage(), QString("en_US"));
|
||||
}
|
||||
|
||||
void TestTranslator::testTranslation()
|
||||
{
|
||||
Translator& translator = Translator::instance();
|
||||
// Test known keys
|
||||
QCOMPARE(translator.tr("appName"), QString("Wino Mail"));
|
||||
QCOMPARE(translator.tr("welcomeMessage"), QString("Welcome to Wino Mail"));
|
||||
QCOMPARE(translator.tr("inbox"), QString("Inbox"));
|
||||
QCOMPARE(translator.tr("settings"), QString("Settings"));
|
||||
// Test unknown key returns the key itself
|
||||
QCOMPARE(translator.tr("unknown.key"), QString("unknown.key"));
|
||||
}
|
||||
|
||||
void TestTranslator::testCleanup()
|
||||
{
|
||||
// Nothing to clean up
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestTranslator)
|
||||
#include "test_translator.moc"
|
||||
Reference in New Issue
Block a user