Closes #373 ## Summary - **Schema v38**: two new columns on `user_preferences` — `prefetch_mode` (default `wifiOnly`) and `body_cache_limit_mb` (default 100 MB). - **`BodyCacheService`**: queries for emails that have no cached body, fetches them newest-first in batches of 20, and evicts the oldest cached bodies when the configured size limit is exceeded. - **Separate WorkManager task** (`si_bg_prefetch`): runs hourly with `NetworkType.unmetered` (Wi-Fi) or `NetworkType.connected` (any) depending on the user's choice. The task is cancelled when prefetch is disabled. - **App startup**: reads the stored preference from the DB and re-registers the WorkManager task with the correct constraint. - **Preferences screen**: radio group for prefetch mode (Wi-Fi only / Any network / Disabled) and a dropdown for cache size limit (50 / 100 / 200 / 500 MB). ## What is NOT downloaded Binary attachments are never fetched — `getEmailBody()` stores only `textBody` and `htmlBody`. The cache size limit + per-run batch cap (20 emails) keep storage bounded even on large mailboxes. ## Test plan - [x] `task analyze` — no issues - [x] `task test` — all 492 tests pass (incl. updated migration_test.dart for v38) Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de> Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/400
118 lines
3.7 KiB
Dart
118 lines
3.7 KiB
Dart
import 'package:drift/drift.dart';
|
|
import 'package:sharedinbox/core/models/user_preferences.dart' as pref;
|
|
import 'package:sharedinbox/core/repositories/user_preferences_repository.dart';
|
|
import 'package:sharedinbox/data/db/database.dart';
|
|
|
|
class UserPreferencesRepositoryImpl implements UserPreferencesRepository {
|
|
UserPreferencesRepositoryImpl(this._db);
|
|
|
|
final AppDatabase _db;
|
|
static const _rowId = 1;
|
|
|
|
@override
|
|
Stream<pref.UserPreferences> observePreferences() {
|
|
return (_db.select(
|
|
_db.userPreferences,
|
|
)..where((t) => t.id.equals(_rowId)))
|
|
.watchSingleOrNull()
|
|
.map(_rowToModel);
|
|
}
|
|
|
|
@override
|
|
Future<void> updateMenuPosition(pref.MenuPosition position) async {
|
|
await _db.into(_db.userPreferences).insertOnConflictUpdate(
|
|
UserPreferencesCompanion(
|
|
id: const Value(_rowId),
|
|
menuPosition: Value(position.name),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> updateMailViewButtonPosition(pref.MenuPosition position) async {
|
|
await _db.into(_db.userPreferences).insertOnConflictUpdate(
|
|
UserPreferencesCompanion(
|
|
id: const Value(_rowId),
|
|
mailViewButtonPosition: Value(position.name),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> updateAfterMailViewAction(
|
|
pref.AfterMailViewAction action,
|
|
) async {
|
|
await _db.into(_db.userPreferences).insertOnConflictUpdate(
|
|
UserPreferencesCompanion(
|
|
id: const Value(_rowId),
|
|
afterMailViewAction: Value(action.name),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> updatePrefetchMode(pref.PrefetchMode mode) async {
|
|
await _db.into(_db.userPreferences).insertOnConflictUpdate(
|
|
UserPreferencesCompanion(
|
|
id: const Value(_rowId),
|
|
prefetchMode: Value(mode.name),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> updateBodyCacheLimitMb(int mb) async {
|
|
await _db.into(_db.userPreferences).insertOnConflictUpdate(
|
|
UserPreferencesCompanion(
|
|
id: const Value(_rowId),
|
|
bodyCacheLimitMb: Value(mb),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Stream<List<String>> observeTrustedImageSenders() {
|
|
return (_db.select(_db.imageTrustedSenders)
|
|
..orderBy([(t) => OrderingTerm.desc(t.addedAt)]))
|
|
.watch()
|
|
.map((rows) => rows.map((r) => r.senderEmail).toList());
|
|
}
|
|
|
|
@override
|
|
Future<void> addTrustedImageSender(String senderEmail) async {
|
|
await _db.into(_db.imageTrustedSenders).insertOnConflictUpdate(
|
|
ImageTrustedSendersCompanion(
|
|
senderEmail: Value(senderEmail.toLowerCase()),
|
|
addedAt: Value(DateTime.now()),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> removeTrustedImageSender(String senderEmail) async {
|
|
await (_db.delete(_db.imageTrustedSenders)
|
|
..where((t) => t.senderEmail.equals(senderEmail.toLowerCase())))
|
|
.go();
|
|
}
|
|
|
|
static pref.UserPreferences _rowToModel(UserPreferencesRow? row) {
|
|
if (row == null) return const pref.UserPreferences();
|
|
return pref.UserPreferences(
|
|
menuPosition: pref.MenuPosition.values.firstWhere(
|
|
(e) => e.name == row.menuPosition,
|
|
orElse: () => pref.MenuPosition.bottom,
|
|
),
|
|
mailViewButtonPosition: pref.MenuPosition.values.firstWhere(
|
|
(e) => e.name == row.mailViewButtonPosition,
|
|
orElse: () => pref.MenuPosition.bottom,
|
|
),
|
|
afterMailViewAction: pref.AfterMailViewAction.values.firstWhere(
|
|
(e) => e.name == row.afterMailViewAction,
|
|
orElse: () => pref.AfterMailViewAction.nextMessage,
|
|
),
|
|
prefetchMode: pref.PrefetchMode.fromString(row.prefetchMode),
|
|
bodyCacheLimitMb: row.bodyCacheLimitMb,
|
|
);
|
|
}
|
|
}
|