mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	refactor(mobile): migrate all Hive boxes to Isar database (#2036)
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							0616a66b05
						
					
				
				
					commit
					eccde8fa07
				
			@@ -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,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								mobile/lib/shared/models/logger_message.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								mobile/lib/shared/models/logger_message.model.dart
									
									
									
									
									
										Normal 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)];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1092
									
								
								mobile/lib/shared/models/logger_message.model.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1092
									
								
								mobile/lib/shared/models/logger_message.model.g.dart
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user