mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-15 03:38:53 +01:00
Optimize SyncFavorite to only update a column
This commit is contained in:
parent
450f43694a
commit
49b5901149
9 changed files with 224 additions and 75 deletions
|
@ -113,10 +113,11 @@ class ListFavoriteBloc
|
||||||
emit(ListFavoriteBlocSuccess(ev.account, remote));
|
emit(ListFavoriteBlocSuccess(ev.account, remote));
|
||||||
|
|
||||||
if (cache != null) {
|
if (cache != null) {
|
||||||
CacheFavorite(_c)(ev.account, remote, cache: cache)
|
CacheFavorite(_c)(ev.account, remote.map((f) => f.fileId!))
|
||||||
.onError((e, stackTrace) {
|
.onError((e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[_onEventQuery] Failed while CacheFavorite", e, stackTrace);
|
"[_onEventQuery] Failed while CacheFavorite", e, stackTrace);
|
||||||
|
return -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
|
|
@ -291,7 +291,7 @@ class ScanAccountDirBloc
|
||||||
// no data in this bloc, ignore
|
// no data in this bloc, ignore
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((ev.newFavorites + ev.removedFavorites).any(_isFileOfInterest)) {
|
if (ev.account.compareServerIdentity(account)) {
|
||||||
_refreshThrottler.trigger(
|
_refreshThrottler.trigger(
|
||||||
maxResponceTime: const Duration(seconds: 3),
|
maxResponceTime: const Duration(seconds: 3),
|
||||||
maxPendingCount: 10,
|
maxPendingCount: 10,
|
||||||
|
|
|
@ -12,6 +12,13 @@ class Favorite with EquatableMixin {
|
||||||
"fileId: '$fileId', "
|
"fileId: '$fileId', "
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
|
Favorite copyWith({
|
||||||
|
int? fileId,
|
||||||
|
}) =>
|
||||||
|
Favorite(
|
||||||
|
fileId: fileId ?? this.fileId,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get props => [
|
get props => [
|
||||||
fileId,
|
fileId,
|
||||||
|
|
|
@ -110,12 +110,9 @@ class ShareRemovedEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FavoriteResyncedEvent {
|
class FavoriteResyncedEvent {
|
||||||
const FavoriteResyncedEvent(
|
const FavoriteResyncedEvent(this.account);
|
||||||
this.account, this.newFavorites, this.removedFavorites);
|
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final List<File> newFavorites;
|
|
||||||
final List<File> removedFavorites;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThemeChangedEvent {}
|
class ThemeChangedEvent {}
|
||||||
|
|
|
@ -5,81 +5,92 @@ import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
|
||||||
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
||||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/list_util.dart' as list_util;
|
import 'package:nc_photos/list_util.dart' as list_util;
|
||||||
import 'package:nc_photos/use_case/list_favorite_offline.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
|
|
||||||
class CacheFavorite {
|
class CacheFavorite {
|
||||||
CacheFavorite(this._c)
|
CacheFavorite(this._c) : assert(require(_c));
|
||||||
: assert(require(_c)),
|
|
||||||
assert(ListFavoriteOffline.require(_c));
|
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
||||||
|
|
||||||
/// Cache favorites
|
/// Cache favorites using results from remote
|
||||||
Future<void> call(
|
///
|
||||||
Account account,
|
/// Return number of files updated
|
||||||
List<File> remote, {
|
Future<int> call(Account account, Iterable<int> remoteFileIds) async {
|
||||||
List<File>? cache,
|
_log.info("[call] Cache favorites");
|
||||||
}) async {
|
final remote = remoteFileIds.sorted(Comparable.compare);
|
||||||
cache ??= await ListFavoriteOffline(_c)(account);
|
final updateCount = await _c.sqliteDb.use((db) async {
|
||||||
final remoteSorted =
|
final dbAccount = await db.accountOf(account);
|
||||||
remote.sorted((a, b) => a.fileId!.compareTo(b.fileId!));
|
final cache = await _getCacheFavorites(db, dbAccount);
|
||||||
final cacheSorted = cache.sorted((a, b) => a.fileId!.compareTo(b.fileId!));
|
final cacheMap =
|
||||||
final result = list_util.diffWith<File>(
|
Map.fromEntries(cache.map((e) => MapEntry(e.fileId, e.rowId)));
|
||||||
cacheSorted, remoteSorted, (a, b) => a.fileId!.compareTo(b.fileId!));
|
final diff =
|
||||||
final newFavorites = result.item1;
|
list_util.diff(cacheMap.keys.sorted(Comparable.compare), remote);
|
||||||
final removedFavorites =
|
final newFileIds = diff.item1;
|
||||||
result.item2.map((f) => f.copyWith(isFavorite: false)).toList();
|
_log.info("[call] New favorites: ${newFileIds.toReadableString()}");
|
||||||
final newFileIds = newFavorites.map((f) => f.fileId!).toList();
|
final removedFildIds = diff.item2;
|
||||||
final removedFileIds = removedFavorites.map((f) => f.fileId!).toList();
|
_log.info(
|
||||||
if (newFileIds.isEmpty && removedFileIds.isEmpty) {
|
"[call] Removed favorites: ${removedFildIds.toReadableString()}");
|
||||||
return;
|
|
||||||
}
|
var updateCount = 0;
|
||||||
await _c.sqliteDb.use((db) async {
|
if (newFileIds.isNotEmpty) {
|
||||||
final rowIds = await db.accountFileRowIdsByFileIds(
|
final rowIds = await db.accountFileRowIdsByFileIds(newFileIds,
|
||||||
newFileIds + removedFileIds,
|
sqlAccount: dbAccount);
|
||||||
appAccount: account,
|
final count = await (db.update(db.accountFiles)
|
||||||
);
|
..where(
|
||||||
final rowIdsMap =
|
(t) => t.rowId.isIn(rowIds.map((id) => id.accountFileRowId))))
|
||||||
Map.fromEntries(rowIds.map((e) => MapEntry(e.fileId, e)));
|
.write(
|
||||||
await db.batch((batch) {
|
const sql.AccountFilesCompanion(isFavorite: sql.Value(true)));
|
||||||
for (final id in newFileIds) {
|
_log.info("[call] Updated $count row (new)");
|
||||||
try {
|
updateCount += count;
|
||||||
batch.update(
|
}
|
||||||
db.accountFiles,
|
if (removedFildIds.isNotEmpty) {
|
||||||
const sql.AccountFilesCompanion(isFavorite: sql.Value(true)),
|
final count = await (db.update(db.accountFiles)
|
||||||
where: (sql.$AccountFilesTable t) =>
|
..where((t) =>
|
||||||
t.rowId.equals(rowIdsMap[id]!.accountFileRowId),
|
t.account.equals(dbAccount.rowId) &
|
||||||
);
|
t.file.isIn(removedFildIds.map((id) => cacheMap[id]))))
|
||||||
} catch (e, stackTrace) {
|
.write(
|
||||||
_log.shout("[call] File not found in DB: $id", e, stackTrace);
|
const sql.AccountFilesCompanion(isFavorite: sql.Value(false)));
|
||||||
}
|
_log.info("[call] Updated $count row (remove)");
|
||||||
}
|
updateCount += count;
|
||||||
for (final id in removedFileIds) {
|
}
|
||||||
try {
|
return updateCount;
|
||||||
batch.update(
|
|
||||||
db.accountFiles,
|
|
||||||
const sql.AccountFilesCompanion(isFavorite: sql.Value(null)),
|
|
||||||
where: (sql.$AccountFilesTable t) =>
|
|
||||||
t.rowId.equals(rowIdsMap[id]!.accountFileRowId),
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[call] File not found in DB: $id", e, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
KiwiContainer()
|
if (updateCount > 0) {
|
||||||
.resolve<EventBus>()
|
KiwiContainer().resolve<EventBus>().fire(FavoriteResyncedEvent(account));
|
||||||
.fire(FavoriteResyncedEvent(account, newFavorites, removedFavorites));
|
}
|
||||||
|
return updateCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<_FileRowIdWithFileId>> _getCacheFavorites(
|
||||||
|
sql.SqliteDb db, sql.Account dbAccount) async {
|
||||||
|
final query = db.queryFiles().run((q) {
|
||||||
|
q
|
||||||
|
..setQueryMode(sql.FilesQueryMode.expression,
|
||||||
|
expressions: [db.files.rowId, db.files.fileId])
|
||||||
|
..setSqlAccount(dbAccount)
|
||||||
|
..byFavorite(true);
|
||||||
|
return q.build();
|
||||||
|
});
|
||||||
|
return await query
|
||||||
|
.map((r) => _FileRowIdWithFileId(
|
||||||
|
r.read(db.files.rowId)!, r.read(db.files.fileId)!))
|
||||||
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
|
||||||
static final _log = Logger("use_case.cache_favorite.CacheFavorite");
|
static final _log = Logger("use_case.cache_favorite.CacheFavorite");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _FileRowIdWithFileId {
|
||||||
|
const _FileRowIdWithFileId(this.rowId, this.fileId);
|
||||||
|
|
||||||
|
final int rowId;
|
||||||
|
final int fileId;
|
||||||
|
}
|
||||||
|
|
|
@ -1,22 +1,34 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/use_case/cache_favorite.dart';
|
import 'package:nc_photos/use_case/cache_favorite.dart';
|
||||||
import 'package:nc_photos/use_case/list_favorite.dart';
|
|
||||||
|
|
||||||
class SyncFavorite {
|
class SyncFavorite {
|
||||||
SyncFavorite(this._c)
|
SyncFavorite(this._c)
|
||||||
: assert(require(_c)),
|
: assert(require(_c)),
|
||||||
assert(CacheFavorite.require(_c)),
|
assert(CacheFavorite.require(_c));
|
||||||
assert(ListFavorite.require(_c));
|
|
||||||
|
|
||||||
static bool require(DiContainer c) => true;
|
static bool require(DiContainer c) => DiContainer.has(c, DiType.favoriteRepo);
|
||||||
|
|
||||||
/// Sync favorites in AppDb with remote server
|
/// Sync favorites in cache db with remote server
|
||||||
Future<void> call(Account account) async {
|
///
|
||||||
|
/// Return number of files updated
|
||||||
|
Future<int> call(Account account) async {
|
||||||
_log.info("[call] Sync favorites with remote");
|
_log.info("[call] Sync favorites with remote");
|
||||||
final remote = await ListFavorite(_c)(account);
|
final remote = await _getRemoteFavoriteFileIds(account);
|
||||||
await CacheFavorite(_c)(account, remote);
|
return await CacheFavorite(_c)(account, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<int>> _getRemoteFavoriteFileIds(Account account) async {
|
||||||
|
final fileIds = <int>[];
|
||||||
|
for (final r in account.roots) {
|
||||||
|
final favorites = await _c.favoriteRepo
|
||||||
|
.list(account, File(path: file_util.unstripPath(account, r)));
|
||||||
|
fileIds.addAll(favorites.map((f) => f.fileId));
|
||||||
|
}
|
||||||
|
return fileIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/ci_string.dart';
|
import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
|
import 'package:nc_photos/entity/favorite.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
@ -98,6 +99,29 @@ class MockEventBus implements EventBus {
|
||||||
final _streamController = StreamController.broadcast();
|
final _streamController = StreamController.broadcast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MockFavoriteRepo implements FavoriteRepo {
|
||||||
|
@override
|
||||||
|
FavoriteDataSource get dataSrc => throw UnimplementedError();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Favorite>> list(Account account, File dir) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockFavoriteMemoryRepo extends MockFavoriteRepo {
|
||||||
|
MockFavoriteMemoryRepo([
|
||||||
|
List<Favorite> initialData = const [],
|
||||||
|
]) : favorite = initialData.map((a) => a.copyWith()).toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
list(Account account, File dir) async {
|
||||||
|
return favorite.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Favorite> favorite;
|
||||||
|
}
|
||||||
|
|
||||||
/// Mock of [FileDataSource] where all methods will throw UnimplementedError
|
/// Mock of [FileDataSource] where all methods will throw UnimplementedError
|
||||||
class MockFileDataSource implements FileDataSource {
|
class MockFileDataSource implements FileDataSource {
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -38,6 +38,7 @@ class FilesBuilder {
|
||||||
DateTime? lastModified,
|
DateTime? lastModified,
|
||||||
bool isCollection = false,
|
bool isCollection = false,
|
||||||
bool hasPreview = true,
|
bool hasPreview = true,
|
||||||
|
bool? isFavorite,
|
||||||
String ownerId = "admin",
|
String ownerId = "admin",
|
||||||
String? ownerDisplayName,
|
String? ownerDisplayName,
|
||||||
Metadata? metadata,
|
Metadata? metadata,
|
||||||
|
@ -52,6 +53,7 @@ class FilesBuilder {
|
||||||
isCollection: isCollection,
|
isCollection: isCollection,
|
||||||
hasPreview: hasPreview,
|
hasPreview: hasPreview,
|
||||||
fileId: fileId++,
|
fileId: fileId++,
|
||||||
|
isFavorite: isFavorite,
|
||||||
ownerId: ownerId.toCi(),
|
ownerId: ownerId.toCi(),
|
||||||
ownerDisplayName: ownerDisplayName ?? ownerId.toString(),
|
ownerDisplayName: ownerDisplayName ?? ownerId.toString(),
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
@ -65,6 +67,7 @@ class FilesBuilder {
|
||||||
String? etag,
|
String? etag,
|
||||||
DateTime? lastModified,
|
DateTime? lastModified,
|
||||||
bool hasPreview = true,
|
bool hasPreview = true,
|
||||||
|
bool? isFavorite,
|
||||||
String ownerId = "admin",
|
String ownerId = "admin",
|
||||||
String? ownerDisplayName,
|
String? ownerDisplayName,
|
||||||
}) =>
|
}) =>
|
||||||
|
@ -75,6 +78,7 @@ class FilesBuilder {
|
||||||
etag: etag,
|
etag: etag,
|
||||||
lastModified: lastModified,
|
lastModified: lastModified,
|
||||||
hasPreview: hasPreview,
|
hasPreview: hasPreview,
|
||||||
|
isFavorite: isFavorite,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
ownerDisplayName: ownerDisplayName,
|
ownerDisplayName: ownerDisplayName,
|
||||||
);
|
);
|
||||||
|
@ -85,6 +89,7 @@ class FilesBuilder {
|
||||||
String? etag,
|
String? etag,
|
||||||
DateTime? lastModified,
|
DateTime? lastModified,
|
||||||
bool hasPreview = true,
|
bool hasPreview = true,
|
||||||
|
bool? isFavorite,
|
||||||
String ownerId = "admin",
|
String ownerId = "admin",
|
||||||
String? ownerDisplayName,
|
String? ownerDisplayName,
|
||||||
OrNull<Metadata>? metadata,
|
OrNull<Metadata>? metadata,
|
||||||
|
@ -96,6 +101,7 @@ class FilesBuilder {
|
||||||
etag: etag,
|
etag: etag,
|
||||||
lastModified: lastModified,
|
lastModified: lastModified,
|
||||||
hasPreview: hasPreview,
|
hasPreview: hasPreview,
|
||||||
|
isFavorite: isFavorite,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
ownerDisplayName: ownerDisplayName,
|
ownerDisplayName: ownerDisplayName,
|
||||||
metadata: metadata?.obj ??
|
metadata: metadata?.obj ??
|
||||||
|
@ -111,6 +117,7 @@ class FilesBuilder {
|
||||||
int contentLength = 1024,
|
int contentLength = 1024,
|
||||||
String? etag,
|
String? etag,
|
||||||
DateTime? lastModified,
|
DateTime? lastModified,
|
||||||
|
bool? isFavorite,
|
||||||
String ownerId = "admin",
|
String ownerId = "admin",
|
||||||
String? ownerDisplayName,
|
String? ownerDisplayName,
|
||||||
}) =>
|
}) =>
|
||||||
|
@ -120,6 +127,7 @@ class FilesBuilder {
|
||||||
lastModified: lastModified,
|
lastModified: lastModified,
|
||||||
isCollection: true,
|
isCollection: true,
|
||||||
hasPreview: false,
|
hasPreview: false,
|
||||||
|
isFavorite: isFavorite,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
ownerDisplayName: ownerDisplayName,
|
ownerDisplayName: ownerDisplayName,
|
||||||
);
|
);
|
||||||
|
|
89
app/test/use_case/sync_favorite_test.dart
Normal file
89
app/test/use_case/sync_favorite_test.dart
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import 'package:drift/drift.dart' as sql;
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/entity/favorite.dart';
|
||||||
|
import 'package:nc_photos/entity/sqlite_table.dart' as sql;
|
||||||
|
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||||
|
import 'package:nc_photos/use_case/sync_favorite.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../mock_type.dart';
|
||||||
|
import '../test_util.dart' as util;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group("SyncFavorite", () {
|
||||||
|
test("new", _new);
|
||||||
|
test("remove", _remove);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _new() async {
|
||||||
|
final account = util.buildAccount();
|
||||||
|
final files = (util.FilesBuilder(initialFileId: 100)
|
||||||
|
..addDir("admin")
|
||||||
|
..addJpeg("admin/test1.jpg", isFavorite: true)
|
||||||
|
..addJpeg("admin/test2.jpg", isFavorite: true)
|
||||||
|
..addJpeg("admin/test3.jpg")
|
||||||
|
..addJpeg("admin/test4.jpg")
|
||||||
|
..addJpeg("admin/test5.jpg"))
|
||||||
|
.build();
|
||||||
|
final c = DiContainer(
|
||||||
|
favoriteRepo: MockFavoriteMemoryRepo([
|
||||||
|
const Favorite(fileId: 101),
|
||||||
|
const Favorite(fileId: 102),
|
||||||
|
const Favorite(fileId: 103),
|
||||||
|
const Favorite(fileId: 104),
|
||||||
|
]),
|
||||||
|
sqliteDb: util.buildTestDb(),
|
||||||
|
);
|
||||||
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
await c.sqliteDb.transaction(() async {
|
||||||
|
await c.sqliteDb.insertAccountOf(account);
|
||||||
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
|
});
|
||||||
|
|
||||||
|
await SyncFavorite(c)(account);
|
||||||
|
expect(
|
||||||
|
await listSqliteDbFavoriteFileIds(c.sqliteDb),
|
||||||
|
{101, 102, 103, 104},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _remove() async {
|
||||||
|
final account = util.buildAccount();
|
||||||
|
final files = (util.FilesBuilder(initialFileId: 100)
|
||||||
|
..addDir("admin")
|
||||||
|
..addJpeg("admin/test1.jpg", isFavorite: true)
|
||||||
|
..addJpeg("admin/test2.jpg", isFavorite: true)
|
||||||
|
..addJpeg("admin/test3.jpg", isFavorite: true)
|
||||||
|
..addJpeg("admin/test4.jpg", isFavorite: true)
|
||||||
|
..addJpeg("admin/test5.jpg"))
|
||||||
|
.build();
|
||||||
|
final c = DiContainer(
|
||||||
|
favoriteRepo: MockFavoriteMemoryRepo([
|
||||||
|
const Favorite(fileId: 103),
|
||||||
|
const Favorite(fileId: 104),
|
||||||
|
]),
|
||||||
|
sqliteDb: util.buildTestDb(),
|
||||||
|
);
|
||||||
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
await c.sqliteDb.transaction(() async {
|
||||||
|
await c.sqliteDb.insertAccountOf(account);
|
||||||
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
|
});
|
||||||
|
|
||||||
|
await SyncFavorite(c)(account);
|
||||||
|
expect(
|
||||||
|
await listSqliteDbFavoriteFileIds(c.sqliteDb),
|
||||||
|
{103, 104},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Set<int>> listSqliteDbFavoriteFileIds(sql.SqliteDb db) async {
|
||||||
|
final query = db.selectOnly(db.files).join([
|
||||||
|
sql.innerJoin(
|
||||||
|
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)),
|
||||||
|
])
|
||||||
|
..addColumns([db.files.fileId])
|
||||||
|
..where(db.accountFiles.isFavorite.equals(true));
|
||||||
|
return (await query.map((r) => r.read(db.files.fileId)!).get()).toSet();
|
||||||
|
}
|
Loading…
Reference in a new issue