mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
resolve merge conflicts
use updated approach for calculating checksums
This commit is contained in:
parent
bd9e4871ec
commit
42f99e8039
8 changed files with 364 additions and 116 deletions
|
|
@ -89,7 +89,8 @@ data class PlatformAsset (
|
|||
val height: Long? = null,
|
||||
val durationInSeconds: Long,
|
||||
val orientation: Long,
|
||||
val isFavorite: Boolean
|
||||
val isFavorite: Boolean,
|
||||
val size: Long? = null
|
||||
)
|
||||
{
|
||||
companion object {
|
||||
|
|
@ -104,7 +105,8 @@ data class PlatformAsset (
|
|||
val durationInSeconds = pigeonVar_list[7] as Long
|
||||
val orientation = pigeonVar_list[8] as Long
|
||||
val isFavorite = pigeonVar_list[9] as Boolean
|
||||
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite)
|
||||
val size = pigeonVar_list[10] as Long?
|
||||
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, size)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
|
|
@ -119,6 +121,7 @@ data class PlatformAsset (
|
|||
durationInSeconds,
|
||||
orientation,
|
||||
isFavorite,
|
||||
size,
|
||||
)
|
||||
}
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
|
@ -243,6 +246,40 @@ data class HashResult (
|
|||
|
||||
override fun hashCode(): Int = toList().hashCode()
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
data class TrashedAssetParams (
|
||||
val id: String,
|
||||
val type: Long,
|
||||
val albumId: String? = null
|
||||
)
|
||||
{
|
||||
companion object {
|
||||
fun fromList(pigeonVar_list: List<Any?>): TrashedAssetParams {
|
||||
val id = pigeonVar_list[0] as String
|
||||
val type = pigeonVar_list[1] as Long
|
||||
val albumId = pigeonVar_list[2] as String?
|
||||
return TrashedAssetParams(id, type, albumId)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf(
|
||||
id,
|
||||
type,
|
||||
albumId,
|
||||
)
|
||||
}
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is TrashedAssetParams) {
|
||||
return false
|
||||
}
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
return MessagesPigeonUtils.deepEquals(toList(), other.toList()) }
|
||||
|
||||
override fun hashCode(): Int = toList().hashCode()
|
||||
}
|
||||
private open class MessagesPigeonCodec : StandardMessageCodec() {
|
||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||
return when (type) {
|
||||
|
|
@ -266,6 +303,11 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
|||
HashResult.fromList(it)
|
||||
}
|
||||
}
|
||||
133.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
TrashedAssetParams.fromList(it)
|
||||
}
|
||||
}
|
||||
else -> super.readValueOfType(type, buffer)
|
||||
}
|
||||
}
|
||||
|
|
@ -287,6 +329,10 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
|||
stream.write(132)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is TrashedAssetParams -> {
|
||||
stream.write(133)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
else -> super.writeValue(stream, value)
|
||||
}
|
||||
}
|
||||
|
|
@ -305,6 +351,8 @@ interface NativeSyncApi {
|
|||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
||||
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
||||
fun cancelHashing()
|
||||
fun getTrashedAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
||||
fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>, callback: (Result<List<HashResult>>) -> Unit)
|
||||
|
||||
companion object {
|
||||
/** The codec used by NativeSyncApi. */
|
||||
|
|
@ -483,6 +531,44 @@ interface NativeSyncApi {
|
|||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$separatedMessageChannelSuffix", codec, taskQueue)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val albumIdArg = args[0] as String
|
||||
val updatedTimeCondArg = args[1] as Long?
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.getTrashedAssetsForAlbum(albumIdArg, updatedTimeCondArg))
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val trashedAssetsArg = args[0] as List<TrashedAssetParams>
|
||||
api.hashTrashedAssets(trashedAssetsArg) { result: Result<List<HashResult>> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na
|
|||
throw IllegalStateException("Method not supported on this Android version.")
|
||||
}
|
||||
|
||||
override fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>): List<ByteArray?> {
|
||||
override fun hashTrashedAssets(
|
||||
trashedAssets: List<TrashedAssetParams>,
|
||||
callback: (Result<List<HashResult>>) -> Unit
|
||||
) {
|
||||
throw IllegalStateException("Method not supported on this Android version.")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,15 @@ import android.os.Bundle
|
|||
import android.provider.MediaStore
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresExtension
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.BufferedInputStream
|
||||
import java.security.DigestInputStream
|
||||
import java.security.MessageDigest
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
@RequiresExtension(extension = Build.VERSION_CODES.R, version = 1)
|
||||
|
|
@ -74,20 +79,41 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
|
|||
return trashed
|
||||
}
|
||||
|
||||
override fun hashTrashedAssets(trashedAssets: List<TrashedAssetParams>): List<ByteArray?> {
|
||||
val result = ArrayList<ByteArray?>(trashedAssets.size)
|
||||
for (item in trashedAssets) {
|
||||
val digest = try {
|
||||
val id = item.id.toLong()
|
||||
val mediaType = item.type.toInt()
|
||||
val uri = contentUriForType(id, mediaType)
|
||||
sha1OfUri(ctx, uri)
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
result.add(digest)
|
||||
override fun hashTrashedAssets(
|
||||
trashedAssets: List<TrashedAssetParams>,
|
||||
callback: (Result<List<HashResult>>) -> Unit
|
||||
) {
|
||||
if (trashedAssets.isEmpty()) {
|
||||
callback(Result.success(emptyList()))
|
||||
return
|
||||
}
|
||||
hashTask?.cancel()
|
||||
hashTask = CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val results = trashedAssets.map { assetParams ->
|
||||
async {
|
||||
hashSemaphore.withPermit {
|
||||
ensureActive()
|
||||
hashTrashedAsset(assetParams)
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
|
||||
callback(Result.success(results))
|
||||
} catch (e: CancellationException) {
|
||||
callback(
|
||||
Result.failure(
|
||||
FlutterError(
|
||||
HASHING_CANCELLED_CODE,
|
||||
"Hashing operation was cancelled",
|
||||
null
|
||||
)
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun shouldFullSync(): Boolean =
|
||||
|
|
@ -144,6 +170,13 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
|
|||
return SyncDelta(hasChanges, changed, deleted, assetAlbums)
|
||||
}
|
||||
|
||||
suspend fun hashTrashedAsset(assetParams: TrashedAssetParams): HashResult {
|
||||
val id = assetParams.id.toLong()
|
||||
val mediaType = assetParams.type.toInt()
|
||||
val assetUri = contentUriForType(id, mediaType)
|
||||
return hashAssetFromUri(assetParams.id, assetUri)
|
||||
}
|
||||
|
||||
private fun contentUriForType(id: Long, mediaType: Int): Uri {
|
||||
val vol = MediaStore.VOLUME_EXTERNAL
|
||||
val base = when (mediaType) {
|
||||
|
|
@ -154,20 +187,4 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
|
|||
}
|
||||
return ContentUris.withAppendedId(base, id)
|
||||
}
|
||||
|
||||
private fun sha1OfUri(ctx: Context, uri: Uri): ByteArray? {
|
||||
return try {
|
||||
val md = MessageDigest.getInstance("SHA-1")
|
||||
ctx.contentResolver.openInputStream(uri)?.use { input ->
|
||||
DigestInputStream(BufferedInputStream(input), md).use { dis ->
|
||||
val buf = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
while (dis.read(buf) != -1) { /* pump */
|
||||
}
|
||||
}
|
||||
} ?: return null
|
||||
md.digest()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.util.Base64
|
||||
import androidx.core.database.getStringOrNull
|
||||
|
|
@ -30,12 +31,12 @@ sealed class AssetResult {
|
|||
open class NativeSyncApiImplBase(context: Context) {
|
||||
private val ctx: Context = context.applicationContext
|
||||
|
||||
private var hashTask: Job? = null
|
||||
internal var hashTask: Job? = null
|
||||
|
||||
companion object {
|
||||
private const val MAX_CONCURRENT_HASH_OPERATIONS = 16
|
||||
private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
|
||||
private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
|
||||
internal val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
|
||||
internal const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
|
||||
|
||||
const val MEDIA_SELECTION =
|
||||
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
||||
|
|
@ -274,12 +275,15 @@ open class NativeSyncApiImplBase(context: Context) {
|
|||
}
|
||||
|
||||
private suspend fun hashAsset(assetId: String): HashResult {
|
||||
return try {
|
||||
val assetUri = ContentUris.withAppendedId(
|
||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
||||
assetId.toLong()
|
||||
)
|
||||
val assetUri = ContentUris.withAppendedId(
|
||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
||||
assetId.toLong()
|
||||
)
|
||||
return hashAssetFromUri(assetId, assetUri)
|
||||
}
|
||||
|
||||
protected suspend fun hashAssetFromUri(assetId: String, assetUri: Uri): HashResult {
|
||||
return try {
|
||||
val digest = MessageDigest.getInstance("SHA-1")
|
||||
ctx.contentResolver.openInputStream(assetUri)?.use { inputStream ->
|
||||
var bytesRead: Int
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ struct PlatformAsset: Hashable {
|
|||
var durationInSeconds: Int64
|
||||
var orientation: Int64
|
||||
var isFavorite: Bool
|
||||
var size: Int64? = nil
|
||||
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
|
|
@ -154,6 +155,7 @@ struct PlatformAsset: Hashable {
|
|||
let durationInSeconds = pigeonVar_list[7] as! Int64
|
||||
let orientation = pigeonVar_list[8] as! Int64
|
||||
let isFavorite = pigeonVar_list[9] as! Bool
|
||||
let size: Int64? = nilOrValue(pigeonVar_list[10])
|
||||
|
||||
return PlatformAsset(
|
||||
id: id,
|
||||
|
|
@ -165,7 +167,8 @@ struct PlatformAsset: Hashable {
|
|||
height: height,
|
||||
durationInSeconds: durationInSeconds,
|
||||
orientation: orientation,
|
||||
isFavorite: isFavorite
|
||||
isFavorite: isFavorite,
|
||||
size: size
|
||||
)
|
||||
}
|
||||
func toList() -> [Any?] {
|
||||
|
|
@ -180,6 +183,7 @@ struct PlatformAsset: Hashable {
|
|||
durationInSeconds,
|
||||
orientation,
|
||||
isFavorite,
|
||||
size,
|
||||
]
|
||||
}
|
||||
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
||||
|
|
@ -300,6 +304,39 @@ struct HashResult: Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generated class from Pigeon that represents data sent in messages.
|
||||
struct TrashedAssetParams: Hashable {
|
||||
var id: String
|
||||
var type: Int64
|
||||
var albumId: String? = nil
|
||||
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
static func fromList(_ pigeonVar_list: [Any?]) -> TrashedAssetParams? {
|
||||
let id = pigeonVar_list[0] as! String
|
||||
let type = pigeonVar_list[1] as! Int64
|
||||
let albumId: String? = nilOrValue(pigeonVar_list[2])
|
||||
|
||||
return TrashedAssetParams(
|
||||
id: id,
|
||||
type: type,
|
||||
albumId: albumId
|
||||
)
|
||||
}
|
||||
func toList() -> [Any?] {
|
||||
return [
|
||||
id,
|
||||
type,
|
||||
albumId,
|
||||
]
|
||||
}
|
||||
static func == (lhs: TrashedAssetParams, rhs: TrashedAssetParams) -> Bool {
|
||||
return deepEqualsMessages(lhs.toList(), rhs.toList()) }
|
||||
func hash(into hasher: inout Hasher) {
|
||||
deepHashMessages(value: toList(), hasher: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
private class MessagesPigeonCodecReader: FlutterStandardReader {
|
||||
override func readValue(ofType type: UInt8) -> Any? {
|
||||
switch type {
|
||||
|
|
@ -311,6 +348,8 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
|
|||
return SyncDelta.fromList(self.readValue() as! [Any?])
|
||||
case 132:
|
||||
return HashResult.fromList(self.readValue() as! [Any?])
|
||||
case 133:
|
||||
return TrashedAssetParams.fromList(self.readValue() as! [Any?])
|
||||
default:
|
||||
return super.readValue(ofType: type)
|
||||
}
|
||||
|
|
@ -331,6 +370,9 @@ private class MessagesPigeonCodecWriter: FlutterStandardWriter {
|
|||
} else if let value = value as? HashResult {
|
||||
super.writeByte(132)
|
||||
super.writeValue(value.toList())
|
||||
} else if let value = value as? TrashedAssetParams {
|
||||
super.writeByte(133)
|
||||
super.writeValue(value.toList())
|
||||
} else {
|
||||
super.writeValue(value)
|
||||
}
|
||||
|
|
@ -364,6 +406,8 @@ protocol NativeSyncApi {
|
|||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
||||
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||
func cancelHashing() throws
|
||||
func getTrashedAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
||||
func hashTrashedAssets(trashedAssets: [TrashedAssetParams], completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
|
|
@ -532,5 +576,42 @@ class NativeSyncApiSetup {
|
|||
} else {
|
||||
cancelHashingChannel.setMessageHandler(nil)
|
||||
}
|
||||
let getTrashedAssetsForAlbumChannel = taskQueue == nil
|
||||
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||
if let api = api {
|
||||
getTrashedAssetsForAlbumChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let albumIdArg = args[0] as! String
|
||||
let updatedTimeCondArg: Int64? = nilOrValue(args[1])
|
||||
do {
|
||||
let result = try api.getTrashedAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getTrashedAssetsForAlbumChannel.setMessageHandler(nil)
|
||||
}
|
||||
let hashTrashedAssetsChannel = taskQueue == nil
|
||||
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||
if let api = api {
|
||||
hashTrashedAssetsChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let trashedAssetsArg = args[0] as! [TrashedAssetParams]
|
||||
api.hashTrashedAssets(trashedAssets: trashedAssetsArg) { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hashTrashedAssetsChannel.setMessageHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class HashService {
|
|||
_localAssetRepository = localAssetRepository,
|
||||
_cancelChecker = cancelChecker,
|
||||
_nativeSyncApi = nativeSyncApi,
|
||||
_batchSize = batchSize ?? kBatchHashFileLimit;
|
||||
_batchSize = batchSize ?? kBatchHashFileLimit,
|
||||
_trashSyncService = trashSyncService;
|
||||
|
||||
bool get isCancelled => _cancelChecker?.call() ?? false;
|
||||
|
|
@ -144,7 +144,6 @@ class HashService {
|
|||
}
|
||||
|
||||
Future<void> _hashTrashedAssets(LocalAlbum album, Iterable<TrashedAsset> assetsToHash) async {
|
||||
int bytesProcessed = 0;
|
||||
final toHash = <TrashedAsset>[];
|
||||
|
||||
for (final asset in assetsToHash) {
|
||||
|
|
@ -160,13 +159,11 @@ class HashService {
|
|||
continue;
|
||||
}
|
||||
|
||||
bytesProcessed += asset.size!;
|
||||
toHash.add(asset);
|
||||
|
||||
if (toHash.length >= batchFileLimit || bytesProcessed >= batchSizeLimit) {
|
||||
if (toHash.length == _batchSize) {
|
||||
await _processTrashedBatch(album, toHash);
|
||||
toHash.clear();
|
||||
bytesProcessed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,27 +178,27 @@ class HashService {
|
|||
_log.fine("Hashing ${toHash.length} trashed files");
|
||||
|
||||
final params = toHash.map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id)).toList();
|
||||
final hashes = await _nativeSyncApi.hashTrashedAssets(params);
|
||||
final hashResults = await _nativeSyncApi.hashTrashedAssets(params);
|
||||
|
||||
assert(
|
||||
hashes.length == toHash.length,
|
||||
"Trashed Assets, Hashes length does not match toHash length: ${hashes.length} != ${toHash.length}",
|
||||
hashResults.length == toHash.length,
|
||||
"Trashed Assets, Hashes length does not match toHash length: ${hashResults.length} != ${toHash.length}",
|
||||
);
|
||||
final hashed = <TrashedAsset>[];
|
||||
|
||||
for (int i = 0; i < hashes.length; i++) {
|
||||
for (int i = 0; i < hashResults.length; i++) {
|
||||
if (isCancelled) {
|
||||
_log.warning("Hashing cancelled. Stopped processing batch.");
|
||||
return;
|
||||
}
|
||||
|
||||
final hash = hashes[i];
|
||||
final hashResult = hashResults[i];
|
||||
final asset = toHash[i];
|
||||
if (hash?.length == 20) {
|
||||
hashed.add(asset.copyWith(checksum: base64.encode(hash!)));
|
||||
if (hashResult.hash != null) {
|
||||
hashed.add(asset.copyWith(checksum: hashResult.hash!));
|
||||
} else {
|
||||
_log.warning(
|
||||
"Failed to hash trashed file for ${asset.id}: ${asset.name} created at ${asset.createdAt} from album: ${album.name}",
|
||||
"Failed to hash trashed asset with id: ${hashResult.assetId}, name: ${asset.name}, createdAt: ${asset.createdAt}, from album: ${album.name}. Error: ${hashResult.error ?? "unknown"}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -209,61 +206,4 @@ class HashService {
|
|||
_log.fine("Hashed ${hashed.length}/${toHash.length} trashed assets");
|
||||
await _trashSyncService.updateChecksums(hashed);
|
||||
}
|
||||
|
||||
// Future<void> _hashTrashedAssets(LocalAlbum album, Iterable<TrashedAsset> assetsToHash) async {
|
||||
// final trashedAssets = assetsToHash
|
||||
// .map((e) => TrashedAssetParams(id: e.id, type: e.type.index, albumId: album.id))
|
||||
// .toList();
|
||||
//
|
||||
// final byId = <String, LocalAsset>{for (final a in assetsToHash) a.id: a};
|
||||
//
|
||||
// final hashed = <LocalAsset>[];
|
||||
// const chunkSize = 10;
|
||||
//
|
||||
// for (int i = 0; i < trashedAssets.length; i += chunkSize) {
|
||||
// if (isCancelled) {
|
||||
// _log.warning("Hashing cancelled. Stopped processing assets.");
|
||||
// return;
|
||||
// }
|
||||
// final end = (i + chunkSize <= trashedAssets.length) ? i + chunkSize : trashedAssets.length;
|
||||
// final batch = trashedAssets.sublist(i, end);
|
||||
//
|
||||
// List<Uint8List?> hashes;
|
||||
// try {
|
||||
// hashes = await _nativeSyncApi.hashTrashedAssets(batch);
|
||||
// } catch (e, s) {
|
||||
// _log.severe("hashTrashedAssets failed for batch [$i..${end - 1}]: $e", e, s);
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// if (hashes.length != batch.length) {
|
||||
// _log.warning(
|
||||
// "hashTrashedAssets returned ${hashes.length} hashes for ${batch.length} assets (batch [$i..${end - 1}]).",
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// final limit = hashes.length < batch.length ? hashes.length : batch.length;
|
||||
// for (int j = 0; j < limit; j++) {
|
||||
// if (isCancelled) {
|
||||
// _log.warning("Hashing cancelled. Stopped processing assets.");
|
||||
// return;
|
||||
// }
|
||||
// final asset = batch[j];
|
||||
// final hash = hashes[j];
|
||||
//
|
||||
// if (hash != null && hash.length == 20) {
|
||||
// final localAsset = byId[asset.id];
|
||||
// if (localAsset != null) {
|
||||
// hashed.add(localAsset.copyWith(checksum: base64.encode(hash)));
|
||||
// }
|
||||
// } else {
|
||||
// _log.warning("Failed to hash file for ${asset.id} from album: ${album.name}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// _log.warning("updateHashes for album: ${album.name}, assets: ${hashed.map((e) => '${e.name}-${e.checksum}')}");
|
||||
//
|
||||
// await _trashSyncService.updateHashes(hashed);
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
118
mobile/lib/platform/native_sync_api.g.dart
generated
118
mobile/lib/platform/native_sync_api.g.dart
generated
|
|
@ -41,6 +41,7 @@ class PlatformAsset {
|
|||
required this.durationInSeconds,
|
||||
required this.orientation,
|
||||
required this.isFavorite,
|
||||
this.size,
|
||||
});
|
||||
|
||||
String id;
|
||||
|
|
@ -63,8 +64,22 @@ class PlatformAsset {
|
|||
|
||||
bool isFavorite;
|
||||
|
||||
int? size;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite];
|
||||
return <Object?>[
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
width,
|
||||
height,
|
||||
durationInSeconds,
|
||||
orientation,
|
||||
isFavorite,
|
||||
size,
|
||||
];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
|
|
@ -84,6 +99,7 @@ class PlatformAsset {
|
|||
durationInSeconds: result[7]! as int,
|
||||
orientation: result[8]! as int,
|
||||
isFavorite: result[9]! as bool,
|
||||
size: result[10] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -244,6 +260,45 @@ class HashResult {
|
|||
int get hashCode => Object.hashAll(_toList());
|
||||
}
|
||||
|
||||
class TrashedAssetParams {
|
||||
TrashedAssetParams({required this.id, required this.type, this.albumId});
|
||||
|
||||
String id;
|
||||
|
||||
int type;
|
||||
|
||||
String? albumId;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[id, type, albumId];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static TrashedAssetParams decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return TrashedAssetParams(id: result[0]! as String, type: result[1]! as int, albumId: result[2] as String?);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! TrashedAssetParams || other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(encode(), other.encode());
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => Object.hashAll(_toList());
|
||||
}
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
|
|
@ -263,6 +318,9 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||
} else if (value is HashResult) {
|
||||
buffer.putUint8(132);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is TrashedAssetParams) {
|
||||
buffer.putUint8(133);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
|
|
@ -279,6 +337,8 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||
return SyncDelta.decode(readValue(buffer)!);
|
||||
case 132:
|
||||
return HashResult.decode(readValue(buffer)!);
|
||||
case 133:
|
||||
return TrashedAssetParams.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
|
|
@ -562,4 +622,60 @@ class NativeSyncApi {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PlatformAsset>> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond}) async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssetsForAlbum$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[albumId, updatedTimeCond]);
|
||||
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else if (pigeonVar_replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<PlatformAsset>();
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<HashResult>> hashTrashedAssets(List<TrashedAssetParams> trashedAssets) async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashTrashedAssets$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[trashedAssets]);
|
||||
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else if (pigeonVar_replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<HashResult>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ abstract class NativeSyncApi {
|
|||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
List<PlatformAsset> getTrashedAssetsForAlbum(String albumId, {int? updatedTimeCond});
|
||||
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
List<Uint8List?> hashTrashedAssets(List<TrashedAssetParams> trashedAssets);
|
||||
List<HashResult> hashTrashedAssets(List<TrashedAssetParams> trashedAssets);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue