improved start-up loading time

This commit is contained in:
Thibault Deckers 2021-08-24 19:08:25 +09:00
parent fc10f97bfa
commit 7aee4c6db3
10 changed files with 50 additions and 62 deletions

View file

@ -3,30 +3,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
class DateMetadata {
final int? contentId, dateMillis;
DateMetadata({
this.contentId,
this.dateMillis,
});
factory DateMetadata.fromMap(Map map) {
return DateMetadata(
contentId: map['contentId'],
dateMillis: map['dateMillis'] ?? 0,
);
}
Map<String, dynamic> toMap() => {
'contentId': contentId,
'dateMillis': dateMillis,
};
@override
String toString() => '$runtimeType#${shortHash(this)}{contentId=$contentId, dateMillis=$dateMillis}';
}
class CatalogMetadata {
final int? contentId, dateMillis;
final bool isAnimated, isGeotiff, is360, isMultiPage;

View file

@ -36,7 +36,7 @@ abstract class MetadataDb {
Future<void> clearDates();
Future<List<DateMetadata>> loadDates();
Future<Map<int?, int?>> loadDates();
// catalog metadata
@ -260,12 +260,10 @@ class SqfliteMetadataDb implements MetadataDb {
}
@override
Future<List<DateMetadata>> loadDates() async {
// final stopwatch = Stopwatch()..start();
Future<Map<int?, int?>> loadDates() async {
final db = await _database;
final maps = await db.query(dateTakenTable);
final metadataEntries = maps.map((map) => DateMetadata.fromMap(map)).toList();
// debugPrint('$runtimeType loadDates complete in ${stopwatch.elapsed.inMilliseconds}ms for ${metadataEntries.length} entries');
final metadataEntries = Map.fromEntries(maps.map((map) => MapEntry(map['contentId'] as int, (map['dateMillis'] ?? 0) as int)));
return metadataEntries;
}
@ -280,11 +278,9 @@ class SqfliteMetadataDb implements MetadataDb {
@override
Future<List<CatalogMetadata>> loadMetadataEntries() async {
// final stopwatch = Stopwatch()..start();
final db = await _database;
final maps = await db.query(metadataTable);
final metadataEntries = maps.map((map) => CatalogMetadata.fromMap(map)).toList();
// debugPrint('$runtimeType loadMetadataEntries complete in ${stopwatch.elapsed.inMilliseconds}ms for ${metadataEntries.length} entries');
return metadataEntries;
}
@ -318,7 +314,10 @@ class SqfliteMetadataDb implements MetadataDb {
if (metadata.dateMillis != 0) {
batch.insert(
dateTakenTable,
DateMetadata(contentId: metadata.contentId, dateMillis: metadata.dateMillis).toMap(),
{
'contentId': metadata.contentId,
'dateMillis': metadata.dateMillis,
},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
@ -340,11 +339,9 @@ class SqfliteMetadataDb implements MetadataDb {
@override
Future<List<AddressDetails>> loadAddresses() async {
// final stopwatch = Stopwatch()..start();
final db = await _database;
final maps = await db.query(addressTable);
final addresses = maps.map((map) => AddressDetails.fromMap(map)).toList();
// debugPrint('$runtimeType loadAddresses complete in ${stopwatch.elapsed.inMilliseconds}ms for ${addresses.length} entries');
return addresses;
}

View file

@ -7,7 +7,6 @@ import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/location.dart';
import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/metadata.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/album.dart';
import 'package:aves/model/source/enums.dart';
@ -22,6 +21,8 @@ import 'package:flutter/foundation.dart';
mixin SourceBase {
EventBus get eventBus;
Map<int?, AvesEntry> get entryById;
Set<AvesEntry> get visibleEntries;
List<AvesEntry> get sortedEntriesByDate;
@ -41,6 +42,11 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
@override
EventBus get eventBus => _eventBus;
final Map<int?, AvesEntry> _entryById = {};
@override
Map<int?, AvesEntry> get entryById => Map.unmodifiable(_entryById);
final Set<AvesEntry> _rawEntries = {};
Set<AvesEntry> get allEntries => Set.unmodifiable(_rawEntries);
@ -61,11 +67,11 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
return _sortedEntriesByDate!;
}
late List<DateMetadata> _savedDates;
late Map<int?, int?> _savedDates;
Future<void> loadDates() async {
final stopwatch = Stopwatch()..start();
_savedDates = List.unmodifiable(await metadataDb.loadDates());
_savedDates = Map.unmodifiable(await metadataDb.loadDates());
debugPrint('$runtimeType loadDates complete in ${stopwatch.elapsed.inMilliseconds}ms for ${_savedDates.length} entries');
}
@ -84,14 +90,16 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
void addEntries(Set<AvesEntry> entries) {
if (entries.isEmpty) return;
final newIdMapEntries = Map.fromEntries(entries.map((v) => MapEntry(v.contentId, v)));
if (_rawEntries.isNotEmpty) {
final newContentIds = entries.map((entry) => entry.contentId).toSet();
final newContentIds = newIdMapEntries.keys.toSet();
_rawEntries.removeWhere((entry) => newContentIds.contains(entry.contentId));
}
entries.forEach((entry) {
final contentId = entry.contentId;
entry.catalogDateMillis = _savedDates.firstWhereOrNull((metadata) => metadata.contentId == contentId)?.dateMillis;
});
entries.forEach((entry) => entry.catalogDateMillis = _savedDates[entry.contentId]);
_entryById.addAll(newIdMapEntries);
_rawEntries.addAll(entries);
_invalidate(entries);
@ -104,6 +112,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
final entries = _rawEntries.where((entry) => uris.contains(entry.uri)).toSet();
await favourites.remove(entries);
await covers.removeEntries(entries);
entries.forEach((v) => _entryById.remove(v.contentId));
_rawEntries.removeAll(entries);
_invalidate(entries);
@ -114,6 +124,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
}
void clearEntries() {
_entryById.clear();
_rawEntries.clear();
_invalidate();

View file

@ -20,10 +20,8 @@ mixin LocationMixin on SourceBase {
Future<void> loadAddresses() async {
final stopwatch = Stopwatch()..start();
final saved = await metadataDb.loadAddresses();
visibleEntries.forEach((entry) {
final contentId = entry.contentId;
entry.addressDetails = saved.firstWhereOrNull((address) => address.contentId == contentId);
});
final idMap = entryById;
saved.forEach((metadata) => idMap[metadata.contentId]?.addressDetails = metadata);
debugPrint('$runtimeType loadAddresses complete in ${stopwatch.elapsed.inMilliseconds}ms for ${saved.length} entries');
onAddressMetadataChanged();
}

View file

@ -36,7 +36,7 @@ class MediaStoreSource extends CollectionSource {
settings.catalogTimeZone = currentTimeZone;
}
}
await loadDates(); // 100ms for 5400 entries
await loadDates();
_initialized = true;
debugPrint('$runtimeType init done, elapsed=${stopwatch.elapsed}');
}
@ -49,15 +49,15 @@ class MediaStoreSource extends CollectionSource {
stateNotifier.value = SourceState.loading;
clearEntries();
final oldEntries = await metadataDb.loadEntries(); // 400ms for 5500 entries
final oldEntries = await metadataDb.loadEntries();
final knownDateById = Map.fromEntries(oldEntries.map((entry) => MapEntry(entry.contentId!, entry.dateModifiedSecs!)));
final obsoleteContentIds = (await mediaStoreService.checkObsoleteContentIds(knownDateById.keys.toList())).toSet();
oldEntries.removeWhere((entry) => obsoleteContentIds.contains(entry.contentId));
// show known entries
addEntries(oldEntries);
await loadCatalogMetadata(); // 600ms for 5500 entries
await loadAddresses(); // 200ms for 3000 entries
await loadCatalogMetadata();
await loadAddresses();
debugPrint('$runtimeType refresh loaded ${oldEntries.length} known entries, elapsed=${stopwatch.elapsed}');
// clean up obsolete entries
@ -94,7 +94,7 @@ class MediaStoreSource extends CollectionSource {
addPendingEntries();
debugPrint('$runtimeType refresh loaded ${allNewEntries.length} new entries, elapsed=${stopwatch.elapsed}');
await metadataDb.saveEntries(allNewEntries); // 700ms for 5500 entries
await metadataDb.saveEntries(allNewEntries);
if (allNewEntries.isNotEmpty) {
// new entries include existing entries with obsolete paths

View file

@ -15,10 +15,8 @@ mixin TagMixin on SourceBase {
Future<void> loadCatalogMetadata() async {
final stopwatch = Stopwatch()..start();
final saved = await metadataDb.loadMetadataEntries();
visibleEntries.forEach((entry) {
final contentId = entry.contentId;
entry.catalogMetadata = saved.firstWhereOrNull((metadata) => metadata.contentId == contentId);
});
final idMap = entryById;
saved.forEach((metadata) => idMap[metadata.contentId]?.catalogMetadata = metadata);
debugPrint('$runtimeType loadCatalogMetadata complete in ${stopwatch.elapsed.inMilliseconds}ms for ${saved.length} entries');
onCatalogMetadataChanged();
}

View file

@ -95,6 +95,14 @@ class _AppDebugPageState extends State<AppDebugPage> {
},
title: const Text('Show tasks overlay'),
),
ElevatedButton(
onPressed: () async {
final source = context.read<CollectionSource>();
await source.init();
await source.refresh();
},
child: const Text('Source full refresh'),
),
const Divider(),
Padding(
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),

View file

@ -17,7 +17,7 @@ class DebugAppDatabaseSection extends StatefulWidget {
class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with AutomaticKeepAliveClientMixin {
late Future<int> _dbFileSizeLoader;
late Future<Set<AvesEntry>> _dbEntryLoader;
late Future<List<DateMetadata>> _dbDateLoader;
late Future<Map<int?, int?>> _dbDateLoader;
late Future<List<CatalogMetadata>> _dbMetadataLoader;
late Future<List<AddressDetails>> _dbAddressLoader;
late Future<Set<FavouriteRow>> _dbFavouritesLoader;
@ -82,7 +82,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
);
},
),
FutureBuilder<List>(
FutureBuilder<Map<int?, int?>>(
future: _dbDateLoader,
builder: (context, snapshot) {
if (snapshot.hasError) return Text(snapshot.error.toString());

View file

@ -18,7 +18,7 @@ class DbTab extends StatefulWidget {
}
class _DbTabState extends State<DbTab> {
late Future<DateMetadata?> _dbDateLoader;
late Future<int?> _dbDateLoader;
late Future<AvesEntry?> _dbEntryLoader;
late Future<CatalogMetadata?> _dbMetadataLoader;
late Future<AddressDetails?> _dbAddressLoader;
@ -33,7 +33,7 @@ class _DbTabState extends State<DbTab> {
void _loadDatabase() {
final contentId = entry.contentId;
_dbDateLoader = metadataDb.loadDates().then((values) => values.firstWhereOrNull((row) => row.contentId == contentId));
_dbDateLoader = metadataDb.loadDates().then((values) => values[contentId]);
_dbEntryLoader = metadataDb.loadEntries().then((values) => values.firstWhereOrNull((row) => row.contentId == contentId));
_dbMetadataLoader = metadataDb.loadMetadataEntries().then((values) => values.firstWhereOrNull((row) => row.contentId == contentId));
_dbAddressLoader = metadataDb.loadAddresses().then((values) => values.firstWhereOrNull((row) => row.contentId == contentId));
@ -45,7 +45,7 @@ class _DbTabState extends State<DbTab> {
return ListView(
padding: const EdgeInsets.all(16),
children: [
FutureBuilder<DateMetadata?>(
FutureBuilder<int?>(
future: _dbDateLoader,
builder: (context, snapshot) {
if (snapshot.hasError) return Text(snapshot.error.toString());
@ -58,7 +58,7 @@ class _DbTabState extends State<DbTab> {
if (data != null)
InfoRowGroup(
info: {
'dateMillis': '${data.dateMillis}',
'dateMillis': '$data',
},
),
],

View file

@ -24,7 +24,7 @@ class FakeMetadataDb extends Fake implements MetadataDb {
Future<void> updateEntryId(int oldId, AvesEntry entry) => SynchronousFuture(null);
@override
Future<List<DateMetadata>> loadDates() => SynchronousFuture([]);
Future<Map<int?, int?>> loadDates() => SynchronousFuture({});
@override
Future<List<CatalogMetadata>> loadMetadataEntries() => SynchronousFuture([]);