Query memories on DB side instead

This commit is contained in:
Ming Ming 2024-05-16 00:46:44 +08:00
parent 1c2892c6cd
commit 21dffd9a13
9 changed files with 239 additions and 29 deletions

View file

@ -47,6 +47,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
on<_SetEnableMemoryCollection>(_onSetEnableMemoryCollection);
on<_SetMemoriesRange>(_onSetMemoriesRange);
on<_UpdateDateTimeGroup>(_onUpdateDateTimeGroup);
on<_UpdateMemories>(_onUpdateMemories);
on<_SetError>(_onSetError);
@ -88,6 +89,15 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
.listen((event) {
add(const _UpdateScrollDate());
}));
_subscriptions.add(stream
.distinct(
(previous, next) => previous.filesSummary == next.filesSummary)
.listen((_) {
add(const _UpdateMemories());
}));
_subscriptions.add(prefController.memoriesRangeChange.listen((_) {
add(const _UpdateMemories());
}));
}
@override
@ -185,7 +195,6 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
_log.info(ev);
emit(state.copyWith(
transformedItems: ev.items,
memoryCollections: ev.memoryCollections,
isLoading: _itemTransformerQueue.isProcessing,
queriedDates: ev.dates,
));
@ -431,6 +440,49 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
add(const _TransformMinimap());
}
Future<void> _onUpdateMemories(
_UpdateMemories ev, Emitter<_State> emit) async {
_log.info(ev);
final localToday = clock.now().toLocal().toDate();
final dbMemories = await _c.npDb.getFilesMemories(
account: account.toDb(),
at: localToday,
radius: prefController.memoriesRangeValue,
includeRelativeRoots: account.roots
.map((e) => File(path: file_util.unstripPath(account, e))
.strippedPathWithEmpty)
.toList(),
excludeRelativeRoots: [remote_storage_util.remoteStorageDirRelativePath],
mimes: file_util.supportedFormatMimes,
);
emit(state.copyWith(
memoryCollections: dbMemories.memories.entries
.sorted((a, b) => a.key.compareTo(b.key))
.reversed
.map((e) {
final center = localToday
.copyWith(year: e.key)
.toLocalDateTime()
.copyWith(hour: 12);
return Collection(
name: L10n.global().memoryAlbumName(localToday.year - e.key),
contentProvider: CollectionMemoryProvider(
account: account,
year: e.key,
month: localToday.month,
day: localToday.day,
cover: e.value
.map((e) => Tuple2(e.bestDateTime.difference(center), e))
.sorted((a, b) => a.item1.compareTo(b.item1))
.firstOrNull
?.let((e) => DbFileDescriptorConverter.fromDb(
account.userId.toString(), e.item2)),
),
);
}).toList(),
));
}
void _onSetError(_SetError ev, Emitter<_State> emit) {
_log.info(ev);
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
@ -447,15 +499,11 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
itemPerRow: state.itemPerRow,
itemSize: state.itemSize,
isGroupByDay: prefController.homePhotosZoomLevelValue >= 0,
memoriesDayRange: prefController.memoriesRangeValue,
locale: language_util.getSelectedLocale() ??
PlatformDispatcher.instance.locale,
),
_buildItem,
(result) {
if (!isClosed) {
add(_OnItemTransformed(
result.items, result.memoryCollections, result.dates));
add(_OnItemTransformed(result.items, result.dates));
}
},
);
@ -694,13 +742,6 @@ _ItemTransformerResult _buildItem(_ItemTransformerArgument arg) {
final dateHelper =
photo_list_util.DateGroupHelper(isMonthOnly: !arg.isGroupByDay);
final today = Date.today();
final memoryCollectionHelper = photo_list_util.MemoryCollectionHelper(
arg.account,
today: today,
dayRange: arg.memoriesDayRange,
);
final dateTimeSet = SplayTreeSet<Date>.of([
...fileGroups.keys,
if (arg.summary != null) ...arg.summary!.items.keys,
@ -724,7 +765,6 @@ _ItemTransformerResult _buildItem(_ItemTransformerArgument arg) {
continue;
}
transformed.add(item);
memoryCollectionHelper.addFile(f, localDate: d);
}
} else if (arg.summary != null) {
// summary
@ -740,12 +780,8 @@ _ItemTransformerResult _buildItem(_ItemTransformerArgument arg) {
}
}
}
final memoryCollections = memoryCollectionHelper
.build((year) => L10n.of(arg.locale).memoryAlbumName(today.year - year));
return _ItemTransformerResult(
items: transformed,
memoryCollections: memoryCollections,
dates: dates,
);
}

