From 97b3e99aa714d2202457ebeded65bc342a7aee81 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Mon, 18 Mar 2024 00:33:26 +0800 Subject: [PATCH] Add db api to query summary of all the files --- np_db/lib/src/api.dart | 39 +++++++++++ np_db/lib/src/api.g.dart | 14 ++++ .../lib/src/database/file_extension.dart | 64 +++++++++++++++++++ np_db_sqlite/lib/src/sqlite_api.dart | 21 ++++++ .../test/database/file_extension_test.dart | 35 ++++++++++ 5 files changed, 173 insertions(+) diff --git a/np_db/lib/src/api.dart b/np_db/lib/src/api.dart index 42b8fc39..409b3753 100644 --- a/np_db/lib/src/api.dart +++ b/np_db/lib/src/api.dart @@ -126,6 +126,31 @@ class DbLocationGroupResult { final List countryCode; } +@toString +class DbFilesSummaryItem { + const DbFilesSummaryItem({ + required this.count, + }); + + @override + String toString() => _$toString(); + + final int count; +} + +@toString +class DbFilesSummary { + const DbFilesSummary({ + required this.items, + }); + + @override + String toString() => _$toString(); + + @Format(r"{length: ${$?.length}}") + final Map items; +} + @npLog abstract class NpDb { factory NpDb() => NpDbSqlite(); @@ -321,9 +346,23 @@ abstract class NpDb { String? location, bool? isFavorite, List? mimes, + int? offset, int? limit, }); + /// Summarize files matching some specific requirements + /// + /// See [getFileDescriptors] + /// + /// Returned data are sorted by [DbFileDescriptor.bestDateTime] in descending + /// order + Future getFilesSummary({ + required DbAccount account, + List? includeRelativeRoots, + List? excludeRelativeRoots, + List? mimes, + }); + Future groupLocations({ required DbAccount account, List? includeRelativeRoots, diff --git a/np_db/lib/src/api.g.dart b/np_db/lib/src/api.g.dart index f5cce00d..6d7b02bc 100644 --- a/np_db/lib/src/api.g.dart +++ b/np_db/lib/src/api.g.dart @@ -37,3 +37,17 @@ extension _$DbLocationGroupResultToString on DbLocationGroupResult { return "DbLocationGroupResult {name: [length: ${name.length}], admin1: [length: ${admin1.length}], admin2: [length: ${admin2.length}], countryCode: [length: ${countryCode.length}]}"; } } + +extension _$DbFilesSummaryItemToString on DbFilesSummaryItem { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "DbFilesSummaryItem {count: $count}"; + } +} + +extension _$DbFilesSummaryToString on DbFilesSummary { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "DbFilesSummary {items: {length: ${items.length}}}"; + } +} diff --git a/np_db_sqlite/lib/src/database/file_extension.dart b/np_db_sqlite/lib/src/database/file_extension.dart index ecab3bac..c422b542 100644 --- a/np_db_sqlite/lib/src/database/file_extension.dart +++ b/np_db_sqlite/lib/src/database/file_extension.dart @@ -40,6 +40,14 @@ class FileDescriptor { final DateTime bestDateTime; } +class CountFileGroupsByDateResult { + const CountFileGroupsByDateResult({ + required this.dateCount, + }); + + final Map dateCount; +} + extension SqliteDbFileExtension on SqliteDb { /// Return files located inside [dir] Future> queryFilesByDirKey({ @@ -574,6 +582,62 @@ extension SqliteDbFileExtension on SqliteDb { return query.map((r) => r.read(files.fileId)!).getSingleOrNull(); } + /// Count number of files per date + Future countFileGroupsByDate({ + required ByAccount account, + List? includeRelativeRoots, + List? excludeRelativeRoots, + List? mimes, + }) async { + _log.info( + "[countFileGroupsByDate] " + "includeRelativeRoots: $includeRelativeRoots, " + "excludeRelativeRoots: $excludeRelativeRoots, " + "mimes: $mimes", + ); + + final count = countAll(); + final localDate = accountFiles.bestDateTime + .modify(const DateTimeModifier.localTime()) + .date; + final query = _queryFiles().let((q) { + q + ..setQueryMode( + FilesQueryMode.expression, + expressions: [localDate, count], + ) + ..setAccount(account); + if (includeRelativeRoots != null) { + if (includeRelativeRoots.none((p) => p.isEmpty)) { + for (final r in includeRelativeRoots) { + q.byOrRelativePathPattern("$r/%"); + } + } + } + return q.build(); + }); + if (excludeRelativeRoots != null) { + for (final r in excludeRelativeRoots) { + query.where(accountFiles.relativePath.like("$r/%").not()); + } + } + if (mimes != null) { + query.where(files.contentType.isIn(mimes)); + } else { + query.where(files.isCollection.isNotValue(true)); + } + query + ..orderBy([OrderingTerm.desc(accountFiles.bestDateTime)]) + ..groupBy([localDate]); + final results = await query + .map((r) => MapEntry( + DateTime.parse(r.read(localDate)!), + r.read(count)!, + )) + .get(); + return CountFileGroupsByDateResult(dateCount: results.toMap()); + } + /// Update Db files /// /// Return a list of files that are not yet inserted to the DB (thus not diff --git a/np_db_sqlite/lib/src/sqlite_api.dart b/np_db_sqlite/lib/src/sqlite_api.dart index 25c5d97e..441549f8 100644 --- a/np_db_sqlite/lib/src/sqlite_api.dart +++ b/np_db_sqlite/lib/src/sqlite_api.dart @@ -461,6 +461,27 @@ class NpDbSqlite implements NpDb { return sqlObjs.toDbFileDescriptors(); } + @override + Future getFilesSummary({ + required DbAccount account, + List? includeRelativeRoots, + List? excludeRelativeRoots, + List? mimes, + }) async { + final result = await _db.use((db) async { + return await db.countFileGroupsByDate( + account: ByAccount.db(account), + includeRelativeRoots: includeRelativeRoots, + excludeRelativeRoots: excludeRelativeRoots, + mimes: mimes, + ); + }); + return DbFilesSummary( + items: result.dateCount + .map((key, value) => MapEntry(key, DbFilesSummaryItem(count: value))), + ); + } + @override Future groupLocations({ required DbAccount account, diff --git a/np_db_sqlite/test/database/file_extension_test.dart b/np_db_sqlite/test/database/file_extension_test.dart index ee84e65c..bfdac635 100644 --- a/np_db_sqlite/test/database/file_extension_test.dart +++ b/np_db_sqlite/test/database/file_extension_test.dart @@ -8,6 +8,7 @@ import '../test_util.dart' as util; void main() { group("database.SqliteDbFileExtension", () { test("cleanUpDanglingFiles", _cleanUpDanglingFiles); + test("countFileGroupsByDate", _countFileGroupsByDate); }); } @@ -46,3 +47,37 @@ Future _cleanUpDanglingFiles() async { [0, 1], ); } + +Future _countFileGroupsByDate() async { + final account = util.buildAccount(); + final files = (util.FilesBuilder() + ..addDir("admin") + ..addJpeg( + "admin/test1.jpg", + lastModified: DateTime(2024, 1, 2, 3, 4, 5), + ) + ..addJpeg( + "admin/test2.jpg", + lastModified: DateTime(2024, 1, 2, 4, 5, 6), + ) + ..addJpeg( + "admin/test3.jpg", + lastModified: DateTime(2024, 1, 3, 4, 5, 6), + )) + .build(); + final db = util.buildTestDb(); + addTearDown(() => db.close()); + await db.transaction(() async { + await db.insertAccounts([account]); + await util.insertFiles(db, account, files); + }); + + final result = await db.countFileGroupsByDate(account: ByAccount.db(account)); + expect( + result.dateCount, + { + DateTime(2024, 1, 2): 2, + DateTime(2024, 1, 3): 1, + }, + ); +}