From 36cffbd44aa2608e4f48b0a8aa05a2921ae637b5 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 17 Jul 2025 11:39:06 -0500 Subject: [PATCH] chore: attempt to handle database lock error when scrubbing the timeline --- .../lib/domain/services/timeline.service.dart | 34 ++++++++- .../repositories/timeline.repository.dart | 76 ++++++++++--------- 2 files changed, 70 insertions(+), 40 deletions(-) diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index 14a854a760..7d15cccc33 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math' as math; import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; @@ -112,8 +113,19 @@ class TimelineService { totalAssets - _bufferOffset, ); } - _buffer = await _assetSource(offset, count); - _bufferOffset = offset; + + try { + _buffer = await _assetSource(offset, count); + _bufferOffset = offset; + } catch (e) { + if (e.toString().contains('database has been locked')) { + debugPrint( + "TimelineService::loadAssets - Database locked, returning cached assets", + ); + return; + } + rethrow; + } } // change the state's total assets count only after the buffer is reloaded @@ -153,8 +165,22 @@ class TimelineService { : (len > kTimelineAssetLoadBatchSize ? index : index + count - len), ); - _buffer = await _assetSource(start, len); - _bufferOffset = start; + try { + _buffer = await _assetSource(start, len); + _bufferOffset = start; + } catch (e) { + if (e.toString().contains('database has been locked') && + _buffer.isNotEmpty) { + debugPrint( + "TimelineService::loadAssets - Database locked, returning cached assets", + ); + if (hasRange(index, count)) { + return getAssets(index, count); + } + return []; + } + rethrow; + } return getAssets(index, count); } diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index a125d87d8d..81fdb61456 100644 --- a/mobile/lib/infrastructure/repositories/timeline.repository.dart +++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart @@ -254,42 +254,44 @@ class DriftTimelineRepository extends DriftDatabaseRepository { required int offset, required int count, }) async { - final albumData = await (_db.remoteAlbumEntity.select() - ..where((row) => row.id.equals(albumId))) - .getSingleOrNull(); + return await transaction(() async { + final albumData = await (_db.remoteAlbumEntity.select() + ..where((row) => row.id.equals(albumId))) + .getSingleOrNull(); - // If album doesn't exist (was deleted), return empty list - if (albumData == null) { - return []; - } + // If album doesn't exist (was deleted), return empty list + if (albumData == null) { + return []; + } - final isAscending = albumData.order == AlbumAssetOrder.asc; + final isAscending = albumData.order == AlbumAssetOrder.asc; - final query = _db.remoteAssetEntity.select().join( - [ - innerJoin( - _db.remoteAlbumAssetEntity, - _db.remoteAlbumAssetEntity.assetId - .equalsExp(_db.remoteAssetEntity.id), - useColumns: false, - ), - ], - )..where( - _db.remoteAssetEntity.deletedAt.isNull() & - _db.remoteAlbumAssetEntity.albumId.equals(albumId), - ); + final query = _db.remoteAssetEntity.select().join( + [ + innerJoin( + _db.remoteAlbumAssetEntity, + _db.remoteAlbumAssetEntity.assetId + .equalsExp(_db.remoteAssetEntity.id), + useColumns: false, + ), + ], + )..where( + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAlbumAssetEntity.albumId.equals(albumId), + ); - if (isAscending) { - query.orderBy([OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]); - } else { - query.orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]); - } + if (isAscending) { + query.orderBy([OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]); + } else { + query.orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]); + } - query.limit(count, offset: offset); + query.limit(count, offset: offset); - return query - .map((row) => row.readTable(_db.remoteAssetEntity).toDto()) - .get(); + return query + .map((row) => row.readTable(_db.remoteAssetEntity).toDto()) + .get(); + }); } TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => @@ -462,13 +464,15 @@ class DriftTimelineRepository extends DriftDatabaseRepository { required Expression Function($RemoteAssetEntityTable row) filter, required int offset, required int count, - }) { - final query = _db.remoteAssetEntity.select() - ..where(filter) - ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) - ..limit(count, offset: offset); + }) async { + return await transaction(() async { + final query = _db.remoteAssetEntity.select() + ..where(filter) + ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) + ..limit(count, offset: offset); - return query.map((row) => row.toDto()).get(); + return query.map((row) => row.toDto()).get(); + }); } }