From 4fe33289b8ee4ecc78b144bfe216a32bfcbdaee0 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:06:04 +0530 Subject: [PATCH] fix: ignore log db corruptions (#29656) * fix: ignore log db corruptions * review suggestion * log corruption recovery --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../repositories/db.repository.dart | 9 ++++ .../lib/pages/common/splash_screen.page.dart | 12 +----- mobile/lib/utils/bootstrap.dart | 43 +++++++++++++++---- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index b2a546821c..69e45685de 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -373,6 +373,15 @@ final class _DriftPoolStreamQueries extends StreamQueryStore { } } +Future deleteSqliteDatabase({required String name}) async { + final file = await _databaseFile(name); + await [ + file.path, + '${file.path}-wal', + '${file.path}-shm', + ].map((path) => File(path).delete().catchError((_) => File(path), test: (e) => e is FileSystemException)).wait; +} + Future openSqliteConnection({required String name}) async { return _openImmichDatabase(await _databaseFile(name)); } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 2f1328d7a1..0d423875cb 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -12,6 +11,7 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; @@ -25,8 +25,6 @@ import 'package:immich_mobile/theme/theme_data.dart'; import 'package:immich_mobile/widgets/common/immich_logo.dart'; import 'package:immich_mobile/widgets/common/immich_title_text.dart'; import 'package:logging/logging.dart'; -import 'package:path/path.dart' as path; -import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart' show launchUrl, LaunchMode; class BootstrapErrorWidget extends StatelessWidget { @@ -132,13 +130,7 @@ class _BottomPanelState extends State<_BottomPanel> { } try { - final dir = await getApplicationDocumentsDirectory(); - for (final suffix in ['', '-wal', '-shm']) { - final file = File(path.join(dir.path, 'immich.sqlite$suffix')); - if (await file.exists()) { - await file.delete(); - } - } + await deleteSqliteDatabase(name: 'immich'); } catch (_) { return; } diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index 119df5b804..37ad748a57 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -6,10 +6,13 @@ import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; +import 'package:logging/logging.dart'; import 'package:photo_manager/photo_manager.dart'; +import 'package:sqlite3/common.dart'; void configureFileDownloaderNotifications() { FileDownloader().configureNotificationForGroup( @@ -46,18 +49,12 @@ abstract final class Bootstrap { await configureSqliteCache(); final (db, updatePool) = await openSqliteConnectionWithUpdatePool(name: 'immich'); final drift = Drift.sqlite(db, updatePool); - final logDb = DriftLogger.sqlite(await openSqliteConnection(name: 'immich_logs')); final DriftStoreRepository storeRepo = DriftStoreRepository(drift); await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates); final settingsRepo = await SettingsRepository.ensureInitialized(drift); - - await LogService.init( - logRepository: LogRepository(logDb), - settingsRepository: settingsRepo, - shouldBuffer: shouldBufferLogs, - ); + final logDb = await _initLogger(settingsRepository: settingsRepo, shouldBufferLogs: shouldBufferLogs); await NetworkRepository.init(); // Remove once all asset operations are migrated to Native APIs @@ -65,3 +62,33 @@ abstract final class Bootstrap { return (drift, logDb); } } + +Future _initLogger({required SettingsRepository settingsRepository, bool shouldBufferLogs = true}) async { + Future open() async => DriftLogger.sqlite(await openSqliteConnection(name: 'immich_logs')); + + DriftLogger logDb = await open(); + bool wasCorrupt = false; + try { + await logDb.customSelect('SELECT COUNT(*) FROM logger_messages').get(); + } on SqliteException catch (error) { + if (error.resultCode != SqlError.SQLITE_CORRUPT && error.resultCode != SqlError.SQLITE_NOTADB) { + await logDb.close(); + rethrow; + } + dPrint(() => 'Logs database is corrupt, recreating it'); + await logDb.close(); + await deleteSqliteDatabase(name: 'immich_logs'); + logDb = await open(); + wasCorrupt = true; + } + + await LogService.init( + logRepository: LogRepository(logDb), + settingsRepository: settingsRepository, + shouldBuffer: shouldBufferLogs, + ); + if (wasCorrupt) { + Logger('bootstrap:initLogger').warning('Logs database was corrupt and has been recreated'); + } + return logDb; +}