#158 guard DateTime against weird timestamps

This commit is contained in:
Thibault Deckers 2022-01-16 18:21:25 +09:00
parent dbc3b1d68d
commit d183541b58
8 changed files with 62 additions and 27 deletions

View file

@ -16,6 +16,7 @@ import 'package:aves/services/geocoding_service.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart'; import 'package:aves/services/metadata/svg_metadata_service.dart';
import 'package:aves/theme/format.dart'; import 'package:aves/theme/format.dart';
import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/change_notifier.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:country_code/country_code.dart'; import 'package:country_code/country_code.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -349,12 +350,17 @@ class AvesEntry {
DateTime? get bestDate { DateTime? get bestDate {
if (_bestDate == null) { if (_bestDate == null) {
if ((_catalogDateMillis ?? 0) > 0) { try {
_bestDate = DateTime.fromMillisecondsSinceEpoch(_catalogDateMillis!); if ((_catalogDateMillis ?? 0) > 0) {
} else if ((sourceDateTakenMillis ?? 0) > 0) { _bestDate = DateTime.fromMillisecondsSinceEpoch(_catalogDateMillis!);
_bestDate = DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis!); } else if ((sourceDateTakenMillis ?? 0) > 0) {
} else if ((dateModifiedSecs ?? 0) > 0) { _bestDate = DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis!);
_bestDate = DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs! * 1000); } else if ((dateModifiedSecs ?? 0) > 0) {
_bestDate = DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs! * 1000);
}
} catch (e, stack) {
// date millis may be out of range
reportService.recordError(e, stack);
} }
} }
return _bestDate; return _bestDate;
@ -748,13 +754,11 @@ class AvesEntry {
return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? ''); return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? '');
} }
static final _epoch = DateTime.fromMillisecondsSinceEpoch(0);
// compare by: // compare by:
// 1) date descending // 1) date descending
// 2) name descending // 2) name descending
static int compareByDate(AvesEntry a, AvesEntry b) { static int compareByDate(AvesEntry a, AvesEntry b) {
var c = (b.bestDate ?? _epoch).compareTo(a.bestDate ?? _epoch); var c = (b.bestDate ?? epoch).compareTo(a.bestDate ?? epoch);
if (c != 0) return c; if (c != 0) return c;
return compareByName(b, a); return compareByName(b, a);
} }

View file

