Files
sharedinbox/lib/data/repositories/sync_log_repository_impl.dart
T
Thomas SharedInboxandClaude Sonnet 4.6 9763a1884a feat(sync-log): add per-mailbox timing to sync log (#104)
Track how long each mailbox takes to sync and display it in the
sync log expanded view (e.g. "2 new · 5 up-to-date · 1.3s").

- Add optional `duration` field to `MailboxSyncStats`
- Capture per-mailbox start/end time in both IMAP and JMAP sync loops
- Store as `duration_ms` in `sync_log_mailboxes` (schema v30 migration)
- Read back and reconstruct `Duration` in repository
- Show timing alongside fetch/skip counts in per-mailbox breakdown
- Extract `_fmtDuration` helper, reuse for the existing total duration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 22:03:36 +02:00

117 lines
3.9 KiB
Dart

import 'package:drift/drift.dart';
import 'package:sharedinbox/core/repositories/sync_log_repository.dart';
import 'package:sharedinbox/data/db/database.dart';
class SyncLogRepositoryImpl implements SyncLogRepository {
SyncLogRepositoryImpl(this._db);
final AppDatabase _db;
@override
Future<void> log({
required String accountId,
required bool success,
String? errorMessage,
required String protocol,
required int emailsFetched,
required int emailsSkipped,
required int mailboxesSynced,
required int pendingFlushed,
required int bytesTransferred,
required DateTime startedAt,
required DateTime finishedAt,
List<MailboxSyncStats> mailboxStats = const [],
String? protocolLog,
}) async {
await _db.transaction(() async {
final logId = await _db.into(_db.syncLogs).insert(
SyncLogsCompanion.insert(
accountId: accountId,
result: success ? 'ok' : 'error',
errorMessage: Value(errorMessage),
protocol: Value(protocol),
itemsSynced: Value(emailsFetched),
emailsSkipped: Value(emailsSkipped),
mailboxesSynced: Value(mailboxesSynced),
pendingFlushed: Value(pendingFlushed),
bytesTransferred: Value(bytesTransferred),
startedAt: startedAt,
finishedAt: finishedAt,
protocolLog: Value(protocolLog),
),
);
for (final s in mailboxStats) {
await _db.into(_db.syncLogMailboxes).insert(
SyncLogMailboxesCompanion.insert(
syncLogId: logId,
mailboxPath: s.mailboxPath,
fetched: Value(s.fetched),
skipped: Value(s.skipped),
bytesTransferred: Value(s.bytesTransferred),
durationMs: Value(s.duration?.inMilliseconds),
),
);
}
});
}
@override
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId) {
final logsQuery = _db.select(_db.syncLogs)
..where((t) => t.accountId.equals(accountId))
..orderBy([(t) => OrderingTerm.desc(t.startedAt)])
..limit(100);
return logsQuery.watch().asyncMap((rows) async {
final entries = <SyncLogEntry>[];
for (final r in rows) {
final mailboxRows = await (_db.select(_db.syncLogMailboxes)
..where((t) => t.syncLogId.equals(r.id))
..orderBy([(t) => OrderingTerm.asc(t.mailboxPath)]))
.get();
entries.add(
SyncLogEntry(
id: r.id,
result: r.result,
errorMessage: r.errorMessage,
protocol: r.protocol,
emailsFetched: r.itemsSynced,
emailsSkipped: r.emailsSkipped,
mailboxesSynced: r.mailboxesSynced,
pendingFlushed: r.pendingFlushed,
bytesTransferred: r.bytesTransferred,
startedAt: r.startedAt,
finishedAt: r.finishedAt,
protocolLog: r.protocolLog,
mailboxStats: mailboxRows
.map(
(m) => MailboxSyncStats(
mailboxPath: m.mailboxPath,
fetched: m.fetched,
skipped: m.skipped,
bytesTransferred: m.bytesTransferred,
duration: m.durationMs != null
? Duration(milliseconds: m.durationMs!)
: null,
),
)
.toList(),
),
);
}
return entries;
});
}
@override
Stream<String?> observeLastError(String accountId) {
return (_db.select(_db.syncLogs)
..where((t) => t.accountId.equals(accountId))
..orderBy([(t) => OrderingTerm.desc(t.startedAt)])
..limit(1))
.watchSingleOrNull()
.map((row) => (row?.result == 'error') ? row?.errorMessage : null);
}
}