Show first 100 photos on startup

This is to create an illusion that the app loads quickly when it's not ;)
This commit is contained in:
Ming Ming 2022-07-14 04:23:41 +08:00
parent 4fa84c4f01
commit 7563fa2ad6
7 changed files with 145 additions and 22 deletions

View file

@ -186,6 +186,17 @@ class ScanAccountDirBloc
final hasContent = state.files.isNotEmpty; final hasContent = state.files.isNotEmpty;
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
if (!hasContent) {
try {
emit(ScanAccountDirBlocLoading(await _queryOfflineMini(ev)));
} catch (e, stackTrace) {
_log.shout(
"[_onEventQuery] Failed while _queryOfflineMini", e, stackTrace);
}
_log.info(
"[_onEventQuery] Elapsed time (_queryOfflineMini): ${stopwatch.elapsedMilliseconds}ms");
stopwatch.reset();
}
final cacheFiles = await _queryOffline(ev); final cacheFiles = await _queryOffline(ev);
_log.info( _log.info(
"[_onEventQuery] Elapsed time (_queryOffline): ${stopwatch.elapsedMilliseconds}ms"); "[_onEventQuery] Elapsed time (_queryOffline): ${stopwatch.elapsedMilliseconds}ms");
@ -322,6 +333,16 @@ class ScanAccountDirBloc
); );
} }
/// Query a small amount of files to give an illusion of quick startup
Future<List<File>> _queryOfflineMini(ScanAccountDirBlocQueryBase ev) async {
return await ScanDirOfflineMini(_c)(
account,
account.roots.map((r) => File(path: file_util.unstripPath(account, r))),
100,
isOnlySupportedFormat: true,
);
}
Future<List<File>> _queryOffline(ScanAccountDirBlocQueryBase ev) async { Future<List<File>> _queryOffline(ScanAccountDirBlocQueryBase ev) async {
final files = <File>[]; final files = <File>[];
for (final r in account.roots) { for (final r in account.roots) {

View file

@ -308,15 +308,12 @@ class FileSqliteDbDataSource implements FileDataSource {
Account account, int fromEpochMs, int toEpochMs) async { Account account, int fromEpochMs, int toEpochMs) async {
_log.info("[listByDate] [$fromEpochMs, $toEpochMs]"); _log.info("[listByDate] [$fromEpochMs, $toEpochMs]");
final dbFiles = await _c.sqliteDb.use((db) async { final dbFiles = await _c.sqliteDb.use((db) async {
final dateTime = sql.coalesce([ final query = db.queryFiles().run((q) {
db.accountFiles.overrideDateTime, q.setQueryMode(sql.FilesQueryMode.completeFile);
db.images.dateTimeOriginal, q.setAppAccount(account);
db.files.lastModified, return q.build();
]).secondsSinceEpoch; });
final queryBuilder = db.queryFiles() final dateTime = db.accountFiles.bestDateTime.secondsSinceEpoch;
..setQueryMode(sql.FilesQueryMode.completeFile)
..setAppAccount(account);
final query = queryBuilder.build();
query query
..where(dateTime.isBetweenValues( ..where(dateTime.isBetweenValues(
fromEpochMs ~/ 1000, (toEpochMs ~/ 1000) - 1)) fromEpochMs ~/ 1000, (toEpochMs ~/ 1000) - 1))

View file

@ -58,6 +58,8 @@ class AccountFiles extends Table {
BoolColumn get isArchived => boolean().nullable()(); BoolColumn get isArchived => boolean().nullable()();
DateTimeColumn get overrideDateTime => DateTimeColumn get overrideDateTime =>
dateTime().map(const _DateTimeConverter()).nullable()(); dateTime().map(const _DateTimeConverter()).nullable()();
DateTimeColumn get bestDateTime =>
dateTime().map(const _DateTimeConverter())();
@override @override
get uniqueKeys => [ get uniqueKeys => [
@ -186,6 +188,8 @@ class SqliteDb extends _$SqliteDb {
"CREATE INDEX account_files_file_index ON account_files(file);")); "CREATE INDEX account_files_file_index ON account_files(file);"));
await m.createIndex(Index("account_files_relative_path_index", await m.createIndex(Index("account_files_relative_path_index",
"CREATE INDEX account_files_relative_path_index ON account_files(relative_path);")); "CREATE INDEX account_files_relative_path_index ON account_files(relative_path);"));
await m.createIndex(Index("account_files_best_date_time_index",
"CREATE INDEX account_files_best_date_time_index ON account_files(best_date_time);"));
await m.createIndex(Index("dir_files_dir_index", await m.createIndex(Index("dir_files_dir_index",
"CREATE INDEX dir_files_dir_index ON dir_files(dir);")); "CREATE INDEX dir_files_dir_index ON dir_files(dir);"));

View file

@ -1000,6 +1000,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
final bool? isFavorite; final bool? isFavorite;
final bool? isArchived; final bool? isArchived;
final DateTime? overrideDateTime; final DateTime? overrideDateTime;
final DateTime bestDateTime;
AccountFile( AccountFile(
{required this.rowId, {required this.rowId,
required this.account, required this.account,
@ -1007,7 +1008,8 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
required this.relativePath, required this.relativePath,
this.isFavorite, this.isFavorite,
this.isArchived, this.isArchived,
this.overrideDateTime}); this.overrideDateTime,
required this.bestDateTime});
factory AccountFile.fromData(Map<String, dynamic> data, {String? prefix}) { factory AccountFile.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? ''; final effectivePrefix = prefix ?? '';
return AccountFile( return AccountFile(
@ -1026,6 +1028,9 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
overrideDateTime: $AccountFilesTable.$converter0.mapToDart( overrideDateTime: $AccountFilesTable.$converter0.mapToDart(
const DateTimeType().mapFromDatabaseResponse( const DateTimeType().mapFromDatabaseResponse(
data['${effectivePrefix}override_date_time'])), data['${effectivePrefix}override_date_time'])),
bestDateTime: $AccountFilesTable.$converter1.mapToDart(
const DateTimeType().mapFromDatabaseResponse(
data['${effectivePrefix}best_date_time']))!,
); );
} }
@override @override
@ -1046,6 +1051,11 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
map['override_date_time'] = map['override_date_time'] =
Variable<DateTime?>(converter.mapToSql(overrideDateTime)); Variable<DateTime?>(converter.mapToSql(overrideDateTime));
} }
{
final converter = $AccountFilesTable.$converter1;
map['best_date_time'] =
Variable<DateTime>(converter.mapToSql(bestDateTime)!);
}
return map; return map;
} }
@ -1064,6 +1074,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
overrideDateTime: overrideDateTime == null && nullToAbsent overrideDateTime: overrideDateTime == null && nullToAbsent
? const Value.absent() ? const Value.absent()
: Value(overrideDateTime), : Value(overrideDateTime),
bestDateTime: Value(bestDateTime),
); );
} }
@ -1079,6 +1090,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
isArchived: serializer.fromJson<bool?>(json['isArchived']), isArchived: serializer.fromJson<bool?>(json['isArchived']),
overrideDateTime: overrideDateTime:
serializer.fromJson<DateTime?>(json['overrideDateTime']), serializer.fromJson<DateTime?>(json['overrideDateTime']),
bestDateTime: serializer.fromJson<DateTime>(json['bestDateTime']),
); );
} }
@override @override
@ -1092,6 +1104,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
'isFavorite': serializer.toJson<bool?>(isFavorite), 'isFavorite': serializer.toJson<bool?>(isFavorite),
'isArchived': serializer.toJson<bool?>(isArchived), 'isArchived': serializer.toJson<bool?>(isArchived),
'overrideDateTime': serializer.toJson<DateTime?>(overrideDateTime), 'overrideDateTime': serializer.toJson<DateTime?>(overrideDateTime),
'bestDateTime': serializer.toJson<DateTime>(bestDateTime),
}; };
} }
@ -1102,7 +1115,8 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
String? relativePath, String? relativePath,
Value<bool?> isFavorite = const Value.absent(), Value<bool?> isFavorite = const Value.absent(),
Value<bool?> isArchived = const Value.absent(), Value<bool?> isArchived = const Value.absent(),
Value<DateTime?> overrideDateTime = const Value.absent()}) => Value<DateTime?> overrideDateTime = const Value.absent(),
DateTime? bestDateTime}) =>
AccountFile( AccountFile(
rowId: rowId ?? this.rowId, rowId: rowId ?? this.rowId,
account: account ?? this.account, account: account ?? this.account,
@ -1113,6 +1127,7 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
overrideDateTime: overrideDateTime.present overrideDateTime: overrideDateTime.present
? overrideDateTime.value ? overrideDateTime.value
: this.overrideDateTime, : this.overrideDateTime,
bestDateTime: bestDateTime ?? this.bestDateTime,
); );
@override @override
String toString() { String toString() {
@ -1123,14 +1138,15 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
..write('relativePath: $relativePath, ') ..write('relativePath: $relativePath, ')
..write('isFavorite: $isFavorite, ') ..write('isFavorite: $isFavorite, ')
..write('isArchived: $isArchived, ') ..write('isArchived: $isArchived, ')
..write('overrideDateTime: $overrideDateTime') ..write('overrideDateTime: $overrideDateTime, ')
..write('bestDateTime: $bestDateTime')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@override @override
int get hashCode => Object.hash(rowId, account, file, relativePath, int get hashCode => Object.hash(rowId, account, file, relativePath,
isFavorite, isArchived, overrideDateTime); isFavorite, isArchived, overrideDateTime, bestDateTime);
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
@ -1141,7 +1157,8 @@ class AccountFile extends DataClass implements Insertable<AccountFile> {
other.relativePath == this.relativePath && other.relativePath == this.relativePath &&
other.isFavorite == this.isFavorite && other.isFavorite == this.isFavorite &&
other.isArchived == this.isArchived && other.isArchived == this.isArchived &&
other.overrideDateTime == this.overrideDateTime); other.overrideDateTime == this.overrideDateTime &&
other.bestDateTime == this.bestDateTime);
} }
class AccountFilesCompanion extends UpdateCompanion<AccountFile> { class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
@ -1152,6 +1169,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
final Value<bool?> isFavorite; final Value<bool?> isFavorite;
final Value<bool?> isArchived; final Value<bool?> isArchived;
final Value<DateTime?> overrideDateTime; final Value<DateTime?> overrideDateTime;
final Value<DateTime> bestDateTime;
const AccountFilesCompanion({ const AccountFilesCompanion({
this.rowId = const Value.absent(), this.rowId = const Value.absent(),
this.account = const Value.absent(), this.account = const Value.absent(),
@ -1160,6 +1178,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
this.isFavorite = const Value.absent(), this.isFavorite = const Value.absent(),
this.isArchived = const Value.absent(), this.isArchived = const Value.absent(),
this.overrideDateTime = const Value.absent(), this.overrideDateTime = const Value.absent(),
this.bestDateTime = const Value.absent(),
}); });
AccountFilesCompanion.insert({ AccountFilesCompanion.insert({
this.rowId = const Value.absent(), this.rowId = const Value.absent(),
@ -1169,9 +1188,11 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
this.isFavorite = const Value.absent(), this.isFavorite = const Value.absent(),
this.isArchived = const Value.absent(), this.isArchived = const Value.absent(),
this.overrideDateTime = const Value.absent(), this.overrideDateTime = const Value.absent(),
required DateTime bestDateTime,
}) : account = Value(account), }) : account = Value(account),
file = Value(file), file = Value(file),
relativePath = Value(relativePath); relativePath = Value(relativePath),
bestDateTime = Value(bestDateTime);
static Insertable<AccountFile> custom({ static Insertable<AccountFile> custom({
Expression<int>? rowId, Expression<int>? rowId,
Expression<int>? account, Expression<int>? account,
@ -1180,6 +1201,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
Expression<bool?>? isFavorite, Expression<bool?>? isFavorite,
Expression<bool?>? isArchived, Expression<bool?>? isArchived,
Expression<DateTime?>? overrideDateTime, Expression<DateTime?>? overrideDateTime,
Expression<DateTime>? bestDateTime,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
if (rowId != null) 'row_id': rowId, if (rowId != null) 'row_id': rowId,
@ -1189,6 +1211,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
if (isFavorite != null) 'is_favorite': isFavorite, if (isFavorite != null) 'is_favorite': isFavorite,
if (isArchived != null) 'is_archived': isArchived, if (isArchived != null) 'is_archived': isArchived,
if (overrideDateTime != null) 'override_date_time': overrideDateTime, if (overrideDateTime != null) 'override_date_time': overrideDateTime,
if (bestDateTime != null) 'best_date_time': bestDateTime,
}); });
} }
@ -1199,7 +1222,8 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
Value<String>? relativePath, Value<String>? relativePath,
Value<bool?>? isFavorite, Value<bool?>? isFavorite,
Value<bool?>? isArchived, Value<bool?>? isArchived,
Value<DateTime?>? overrideDateTime}) { Value<DateTime?>? overrideDateTime,
Value<DateTime>? bestDateTime}) {
return AccountFilesCompanion( return AccountFilesCompanion(
rowId: rowId ?? this.rowId, rowId: rowId ?? this.rowId,
account: account ?? this.account, account: account ?? this.account,
@ -1208,6 +1232,7 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
isFavorite: isFavorite ?? this.isFavorite, isFavorite: isFavorite ?? this.isFavorite,
isArchived: isArchived ?? this.isArchived, isArchived: isArchived ?? this.isArchived,
overrideDateTime: overrideDateTime ?? this.overrideDateTime, overrideDateTime: overrideDateTime ?? this.overrideDateTime,
bestDateTime: bestDateTime ?? this.bestDateTime,
); );
} }
@ -1237,6 +1262,11 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
map['override_date_time'] = map['override_date_time'] =
Variable<DateTime?>(converter.mapToSql(overrideDateTime.value)); Variable<DateTime?>(converter.mapToSql(overrideDateTime.value));
} }
if (bestDateTime.present) {
final converter = $AccountFilesTable.$converter1;
map['best_date_time'] =
Variable<DateTime>(converter.mapToSql(bestDateTime.value)!);
}
return map; return map;
} }
@ -1249,7 +1279,8 @@ class AccountFilesCompanion extends UpdateCompanion<AccountFile> {
..write('relativePath: $relativePath, ') ..write('relativePath: $relativePath, ')
..write('isFavorite: $isFavorite, ') ..write('isFavorite: $isFavorite, ')
..write('isArchived: $isArchived, ') ..write('isArchived: $isArchived, ')
..write('overrideDateTime: $overrideDateTime') ..write('overrideDateTime: $overrideDateTime, ')
..write('bestDateTime: $bestDateTime')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@ -1310,6 +1341,14 @@ class $AccountFilesTable extends AccountFiles
'override_date_time', aliasedName, true, 'override_date_time', aliasedName, true,
type: const IntType(), requiredDuringInsert: false) type: const IntType(), requiredDuringInsert: false)
.withConverter<DateTime>($AccountFilesTable.$converter0); .withConverter<DateTime>($AccountFilesTable.$converter0);
final VerificationMeta _bestDateTimeMeta =
const VerificationMeta('bestDateTime');
@override
late final GeneratedColumnWithTypeConverter<DateTime, DateTime?>
bestDateTime = GeneratedColumn<DateTime?>(
'best_date_time', aliasedName, false,
type: const IntType(), requiredDuringInsert: true)
.withConverter<DateTime>($AccountFilesTable.$converter1);
@override @override
List<GeneratedColumn> get $columns => [ List<GeneratedColumn> get $columns => [
rowId, rowId,
@ -1318,7 +1357,8 @@ class $AccountFilesTable extends AccountFiles
relativePath, relativePath,
isFavorite, isFavorite,
isArchived, isArchived,
overrideDateTime overrideDateTime,
bestDateTime
]; ];
@override @override
String get aliasedName => _alias ?? 'account_files'; String get aliasedName => _alias ?? 'account_files';
@ -1366,6 +1406,7 @@ class $AccountFilesTable extends AccountFiles
data['is_archived']!, _isArchivedMeta)); data['is_archived']!, _isArchivedMeta));
} }
context.handle(_overrideDateTimeMeta, const VerificationResult.success()); context.handle(_overrideDateTimeMeta, const VerificationResult.success());
context.handle(_bestDateTimeMeta, const VerificationResult.success());
return context; return context;
} }
@ -1388,6 +1429,8 @@ class $AccountFilesTable extends AccountFiles
static TypeConverter<DateTime, DateTime> $converter0 = static TypeConverter<DateTime, DateTime> $converter0 =
const _DateTimeConverter(); const _DateTimeConverter();
static TypeConverter<DateTime, DateTime> $converter1 =
const _DateTimeConverter();
} }
class Image extends DataClass implements Insertable<Image> { class Image extends DataClass implements Insertable<Image> {

View file

@ -122,6 +122,7 @@ class SqliteFileConverter {
isFavorite: Value(file.isFavorite), isFavorite: Value(file.isFavorite),
isArchived: Value(file.isArchived), isArchived: Value(file.isArchived),
overrideDateTime: Value(file.overrideDateTime), overrideDateTime: Value(file.overrideDateTime),
bestDateTime: Value(file.bestDateTime),
); );
final dbImage = file.metadata?.run((m) => sql.ImagesCompanion.insert( final dbImage = file.metadata?.run((m) => sql.ImagesCompanion.insert(
lastUpdated: m.lastUpdated, lastUpdated: m.lastUpdated,

View file

@ -466,7 +466,7 @@ class FilesQueryBuilder {
} }
void byRelativePathPattern(String pattern) { void byRelativePathPattern(String pattern) {
_byRelativePathPattern = pattern; (_byRelativePathPatterns ??= []).add(pattern);
} }
void byMimePattern(String pattern) { void byMimePattern(String pattern) {
@ -536,8 +536,14 @@ class FilesQueryBuilder {
if (_byRelativePath != null) { if (_byRelativePath != null) {
query.where(db.accountFiles.relativePath.equals(_byRelativePath)); query.where(db.accountFiles.relativePath.equals(_byRelativePath));
} }
if (_byRelativePathPattern != null) { if (_byRelativePathPatterns?.isNotEmpty == true) {
query.where(db.accountFiles.relativePath.like(_byRelativePathPattern!)); final expression = _byRelativePathPatterns!
.sublist(1)
.fold<Expression<bool?>>(
db.accountFiles.relativePath.like(_byRelativePathPatterns![0]),
(previousValue, element) =>
previousValue | db.accountFiles.relativePath.like(element));
query.where(expression);
} }
if (_byMimePatterns?.isNotEmpty == true) { if (_byMimePatterns?.isNotEmpty == true) {
final expression = _byMimePatterns!.sublist(1).fold<Expression<bool?>>( final expression = _byMimePatterns!.sublist(1).fold<Expression<bool?>>(
@ -576,7 +582,7 @@ class FilesQueryBuilder {
int? _byFileId; int? _byFileId;
Iterable<int>? _byFileIds; Iterable<int>? _byFileIds;
String? _byRelativePath; String? _byRelativePath;
String? _byRelativePathPattern; List<String>? _byRelativePathPatterns;
List<String>? _byMimePatterns; List<String>? _byMimePatterns;
bool? _byFavorite; bool? _byFavorite;
int? _byDirRowId; int? _byDirRowId;

View file

@ -1,3 +1,4 @@
import 'package:drift/drift.dart' as sql;
import 'package:nc_photos/account.dart'; import 'package:nc_photos/account.dart';
import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file.dart';
@ -57,3 +58,53 @@ class ScanDirOffline {
final DiContainer _c; final DiContainer _c;
} }
class ScanDirOfflineMini {
ScanDirOfflineMini(this._c) : assert(require(_c));
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
Future<List<File>> call(
Account account,
Iterable<File> roots,
int limit, {
bool isOnlySupportedFormat = true,
}) async {
final dbFiles = await _c.sqliteDb.use((db) async {
final query = db.queryFiles().run((q) {
q
..setQueryMode(sql.FilesQueryMode.completeFile)
..setAppAccount(account);
for (final r in roots) {
final path = r.strippedPathWithEmpty;
if (path.isEmpty) {
break;
}
q.byRelativePathPattern("$path/%");
}
if (isOnlySupportedFormat) {
q
..byMimePattern("image/%")
..byMimePattern("video/%");
}
return q.build();
});
query
..orderBy([sql.OrderingTerm.desc(db.accountFiles.bestDateTime)])
..limit(limit);
return await query
.map((r) => sql.CompleteFile(
r.readTable(db.files),
r.readTable(db.accountFiles),
r.readTableOrNull(db.images),
r.readTableOrNull(db.trashes),
))
.get();
});
return dbFiles
.map((f) => SqliteFileConverter.fromSql(account.userId.toString(), f))
.toList();
}
final DiContainer _c;
}