refactor(mobile): migrate all Hive boxes to Isar database (#2036)

This commit is contained in:
Fynn Petersen-Frey
2023-03-23 02:36:44 +01:00
committed by GitHub
parent 0616a66b05
commit eccde8fa07
33 changed files with 1540 additions and 383 deletions

View File

@@ -1,6 +1,5 @@
import 'package:immich_mobile/shared/models/exif_info.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/utils/hash.dart';
import 'package:isar/isar.dart';
import 'package:openapi/api.dart';
@@ -40,7 +39,7 @@ class Asset {
width = local.width,
fileName = local.title!,
deviceId = Store.get(StoreKey.deviceIdHash),
ownerId = Store.get<User>(StoreKey.currentUser)!.isarId,
ownerId = Store.get(StoreKey.currentUser).isarId,
fileModifiedAt = local.modifiedDateTime.toUtc(),
updatedAt = local.modifiedDateTime.toUtc(),
isFavorite = local.isFavorite,

View File

@@ -0,0 +1,48 @@
// ignore_for_file: constant_identifier_names
import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
part 'logger_message.model.g.dart';
@Collection(inheritance: false)
class LoggerMessage {
Id id = Isar.autoIncrement;
String message;
@Enumerated(EnumType.ordinal)
LogLevel level = LogLevel.INFO;
DateTime createdAt;
String? context1;
String? context2;
LoggerMessage({
required this.message,
required this.level,
required this.createdAt,
required this.context1,
required this.context2,
});
@override
String toString() {
return 'InAppLoggerMessage(message: $message, level: $level, createdAt: $createdAt)';
}
}
/// Log levels according to dart logging [Level]
enum LogLevel {
ALL,
FINEST,
FINER,
FINE,
CONFIG,
INFO,
WARNING,
SEVERE,
SHOUT,
OFF,
}
extension LevelExtension on Level {
LogLevel toLogLevel() => LogLevel.values[Level.LEVELS.indexOf(this)];
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
import 'package:collection/collection.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:isar/isar.dart';
import 'dart:convert';
part 'store.g.dart';
@@ -26,12 +25,21 @@ class Store {
return _db.writeTxn(() => _db.storeValues.clear());
}
/// Returns the stored value for the given key, or the default value if null
static T? get<T>(StoreKey key, [T? defaultValue]) =>
_cache[key.id] ?? defaultValue;
/// Returns the stored value for the given key or if null the [defaultValue]
/// Throws a [StoreKeyNotFoundException] if both are null
static T get<T>(StoreKey<T> key, [T? defaultValue]) {
final value = _cache[key.id] ?? defaultValue;
if (value == null) {
throw StoreKeyNotFoundException(key);
}
return value;
}
/// Returns the stored value for the given key (possibly null)
static T? tryGet<T>(StoreKey<T> key) => _cache[key.id];
/// Stores the value synchronously in the cache and asynchronously in the DB
static Future<void> put<T>(StoreKey key, T value) {
static Future<void> put<T>(StoreKey<T> key, T value) {
_cache[key.id] = value;
return _db.writeTxn(
() async => _db.storeValues.put(await StoreValue._of(value, key)),
@@ -39,7 +47,7 @@ class Store {
}
/// Removes the value synchronously from the cache and asynchronously from the DB
static Future<void> delete(StoreKey key) {
static Future<void> delete<T>(StoreKey<T> key) {
_cache[key.id] = null;
return _db.writeTxn(() => _db.storeValues.delete(key.id));
}
@@ -58,7 +66,8 @@ class Store {
static void _onChangeListener(List<StoreValue>? data) {
if (data != null) {
for (StoreValue value in data) {
_cache[value.id] = value._extract(StoreKey.values[value.id]);
_cache[value.id] =
value._extract(StoreKey.values.firstWhere((e) => e.id == value.id));
}
}
}
@@ -72,76 +81,113 @@ class StoreValue {
int? intValue;
String? strValue;
dynamic _extract(StoreKey key) {
T? _extract<T>(StoreKey<T> key) {
switch (key.type) {
case int:
return key.fromDb == null
? intValue
: key.fromDb!.call(Store._db, intValue!);
return intValue as T?;
case bool:
return intValue == null ? null : intValue! == 1;
return intValue == null ? null : (intValue! == 1) as T;
case DateTime:
return intValue == null
? null
: DateTime.fromMicrosecondsSinceEpoch(intValue!);
: DateTime.fromMicrosecondsSinceEpoch(intValue!) as T;
case String:
return key.fromJson != null
? key.fromJson!.call(json.decode(strValue!))
: strValue;
return strValue as T?;
default:
if (key.fromDb != null) {
return key.fromDb!.call(Store._db, intValue!);
}
}
throw TypeError();
}
static Future<StoreValue> _of(dynamic value, StoreKey key) async {
static Future<StoreValue> _of<T>(T? value, StoreKey<T> key) async {
int? i;
String? s;
switch (key.type) {
case int:
i = (key.toDb == null ? value : await key.toDb!.call(Store._db, value));
i = value as int?;
break;
case bool:
i = value == null ? null : (value ? 1 : 0);
i = value == null ? null : (value == true ? 1 : 0);
break;
case DateTime:
i = value == null ? null : (value as DateTime).microsecondsSinceEpoch;
break;
case String:
s = key.fromJson == null ? value : json.encode(value.toJson());
s = value as String?;
break;
default:
if (key.toDb != null) {
i = await key.toDb!.call(Store._db, value);
break;
}
throw TypeError();
}
return StoreValue(key.id, intValue: i, strValue: s);
}
}
class StoreKeyNotFoundException implements Exception {
final StoreKey key;
StoreKeyNotFoundException(this.key);
@override
String toString() => "Key '${key.name}' not found in Store";
}
/// Key for each possible value in the `Store`.
/// Defines the data type (int, String, JSON) for each value
enum StoreKey {
userRemoteId(0),
assetETag(1),
currentUser(2, type: int, fromDb: _getUser, toDb: _toUser),
deviceIdHash(3, type: int),
deviceId(4),
backupFailedSince(5, type: DateTime),
backupRequireWifi(6, type: bool),
backupRequireCharging(7, type: bool),
backupTriggerDelay(8, type: int);
/// Defines the data type for each value
enum StoreKey<T> {
userRemoteId<String>(0, type: String),
assetETag<String>(1, type: String),
currentUser<User>(2, type: User, fromDb: _getUser, toDb: _toUser),
deviceIdHash<int>(3, type: int),
deviceId<String>(4, type: String),
backupFailedSince<DateTime>(5, type: DateTime),
backupRequireWifi<bool>(6, type: bool),
backupRequireCharging<bool>(7, type: bool),
backupTriggerDelay<int>(8, type: int),
githubReleaseInfo<String>(9, type: String),
serverUrl<String>(10, type: String),
accessToken<String>(11, type: String),
serverEndpoint<String>(12, type: String),
// user settings from [AppSettingsEnum] below:
loadPreview<bool>(100, type: bool),
loadOriginal<bool>(101, type: bool),
themeMode<String>(102, type: String),
tilesPerRow<int>(103, type: int),
dynamicLayout<bool>(104, type: bool),
groupAssetsBy<int>(105, type: int),
uploadErrorNotificationGracePeriod<int>(106, type: int),
backgroundBackupTotalProgress<bool>(107, type: bool),
backgroundBackupSingleProgress<bool>(108, type: bool),
storageIndicator<bool>(109, type: bool),
thumbnailCacheSize<int>(110, type: int),
imageCacheSize<int>(111, type: int),
albumThumbnailCacheSize<int>(112, type: int),
selectedAlbumSortOrder<int>(113, type: int),
;
const StoreKey(
this.id, {
this.type = String,
required this.type,
this.fromDb,
this.toDb,
// ignore: unused_element
this.fromJson,
});
final int id;
final Type type;
final dynamic Function(Isar, int)? fromDb;
final Future<int> Function(Isar, dynamic)? toDb;
final Function(dynamic)? fromJson;
final T? Function<T>(Isar, int)? fromDb;
final Future<int> Function<T>(Isar, T)? toDb;
}
User? _getUser(Isar db, int i) => db.users.getSync(i);
Future<int> _toUser(Isar db, dynamic u) {
User user = (u as User);
return db.users.put(user);
T? _getUser<T>(Isar db, int i) {
final User? u = db.users.getSync(i);
return u as T?;
}
Future<int> _toUser<T>(Isar db, T u) {
if (u is User) {
return db.users.put(u);
}
throw TypeError();
}