diff --git a/app/lib/use_case/compat/v55.dart b/app/lib/use_case/compat/v55.dart new file mode 100644 index 00000000..96f05f65 --- /dev/null +++ b/app/lib/use_case/compat/v55.dart @@ -0,0 +1,81 @@ +import 'package:drift/drift.dart' as sql; +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:nc_photos/entity/file_util.dart' as file_util; +import 'package:nc_photos/entity/sqlite_table.dart' as sql; +import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql; +import 'package:nc_photos/iterable_extension.dart'; +import 'package:tuple/tuple.dart'; + +class CompatV55 { + static Future<void> migrateDb( + sql.SqliteDb db, { + void Function(int current, int count)? onProgress, + }) { + return db.use((db) async { + final countExp = db.accountFiles.rowId.count(); + final countQ = db.selectOnly(db.accountFiles)..addColumns([countExp]); + final count = await countQ.map((r) => r.read<int>(countExp)).getSingle(); + onProgress?.call(0, count); + + final needUpdates = <Tuple2<int, DateTime>>[]; + for (var i = 0; i < count; i += 1000) { + final q = db.select(db.files).join([ + sql.innerJoin( + db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)), + sql.innerJoin(db.images, + db.images.accountFile.equalsExp(db.accountFiles.rowId)), + ]); + q + ..orderBy([ + sql.OrderingTerm( + expression: db.accountFiles.rowId, + mode: sql.OrderingMode.asc, + ), + ]) + ..limit(1000, offset: i); + final dbFiles = await q + .map((r) => sql.CompleteFile( + r.readTable(db.files), + r.readTable(db.accountFiles), + r.readTable(db.images), + null, + null, + )) + .get(); + for (final f in dbFiles) { + final bestDateTime = file_util.getBestDateTime( + overrideDateTime: f.accountFile.overrideDateTime, + dateTimeOriginal: f.image?.dateTimeOriginal, + lastModified: f.file.lastModified, + ); + if (f.accountFile.bestDateTime != bestDateTime) { + // need update + needUpdates.add(Tuple2(f.accountFile.rowId, bestDateTime)); + } + } + onProgress?.call(i, count); + } + + _log.info("[migrateDb] ${needUpdates.length} rows require updating"); + if (kDebugMode) { + _log.fine( + "[migrateDb] ${needUpdates.map((e) => e.item1).toReadableString()}"); + } + await db.batch((batch) { + for (final pair in needUpdates) { + batch.update( + db.accountFiles, + sql.AccountFilesCompanion( + bestDateTime: sql.Value(pair.item2), + ), + where: (sql.$AccountFilesTable table) => + table.rowId.equals(pair.item1), + ); + } + }); + }); + } + + static final _log = Logger("use_case.compat.v55.CompatV55"); +} diff --git a/app/lib/widget/splash.dart b/app/lib/widget/splash.dart index 937e6894..b7ca5940 100644 --- a/app/lib/widget/splash.dart +++ b/app/lib/widget/splash.dart @@ -1,10 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/di_container.dart'; +import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/mobile/android/activity.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; @@ -12,6 +14,7 @@ import 'package:nc_photos/pref.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/use_case/compat/v29.dart'; import 'package:nc_photos/use_case/compat/v46.dart'; +import 'package:nc_photos/use_case/compat/v55.dart'; import 'package:nc_photos/widget/changelog.dart'; import 'package:nc_photos/widget/home.dart'; import 'package:nc_photos/widget/setup.dart'; @@ -68,10 +71,11 @@ class _SplashState extends State<Splash> { Widget _buildContent(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Center( - child: Stack( - children: [ - Column( + child: Stack( + fit: StackFit.expand, + children: [ + Center( + child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -88,25 +92,36 @@ class _SplashState extends State<Splash> { ), ], ), - if (_isUpgrading) - Positioned( - left: 0, - right: 0, - bottom: 64, - child: Column( - children: const [ - SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator(), - ), - SizedBox(height: 8), - Text("Updating"), - ], - ), - ), - ], - ), + ), + if (_isUpgrading) + BlocBuilder<_UpgradeCubit, _UpgradeState>( + bloc: _upgradeCubit, + builder: (context, state) { + return Positioned( + left: 0, + right: 0, + bottom: 64, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text(state.text), + const SizedBox(height: 8), + if (state.count == null) + const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(), + ) + else + LinearProgressIndicator( + value: state.current / state.count!, + ), + ], + ), + ); + }, + ), + ], ), ); } @@ -164,6 +179,9 @@ class _SplashState extends State<Splash> { if (lastVersion < 460) { await _upgrade46(lastVersion); } + if (lastVersion < 550) { + await _upgrade55(lastVersion); + } } Future<void> _upgrade29(int lastVersion) async { @@ -204,6 +222,33 @@ class _SplashState extends State<Splash> { } } + Future<void> _upgrade55(int lastVersion) async { + final c = KiwiContainer().resolve<DiContainer>(); + try { + _log.info("[_upgrade55] migrate DB"); + await CompatV55.migrateDb( + c.sqliteDb, + onProgress: (current, count) { + _upgradeCubit.setState( + L10n.global().migrateDatabaseProcessingNotification, + current, + count, + ); + }, + ); + } catch (e, stackTrace) { + _log.shout("[_upgrade55] Failed while migrateDb", e, stackTrace); + await c.sqliteDb.use((db) async { + await db.truncate(); + final accounts = Pref().getAccounts3Or([]); + for (final a in accounts) { + await db.insertAccountOf(a); + } + }); + } + _upgradeCubit.setIntermediate(); + } + Future<void> _showChangelogIfAvailable(int lastVersion) async { if (Changelog.hasContent(lastVersion)) { try { @@ -222,6 +267,36 @@ class _SplashState extends State<Splash> { final _changelogCompleter = Completer(); var _isUpgrading = false; + late final _upgradeCubit = _UpgradeCubit(); static final _log = Logger("widget.splash._SplashState"); } + +class _UpgradeState { + const _UpgradeState(String text, int current, int count) + : this._(text, current, count); + + const _UpgradeState.intermediate([String? text]) + : this._(text ?? "Updating", 0, null); + + const _UpgradeState._(this.text, this.current, this.count); + + @override + String toString() => "_UpgradeState {" + "current: $current, " + "count: $count, " + "}"; + + final String text; + final int current; + final int? count; +} + +class _UpgradeCubit extends Cubit<_UpgradeState> { + _UpgradeCubit() : super(const _UpgradeState.intermediate()); + + void setIntermediate() => emit(const _UpgradeState.intermediate()); + + void setState(String text, int current, int count) => + emit(_UpgradeState(text, current, count)); +}