feat: extend sync log with skipped count and bytes transferred
Track how many emails were already up-to-date (skipped) and the approximate bytes transferred per sync cycle. SyncEmailsResult accumulates fetched/skipped/bytes across mailboxes; DB schema v11 adds emailsSkipped and bytesTransferred columns to sync_logs. SyncLogScreen shows "X new · Y up-to-date · took Zs" in the tile subtitle with full detail rows in the expansion panel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
0435129434
commit
1ab915d73a
@@ -119,3 +119,24 @@ class EmailDraft {
|
||||
this.attachmentFilePaths = const [],
|
||||
});
|
||||
}
|
||||
|
||||
class SyncEmailsResult {
|
||||
const SyncEmailsResult({
|
||||
required this.fetched,
|
||||
required this.skipped,
|
||||
required this.bytesTransferred,
|
||||
});
|
||||
|
||||
final int fetched;
|
||||
final int skipped;
|
||||
final int bytesTransferred;
|
||||
|
||||
static const zero =
|
||||
SyncEmailsResult(fetched: 0, skipped: 0, bytesTransferred: 0);
|
||||
|
||||
SyncEmailsResult operator +(SyncEmailsResult other) => SyncEmailsResult(
|
||||
fetched: fetched + other.fetched,
|
||||
skipped: skipped + other.skipped,
|
||||
bytesTransferred: bytesTransferred + other.bytesTransferred,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ abstract class EmailRepository {
|
||||
Stream<List<Email>> observeEmails(String accountId, String mailboxPath);
|
||||
Future<Email?> getEmail(String emailId);
|
||||
Future<EmailBody> getEmailBody(String emailId);
|
||||
Future<int> syncEmails(String accountId, String mailboxPath);
|
||||
Future<SyncEmailsResult> syncEmails(String accountId, String mailboxPath);
|
||||
Future<void> setFlag(
|
||||
String emailId, {
|
||||
bool? seen,
|
||||
|
||||
@@ -5,8 +5,10 @@ class SyncLogEntry {
|
||||
this.errorMessage,
|
||||
required this.protocol,
|
||||
required this.emailsFetched,
|
||||
required this.emailsSkipped,
|
||||
required this.mailboxesSynced,
|
||||
required this.pendingFlushed,
|
||||
required this.bytesTransferred,
|
||||
required this.startedAt,
|
||||
required this.finishedAt,
|
||||
});
|
||||
@@ -16,8 +18,10 @@ class SyncLogEntry {
|
||||
final String? errorMessage;
|
||||
final String protocol; // 'imap' or 'jmap'
|
||||
final int emailsFetched;
|
||||
final int emailsSkipped;
|
||||
final int mailboxesSynced;
|
||||
final int pendingFlushed;
|
||||
final int bytesTransferred;
|
||||
final DateTime startedAt;
|
||||
final DateTime finishedAt;
|
||||
|
||||
@@ -32,8 +36,10 @@ abstract class SyncLogRepository {
|
||||
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,
|
||||
});
|
||||
@@ -51,8 +57,10 @@ class NoOpSyncLogRepository implements SyncLogRepository {
|
||||
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,
|
||||
}) async {}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:enough_mail/enough_mail.dart' as imap;
|
||||
|
||||
import '../../data/imap/imap_client_factory.dart';
|
||||
import '../models/account.dart';
|
||||
import '../models/email.dart' show SyncEmailsResult;
|
||||
import '../repositories/account_repository.dart';
|
||||
import '../repositories/email_repository.dart';
|
||||
import '../repositories/mailbox_repository.dart';
|
||||
@@ -129,8 +130,10 @@ class _AccountSync implements _SyncLoop {
|
||||
success: true,
|
||||
protocol: 'imap',
|
||||
emailsFetched: stats.emailsFetched,
|
||||
emailsSkipped: stats.emailsSkipped,
|
||||
mailboxesSynced: stats.mailboxesSynced,
|
||||
pendingFlushed: stats.pendingFlushed,
|
||||
bytesTransferred: stats.bytesTransferred,
|
||||
startedAt: startedAt,
|
||||
finishedAt: DateTime.now(),
|
||||
);
|
||||
@@ -143,8 +146,10 @@ class _AccountSync implements _SyncLoop {
|
||||
errorMessage: e.toString(),
|
||||
protocol: 'imap',
|
||||
emailsFetched: 0,
|
||||
emailsSkipped: 0,
|
||||
mailboxesSynced: 0,
|
||||
pendingFlushed: 0,
|
||||
bytesTransferred: 0,
|
||||
startedAt: startedAt,
|
||||
finishedAt: DateTime.now(),
|
||||
);
|
||||
@@ -165,15 +170,17 @@ class _AccountSync implements _SyncLoop {
|
||||
await _emails.flushPendingChanges(account.id, password);
|
||||
final mailboxesSynced = await _mailboxes.syncMailboxes(account.id);
|
||||
final mailboxes = await _mailboxes.observeMailboxes(account.id).first;
|
||||
var emailsFetched = 0;
|
||||
var emailResult = SyncEmailsResult.zero;
|
||||
for (final mailbox in mailboxes) {
|
||||
if (!_running) break;
|
||||
emailsFetched += await _emails.syncEmails(account.id, mailbox.path);
|
||||
emailResult += await _emails.syncEmails(account.id, mailbox.path);
|
||||
}
|
||||
return _SyncStats(
|
||||
emailsFetched: emailsFetched,
|
||||
emailsFetched: emailResult.fetched,
|
||||
emailsSkipped: emailResult.skipped,
|
||||
mailboxesSynced: mailboxesSynced,
|
||||
pendingFlushed: pendingFlushed,
|
||||
bytesTransferred: emailResult.bytesTransferred,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -267,8 +274,10 @@ class _JmapAccountSync implements _SyncLoop {
|
||||
success: true,
|
||||
protocol: 'jmap',
|
||||
emailsFetched: stats.emailsFetched,
|
||||
emailsSkipped: stats.emailsSkipped,
|
||||
mailboxesSynced: stats.mailboxesSynced,
|
||||
pendingFlushed: stats.pendingFlushed,
|
||||
bytesTransferred: stats.bytesTransferred,
|
||||
startedAt: startedAt,
|
||||
finishedAt: DateTime.now(),
|
||||
);
|
||||
@@ -281,8 +290,10 @@ class _JmapAccountSync implements _SyncLoop {
|
||||
errorMessage: e.toString(),
|
||||
protocol: 'jmap',
|
||||
emailsFetched: 0,
|
||||
emailsSkipped: 0,
|
||||
mailboxesSynced: 0,
|
||||
pendingFlushed: 0,
|
||||
bytesTransferred: 0,
|
||||
startedAt: startedAt,
|
||||
finishedAt: DateTime.now(),
|
||||
);
|
||||
@@ -307,15 +318,17 @@ class _JmapAccountSync implements _SyncLoop {
|
||||
final mailboxesSynced = await _mailboxes.syncMailboxes(account.id);
|
||||
|
||||
final mailboxes = await _mailboxes.observeMailboxes(account.id).first;
|
||||
var emailsFetched = 0;
|
||||
var emailResult = SyncEmailsResult.zero;
|
||||
for (final mailbox in mailboxes) {
|
||||
if (!_running) break;
|
||||
emailsFetched += await _emails.syncEmails(account.id, mailbox.path);
|
||||
emailResult += await _emails.syncEmails(account.id, mailbox.path);
|
||||
}
|
||||
return _SyncStats(
|
||||
emailsFetched: emailsFetched,
|
||||
emailsFetched: emailResult.fetched,
|
||||
emailsSkipped: emailResult.skipped,
|
||||
mailboxesSynced: mailboxesSynced,
|
||||
pendingFlushed: pendingFlushed,
|
||||
bytesTransferred: emailResult.bytesTransferred,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -359,11 +372,15 @@ class _JmapAccountSync implements _SyncLoop {
|
||||
class _SyncStats {
|
||||
const _SyncStats({
|
||||
required this.emailsFetched,
|
||||
required this.emailsSkipped,
|
||||
required this.mailboxesSynced,
|
||||
required this.pendingFlushed,
|
||||
required this.bytesTransferred,
|
||||
});
|
||||
|
||||
final int emailsFetched;
|
||||
final int emailsSkipped;
|
||||
final int mailboxesSynced;
|
||||
final int pendingFlushed;
|
||||
final int bytesTransferred;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,8 @@ class SyncLogs extends Table {
|
||||
IntColumn get itemsSynced => integer().withDefault(const Constant(0))();
|
||||
IntColumn get mailboxesSynced => integer().withDefault(const Constant(0))();
|
||||
IntColumn get pendingFlushed => integer().withDefault(const Constant(0))();
|
||||
IntColumn get emailsSkipped => integer().withDefault(const Constant(0))();
|
||||
IntColumn get bytesTransferred => integer().withDefault(const Constant(0))();
|
||||
DateTimeColumn get startedAt => dateTime()();
|
||||
DateTimeColumn get finishedAt => dateTime()();
|
||||
}
|
||||
@@ -169,7 +171,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
||||
|
||||
@override
|
||||
int get schemaVersion => 10;
|
||||
int get schemaVersion => 11;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -204,6 +206,10 @@ class AppDatabase extends _$AppDatabase {
|
||||
await m.addColumn(syncLogs, syncLogs.mailboxesSynced);
|
||||
await m.addColumn(syncLogs, syncLogs.pendingFlushed);
|
||||
}
|
||||
if (from < 11) {
|
||||
await m.addColumn(syncLogs, syncLogs.emailsSkipped);
|
||||
await m.addColumn(syncLogs, syncLogs.bytesTransferred);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -208,7 +208,10 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
// ── Sync ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@override
|
||||
Future<int> syncEmails(String accountId, String mailboxPath) async {
|
||||
Future<model.SyncEmailsResult> syncEmails(
|
||||
String accountId,
|
||||
String mailboxPath,
|
||||
) async {
|
||||
final account = (await _accounts.getAccount(accountId))!;
|
||||
final password = await _accounts.getPassword(accountId);
|
||||
switch (account.type) {
|
||||
@@ -219,7 +222,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _syncEmailsImap(
|
||||
Future<model.SyncEmailsResult> _syncEmailsImap(
|
||||
account_model.Account account,
|
||||
String password,
|
||||
String mailboxPath,
|
||||
@@ -258,8 +261,9 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
.matchingSequence
|
||||
?.toList() ??
|
||||
[];
|
||||
var bytes = 0;
|
||||
if (allUids.isNotEmpty) {
|
||||
await _fetchAndUpsertImap(
|
||||
bytes = await _fetchAndUpsertImap(
|
||||
client,
|
||||
account,
|
||||
mailboxPath,
|
||||
@@ -274,17 +278,26 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
maxUid,
|
||||
highestModSeq: serverModSeq,
|
||||
);
|
||||
return allUids.length;
|
||||
return model.SyncEmailsResult(
|
||||
fetched: allUids.length,
|
||||
skipped: 0,
|
||||
bytesTransferred: bytes,
|
||||
);
|
||||
} else {
|
||||
// Incremental sync.
|
||||
final lastUid = checkpoint['lastUid'] as int;
|
||||
final storedModSeq = checkpoint['highestModSeq'] as int?;
|
||||
final totalOnServer = selectedMailbox.messagesExists;
|
||||
|
||||
// CONDSTORE fast-path: nothing has changed on the server.
|
||||
if (serverModSeq != null &&
|
||||
storedModSeq != null &&
|
||||
serverModSeq == storedModSeq) {
|
||||
return 0;
|
||||
return model.SyncEmailsResult(
|
||||
fetched: 0,
|
||||
skipped: totalOnServer,
|
||||
bytesTransferred: 0,
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch new messages.
|
||||
@@ -294,8 +307,9 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
.matchingSequence
|
||||
?.toList() ??
|
||||
[];
|
||||
var bytes = 0;
|
||||
if (newUids.isNotEmpty) {
|
||||
await _fetchAndUpsertImap(
|
||||
bytes = await _fetchAndUpsertImap(
|
||||
client,
|
||||
account,
|
||||
mailboxPath,
|
||||
@@ -324,7 +338,11 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
maxUid,
|
||||
highestModSeq: serverModSeq,
|
||||
);
|
||||
return newUids.length;
|
||||
return model.SyncEmailsResult(
|
||||
fetched: newUids.length,
|
||||
skipped: serverUids.length - newUids.length,
|
||||
bytesTransferred: bytes,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await client.logout();
|
||||
@@ -358,7 +376,8 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchAndUpsertImap(
|
||||
// Returns the total bytes transferred (sum of RFC822.SIZE for each message).
|
||||
Future<int> _fetchAndUpsertImap(
|
||||
imap.ImapClient client,
|
||||
account_model.Account account,
|
||||
String mailboxPath,
|
||||
@@ -367,12 +386,13 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
final fetch = sequence.isUidSequence
|
||||
? await client.uidFetchMessages(
|
||||
sequence,
|
||||
'(UID FLAGS ENVELOPE BODYSTRUCTURE)',
|
||||
'(UID FLAGS ENVELOPE BODYSTRUCTURE RFC822.SIZE)',
|
||||
)
|
||||
: await client.fetchMessages(
|
||||
sequence,
|
||||
'(UID FLAGS ENVELOPE BODYSTRUCTURE)',
|
||||
'(UID FLAGS ENVELOPE BODYSTRUCTURE RFC822.SIZE)',
|
||||
);
|
||||
var bytes = 0;
|
||||
for (final msg in fetch.messages) {
|
||||
final envelope = msg.envelope;
|
||||
if (envelope == null) {
|
||||
@@ -384,6 +404,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
log('IMAP: skipping message with no uid (mailbox=$mailboxPath)');
|
||||
continue;
|
||||
}
|
||||
bytes += msg.size ?? 0;
|
||||
final emailId = '${account.id}:$uid';
|
||||
await _db.into(_db.emails).insertOnConflictUpdate(
|
||||
EmailsCompanion.insert(
|
||||
@@ -403,6 +424,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
),
|
||||
);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> _loadImapCheckpoint(
|
||||
@@ -480,7 +502,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
'fetchTextBodyValues': true,
|
||||
};
|
||||
|
||||
Future<int> _syncEmailsJmap(
|
||||
Future<model.SyncEmailsResult> _syncEmailsJmap(
|
||||
account_model.Account account,
|
||||
String password,
|
||||
String mailboxJmapId,
|
||||
@@ -506,7 +528,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _jmapFullEmailSync(
|
||||
Future<model.SyncEmailsResult> _jmapFullEmailSync(
|
||||
String accountId,
|
||||
JmapClient jmap,
|
||||
String mailboxJmapId,
|
||||
@@ -514,6 +536,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
int position = 0;
|
||||
String? firstState;
|
||||
var fetched = 0;
|
||||
var bytes = 0;
|
||||
|
||||
while (true) {
|
||||
final responses = await jmap.call([
|
||||
@@ -550,7 +573,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
final getResult = _responseArgs(responses, 1, 'Email/get');
|
||||
firstState ??= getResult['state'] as String;
|
||||
final list = getResult['list'] as List<dynamic>;
|
||||
await _upsertJmapEmails(accountId, list);
|
||||
bytes += await _upsertJmapEmails(accountId, list);
|
||||
fetched += list.length;
|
||||
|
||||
position += ids.length;
|
||||
@@ -558,10 +581,14 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
}
|
||||
|
||||
await _saveSyncState(accountId, 'Email', firstState);
|
||||
return fetched;
|
||||
return model.SyncEmailsResult(
|
||||
fetched: fetched,
|
||||
skipped: 0,
|
||||
bytesTransferred: bytes,
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> _jmapIncrementalEmailSync(
|
||||
Future<model.SyncEmailsResult> _jmapIncrementalEmailSync(
|
||||
String accountId,
|
||||
JmapClient jmap,
|
||||
String sinceState,
|
||||
@@ -581,6 +608,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
final destroyed = List<String>.from(changes['destroyed'] as List? ?? []);
|
||||
|
||||
var fetched = 0;
|
||||
var bytes = 0;
|
||||
final toFetch = [...created, ...updated];
|
||||
if (toFetch.isNotEmpty) {
|
||||
final getResponses = await jmap.call([
|
||||
@@ -597,7 +625,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
]);
|
||||
final getResult = _responseArgs(getResponses, 0, 'Email/get');
|
||||
final list = getResult['list'] as List<dynamic>;
|
||||
await _upsertJmapEmails(accountId, list);
|
||||
bytes += await _upsertJmapEmails(accountId, list);
|
||||
fetched += list.length;
|
||||
}
|
||||
|
||||
@@ -608,14 +636,21 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
}
|
||||
|
||||
await _saveSyncState(accountId, 'Email', newState);
|
||||
return fetched;
|
||||
return model.SyncEmailsResult(
|
||||
fetched: fetched,
|
||||
skipped: 0,
|
||||
bytesTransferred: bytes,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _upsertJmapEmails(String accountId, List<dynamic> emails) async {
|
||||
// Returns total bytes transferred (sum of JMAP `size` fields).
|
||||
Future<int> _upsertJmapEmails(String accountId, List<dynamic> emails) async {
|
||||
var bytes = 0;
|
||||
for (final e in emails) {
|
||||
final m = e as Map<String, dynamic>;
|
||||
final jmapId = m['id'] as String;
|
||||
final dbId = '$accountId:$jmapId';
|
||||
bytes += (m['size'] as int?) ?? 0;
|
||||
|
||||
// Use first mailbox ID as the primary mailboxPath.
|
||||
final mailboxIds = m['mailboxIds'] as Map<String, dynamic>?;
|
||||
@@ -662,6 +697,7 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// Extracts text body, HTML body, and attachments JSON from a JMAP Email object
|
||||
|
||||
@@ -15,8 +15,10 @@ class SyncLogRepositoryImpl implements SyncLogRepository {
|
||||
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,
|
||||
}) async {
|
||||
@@ -27,8 +29,10 @@ class SyncLogRepositoryImpl implements SyncLogRepository {
|
||||
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,
|
||||
),
|
||||
@@ -51,8 +55,10 @@ class SyncLogRepositoryImpl implements SyncLogRepository {
|
||||
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,
|
||||
),
|
||||
|
||||
@@ -7,6 +7,13 @@ import '../../di.dart';
|
||||
|
||||
final _timeFmt = DateFormat('MMM d, HH:mm:ss');
|
||||
|
||||
String _fmtBytes(int bytes) {
|
||||
if (bytes <= 0) return '0 B';
|
||||
if (bytes < 1024) return '$bytes B';
|
||||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||||
}
|
||||
|
||||
class SyncLogScreen extends ConsumerWidget {
|
||||
const SyncLogScreen({super.key, required this.accountId});
|
||||
|
||||
@@ -63,7 +70,7 @@ class _SyncLogTile extends StatelessWidget {
|
||||
),
|
||||
subtitle: Text(
|
||||
entry.isOk
|
||||
? '${entry.emailsFetched} emails · ${entry.mailboxesSynced} mailboxes · took $durationLabel'
|
||||
? '${entry.emailsFetched} new · ${entry.emailsSkipped} up-to-date · took $durationLabel'
|
||||
: 'Error · took $durationLabel',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
@@ -82,8 +89,10 @@ class _SyncLogTile extends StatelessWidget {
|
||||
if (entry.protocol.isNotEmpty)
|
||||
_row('Protocol', entry.protocol.toUpperCase()),
|
||||
_row('Emails fetched', '${entry.emailsFetched}'),
|
||||
_row('Emails up-to-date', '${entry.emailsSkipped}'),
|
||||
_row('Mailboxes synced', '${entry.mailboxesSynced}'),
|
||||
_row('Pending changes flushed', '${entry.pendingFlushed}'),
|
||||
_row('Data transferred', _fmtBytes(entry.bytesTransferred)),
|
||||
if (entry.errorMessage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
|
||||
@@ -71,7 +71,8 @@ class _FakeEmails implements EmailRepository {
|
||||
const EmailBody(emailId: '', attachments: []);
|
||||
|
||||
@override
|
||||
Future<int> syncEmails(String a, String m) async => 0;
|
||||
Future<SyncEmailsResult> syncEmails(String a, String m) async =>
|
||||
SyncEmailsResult.zero;
|
||||
|
||||
@override
|
||||
Future<void> setFlag(String id, {bool? seen, bool? flagged}) async {}
|
||||
|
||||
@@ -71,7 +71,11 @@ class FakeEmailRepository implements EmailRepository {
|
||||
const EmailBody(emailId: '', attachments: []);
|
||||
|
||||
@override
|
||||
Future<int> syncEmails(String accountId, String mailboxPath) async => 0;
|
||||
Future<SyncEmailsResult> syncEmails(
|
||||
String accountId,
|
||||
String mailboxPath,
|
||||
) async =>
|
||||
SyncEmailsResult.zero;
|
||||
|
||||
@override
|
||||
Future<void> setFlag(String emailId, {bool? seen, bool? flagged}) async {}
|
||||
@@ -157,9 +161,12 @@ class _CountingEmailRepository extends FakeEmailRepository {
|
||||
final List<String> syncedPaths = [];
|
||||
|
||||
@override
|
||||
Future<int> syncEmails(String accountId, String mailboxPath) async {
|
||||
Future<SyncEmailsResult> syncEmails(
|
||||
String accountId,
|
||||
String mailboxPath,
|
||||
) async {
|
||||
syncedPaths.add(mailboxPath);
|
||||
return 0;
|
||||
return SyncEmailsResult.zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,10 +174,13 @@ class FailingJmapEmailRepository extends FakeEmailRepository {
|
||||
int syncCount = 0;
|
||||
|
||||
@override
|
||||
Future<int> syncEmails(String accountId, String mailboxPath) async {
|
||||
Future<SyncEmailsResult> syncEmails(
|
||||
String accountId,
|
||||
String mailboxPath,
|
||||
) async {
|
||||
syncCount++;
|
||||
if (syncCount == 1) throw Exception('simulated JMAP failure');
|
||||
return 0;
|
||||
return SyncEmailsResult.zero;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,10 @@ void main() {
|
||||
success: true,
|
||||
protocol: 'imap',
|
||||
emailsFetched: 5,
|
||||
emailsSkipped: 0,
|
||||
mailboxesSynced: 3,
|
||||
pendingFlushed: 0,
|
||||
bytesTransferred: 0,
|
||||
startedAt: start,
|
||||
finishedAt: end,
|
||||
);
|
||||
@@ -65,8 +67,10 @@ void main() {
|
||||
errorMessage: 'Connection refused',
|
||||
protocol: 'imap',
|
||||
emailsFetched: 0,
|
||||
emailsSkipped: 0,
|
||||
mailboxesSynced: 0,
|
||||
pendingFlushed: 0,
|
||||
bytesTransferred: 0,
|
||||
startedAt: start,
|
||||
finishedAt: end,
|
||||
);
|
||||
|
||||
@@ -156,7 +156,11 @@ class FakeEmailRepository implements EmailRepository {
|
||||
Future<EmailBody> getEmailBody(String emailId) async => _emailBody;
|
||||
|
||||
@override
|
||||
Future<int> syncEmails(String accountId, String mailboxPath) async => 0;
|
||||
Future<SyncEmailsResult> syncEmails(
|
||||
String accountId,
|
||||
String mailboxPath,
|
||||
) async =>
|
||||
SyncEmailsResult.zero;
|
||||
|
||||
@override
|
||||
Future<void> setFlag(String emailId, {bool? seen, bool? flagged}) async {}
|
||||
|
||||
Reference in New Issue
Block a user