load metadata/address on startup

This commit is contained in:
Thibault Deckers 2019-08-11 23:12:08 +09:00
parent 27bf3f4dad
commit 6206dbde62
6 changed files with 55 additions and 42 deletions

View file

@ -47,11 +47,14 @@ class _HomePageState extends State<HomePage> {
await metadataDb.init(); await metadataDb.init();
eventChannel.receiveBroadcastStream().cast<Map>().listen( eventChannel.receiveBroadcastStream().cast<Map>().listen(
(entryMap) => setState(() => entries.add(ImageEntry.fromMap(entryMap))), (entryMap) => entries.add(ImageEntry.fromMap(entryMap)),
onDone: () async { onDone: () async {
debugPrint('mediastore stream done'); debugPrint('mediastore stream done');
await loadCatalogMetadata();
setState(() {}); setState(() {});
await catalogEntries(); await catalogEntries();
setState(() {});
await loadAddresses();
await locateEntries(); await locateEntries();
}, },
onError: (error) => debugPrint('mediastore stream error=$error'), onError: (error) => debugPrint('mediastore stream error=$error'),
@ -69,10 +72,36 @@ class _HomePageState extends State<HomePage> {
); );
} }
loadCatalogMetadata() async {
debugPrint('$runtimeType loadCatalogMetadata start');
final start = DateTime.now();
final saved = await metadataDb.loadMetadataEntries();
entries.forEach((entry) {
final contentId = entry.contentId;
if (contentId != null) {
entry.catalogMetadata = saved.firstWhere((metadata) => metadata.contentId == contentId, orElse: () => null);
}
});
debugPrint('$runtimeType loadCatalogMetadata complete in ${DateTime.now().difference(start).inSeconds}s with ${saved.length} saved entries');
}
loadAddresses() async {
debugPrint('$runtimeType loadAddresses start');
final start = DateTime.now();
final saved = await metadataDb.loadAddresses();
entries.forEach((entry) {
final contentId = entry.contentId;
if (contentId != null) {
entry.addressDetails = saved.firstWhere((address) => address.contentId == contentId, orElse: () => null);
}
});
debugPrint('$runtimeType loadAddresses complete in ${DateTime.now().difference(start).inSeconds}s with ${saved.length} saved entries');
}
catalogEntries() async { catalogEntries() async {
debugPrint('$runtimeType catalogEntries start'); debugPrint('$runtimeType catalogEntries start');
final start = DateTime.now(); final start = DateTime.now();
final uncataloguedEntries = entries.where((entry) => !entry.isCataloged); final uncataloguedEntries = entries.where((entry) => !entry.isCatalogued);
final newMetadata = List<CatalogMetadata>(); final newMetadata = List<CatalogMetadata>();
await Future.forEach<ImageEntry>(uncataloguedEntries, (entry) async { await Future.forEach<ImageEntry>(uncataloguedEntries, (entry) async {
await entry.catalog(); await entry.catalog();
@ -82,7 +111,6 @@ class _HomePageState extends State<HomePage> {
// sort with more accurate date // sort with more accurate date
entries.sort((a, b) => b.bestDate.compareTo(a.bestDate)); entries.sort((a, b) => b.bestDate.compareTo(a.bestDate));
setState(() {});
metadataDb.saveMetadata(List.unmodifiable(newMetadata)); metadataDb.saveMetadata(List.unmodifiable(newMetadata));
} }
@ -100,6 +128,6 @@ class _HomePageState extends State<HomePage> {
newAddresses.clear(); newAddresses.clear();
} }
}); });
debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s with ${newAddresses.length} new addresses'); debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s');
} }
} }

View file