View file

@ -114,13 +114,12 @@ class _TransformItems implements _Event {
@toString
class _OnItemTransformed implements _Event {
const _OnItemTransformed(this.items, this.memoryCollections, this.dates);
const _OnItemTransformed(this.items, this.dates);
@override
String toString() => _$toString();
final List<_Item> items;
final List<Collection> memoryCollections;
final Set<Date> dates;
}
@ -308,6 +307,14 @@ class _UpdateDateTimeGroup implements _Event {
String toString() => _$toString();
}
@toString
class _UpdateMemories implements _Event {
const _UpdateMemories();
@override
String toString() => _$toString();
}
@toString
class _SetError implements _Event {
const _SetError(this.error, [this.stackTrace]);

View file

@ -114,8 +114,6 @@ class _ItemTransformerArgument {
this.itemPerRow,
this.itemSize,
required this.isGroupByDay,
required this.memoriesDayRange,
required this.locale,
});
final Account account;
@ -124,19 +122,15 @@ class _ItemTransformerArgument {
final int? itemPerRow;
final double? itemSize;
final bool isGroupByDay;
final int memoriesDayRange;
final Locale locale;
}
class _ItemTransformerResult {
const _ItemTransformerResult({
required this.items,
required this.memoryCollections,
required this.dates,
});
final List<_Item> items;
final List<Collection> memoryCollections;
final Set<Date> dates;
}

View file

@ -5,7 +5,6 @@ import 'package:clock/clock.dart';
import 'package:collection/collection.dart';
import 'package:copy_with/copy_with.dart';
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
@ -23,9 +22,12 @@ import 'package:nc_photos/controller/metadata_controller.dart';
import 'package:nc_photos/controller/persons_controller.dart';
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/controller/sync_controller.dart';
import 'package:nc_photos/db/entity_converter.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/download_handler.dart';
import 'package:nc_photos/entity/collection.dart';
import 'package:nc_photos/entity/collection/content_provider/memory.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/event/event.dart';
@ -33,8 +35,8 @@ import 'package:nc_photos/exception_event.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/flutter_util.dart' as flutter_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/language_util.dart' as language_util;
import 'package:nc_photos/progress_util.dart';
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/stream_extension.dart';
import 'package:nc_photos/theme.dart';
@ -63,6 +65,7 @@ import 'package:np_db/np_db.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:np_ui/np_ui.dart';
import 'package:to_string/to_string.dart';
import 'package:tuple/tuple.dart';
import 'package:visibility_detector/visibility_detector.dart';
part 'home_photos/app_bar.dart';

View file

@ -208,7 +208,7 @@ extension _$_TransformItemsToString on _TransformItems {
extension _$_OnItemTransformedToString on _OnItemTransformed {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_OnItemTransformed {items: [length: ${items.length}], memoryCollections: [length: ${memoryCollections.length}], dates: {length: ${dates.length}}}";
return "_OnItemTransformed {items: [length: ${items.length}], dates: {length: ${dates.length}}}";
}
}
@ -353,6 +353,13 @@ extension _$_UpdateDateTimeGroupToString on _UpdateDateTimeGroup {
}
}
extension _$_UpdateMemoriesToString on _UpdateMemories {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_UpdateMemories {}";
}
}
extension _$_SetErrorToString on _SetError {
String _$toString() {
// ignore: unnecessary_string_interpolations

View file

@ -164,6 +164,20 @@ class DbFilesSummary {
final Map<Date, DbFilesSummaryItem> items;
}
@genCopyWith
@toString
class DbFilesMemory {
const DbFilesMemory({
required this.memories,
});
@override
String toString() => _$toString();
/// Mapping year to files
final Map<int, List<DbFileDescriptor>> memories;
}
@npLog
abstract class NpDb {
factory NpDb() => NpDbSqlite();
@ -378,6 +392,17 @@ abstract class NpDb {
List<String>? mimes,
});
/// Return [DbFileDescriptor]s whose date is lying in a specific month and day
/// range
Future<DbFilesMemory> getFilesMemories({
required DbAccount account,
required Date at,
required int radius,
List<String>? includeRelativeRoots,
List<String>? excludeRelativeRoots,
List<String>? mimes,
});
Future<DbLocationGroupResult> groupLocations({
required DbAccount account,
List<String>? includeRelativeRoots,

View file

@ -57,6 +57,30 @@ extension $DbFilesSummaryCopyWith on DbFilesSummary {
_$DbFilesSummaryCopyWithWorkerImpl(this);
}
abstract class $DbFilesMemoryCopyWithWorker {
DbFilesMemory call({Map<int, List<DbFileDescriptor>>? memories});
}
class _$DbFilesMemoryCopyWithWorkerImpl
implements $DbFilesMemoryCopyWithWorker {
_$DbFilesMemoryCopyWithWorkerImpl(this.that);
@override
DbFilesMemory call({dynamic memories}) {
return DbFilesMemory(
memories:
memories as Map<int, List<DbFileDescriptor>>? ?? that.memories);
}
final DbFilesMemory that;
}
extension $DbFilesMemoryCopyWith on DbFilesMemory {
$DbFilesMemoryCopyWithWorker get copyWith => _$copyWith;
$DbFilesMemoryCopyWithWorker get _$copyWith =>
_$DbFilesMemoryCopyWithWorkerImpl(this);
}
// **************************************************************************
// NpLogGenerator
// **************************************************************************
@ -113,3 +137,10 @@ extension _$DbFilesSummaryToString on DbFilesSummary {
return "DbFilesSummary {items: {length: ${items.length}}}";
}
}
extension _$DbFilesMemoryToString on DbFilesMemory {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "DbFilesMemory {memories: $memories}";
}
}

View file

@ -677,6 +677,87 @@ extension SqliteDbFileExtension on SqliteDb {
return CountFileGroupsByDateResult(dateCount: results.toMap());
}
/// Query files descriptors whose date is lying in a specific month and day
/// range
Future<List<FileDescriptor>> queryFileDescriptorMemories({
required ByAccount account,
required Date at,
required int radius,
List<String>? includeRelativeRoots,
List<String>? excludeRelativeRoots,
List<String>? mimes,
}) async {
_log.info(
"[queryFileDescriptorMemoryGroups] "
"at: $at, "
"radius: $radius, "
"includeRelativeRoots: $includeRelativeRoots, "
"excludeRelativeRoots: $excludeRelativeRoots, "
"mimes: $mimes",
);
final dates =
List.generate(radius * 2 + 1, (index) => at.add(day: -radius + index));
final localTimeColumn =
accountFiles.bestDateTime.modify(const DateTimeModifier.localTime());
final query = _queryFiles().let((q) {
q
..setQueryMode(
FilesQueryMode.expression,
expressions: [
accountFiles.relativePath,
files.fileId,
files.contentType,
accountFiles.isArchived,
accountFiles.isFavorite,
accountFiles.bestDateTime,
],
)
..setAccount(account)
..byArchived(false);
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));
}
Expression<bool>? dateExp;
for (final d in dates) {
final thisExp = localTimeColumn.month.equals(d.month) &
localTimeColumn.day.equals(d.day);
if (dateExp == null) {
dateExp = thisExp;
} else {
dateExp |= thisExp;
}
}
query.where(dateExp!.isValue(true));
final results = await query
.map((r) => FileDescriptor(
relativePath: r.read(accountFiles.relativePath)!,
fileId: r.read(files.fileId)!,
contentType: r.read(files.contentType),
isArchived: r.read(accountFiles.isArchived),
isFavorite: r.read(accountFiles.isFavorite),
bestDateTime: r.read(accountFiles.bestDateTime)!.toUtc(),
))
.get();
return results;
}
/// Update Db files
///
/// Return a list of files that are not yet inserted to the DB (thus not

View file

@ -489,6 +489,32 @@ class NpDbSqlite implements NpDb {
);
}
@override
Future<DbFilesMemory> getFilesMemories({
required DbAccount account,
required Date at,
required int radius,
List<String>? includeRelativeRoots,
List<String>? excludeRelativeRoots,
List<String>? mimes,
}) async {
final result = await _db.use((db) async {
return await db.queryFileDescriptorMemories(
account: ByAccount.db(account),
at: at,
radius: radius,
includeRelativeRoots: includeRelativeRoots,
excludeRelativeRoots: excludeRelativeRoots,
mimes: mimes,
);
});
final memories = <int, List<DbFileDescriptor>>{};
for (final r in result.map(FileDescriptorConverter.fromSql)) {
(memories[r.bestDateTime.year] ??= <DbFileDescriptor>[]).add(r);
}
return DbFilesMemory(memories: memories);
}
@override
Future<DbLocationGroupResult> groupLocations({
required DbAccount account,