feat: IMAP sync incremental + Account Setup Wizard + robust FETCH parser

- ImapSynchronizer::syncFolder(): detecta eliminados (UIDs locales no en servidor), fetch nuevos (sinceUid), actualiza flags (FLAGS batch)
- fetchAllUids(): SELECT + SEARCH ALL para lista completa UIDs servidor
- parseAndUpdateFlags(): FETCH FLAGS en lotes 100, update DB si cambió read/flagged
- AccountSetupDialog: integra ConnectionWizard para IMAP/POP3 con test de conexión real
- IMAP FETCH parser robusto: logging respuesta servidor + fallback UID-by-UID
- Fix: FETCH failed logging muestra first/last UID + respuesta truncada
This commit is contained in:
2026-08-17 15:34:49 +02:00
parent 364624aada
commit a004bdd60f
94 changed files with 19408 additions and 1047 deletions
+240
View File
@@ -0,0 +1,240 @@
import sys
filename = 'mainmainwindow.cpp'
with open(filename, 'r') as f:
lines = f.readlines()
# 1. Modify onFolderSelected function
# Find the start of onFolderSelected
on_folder_start = None
for i, line in enumerate(lines):
if line.strip().startswith('void MainMainWindow::onFolderSelected(const QModelIndex &index)'):
on_folder_start = i
break
if on_folder_start is None:
print('Could not find onFolderSelected')
sys.exit(1)
# Find the end of the function (look for a line that is just '}' after the function start)
# We'll assume the function ends at the next line that starts with 'void ' or '}' at indentation 0? Safer: find the matching brace.
brace_count = 0
in_function = False
on_folder_end = None
for i in range(on_folder_start, len(lines)):
line = lines[i]
for ch in line:
if ch == '{':
brace_count += 1
in_function = True
elif ch == '}':
brace_count -= 1
if brace_count == 0 and in_function:
on_folder_end = i
break
if on_folder_end is not None:
break
if on_folder_end is None:
print('Could not find end of onFolderSelected')
sys.exit(1)
# Now we need to replace the content between on_folder_start+1 and on_folder_end with new implementation.
# Let's first extract the current function to see what we have.
# We'll replace from line after the opening brace? Actually we want to keep the signature and the opening brace.
# We'll replace lines from on_folder_start+1 to on_folder_end-1 with new body.
# But we need to keep the opening brace line (which is at on_folder_start? Actually the signature line is on_folder_start, the opening brace is on the same line or next?
# Look at the signature line: it ends with '{'? Let's check.
# Let's just replace the whole block from on_folder_start to on_folder_end with new function.
new_on_folder = '''void MainMainWindow::onFolderSelected(const QModelIndex &index)
{
if (!index.isValid()) return;
int itemType = index.data(FolderListModel::ItemTypeRole).toInt();
if (itemType == FolderTreeItem::FolderNode) {
m_currentFolderId = index.data(FolderListModel::FolderIdRole).toInt();
m_emailModel->setFolderId(m_currentFolderId);
// Fetch mails for this folder
std::optional<Folder> optFolder = FolderDao::findById(m_currentFolderId);
if (optFolder.has_value()) {
Folder folder = optFolder.value();
Account* account = m_accountService->findAccountById(folder.accountId());
if (account) {
QString accountId = QString::number(account->id());
QString folderId = QString::number(m_currentFolderId);
// Fetch mails asynchronously to avoid blocking UI
QMetaObject::invokeMethod(m_mailService, "fetchMails", Qt::QueuedConnection,
Q_ARG(QString, accountId), Q_ARG(QString, folderId));
}
}
} else if (itemType == FolderTreeItem::AccountNode) {
// When an account node is selected, select its first folder (if any)
int childCount = m_folderModel->rowCount(index);
if (childCount > 0) {
QModelIndex firstChildIdx = m_folderModel->index(0, 0, index);
m_folderTree->setCurrentIndex(firstChildIdx);
onFolderSelected(firstChildIdx); // recursive call to handle folder selection
} else {
// No folders yet; clear email list
m_currentFolderId = -1;
m_emailModel->setFolderId(m_currentFolderId);
m_emailModel->refresh();
}
}
}
'''
# Replace lines
lines = lines[:on_folder_start] + [new_on_folder] + lines[on_folder_end+1:]
# 2. Modify syncAction slot
# Find the createToolBox function? Actually we need to find the connect(syncAction, ...) line.
# Let's search for 'connect(syncAction, &QAction::triggered'
sync_connect_line = None
for i, line in enumerate(lines):
if 'connect(syncAction, &QAction::triggered' in line:
sync_connect_line = i
break
if sync_connect_line is None:
print('Could not find syncAction connect')
sys.exit(1)
# Find the end of that lambda (the matching '});' after that line)
brace_count = 0
in_lambda = False
sync_end = None
for i in range(sync_connect_line, len(lines)):
line = lines[i]
for ch in line:
if ch == '{':
brace_count += 1
in_lambda = True
elif ch == '}':
brace_count -= 1
if brace_count == 0 and in_lambda:
# Check if the line contains '});' (the end of the lambda)
if '});' in line:
sync_end = i
break
if sync_end is not None:
break
if sync_end is None:
print('Could not find end of syncAction lambda')
sys.exit(1)
# Replace the lambda body with new implementation
new_sync_lambda = ''' connect(syncAction, &QAction::triggered, [this]() {
if (m_currentFolderId >= 0) {
statusBar()->showMessage(tr(\"Syncing...\"), 0); // 0 means until cleared
std::optional<Folder> optFolder = FolderDao::findById(m_currentFolderId);
if (optFolder.has_value()) {
Folder folder = optFolder.value();
Account* account = m_accountService->findAccountById(folder.accountId());
if (account) {
QString accountId = QString::number(account->id());
QString folderId = QString::number(m_currentFolderId);
// Disconnect previous connections to avoid multiple slots? We'll just call directly via queued connection.
QMetaObject::invokeMethod(m_mailService, \"fetchMails\", Qt::QueuedConnection,
Q_ARG(QString, accountId), Q_ARG(QString, folderId));
}
}
}
});'''
# Replace lines from sync_connect_line to sync_end inclusive
lines = lines[:sync_connect_line] + [new_sync_lambda] + lines[sync_end+1:]
# 3. Update mailFetched and mailFetchError lambdas in connectModels
# Find the connectModels function
connect_models_start = None
for i, line in enumerate(lines):
if line.strip().startswith('void MainMainWindow::connectModels()'):
connect_models_start = i
break
if connect_models_start is None:
print('Could not find connectModels')
sys.exit(1)
# Find end of connectModels
brace_count = 0
in_func = False
connect_models_end = None
for i in range(connect_models_start, len(lines)):
line = lines[i]
for ch in line:
if ch == '{':
brace_count += 1
in_func = True
elif ch == '}':
brace_count -= 1
if brace_count == 0 and in_func:
connect_models_end = i
break
if connect_models_end is not None:
break
if connect_models_end is None:
print('Could not find end of connectModels')
sys.exit(1)
# Within this function, we need to find the two lambdas: mailFetched and mailFetchError.
# We'll replace the whole function with a new version? That's risky.
# Instead, we'll replace the specific lambda bodies.
# Let's find the line numbers for the mailFetched connect and mailFetchError connect.
mail_fetched_line = None
mail_fetch_error_line = None
for i in range(connect_models_start, connect_models_end+1):
if 'connect(m_mailService, &MailService::mailFetched' in lines[i]:
mail_fetched_line = i
if 'connect(m_mailService, &MailService::mailFetchError' in lines[i]:
mail_fetch_error_line = i
# For each, find the end of the lambda (the '});' line)
def find_lambda_end(start_line):
brace_count = 0
in_lambda = False
for i in range(start_line, len(lines)):
line = lines[i]
for ch in line:
if ch == '{':
brace_count += 1
in_lambda = True
elif ch == '}':
brace_count -= 1
if brace_count == 0 and in_lambda:
# Check if line ends with '});'
if '});' in line:
return i
return None
mfe_end = None
mfer_end = None
if mail_fetched_line is not None:
mfe_end = find_lambda_end(mail_fetched_line)
if mail_fetch_error_line is not None:
mfer_end = find_lambda_end(mail_fetch_error_line)
if mfe_end is None or mfer_end is None:
print('Could not find lambda ends')
sys.exit(1)
# New lambda bodies
new_mfe_lambda = ''' connect(m_mailService, &MailService::mailFetched, this, [this](const QString &accountId, const QString &folderId, const QVector<MailItem> &items) {
int fid = folderId.toInt();
if (m_currentFolderId == fid || m_currentFolderId == -1) {
m_emailModel->refresh();
}
statusBar()->showMessage(tr(\"Synced %1 message(s)\").arg(items.size()), 3000);
});'''
new_mfer_lambda = ''' connect(m_mailService, &MailService::mailFetchError, this, [this](const QString &accountId, const QString &folderId, const QString &error) {
qWarning() << \"[MailFetchError]\" << error;
statusBar()->showMessage(tr(\"Error fetching mail: %1\").arg(error), 5000);
});'''
# Replace the lambdas
lines = lines[:mail_fetched_line] + [new_mfe_lambda] + lines[mfe_end+1:mail_fetch_error_line] + [new_mfer_lambda] + lines[mfer_end+1:]
# Write back
with open(filename, 'w') as f:
f.writelines(lines)
print('Updated mainmainwindow.cpp')