Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db3bf5937d |
@@ -44,6 +44,16 @@ class AccountSyncManager {
|
|||||||
StreamSubscription<List<Account>>? _accountsSub;
|
StreamSubscription<List<Account>>? _accountsSub;
|
||||||
StreamSubscription<String>? _onChangesSub;
|
StreamSubscription<String>? _onChangesSub;
|
||||||
|
|
||||||
|
final _syncPhaseCtrl = StreamController<(String, bool)>.broadcast();
|
||||||
|
|
||||||
|
/// Emits `true` when [accountId] starts syncing, `false` when it stops.
|
||||||
|
Stream<bool> watchSyncing(String accountId) =>
|
||||||
|
_syncPhaseCtrl.stream.where((e) => e.$1 == accountId).map((e) => e.$2);
|
||||||
|
|
||||||
|
void _emitSyncing(String accountId, {required bool syncing}) {
|
||||||
|
if (!_syncPhaseCtrl.isClosed) _syncPhaseCtrl.add((accountId, syncing));
|
||||||
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
_onChangesSub = _emails.onChangesQueued.listen((accountId) {
|
_onChangesSub = _emails.onChangesQueued.listen((accountId) {
|
||||||
_active[accountId]?.kick();
|
_active[accountId]?.kick();
|
||||||
@@ -54,6 +64,7 @@ class AccountSyncManager {
|
|||||||
|
|
||||||
for (final account in accounts) {
|
for (final account in accounts) {
|
||||||
if (_active.containsKey(account.id)) continue;
|
if (_active.containsKey(account.id)) continue;
|
||||||
|
final id = account.id;
|
||||||
final loop = switch (account.type) {
|
final loop = switch (account.type) {
|
||||||
AccountType.imap => _AccountSync(
|
AccountType.imap => _AccountSync(
|
||||||
account,
|
account,
|
||||||
@@ -64,6 +75,8 @@ class AccountSyncManager {
|
|||||||
_syncLog,
|
_syncLog,
|
||||||
_drafts,
|
_drafts,
|
||||||
_onNewMail,
|
_onNewMail,
|
||||||
|
onSyncStart: () => _emitSyncing(id, syncing: true),
|
||||||
|
onSyncEnd: () => _emitSyncing(id, syncing: false),
|
||||||
),
|
),
|
||||||
AccountType.jmap => _JmapAccountSync(
|
AccountType.jmap => _JmapAccountSync(
|
||||||
account,
|
account,
|
||||||
@@ -71,6 +84,8 @@ class AccountSyncManager {
|
|||||||
_emails,
|
_emails,
|
||||||
_accounts,
|
_accounts,
|
||||||
_syncLog,
|
_syncLog,
|
||||||
|
onSyncStart: () => _emitSyncing(id, syncing: true),
|
||||||
|
onSyncEnd: () => _emitSyncing(id, syncing: false),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
_active[account.id] = loop;
|
_active[account.id] = loop;
|
||||||
@@ -92,6 +107,7 @@ class AccountSyncManager {
|
|||||||
s.stop();
|
s.stop();
|
||||||
}
|
}
|
||||||
_active.clear();
|
_active.clear();
|
||||||
|
unawaited(_syncPhaseCtrl.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wakes the idle/wait phase of the given account's sync loop so a new
|
/// Wakes the idle/wait phase of the given account's sync loop so a new
|
||||||
@@ -126,6 +142,8 @@ class AccountSyncManager {
|
|||||||
_syncLog,
|
_syncLog,
|
||||||
_drafts,
|
_drafts,
|
||||||
_onNewMail,
|
_onNewMail,
|
||||||
|
onSyncStart: () => _emitSyncing(accountId, syncing: true),
|
||||||
|
onSyncEnd: () => _emitSyncing(accountId, syncing: false),
|
||||||
),
|
),
|
||||||
AccountType.jmap => _JmapAccountSync(
|
AccountType.jmap => _JmapAccountSync(
|
||||||
account,
|
account,
|
||||||
@@ -133,6 +151,8 @@ class AccountSyncManager {
|
|||||||
_emails,
|
_emails,
|
||||||
_accounts,
|
_accounts,
|
||||||
_syncLog,
|
_syncLog,
|
||||||
|
onSyncStart: () => _emitSyncing(accountId, syncing: true),
|
||||||
|
onSyncEnd: () => _emitSyncing(accountId, syncing: false),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
_active[accountId] = loop;
|
_active[accountId] = loop;
|
||||||
@@ -159,8 +179,11 @@ class _AccountSync implements _SyncLoop {
|
|||||||
this._imapConnect,
|
this._imapConnect,
|
||||||
this._syncLog,
|
this._syncLog,
|
||||||
this._drafts,
|
this._drafts,
|
||||||
this._onNewMail,
|
this._onNewMail, {
|
||||||
);
|
void Function()? onSyncStart,
|
||||||
|
void Function()? onSyncEnd,
|
||||||
|
}) : _onSyncStart = onSyncStart,
|
||||||
|
_onSyncEnd = onSyncEnd;
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final AccountRepository _accounts;
|
final AccountRepository _accounts;
|
||||||
@@ -170,6 +193,8 @@ class _AccountSync implements _SyncLoop {
|
|||||||
final SyncLogRepository _syncLog;
|
final SyncLogRepository _syncLog;
|
||||||
final DraftRepository? _drafts;
|
final DraftRepository? _drafts;
|
||||||
final OnNewMailCallback? _onNewMail;
|
final OnNewMailCallback? _onNewMail;
|
||||||
|
final void Function()? _onSyncStart;
|
||||||
|
final void Function()? _onSyncEnd;
|
||||||
|
|
||||||
imap.ImapClient? _idleClient;
|
imap.ImapClient? _idleClient;
|
||||||
bool _running = false;
|
bool _running = false;
|
||||||
@@ -202,6 +227,7 @@ class _AccountSync implements _SyncLoop {
|
|||||||
Future<void> _loop() async {
|
Future<void> _loop() async {
|
||||||
while (_running) {
|
while (_running) {
|
||||||
final startedAt = DateTime.now();
|
final startedAt = DateTime.now();
|
||||||
|
_onSyncStart?.call();
|
||||||
try {
|
try {
|
||||||
final (_SyncStats stats, String? capturedLog) = await _runSync(
|
final (_SyncStats stats, String? capturedLog) = await _runSync(
|
||||||
account.verbose,
|
account.verbose,
|
||||||
@@ -221,8 +247,10 @@ class _AccountSync implements _SyncLoop {
|
|||||||
protocolLog: capturedLog,
|
protocolLog: capturedLog,
|
||||||
);
|
);
|
||||||
_backoffSeconds = 5;
|
_backoffSeconds = 5;
|
||||||
|
_onSyncEnd?.call();
|
||||||
await _idle();
|
await _idle();
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
_onSyncEnd?.call();
|
||||||
final isPermanent = _isPermanentError(e);
|
final isPermanent = _isPermanentError(e);
|
||||||
try {
|
try {
|
||||||
await _syncLog.log(
|
await _syncLog.log(
|
||||||
@@ -392,14 +420,19 @@ class _JmapAccountSync implements _SyncLoop {
|
|||||||
this._mailboxes,
|
this._mailboxes,
|
||||||
this._emails,
|
this._emails,
|
||||||
this._accounts,
|
this._accounts,
|
||||||
this._syncLog,
|
this._syncLog, {
|
||||||
);
|
void Function()? onSyncStart,
|
||||||
|
void Function()? onSyncEnd,
|
||||||
|
}) : _onSyncStart = onSyncStart,
|
||||||
|
_onSyncEnd = onSyncEnd;
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final MailboxRepository _mailboxes;
|
final MailboxRepository _mailboxes;
|
||||||
final EmailRepository _emails;
|
final EmailRepository _emails;
|
||||||
final AccountRepository _accounts;
|
final AccountRepository _accounts;
|
||||||
final SyncLogRepository _syncLog;
|
final SyncLogRepository _syncLog;
|
||||||
|
final void Function()? _onSyncStart;
|
||||||
|
final void Function()? _onSyncEnd;
|
||||||
|
|
||||||
bool _running = false;
|
bool _running = false;
|
||||||
int _backoffSeconds = 5;
|
int _backoffSeconds = 5;
|
||||||
@@ -431,6 +464,7 @@ class _JmapAccountSync implements _SyncLoop {
|
|||||||
Future<void> _loop() async {
|
Future<void> _loop() async {
|
||||||
while (_running) {
|
while (_running) {
|
||||||
final startedAt = DateTime.now();
|
final startedAt = DateTime.now();
|
||||||
|
_onSyncStart?.call();
|
||||||
try {
|
try {
|
||||||
final (_SyncStats stats, String? capturedLog) = await _runSync(
|
final (_SyncStats stats, String? capturedLog) = await _runSync(
|
||||||
account.verbose,
|
account.verbose,
|
||||||
@@ -450,8 +484,10 @@ class _JmapAccountSync implements _SyncLoop {
|
|||||||
protocolLog: capturedLog,
|
protocolLog: capturedLog,
|
||||||
);
|
);
|
||||||
_backoffSeconds = 5;
|
_backoffSeconds = 5;
|
||||||
|
_onSyncEnd?.call();
|
||||||
await _wait();
|
await _wait();
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
_onSyncEnd?.call();
|
||||||
final isPermanent = _isPermanentError(e);
|
final isPermanent = _isPermanentError(e);
|
||||||
try {
|
try {
|
||||||
await _syncLog.log(
|
await _syncLog.log(
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ final syncHealthProvider =
|
|||||||
.watchSingleOrNull();
|
.watchSingleOrNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final isSyncingProvider =
|
||||||
|
StreamProvider.autoDispose.family<bool, String>((ref, accountId) {
|
||||||
|
return ref.watch(syncManagerProvider).watchSyncing(accountId);
|
||||||
|
});
|
||||||
|
|
||||||
final syncManagerProvider = Provider<AccountSyncManager>((ref) {
|
final syncManagerProvider = Provider<AccountSyncManager>((ref) {
|
||||||
final manager = AccountSyncManager(
|
final manager = AccountSyncManager(
|
||||||
ref.watch(accountRepositoryProvider),
|
ref.watch(accountRepositoryProvider),
|
||||||
|
|||||||
@@ -180,22 +180,7 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
_buildSyncButton(emailRepo),
|
||||||
icon: const Icon(Icons.sync),
|
|
||||||
onPressed: () async {
|
|
||||||
try {
|
|
||||||
await emailRepo.syncEmails(
|
|
||||||
widget.accountId,
|
|
||||||
widget.mailboxPath,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text('Sync failed: $e')));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
onPressed: () => context.push(
|
onPressed: () => context.push(
|
||||||
@@ -229,6 +214,44 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildSyncButton(EmailRepository emailRepo) {
|
||||||
|
final isSyncing =
|
||||||
|
ref.watch(isSyncingProvider(widget.accountId)).valueOrNull ?? false;
|
||||||
|
final hasError =
|
||||||
|
ref.watch(syncLastErrorProvider(widget.accountId)).valueOrNull != null;
|
||||||
|
return IconButton(
|
||||||
|
tooltip: isSyncing
|
||||||
|
? 'Syncing…'
|
||||||
|
: hasError
|
||||||
|
? 'Sync error'
|
||||||
|
: 'Sync',
|
||||||
|
icon: isSyncing
|
||||||
|
? const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: hasError
|
||||||
|
? const Icon(Icons.sync_problem, color: Colors.red)
|
||||||
|
: const Icon(Icons.sync),
|
||||||
|
onPressed: isSyncing
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
try {
|
||||||
|
await emailRepo.syncEmails(
|
||||||
|
widget.accountId,
|
||||||
|
widget.mailboxPath,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Sync failed: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _selectionBottomBar() {
|
Widget _selectionBottomBar() {
|
||||||
return BottomAppBar(
|
return BottomAppBar(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
Reference in New Issue
Block a user