@ -86,11 +86,11 @@ class ImageEntry with ChangeNotifier {
bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO); bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO);
bool get isCataloged => catalogMetadata != null; bool get isCatalogued => catalogMetadata != null;
double get aspectRatio { double get aspectRatio {
if (width == 0 || height == 0) return 1; if (width == 0 || height == 0) return 1;
if (isVideo && isCataloged) { if (isVideo && isCatalogued) {
if (catalogMetadata.videoRotation % 180 == 90) return height / width; if (catalogMetadata.videoRotation % 180 == 90) return height / width;
} }
return width / height; return width / height;
@ -125,18 +125,18 @@ class ImageEntry with ChangeNotifier {
return '${d.inHours}:$twoDigitMinutes:$twoDigitSeconds'; return '${d.inHours}:$twoDigitMinutes:$twoDigitSeconds';
} }
bool get hasGps => isCataloged && catalogMetadata.latitude != null; bool get hasGps => isCatalogued && catalogMetadata.latitude != null;
bool get isLocated => addressDetails != null; bool get isLocated => addressDetails != null;
Tuple2<double, double> get latLng => isCataloged ? Tuple2(catalogMetadata.latitude, catalogMetadata.longitude) : null; Tuple2<double, double> get latLng => isCatalogued ? Tuple2(catalogMetadata.latitude, catalogMetadata.longitude) : null;
String get geoUri => hasGps ? 'geo:${catalogMetadata.latitude},${catalogMetadata.longitude}' : null; String get geoUri => hasGps ? 'geo:${catalogMetadata.latitude},${catalogMetadata.longitude}' : null;
List<String> get xmpSubjects => catalogMetadata?.xmpSubjects?.split(';')?.where((tag) => tag.isNotEmpty)?.toList() ?? []; List<String> get xmpSubjects => catalogMetadata?.xmpSubjects?.split(';')?.where((tag) => tag.isNotEmpty)?.toList() ?? [];
catalog() async { catalog() async {
if (isCataloged) return; if (isCatalogued) return;
catalogMetadata = await MetadataService.getCatalogMetadata(this); catalogMetadata = await MetadataService.getCatalogMetadata(this);
notifyListeners(); notifyListeners();
} }

View file

@ -34,21 +34,13 @@ class MetadataDb {
await init(); await init();
} }
Future<List<CatalogMetadata>> getAllMetadata() async { Future<List<CatalogMetadata>> loadMetadataEntries() async {
debugPrint('$runtimeType getAllMetadata'); final start = DateTime.now();
final db = await _database; final db = await _database;
final maps = await db.query(metadataTable); final maps = await db.query(metadataTable);
return maps.map((map) => CatalogMetadata.fromMap(map)).toList(); final metadataEntries = maps.map((map) => CatalogMetadata.fromMap(map)).toList();
} debugPrint('$runtimeType loadMetadataEntries complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries');
return metadataEntries;
Future<CatalogMetadata> getMetadata(int contentId) async {
debugPrint('$runtimeType getMetadata contentId=$contentId');
final db = await _database;
List<Map> maps = await db.query(metadataTable, where: 'contentId = ?', whereArgs: [contentId]);
if (maps.length > 0) {
return CatalogMetadata.fromMap(maps.first);
}
return null;
} }
saveMetadata(Iterable<CatalogMetadata> metadataEntries) async { saveMetadata(Iterable<CatalogMetadata> metadataEntries) async {
@ -65,21 +57,13 @@ class MetadataDb {
debugPrint('$runtimeType saveMetadata complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries'); debugPrint('$runtimeType saveMetadata complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries');
} }
Future<List<AddressDetails>> getAllAddresses() async { Future<List<AddressDetails>> loadAddresses() async {
debugPrint('$runtimeType getAllAddresses'); final start = DateTime.now();
final db = await _database; final db = await _database;
final maps = await db.query(addressTable); final maps = await db.query(addressTable);
return maps.map((map) => AddressDetails.fromMap(map)).toList(); final addresses = maps.map((map) => AddressDetails.fromMap(map)).toList();
} debugPrint('$runtimeType loadAddresses complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${addresses.length} entries');
return addresses;
Future<AddressDetails> getAddress(int contentId) async {
debugPrint('$runtimeType getAddress contentId=$contentId');
final db = await _database;
List<Map> maps = await db.query(addressTable, where: 'contentId = ?', whereArgs: [contentId]);
if (maps.length > 0) {
return AddressDetails.fromMap(maps.first);
}
return null;
} }
saveAddresses(Iterable<AddressDetails> addresses) async { saveAddresses(Iterable<AddressDetails> addresses) async {

View file

@ -22,15 +22,15 @@ class DebugPageState extends State<DebugPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_dbMetadataLoader = metadataDb.getAllMetadata(); _dbMetadataLoader = metadataDb.loadMetadataEntries();
_dbAddressLoader = metadataDb.getAllAddresses(); _dbAddressLoader = metadataDb.loadAddresses();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Map<String, List<ImageEntry>> byMimeTypes = groupBy(entries, (entry) => entry.mimeType); final Map<String, List<ImageEntry>> byMimeTypes = groupBy(entries, (entry) => entry.mimeType);
final cataloged = entries.where((entry) => entry.isCataloged); final catalogued = entries.where((entry) => entry.isCatalogued);
final withGps = cataloged.where((entry) => entry.hasGps); final withGps = catalogued.where((entry) => entry.hasGps);
final located = withGps.where((entry) => entry.isLocated); final located = withGps.where((entry) => entry.isLocated);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -41,7 +41,7 @@ class DebugPageState extends State<DebugPage> {
children: [ children: [
Text('Entries: ${entries.length}'), Text('Entries: ${entries.length}'),
...byMimeTypes.keys.map((mimeType) => Text('- $mimeType: ${byMimeTypes[mimeType].length}')), ...byMimeTypes.keys.map((mimeType) => Text('- $mimeType: ${byMimeTypes[mimeType].length}')),
Text('Cataloged: ${cataloged.length}'), Text('Catalogued: ${catalogued.length}'),
Text('With GPS: ${withGps.length}'), Text('With GPS: ${withGps.length}'),
Text('With address: ${located.length}'), Text('With address: ${located.length}'),
Divider(), Divider(),

View file

@ -41,6 +41,7 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
return SizedBox( return SizedBox(
height: 200, height: 200,
child: ClipRRect( child: ClipRRect(
@ -55,7 +56,7 @@ class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
markers: [ markers: [
Marker( Marker(
markerId: MarkerId(widget.markerId), markerId: MarkerId(widget.markerId),
icon: BitmapDescriptor.defaultMarker, icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
position: widget.latLng, position: widget.latLng,
) )
].toSet(), ].toSet(),

View file

@ -21,7 +21,7 @@ class XmpTagSection extends AnimatedWidget {
.map((tag) => Padding( .map((tag) => Padding(
padding: EdgeInsets.symmetric(horizontal: 4.0), padding: EdgeInsets.symmetric(horizontal: 4.0),
child: Chip( child: Chip(
backgroundColor: Colors.indigo, backgroundColor: Theme.of(context).accentColor,
label: Text(tag), label: Text(tag),
), ),
)) ))