mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 04:05:39 -04:00
feature(mobile): hash assets & sync via checksum (#2592)
* compare different sha1 implementations * remove openssl sha1 * sync via checksum * hash assets in batches * hash in background, show spinner in tab * undo tmp changes * migrate by clearing assets * ignore duplicate assets * error handling * trigger sync/merge after download and update view * review feedback improvements * hash in background isolate on iOS * rework linking assets with existing from DB * fine-grained errors on unique index violation * hash lenth validation * revert compute in background on iOS * ignore duplicate assets on device * fix bug with batching based on accumulated size --------- Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
This commit is contained in:
parent
053a0482b4
commit
73075c64d1
@ -84,6 +84,7 @@ flutter {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||||
implementation "androidx.concurrent:concurrent-futures:$concurrent_version"
|
implementation "androidx.concurrent:concurrent-futures:$concurrent_version"
|
||||||
implementation "com.google.guava:guava:$guava_version"
|
implementation "com.google.guava:guava:$guava_version"
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package app.alextran.immich
|
package app.alextran.immich
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
import io.flutter.plugin.common.BinaryMessenger
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Android plugin for Dart `BackgroundService`
|
* Android plugin for Dart `BackgroundService`
|
||||||
@ -16,6 +21,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
|
|
||||||
private var methodChannel: MethodChannel? = null
|
private var methodChannel: MethodChannel? = null
|
||||||
private var context: Context? = null
|
private var context: Context? = null
|
||||||
|
private val sha1: MessageDigest = MessageDigest.getInstance("SHA-1")
|
||||||
|
|
||||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
|
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
|
||||||
@ -70,9 +76,40 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
"isIgnoringBatteryOptimizations" -> {
|
"isIgnoringBatteryOptimizations" -> {
|
||||||
result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx))
|
result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx))
|
||||||
}
|
}
|
||||||
|
"digestFiles" -> {
|
||||||
|
val args = call.arguments<ArrayList<String>>()!!
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val buf = ByteArray(BUFSIZE)
|
||||||
|
val digest: MessageDigest = MessageDigest.getInstance("SHA-1")
|
||||||
|
val hashes = arrayOfNulls<ByteArray>(args.size)
|
||||||
|
for (i in args.indices) {
|
||||||
|
val path = args[i]
|
||||||
|
var len = 0
|
||||||
|
try {
|
||||||
|
val file = FileInputStream(path)
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
len = file.read(buf)
|
||||||
|
if (len != BUFSIZE) break
|
||||||
|
digest.update(buf)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
file.close()
|
||||||
|
}
|
||||||
|
digest.update(buf, 0, len)
|
||||||
|
hashes[i] = digest.digest()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// skip this file
|
||||||
|
Log.w(TAG, "Failed to hash file ${args[i]}: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.success(hashes.asList())
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "BackgroundServicePlugin"
|
private const val TAG = "BackgroundServicePlugin"
|
||||||
|
private const val BUFSIZE = 2*1024*1024;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.8.20'
|
ext.kotlin_version = '1.8.20'
|
||||||
|
ext.kotlin_coroutines_version = '1.7.1'
|
||||||
ext.work_version = '2.7.1'
|
ext.work_version = '2.7.1'
|
||||||
ext.concurrent_version = '1.1.0'
|
ext.concurrent_version = '1.1.0'
|
||||||
ext.guava_version = '31.0.1-android'
|
ext.guava_version = '31.0.1-android'
|
||||||
|
@ -19,9 +19,11 @@ import 'package:immich_mobile/modules/settings/providers/notification_permission
|
|||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
||||||
import 'package:immich_mobile/shared/models/album.dart';
|
import 'package:immich_mobile/shared/models/album.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/android_device_asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/etag.dart';
|
import 'package:immich_mobile/shared/models/etag.dart';
|
||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/ios_device_asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
@ -91,6 +93,7 @@ Future<Isar> loadDb() async {
|
|||||||
DuplicatedAssetSchema,
|
DuplicatedAssetSchema,
|
||||||
LoggerMessageSchema,
|
LoggerMessageSchema,
|
||||||
ETagSchema,
|
ETagSchema,
|
||||||
|
Platform.isAndroid ? AndroidDeviceAssetSchema : IOSDeviceAssetSchema,
|
||||||
],
|
],
|
||||||
directory: dir.path,
|
directory: dir.path,
|
||||||
maxSizeMiB: 256,
|
maxSizeMiB: 256,
|
||||||
|
@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
|
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/services/image_viewer.service.dart';
|
import 'package:immich_mobile/modules/asset_viewer/services/image_viewer.service.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
@ -12,9 +13,13 @@ import 'package:immich_mobile/shared/ui/share_dialog.dart';
|
|||||||
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
||||||
final ImageViewerService _imageViewerService;
|
final ImageViewerService _imageViewerService;
|
||||||
final ShareService _shareService;
|
final ShareService _shareService;
|
||||||
|
final AlbumService _albumService;
|
||||||
|
|
||||||
ImageViewerStateNotifier(this._imageViewerService, this._shareService)
|
ImageViewerStateNotifier(
|
||||||
: super(
|
this._imageViewerService,
|
||||||
|
this._shareService,
|
||||||
|
this._albumService,
|
||||||
|
) : super(
|
||||||
ImageViewerPageState(
|
ImageViewerPageState(
|
||||||
downloadAssetStatus: DownloadAssetStatus.idle,
|
downloadAssetStatus: DownloadAssetStatus.idle,
|
||||||
),
|
),
|
||||||
@ -34,6 +39,7 @@ class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
|||||||
toastType: ToastType.success,
|
toastType: ToastType.success,
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
|
_albumService.refreshDeviceAlbums();
|
||||||
} else {
|
} else {
|
||||||
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.error);
|
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.error);
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
@ -66,5 +72,6 @@ final imageViewerStateProvider =
|
|||||||
((ref) => ImageViewerStateNotifier(
|
((ref) => ImageViewerStateNotifier(
|
||||||
ref.watch(imageViewerServiceProvider),
|
ref.watch(imageViewerServiceProvider),
|
||||||
ref.watch(shareServiceProvider),
|
ref.watch(shareServiceProvider),
|
||||||
|
ref.watch(albumServiceProvider),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
@ -72,15 +72,7 @@ class TopControlAppBar extends HookConsumerWidget {
|
|||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!asset.isLocal)
|
if (asset.storage == AssetState.remote)
|
||||||
IconButton(
|
|
||||||
onPressed: onDownloadPressed,
|
|
||||||
icon: Icon(
|
|
||||||
Icons.cloud_download_outlined,
|
|
||||||
color: Colors.grey[200],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (asset.storage == AssetState.merged)
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: onDownloadPressed,
|
onPressed: onDownloadPressed,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
|
@ -287,7 +287,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
isFavorite: asset().isFavorite,
|
isFavorite: asset().isFavorite,
|
||||||
onMoreInfoPressed: showInfo,
|
onMoreInfoPressed: showInfo,
|
||||||
onFavorite: asset().isRemote ? () => toggleFavorite(asset()) : null,
|
onFavorite: asset().isRemote ? () => toggleFavorite(asset()) : null,
|
||||||
onDownloadPressed: asset().storage == AssetState.local
|
onDownloadPressed: asset().isLocal
|
||||||
? null
|
? null
|
||||||
: () =>
|
: () =>
|
||||||
ref.watch(imageViewerStateProvider.notifier).downloadAsset(
|
ref.watch(imageViewerStateProvider.notifier).downloadAsset(
|
||||||
|
@ -132,6 +132,17 @@ class BackgroundService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Uint8List?> digestFile(String path) {
|
||||||
|
return _foregroundChannel.invokeMethod<Uint8List>("digestFile", [path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Uint8List?>?> digestFiles(List<String> paths) {
|
||||||
|
return _foregroundChannel.invokeListMethod<Uint8List?>(
|
||||||
|
"digestFiles",
|
||||||
|
paths,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the notification shown by the background service
|
/// Updates the notification shown by the background service
|
||||||
Future<bool?> _updateNotification({
|
Future<bool?> _updateNotification({
|
||||||
String? title,
|
String? title,
|
||||||
|
@ -47,11 +47,11 @@ class HomePage extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
ref.watch(websocketProvider.notifier).connect();
|
ref.read(websocketProvider.notifier).connect();
|
||||||
ref.watch(assetProvider.notifier).getAllAsset();
|
Future(() => ref.read(assetProvider.notifier).getAllAsset());
|
||||||
ref.watch(albumProvider.notifier).getAllAlbums();
|
ref.read(albumProvider.notifier).getAllAlbums();
|
||||||
ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||||
ref.watch(serverInfoProvider.notifier).getServerVersion();
|
ref.read(serverInfoProvider.notifier).getServerVersion();
|
||||||
|
|
||||||
selectionEnabledHook.addListener(() {
|
selectionEnabledHook.addListener(() {
|
||||||
multiselectEnabled.state = selectionEnabledHook.value;
|
multiselectEnabled.state = selectionEnabledHook.value;
|
||||||
@ -144,7 +144,7 @@ class HomePage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
if (remoteAssets.isNotEmpty) {
|
if (remoteAssets.isNotEmpty) {
|
||||||
await ref
|
await ref
|
||||||
.watch(assetProvider.notifier)
|
.read(assetProvider.notifier)
|
||||||
.toggleArchive(remoteAssets, true);
|
.toggleArchive(remoteAssets, true);
|
||||||
|
|
||||||
final assetOrAssets = remoteAssets.length > 1 ? 'assets' : 'asset';
|
final assetOrAssets = remoteAssets.length > 1 ? 'assets' : 'asset';
|
||||||
@ -163,7 +163,7 @@ class HomePage extends HookConsumerWidget {
|
|||||||
void onDelete() async {
|
void onDelete() async {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
try {
|
try {
|
||||||
await ref.watch(assetProvider.notifier).deleteAssets(selection.value);
|
await ref.read(assetProvider.notifier).deleteAssets(selection.value);
|
||||||
selectionEnabledHook.value = false;
|
selectionEnabledHook.value = false;
|
||||||
} finally {
|
} finally {
|
||||||
processing.value = false;
|
processing.value = false;
|
||||||
|
@ -166,23 +166,10 @@ extension AssetsHelper on IsarCollection<Album> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AssetPathEntityHelper on AssetPathEntity {
|
|
||||||
Future<List<Asset>> getAssets({
|
|
||||||
int start = 0,
|
|
||||||
int end = 0x7fffffffffffffff,
|
|
||||||
Set<String>? excludedAssets,
|
|
||||||
}) async {
|
|
||||||
final assetEntities = await getAssetListRange(start: start, end: end);
|
|
||||||
if (excludedAssets != null) {
|
|
||||||
return assetEntities
|
|
||||||
.where((e) => !excludedAssets.contains(e.id))
|
|
||||||
.map(Asset.local)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
return assetEntities.map(Asset.local).toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AlbumResponseDtoHelper on AlbumResponseDto {
|
extension AlbumResponseDtoHelper on AlbumResponseDto {
|
||||||
List<Asset> getAssets() => assets.map(Asset.remote).toList();
|
List<Asset> getAssets() => assets.map(Asset.remote).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AssetPathEntityHelper on AssetPathEntity {
|
||||||
|
String get eTagKeyAssetCount => "device-album-$id-asset-count";
|
||||||
|
}
|
||||||
|
10
mobile/lib/shared/models/android_device_asset.dart
Normal file
10
mobile/lib/shared/models/android_device_asset.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:immich_mobile/shared/models/device_asset.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
part 'android_device_asset.g.dart';
|
||||||
|
|
||||||
|
@Collection()
|
||||||
|
class AndroidDeviceAsset extends DeviceAsset {
|
||||||
|
AndroidDeviceAsset({required this.id, required super.hash});
|
||||||
|
Id id;
|
||||||
|
}
|
493
mobile/lib/shared/models/android_device_asset.g.dart
Normal file
493
mobile/lib/shared/models/android_device_asset.g.dart
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'android_device_asset.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// IsarCollectionGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||||
|
|
||||||
|
extension GetAndroidDeviceAssetCollection on Isar {
|
||||||
|
IsarCollection<AndroidDeviceAsset> get androidDeviceAssets =>
|
||||||
|
this.collection();
|
||||||
|
}
|
||||||
|
|
||||||
|
const AndroidDeviceAssetSchema = CollectionSchema(
|
||||||
|
name: r'AndroidDeviceAsset',
|
||||||
|
id: -6758387181232899335,
|
||||||
|
properties: {
|
||||||
|
r'hash': PropertySchema(
|
||||||
|
id: 0,
|
||||||
|
name: r'hash',
|
||||||
|
type: IsarType.byteList,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
estimateSize: _androidDeviceAssetEstimateSize,
|
||||||
|
serialize: _androidDeviceAssetSerialize,
|
||||||
|
deserialize: _androidDeviceAssetDeserialize,
|
||||||
|
deserializeProp: _androidDeviceAssetDeserializeProp,
|
||||||
|
idName: r'id',
|
||||||
|
indexes: {
|
||||||
|
r'hash': IndexSchema(
|
||||||
|
id: -7973251393006690288,
|
||||||
|
name: r'hash',
|
||||||
|
unique: false,
|
||||||
|
replace: false,
|
||||||
|
properties: [
|
||||||
|
IndexPropertySchema(
|
||||||
|
name: r'hash',
|
||||||
|
type: IndexType.hash,
|
||||||
|
caseSensitive: false,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
links: {},
|
||||||
|
embeddedSchemas: {},
|
||||||
|
getId: _androidDeviceAssetGetId,
|
||||||
|
getLinks: _androidDeviceAssetGetLinks,
|
||||||
|
attach: _androidDeviceAssetAttach,
|
||||||
|
version: '3.1.0+1',
|
||||||
|
);
|
||||||
|
|
||||||
|
int _androidDeviceAssetEstimateSize(
|
||||||
|
AndroidDeviceAsset object,
|
||||||
|
List<int> offsets,
|
||||||
|
Map<Type, List<int>> allOffsets,
|
||||||
|
) {
|
||||||
|
var bytesCount = offsets.last;
|
||||||
|
bytesCount += 3 + object.hash.length;
|
||||||
|
return bytesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _androidDeviceAssetSerialize(
|
||||||
|
AndroidDeviceAsset object,
|
||||||
|
IsarWriter writer,
|
||||||
|
List<int> offsets,
|
||||||
|
Map<Type, List<int>> allOffsets,
|
||||||
|
) {
|
||||||
|
writer.writeByteList(offsets[0], object.hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidDeviceAsset _androidDeviceAssetDeserialize(
|
||||||
|
Id id,
|
||||||
|
IsarReader reader,
|
||||||
|
List<int> offsets,
|
||||||
|
Map<Type, List<int>> allOffsets,
|
||||||
|
) {
|
||||||
|
final object = AndroidDeviceAsset(
|
||||||
|
hash: reader.readByteList(offsets[0]) ?? [],
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
P _androidDeviceAssetDeserializeProp<P>(
|
||||||
|
IsarReader reader,
|
||||||
|
int propertyId,
|
||||||
|
int offset,
|
||||||
|
Map<Type, List<int>> allOffsets,
|
||||||
|
) {
|
||||||
|
switch (propertyId) {
|
||||||
|
case 0:
|
||||||
|
return (reader.readByteList(offset) ?? []) as P;
|
||||||
|
default:
|
||||||
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id _androidDeviceAssetGetId(AndroidDeviceAsset object) {
|
||||||
|
return object.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IsarLinkBase<dynamic>> _androidDeviceAssetGetLinks(
|
||||||
|
AndroidDeviceAsset object) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
void _androidDeviceAssetAttach(
|
||||||
|
IsarCollection<dynamic> col, Id id, AndroidDeviceAsset object) {
|
||||||
|
object.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQueryWhereSort
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QWhere> {
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhere> anyId() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(const IdWhereClause.any());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQueryWhere
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QWhereClause> {
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
|
||||||
|
idEqualTo(Id id) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IdWhereClause.between(
|
||||||
|
lower: id,
|
||||||
|
upper: id,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
|
||||||
|
idNotEqualTo(Id id) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
if (query.whereSort == Sort.asc) {
|
||||||
|
return query
|
||||||
|
.addWhereClause(
|
||||||
|
IdWhereClause.lessThan(upper: id, includeUpper: false),
|
||||||
|
)
|
||||||
|
.addWhereClause(
|
||||||
|
IdWhereClause.greaterThan(lower: id, includeLower: false),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return query
|
||||||
|
.addWhereClause(
|
||||||
|
IdWhereClause.greaterThan(lower: id, includeLower: false),
|
||||||
|
)
|
||||||
|
.addWhereClause(
|
||||||
|
IdWhereClause.lessThan(upper: id, includeUpper: false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
|
||||||
|
idGreaterThan(Id id, {bool include = false}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(
|
||||||
|
IdWhereClause.greaterThan(lower: id, includeLower: include),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
|
||||||
|
idLessThan(Id id, {bool include = false}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(
|
||||||
|
IdWhereClause.lessThan(upper: id, includeUpper: include),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
|
||||||
|
idBetween(
|
||||||
|
Id lowerId,
|
||||||
|
Id upperId, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IdWhereClause.between(
|
||||||
|
lower: lowerId,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upperId,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
|
||||||
|
hashEqualTo(List<int> hash) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||||
|
indexName: r'hash',
|
||||||
|
value: [hash],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterWhereClause>
|
||||||
|
hashNotEqualTo(List<int> hash) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
if (query.whereSort == Sort.asc) {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'hash',
|
||||||
|
lower: [],
|
||||||
|
upper: [hash],
|
||||||
|
includeUpper: false,
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'hash',
|
||||||
|
lower: [hash],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'hash',
|
||||||
|
lower: [hash],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'hash',
|
||||||
|
lower: [],
|
||||||
|
upper: [hash],
|
||||||
|
includeUpper: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQueryFilter
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QFilterCondition> {
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashElementEqualTo(int value) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'hash',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashElementGreaterThan(
|
||||||
|
int value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'hash',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashElementLessThan(
|
||||||
|
int value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'hash',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashElementBetween(
|
||||||
|
int lower,
|
||||||
|
int upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'hash',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashLengthEqualTo(int length) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
length,
|
||||||
|
true,
|
||||||
|
length,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
999999,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashLengthLessThan(
|
||||||
|
int length, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
length,
|
||||||
|
include,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashLengthGreaterThan(
|
||||||
|
int length, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
length,
|
||||||
|
include,
|
||||||
|
999999,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashLengthBetween(
|
||||||
|
int lower,
|
||||||
|
int upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
lower,
|
||||||
|
includeLower,
|
||||||
|
upper,
|
||||||
|
includeUpper,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
idEqualTo(Id value) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
idGreaterThan(
|
||||||
|
Id value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
idLessThan(
|
||||||
|
Id value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterFilterCondition>
|
||||||
|
idBetween(
|
||||||
|
Id lower,
|
||||||
|
Id upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'id',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQueryObject
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QFilterCondition> {}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQueryLinks
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QFilterCondition> {}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQuerySortBy
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QSortBy> {}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQuerySortThenBy
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QSortThenBy> {
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterSortBy>
|
||||||
|
thenById() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'id', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QAfterSortBy>
|
||||||
|
thenByIdDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'id', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQueryWhereDistinct
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QDistinct> {
|
||||||
|
QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QDistinct>
|
||||||
|
distinctByHash() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'hash');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AndroidDeviceAssetQueryProperty
|
||||||
|
on QueryBuilder<AndroidDeviceAsset, AndroidDeviceAsset, QQueryProperty> {
|
||||||
|
QueryBuilder<AndroidDeviceAsset, int, QQueryOperations> idProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<AndroidDeviceAsset, List<int>, QQueryOperations> hashProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'hash');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/utils/hash.dart';
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
@ -14,7 +16,7 @@ part 'asset.g.dart';
|
|||||||
class Asset {
|
class Asset {
|
||||||
Asset.remote(AssetResponseDto remote)
|
Asset.remote(AssetResponseDto remote)
|
||||||
: remoteId = remote.id,
|
: remoteId = remote.id,
|
||||||
isLocal = false,
|
checksum = remote.checksum,
|
||||||
fileCreatedAt = remote.fileCreatedAt,
|
fileCreatedAt = remote.fileCreatedAt,
|
||||||
fileModifiedAt = remote.fileModifiedAt,
|
fileModifiedAt = remote.fileModifiedAt,
|
||||||
updatedAt = remote.updatedAt,
|
updatedAt = remote.updatedAt,
|
||||||
@ -24,23 +26,20 @@ class Asset {
|
|||||||
height = remote.exifInfo?.exifImageHeight?.toInt(),
|
height = remote.exifInfo?.exifImageHeight?.toInt(),
|
||||||
width = remote.exifInfo?.exifImageWidth?.toInt(),
|
width = remote.exifInfo?.exifImageWidth?.toInt(),
|
||||||
livePhotoVideoId = remote.livePhotoVideoId,
|
livePhotoVideoId = remote.livePhotoVideoId,
|
||||||
localId = remote.deviceAssetId,
|
|
||||||
deviceId = fastHash(remote.deviceId),
|
|
||||||
ownerId = fastHash(remote.ownerId),
|
ownerId = fastHash(remote.ownerId),
|
||||||
exifInfo =
|
exifInfo =
|
||||||
remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
|
remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
|
||||||
isFavorite = remote.isFavorite,
|
isFavorite = remote.isFavorite,
|
||||||
isArchived = remote.isArchived;
|
isArchived = remote.isArchived;
|
||||||
|
|
||||||
Asset.local(AssetEntity local)
|
Asset.local(AssetEntity local, List<int> hash)
|
||||||
: localId = local.id,
|
: localId = local.id,
|
||||||
isLocal = true,
|
checksum = base64.encode(hash),
|
||||||
durationInSeconds = local.duration,
|
durationInSeconds = local.duration,
|
||||||
type = AssetType.values[local.typeInt],
|
type = AssetType.values[local.typeInt],
|
||||||
height = local.height,
|
height = local.height,
|
||||||
width = local.width,
|
width = local.width,
|
||||||
fileName = local.title!,
|
fileName = local.title!,
|
||||||
deviceId = Store.get(StoreKey.deviceIdHash),
|
|
||||||
ownerId = Store.get(StoreKey.currentUser).isarId,
|
ownerId = Store.get(StoreKey.currentUser).isarId,
|
||||||
fileModifiedAt = local.modifiedDateTime,
|
fileModifiedAt = local.modifiedDateTime,
|
||||||
updatedAt = local.modifiedDateTime,
|
updatedAt = local.modifiedDateTime,
|
||||||
@ -53,13 +52,15 @@ class Asset {
|
|||||||
if (local.latitude != null) {
|
if (local.latitude != null) {
|
||||||
exifInfo = ExifInfo(lat: local.latitude, long: local.longitude);
|
exifInfo = ExifInfo(lat: local.latitude, long: local.longitude);
|
||||||
}
|
}
|
||||||
|
_local = local;
|
||||||
|
assert(hash.length == 20, "invalid SHA1 hash");
|
||||||
}
|
}
|
||||||
|
|
||||||
Asset({
|
Asset({
|
||||||
this.id = Isar.autoIncrement,
|
this.id = Isar.autoIncrement,
|
||||||
|
required this.checksum,
|
||||||
this.remoteId,
|
this.remoteId,
|
||||||
required this.localId,
|
required this.localId,
|
||||||
required this.deviceId,
|
|
||||||
required this.ownerId,
|
required this.ownerId,
|
||||||
required this.fileCreatedAt,
|
required this.fileCreatedAt,
|
||||||
required this.fileModifiedAt,
|
required this.fileModifiedAt,
|
||||||
@ -72,7 +73,6 @@ class Asset {
|
|||||||
this.livePhotoVideoId,
|
this.livePhotoVideoId,
|
||||||
this.exifInfo,
|
this.exifInfo,
|
||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
required this.isLocal,
|
|
||||||
required this.isArchived,
|
required this.isArchived,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ class Asset {
|
|||||||
AssetEntity? get local {
|
AssetEntity? get local {
|
||||||
if (isLocal && _local == null) {
|
if (isLocal && _local == null) {
|
||||||
_local = AssetEntity(
|
_local = AssetEntity(
|
||||||
id: localId,
|
id: localId!,
|
||||||
typeInt: isImage ? 1 : 2,
|
typeInt: isImage ? 1 : 2,
|
||||||
width: width ?? 0,
|
width: width ?? 0,
|
||||||
height: height ?? 0,
|
height: height ?? 0,
|
||||||
@ -98,18 +98,21 @@ class Asset {
|
|||||||
|
|
||||||
Id id = Isar.autoIncrement;
|
Id id = Isar.autoIncrement;
|
||||||
|
|
||||||
|
/// stores the raw SHA1 bytes as a base64 String
|
||||||
|
/// because Isar cannot sort lists of byte arrays
|
||||||
|
@Index(
|
||||||
|
unique: true,
|
||||||
|
replace: false,
|
||||||
|
type: IndexType.hash,
|
||||||
|
composite: [CompositeIndex("ownerId")],
|
||||||
|
)
|
||||||
|
String checksum;
|
||||||
|
|
||||||
@Index(unique: false, replace: false, type: IndexType.hash)
|
@Index(unique: false, replace: false, type: IndexType.hash)
|
||||||
String? remoteId;
|
String? remoteId;
|
||||||
|
|
||||||
@Index(
|
@Index(unique: false, replace: false, type: IndexType.hash)
|
||||||
unique: false,
|
String? localId;
|
||||||
replace: false,
|
|
||||||
type: IndexType.hash,
|
|
||||||
composite: [CompositeIndex('deviceId')],
|
|
||||||
)
|
|
||||||
String localId;
|
|
||||||
|
|
||||||
int deviceId;
|
|
||||||
|
|
||||||
int ownerId;
|
int ownerId;
|
||||||
|
|
||||||
@ -134,14 +137,15 @@ class Asset {
|
|||||||
|
|
||||||
bool isFavorite;
|
bool isFavorite;
|
||||||
|
|
||||||
/// `true` if this [Asset] is present on the device
|
|
||||||
bool isLocal;
|
|
||||||
|
|
||||||
bool isArchived;
|
bool isArchived;
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
ExifInfo? exifInfo;
|
ExifInfo? exifInfo;
|
||||||
|
|
||||||
|
/// `true` if this [Asset] is present on the device
|
||||||
|
@ignore
|
||||||
|
bool get isLocal => localId != null;
|
||||||
|
|
||||||
@ignore
|
@ignore
|
||||||
bool get isInDb => id != Isar.autoIncrement;
|
bool get isInDb => id != Isar.autoIncrement;
|
||||||
|
|
||||||
@ -175,9 +179,9 @@ class Asset {
|
|||||||
bool operator ==(other) {
|
bool operator ==(other) {
|
||||||
if (other is! Asset) return false;
|
if (other is! Asset) return false;
|
||||||
return id == other.id &&
|
return id == other.id &&
|
||||||
|
checksum == other.checksum &&
|
||||||
remoteId == other.remoteId &&
|
remoteId == other.remoteId &&
|
||||||
localId == other.localId &&
|
localId == other.localId &&
|
||||||
deviceId == other.deviceId &&
|
|
||||||
ownerId == other.ownerId &&
|
ownerId == other.ownerId &&
|
||||||
fileCreatedAt.isAtSameMomentAs(other.fileCreatedAt) &&
|
fileCreatedAt.isAtSameMomentAs(other.fileCreatedAt) &&
|
||||||
fileModifiedAt.isAtSameMomentAs(other.fileModifiedAt) &&
|
fileModifiedAt.isAtSameMomentAs(other.fileModifiedAt) &&
|
||||||
@ -197,9 +201,9 @@ class Asset {
|
|||||||
@ignore
|
@ignore
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
id.hashCode ^
|
id.hashCode ^
|
||||||
|
checksum.hashCode ^
|
||||||
remoteId.hashCode ^
|
remoteId.hashCode ^
|
||||||
localId.hashCode ^
|
localId.hashCode ^
|
||||||
deviceId.hashCode ^
|
|
||||||
ownerId.hashCode ^
|
ownerId.hashCode ^
|
||||||
fileCreatedAt.hashCode ^
|
fileCreatedAt.hashCode ^
|
||||||
fileModifiedAt.hashCode ^
|
fileModifiedAt.hashCode ^
|
||||||
@ -217,8 +221,7 @@ class Asset {
|
|||||||
/// Returns `true` if this [Asset] can updated with values from parameter [a]
|
/// Returns `true` if this [Asset] can updated with values from parameter [a]
|
||||||
bool canUpdate(Asset a) {
|
bool canUpdate(Asset a) {
|
||||||
assert(isInDb);
|
assert(isInDb);
|
||||||
assert(localId == a.localId);
|
assert(checksum == a.checksum);
|
||||||
assert(deviceId == a.deviceId);
|
|
||||||
assert(a.storage != AssetState.merged);
|
assert(a.storage != AssetState.merged);
|
||||||
return a.updatedAt.isAfter(updatedAt) ||
|
return a.updatedAt.isAfter(updatedAt) ||
|
||||||
a.isRemote && !isRemote ||
|
a.isRemote && !isRemote ||
|
||||||
@ -239,11 +242,18 @@ class Asset {
|
|||||||
if (a.isRemote) {
|
if (a.isRemote) {
|
||||||
return a._copyWith(
|
return a._copyWith(
|
||||||
id: id,
|
id: id,
|
||||||
isLocal: isLocal,
|
localId: localId,
|
||||||
width: a.width ?? width,
|
width: a.width ?? width,
|
||||||
height: a.height ?? height,
|
height: a.height ?? height,
|
||||||
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
||||||
);
|
);
|
||||||
|
} else if (isRemote) {
|
||||||
|
return _copyWith(
|
||||||
|
localId: localId ?? a.localId,
|
||||||
|
width: width ?? a.width,
|
||||||
|
height: height ?? a.height,
|
||||||
|
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return a._copyWith(
|
return a._copyWith(
|
||||||
id: id,
|
id: id,
|
||||||
@ -270,7 +280,7 @@ class Asset {
|
|||||||
} else {
|
} else {
|
||||||
// add only missing values (and set isLocal to true)
|
// add only missing values (and set isLocal to true)
|
||||||
return _copyWith(
|
return _copyWith(
|
||||||
isLocal: true,
|
localId: localId ?? a.localId,
|
||||||
width: width ?? a.width,
|
width: width ?? a.width,
|
||||||
height: height ?? a.height,
|
height: height ?? a.height,
|
||||||
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
|
exifInfo: exifInfo ?? a.exifInfo?.copyWith(id: id),
|
||||||
@ -281,9 +291,9 @@ class Asset {
|
|||||||
|
|
||||||
Asset _copyWith({
|
Asset _copyWith({
|
||||||
Id? id,
|
Id? id,
|
||||||
|
String? checksum,
|
||||||
String? remoteId,
|
String? remoteId,
|
||||||
String? localId,
|
String? localId,
|
||||||
int? deviceId,
|
|
||||||
int? ownerId,
|
int? ownerId,
|
||||||
DateTime? fileCreatedAt,
|
DateTime? fileCreatedAt,
|
||||||
DateTime? fileModifiedAt,
|
DateTime? fileModifiedAt,
|
||||||
@ -295,15 +305,14 @@ class Asset {
|
|||||||
String? fileName,
|
String? fileName,
|
||||||
String? livePhotoVideoId,
|
String? livePhotoVideoId,
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
bool? isLocal,
|
|
||||||
bool? isArchived,
|
bool? isArchived,
|
||||||
ExifInfo? exifInfo,
|
ExifInfo? exifInfo,
|
||||||
}) =>
|
}) =>
|
||||||
Asset(
|
Asset(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
|
checksum: checksum ?? this.checksum,
|
||||||
remoteId: remoteId ?? this.remoteId,
|
remoteId: remoteId ?? this.remoteId,
|
||||||
localId: localId ?? this.localId,
|
localId: localId ?? this.localId,
|
||||||
deviceId: deviceId ?? this.deviceId,
|
|
||||||
ownerId: ownerId ?? this.ownerId,
|
ownerId: ownerId ?? this.ownerId,
|
||||||
fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt,
|
fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt,
|
||||||
fileModifiedAt: fileModifiedAt ?? this.fileModifiedAt,
|
fileModifiedAt: fileModifiedAt ?? this.fileModifiedAt,
|
||||||
@ -315,7 +324,6 @@ class Asset {
|
|||||||
fileName: fileName ?? this.fileName,
|
fileName: fileName ?? this.fileName,
|
||||||
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
isLocal: isLocal ?? this.isLocal,
|
|
||||||
isArchived: isArchived ?? this.isArchived,
|
isArchived: isArchived ?? this.isArchived,
|
||||||
exifInfo: exifInfo ?? this.exifInfo,
|
exifInfo: exifInfo ?? this.exifInfo,
|
||||||
);
|
);
|
||||||
@ -328,39 +336,36 @@ class Asset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// compares assets by [ownerId], [deviceId], [localId]
|
|
||||||
static int compareByOwnerDeviceLocalId(Asset a, Asset b) {
|
|
||||||
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
|
|
||||||
if (ownerIdOrder != 0) {
|
|
||||||
return ownerIdOrder;
|
|
||||||
}
|
|
||||||
final int deviceIdOrder = a.deviceId.compareTo(b.deviceId);
|
|
||||||
if (deviceIdOrder != 0) {
|
|
||||||
return deviceIdOrder;
|
|
||||||
}
|
|
||||||
final int localIdOrder = a.localId.compareTo(b.localId);
|
|
||||||
return localIdOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// compares assets by [ownerId], [deviceId], [localId], [fileModifiedAt]
|
|
||||||
static int compareByOwnerDeviceLocalIdModified(Asset a, Asset b) {
|
|
||||||
final int order = compareByOwnerDeviceLocalId(a, b);
|
|
||||||
return order != 0 ? order : a.fileModifiedAt.compareTo(b.fileModifiedAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int compareById(Asset a, Asset b) => a.id.compareTo(b.id);
|
static int compareById(Asset a, Asset b) => a.id.compareTo(b.id);
|
||||||
|
|
||||||
static int compareByLocalId(Asset a, Asset b) =>
|
static int compareByChecksum(Asset a, Asset b) =>
|
||||||
a.localId.compareTo(b.localId);
|
a.checksum.compareTo(b.checksum);
|
||||||
|
|
||||||
|
static int compareByOwnerChecksum(Asset a, Asset b) {
|
||||||
|
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
|
||||||
|
if (ownerIdOrder != 0) return ownerIdOrder;
|
||||||
|
return compareByChecksum(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compareByOwnerChecksumCreatedModified(Asset a, Asset b) {
|
||||||
|
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
|
||||||
|
if (ownerIdOrder != 0) return ownerIdOrder;
|
||||||
|
final int checksumOrder = compareByChecksum(a, b);
|
||||||
|
if (checksumOrder != 0) return checksumOrder;
|
||||||
|
final int createdOrder = a.fileCreatedAt.compareTo(b.fileCreatedAt);
|
||||||
|
if (createdOrder != 0) return createdOrder;
|
||||||
|
return a.fileModifiedAt.compareTo(b.fileModifiedAt);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return """
|
return """
|
||||||
{
|
{
|
||||||
|
"id": ${id == Isar.autoIncrement ? '"N/A"' : id},
|
||||||
"remoteId": "${remoteId ?? "N/A"}",
|
"remoteId": "${remoteId ?? "N/A"}",
|
||||||
"localId": "$localId",
|
"localId": "${localId ?? "N/A"}",
|
||||||
"deviceId": "$deviceId",
|
"checksum": "$checksum",
|
||||||
"ownerId": "$ownerId",
|
"ownerId": $ownerId,
|
||||||
"livePhotoVideoId": "${livePhotoVideoId ?? "N/A"}",
|
"livePhotoVideoId": "${livePhotoVideoId ?? "N/A"}",
|
||||||
"fileCreatedAt": "$fileCreatedAt",
|
"fileCreatedAt": "$fileCreatedAt",
|
||||||
"fileModifiedAt": "$fileModifiedAt",
|
"fileModifiedAt": "$fileModifiedAt",
|
||||||
@ -369,9 +374,8 @@ class Asset {
|
|||||||
"type": "$type",
|
"type": "$type",
|
||||||
"fileName": "$fileName",
|
"fileName": "$fileName",
|
||||||
"isFavorite": $isFavorite,
|
"isFavorite": $isFavorite,
|
||||||
"isLocal": $isLocal,
|
|
||||||
"isRemote: $isRemote,
|
"isRemote: $isRemote,
|
||||||
"storage": $storage,
|
"storage": "$storage",
|
||||||
"width": ${width ?? "N/A"},
|
"width": ${width ?? "N/A"},
|
||||||
"height": ${height ?? "N/A"},
|
"height": ${height ?? "N/A"},
|
||||||
"isArchived": $isArchived
|
"isArchived": $isArchived
|
||||||
@ -424,10 +428,6 @@ extension AssetsHelper on IsarCollection<Asset> {
|
|||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> _remote(Iterable<String> ids) =>
|
QueryBuilder<Asset, Asset, QAfterWhereClause> _remote(Iterable<String> ids) =>
|
||||||
where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e));
|
where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e));
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> _local(Iterable<String> ids) {
|
QueryBuilder<Asset, Asset, QAfterWhereClause> _local(Iterable<String> ids) {
|
||||||
return where().anyOf(
|
return where().anyOf(ids, (q, String e) => q.localIdEqualTo(e));
|
||||||
ids,
|
|
||||||
(q, String e) =>
|
|
||||||
q.localIdDeviceIdEqualTo(e, Store.get(StoreKey.deviceIdHash)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ const AssetSchema = CollectionSchema(
|
|||||||
name: r'Asset',
|
name: r'Asset',
|
||||||
id: -2933289051367723566,
|
id: -2933289051367723566,
|
||||||
properties: {
|
properties: {
|
||||||
r'deviceId': PropertySchema(
|
r'checksum': PropertySchema(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: r'deviceId',
|
name: r'checksum',
|
||||||
type: IsarType.long,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'durationInSeconds': PropertySchema(
|
r'durationInSeconds': PropertySchema(
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -57,44 +57,39 @@ const AssetSchema = CollectionSchema(
|
|||||||
name: r'isFavorite',
|
name: r'isFavorite',
|
||||||
type: IsarType.bool,
|
type: IsarType.bool,
|
||||||
),
|
),
|
||||||
r'isLocal': PropertySchema(
|
|
||||||
id: 8,
|
|
||||||
name: r'isLocal',
|
|
||||||
type: IsarType.bool,
|
|
||||||
),
|
|
||||||
r'livePhotoVideoId': PropertySchema(
|
r'livePhotoVideoId': PropertySchema(
|
||||||
id: 9,
|
id: 8,
|
||||||
name: r'livePhotoVideoId',
|
name: r'livePhotoVideoId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'localId': PropertySchema(
|
r'localId': PropertySchema(
|
||||||
id: 10,
|
id: 9,
|
||||||
name: r'localId',
|
name: r'localId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'ownerId': PropertySchema(
|
r'ownerId': PropertySchema(
|
||||||
id: 11,
|
id: 10,
|
||||||
name: r'ownerId',
|
name: r'ownerId',
|
||||||
type: IsarType.long,
|
type: IsarType.long,
|
||||||
),
|
),
|
||||||
r'remoteId': PropertySchema(
|
r'remoteId': PropertySchema(
|
||||||
id: 12,
|
id: 11,
|
||||||
name: r'remoteId',
|
name: r'remoteId',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
r'type': PropertySchema(
|
r'type': PropertySchema(
|
||||||
id: 13,
|
id: 12,
|
||||||
name: r'type',
|
name: r'type',
|
||||||
type: IsarType.byte,
|
type: IsarType.byte,
|
||||||
enumMap: _AssettypeEnumValueMap,
|
enumMap: _AssettypeEnumValueMap,
|
||||||
),
|
),
|
||||||
r'updatedAt': PropertySchema(
|
r'updatedAt': PropertySchema(
|
||||||
id: 14,
|
id: 13,
|
||||||
name: r'updatedAt',
|
name: r'updatedAt',
|
||||||
type: IsarType.dateTime,
|
type: IsarType.dateTime,
|
||||||
),
|
),
|
||||||
r'width': PropertySchema(
|
r'width': PropertySchema(
|
||||||
id: 15,
|
id: 14,
|
||||||
name: r'width',
|
name: r'width',
|
||||||
type: IsarType.int,
|
type: IsarType.int,
|
||||||
)
|
)
|
||||||
@ -105,6 +100,24 @@ const AssetSchema = CollectionSchema(
|
|||||||
deserializeProp: _assetDeserializeProp,
|
deserializeProp: _assetDeserializeProp,
|
||||||
idName: r'id',
|
idName: r'id',
|
||||||
indexes: {
|
indexes: {
|
||||||
|
r'checksum_ownerId': IndexSchema(
|
||||||
|
id: 5611361749756160119,
|
||||||
|
name: r'checksum_ownerId',
|
||||||
|
unique: true,
|
||||||
|
replace: false,
|
||||||
|
properties: [
|
||||||
|
IndexPropertySchema(
|
||||||
|
name: r'checksum',
|
||||||
|
type: IndexType.hash,
|
||||||
|
caseSensitive: true,
|
||||||
|
),
|
||||||
|
IndexPropertySchema(
|
||||||
|
name: r'ownerId',
|
||||||
|
type: IndexType.value,
|
||||||
|
caseSensitive: false,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
r'remoteId': IndexSchema(
|
r'remoteId': IndexSchema(
|
||||||
id: 6301175856541681032,
|
id: 6301175856541681032,
|
||||||
name: r'remoteId',
|
name: r'remoteId',
|
||||||
@ -118,9 +131,9 @@ const AssetSchema = CollectionSchema(
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
r'localId_deviceId': IndexSchema(
|
r'localId': IndexSchema(
|
||||||
id: 7649417350086526165,
|
id: 1199848425898359622,
|
||||||
name: r'localId_deviceId',
|
name: r'localId',
|
||||||
unique: false,
|
unique: false,
|
||||||
replace: false,
|
replace: false,
|
||||||
properties: [
|
properties: [
|
||||||
@ -128,11 +141,6 @@ const AssetSchema = CollectionSchema(
|
|||||||
name: r'localId',
|
name: r'localId',
|
||||||
type: IndexType.hash,
|
type: IndexType.hash,
|
||||||
caseSensitive: true,
|
caseSensitive: true,
|
||||||
),
|
|
||||||
IndexPropertySchema(
|
|
||||||
name: r'deviceId',
|
|
||||||
type: IndexType.value,
|
|
||||||
caseSensitive: false,
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -151,6 +159,7 @@ int _assetEstimateSize(
|
|||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
var bytesCount = offsets.last;
|
var bytesCount = offsets.last;
|
||||||
|
bytesCount += 3 + object.checksum.length * 3;
|
||||||
bytesCount += 3 + object.fileName.length * 3;
|
bytesCount += 3 + object.fileName.length * 3;
|
||||||
{
|
{
|
||||||
final value = object.livePhotoVideoId;
|
final value = object.livePhotoVideoId;
|
||||||
@ -158,7 +167,12 @@ int _assetEstimateSize(
|
|||||||
bytesCount += 3 + value.length * 3;
|
bytesCount += 3 + value.length * 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bytesCount += 3 + object.localId.length * 3;
|
{
|
||||||
|
final value = object.localId;
|
||||||
|
if (value != null) {
|
||||||
|
bytesCount += 3 + value.length * 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
{
|
{
|
||||||
final value = object.remoteId;
|
final value = object.remoteId;
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@ -174,7 +188,7 @@ void _assetSerialize(
|
|||||||
List<int> offsets,
|
List<int> offsets,
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
writer.writeLong(offsets[0], object.deviceId);
|
writer.writeString(offsets[0], object.checksum);
|
||||||
writer.writeLong(offsets[1], object.durationInSeconds);
|
writer.writeLong(offsets[1], object.durationInSeconds);
|
||||||
writer.writeDateTime(offsets[2], object.fileCreatedAt);
|
writer.writeDateTime(offsets[2], object.fileCreatedAt);
|
||||||
writer.writeDateTime(offsets[3], object.fileModifiedAt);
|
writer.writeDateTime(offsets[3], object.fileModifiedAt);
|
||||||
@ -182,14 +196,13 @@ void _assetSerialize(
|
|||||||
writer.writeInt(offsets[5], object.height);
|
writer.writeInt(offsets[5], object.height);
|
||||||
writer.writeBool(offsets[6], object.isArchived);
|
writer.writeBool(offsets[6], object.isArchived);
|
||||||
writer.writeBool(offsets[7], object.isFavorite);
|
writer.writeBool(offsets[7], object.isFavorite);
|
||||||
writer.writeBool(offsets[8], object.isLocal);
|
writer.writeString(offsets[8], object.livePhotoVideoId);
|
||||||
writer.writeString(offsets[9], object.livePhotoVideoId);
|
writer.writeString(offsets[9], object.localId);
|
||||||
writer.writeString(offsets[10], object.localId);
|
writer.writeLong(offsets[10], object.ownerId);
|
||||||
writer.writeLong(offsets[11], object.ownerId);
|
writer.writeString(offsets[11], object.remoteId);
|
||||||
writer.writeString(offsets[12], object.remoteId);
|
writer.writeByte(offsets[12], object.type.index);
|
||||||
writer.writeByte(offsets[13], object.type.index);
|
writer.writeDateTime(offsets[13], object.updatedAt);
|
||||||
writer.writeDateTime(offsets[14], object.updatedAt);
|
writer.writeInt(offsets[14], object.width);
|
||||||
writer.writeInt(offsets[15], object.width);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Asset _assetDeserialize(
|
Asset _assetDeserialize(
|
||||||
@ -199,7 +212,7 @@ Asset _assetDeserialize(
|
|||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
final object = Asset(
|
final object = Asset(
|
||||||
deviceId: reader.readLong(offsets[0]),
|
checksum: reader.readString(offsets[0]),
|
||||||
durationInSeconds: reader.readLong(offsets[1]),
|
durationInSeconds: reader.readLong(offsets[1]),
|
||||||
fileCreatedAt: reader.readDateTime(offsets[2]),
|
fileCreatedAt: reader.readDateTime(offsets[2]),
|
||||||
fileModifiedAt: reader.readDateTime(offsets[3]),
|
fileModifiedAt: reader.readDateTime(offsets[3]),
|
||||||
@ -208,15 +221,14 @@ Asset _assetDeserialize(
|
|||||||
id: id,
|
id: id,
|
||||||
isArchived: reader.readBool(offsets[6]),
|
isArchived: reader.readBool(offsets[6]),
|
||||||
isFavorite: reader.readBool(offsets[7]),
|
isFavorite: reader.readBool(offsets[7]),
|
||||||
isLocal: reader.readBool(offsets[8]),
|
livePhotoVideoId: reader.readStringOrNull(offsets[8]),
|
||||||
livePhotoVideoId: reader.readStringOrNull(offsets[9]),
|
localId: reader.readStringOrNull(offsets[9]),
|
||||||
localId: reader.readString(offsets[10]),
|
ownerId: reader.readLong(offsets[10]),
|
||||||
ownerId: reader.readLong(offsets[11]),
|
remoteId: reader.readStringOrNull(offsets[11]),
|
||||||
remoteId: reader.readStringOrNull(offsets[12]),
|
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[12])] ??
|
||||||
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[13])] ??
|
|
||||||
AssetType.other,
|
AssetType.other,
|
||||||
updatedAt: reader.readDateTime(offsets[14]),
|
updatedAt: reader.readDateTime(offsets[13]),
|
||||||
width: reader.readIntOrNull(offsets[15]),
|
width: reader.readIntOrNull(offsets[14]),
|
||||||
);
|
);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
@ -229,7 +241,7 @@ P _assetDeserializeProp<P>(
|
|||||||
) {
|
) {
|
||||||
switch (propertyId) {
|
switch (propertyId) {
|
||||||
case 0:
|
case 0:
|
||||||
return (reader.readLong(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 1:
|
case 1:
|
||||||
return (reader.readLong(offset)) as P;
|
return (reader.readLong(offset)) as P;
|
||||||
case 2:
|
case 2:
|
||||||
@ -245,21 +257,19 @@ P _assetDeserializeProp<P>(
|
|||||||
case 7:
|
case 7:
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readBool(offset)) as P;
|
||||||
case 8:
|
case 8:
|
||||||
return (reader.readBool(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 9:
|
case 9:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 10:
|
case 10:
|
||||||
return (reader.readString(offset)) as P;
|
|
||||||
case 11:
|
|
||||||
return (reader.readLong(offset)) as P;
|
return (reader.readLong(offset)) as P;
|
||||||
case 12:
|
case 11:
|
||||||
return (reader.readStringOrNull(offset)) as P;
|
return (reader.readStringOrNull(offset)) as P;
|
||||||
case 13:
|
case 12:
|
||||||
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
|
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||||
AssetType.other) as P;
|
AssetType.other) as P;
|
||||||
case 14:
|
case 13:
|
||||||
return (reader.readDateTime(offset)) as P;
|
return (reader.readDateTime(offset)) as P;
|
||||||
case 15:
|
case 14:
|
||||||
return (reader.readIntOrNull(offset)) as P;
|
return (reader.readIntOrNull(offset)) as P;
|
||||||
default:
|
default:
|
||||||
throw IsarError('Unknown property with id $propertyId');
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
@ -291,6 +301,94 @@ void _assetAttach(IsarCollection<dynamic> col, Id id, Asset object) {
|
|||||||
object.id = id;
|
object.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AssetByIndex on IsarCollection<Asset> {
|
||||||
|
Future<Asset?> getByChecksumOwnerId(String checksum, int ownerId) {
|
||||||
|
return getByIndex(r'checksum_ownerId', [checksum, ownerId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Asset? getByChecksumOwnerIdSync(String checksum, int ownerId) {
|
||||||
|
return getByIndexSync(r'checksum_ownerId', [checksum, ownerId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteByChecksumOwnerId(String checksum, int ownerId) {
|
||||||
|
return deleteByIndex(r'checksum_ownerId', [checksum, ownerId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleteByChecksumOwnerIdSync(String checksum, int ownerId) {
|
||||||
|
return deleteByIndexSync(r'checksum_ownerId', [checksum, ownerId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Asset?>> getAllByChecksumOwnerId(
|
||||||
|
List<String> checksumValues, List<int> ownerIdValues) {
|
||||||
|
final len = checksumValues.length;
|
||||||
|
assert(ownerIdValues.length == len,
|
||||||
|
'All index values must have the same length');
|
||||||
|
final values = <List<dynamic>>[];
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
values.add([checksumValues[i], ownerIdValues[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllByIndex(r'checksum_ownerId', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Asset?> getAllByChecksumOwnerIdSync(
|
||||||
|
List<String> checksumValues, List<int> ownerIdValues) {
|
||||||
|
final len = checksumValues.length;
|
||||||
|
assert(ownerIdValues.length == len,
|
||||||
|
'All index values must have the same length');
|
||||||
|
final values = <List<dynamic>>[];
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
values.add([checksumValues[i], ownerIdValues[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAllByIndexSync(r'checksum_ownerId', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> deleteAllByChecksumOwnerId(
|
||||||
|
List<String> checksumValues, List<int> ownerIdValues) {
|
||||||
|
final len = checksumValues.length;
|
||||||
|
assert(ownerIdValues.length == len,
|
||||||
|
'All index values must have the same length');
|
||||||
|
final values = <List<dynamic>>[];
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
values.add([checksumValues[i], ownerIdValues[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleteAllByIndex(r'checksum_ownerId', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
int deleteAllByChecksumOwnerIdSync(
|
||||||
|
List<String> checksumValues, List<int> ownerIdValues) {
|
||||||
|
final len = checksumValues.length;
|
||||||
|
assert(ownerIdValues.length == len,
|
||||||
|
'All index values must have the same length');
|
||||||
|
final values = <List<dynamic>>[];
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
values.add([checksumValues[i], ownerIdValues[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleteAllByIndexSync(r'checksum_ownerId', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Id> putByChecksumOwnerId(Asset object) {
|
||||||
|
return putByIndex(r'checksum_ownerId', object);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id putByChecksumOwnerIdSync(Asset object, {bool saveLinks = true}) {
|
||||||
|
return putByIndexSync(r'checksum_ownerId', object, saveLinks: saveLinks);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Id>> putAllByChecksumOwnerId(List<Asset> objects) {
|
||||||
|
return putAllByIndex(r'checksum_ownerId', objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Id> putAllByChecksumOwnerIdSync(List<Asset> objects,
|
||||||
|
{bool saveLinks = true}) {
|
||||||
|
return putAllByIndexSync(r'checksum_ownerId', objects,
|
||||||
|
saveLinks: saveLinks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension AssetQueryWhereSort on QueryBuilder<Asset, Asset, QWhere> {
|
extension AssetQueryWhereSort on QueryBuilder<Asset, Asset, QWhere> {
|
||||||
QueryBuilder<Asset, Asset, QAfterWhere> anyId() {
|
QueryBuilder<Asset, Asset, QAfterWhere> anyId() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -365,6 +463,145 @@ extension AssetQueryWhere on QueryBuilder<Asset, Asset, QWhereClause> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumEqualToAnyOwnerId(
|
||||||
|
String checksum) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
value: [checksum],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumNotEqualToAnyOwnerId(
|
||||||
|
String checksum) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
if (query.whereSort == Sort.asc) {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [],
|
||||||
|
upper: [checksum],
|
||||||
|
includeUpper: false,
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [],
|
||||||
|
upper: [checksum],
|
||||||
|
includeUpper: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumOwnerIdEqualTo(
|
||||||
|
String checksum, int ownerId) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
value: [checksum, ownerId],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause>
|
||||||
|
checksumEqualToOwnerIdNotEqualTo(String checksum, int ownerId) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
if (query.whereSort == Sort.asc) {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum],
|
||||||
|
upper: [checksum, ownerId],
|
||||||
|
includeUpper: false,
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum, ownerId],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [checksum],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum, ownerId],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [checksum],
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum],
|
||||||
|
upper: [checksum, ownerId],
|
||||||
|
includeUpper: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause>
|
||||||
|
checksumEqualToOwnerIdGreaterThan(
|
||||||
|
String checksum,
|
||||||
|
int ownerId, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum, ownerId],
|
||||||
|
includeLower: include,
|
||||||
|
upper: [checksum],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumEqualToOwnerIdLessThan(
|
||||||
|
String checksum,
|
||||||
|
int ownerId, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum],
|
||||||
|
upper: [checksum, ownerId],
|
||||||
|
includeUpper: include,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause> checksumEqualToOwnerIdBetween(
|
||||||
|
String checksum,
|
||||||
|
int lowerOwnerId,
|
||||||
|
int upperOwnerId, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'checksum_ownerId',
|
||||||
|
lower: [checksum, lowerOwnerId],
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: [checksum, upperOwnerId],
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> remoteIdIsNull() {
|
QueryBuilder<Asset, Asset, QAfterWhereClause> remoteIdIsNull() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||||
@ -430,29 +667,49 @@ extension AssetQueryWhere on QueryBuilder<Asset, Asset, QWhereClause> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdEqualToAnyDeviceId(
|
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdIsNull() {
|
||||||
String localId) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||||
indexName: r'localId_deviceId',
|
indexName: r'localId',
|
||||||
|
value: [null],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdIsNotNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'localId',
|
||||||
|
lower: [null],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdEqualTo(
|
||||||
|
String? localId) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||||
|
indexName: r'localId',
|
||||||
value: [localId],
|
value: [localId],
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdNotEqualToAnyDeviceId(
|
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdNotEqualTo(
|
||||||
String localId) {
|
String? localId) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
if (query.whereSort == Sort.asc) {
|
if (query.whereSort == Sort.asc) {
|
||||||
return query
|
return query
|
||||||
.addWhereClause(IndexWhereClause.between(
|
.addWhereClause(IndexWhereClause.between(
|
||||||
indexName: r'localId_deviceId',
|
indexName: r'localId',
|
||||||
lower: [],
|
lower: [],
|
||||||
upper: [localId],
|
upper: [localId],
|
||||||
includeUpper: false,
|
includeUpper: false,
|
||||||
))
|
))
|
||||||
.addWhereClause(IndexWhereClause.between(
|
.addWhereClause(IndexWhereClause.between(
|
||||||
indexName: r'localId_deviceId',
|
indexName: r'localId',
|
||||||
lower: [localId],
|
lower: [localId],
|
||||||
includeLower: false,
|
includeLower: false,
|
||||||
upper: [],
|
upper: [],
|
||||||
@ -460,13 +717,13 @@ extension AssetQueryWhere on QueryBuilder<Asset, Asset, QWhereClause> {
|
|||||||
} else {
|
} else {
|
||||||
return query
|
return query
|
||||||
.addWhereClause(IndexWhereClause.between(
|
.addWhereClause(IndexWhereClause.between(
|
||||||
indexName: r'localId_deviceId',
|
indexName: r'localId',
|
||||||
lower: [localId],
|
lower: [localId],
|
||||||
includeLower: false,
|
includeLower: false,
|
||||||
upper: [],
|
upper: [],
|
||||||
))
|
))
|
||||||
.addWhereClause(IndexWhereClause.between(
|
.addWhereClause(IndexWhereClause.between(
|
||||||
indexName: r'localId_deviceId',
|
indexName: r'localId',
|
||||||
lower: [],
|
lower: [],
|
||||||
upper: [localId],
|
upper: [localId],
|
||||||
includeUpper: false,
|
includeUpper: false,
|
||||||
@ -474,151 +731,135 @@ extension AssetQueryWhere on QueryBuilder<Asset, Asset, QWhereClause> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdDeviceIdEqualTo(
|
|
||||||
String localId, int deviceId) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
|
||||||
indexName: r'localId_deviceId',
|
|
||||||
value: [localId, deviceId],
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause>
|
|
||||||
localIdEqualToDeviceIdNotEqualTo(String localId, int deviceId) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
if (query.whereSort == Sort.asc) {
|
|
||||||
return query
|
|
||||||
.addWhereClause(IndexWhereClause.between(
|
|
||||||
indexName: r'localId_deviceId',
|
|
||||||
lower: [localId],
|
|
||||||
upper: [localId, deviceId],
|
|
||||||
includeUpper: false,
|
|
||||||
))
|
|
||||||
.addWhereClause(IndexWhereClause.between(
|
|
||||||
indexName: r'localId_deviceId',
|
|
||||||
lower: [localId, deviceId],
|
|
||||||
includeLower: false,
|
|
||||||
upper: [localId],
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
return query
|
|
||||||
.addWhereClause(IndexWhereClause.between(
|
|
||||||
indexName: r'localId_deviceId',
|
|
||||||
lower: [localId, deviceId],
|
|
||||||
includeLower: false,
|
|
||||||
upper: [localId],
|
|
||||||
))
|
|
||||||
.addWhereClause(IndexWhereClause.between(
|
|
||||||
indexName: r'localId_deviceId',
|
|
||||||
lower: [localId],
|
|
||||||
upper: [localId, deviceId],
|
|
||||||
includeUpper: false,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause>
|
|
||||||
localIdEqualToDeviceIdGreaterThan(
|
|
||||||
String localId,
|
|
||||||
int deviceId, {
|
|
||||||
bool include = false,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addWhereClause(IndexWhereClause.between(
|
|
||||||
indexName: r'localId_deviceId',
|
|
||||||
lower: [localId, deviceId],
|
|
||||||
includeLower: include,
|
|
||||||
upper: [localId],
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdEqualToDeviceIdLessThan(
|
|
||||||
String localId,
|
|
||||||
int deviceId, {
|
|
||||||
bool include = false,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addWhereClause(IndexWhereClause.between(
|
|
||||||
indexName: r'localId_deviceId',
|
|
||||||
lower: [localId],
|
|
||||||
upper: [localId, deviceId],
|
|
||||||
includeUpper: include,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterWhereClause> localIdEqualToDeviceIdBetween(
|
|
||||||
String localId,
|
|
||||||
int lowerDeviceId,
|
|
||||||
int upperDeviceId, {
|
|
||||||
bool includeLower = true,
|
|
||||||
bool includeUpper = true,
|
|
||||||
}) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addWhereClause(IndexWhereClause.between(
|
|
||||||
indexName: r'localId_deviceId',
|
|
||||||
lower: [localId, lowerDeviceId],
|
|
||||||
includeLower: includeLower,
|
|
||||||
upper: [localId, upperDeviceId],
|
|
||||||
includeUpper: includeUpper,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> deviceIdEqualTo(int value) {
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumEqualTo(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
property: r'deviceId',
|
property: r'checksum',
|
||||||
value: value,
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> deviceIdGreaterThan(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumGreaterThan(
|
||||||
int value, {
|
String value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
|
bool caseSensitive = true,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
include: include,
|
include: include,
|
||||||
property: r'deviceId',
|
property: r'checksum',
|
||||||
value: value,
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> deviceIdLessThan(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumLessThan(
|
||||||
int value, {
|
String value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
|
bool caseSensitive = true,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.lessThan(
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
include: include,
|
include: include,
|
||||||
property: r'deviceId',
|
property: r'checksum',
|
||||||
value: value,
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> deviceIdBetween(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumBetween(
|
||||||
int lower,
|
String lower,
|
||||||
int upper, {
|
String upper, {
|
||||||
bool includeLower = true,
|
bool includeLower = true,
|
||||||
bool includeUpper = true,
|
bool includeUpper = true,
|
||||||
|
bool caseSensitive = true,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.between(
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
property: r'deviceId',
|
property: r'checksum',
|
||||||
lower: lower,
|
lower: lower,
|
||||||
includeLower: includeLower,
|
includeLower: includeLower,
|
||||||
upper: upper,
|
upper: upper,
|
||||||
includeUpper: includeUpper,
|
includeUpper: includeUpper,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumStartsWith(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.startsWith(
|
||||||
|
property: r'checksum',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumEndsWith(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.endsWith(
|
||||||
|
property: r'checksum',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumContains(
|
||||||
|
String value,
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.contains(
|
||||||
|
property: r'checksum',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumMatches(
|
||||||
|
String pattern,
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.matches(
|
||||||
|
property: r'checksum',
|
||||||
|
wildcard: pattern,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'checksum',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> checksumIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
property: r'checksum',
|
||||||
|
value: '',
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1053,15 +1294,6 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> isLocalEqualTo(bool value) {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
|
||||||
property: r'isLocal',
|
|
||||||
value: value,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> livePhotoVideoIdIsNull() {
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> livePhotoVideoIdIsNull() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(const FilterCondition.isNull(
|
return query.addFilterCondition(const FilterCondition.isNull(
|
||||||
@ -1210,8 +1442,24 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdIsNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(const FilterCondition.isNull(
|
||||||
|
property: r'localId',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdIsNotNull() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||||
|
property: r'localId',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdEqualTo(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdEqualTo(
|
||||||
String value, {
|
String? value, {
|
||||||
bool caseSensitive = true,
|
bool caseSensitive = true,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -1224,7 +1472,7 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdGreaterThan(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdGreaterThan(
|
||||||
String value, {
|
String? value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
bool caseSensitive = true,
|
bool caseSensitive = true,
|
||||||
}) {
|
}) {
|
||||||
@ -1239,7 +1487,7 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdLessThan(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdLessThan(
|
||||||
String value, {
|
String? value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
bool caseSensitive = true,
|
bool caseSensitive = true,
|
||||||
}) {
|
}) {
|
||||||
@ -1254,8 +1502,8 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdBetween(
|
QueryBuilder<Asset, Asset, QAfterFilterCondition> localIdBetween(
|
||||||
String lower,
|
String? lower,
|
||||||
String upper, {
|
String? upper, {
|
||||||
bool includeLower = true,
|
bool includeLower = true,
|
||||||
bool includeUpper = true,
|
bool includeUpper = true,
|
||||||
bool caseSensitive = true,
|
bool caseSensitive = true,
|
||||||
@ -1718,15 +1966,15 @@ extension AssetQueryObject on QueryBuilder<Asset, Asset, QFilterCondition> {}
|
|||||||
extension AssetQueryLinks on QueryBuilder<Asset, Asset, QFilterCondition> {}
|
extension AssetQueryLinks on QueryBuilder<Asset, Asset, QFilterCondition> {}
|
||||||
|
|
||||||
extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
|
extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByDeviceId() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByChecksum() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'deviceId', Sort.asc);
|
return query.addSortBy(r'checksum', Sort.asc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByDeviceIdDesc() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByChecksumDesc() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'deviceId', Sort.desc);
|
return query.addSortBy(r'checksum', Sort.desc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1814,18 +2062,6 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsLocal() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addSortBy(r'isLocal', Sort.asc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsLocalDesc() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addSortBy(r'isLocal', Sort.desc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByLivePhotoVideoId() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> sortByLivePhotoVideoId() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'livePhotoVideoId', Sort.asc);
|
return query.addSortBy(r'livePhotoVideoId', Sort.asc);
|
||||||
@ -1912,15 +2148,15 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
|
extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByDeviceId() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByChecksum() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'deviceId', Sort.asc);
|
return query.addSortBy(r'checksum', Sort.asc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByDeviceIdDesc() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByChecksumDesc() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'deviceId', Sort.desc);
|
return query.addSortBy(r'checksum', Sort.desc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2020,18 +2256,6 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsLocal() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addSortBy(r'isLocal', Sort.asc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsLocalDesc() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addSortBy(r'isLocal', Sort.desc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByLivePhotoVideoId() {
|
QueryBuilder<Asset, Asset, QAfterSortBy> thenByLivePhotoVideoId() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addSortBy(r'livePhotoVideoId', Sort.asc);
|
return query.addSortBy(r'livePhotoVideoId', Sort.asc);
|
||||||
@ -2118,9 +2342,10 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
|
extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
|
||||||
QueryBuilder<Asset, Asset, QDistinct> distinctByDeviceId() {
|
QueryBuilder<Asset, Asset, QDistinct> distinctByChecksum(
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addDistinctBy(r'deviceId');
|
return query.addDistinctBy(r'checksum', caseSensitive: caseSensitive);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2167,12 +2392,6 @@ extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QDistinct> distinctByIsLocal() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addDistinctBy(r'isLocal');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, Asset, QDistinct> distinctByLivePhotoVideoId(
|
QueryBuilder<Asset, Asset, QDistinct> distinctByLivePhotoVideoId(
|
||||||
{bool caseSensitive = true}) {
|
{bool caseSensitive = true}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
@ -2227,9 +2446,9 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, int, QQueryOperations> deviceIdProperty() {
|
QueryBuilder<Asset, String, QQueryOperations> checksumProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'deviceId');
|
return query.addPropertyName(r'checksum');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2275,19 +2494,13 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, bool, QQueryOperations> isLocalProperty() {
|
|
||||||
return QueryBuilder.apply(this, (query) {
|
|
||||||
return query.addPropertyName(r'isLocal');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryBuilder<Asset, String?, QQueryOperations> livePhotoVideoIdProperty() {
|
QueryBuilder<Asset, String?, QQueryOperations> livePhotoVideoIdProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'livePhotoVideoId');
|
return query.addPropertyName(r'livePhotoVideoId');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Asset, String, QQueryOperations> localIdProperty() {
|
QueryBuilder<Asset, String?, QQueryOperations> localIdProperty() {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addPropertyName(r'localId');
|
return query.addPropertyName(r'localId');
|
||||||
});
|
});
|
||||||
|
8
mobile/lib/shared/models/device_asset.dart
Normal file
8
mobile/lib/shared/models/device_asset.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
class DeviceAsset {
|
||||||
|
DeviceAsset({required this.hash});
|
||||||
|
|
||||||
|
@Index(unique: false, type: IndexType.hash)
|
||||||
|
List<byte> hash;
|
||||||
|
}
|
14
mobile/lib/shared/models/ios_device_asset.dart
Normal file
14
mobile/lib/shared/models/ios_device_asset.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:immich_mobile/shared/models/device_asset.dart';
|
||||||
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
part 'ios_device_asset.g.dart';
|
||||||
|
|
||||||
|
@Collection()
|
||||||
|
class IOSDeviceAsset extends DeviceAsset {
|
||||||
|
IOSDeviceAsset({required this.id, required super.hash});
|
||||||
|
|
||||||
|
@Index(replace: true, unique: true, type: IndexType.hash)
|
||||||
|
String id;
|
||||||
|
Id get isarId => fastHash(id);
|
||||||
|
}
|
780
mobile/lib/shared/models/ios_device_asset.g.dart
Normal file
780
mobile/lib/shared/models/ios_device_asset.g.dart
Normal file
@ -0,0 +1,780 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'ios_device_asset.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// IsarCollectionGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||||
|
|
||||||
|
extension GetIOSDeviceAssetCollection on Isar {
|
||||||
|
IsarCollection<IOSDeviceAsset> get iOSDeviceAssets => this.collection();
|
||||||
|
}
|
||||||
|
|
||||||
|
const IOSDeviceAssetSchema = CollectionSchema(
|
||||||
|
name: r'IOSDeviceAsset',
|
||||||
|
id: -1671546753821948030,
|
||||||
|
properties: {
|
||||||
|
r'hash': PropertySchema(
|
||||||
|
id: 0,
|
||||||
|
name: r'hash',
|
||||||
|
type: IsarType.byteList,
|
||||||
|
),
|
||||||
|
r'id': PropertySchema(
|
||||||
|
id: 1,
|
||||||
|
name: r'id',
|
||||||
|
type: IsarType.string,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
estimateSize: _iOSDeviceAssetEstimateSize,
|
||||||
|
serialize: _iOSDeviceAssetSerialize,
|
||||||
|
deserialize: _iOSDeviceAssetDeserialize,
|
||||||
|
deserializeProp: _iOSDeviceAssetDeserializeProp,
|
||||||
|
idName: r'isarId',
|
||||||
|
indexes: {
|
||||||
|
r'id': IndexSchema(
|
||||||
|
id: -3268401673993471357,
|
||||||
|
name: r'id',
|
||||||
|
unique: true,
|
||||||
|
replace: true,
|
||||||
|
properties: [
|
||||||
|
IndexPropertySchema(
|
||||||
|
name: r'id',
|
||||||
|
type: IndexType.hash,
|
||||||
|
caseSensitive: true,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
r'hash': IndexSchema(
|
||||||
|
id: -7973251393006690288,
|
||||||
|
name: r'hash',
|
||||||
|
unique: false,
|
||||||
|
replace: false,
|
||||||
|
properties: [
|
||||||
|
IndexPropertySchema(
|
||||||
|
name: r'hash',
|
||||||
|
type: IndexType.hash,
|
||||||
|
caseSensitive: false,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
links: {},
|
||||||
|
embeddedSchemas: {},
|
||||||
|
getId: _iOSDeviceAssetGetId,
|
||||||
|
getLinks: _iOSDeviceAssetGetLinks,
|
||||||
|
attach: _iOSDeviceAssetAttach,
|
||||||
|
version: '3.1.0+1',
|
||||||
|
);
|
||||||
|
|
||||||
|
int _iOSDeviceAssetEstimateSize(
|
||||||
|
IOSDeviceAsset object,
|
||||||
|
List<int> offsets,
|
||||||
|
Map<Type, List<int>> allOffsets,
|
||||||
|
) {
|
||||||
|
var bytesCount = offsets.last;
|
||||||
|
bytesCount += 3 + object.hash.length;
|
||||||
|
bytesCount += 3 + object.id.length * 3;
|
||||||
|
return bytesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _iOSDeviceAssetSerialize(
|
||||||
|
IOSDeviceAsset object,
|
||||||
|
IsarWriter writer,
|
||||||
|
List<int> offsets,
|
||||||
|
Map<Type, List<int>> allOffsets,
|
||||||
|
) {
|
||||||
|
writer.writeByteList(offsets[0], object.hash);
|
||||||
|
writer.writeString(offsets[1], object.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
IOSDeviceAsset _iOSDeviceAssetDeserialize(
|
||||||
|
Id id,
|
||||||
|
IsarReader reader,
|
||||||
|
List<int> offsets,
|
||||||
|
Map<Type, List<int>> allOffsets,
|
||||||
|
) {
|
||||||
|
final object = IOSDeviceAsset(
|
||||||
|
hash: reader.readByteList(offsets[0]) ?? [],
|
||||||
|
id: reader.readString(offsets[1]),
|
||||||
|
);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
P _iOSDeviceAssetDeserializeProp<P>(
|
||||||
|
IsarReader reader,
|
||||||
|
int propertyId,
|
||||||
|
int offset,
|
||||||
|
Map<Type, List<int>> allOffsets,
|
||||||
|
) {
|
||||||
|
switch (propertyId) {
|
||||||
|
case 0:
|
||||||
|
return (reader.readByteList(offset) ?? []) as P;
|
||||||
|
case 1:
|
||||||
|
return (reader.readString(offset)) as P;
|
||||||
|
default:
|
||||||
|
throw IsarError('Unknown property with id $propertyId');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Id _iOSDeviceAssetGetId(IOSDeviceAsset object) {
|
||||||
|
return object.isarId;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IsarLinkBase<dynamic>> _iOSDeviceAssetGetLinks(IOSDeviceAsset object) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
void _iOSDeviceAssetAttach(
|
||||||
|
IsarCollection<dynamic> col, Id id, IOSDeviceAsset object) {}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetByIndex on IsarCollection<IOSDeviceAsset> {
|
||||||
|
Future<IOSDeviceAsset?> getById(String id) {
|
||||||
|
return getByIndex(r'id', [id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
IOSDeviceAsset? getByIdSync(String id) {
|
||||||
|
return getByIndexSync(r'id', [id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteById(String id) {
|
||||||
|
return deleteByIndex(r'id', [id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleteByIdSync(String id) {
|
||||||
|
return deleteByIndexSync(r'id', [id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<IOSDeviceAsset?>> getAllById(List<String> idValues) {
|
||||||
|
final values = idValues.map((e) => [e]).toList();
|
||||||
|
return getAllByIndex(r'id', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IOSDeviceAsset?> getAllByIdSync(List<String> idValues) {
|
||||||
|
final values = idValues.map((e) => [e]).toList();
|
||||||
|
return getAllByIndexSync(r'id', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> deleteAllById(List<String> idValues) {
|
||||||
|
final values = idValues.map((e) => [e]).toList();
|
||||||
|
return deleteAllByIndex(r'id', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
int deleteAllByIdSync(List<String> idValues) {
|
||||||
|
final values = idValues.map((e) => [e]).toList();
|
||||||
|
return deleteAllByIndexSync(r'id', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Id> putById(IOSDeviceAsset object) {
|
||||||
|
return putByIndex(r'id', object);
|
||||||
|
}
|
||||||
|
|
||||||
|
Id putByIdSync(IOSDeviceAsset object, {bool saveLinks = true}) {
|
||||||
|
return putByIndexSync(r'id', object, saveLinks: saveLinks);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Id>> putAllById(List<IOSDeviceAsset> objects) {
|
||||||
|
return putAllByIndex(r'id', objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Id> putAllByIdSync(List<IOSDeviceAsset> objects,
|
||||||
|
{bool saveLinks = true}) {
|
||||||
|
return putAllByIndexSync(r'id', objects, saveLinks: saveLinks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQueryWhereSort
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QWhere> {
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhere> anyIsarId() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(const IdWhereClause.any());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQueryWhere
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QWhereClause> {
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> isarIdEqualTo(
|
||||||
|
Id isarId) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IdWhereClause.between(
|
||||||
|
lower: isarId,
|
||||||
|
upper: isarId,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause>
|
||||||
|
isarIdNotEqualTo(Id isarId) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
if (query.whereSort == Sort.asc) {
|
||||||
|
return query
|
||||||
|
.addWhereClause(
|
||||||
|
IdWhereClause.lessThan(upper: isarId, includeUpper: false),
|
||||||
|
)
|
||||||
|
.addWhereClause(
|
||||||
|
IdWhereClause.greaterThan(lower: isarId, includeLower: false),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return query
|
||||||
|
.addWhereClause(
|
||||||
|
IdWhereClause.greaterThan(lower: isarId, includeLower: false),
|
||||||
|
)
|
||||||
|
.addWhereClause(
|
||||||
|
IdWhereClause.lessThan(upper: isarId, includeUpper: false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause>
|
||||||
|
isarIdGreaterThan(Id isarId, {bool include = false}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(
|
||||||
|
IdWhereClause.greaterThan(lower: isarId, includeLower: include),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause>
|
||||||
|
isarIdLessThan(Id isarId, {bool include = false}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(
|
||||||
|
IdWhereClause.lessThan(upper: isarId, includeUpper: include),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> isarIdBetween(
|
||||||
|
Id lowerIsarId,
|
||||||
|
Id upperIsarId, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IdWhereClause.between(
|
||||||
|
lower: lowerIsarId,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upperIsarId,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> idEqualTo(
|
||||||
|
String id) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||||
|
indexName: r'id',
|
||||||
|
value: [id],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> idNotEqualTo(
|
||||||
|
String id) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
if (query.whereSort == Sort.asc) {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'id',
|
||||||
|
lower: [],
|
||||||
|
upper: [id],
|
||||||
|
includeUpper: false,
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'id',
|
||||||
|
lower: [id],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'id',
|
||||||
|
lower: [id],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'id',
|
||||||
|
lower: [],
|
||||||
|
upper: [id],
|
||||||
|
includeUpper: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause> hashEqualTo(
|
||||||
|
List<int> hash) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||||
|
indexName: r'hash',
|
||||||
|
value: [hash],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterWhereClause>
|
||||||
|
hashNotEqualTo(List<int> hash) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
if (query.whereSort == Sort.asc) {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'hash',
|
||||||
|
lower: [],
|
||||||
|
upper: [hash],
|
||||||
|
includeUpper: false,
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'hash',
|
||||||
|
lower: [hash],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return query
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'hash',
|
||||||
|
lower: [hash],
|
||||||
|
includeLower: false,
|
||||||
|
upper: [],
|
||||||
|
))
|
||||||
|
.addWhereClause(IndexWhereClause.between(
|
||||||
|
indexName: r'hash',
|
||||||
|
lower: [],
|
||||||
|
upper: [hash],
|
||||||
|
includeUpper: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQueryFilter
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QFilterCondition> {
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashElementEqualTo(int value) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'hash',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashElementGreaterThan(
|
||||||
|
int value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'hash',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashElementLessThan(
|
||||||
|
int value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'hash',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashElementBetween(
|
||||||
|
int lower,
|
||||||
|
int upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'hash',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashLengthEqualTo(int length) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
length,
|
||||||
|
true,
|
||||||
|
length,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
999999,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashLengthLessThan(
|
||||||
|
int length, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
length,
|
||||||
|
include,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashLengthGreaterThan(
|
||||||
|
int length, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
length,
|
||||||
|
include,
|
||||||
|
999999,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
hashLengthBetween(
|
||||||
|
int lower,
|
||||||
|
int upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.listLength(
|
||||||
|
r'hash',
|
||||||
|
lower,
|
||||||
|
includeLower,
|
||||||
|
upper,
|
||||||
|
includeUpper,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition> idEqualTo(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
idGreaterThan(
|
||||||
|
String value, {
|
||||||
|
bool include = false,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
idLessThan(
|
||||||
|
String value, {
|
||||||
|
bool include = false,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition> idBetween(
|
||||||
|
String lower,
|
||||||
|
String upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'id',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
idStartsWith(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.startsWith(
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
idEndsWith(
|
||||||
|
String value, {
|
||||||
|
bool caseSensitive = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.endsWith(
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
idContains(String value, {bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.contains(
|
||||||
|
property: r'id',
|
||||||
|
value: value,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition> idMatches(
|
||||||
|
String pattern,
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.matches(
|
||||||
|
property: r'id',
|
||||||
|
wildcard: pattern,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
idIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'id',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
idIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
property: r'id',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
isarIdEqualTo(Id value) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'isarId',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
isarIdGreaterThan(
|
||||||
|
Id value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
include: include,
|
||||||
|
property: r'isarId',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
isarIdLessThan(
|
||||||
|
Id value, {
|
||||||
|
bool include = false,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.lessThan(
|
||||||
|
include: include,
|
||||||
|
property: r'isarId',
|
||||||
|
value: value,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterFilterCondition>
|
||||||
|
isarIdBetween(
|
||||||
|
Id lower,
|
||||||
|
Id upper, {
|
||||||
|
bool includeLower = true,
|
||||||
|
bool includeUpper = true,
|
||||||
|
}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.between(
|
||||||
|
property: r'isarId',
|
||||||
|
lower: lower,
|
||||||
|
includeLower: includeLower,
|
||||||
|
upper: upper,
|
||||||
|
includeUpper: includeUpper,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQueryObject
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QFilterCondition> {}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQueryLinks
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QFilterCondition> {}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQuerySortBy
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QSortBy> {
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> sortById() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'id', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> sortByIdDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'id', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQuerySortThenBy
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QSortThenBy> {
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> thenById() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'id', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> thenByIdDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'id', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy> thenByIsarId() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'isarId', Sort.asc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QAfterSortBy>
|
||||||
|
thenByIsarIdDesc() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addSortBy(r'isarId', Sort.desc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQueryWhereDistinct
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QDistinct> {
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QDistinct> distinctByHash() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'hash');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QDistinct> distinctById(
|
||||||
|
{bool caseSensitive = true}) {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addDistinctBy(r'id', caseSensitive: caseSensitive);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IOSDeviceAssetQueryProperty
|
||||||
|
on QueryBuilder<IOSDeviceAsset, IOSDeviceAsset, QQueryProperty> {
|
||||||
|
QueryBuilder<IOSDeviceAsset, int, QQueryOperations> isarIdProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'isarId');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, List<int>, QQueryOperations> hashProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'hash');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<IOSDeviceAsset, String, QQueryOperations> idProperty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addPropertyName(r'id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -18,11 +18,7 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
/// State does not contain archived assets.
|
class AssetNotifier extends StateNotifier<bool> {
|
||||||
/// Use database provider if you want to access the isArchived assets
|
|
||||||
class AssetsState {}
|
|
||||||
|
|
||||||
class AssetNotifier extends StateNotifier<AssetsState> {
|
|
||||||
final AssetService _assetService;
|
final AssetService _assetService;
|
||||||
final AlbumService _albumService;
|
final AlbumService _albumService;
|
||||||
final UserService _userService;
|
final UserService _userService;
|
||||||
@ -38,7 +34,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
|
|||||||
this._userService,
|
this._userService,
|
||||||
this._syncService,
|
this._syncService,
|
||||||
this._db,
|
this._db,
|
||||||
) : super(AssetsState());
|
) : super(false);
|
||||||
|
|
||||||
Future<void> getAllAsset({bool clear = false}) async {
|
Future<void> getAllAsset({bool clear = false}) async {
|
||||||
if (_getAllAssetInProgress || _deleteInProgress) {
|
if (_getAllAssetInProgress || _deleteInProgress) {
|
||||||
@ -48,14 +44,15 @@ class AssetNotifier extends StateNotifier<AssetsState> {
|
|||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
_getAllAssetInProgress = true;
|
_getAllAssetInProgress = true;
|
||||||
|
state = true;
|
||||||
if (clear) {
|
if (clear) {
|
||||||
await clearAssetsAndAlbums(_db);
|
await clearAssetsAndAlbums(_db);
|
||||||
log.info("Manual refresh requested, cleared assets and albums from db");
|
log.info("Manual refresh requested, cleared assets and albums from db");
|
||||||
}
|
}
|
||||||
|
await _userService.refreshUsers();
|
||||||
final bool newRemote = await _assetService.refreshRemoteAssets();
|
final bool newRemote = await _assetService.refreshRemoteAssets();
|
||||||
final bool newLocal = await _albumService.refreshDeviceAlbums();
|
final bool newLocal = await _albumService.refreshDeviceAlbums();
|
||||||
debugPrint("newRemote: $newRemote, newLocal: $newLocal");
|
debugPrint("newRemote: $newRemote, newLocal: $newLocal");
|
||||||
await _userService.refreshUsers();
|
|
||||||
final List<User> partners =
|
final List<User> partners =
|
||||||
await _db.users.filter().isPartnerSharedWithEqualTo(true).findAll();
|
await _db.users.filter().isPartnerSharedWithEqualTo(true).findAll();
|
||||||
for (User u in partners) {
|
for (User u in partners) {
|
||||||
@ -64,6 +61,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
|
|||||||
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
||||||
} finally {
|
} finally {
|
||||||
_getAllAssetInProgress = false;
|
_getAllAssetInProgress = false;
|
||||||
|
state = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +77,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
|
|||||||
|
|
||||||
Future<void> deleteAssets(Set<Asset> deleteAssets) async {
|
Future<void> deleteAssets(Set<Asset> deleteAssets) async {
|
||||||
_deleteInProgress = true;
|
_deleteInProgress = true;
|
||||||
|
state = true;
|
||||||
try {
|
try {
|
||||||
final localDeleted = await _deleteLocalAssets(deleteAssets);
|
final localDeleted = await _deleteLocalAssets(deleteAssets);
|
||||||
final remoteDeleted = await _deleteRemoteAssets(deleteAssets);
|
final remoteDeleted = await _deleteRemoteAssets(deleteAssets);
|
||||||
@ -91,24 +90,14 @@ class AssetNotifier extends StateNotifier<AssetsState> {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
_deleteInProgress = false;
|
_deleteInProgress = false;
|
||||||
|
state = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> _deleteLocalAssets(Set<Asset> assetsToDelete) async {
|
Future<List<String>> _deleteLocalAssets(Set<Asset> assetsToDelete) async {
|
||||||
final int deviceId = Store.get(StoreKey.deviceIdHash);
|
final List<String> local =
|
||||||
final List<String> local = [];
|
assetsToDelete.where((a) => a.isLocal).map((a) => a.localId!).toList();
|
||||||
// Delete asset from device
|
// Delete asset from device
|
||||||
for (final Asset asset in assetsToDelete) {
|
|
||||||
if (asset.isLocal) {
|
|
||||||
local.add(asset.localId);
|
|
||||||
} else if (asset.deviceId == deviceId) {
|
|
||||||
// Delete asset on device if it is still present
|
|
||||||
var localAsset = await AssetEntity.fromId(asset.localId);
|
|
||||||
if (localAsset != null) {
|
|
||||||
local.add(localAsset.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (local.isNotEmpty) {
|
if (local.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
return await PhotoManager.editor.deleteWithIds(local);
|
return await PhotoManager.editor.deleteWithIds(local);
|
||||||
@ -153,7 +142,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final assetProvider = StateNotifierProvider<AssetNotifier, AssetsState>((ref) {
|
final assetProvider = StateNotifierProvider<AssetNotifier, bool>((ref) {
|
||||||
return AssetNotifier(
|
return AssetNotifier(
|
||||||
ref.watch(assetServiceProvider),
|
ref.watch(assetServiceProvider),
|
||||||
ref.watch(albumServiceProvider),
|
ref.watch(albumServiceProvider),
|
||||||
|
175
mobile/lib/shared/services/hash.service.dart
Normal file
175
mobile/lib/shared/services/hash.service.dart
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/android_device_asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/device_asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/ios_device_asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
|
import 'package:immich_mobile/utils/builtin_extensions.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
|
class HashService {
|
||||||
|
HashService(this._db, this._backgroundService);
|
||||||
|
final Isar _db;
|
||||||
|
final BackgroundService _backgroundService;
|
||||||
|
final _log = Logger('HashService');
|
||||||
|
|
||||||
|
/// Returns all assets that were successfully hashed
|
||||||
|
Future<List<Asset>> getHashedAssets(
|
||||||
|
AssetPathEntity album, {
|
||||||
|
int start = 0,
|
||||||
|
int end = 0x7fffffffffffffff,
|
||||||
|
Set<String>? excludedAssets,
|
||||||
|
}) async {
|
||||||
|
final entities = await album.getAssetListRange(start: start, end: end);
|
||||||
|
final filtered = excludedAssets == null
|
||||||
|
? entities
|
||||||
|
: entities.where((e) => !excludedAssets.contains(e.id)).toList();
|
||||||
|
return _hashAssets(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a list of [AssetEntity]s to [Asset]s including only those
|
||||||
|
/// that were successfully hashed. Hashes are looked up in a DB table
|
||||||
|
/// [AndroidDeviceAsset] / [IOSDeviceAsset] by local id. Only missing
|
||||||
|
/// entries are newly hashed and added to the DB table.
|
||||||
|
Future<List<Asset>> _hashAssets(List<AssetEntity> assetEntities) async {
|
||||||
|
const int batchFileCount = 128;
|
||||||
|
const int batchDataSize = 1024 * 1024 * 1024; // 1GB
|
||||||
|
|
||||||
|
final ids = assetEntities
|
||||||
|
.map(Platform.isAndroid ? (a) => a.id.toInt() : (a) => a.id)
|
||||||
|
.toList();
|
||||||
|
final List<DeviceAsset?> hashes = await _lookupHashes(ids);
|
||||||
|
final List<DeviceAsset> toAdd = [];
|
||||||
|
final List<String> toHash = [];
|
||||||
|
|
||||||
|
int bytes = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < assetEntities.length; i++) {
|
||||||
|
if (hashes[i] != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final file = await assetEntities[i].originFile;
|
||||||
|
if (file == null) {
|
||||||
|
_log.warning(
|
||||||
|
"Failed to get file for asset ${assetEntities[i].id}, skipping",
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bytes += await file.length();
|
||||||
|
toHash.add(file.path);
|
||||||
|
final deviceAsset = Platform.isAndroid
|
||||||
|
? AndroidDeviceAsset(id: ids[i] as int, hash: const [])
|
||||||
|
: IOSDeviceAsset(id: ids[i] as String, hash: const []);
|
||||||
|
toAdd.add(deviceAsset);
|
||||||
|
hashes[i] = deviceAsset;
|
||||||
|
if (toHash.length == batchFileCount || bytes >= batchDataSize) {
|
||||||
|
await _processBatch(toHash, toAdd);
|
||||||
|
toAdd.clear();
|
||||||
|
toHash.clear();
|
||||||
|
bytes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toHash.isNotEmpty) {
|
||||||
|
await _processBatch(toHash, toAdd);
|
||||||
|
}
|
||||||
|
return _mapAllHashedAssets(assetEntities, hashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookup hashes of assets by their local ID
|
||||||
|
Future<List<DeviceAsset?>> _lookupHashes(List<Object> ids) =>
|
||||||
|
Platform.isAndroid
|
||||||
|
? _db.androidDeviceAssets.getAll(ids.cast())
|
||||||
|
: _db.iOSDeviceAssets.getAllById(ids.cast());
|
||||||
|
|
||||||
|
/// Processes a batch of files and saves any successfully hashed
|
||||||
|
/// values to the DB table.
|
||||||
|
Future<void> _processBatch(
|
||||||
|
final List<String> toHash,
|
||||||
|
final List<DeviceAsset> toAdd,
|
||||||
|
) async {
|
||||||
|
final hashes = await _hashFiles(toHash);
|
||||||
|
bool anyNull = false;
|
||||||
|
for (int j = 0; j < hashes.length; j++) {
|
||||||
|
if (hashes[j]?.length == 20) {
|
||||||
|
toAdd[j].hash = hashes[j]!;
|
||||||
|
} else {
|
||||||
|
_log.warning("Failed to hash file ${toHash[j]}, skipping");
|
||||||
|
anyNull = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final validHashes = anyNull
|
||||||
|
? toAdd.where((e) => e.hash.length == 20).toList(growable: false)
|
||||||
|
: toAdd;
|
||||||
|
await _db.writeTxn(
|
||||||
|
() => Platform.isAndroid
|
||||||
|
? _db.androidDeviceAssets.putAll(validHashes.cast())
|
||||||
|
: _db.iOSDeviceAssets.putAll(validHashes.cast()),
|
||||||
|
);
|
||||||
|
_log.fine("Hashed ${validHashes.length}/${toHash.length} assets");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hashes the given files and returns a list of the same length
|
||||||
|
/// files that could not be hashed have a `null` value
|
||||||
|
Future<List<Uint8List?>> _hashFiles(List<String> paths) async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
final List<Uint8List?>? hashes =
|
||||||
|
await _backgroundService.digestFiles(paths);
|
||||||
|
if (hashes == null) {
|
||||||
|
throw Exception("Hashing ${paths.length} files failed");
|
||||||
|
}
|
||||||
|
return hashes;
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
final List<Uint8List?> result = List.filled(paths.length, null);
|
||||||
|
for (int i = 0; i < paths.length; i++) {
|
||||||
|
result[i] = await _hashAssetDart(File(paths[i]));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw Exception("_hashFiles implementation missing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hashes a single file using Dart's crypto package
|
||||||
|
Future<Uint8List?> _hashAssetDart(File f) async {
|
||||||
|
late Digest output;
|
||||||
|
final sink = sha1.startChunkedConversion(
|
||||||
|
ChunkedConversionSink<Digest>.withCallback((accumulated) {
|
||||||
|
output = accumulated.first;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await for (final chunk in f.openRead()) {
|
||||||
|
sink.add(chunk);
|
||||||
|
}
|
||||||
|
sink.close();
|
||||||
|
return Uint8List.fromList(output.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts [AssetEntity]s that were successfully hashed to [Asset]s
|
||||||
|
List<Asset> _mapAllHashedAssets(
|
||||||
|
List<AssetEntity> assets,
|
||||||
|
List<DeviceAsset?> hashes,
|
||||||
|
) {
|
||||||
|
final List<Asset> result = [];
|
||||||
|
for (int i = 0; i < assets.length; i++) {
|
||||||
|
if (hashes[i] != null && hashes[i]!.hash.isNotEmpty) {
|
||||||
|
result.add(Asset.local(assets[i], hashes[i]!.hash));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final hashServiceProvider = Provider(
|
||||||
|
(ref) => HashService(
|
||||||
|
ref.watch(dbProvider),
|
||||||
|
ref.watch(backgroundServiceProvider),
|
||||||
|
),
|
||||||
|
);
|
@ -4,10 +4,12 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/shared/models/album.dart';
|
import 'package:immich_mobile/shared/models/album.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/etag.dart';
|
||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/hash.service.dart';
|
||||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||||
import 'package:immich_mobile/utils/builtin_extensions.dart';
|
import 'package:immich_mobile/utils/builtin_extensions.dart';
|
||||||
import 'package:immich_mobile/utils/diff.dart';
|
import 'package:immich_mobile/utils/diff.dart';
|
||||||
@ -16,15 +18,17 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
final syncServiceProvider =
|
final syncServiceProvider = Provider(
|
||||||
Provider((ref) => SyncService(ref.watch(dbProvider)));
|
(ref) => SyncService(ref.watch(dbProvider), ref.watch(hashServiceProvider)),
|
||||||
|
);
|
||||||
|
|
||||||
class SyncService {
|
class SyncService {
|
||||||
final Isar _db;
|
final Isar _db;
|
||||||
|
final HashService _hashService;
|
||||||
final AsyncMutex _lock = AsyncMutex();
|
final AsyncMutex _lock = AsyncMutex();
|
||||||
final Logger _log = Logger('SyncService');
|
final Logger _log = Logger('SyncService');
|
||||||
|
|
||||||
SyncService(this._db);
|
SyncService(this._db, this._hashService);
|
||||||
|
|
||||||
// public methods:
|
// public methods:
|
||||||
|
|
||||||
@ -33,6 +37,7 @@ class SyncService {
|
|||||||
Future<bool> syncUsersFromServer(List<User> users) async {
|
Future<bool> syncUsersFromServer(List<User> users) async {
|
||||||
users.sortBy((u) => u.id);
|
users.sortBy((u) => u.id);
|
||||||
final dbUsers = await _db.users.where().sortById().findAll();
|
final dbUsers = await _db.users.where().sortById().findAll();
|
||||||
|
assert(dbUsers.isSortedBy((u) => u.id), "dbUsers not sorted!");
|
||||||
final List<int> toDelete = [];
|
final List<int> toDelete = [];
|
||||||
final List<User> toUpsert = [];
|
final List<User> toUpsert = [];
|
||||||
final changes = diffSortedListsSync(
|
final changes = diffSortedListsSync(
|
||||||
@ -108,40 +113,16 @@ class SyncService {
|
|||||||
// private methods:
|
// private methods:
|
||||||
|
|
||||||
/// Syncs a new asset to the db. Returns `true` if successful
|
/// Syncs a new asset to the db. Returns `true` if successful
|
||||||
Future<bool> _syncNewAssetToDb(Asset newAsset) async {
|
Future<bool> _syncNewAssetToDb(Asset a) async {
|
||||||
final List<Asset> inDb = await _db.assets
|
final Asset? inDb =
|
||||||
.where()
|
await _db.assets.getByChecksumOwnerId(a.checksum, a.ownerId);
|
||||||
.localIdDeviceIdEqualTo(newAsset.localId, newAsset.deviceId)
|
if (inDb != null) {
|
||||||
.findAll();
|
|
||||||
Asset? match;
|
|
||||||
if (inDb.length == 1) {
|
|
||||||
// exactly one match: trivial case
|
|
||||||
match = inDb.first;
|
|
||||||
} else if (inDb.length > 1) {
|
|
||||||
// TODO instead of this heuristics: match by checksum once available
|
|
||||||
for (Asset a in inDb) {
|
|
||||||
if (a.ownerId == newAsset.ownerId &&
|
|
||||||
a.fileModifiedAt.isAtSameMomentAs(newAsset.fileModifiedAt)) {
|
|
||||||
assert(match == null);
|
|
||||||
match = a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (match == null) {
|
|
||||||
for (Asset a in inDb) {
|
|
||||||
if (a.ownerId == newAsset.ownerId) {
|
|
||||||
assert(match == null);
|
|
||||||
match = a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (match != null) {
|
|
||||||
// unify local/remote assets by replacing the
|
// unify local/remote assets by replacing the
|
||||||
// local-only asset in the DB with a local&remote asset
|
// local-only asset in the DB with a local&remote asset
|
||||||
newAsset = match.updatedCopy(newAsset);
|
a = inDb.updatedCopy(a);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await _db.writeTxn(() => newAsset.put(_db));
|
await _db.writeTxn(() => a.put(_db));
|
||||||
} on IsarError catch (e) {
|
} on IsarError catch (e) {
|
||||||
_log.severe("Failed to put new asset into db: $e");
|
_log.severe("Failed to put new asset into db: $e");
|
||||||
return false;
|
return false;
|
||||||
@ -162,11 +143,11 @@ class SyncService {
|
|||||||
final List<Asset> inDb = await _db.assets
|
final List<Asset> inDb = await _db.assets
|
||||||
.filter()
|
.filter()
|
||||||
.ownerIdEqualTo(user.isarId)
|
.ownerIdEqualTo(user.isarId)
|
||||||
.sortByDeviceId()
|
.sortByChecksum()
|
||||||
.thenByLocalId()
|
|
||||||
.thenByFileModifiedAt()
|
|
||||||
.findAll();
|
.findAll();
|
||||||
remote.sort(Asset.compareByOwnerDeviceLocalIdModified);
|
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
|
||||||
|
|
||||||
|
remote.sort(Asset.compareByChecksum);
|
||||||
final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true);
|
final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true);
|
||||||
if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) {
|
if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) {
|
||||||
return false;
|
return false;
|
||||||
@ -199,6 +180,7 @@ class SyncService {
|
|||||||
query = baseQuery.owner((q) => q.isarIdEqualTo(me.isarId));
|
query = baseQuery.owner((q) => q.isarIdEqualTo(me.isarId));
|
||||||
}
|
}
|
||||||
final List<Album> dbAlbums = await query.sortByRemoteId().findAll();
|
final List<Album> dbAlbums = await query.sortByRemoteId().findAll();
|
||||||
|
assert(dbAlbums.isSortedBy((e) => e.remoteId!), "dbAlbums not sorted!");
|
||||||
|
|
||||||
final List<Asset> toDelete = [];
|
final List<Asset> toDelete = [];
|
||||||
final List<Asset> existing = [];
|
final List<Asset> existing = [];
|
||||||
@ -245,16 +227,16 @@ class SyncService {
|
|||||||
if (dto.assetCount != dto.assets.length) {
|
if (dto.assetCount != dto.assets.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final assetsInDb = await album.assets
|
final assetsInDb =
|
||||||
.filter()
|
await album.assets.filter().sortByOwnerId().thenByChecksum().findAll();
|
||||||
.sortByOwnerId()
|
assert(assetsInDb.isSorted(Asset.compareByOwnerChecksum), "inDb unsorted!");
|
||||||
.thenByDeviceId()
|
|
||||||
.thenByLocalId()
|
|
||||||
.thenByFileModifiedAt()
|
|
||||||
.findAll();
|
|
||||||
final List<Asset> assetsOnRemote = dto.getAssets();
|
final List<Asset> assetsOnRemote = dto.getAssets();
|
||||||
assetsOnRemote.sort(Asset.compareByOwnerDeviceLocalIdModified);
|
assetsOnRemote.sort(Asset.compareByOwnerChecksum);
|
||||||
final (toAdd, toUpdate, toUnlink) = _diffAssets(assetsOnRemote, assetsInDb);
|
final (toAdd, toUpdate, toUnlink) = _diffAssets(
|
||||||
|
assetsOnRemote,
|
||||||
|
assetsInDb,
|
||||||
|
compare: Asset.compareByOwnerChecksum,
|
||||||
|
);
|
||||||
|
|
||||||
// update shared users
|
// update shared users
|
||||||
final List<User> sharedUsers = album.sharedUsers.toList(growable: false);
|
final List<User> sharedUsers = album.sharedUsers.toList(growable: false);
|
||||||
@ -297,6 +279,7 @@ class SyncService {
|
|||||||
await album.assets.update(link: assetsToLink, unlink: toUnlink.cast());
|
await album.assets.update(link: assetsToLink, unlink: toUnlink.cast());
|
||||||
await _db.albums.put(album);
|
await _db.albums.put(album);
|
||||||
});
|
});
|
||||||
|
_log.info("Synced changes of remote album ${album.name} to DB");
|
||||||
} on IsarError catch (e) {
|
} on IsarError catch (e) {
|
||||||
_log.severe("Failed to sync remote album to database $e");
|
_log.severe("Failed to sync remote album to database $e");
|
||||||
}
|
}
|
||||||
@ -382,10 +365,11 @@ class SyncService {
|
|||||||
Set<String>? excludedAssets,
|
Set<String>? excludedAssets,
|
||||||
]) async {
|
]) async {
|
||||||
onDevice.sort((a, b) => a.id.compareTo(b.id));
|
onDevice.sort((a, b) => a.id.compareTo(b.id));
|
||||||
final List<Album> inDb =
|
final inDb =
|
||||||
await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll();
|
await _db.albums.where().localIdIsNotNull().sortByLocalId().findAll();
|
||||||
final List<Asset> deleteCandidates = [];
|
final List<Asset> deleteCandidates = [];
|
||||||
final List<Asset> existing = [];
|
final List<Asset> existing = [];
|
||||||
|
assert(inDb.isSorted((a, b) => a.localId!.compareTo(b.localId!)), "sort!");
|
||||||
final bool anyChanges = await diffSortedLists(
|
final bool anyChanges = await diffSortedLists(
|
||||||
onDevice,
|
onDevice,
|
||||||
inDb,
|
inDb,
|
||||||
@ -447,14 +431,15 @@ class SyncService {
|
|||||||
final inDb = await album.assets
|
final inDb = await album.assets
|
||||||
.filter()
|
.filter()
|
||||||
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
.ownerIdEqualTo(Store.get(StoreKey.currentUser).isarId)
|
||||||
.deviceIdEqualTo(Store.get(StoreKey.deviceIdHash))
|
.sortByChecksum()
|
||||||
.sortByLocalId()
|
|
||||||
.findAll();
|
.findAll();
|
||||||
|
assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!");
|
||||||
|
final int assetCountOnDevice = await ape.assetCountAsync;
|
||||||
final List<Asset> onDevice =
|
final List<Asset> onDevice =
|
||||||
await ape.getAssets(excludedAssets: excludedAssets);
|
await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets);
|
||||||
onDevice.sort(Asset.compareByLocalId);
|
_removeDuplicates(onDevice);
|
||||||
final (toAdd, toUpdate, toDelete) =
|
// _removeDuplicates sorts `onDevice` by checksum
|
||||||
_diffAssets(onDevice, inDb, compare: Asset.compareByLocalId);
|
final (toAdd, toUpdate, toDelete) = _diffAssets(onDevice, inDb);
|
||||||
if (toAdd.isEmpty &&
|
if (toAdd.isEmpty &&
|
||||||
toUpdate.isEmpty &&
|
toUpdate.isEmpty &&
|
||||||
toDelete.isEmpty &&
|
toDelete.isEmpty &&
|
||||||
@ -491,6 +476,9 @@ class SyncService {
|
|||||||
await _db.albums.put(album);
|
await _db.albums.put(album);
|
||||||
album.thumbnail.value ??= await album.assets.filter().findFirst();
|
album.thumbnail.value ??= await album.assets.filter().findFirst();
|
||||||
await album.thumbnail.save();
|
await album.thumbnail.save();
|
||||||
|
await _db.eTags.put(
|
||||||
|
ETag(id: ape.eTagKeyAssetCount, value: assetCountOnDevice.toString()),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
_log.info("Synced changes of local album ${ape.name} to DB");
|
_log.info("Synced changes of local album ${ape.name} to DB");
|
||||||
} on IsarError catch (e) {
|
} on IsarError catch (e) {
|
||||||
@ -503,8 +491,13 @@ class SyncService {
|
|||||||
/// fast path for common case: only new assets were added to device album
|
/// fast path for common case: only new assets were added to device album
|
||||||
/// returns `true` if successfull, else `false`
|
/// returns `true` if successfull, else `false`
|
||||||
Future<bool> _syncDeviceAlbumFast(AssetPathEntity ape, Album album) async {
|
Future<bool> _syncDeviceAlbumFast(AssetPathEntity ape, Album album) async {
|
||||||
|
if (!(ape.lastModified ?? DateTime.now()).isAfter(album.modifiedAt)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
final int totalOnDevice = await ape.assetCountAsync;
|
final int totalOnDevice = await ape.assetCountAsync;
|
||||||
final AssetPathEntity? modified = totalOnDevice > album.assetCount
|
final int lastKnownTotal =
|
||||||
|
(await _db.eTags.getById(ape.eTagKeyAssetCount))?.value?.toInt() ?? 0;
|
||||||
|
final AssetPathEntity? modified = totalOnDevice > lastKnownTotal
|
||||||
? await ape.fetchPathProperties(
|
? await ape.fetchPathProperties(
|
||||||
filterOptionGroup: FilterOptionGroup(
|
filterOptionGroup: FilterOptionGroup(
|
||||||
updateTimeCond: DateTimeCond(
|
updateTimeCond: DateTimeCond(
|
||||||
@ -517,17 +510,22 @@ class SyncService {
|
|||||||
if (modified == null) {
|
if (modified == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final List<Asset> newAssets = await modified.getAssets();
|
final List<Asset> newAssets = await _hashService.getHashedAssets(modified);
|
||||||
if (totalOnDevice != album.assets.length + newAssets.length) {
|
|
||||||
|
if (totalOnDevice != lastKnownTotal + newAssets.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
album.modifiedAt = ape.lastModified ?? DateTime.now();
|
album.modifiedAt = ape.lastModified ?? DateTime.now();
|
||||||
|
_removeDuplicates(newAssets);
|
||||||
final (existingInDb, updated) = await _linkWithExistingFromDb(newAssets);
|
final (existingInDb, updated) = await _linkWithExistingFromDb(newAssets);
|
||||||
try {
|
try {
|
||||||
await _db.writeTxn(() async {
|
await _db.writeTxn(() async {
|
||||||
await _db.assets.putAll(updated);
|
await _db.assets.putAll(updated);
|
||||||
await album.assets.update(link: existingInDb + updated);
|
await album.assets.update(link: existingInDb + updated);
|
||||||
await _db.albums.put(album);
|
await _db.albums.put(album);
|
||||||
|
await _db.eTags.put(
|
||||||
|
ETag(id: ape.eTagKeyAssetCount, value: totalOnDevice.toString()),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
_log.info("Fast synced local album ${ape.name} to DB");
|
_log.info("Fast synced local album ${ape.name} to DB");
|
||||||
} on IsarError catch (e) {
|
} on IsarError catch (e) {
|
||||||
@ -547,7 +545,9 @@ class SyncService {
|
|||||||
]) async {
|
]) async {
|
||||||
_log.info("Syncing a new local album to DB: ${ape.name}");
|
_log.info("Syncing a new local album to DB: ${ape.name}");
|
||||||
final Album a = Album.local(ape);
|
final Album a = Album.local(ape);
|
||||||
final assets = await ape.getAssets(excludedAssets: excludedAssets);
|
final assets =
|
||||||
|
await _hashService.getHashedAssets(ape, excludedAssets: excludedAssets);
|
||||||
|
_removeDuplicates(assets);
|
||||||
final (existingInDb, updated) = await _linkWithExistingFromDb(assets);
|
final (existingInDb, updated) = await _linkWithExistingFromDb(assets);
|
||||||
_log.info(
|
_log.info(
|
||||||
"${existingInDb.length} assets already existed in DB, to upsert ${updated.length}",
|
"${existingInDb.length} assets already existed in DB, to upsert ${updated.length}",
|
||||||
@ -570,44 +570,29 @@ class SyncService {
|
|||||||
Future<(List<Asset> existing, List<Asset> updated)> _linkWithExistingFromDb(
|
Future<(List<Asset> existing, List<Asset> updated)> _linkWithExistingFromDb(
|
||||||
List<Asset> assets,
|
List<Asset> assets,
|
||||||
) async {
|
) async {
|
||||||
if (assets.isEmpty) {
|
if (assets.isEmpty) return ([].cast<Asset>(), [].cast<Asset>());
|
||||||
return ([].cast<Asset>(), [].cast<Asset>());
|
|
||||||
}
|
final List<Asset?> inDb = await _db.assets.getAllByChecksumOwnerId(
|
||||||
final List<Asset> inDb = await _db.assets
|
assets.map((a) => a.checksum).toList(growable: false),
|
||||||
.where()
|
assets.map((a) => a.ownerId).toInt64List(),
|
||||||
.anyOf(
|
|
||||||
assets,
|
|
||||||
(q, Asset e) => q.localIdDeviceIdEqualTo(e.localId, e.deviceId),
|
|
||||||
)
|
|
||||||
.sortByOwnerId()
|
|
||||||
.thenByDeviceId()
|
|
||||||
.thenByLocalId()
|
|
||||||
.thenByFileModifiedAt()
|
|
||||||
.findAll();
|
|
||||||
assets.sort(Asset.compareByOwnerDeviceLocalIdModified);
|
|
||||||
final List<Asset> existing = [], toUpsert = [];
|
|
||||||
diffSortedListsSync(
|
|
||||||
inDb,
|
|
||||||
assets,
|
|
||||||
// do not compare by modified date because for some assets dates differ on
|
|
||||||
// client and server, thus never reaching "both" case below
|
|
||||||
compare: Asset.compareByOwnerDeviceLocalId,
|
|
||||||
both: (Asset a, Asset b) {
|
|
||||||
if (a.canUpdate(b)) {
|
|
||||||
toUpsert.add(a.updatedCopy(b));
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
existing.add(a);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onlyFirst: (Asset a) => _log.finer(
|
|
||||||
"_linkWithExistingFromDb encountered asset only in DB: $a",
|
|
||||||
null,
|
|
||||||
StackTrace.current,
|
|
||||||
),
|
|
||||||
onlySecond: (Asset b) => toUpsert.add(b),
|
|
||||||
);
|
);
|
||||||
|
assert(inDb.length == assets.length);
|
||||||
|
final List<Asset> existing = [], toUpsert = [];
|
||||||
|
for (int i = 0; i < assets.length; i++) {
|
||||||
|
final Asset? b = inDb[i];
|
||||||
|
if (b == null) {
|
||||||
|
toUpsert.add(assets[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (b.canUpdate(assets[i])) {
|
||||||
|
final updated = b.updatedCopy(assets[i]);
|
||||||
|
assert(updated.id != Isar.autoIncrement);
|
||||||
|
toUpsert.add(updated);
|
||||||
|
} else {
|
||||||
|
existing.add(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(existing.length + toUpsert.length == assets.length);
|
||||||
return (existing, toUpsert);
|
return (existing, toUpsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,11 +612,63 @@ class SyncService {
|
|||||||
});
|
});
|
||||||
_log.info("Upserted ${assets.length} assets into the DB");
|
_log.info("Upserted ${assets.length} assets into the DB");
|
||||||
} on IsarError catch (e) {
|
} on IsarError catch (e) {
|
||||||
_log.warning(
|
_log.severe(
|
||||||
"Failed to upsert ${assets.length} assets into the DB: ${e.toString()}",
|
"Failed to upsert ${assets.length} assets into the DB: ${e.toString()}",
|
||||||
);
|
);
|
||||||
|
// give details on the errors
|
||||||
|
assets.sort(Asset.compareByOwnerChecksum);
|
||||||
|
final inDb = await _db.assets.getAllByChecksumOwnerId(
|
||||||
|
assets.map((e) => e.checksum).toList(growable: false),
|
||||||
|
assets.map((e) => e.ownerId).toInt64List(),
|
||||||
|
);
|
||||||
|
for (int i = 0; i < assets.length; i++) {
|
||||||
|
final Asset a = assets[i];
|
||||||
|
final Asset? b = inDb[i];
|
||||||
|
if (b == null) {
|
||||||
|
if (a.id != Isar.autoIncrement) {
|
||||||
|
_log.warning(
|
||||||
|
"Trying to update an asset that does not exist in DB:\n$a",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (a.id != b.id) {
|
||||||
|
_log.warning(
|
||||||
|
"Trying to insert another asset with the same checksum+owner. In DB:\n$b\nTo insert:\n$a",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 1; i < assets.length; i++) {
|
||||||
|
if (Asset.compareByOwnerChecksum(assets[i - 1], assets[i]) == 0) {
|
||||||
|
_log.warning(
|
||||||
|
"Trying to insert duplicate assets:\n${assets[i - 1]}\n${assets[i]}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Asset> _removeDuplicates(List<Asset> assets) {
|
||||||
|
final int before = assets.length;
|
||||||
|
assets.sort(Asset.compareByOwnerChecksumCreatedModified);
|
||||||
|
assets.uniqueConsecutive(
|
||||||
|
compare: Asset.compareByOwnerChecksum,
|
||||||
|
onDuplicate: (a, b) =>
|
||||||
|
_log.info("Ignoring duplicate assets on device:\n$a\n$b"),
|
||||||
|
);
|
||||||
|
final int duplicates = before - assets.length;
|
||||||
|
if (duplicates > 0) {
|
||||||
|
_log.warning("Ignored $duplicates duplicate assets on device");
|
||||||
|
}
|
||||||
|
return assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns `true` if the albums differ on the surface
|
||||||
|
Future<bool> _hasAssetPathEntityChanged(AssetPathEntity a, Album b) async {
|
||||||
|
return a.name != b.name ||
|
||||||
|
a.lastModified == null ||
|
||||||
|
!a.lastModified!.isAtSameMomentAs(b.modifiedAt) ||
|
||||||
|
await a.assetCountAsync !=
|
||||||
|
(await _db.eTags.getById(a.eTagKeyAssetCount))?.value?.toInt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a triple(toAdd, toUpdate, toRemove)
|
/// Returns a triple(toAdd, toUpdate, toRemove)
|
||||||
@ -639,7 +676,7 @@ class SyncService {
|
|||||||
List<Asset> assets,
|
List<Asset> assets,
|
||||||
List<Asset> inDb, {
|
List<Asset> inDb, {
|
||||||
bool? remote,
|
bool? remote,
|
||||||
int Function(Asset, Asset) compare = Asset.compareByOwnerDeviceLocalId,
|
int Function(Asset, Asset) compare = Asset.compareByChecksum,
|
||||||
}) {
|
}) {
|
||||||
final List<Asset> toAdd = [];
|
final List<Asset> toAdd = [];
|
||||||
final List<Asset> toUpdate = [];
|
final List<Asset> toUpdate = [];
|
||||||
@ -663,7 +700,7 @@ class SyncService {
|
|||||||
}
|
}
|
||||||
} else if (remote == false && a.isRemote) {
|
} else if (remote == false && a.isRemote) {
|
||||||
if (a.isLocal) {
|
if (a.isLocal) {
|
||||||
a.isLocal = false;
|
a.localId = null;
|
||||||
toUpdate.add(a);
|
toUpdate.add(a);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -685,9 +722,9 @@ class SyncService {
|
|||||||
return const ([], []);
|
return const ([], []);
|
||||||
}
|
}
|
||||||
deleteCandidates.sort(Asset.compareById);
|
deleteCandidates.sort(Asset.compareById);
|
||||||
deleteCandidates.uniqueConsecutive((a) => a.id);
|
deleteCandidates.uniqueConsecutive(compare: Asset.compareById);
|
||||||
existing.sort(Asset.compareById);
|
existing.sort(Asset.compareById);
|
||||||
existing.uniqueConsecutive((a) => a.id);
|
existing.uniqueConsecutive(compare: Asset.compareById);
|
||||||
final (tooAdd, toUpdate, toRemove) = _diffAssets(
|
final (tooAdd, toUpdate, toRemove) = _diffAssets(
|
||||||
existing,
|
existing,
|
||||||
deleteCandidates,
|
deleteCandidates,
|
||||||
@ -698,14 +735,6 @@ class SyncService {
|
|||||||
return (toRemove.map((e) => e.id).toList(), toUpdate);
|
return (toRemove.map((e) => e.id).toList(), toUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns `true` if the albums differ on the surface
|
|
||||||
Future<bool> _hasAssetPathEntityChanged(AssetPathEntity a, Album b) async {
|
|
||||||
return a.name != b.name ||
|
|
||||||
a.lastModified == null ||
|
|
||||||
!a.lastModified!.isAtSameMomentAs(b.modifiedAt) ||
|
|
||||||
await a.assetCountAsync != b.assetCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns `true` if the albums differ on the surface
|
/// returns `true` if the albums differ on the surface
|
||||||
bool _hasAlbumResponseDtoChanged(AlbumResponseDto dto, Album a) {
|
bool _hasAlbumResponseDtoChanged(AlbumResponseDto dto, Album a) {
|
||||||
return dto.assetCount != a.assetCount ||
|
return dto.assetCount != a.assetCount ||
|
||||||
|
@ -6,12 +6,39 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
|
||||||
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
|
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
|
|
||||||
class TabControllerPage extends ConsumerWidget {
|
class TabControllerPage extends HookConsumerWidget {
|
||||||
const TabControllerPage({Key? key}) : super(key: key);
|
const TabControllerPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final refreshing = ref.watch(assetProvider);
|
||||||
|
|
||||||
|
Widget buildIcon(Widget icon) {
|
||||||
|
if (!refreshing) return icon;
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
icon,
|
||||||
|
Positioned(
|
||||||
|
right: -14,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 12,
|
||||||
|
width: 12,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
navigationRail(TabsRouter tabsRouter) {
|
navigationRail(TabsRouter tabsRouter) {
|
||||||
return NavigationRail(
|
return NavigationRail(
|
||||||
labelType: NavigationRailLabelType.all,
|
labelType: NavigationRailLabelType.all,
|
||||||
@ -83,9 +110,12 @@ class TabControllerPage extends ConsumerWidget {
|
|||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.photo_library_outlined,
|
Icons.photo_library_outlined,
|
||||||
),
|
),
|
||||||
selectedIcon: Icon(
|
selectedIcon: buildIcon(
|
||||||
Icons.photo_library,
|
Icon(
|
||||||
color: Theme.of(context).primaryColor,
|
size: 24,
|
||||||
|
Icons.photo_library,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
@ -113,9 +143,11 @@ class TabControllerPage extends ConsumerWidget {
|
|||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.photo_album_outlined,
|
Icons.photo_album_outlined,
|
||||||
),
|
),
|
||||||
selectedIcon: Icon(
|
selectedIcon: buildIcon(
|
||||||
Icons.photo_album_rounded,
|
Icon(
|
||||||
color: Theme.of(context).primaryColor,
|
Icons.photo_album_rounded,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
extension DurationExtension on String {
|
extension DurationExtension on String {
|
||||||
@ -22,15 +24,20 @@ extension DurationExtension on String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension ListExtension<E> on List<E> {
|
extension ListExtension<E> on List<E> {
|
||||||
List<E> uniqueConsecutive<T>([T Function(E element)? key]) {
|
List<E> uniqueConsecutive({
|
||||||
key ??= (E e) => e as T;
|
int Function(E a, E b)? compare,
|
||||||
|
void Function(E a, E b)? onDuplicate,
|
||||||
|
}) {
|
||||||
|
compare ??= (E a, E b) => a == b ? 0 : 1;
|
||||||
int i = 1, j = 1;
|
int i = 1, j = 1;
|
||||||
for (; i < length; i++) {
|
for (; i < length; i++) {
|
||||||
if (key(this[i]) != key(this[i - 1])) {
|
if (compare(this[i - 1], this[i]) != 0) {
|
||||||
if (i != j) {
|
if (i != j) {
|
||||||
this[j] = this[i];
|
this[j] = this[i];
|
||||||
}
|
}
|
||||||
j++;
|
j++;
|
||||||
|
} else if (onDuplicate != null) {
|
||||||
|
onDuplicate(this[i - 1], this[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
length = length == 0 ? 0 : j;
|
length = length == 0 ? 0 : j;
|
||||||
@ -45,3 +52,11 @@ extension ListExtension<E> on List<E> {
|
|||||||
return ListSlice<E>(this, start, end);
|
return ListSlice<E>(this, start, end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension IntListExtension on Iterable<int> {
|
||||||
|
Int64List toInt64List() {
|
||||||
|
final list = Int64List(length);
|
||||||
|
list.setAll(0, this);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,11 +8,13 @@ Future<void> migrateDatabaseIfNeeded(Isar db) async {
|
|||||||
final int version = Store.get(StoreKey.version, 1);
|
final int version = Store.get(StoreKey.version, 1);
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case 1:
|
case 1:
|
||||||
await _migrateV1ToV2(db);
|
await _migrateTo(db, 2);
|
||||||
|
case 2:
|
||||||
|
await _migrateTo(db, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateV1ToV2(Isar db) async {
|
Future<void> _migrateTo(Isar db, int version) async {
|
||||||
await clearAssetsAndAlbums(db);
|
await clearAssetsAndAlbums(db);
|
||||||
await Store.put(StoreKey.version, 2);
|
await Store.put(StoreKey.version, version);
|
||||||
}
|
}
|
||||||
|
@ -242,13 +242,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+4"
|
version: "0.3.3+4"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.3"
|
||||||
csslib:
|
csslib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -333,10 +333,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
|
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.2"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -45,6 +45,7 @@ dependencies:
|
|||||||
isar_flutter_libs: *isar_version # contains Isar Core
|
isar_flutter_libs: *isar_version # contains Isar Core
|
||||||
permission_handler: ^10.2.0
|
permission_handler: ^10.2.0
|
||||||
device_info_plus: ^8.1.0
|
device_info_plus: ^8.1.0
|
||||||
|
crypto: ^3.0.3 # TODO remove once native crypto is used on iOS
|
||||||
|
|
||||||
openapi:
|
openapi:
|
||||||
path: openapi
|
path: openapi
|
||||||
|
@ -13,8 +13,8 @@ void main() {
|
|||||||
|
|
||||||
testAssets.add(
|
testAssets.add(
|
||||||
Asset(
|
Asset(
|
||||||
|
checksum: "",
|
||||||
localId: '$i',
|
localId: '$i',
|
||||||
deviceId: 1,
|
|
||||||
ownerId: 1,
|
ownerId: 1,
|
||||||
fileCreatedAt: date,
|
fileCreatedAt: date,
|
||||||
fileModifiedAt: date,
|
fileModifiedAt: date,
|
||||||
@ -23,7 +23,6 @@ void main() {
|
|||||||
type: AssetType.image,
|
type: AssetType.image,
|
||||||
fileName: '',
|
fileName: '',
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
isLocal: false,
|
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -43,7 +43,12 @@ void main() {
|
|||||||
|
|
||||||
test('withKey', () {
|
test('withKey', () {
|
||||||
final a = ["a", "bb", "cc", "ddd"];
|
final a = ["a", "bb", "cc", "ddd"];
|
||||||
expect(a.uniqueConsecutive((s) => s.length), ["a", "bb", "ddd"]);
|
expect(
|
||||||
|
a.uniqueConsecutive(
|
||||||
|
compare: (s1, s2) => s1.length.compareTo(s2.length),
|
||||||
|
),
|
||||||
|
["a", "bb", "ddd"],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -6,32 +6,33 @@ import 'package:immich_mobile/shared/models/exif_info.dart';
|
|||||||
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
import 'package:immich_mobile/shared/models/logger_message.model.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/hash.service.dart';
|
||||||
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
||||||
import 'package:immich_mobile/shared/services/sync.service.dart';
|
import 'package:immich_mobile/shared/services/sync.service.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Asset makeAsset({
|
Asset makeAsset({
|
||||||
required String localId,
|
required String checksum,
|
||||||
|
String? localId,
|
||||||
String? remoteId,
|
String? remoteId,
|
||||||
int deviceId = 1,
|
int deviceId = 1,
|
||||||
int ownerId = 590700560494856554, // hash of "1"
|
int ownerId = 590700560494856554, // hash of "1"
|
||||||
bool isLocal = false,
|
|
||||||
}) {
|
}) {
|
||||||
final DateTime date = DateTime(2000);
|
final DateTime date = DateTime(2000);
|
||||||
return Asset(
|
return Asset(
|
||||||
|
checksum: checksum,
|
||||||
localId: localId,
|
localId: localId,
|
||||||
remoteId: remoteId,
|
remoteId: remoteId,
|
||||||
deviceId: deviceId,
|
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
fileCreatedAt: date,
|
fileCreatedAt: date,
|
||||||
fileModifiedAt: date,
|
fileModifiedAt: date,
|
||||||
updatedAt: date,
|
updatedAt: date,
|
||||||
durationInSeconds: 0,
|
durationInSeconds: 0,
|
||||||
type: AssetType.image,
|
type: AssetType.image,
|
||||||
fileName: localId,
|
fileName: localId ?? remoteId ?? "",
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
isLocal: isLocal,
|
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -53,6 +54,7 @@ void main() {
|
|||||||
|
|
||||||
group('Test SyncService grouped', () {
|
group('Test SyncService grouped', () {
|
||||||
late final Isar db;
|
late final Isar db;
|
||||||
|
final MockHashService hs = MockHashService();
|
||||||
final owner = User(
|
final owner = User(
|
||||||
id: "1",
|
id: "1",
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
@ -71,11 +73,11 @@ void main() {
|
|||||||
await Store.put(StoreKey.currentUser, owner);
|
await Store.put(StoreKey.currentUser, owner);
|
||||||
});
|
});
|
||||||
final List<Asset> initialAssets = [
|
final List<Asset> initialAssets = [
|
||||||
makeAsset(localId: "1", remoteId: "0-1", deviceId: 0),
|
makeAsset(checksum: "a", remoteId: "0-1", deviceId: 0),
|
||||||
makeAsset(localId: "1", remoteId: "2-1", deviceId: 2),
|
makeAsset(checksum: "b", remoteId: "2-1", deviceId: 2),
|
||||||
makeAsset(localId: "1", remoteId: "1-1", isLocal: true),
|
makeAsset(checksum: "c", localId: "1", remoteId: "1-1"),
|
||||||
makeAsset(localId: "2", isLocal: true),
|
makeAsset(checksum: "d", localId: "2"),
|
||||||
makeAsset(localId: "3", isLocal: true),
|
makeAsset(checksum: "e", localId: "3"),
|
||||||
];
|
];
|
||||||
setUp(() {
|
setUp(() {
|
||||||
db.writeTxnSync(() {
|
db.writeTxnSync(() {
|
||||||
@ -84,11 +86,11 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
test('test inserting existing assets', () async {
|
test('test inserting existing assets', () async {
|
||||||
SyncService s = SyncService(db);
|
SyncService s = SyncService(db, hs);
|
||||||
final List<Asset> remoteAssets = [
|
final List<Asset> remoteAssets = [
|
||||||
makeAsset(localId: "1", remoteId: "0-1", deviceId: 0),
|
makeAsset(checksum: "a", remoteId: "0-1", deviceId: 0),
|
||||||
makeAsset(localId: "1", remoteId: "2-1", deviceId: 2),
|
makeAsset(checksum: "b", remoteId: "2-1", deviceId: 2),
|
||||||
makeAsset(localId: "1", remoteId: "1-1"),
|
makeAsset(checksum: "c", remoteId: "1-1"),
|
||||||
];
|
];
|
||||||
expect(db.assets.countSync(), 5);
|
expect(db.assets.countSync(), 5);
|
||||||
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||||
@ -97,14 +99,14 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('test inserting new assets', () async {
|
test('test inserting new assets', () async {
|
||||||
SyncService s = SyncService(db);
|
SyncService s = SyncService(db, hs);
|
||||||
final List<Asset> remoteAssets = [
|
final List<Asset> remoteAssets = [
|
||||||
makeAsset(localId: "1", remoteId: "0-1", deviceId: 0),
|
makeAsset(checksum: "a", remoteId: "0-1", deviceId: 0),
|
||||||
makeAsset(localId: "1", remoteId: "2-1", deviceId: 2),
|
makeAsset(checksum: "b", remoteId: "2-1", deviceId: 2),
|
||||||
makeAsset(localId: "1", remoteId: "1-1"),
|
makeAsset(checksum: "c", remoteId: "1-1"),
|
||||||
makeAsset(localId: "2", remoteId: "1-2"),
|
makeAsset(checksum: "d", remoteId: "1-2"),
|
||||||
makeAsset(localId: "4", remoteId: "1-4"),
|
makeAsset(checksum: "f", remoteId: "1-4"),
|
||||||
makeAsset(localId: "1", remoteId: "3-1", deviceId: 3),
|
makeAsset(checksum: "g", remoteId: "3-1", deviceId: 3),
|
||||||
];
|
];
|
||||||
expect(db.assets.countSync(), 5);
|
expect(db.assets.countSync(), 5);
|
||||||
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||||
@ -113,14 +115,14 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('test syncing duplicate assets', () async {
|
test('test syncing duplicate assets', () async {
|
||||||
SyncService s = SyncService(db);
|
SyncService s = SyncService(db, hs);
|
||||||
final List<Asset> remoteAssets = [
|
final List<Asset> remoteAssets = [
|
||||||
makeAsset(localId: "1", remoteId: "0-1", deviceId: 0),
|
makeAsset(checksum: "a", remoteId: "0-1", deviceId: 0),
|
||||||
makeAsset(localId: "1", remoteId: "1-1"),
|
makeAsset(checksum: "b", remoteId: "1-1"),
|
||||||
makeAsset(localId: "1", remoteId: "2-1", deviceId: 2),
|
makeAsset(checksum: "c", remoteId: "2-1", deviceId: 2),
|
||||||
makeAsset(localId: "1", remoteId: "2-1b", deviceId: 2),
|
makeAsset(checksum: "h", remoteId: "2-1b", deviceId: 2),
|
||||||
makeAsset(localId: "1", remoteId: "2-1c", deviceId: 2),
|
makeAsset(checksum: "i", remoteId: "2-1c", deviceId: 2),
|
||||||
makeAsset(localId: "1", remoteId: "2-1d", deviceId: 2),
|
makeAsset(checksum: "j", remoteId: "2-1d", deviceId: 2),
|
||||||
];
|
];
|
||||||
expect(db.assets.countSync(), 5);
|
expect(db.assets.countSync(), 5);
|
||||||
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
final bool c1 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||||
@ -133,11 +135,13 @@ void main() {
|
|||||||
final bool c3 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
final bool c3 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||||
expect(c3, true);
|
expect(c3, true);
|
||||||
expect(db.assets.countSync(), 7);
|
expect(db.assets.countSync(), 7);
|
||||||
remoteAssets.add(makeAsset(localId: "1", remoteId: "2-1e", deviceId: 2));
|
remoteAssets.add(makeAsset(checksum: "k", remoteId: "2-1e", deviceId: 2));
|
||||||
remoteAssets.add(makeAsset(localId: "2", remoteId: "2-2", deviceId: 2));
|
remoteAssets.add(makeAsset(checksum: "l", remoteId: "2-2", deviceId: 2));
|
||||||
final bool c4 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
final bool c4 = await s.syncRemoteAssetsToDb(owner, () => remoteAssets);
|
||||||
expect(c4, true);
|
expect(c4, true);
|
||||||
expect(db.assets.countSync(), 9);
|
expect(db.assets.countSync(), 9);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MockHashService extends Mock implements HashService {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user