@ -15,13 +15,13 @@ import 'package:aves/theme/format.dart';
import 'package:aves/utils/file_utils.dart'; import 'package:aves/utils/file_utils.dart';
import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/math_utils.dart';
import 'package:aves/utils/string_utils.dart'; import 'package:aves/utils/string_utils.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/viewer/video/fijkplayer.dart'; import 'package:aves/widgets/viewer/video/fijkplayer.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fijkplayer/fijkplayer.dart'; import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
class VideoMetadataFormatter { class VideoMetadataFormatter {
static final _epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
static final _anotherDatePattern = RegExp(r'(\d{4})[-/](\d{2})[-/](\d{2}) (\d{2}):(\d{2}):(\d{2})'); static final _anotherDatePattern = RegExp(r'(\d{4})[-/](\d{2})[-/](\d{2}) (\d{2}):(\d{2}):(\d{2})');
static final _durationPattern = RegExp(r'(\d+):(\d+):(\d+)(.\d+)'); static final _durationPattern = RegExp(r'(\d+):(\d+):(\d+)(.\d+)');
static final _locationPattern = RegExp(r'([+-][.0-9]+)'); static final _locationPattern = RegExp(r'([+-][.0-9]+)');
@ -349,7 +349,7 @@ class VideoMetadataFormatter {
static String? _formatDate(String value) { static String? _formatDate(String value) {
final date = DateTime.tryParse(value); final date = DateTime.tryParse(value);
if (date == null) return value; if (date == null) return value;
if (date == _epoch) return null; if (date == epoch) return null;
return date.toIso8601String(); return date.toIso8601String();
} }

View file

@ -237,7 +237,14 @@ class PlatformMetadataFetchService implements MetadataFetchService {
'sizeBytes': entry.sizeBytes, 'sizeBytes': entry.sizeBytes,
'field': field.toExifInterfaceTag(), 'field': field.toExifInterfaceTag(),
}); });
if (result is int) return DateTime.fromMillisecondsSinceEpoch(result); if (result is int) {
try {
return DateTime.fromMillisecondsSinceEpoch(result);
} catch (e, stack) {
// date millis may be out of range
await reportService.recordError(e, stack);
}
}
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
if (!entry.isMissingAtPath) { if (!entry.isMissingAtPath) {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);

View file

@ -1,3 +1,5 @@
import 'package:aves/services/common/services.dart';
extension ExtraDateTime on DateTime { extension ExtraDateTime on DateTime {
bool isAtSameYearAs(DateTime? other) => year == other?.year; bool isAtSameYearAs(DateTime? other) => year == other?.year;
@ -14,6 +16,8 @@ extension ExtraDateTime on DateTime {
bool get isThisYear => isAtSameYearAs(DateTime.now()); bool get isThisYear => isAtSameYearAs(DateTime.now());
} }
final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
final _unixStampMillisPattern = RegExp(r'\d{13}'); final _unixStampMillisPattern = RegExp(r'\d{13}');
final _unixStampSecPattern = RegExp(r'\d{10}'); final _unixStampSecPattern = RegExp(r'\d{10}');
final _plainPattern = RegExp(r'(\d{8})([_-\s](\d{6})([_-\s](\d{3}))?)?'); final _plainPattern = RegExp(r'(\d{8})([_-\s](\d{6})([_-\s](\d{3}))?)?');
@ -23,22 +27,32 @@ DateTime? parseUnknownDateFormat(String? s) {
var match = _unixStampMillisPattern.firstMatch(s); var match = _unixStampMillisPattern.firstMatch(s);
if (match != null) { if (match != null) {
final stampString = match.group(0); final stampMillisString = match.group(0);
if (stampString != null) { if (stampMillisString != null) {
final stampMillis = int.tryParse(stampString); final stampMillis = int.tryParse(stampMillisString);
if (stampMillis != null) { if (stampMillis != null) {
return DateTime.fromMillisecondsSinceEpoch(stampMillis, isUtc: false); try {
return DateTime.fromMillisecondsSinceEpoch(stampMillis, isUtc: false);
} catch (e, stack) {
// date millis may be out of range
reportService.recordError(e, stack);
}
} }
} }
} }
match = _unixStampSecPattern.firstMatch(s); match = _unixStampSecPattern.firstMatch(s);
if (match != null) { if (match != null) {
final stampString = match.group(0); final stampSecString = match.group(0);
if (stampString != null) { if (stampSecString != null) {
final stampMillis = int.tryParse(stampString); final stampSec = int.tryParse(stampSecString);
if (stampMillis != null) { if (stampSec != null) {
return DateTime.fromMillisecondsSinceEpoch(stampMillis * 1000, isUtc: false); try {
return DateTime.fromMillisecondsSinceEpoch(stampSec * 1000, isUtc: false);
} catch (e, stack) {
// date millis may be out of range
reportService.recordError(e, stack);
}
} }
} }
} }

View file

@ -76,18 +76,18 @@ class _RenderSliverKnownExtentBoxAdaptor extends RenderSliverMultiBoxAdaptor {
SectionLayout? sectionAtIndex(int index) => sectionLayouts.firstWhereOrNull((section) => section.hasChild(index)); SectionLayout? sectionAtIndex(int index) => sectionLayouts.firstWhereOrNull((section) => section.hasChild(index));
SectionLayout sectionAtOffset(double scrollOffset) => sectionLayouts.firstWhere((section) => section.hasChildAtOffset(scrollOffset), orElse: () => sectionLayouts.last); SectionLayout? sectionAtOffset(double scrollOffset) => sectionLayouts.firstWhereOrNull((section) => section.hasChildAtOffset(scrollOffset)) ?? sectionLayouts.lastOrNull;
double indexToLayoutOffset(int index) { double indexToLayoutOffset(int index) {
return (sectionAtIndex(index) ?? sectionLayouts.last).indexToLayoutOffset(index); return (sectionAtIndex(index) ?? sectionLayouts.last).indexToLayoutOffset(index);
} }
int getMinChildIndexForScrollOffset(double scrollOffset) { int getMinChildIndexForScrollOffset(double scrollOffset) {
return sectionAtOffset(scrollOffset).getMinChildIndexForScrollOffset(scrollOffset); return sectionAtOffset(scrollOffset)?.getMinChildIndexForScrollOffset(scrollOffset) ?? 0;
} }
int getMaxChildIndexForScrollOffset(double scrollOffset) { int getMaxChildIndexForScrollOffset(double scrollOffset) {
return sectionAtOffset(scrollOffset).getMaxChildIndexForScrollOffset(scrollOffset); return sectionAtOffset(scrollOffset)?.getMaxChildIndexForScrollOffset(scrollOffset) ?? 0;
} }
double estimateMaxScrollOffset( double estimateMaxScrollOffset(

View file

@ -1,6 +1,8 @@
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/enums.dart'; import 'package:aves/model/source/enums.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/common/providers/selection_provider.dart'; import 'package:aves/widgets/common/providers/selection_provider.dart';
import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart';
@ -63,7 +65,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
} }
static int compareFiltersByDate(FilterGridItem<CollectionFilter> a, FilterGridItem<CollectionFilter> b) { static int compareFiltersByDate(FilterGridItem<CollectionFilter> a, FilterGridItem<CollectionFilter> b) {
final c = (b.entry?.bestDate ?? DateTime.fromMillisecondsSinceEpoch(0)).compareTo(a.entry?.bestDate ?? DateTime.fromMillisecondsSinceEpoch(0)); final c = (b.entry?.bestDate ?? epoch).compareTo(a.entry?.bestDate ?? epoch);
return c != 0 ? c : a.filter.compareTo(b.filter); return c != 0 ? c : a.filter.compareTo(b.filter);
} }

View file

@ -53,7 +53,11 @@ class ViewerDebugPage extends StatelessWidget {
String toDateValue(int? time, {int factor = 1}) { String toDateValue(int? time, {int factor = 1}) {
var value = '$time'; var value = '$time';
if (time != null && time > 0) { if (time != null && time > 0) {
value += ' (${DateTime.fromMillisecondsSinceEpoch(time * factor)})'; try {
value += ' (${DateTime.fromMillisecondsSinceEpoch(time * factor)})';
} catch (e) {
value += ' (invalid DateTime})';
}
} }
return value; return value;
} }

View file

@ -57,7 +57,11 @@ class _MetadataTabState extends State<MetadataTab> {
if (secondTimestampKeys.contains(key)) { if (secondTimestampKeys.contains(key)) {
v *= 1000; v *= 1000;
} }
value += ' (${DateTime.fromMillisecondsSinceEpoch(v)})'; try {
value += ' (${DateTime.fromMillisecondsSinceEpoch(v)})';
} catch (e) {
value += ' (invalid DateTime})';
}
} }
if (key == 'xmp' && v != null && v is Uint8List) { if (key == 'xmp' && v != null && v is Uint8List) {
value = String.fromCharCodes(v); value = String.fromCharCodes(v);