Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions lib/core/storage/folder_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,32 @@ class FolderRepository {
await db.delete('folders');
}

/// Makes the stored folders exactly [names], in one transaction: a failure
/// leaves the old list. Folders that stay keep their connections; the ones
/// that go release theirs first (see [removeFolder]).
Future<void> replaceFolders(List<String> names) async {
final wanted = <String>{
for (final name in names)
if (name.trim().isNotEmpty) name.trim(),
};
final db = await _open();
await db.transaction((txn) async {
final rows = await txn.query('folders', columns: ['name']);
final existing = {for (final r in rows) r['name'] as String};
for (final name in existing.difference(wanted)) {
await txn.rawUpdate(
'UPDATE connections SET folder_id = NULL WHERE folder_id IN '
'(SELECT id FROM folders WHERE name = ?)',
[name],
);
await txn.delete('folders', where: 'name = ?', whereArgs: [name]);
}
for (final name in wanted.difference(existing)) {
await txn.insert('folders', {'name': name});
}
});
}

Future<int?> getFolderIdByName(String name) async {
final db = await _open();
final rows = await db.query('folders',
Expand Down
11 changes: 5 additions & 6 deletions lib/core/storage/folders_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ class FoldersStorage {
try {
await _migrateFromLegacyIfNeeded();
_folders = await LocalDb.instance.getFolders();
_loaded = true;
} catch (e) {
// Not marked loaded: the next call reads again instead of caching an
// empty list for the rest of the session.
debugPrint('FoldersStorage.load: $e');
_folders = [];
}
_loaded = true;
return folders;
}

Expand Down Expand Up @@ -64,11 +66,8 @@ class FoldersStorage {
}

Future<void> save(List<String> folders) async {
_folders = List.from(folders);
await LocalDb.instance.clearFolders();
for (final name in _folders) {
await LocalDb.instance.addFolder(name);
}
await LocalDb.instance.replaceFolders(folders);
_folders = await LocalDb.instance.getFolders();
}

Future<void> add(String name) async {
Expand Down
3 changes: 3 additions & 0 deletions lib/core/storage/local_db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class LocalDb {

Future<void> clearFolders() => folders.clearFolders();

Future<void> replaceFolders(List<String> names) =>
folders.replaceFolders(names);

Future<int?> getFolderIdByName(String name) =>
folders.getFolderIdByName(name);

Expand Down
27 changes: 27 additions & 0 deletions test/core/storage/storage_repositories_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,33 @@ void main() {
expect(rows.single['folder_id'], isNull);
});

test('folders: replaceFolders is all-or-nothing and keeps the connections',
() async {
final folders = FolderRepository(open);
await folders.addFolder('work');
await folders.addFolder('home');
final workId = await folders.getFolderIdByName('work');
final homeId = await folders.getFolderIdByName('home');
final inWork = await insertConnection(folderId: workId);
final inHome = await insertConnection(folderId: homeId);

await folders.replaceFolders(['work', ' new ', '', 'work']);

expect(await folders.getFolders(), ['new', 'work']);
expect(await folders.getFolderIdByName('work'), workId);
final kept = await db.query('connections', where: 'id = ?', whereArgs: [inWork]);
expect(kept.single['folder_id'], workId);
final released = await db.query('connections', where: 'id = ?', whereArgs: [inHome]);
expect(released.single['folder_id'], isNull);

// A name the table refuses mid-way rolls the whole replace back.
await db.execute(
'CREATE TRIGGER no_boom BEFORE INSERT ON folders '
"WHEN NEW.name = 'boom' BEGIN SELECT RAISE(ABORT, 'boom'); END");
await expectLater(folders.replaceFolders(['zzz', 'boom']), throwsA(anything));
expect(await folders.getFolders(), ['new', 'work']);
});

test('app settings: set, replace, delete and ER layouts per scope', () async {
final settings = AppSettingsRepository(open);
expect(await settings.getAppSetting('k'), isNull);
Expand Down
Loading