mirror of
https://github.com/immich-app/immich.git
synced 2025-08-30 23:02:39 -04:00
feat: migrate store to sqlite (#21078)
* add store entity and migration * make store service take both isar and drift repos * migrate and switch store on beta timeline state change * chore: make drift variables final * dispose old store before switching repos * use store to update values for beta timeline * change log service to use the proper store * migrate store when beta already enabled * use isar repository to check beta timeline in store service * remove unused update method from store repo * dispose after create * change watchAll signature in store repo * fix test * rename init isar to initDB * request user to close and reopen on beta migration * fix tests * handle empty version in migration * wait for cache to be populated after migration --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
ed3997d844
commit
6f4f79d8cc
1
mobile/drift_schemas/main/drift_schema_v8.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v8.json
generated
Normal file
File diff suppressed because one or more lines are too long
@ -4,7 +4,6 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
|
||||||
import 'package:immich_mobile/main.dart' as app;
|
import 'package:immich_mobile/main.dart' as app;
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
@ -40,15 +39,18 @@ class ImmichTestHelper {
|
|||||||
static Future<void> loadApp(WidgetTester tester) async {
|
static Future<void> loadApp(WidgetTester tester) async {
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
// Clear all data from Isar (reuse existing instance if available)
|
// Clear all data from Isar (reuse existing instance if available)
|
||||||
final db = await Bootstrap.initIsar();
|
final (isar, drift, logDb) = await Bootstrap.initDB();
|
||||||
final logDb = DriftLogger();
|
await Bootstrap.initDomain(isar, drift, logDb);
|
||||||
await Bootstrap.initDomain(db, logDb);
|
|
||||||
await Store.clear();
|
await Store.clear();
|
||||||
await db.writeTxn(() => db.clear());
|
await isar.writeTxn(() => isar.clear());
|
||||||
// Load main Widget
|
// Load main Widget
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
overrides: [dbProvider.overrideWithValue(db), isarProvider.overrideWithValue(db)],
|
overrides: [
|
||||||
|
dbProvider.overrideWithValue(isar),
|
||||||
|
isarProvider.overrideWithValue(isar),
|
||||||
|
driftProvider.overrideWith(driftOverride(drift)),
|
||||||
|
],
|
||||||
child: const app.MainWidget(),
|
child: const app.MainWidget(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -15,7 +15,7 @@ import 'package:logging/logging.dart';
|
|||||||
/// via [IStoreRepository]
|
/// via [IStoreRepository]
|
||||||
class LogService {
|
class LogService {
|
||||||
final LogRepository _logRepository;
|
final LogRepository _logRepository;
|
||||||
final IsarStoreRepository _storeRepository;
|
final IStoreRepository _storeRepository;
|
||||||
|
|
||||||
final List<LogMessage> _msgBuffer = [];
|
final List<LogMessage> _msgBuffer = [];
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ class LogService {
|
|||||||
|
|
||||||
static Future<LogService> init({
|
static Future<LogService> init({
|
||||||
required LogRepository logRepository,
|
required LogRepository logRepository,
|
||||||
required IsarStoreRepository storeRepository,
|
required IStoreRepository storeRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
_instance ??= await create(
|
_instance ??= await create(
|
||||||
@ -51,7 +51,7 @@ class LogService {
|
|||||||
|
|
||||||
static Future<LogService> create({
|
static Future<LogService> create({
|
||||||
required LogRepository logRepository,
|
required LogRepository logRepository,
|
||||||
required IsarStoreRepository storeRepository,
|
required IStoreRepository storeRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
|
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
|
||||||
@ -92,7 +92,7 @@ class LogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setLogLevel(LogLevel level) async {
|
Future<void> setLogLevel(LogLevel level) async {
|
||||||
await _storeRepository.insert(StoreKey.logLevel, level.index);
|
await _storeRepository.upsert(StoreKey.logLevel, level.index);
|
||||||
Logger.root.level = level.toLevel();
|
Logger.root.level = level.toLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,13 +6,13 @@ import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'
|
|||||||
/// Provides access to a persistent key-value store with an in-memory cache.
|
/// Provides access to a persistent key-value store with an in-memory cache.
|
||||||
/// Listens for repository changes to keep the cache updated.
|
/// Listens for repository changes to keep the cache updated.
|
||||||
class StoreService {
|
class StoreService {
|
||||||
final IsarStoreRepository _storeRepository;
|
final IStoreRepository _storeRepository;
|
||||||
|
|
||||||
/// In-memory cache. Keys are [StoreKey.id]
|
/// In-memory cache. Keys are [StoreKey.id]
|
||||||
final Map<int, Object?> _cache = {};
|
final Map<int, Object?> _cache = {};
|
||||||
late final StreamSubscription<StoreDto> _storeUpdateSubscription;
|
late final StreamSubscription<List<StoreDto>> _storeUpdateSubscription;
|
||||||
|
|
||||||
StoreService._({required IsarStoreRepository storeRepository}) : _storeRepository = storeRepository;
|
StoreService._({required IStoreRepository isarStoreRepository}) : _storeRepository = isarStoreRepository;
|
||||||
|
|
||||||
// TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider
|
// TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider
|
||||||
static StoreService? _instance;
|
static StoreService? _instance;
|
||||||
@ -24,27 +24,29 @@ class StoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace the implementation with the one from create after removing the typedef
|
// TODO: Replace the implementation with the one from create after removing the typedef
|
||||||
static Future<StoreService> init({required IsarStoreRepository storeRepository}) async {
|
static Future<StoreService> init({required IStoreRepository storeRepository}) async {
|
||||||
_instance ??= await create(storeRepository: storeRepository);
|
_instance ??= await create(storeRepository: storeRepository);
|
||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<StoreService> create({required IsarStoreRepository storeRepository}) async {
|
static Future<StoreService> create({required IStoreRepository storeRepository}) async {
|
||||||
final instance = StoreService._(storeRepository: storeRepository);
|
final instance = StoreService._(isarStoreRepository: storeRepository);
|
||||||
await instance._populateCache();
|
await instance.populateCache();
|
||||||
instance._storeUpdateSubscription = instance._listenForChange();
|
instance._storeUpdateSubscription = instance._listenForChange();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _populateCache() async {
|
Future<void> populateCache() async {
|
||||||
final storeValues = await _storeRepository.getAll();
|
final storeValues = await _storeRepository.getAll();
|
||||||
for (StoreDto storeValue in storeValues) {
|
for (StoreDto storeValue in storeValues) {
|
||||||
_cache[storeValue.key.id] = storeValue.value;
|
_cache[storeValue.key.id] = storeValue.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamSubscription<StoreDto> _listenForChange() => _storeRepository.watchAll().listen((event) {
|
StreamSubscription<List<StoreDto>> _listenForChange() => _storeRepository.watchAll().listen((events) {
|
||||||
|
for (final event in events) {
|
||||||
_cache[event.key.id] = event.value;
|
_cache[event.key.id] = event.value;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Disposes the store and cancels the subscription. To reuse the store call init() again
|
/// Disposes the store and cancels the subscription. To reuse the store call init() again
|
||||||
@ -69,7 +71,7 @@ class StoreService {
|
|||||||
/// Stores the [value] for the [key]. Skips write if value hasn't changed.
|
/// Stores the [value] for the [key]. Skips write if value hasn't changed.
|
||||||
Future<void> put<U extends StoreKey<T>, T>(U key, T value) async {
|
Future<void> put<U extends StoreKey<T>, T>(U key, T value) async {
|
||||||
if (_cache[key.id] == value) return;
|
if (_cache[key.id] == value) return;
|
||||||
await _storeRepository.insert(key, value);
|
await _storeRepository.upsert(key, value);
|
||||||
_cache[key.id] = value;
|
_cache[key.id] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
part 'store.entity.g.dart';
|
part 'store.entity.g.dart';
|
||||||
@ -11,3 +13,13 @@ class StoreValue {
|
|||||||
|
|
||||||
const StoreValue(this.id, {this.intValue, this.strValue});
|
const StoreValue(this.id, {this.intValue, this.strValue});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StoreEntity extends Table with DriftDefaultsMixin {
|
||||||
|
IntColumn get id => integer()();
|
||||||
|
|
||||||
|
TextColumn get stringValue => text().nullable()();
|
||||||
|
IntColumn get intValue => integer().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
|
}
|
||||||
|
426
mobile/lib/infrastructure/entities/store.entity.drift.dart
generated
Normal file
426
mobile/lib/infrastructure/entities/store.entity.drift.dart
generated
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart' as i2;
|
||||||
|
|
||||||
|
typedef $$StoreEntityTableCreateCompanionBuilder =
|
||||||
|
i1.StoreEntityCompanion Function({
|
||||||
|
required int id,
|
||||||
|
i0.Value<String?> stringValue,
|
||||||
|
i0.Value<int?> intValue,
|
||||||
|
});
|
||||||
|
typedef $$StoreEntityTableUpdateCompanionBuilder =
|
||||||
|
i1.StoreEntityCompanion Function({
|
||||||
|
i0.Value<int> id,
|
||||||
|
i0.Value<String?> stringValue,
|
||||||
|
i0.Value<int?> intValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$StoreEntityTableFilterComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$StoreEntityTable> {
|
||||||
|
$$StoreEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnFilters<int> get id => $composableBuilder(
|
||||||
|
column: $table.id,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get stringValue => $composableBuilder(
|
||||||
|
column: $table.stringValue,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<int> get intValue => $composableBuilder(
|
||||||
|
column: $table.intValue,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$StoreEntityTableOrderingComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$StoreEntityTable> {
|
||||||
|
$$StoreEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnOrderings<int> get id => $composableBuilder(
|
||||||
|
column: $table.id,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get stringValue => $composableBuilder(
|
||||||
|
column: $table.stringValue,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<int> get intValue => $composableBuilder(
|
||||||
|
column: $table.intValue,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$StoreEntityTableAnnotationComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$StoreEntityTable> {
|
||||||
|
$$StoreEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.GeneratedColumn<int> get id =>
|
||||||
|
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get stringValue => $composableBuilder(
|
||||||
|
column: $table.stringValue,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<int> get intValue =>
|
||||||
|
$composableBuilder(column: $table.intValue, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$StoreEntityTableTableManager
|
||||||
|
extends
|
||||||
|
i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$StoreEntityTable,
|
||||||
|
i1.StoreEntityData,
|
||||||
|
i1.$$StoreEntityTableFilterComposer,
|
||||||
|
i1.$$StoreEntityTableOrderingComposer,
|
||||||
|
i1.$$StoreEntityTableAnnotationComposer,
|
||||||
|
$$StoreEntityTableCreateCompanionBuilder,
|
||||||
|
$$StoreEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.StoreEntityData,
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$StoreEntityTable,
|
||||||
|
i1.StoreEntityData
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
i1.StoreEntityData,
|
||||||
|
i0.PrefetchHooks Function()
|
||||||
|
> {
|
||||||
|
$$StoreEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db,
|
||||||
|
i1.$StoreEntityTable table,
|
||||||
|
) : super(
|
||||||
|
i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$StoreEntityTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
i1.$$StoreEntityTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
i1.$$StoreEntityTableAnnotationComposer($db: db, $table: table),
|
||||||
|
updateCompanionCallback:
|
||||||
|
({
|
||||||
|
i0.Value<int> id = const i0.Value.absent(),
|
||||||
|
i0.Value<String?> stringValue = const i0.Value.absent(),
|
||||||
|
i0.Value<int?> intValue = const i0.Value.absent(),
|
||||||
|
}) => i1.StoreEntityCompanion(
|
||||||
|
id: id,
|
||||||
|
stringValue: stringValue,
|
||||||
|
intValue: intValue,
|
||||||
|
),
|
||||||
|
createCompanionCallback:
|
||||||
|
({
|
||||||
|
required int id,
|
||||||
|
i0.Value<String?> stringValue = const i0.Value.absent(),
|
||||||
|
i0.Value<int?> intValue = const i0.Value.absent(),
|
||||||
|
}) => i1.StoreEntityCompanion.insert(
|
||||||
|
id: id,
|
||||||
|
stringValue: stringValue,
|
||||||
|
intValue: intValue,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$StoreEntityTableProcessedTableManager =
|
||||||
|
i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$StoreEntityTable,
|
||||||
|
i1.StoreEntityData,
|
||||||
|
i1.$$StoreEntityTableFilterComposer,
|
||||||
|
i1.$$StoreEntityTableOrderingComposer,
|
||||||
|
i1.$$StoreEntityTableAnnotationComposer,
|
||||||
|
$$StoreEntityTableCreateCompanionBuilder,
|
||||||
|
$$StoreEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.StoreEntityData,
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$StoreEntityTable,
|
||||||
|
i1.StoreEntityData
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
i1.StoreEntityData,
|
||||||
|
i0.PrefetchHooks Function()
|
||||||
|
>;
|
||||||
|
|
||||||
|
class $StoreEntityTable extends i2.StoreEntity
|
||||||
|
with i0.TableInfo<$StoreEntityTable, i1.StoreEntityData> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$StoreEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<int> id = i0.GeneratedColumn<int>(
|
||||||
|
'id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.int,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _stringValueMeta = const i0.VerificationMeta(
|
||||||
|
'stringValue',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> stringValue =
|
||||||
|
i0.GeneratedColumn<String>(
|
||||||
|
'string_value',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _intValueMeta = const i0.VerificationMeta(
|
||||||
|
'intValue',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<int> intValue = i0.GeneratedColumn<int>(
|
||||||
|
'int_value',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns => [id, stringValue, intValue];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'store_entity';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.StoreEntityData> instance, {
|
||||||
|
bool isInserting = false,
|
||||||
|
}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('id')) {
|
||||||
|
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_idMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('string_value')) {
|
||||||
|
context.handle(
|
||||||
|
_stringValueMeta,
|
||||||
|
stringValue.isAcceptableOrUnknown(
|
||||||
|
data['string_value']!,
|
||||||
|
_stringValueMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('int_value')) {
|
||||||
|
context.handle(
|
||||||
|
_intValueMeta,
|
||||||
|
intValue.isAcceptableOrUnknown(data['int_value']!, _intValueMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
i1.StoreEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.StoreEntityData(
|
||||||
|
id: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}id'],
|
||||||
|
)!,
|
||||||
|
stringValue: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}string_value'],
|
||||||
|
),
|
||||||
|
intValue: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}int_value'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$StoreEntityTable createAlias(String alias) {
|
||||||
|
return $StoreEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class StoreEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.StoreEntityData> {
|
||||||
|
final int id;
|
||||||
|
final String? stringValue;
|
||||||
|
final int? intValue;
|
||||||
|
const StoreEntityData({required this.id, this.stringValue, this.intValue});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['id'] = i0.Variable<int>(id);
|
||||||
|
if (!nullToAbsent || stringValue != null) {
|
||||||
|
map['string_value'] = i0.Variable<String>(stringValue);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || intValue != null) {
|
||||||
|
map['int_value'] = i0.Variable<int>(intValue);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory StoreEntityData.fromJson(
|
||||||
|
Map<String, dynamic> json, {
|
||||||
|
i0.ValueSerializer? serializer,
|
||||||
|
}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return StoreEntityData(
|
||||||
|
id: serializer.fromJson<int>(json['id']),
|
||||||
|
stringValue: serializer.fromJson<String?>(json['stringValue']),
|
||||||
|
intValue: serializer.fromJson<int?>(json['intValue']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<int>(id),
|
||||||
|
'stringValue': serializer.toJson<String?>(stringValue),
|
||||||
|
'intValue': serializer.toJson<int?>(intValue),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.StoreEntityData copyWith({
|
||||||
|
int? id,
|
||||||
|
i0.Value<String?> stringValue = const i0.Value.absent(),
|
||||||
|
i0.Value<int?> intValue = const i0.Value.absent(),
|
||||||
|
}) => i1.StoreEntityData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
stringValue: stringValue.present ? stringValue.value : this.stringValue,
|
||||||
|
intValue: intValue.present ? intValue.value : this.intValue,
|
||||||
|
);
|
||||||
|
StoreEntityData copyWithCompanion(i1.StoreEntityCompanion data) {
|
||||||
|
return StoreEntityData(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
stringValue: data.stringValue.present
|
||||||
|
? data.stringValue.value
|
||||||
|
: this.stringValue,
|
||||||
|
intValue: data.intValue.present ? data.intValue.value : this.intValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('StoreEntityData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('stringValue: $stringValue, ')
|
||||||
|
..write('intValue: $intValue')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(id, stringValue, intValue);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.StoreEntityData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.stringValue == this.stringValue &&
|
||||||
|
other.intValue == this.intValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
class StoreEntityCompanion extends i0.UpdateCompanion<i1.StoreEntityData> {
|
||||||
|
final i0.Value<int> id;
|
||||||
|
final i0.Value<String?> stringValue;
|
||||||
|
final i0.Value<int?> intValue;
|
||||||
|
const StoreEntityCompanion({
|
||||||
|
this.id = const i0.Value.absent(),
|
||||||
|
this.stringValue = const i0.Value.absent(),
|
||||||
|
this.intValue = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
StoreEntityCompanion.insert({
|
||||||
|
required int id,
|
||||||
|
this.stringValue = const i0.Value.absent(),
|
||||||
|
this.intValue = const i0.Value.absent(),
|
||||||
|
}) : id = i0.Value(id);
|
||||||
|
static i0.Insertable<i1.StoreEntityData> custom({
|
||||||
|
i0.Expression<int>? id,
|
||||||
|
i0.Expression<String>? stringValue,
|
||||||
|
i0.Expression<int>? intValue,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (stringValue != null) 'string_value': stringValue,
|
||||||
|
if (intValue != null) 'int_value': intValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.StoreEntityCompanion copyWith({
|
||||||
|
i0.Value<int>? id,
|
||||||
|
i0.Value<String?>? stringValue,
|
||||||
|
i0.Value<int?>? intValue,
|
||||||
|
}) {
|
||||||
|
return i1.StoreEntityCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
stringValue: stringValue ?? this.stringValue,
|
||||||
|
intValue: intValue ?? this.intValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = i0.Variable<int>(id.value);
|
||||||
|
}
|
||||||
|
if (stringValue.present) {
|
||||||
|
map['string_value'] = i0.Variable<String>(stringValue.value);
|
||||||
|
}
|
||||||
|
if (intValue.present) {
|
||||||
|
map['int_value'] = i0.Variable<int>(intValue.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('StoreEntityCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('stringValue: $stringValue, ')
|
||||||
|
..write('intValue: $intValue')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.
|
|||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.dart';
|
||||||
@ -58,6 +59,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
|||||||
StackEntity,
|
StackEntity,
|
||||||
PersonEntity,
|
PersonEntity,
|
||||||
AssetFaceEntity,
|
AssetFaceEntity,
|
||||||
|
StoreEntity,
|
||||||
],
|
],
|
||||||
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
||||||
)
|
)
|
||||||
@ -66,7 +68,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
: super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true)));
|
: super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true)));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 7;
|
int get schemaVersion => 8;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@ -118,6 +120,9 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
from6To7: (m, v7) async {
|
from6To7: (m, v7) async {
|
||||||
await m.createIndex(v7.idxLatLng);
|
await m.createIndex(v7.idxLatLng);
|
||||||
},
|
},
|
||||||
|
from7To8: (m, v8) async {
|
||||||
|
await m.create(v8.storeEntity);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -33,9 +33,11 @@ import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
|
|||||||
as i15;
|
as i15;
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
|
||||||
as i16;
|
as i16;
|
||||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
||||||
as i17;
|
as i17;
|
||||||
import 'package:drift/internal/modular.dart' as i18;
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
|
as i18;
|
||||||
|
import 'package:drift/internal/modular.dart' as i19;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@ -69,9 +71,10 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
late final i15.$PersonEntityTable personEntity = i15.$PersonEntityTable(this);
|
late final i15.$PersonEntityTable personEntity = i15.$PersonEntityTable(this);
|
||||||
late final i16.$AssetFaceEntityTable assetFaceEntity = i16
|
late final i16.$AssetFaceEntityTable assetFaceEntity = i16
|
||||||
.$AssetFaceEntityTable(this);
|
.$AssetFaceEntityTable(this);
|
||||||
i17.MergedAssetDrift get mergedAssetDrift => i18.ReadDatabaseContainer(
|
late final i17.$StoreEntityTable storeEntity = i17.$StoreEntityTable(this);
|
||||||
|
i18.MergedAssetDrift get mergedAssetDrift => i19.ReadDatabaseContainer(
|
||||||
this,
|
this,
|
||||||
).accessor<i17.MergedAssetDrift>(i17.MergedAssetDrift.new);
|
).accessor<i18.MergedAssetDrift>(i18.MergedAssetDrift.new);
|
||||||
@override
|
@override
|
||||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||||
@ -98,6 +101,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
memoryAssetEntity,
|
memoryAssetEntity,
|
||||||
personEntity,
|
personEntity,
|
||||||
assetFaceEntity,
|
assetFaceEntity,
|
||||||
|
storeEntity,
|
||||||
i9.idxLatLng,
|
i9.idxLatLng,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
@ -313,4 +317,6 @@ class $DriftManager {
|
|||||||
i15.$$PersonEntityTableTableManager(_db, _db.personEntity);
|
i15.$$PersonEntityTableTableManager(_db, _db.personEntity);
|
||||||
i16.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
|
i16.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
|
||||||
i16.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
|
i16.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
|
||||||
|
i17.$$StoreEntityTableTableManager get storeEntity =>
|
||||||
|
i17.$$StoreEntityTableTableManager(_db, _db.storeEntity);
|
||||||
}
|
}
|
||||||
|
@ -3049,6 +3049,392 @@ final class Schema7 extends i0.VersionedSchema {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Schema8 extends i0.VersionedSchema {
|
||||||
|
Schema8({required super.database}) : super(version: 8);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
userEntity,
|
||||||
|
remoteAssetEntity,
|
||||||
|
stackEntity,
|
||||||
|
localAssetEntity,
|
||||||
|
localAlbumEntity,
|
||||||
|
localAlbumAssetEntity,
|
||||||
|
idxLocalAssetChecksum,
|
||||||
|
idxRemoteAssetOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerLibraryChecksum,
|
||||||
|
idxRemoteAssetChecksum,
|
||||||
|
userMetadataEntity,
|
||||||
|
partnerEntity,
|
||||||
|
remoteExifEntity,
|
||||||
|
remoteAlbumEntity,
|
||||||
|
remoteAlbumAssetEntity,
|
||||||
|
remoteAlbumUserEntity,
|
||||||
|
memoryEntity,
|
||||||
|
memoryAssetEntity,
|
||||||
|
personEntity,
|
||||||
|
assetFaceEntity,
|
||||||
|
storeEntity,
|
||||||
|
idxLatLng,
|
||||||
|
];
|
||||||
|
late final Shape16 userEntity = Shape16(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
_column_84,
|
||||||
|
_column_85,
|
||||||
|
_column_5,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape17 remoteAssetEntity = Shape17(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_1,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_0,
|
||||||
|
_column_13,
|
||||||
|
_column_14,
|
||||||
|
_column_15,
|
||||||
|
_column_16,
|
||||||
|
_column_17,
|
||||||
|
_column_18,
|
||||||
|
_column_19,
|
||||||
|
_column_20,
|
||||||
|
_column_21,
|
||||||
|
_column_86,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape3 stackEntity = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'stack_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape2 localAssetEntity = Shape2(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_1,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_0,
|
||||||
|
_column_22,
|
||||||
|
_column_14,
|
||||||
|
_column_23,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape6 localAlbumEntity = Shape6(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_5,
|
||||||
|
_column_31,
|
||||||
|
_column_32,
|
||||||
|
_column_33,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape7 localAlbumAssetEntity = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_34, _column_35],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_owner_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_library_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
late final Shape4 userMetadataEntity = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_metadata_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||||
|
columns: [_column_25, _column_26, _column_27],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape5 partnerEntity = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'partner_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||||
|
columns: [_column_28, _column_29, _column_30],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape8 remoteExifEntity = Shape8(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_exif_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_36,
|
||||||
|
_column_37,
|
||||||
|
_column_38,
|
||||||
|
_column_39,
|
||||||
|
_column_40,
|
||||||
|
_column_41,
|
||||||
|
_column_11,
|
||||||
|
_column_10,
|
||||||
|
_column_42,
|
||||||
|
_column_43,
|
||||||
|
_column_44,
|
||||||
|
_column_45,
|
||||||
|
_column_46,
|
||||||
|
_column_47,
|
||||||
|
_column_48,
|
||||||
|
_column_49,
|
||||||
|
_column_50,
|
||||||
|
_column_51,
|
||||||
|
_column_52,
|
||||||
|
_column_53,
|
||||||
|
_column_54,
|
||||||
|
_column_55,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape9 remoteAlbumEntity = Shape9(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_56,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_15,
|
||||||
|
_column_57,
|
||||||
|
_column_58,
|
||||||
|
_column_59,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_36, _column_60],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||||
|
columns: [_column_60, _column_25, _column_61],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape11 memoryEntity = Shape11(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_18,
|
||||||
|
_column_15,
|
||||||
|
_column_8,
|
||||||
|
_column_62,
|
||||||
|
_column_63,
|
||||||
|
_column_64,
|
||||||
|
_column_65,
|
||||||
|
_column_66,
|
||||||
|
_column_67,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape12 memoryAssetEntity = Shape12(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||||
|
columns: [_column_36, _column_68],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape14 personEntity = Shape14(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'person_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_15,
|
||||||
|
_column_1,
|
||||||
|
_column_69,
|
||||||
|
_column_71,
|
||||||
|
_column_72,
|
||||||
|
_column_73,
|
||||||
|
_column_74,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape15 assetFaceEntity = Shape15(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_face_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_36,
|
||||||
|
_column_76,
|
||||||
|
_column_77,
|
||||||
|
_column_78,
|
||||||
|
_column_79,
|
||||||
|
_column_80,
|
||||||
|
_column_81,
|
||||||
|
_column_82,
|
||||||
|
_column_83,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape18 storeEntity = Shape18(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'store_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_87, _column_88, _column_89],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLatLng = i1.Index(
|
||||||
|
'idx_lat_lng',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape18 extends i0.VersionedTable {
|
||||||
|
Shape18({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get stringValue =>
|
||||||
|
columnsByName['string_value']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get intValue =>
|
||||||
|
columnsByName['int_value']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_87(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<String> _column_88(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>(
|
||||||
|
'string_value',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<int> _column_89(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'int_value',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
);
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
@ -3056,6 +3442,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
||||||
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
||||||
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@ -3089,6 +3476,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from6To7(migrator, schema);
|
await from6To7(migrator, schema);
|
||||||
return 7;
|
return 7;
|
||||||
|
case 7:
|
||||||
|
final schema = Schema8(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from7To8(migrator, schema);
|
||||||
|
return 8;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@ -3102,6 +3494,7 @@ i1.OnUpgrade stepByStep({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
||||||
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
||||||
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@ -3110,5 +3503,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
from4To5: from4To5,
|
from4To5: from4To5,
|
||||||
from5To6: from5To6,
|
from5To6: from5To6,
|
||||||
from6To7: from6To7,
|
from6To7: from6To7,
|
||||||
|
from7To8: from7To8,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,30 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
class IsarStoreRepository extends IsarDatabaseRepository {
|
// Temporary interface until Isar is removed to make the service work with both Isar and Sqlite
|
||||||
|
abstract class IStoreRepository {
|
||||||
|
Future<bool> deleteAll();
|
||||||
|
Stream<List<StoreDto<Object>>> watchAll();
|
||||||
|
Future<void> delete<T>(StoreKey<T> key);
|
||||||
|
Future<bool> upsert<T>(StoreKey<T> key, T value);
|
||||||
|
Future<T?> tryGet<T>(StoreKey<T> key);
|
||||||
|
Stream<T?> watch<T>(StoreKey<T> key);
|
||||||
|
Future<List<StoreDto<Object>>> getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
class IsarStoreRepository extends IsarDatabaseRepository implements IStoreRepository {
|
||||||
final Isar _db;
|
final Isar _db;
|
||||||
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
|
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
|
||||||
|
|
||||||
IsarStoreRepository(super.db) : _db = db;
|
IsarStoreRepository(super.db) : _db = db;
|
||||||
|
|
||||||
|
@override
|
||||||
Future<bool> deleteAll() async {
|
Future<bool> deleteAll() async {
|
||||||
return await transaction(() async {
|
return await transaction(() async {
|
||||||
await _db.storeValues.clear();
|
await _db.storeValues.clear();
|
||||||
@ -18,25 +32,29 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<StoreDto<Object>> watchAll() {
|
@override
|
||||||
|
Stream<List<StoreDto<Object>>> watchAll() {
|
||||||
return _db.storeValues
|
return _db.storeValues
|
||||||
.filter()
|
.filter()
|
||||||
.anyOf(validStoreKeys, (query, id) => query.idEqualTo(id))
|
.anyOf(validStoreKeys, (query, id) => query.idEqualTo(id))
|
||||||
.watch(fireImmediately: true)
|
.watch(fireImmediately: true)
|
||||||
.asyncExpand((entities) => Stream.fromFutures(entities.map((e) async => _toUpdateEvent(e))));
|
.asyncMap((entities) => Future.wait(entities.map((entity) => _toUpdateEvent(entity))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> delete<T>(StoreKey<T> key) async {
|
Future<void> delete<T>(StoreKey<T> key) async {
|
||||||
return await transaction(() async => await _db.storeValues.delete(key.id));
|
return await transaction(() async => await _db.storeValues.delete(key.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> insert<T>(StoreKey<T> key, T value) async {
|
@override
|
||||||
|
Future<bool> upsert<T>(StoreKey<T> key, T value) async {
|
||||||
return await transaction(() async {
|
return await transaction(() async {
|
||||||
await _db.storeValues.put(await _fromValue(key, value));
|
await _db.storeValues.put(await _fromValue(key, value));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<T?> tryGet<T>(StoreKey<T> key) async {
|
Future<T?> tryGet<T>(StoreKey<T> key) async {
|
||||||
final entity = (await _db.storeValues.get(key.id));
|
final entity = (await _db.storeValues.get(key.id));
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
@ -45,13 +63,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
|||||||
return await _toValue(key, entity);
|
return await _toValue(key, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> update<T>(StoreKey<T> key, T value) async {
|
@override
|
||||||
return await transaction(() async {
|
|
||||||
await _db.storeValues.put(await _fromValue(key, value));
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<T?> watch<T>(StoreKey<T> key) async* {
|
Stream<T?> watch<T>(StoreKey<T> key) async* {
|
||||||
yield* _db.storeValues
|
yield* _db.storeValues
|
||||||
.watchObject(key.id, fireImmediately: true)
|
.watchObject(key.id, fireImmediately: true)
|
||||||
@ -88,8 +100,93 @@ class IsarStoreRepository extends IsarDatabaseRepository {
|
|||||||
return StoreValue(key.id, intValue: intValue, strValue: strValue);
|
return StoreValue(key.id, intValue: intValue, strValue: strValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<List<StoreDto<Object>>> getAll() async {
|
Future<List<StoreDto<Object>>> getAll() async {
|
||||||
final entities = await _db.storeValues.filter().anyOf(validStoreKeys, (query, id) => query.idEqualTo(id)).findAll();
|
final entities = await _db.storeValues.filter().anyOf(validStoreKeys, (query, id) => query.idEqualTo(id)).findAll();
|
||||||
return Future.wait(entities.map((e) => _toUpdateEvent(e)).toList());
|
return Future.wait(entities.map((e) => _toUpdateEvent(e)).toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepository {
|
||||||
|
final Drift _db;
|
||||||
|
final validStoreKeys = StoreKey.values.map((e) => e.id).toSet();
|
||||||
|
|
||||||
|
DriftStoreRepository(super.db) : _db = db;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> deleteAll() async {
|
||||||
|
await _db.storeEntity.deleteAll();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<StoreDto<Object>>> getAll() async {
|
||||||
|
final query = _db.storeEntity.select()..where((entity) => entity.id.isIn(validStoreKeys));
|
||||||
|
return query.asyncMap((entity) => _toUpdateEvent(entity)).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<StoreDto<Object>>> watchAll() {
|
||||||
|
final query = _db.storeEntity.select()..where((entity) => entity.id.isIn(validStoreKeys));
|
||||||
|
|
||||||
|
return query.asyncMap((entity) => _toUpdateEvent(entity)).watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete<T>(StoreKey<T> key) async {
|
||||||
|
await _db.storeEntity.deleteWhere((entity) => entity.id.equals(key.id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> upsert<T>(StoreKey<T> key, T value) async {
|
||||||
|
await _db.storeEntity.insertOnConflictUpdate(await _fromValue(key, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> tryGet<T>(StoreKey<T> key) async {
|
||||||
|
final entity = await _db.managers.storeEntity.filter((entity) => entity.id.equals(key.id)).getSingleOrNull();
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await _toValue(key, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<T?> watch<T>(StoreKey<T> key) async* {
|
||||||
|
final query = _db.storeEntity.select()..where((entity) => entity.id.equals(key.id));
|
||||||
|
|
||||||
|
yield* query.watchSingleOrNull().asyncMap((e) async => e == null ? null : await _toValue(key, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StoreDto<Object>> _toUpdateEvent(StoreEntityData entity) async {
|
||||||
|
final key = StoreKey.values.firstWhere((e) => e.id == entity.id) as StoreKey<Object>;
|
||||||
|
final value = await _toValue(key, entity);
|
||||||
|
return StoreDto(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T?> _toValue<T>(StoreKey<T> key, StoreEntityData entity) async =>
|
||||||
|
switch (key.type) {
|
||||||
|
const (int) => entity.intValue,
|
||||||
|
const (String) => entity.stringValue,
|
||||||
|
const (bool) => entity.intValue == 1,
|
||||||
|
const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
|
||||||
|
const (UserDto) =>
|
||||||
|
entity.stringValue == null ? null : await DriftUserRepository(_db).get(entity.stringValue!),
|
||||||
|
_ => null,
|
||||||
|
}
|
||||||
|
as T?;
|
||||||
|
|
||||||
|
Future<StoreEntityCompanion> _fromValue<T>(StoreKey<T> key, T value) async {
|
||||||
|
final (int? intValue, String? strValue) = switch (key.type) {
|
||||||
|
const (int) => (value as int, null),
|
||||||
|
const (String) => (null, value as String),
|
||||||
|
const (bool) => ((value as bool) ? 1 : 0, null),
|
||||||
|
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
|
||||||
|
const (UserDto) => (null, (await DriftUserRepository(_db).upsert(value as UserDto)).id),
|
||||||
|
_ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
|
||||||
|
};
|
||||||
|
return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity;
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
@ -63,3 +65,40 @@ class IsarUserRepository extends IsarDatabaseRepository {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DriftUserRepository extends DriftDatabaseRepository {
|
||||||
|
final Drift _db;
|
||||||
|
const DriftUserRepository(super.db) : _db = db;
|
||||||
|
|
||||||
|
Future<UserDto?> get(String id) =>
|
||||||
|
_db.managers.userEntity.filter((user) => user.id.equals(id)).getSingleOrNull().then((user) => user?.toDto());
|
||||||
|
|
||||||
|
Future<UserDto> upsert(UserDto user) async {
|
||||||
|
await _db.userEntity.insertOnConflictUpdate(
|
||||||
|
UserEntityCompanion(
|
||||||
|
id: Value(user.id),
|
||||||
|
isAdmin: Value(user.isAdmin),
|
||||||
|
updatedAt: Value(user.updatedAt),
|
||||||
|
name: Value(user.name),
|
||||||
|
email: Value(user.email),
|
||||||
|
hasProfileImage: Value(user.hasProfileImage),
|
||||||
|
profileChangedAt: Value(user.profileChangedAt),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on UserEntityData {
|
||||||
|
UserDto toDto() {
|
||||||
|
return UserDto(
|
||||||
|
id: id,
|
||||||
|
email: email,
|
||||||
|
name: name,
|
||||||
|
isAdmin: isAdmin,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
profileChangedAt: profileChangedAt,
|
||||||
|
hasProfileImage: hasProfileImage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,6 @@ import 'package:immich_mobile/constants/constants.dart';
|
|||||||
import 'package:immich_mobile/constants/locales.dart';
|
import 'package:immich_mobile/constants/locales.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
|
||||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
@ -41,18 +40,21 @@ import 'package:worker_manager/worker_manager.dart';
|
|||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
ImmichWidgetsBinding();
|
ImmichWidgetsBinding();
|
||||||
final db = await Bootstrap.initIsar();
|
final (isar, drift, logDb) = await Bootstrap.initDB();
|
||||||
final logDb = DriftLogger();
|
await Bootstrap.initDomain(isar, drift, logDb);
|
||||||
await Bootstrap.initDomain(db, logDb);
|
|
||||||
await initApp();
|
await initApp();
|
||||||
// Warm-up isolate pool for worker manager
|
// Warm-up isolate pool for worker manager
|
||||||
await workerManager.init(dynamicSpawning: true);
|
await workerManager.init(dynamicSpawning: true);
|
||||||
await migrateDatabaseIfNeeded(db);
|
await migrateDatabaseIfNeeded(isar, drift);
|
||||||
HttpSSLOptions.apply();
|
HttpSSLOptions.apply();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
overrides: [dbProvider.overrideWithValue(db), isarProvider.overrideWithValue(db)],
|
overrides: [
|
||||||
|
dbProvider.overrideWithValue(isar),
|
||||||
|
isarProvider.overrideWithValue(isar),
|
||||||
|
driftProvider.overrideWith(driftOverride(drift)),
|
||||||
|
],
|
||||||
child: const MainWidget(),
|
child: const MainWidget(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/providers/album/album.provider.dart';
|
import 'package:immich_mobile/providers/album/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset.provider.dart';
|
import 'package:immich_mobile/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
@ -13,8 +14,8 @@ import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
|
||||||
import 'package:immich_mobile/utils/migration.dart';
|
import 'package:immich_mobile/utils/migration.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@ -28,7 +29,7 @@ class ChangeExperiencePage extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||||
bool hasMigrated = false;
|
AsyncValue<bool> hasMigrated = const AsyncValue.loading();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -37,6 +38,7 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleMigration() async {
|
Future<void> _handleMigration() async {
|
||||||
|
try {
|
||||||
if (widget.switchingToBeta) {
|
if (widget.switchingToBeta) {
|
||||||
final assetNotifier = ref.read(assetProvider.notifier);
|
final assetNotifier = ref.read(assetProvider.notifier);
|
||||||
if (assetNotifier.mounted) {
|
if (assetNotifier.mounted) {
|
||||||
@ -65,19 +67,32 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
|||||||
await ref.read(backgroundSyncProvider).syncLocal(full: true);
|
await ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||||
await migrateDeviceAssetToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
await migrateDeviceAssetToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||||
await migrateBackupAlbumsToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
await migrateBackupAlbumsToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||||
|
await migrateStoreToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await ref.read(backgroundSyncProvider).cancel();
|
await ref.read(backgroundSyncProvider).cancel();
|
||||||
ref.read(websocketProvider.notifier).stopListeningToBetaEvents();
|
ref.read(websocketProvider.notifier).stopListeningToBetaEvents();
|
||||||
ref.read(websocketProvider.notifier).startListeningToOldEvents();
|
ref.read(websocketProvider.notifier).startListeningToOldEvents();
|
||||||
|
await migrateStoreToIsar(ref.read(isarProvider), ref.read(driftProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
||||||
|
await DriftStoreRepository(ref.read(driftProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
hasMigrated = true;
|
hasMigrated = const AsyncValue.data(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logger("ChangeExperiencePage").severe("Error during migration", e, s);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
hasMigrated = AsyncValue.error(e, s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -89,45 +104,35 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
|||||||
children: [
|
children: [
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: Durations.long4,
|
duration: Durations.long4,
|
||||||
child: hasMigrated
|
child: hasMigrated.when(
|
||||||
? const Icon(Icons.check_circle_rounded, color: Colors.green, size: 48.0)
|
data: (data) => const Icon(Icons.check_circle_rounded, color: Colors.green, size: 48.0),
|
||||||
: const SizedBox(width: 50.0, height: 50.0, child: CircularProgressIndicator()),
|
error: (error, stackTrace) => const Icon(Icons.error, color: Colors.red, size: 48.0),
|
||||||
|
loading: () => const SizedBox(width: 50.0, height: 50.0, child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 300.0,
|
width: 300.0,
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: Durations.long4,
|
duration: Durations.long4,
|
||||||
child: hasMigrated
|
child: hasMigrated.when(
|
||||||
? Text(
|
data: (data) => Text(
|
||||||
"Migration success!",
|
"Migration success!\nPlease close and reopen the app to apply changes",
|
||||||
style: context.textTheme.titleMedium,
|
style: context.textTheme.titleMedium,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
)
|
),
|
||||||
: Text(
|
error: (error, stackTrace) => Text(
|
||||||
|
"Migration failed!\nError: $error",
|
||||||
|
style: context.textTheme.titleMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
loading: () => Text(
|
||||||
"Data migration in progress...\nPlease wait and don't close this page",
|
"Data migration in progress...\nPlease wait and don't close this page",
|
||||||
style: context.textTheme.titleMedium,
|
style: context.textTheme.titleMedium,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (hasMigrated)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.replaceRoute(
|
|
||||||
widget.switchingToBeta ? const TabShellRoute() : const TabControllerRoute(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text("Continue"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -10,9 +10,12 @@ part 'db.provider.g.dart';
|
|||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
Isar isar(Ref ref) => throw UnimplementedError('isar');
|
Isar isar(Ref ref) => throw UnimplementedError('isar');
|
||||||
|
|
||||||
final driftProvider = Provider<Drift>((ref) {
|
Drift Function(Ref ref) driftOverride(Drift drift) => (ref) {
|
||||||
final drift = Drift();
|
|
||||||
ref.onDispose(() => unawaited(drift.close()));
|
ref.onDispose(() => unawaited(drift.close()));
|
||||||
ref.keepAlive();
|
ref.keepAlive();
|
||||||
return drift;
|
return drift;
|
||||||
});
|
};
|
||||||
|
|
||||||
|
final driftProvider = Provider<Drift>(
|
||||||
|
(ref) => throw UnimplementedError("driftProvider must be overridden in the isolate's ProviderContainer before use"),
|
||||||
|
);
|
||||||
|
@ -14,7 +14,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
|
||||||
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
||||||
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
|
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
|
||||||
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
|
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
|
||||||
@ -331,11 +330,16 @@ class BackgroundService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _onAssetsChanged() async {
|
Future<bool> _onAssetsChanged() async {
|
||||||
final db = await Bootstrap.initIsar();
|
final (isar, drift, logDb) = await Bootstrap.initDB();
|
||||||
final logDb = DriftLogger();
|
await Bootstrap.initDomain(isar, drift, logDb);
|
||||||
await Bootstrap.initDomain(db, logDb);
|
|
||||||
|
|
||||||
final ref = ProviderContainer(overrides: [dbProvider.overrideWithValue(db), isarProvider.overrideWithValue(db)]);
|
final ref = ProviderContainer(
|
||||||
|
overrides: [
|
||||||
|
dbProvider.overrideWithValue(isar),
|
||||||
|
isarProvider.overrideWithValue(isar),
|
||||||
|
driftProvider.overrideWith(driftOverride(drift)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
HttpSSLOptions.apply();
|
HttpSSLOptions.apply();
|
||||||
ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken));
|
ref.read(apiServiceProvider).setAccessToken(Store.get(StoreKey.accessToken));
|
||||||
|
@ -11,7 +11,6 @@ import 'package:immich_mobile/domain/services/user.service.dart';
|
|||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
@ -116,9 +115,8 @@ class BackupVerificationService {
|
|||||||
assert(tuple.deleteCandidates.length == tuple.originals.length);
|
assert(tuple.deleteCandidates.length == tuple.originals.length);
|
||||||
final List<Asset> result = [];
|
final List<Asset> result = [];
|
||||||
BackgroundIsolateBinaryMessenger.ensureInitialized(tuple.rootIsolateToken);
|
BackgroundIsolateBinaryMessenger.ensureInitialized(tuple.rootIsolateToken);
|
||||||
final db = await Bootstrap.initIsar();
|
final (isar, drift, logDb) = await Bootstrap.initDB();
|
||||||
final logDb = DriftLogger();
|
await Bootstrap.initDomain(isar, drift, logDb);
|
||||||
await Bootstrap.initDomain(db, logDb);
|
|
||||||
await tuple.fileMediaRepository.enableBackgroundAccess();
|
await tuple.fileMediaRepository.enableBackgroundAccess();
|
||||||
final ApiService apiService = ApiService();
|
final ApiService apiService = ApiService();
|
||||||
apiService.setEndpoint(tuple.endpoint);
|
apiService.setEndpoint(tuple.endpoint);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/domain/services/store.service.dart';
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
@ -14,6 +15,7 @@ import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.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/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
@ -21,18 +23,23 @@ import 'package:isar/isar.dart';
|
|||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
abstract final class Bootstrap {
|
abstract final class Bootstrap {
|
||||||
static Future<Isar> initIsar() async {
|
static Future<(Isar isar, Drift drift, DriftLogger logDb)> initDB() async {
|
||||||
if (Isar.getInstance() != null) {
|
final drift = Drift();
|
||||||
return Isar.getInstance()!;
|
final logDb = DriftLogger();
|
||||||
|
|
||||||
|
Isar? isar = Isar.getInstance();
|
||||||
|
|
||||||
|
if (isar != null) {
|
||||||
|
return (isar, drift, logDb);
|
||||||
}
|
}
|
||||||
|
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
return await Isar.open(
|
isar = await Isar.open(
|
||||||
[
|
[
|
||||||
StoreValueSchema,
|
StoreValueSchema,
|
||||||
ExifInfoSchema,
|
|
||||||
AssetSchema,
|
AssetSchema,
|
||||||
AlbumSchema,
|
AlbumSchema,
|
||||||
|
ExifInfoSchema,
|
||||||
UserSchema,
|
UserSchema,
|
||||||
BackupAlbumSchema,
|
BackupAlbumSchema,
|
||||||
DuplicatedAssetSchema,
|
DuplicatedAssetSchema,
|
||||||
@ -45,14 +52,19 @@ abstract final class Bootstrap {
|
|||||||
maxSizeMiB: 2048,
|
maxSizeMiB: 2048,
|
||||||
inspector: kDebugMode,
|
inspector: kDebugMode,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return (isar, drift, logDb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> initDomain(Isar db, DriftLogger logDb, {bool shouldBufferLogs = true}) async {
|
static Future<void> initDomain(Isar db, Drift drift, DriftLogger logDb, {bool shouldBufferLogs = true}) async {
|
||||||
await StoreService.init(storeRepository: IsarStoreRepository(db));
|
final isBeta = await IsarStoreRepository(db).tryGet(StoreKey.betaTimeline) ?? false;
|
||||||
|
final IStoreRepository storeRepo = isBeta ? DriftStoreRepository(drift) : IsarStoreRepository(db);
|
||||||
|
|
||||||
|
await StoreService.init(storeRepository: storeRepo);
|
||||||
|
|
||||||
await LogService.init(
|
await LogService.init(
|
||||||
logRepository: LogRepository(logDb),
|
logRepository: LogRepository(logDb),
|
||||||
storeRepository: IsarStoreRepository(db),
|
storeRepository: storeRepo,
|
||||||
shouldBuffer: shouldBufferLogs,
|
shouldBuffer: shouldBufferLogs,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
@ -35,15 +34,15 @@ Cancelable<T?> runInIsolateGentle<T>({
|
|||||||
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
|
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
|
||||||
DartPluginRegistrant.ensureInitialized();
|
DartPluginRegistrant.ensureInitialized();
|
||||||
|
|
||||||
final db = await Bootstrap.initIsar();
|
final (isar, drift, logDb) = await Bootstrap.initDB();
|
||||||
final logDb = DriftLogger();
|
await Bootstrap.initDomain(isar, drift, logDb, shouldBufferLogs: false);
|
||||||
await Bootstrap.initDomain(db, logDb, shouldBufferLogs: false);
|
|
||||||
final ref = ProviderContainer(
|
final ref = ProviderContainer(
|
||||||
overrides: [
|
overrides: [
|
||||||
// TODO: Remove once isar is removed
|
// TODO: Remove once isar is removed
|
||||||
dbProvider.overrideWithValue(db),
|
dbProvider.overrideWithValue(isar),
|
||||||
isarProvider.overrideWithValue(db),
|
isarProvider.overrideWithValue(isar),
|
||||||
cancellationProvider.overrideWithValue(cancelledChecker),
|
cancellationProvider.overrideWithValue(cancelledChecker),
|
||||||
|
driftProvider.overrideWith(driftOverride(drift)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
@ -30,9 +31,10 @@ import 'package:logging/logging.dart';
|
|||||||
// ignore: import_rule_photo_manager
|
// ignore: import_rule_photo_manager
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
const int targetVersion = 13;
|
const int targetVersion = 14;
|
||||||
|
|
||||||
Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
||||||
|
final hasVersion = Store.tryGet(StoreKey.version) != null;
|
||||||
final int version = Store.get(StoreKey.version, targetVersion);
|
final int version = Store.get(StoreKey.version, targetVersion);
|
||||||
|
|
||||||
if (version < 9) {
|
if (version < 9) {
|
||||||
@ -58,6 +60,12 @@ Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
|||||||
await Store.put(StoreKey.photoManagerCustomFilter, true);
|
await Store.put(StoreKey.photoManagerCustomFilter, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This means that the SQLite DB is just created and has no version
|
||||||
|
if (version < 14 || !hasVersion) {
|
||||||
|
await migrateStoreToSqlite(db, drift);
|
||||||
|
await Store.populateCache();
|
||||||
|
}
|
||||||
|
|
||||||
if (targetVersion >= 12) {
|
if (targetVersion >= 12) {
|
||||||
await Store.put(StoreKey.version, targetVersion);
|
await Store.put(StoreKey.version, targetVersion);
|
||||||
return;
|
return;
|
||||||
@ -215,6 +223,39 @@ Future<void> migrateBackupAlbumsToSqlite(Isar db, Drift drift) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> migrateStoreToSqlite(Isar db, Drift drift) async {
|
||||||
|
try {
|
||||||
|
final isarStoreValues = await db.storeValues.where().findAll();
|
||||||
|
await drift.batch((batch) {
|
||||||
|
for (final storeValue in isarStoreValues) {
|
||||||
|
final companion = StoreEntityCompanion(
|
||||||
|
id: Value(storeValue.id),
|
||||||
|
stringValue: Value(storeValue.strValue),
|
||||||
|
intValue: Value(storeValue.intValue),
|
||||||
|
);
|
||||||
|
batch.insert(drift.storeEntity, companion, onConflict: DoUpdate((_) => companion));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
debugPrint("[MIGRATION] Error while migrating store values to SQLite: $error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> migrateStoreToIsar(Isar db, Drift drift) async {
|
||||||
|
try {
|
||||||
|
final driftStoreValues = await drift.storeEntity
|
||||||
|
.select()
|
||||||
|
.map((entity) => StoreValue(entity.id, intValue: entity.intValue, strValue: entity.stringValue))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
await db.writeTxn(() async {
|
||||||
|
await db.storeValues.putAll(driftStoreValues);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
debugPrint("[MIGRATION] Error while migrating store values to Isar: $error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _DeviceAsset {
|
class _DeviceAsset {
|
||||||
final String assetId;
|
final String assetId;
|
||||||
final List<int>? hash;
|
final List<int>? hash;
|
||||||
|
@ -89,7 +89,6 @@ class _BetaTimelineListTileState extends ConsumerState<BetaTimelineListTile> wit
|
|||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.betaTimeline, value);
|
|
||||||
context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)]);
|
context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)]);
|
||||||
},
|
},
|
||||||
child: Text("ok".t(context: context)),
|
child: Text("ok".t(context: context)),
|
||||||
|
@ -64,12 +64,12 @@ void main() {
|
|||||||
|
|
||||||
group("Log Service Set Level:", () {
|
group("Log Service Set Level:", () {
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
when(() => mockStoreRepo.insert<int>(StoreKey.logLevel, any())).thenAnswer((_) async => true);
|
when(() => mockStoreRepo.upsert<int>(StoreKey.logLevel, any())).thenAnswer((_) async => true);
|
||||||
await sut.setLogLevel(LogLevel.shout);
|
await sut.setLogLevel(LogLevel.shout);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Updates the log level in store', () {
|
test('Updates the log level in store', () {
|
||||||
final index = verify(() => mockStoreRepo.insert<int>(StoreKey.logLevel, captureAny())).captured.firstOrNull;
|
final index = verify(() => mockStoreRepo.upsert<int>(StoreKey.logLevel, captureAny())).captured.firstOrNull;
|
||||||
expect(index, LogLevel.shout.index);
|
expect(index, LogLevel.shout.index);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,11 +16,13 @@ final _kBackupFailedSince = DateTime.utc(2023);
|
|||||||
void main() {
|
void main() {
|
||||||
late StoreService sut;
|
late StoreService sut;
|
||||||
late IsarStoreRepository mockStoreRepo;
|
late IsarStoreRepository mockStoreRepo;
|
||||||
late StreamController<StoreDto<Object>> controller;
|
late DriftStoreRepository mockDriftStoreRepo;
|
||||||
|
late StreamController<List<StoreDto<Object>>> controller;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
controller = StreamController<StoreDto<Object>>.broadcast();
|
controller = StreamController<List<StoreDto<Object>>>.broadcast();
|
||||||
mockStoreRepo = MockStoreRepository();
|
mockStoreRepo = MockStoreRepository();
|
||||||
|
mockDriftStoreRepo = MockDriftStoreRepository();
|
||||||
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
|
// For generics, we need to provide fallback to each concrete type to avoid runtime errors
|
||||||
registerFallbackValue(StoreKey.accessToken);
|
registerFallbackValue(StoreKey.accessToken);
|
||||||
registerFallbackValue(StoreKey.backupTriggerDelay);
|
registerFallbackValue(StoreKey.backupTriggerDelay);
|
||||||
@ -37,6 +39,16 @@ void main() {
|
|||||||
);
|
);
|
||||||
when(() => mockStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
|
when(() => mockStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
|
||||||
|
|
||||||
|
when(() => mockDriftStoreRepo.getAll()).thenAnswer(
|
||||||
|
(_) async => [
|
||||||
|
const StoreDto(StoreKey.accessToken, _kAccessToken),
|
||||||
|
const StoreDto(StoreKey.backgroundBackup, _kBackgroundBackup),
|
||||||
|
const StoreDto(StoreKey.groupAssetsBy, _kGroupAssetsBy),
|
||||||
|
StoreDto(StoreKey.backupFailedSince, _kBackupFailedSince),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
when(() => mockDriftStoreRepo.watchAll()).thenAnswer((_) => controller.stream);
|
||||||
|
|
||||||
sut = await StoreService.create(storeRepository: mockStoreRepo);
|
sut = await StoreService.create(storeRepository: mockStoreRepo);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,7 +70,7 @@ void main() {
|
|||||||
|
|
||||||
test('Listens to stream of store updates', () async {
|
test('Listens to stream of store updates', () async {
|
||||||
final event = StoreDto(StoreKey.accessToken, _kAccessToken.toUpperCase());
|
final event = StoreDto(StoreKey.accessToken, _kAccessToken.toUpperCase());
|
||||||
controller.add(event);
|
controller.add([event]);
|
||||||
|
|
||||||
await pumpEventQueue();
|
await pumpEventQueue();
|
||||||
|
|
||||||
@ -83,18 +95,19 @@ void main() {
|
|||||||
|
|
||||||
group('Store Service put:', () {
|
group('Store Service put:', () {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
when(() => mockStoreRepo.insert<String>(any<StoreKey<String>>(), any())).thenAnswer((_) async => true);
|
when(() => mockStoreRepo.upsert<String>(any<StoreKey<String>>(), any())).thenAnswer((_) async => true);
|
||||||
|
when(() => mockDriftStoreRepo.upsert<String>(any<StoreKey<String>>(), any())).thenAnswer((_) async => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Skip insert when value is not modified', () async {
|
test('Skip insert when value is not modified', () async {
|
||||||
await sut.put(StoreKey.accessToken, _kAccessToken);
|
await sut.put(StoreKey.accessToken, _kAccessToken);
|
||||||
verifyNever(() => mockStoreRepo.insert<String>(StoreKey.accessToken, any()));
|
verifyNever(() => mockStoreRepo.upsert<String>(StoreKey.accessToken, any()));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Insert value when modified', () async {
|
test('Insert value when modified', () async {
|
||||||
final newAccessToken = _kAccessToken.toUpperCase();
|
final newAccessToken = _kAccessToken.toUpperCase();
|
||||||
await sut.put(StoreKey.accessToken, newAccessToken);
|
await sut.put(StoreKey.accessToken, newAccessToken);
|
||||||
verify(() => mockStoreRepo.insert<String>(StoreKey.accessToken, newAccessToken)).called(1);
|
verify(() => mockStoreRepo.upsert<String>(StoreKey.accessToken, newAccessToken)).called(1);
|
||||||
expect(sut.tryGet(StoreKey.accessToken), newAccessToken);
|
expect(sut.tryGet(StoreKey.accessToken), newAccessToken);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -105,6 +118,7 @@ void main() {
|
|||||||
setUp(() {
|
setUp(() {
|
||||||
valueController = StreamController<String?>.broadcast();
|
valueController = StreamController<String?>.broadcast();
|
||||||
when(() => mockStoreRepo.watch<String>(any<StoreKey<String>>())).thenAnswer((_) => valueController.stream);
|
when(() => mockStoreRepo.watch<String>(any<StoreKey<String>>())).thenAnswer((_) => valueController.stream);
|
||||||
|
when(() => mockDriftStoreRepo.watch<String>(any<StoreKey<String>>())).thenAnswer((_) => valueController.stream);
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
@ -129,6 +143,7 @@ void main() {
|
|||||||
group('Store Service delete:', () {
|
group('Store Service delete:', () {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
when(() => mockStoreRepo.delete<String>(any<StoreKey<String>>())).thenAnswer((_) async => true);
|
when(() => mockStoreRepo.delete<String>(any<StoreKey<String>>())).thenAnswer((_) async => true);
|
||||||
|
when(() => mockDriftStoreRepo.delete<String>(any<StoreKey<String>>())).thenAnswer((_) async => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Removes the value from the DB', () async {
|
test('Removes the value from the DB', () async {
|
||||||
@ -145,6 +160,7 @@ void main() {
|
|||||||
group('Store Service clear:', () {
|
group('Store Service clear:', () {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
when(() => mockStoreRepo.deleteAll()).thenAnswer((_) async => true);
|
when(() => mockStoreRepo.deleteAll()).thenAnswer((_) async => true);
|
||||||
|
when(() => mockDriftStoreRepo.deleteAll()).thenAnswer((_) async => true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Clears all values from the store', () async {
|
test('Clears all values from the store', () async {
|
||||||
|
5
mobile/test/drift/main/generated/schema.dart
generated
5
mobile/test/drift/main/generated/schema.dart
generated
@ -10,6 +10,7 @@ import 'schema_v4.dart' as v4;
|
|||||||
import 'schema_v5.dart' as v5;
|
import 'schema_v5.dart' as v5;
|
||||||
import 'schema_v6.dart' as v6;
|
import 'schema_v6.dart' as v6;
|
||||||
import 'schema_v7.dart' as v7;
|
import 'schema_v7.dart' as v7;
|
||||||
|
import 'schema_v8.dart' as v8;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@ -29,10 +30,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v6.DatabaseAtV6(db);
|
return v6.DatabaseAtV6(db);
|
||||||
case 7:
|
case 7:
|
||||||
return v7.DatabaseAtV7(db);
|
return v7.DatabaseAtV7(db);
|
||||||
|
case 8:
|
||||||
|
return v8.DatabaseAtV8(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3, 4, 5, 6, 7];
|
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8];
|
||||||
}
|
}
|
||||||
|
6663
mobile/test/drift/main/generated/schema_v8.dart
generated
Normal file
6663
mobile/test/drift/main/generated/schema_v8.dart
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -44,7 +44,7 @@ void main() {
|
|||||||
test('converts int', () async {
|
test('converts int', () async {
|
||||||
int? version = await sut.tryGet(StoreKey.version);
|
int? version = await sut.tryGet(StoreKey.version);
|
||||||
expect(version, isNull);
|
expect(version, isNull);
|
||||||
await sut.insert(StoreKey.version, _kTestVersion);
|
await sut.upsert(StoreKey.version, _kTestVersion);
|
||||||
version = await sut.tryGet(StoreKey.version);
|
version = await sut.tryGet(StoreKey.version);
|
||||||
expect(version, _kTestVersion);
|
expect(version, _kTestVersion);
|
||||||
});
|
});
|
||||||
@ -52,7 +52,7 @@ void main() {
|
|||||||
test('converts string', () async {
|
test('converts string', () async {
|
||||||
String? accessToken = await sut.tryGet(StoreKey.accessToken);
|
String? accessToken = await sut.tryGet(StoreKey.accessToken);
|
||||||
expect(accessToken, isNull);
|
expect(accessToken, isNull);
|
||||||
await sut.insert(StoreKey.accessToken, _kTestAccessToken);
|
await sut.upsert(StoreKey.accessToken, _kTestAccessToken);
|
||||||
accessToken = await sut.tryGet(StoreKey.accessToken);
|
accessToken = await sut.tryGet(StoreKey.accessToken);
|
||||||
expect(accessToken, _kTestAccessToken);
|
expect(accessToken, _kTestAccessToken);
|
||||||
});
|
});
|
||||||
@ -60,7 +60,7 @@ void main() {
|
|||||||
test('converts datetime', () async {
|
test('converts datetime', () async {
|
||||||
DateTime? backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince);
|
DateTime? backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince);
|
||||||
expect(backupFailedSince, isNull);
|
expect(backupFailedSince, isNull);
|
||||||
await sut.insert(StoreKey.backupFailedSince, _kTestBackupFailed);
|
await sut.upsert(StoreKey.backupFailedSince, _kTestBackupFailed);
|
||||||
backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince);
|
backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince);
|
||||||
expect(backupFailedSince, _kTestBackupFailed);
|
expect(backupFailedSince, _kTestBackupFailed);
|
||||||
});
|
});
|
||||||
@ -68,7 +68,7 @@ void main() {
|
|||||||
test('converts bool', () async {
|
test('converts bool', () async {
|
||||||
bool? colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface);
|
bool? colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface);
|
||||||
expect(colorfulInterface, isNull);
|
expect(colorfulInterface, isNull);
|
||||||
await sut.insert(StoreKey.colorfulInterface, _kTestColorfulInterface);
|
await sut.upsert(StoreKey.colorfulInterface, _kTestColorfulInterface);
|
||||||
colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface);
|
colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface);
|
||||||
expect(colorfulInterface, _kTestColorfulInterface);
|
expect(colorfulInterface, _kTestColorfulInterface);
|
||||||
});
|
});
|
||||||
@ -76,7 +76,7 @@ void main() {
|
|||||||
test('converts user', () async {
|
test('converts user', () async {
|
||||||
UserDto? user = await sut.tryGet(StoreKey.currentUser);
|
UserDto? user = await sut.tryGet(StoreKey.currentUser);
|
||||||
expect(user, isNull);
|
expect(user, isNull);
|
||||||
await sut.insert(StoreKey.currentUser, _kTestUser);
|
await sut.upsert(StoreKey.currentUser, _kTestUser);
|
||||||
user = await sut.tryGet(StoreKey.currentUser);
|
user = await sut.tryGet(StoreKey.currentUser);
|
||||||
expect(user, _kTestUser);
|
expect(user, _kTestUser);
|
||||||
});
|
});
|
||||||
@ -108,10 +108,10 @@ void main() {
|
|||||||
await _populateStore(db);
|
await _populateStore(db);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('update()', () async {
|
test('upsert()', () async {
|
||||||
int? version = await sut.tryGet(StoreKey.version);
|
int? version = await sut.tryGet(StoreKey.version);
|
||||||
expect(version, _kTestVersion);
|
expect(version, _kTestVersion);
|
||||||
await sut.update(StoreKey.version, _kTestVersion + 10);
|
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||||
version = await sut.tryGet(StoreKey.version);
|
version = await sut.tryGet(StoreKey.version);
|
||||||
expect(version, _kTestVersion + 10);
|
expect(version, _kTestVersion + 10);
|
||||||
});
|
});
|
||||||
@ -126,22 +126,29 @@ void main() {
|
|||||||
final stream = sut.watch(StoreKey.version);
|
final stream = sut.watch(StoreKey.version);
|
||||||
expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10]));
|
expectLater(stream, emitsInOrder([_kTestVersion, _kTestVersion + 10]));
|
||||||
await pumpEventQueue();
|
await pumpEventQueue();
|
||||||
await sut.update(StoreKey.version, _kTestVersion + 10);
|
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('watchAll()', () async {
|
test('watchAll()', () async {
|
||||||
final stream = sut.watchAll();
|
final stream = sut.watchAll();
|
||||||
expectLater(
|
expectLater(
|
||||||
stream,
|
stream,
|
||||||
emitsInAnyOrder([
|
emitsInOrder([
|
||||||
emits(const StoreDto<Object>(StoreKey.version, _kTestVersion)),
|
[
|
||||||
emits(StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed)),
|
const StoreDto<Object>(StoreKey.version, _kTestVersion),
|
||||||
emits(const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken)),
|
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||||
emits(const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface)),
|
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||||
emits(const StoreDto<Object>(StoreKey.version, _kTestVersion + 10)),
|
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
const StoreDto<Object>(StoreKey.version, _kTestVersion + 10),
|
||||||
|
StoreDto<Object>(StoreKey.backupFailedSince, _kTestBackupFailed),
|
||||||
|
const StoreDto<Object>(StoreKey.accessToken, _kTestAccessToken),
|
||||||
|
const StoreDto<Object>(StoreKey.colorfulInterface, _kTestColorfulInterface),
|
||||||
|
],
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
await sut.update(StoreKey.version, _kTestVersion + 10);
|
await sut.upsert(StoreKey.version, _kTestVersion + 10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ import 'package:mocktail/mocktail.dart';
|
|||||||
|
|
||||||
class MockStoreRepository extends Mock implements IsarStoreRepository {}
|
class MockStoreRepository extends Mock implements IsarStoreRepository {}
|
||||||
|
|
||||||
|
class MockDriftStoreRepository extends Mock implements DriftStoreRepository {}
|
||||||
|
|
||||||
class MockLogRepository extends Mock implements LogRepository {}
|
class MockLogRepository extends Mock implements LogRepository {}
|
||||||
|
|
||||||
class MockIsarUserRepository extends Mock implements IsarUserRepository {}
|
class MockIsarUserRepository extends Mock implements IsarUserRepository {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user