mirror of
https://github.com/immich-app/immich.git
synced 2025-12-06 04:41:40 -08:00
Compare commits
4 Commits
v1.144.0
...
fix/async-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f632e4f666 | ||
|
|
0e8492ceba | ||
|
|
13abe14142 | ||
|
|
ae595f2947 |
@@ -3,7 +3,6 @@ package app.alextran.immich
|
||||
import android.app.Application
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import app.alextran.immich.background.BackgroundWorkerApiImpl
|
||||
|
||||
class ImmichApp : Application() {
|
||||
override fun onCreate() {
|
||||
@@ -17,6 +16,5 @@ class ImmichApp : Application() {
|
||||
// As a workaround, we also run a backup check when initializing the application
|
||||
|
||||
ContentObserverWorker.startBackupWorker(context = this, delayMilliseconds = 0)
|
||||
BackgroundWorkerApiImpl.enqueueBackgroundWorker(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package app.alextran.immich
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
fun <T> dispatch(
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
callback: (Result<T>) -> Unit,
|
||||
block: () -> T
|
||||
) {
|
||||
CoroutineScope(dispatcher).launch {
|
||||
callback(runCatching { block() })
|
||||
}
|
||||
}
|
||||
@@ -2,49 +2,50 @@ package app.alextran.immich.background
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import app.alextran.immich.dispatch
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
private const val TAG = "BackgroundEngineLock"
|
||||
|
||||
class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, FlutterPlugin {
|
||||
private val ctx: Context = context.applicationContext
|
||||
private val ctx: Context = context.applicationContext
|
||||
|
||||
companion object {
|
||||
companion object {
|
||||
|
||||
private var engineCount = AtomicInteger(0)
|
||||
private var engineCount = AtomicInteger(0)
|
||||
|
||||
private fun checkAndEnforceBackgroundLock(ctx: Context) {
|
||||
// work manager task is running while the main app is opened, cancel the worker
|
||||
if (BackgroundWorkerPreferences(ctx).isLocked() &&
|
||||
engineCount.get() > 1 &&
|
||||
BackgroundWorkerApiImpl.isBackgroundWorkerRunning()
|
||||
) {
|
||||
Log.i(TAG, "Background worker is locked, cancelling the background worker")
|
||||
BackgroundWorkerApiImpl.cancelBackgroundWorker(ctx)
|
||||
}
|
||||
}
|
||||
private fun checkAndEnforceBackgroundLock(ctx: Context) {
|
||||
// work manager task is running while the main app is opened, cancel the worker
|
||||
if (BackgroundWorkerPreferences(ctx).isLocked() &&
|
||||
engineCount.get() > 1 &&
|
||||
BackgroundWorkerApiImpl.isBackgroundWorkerRunning()
|
||||
) {
|
||||
Log.i(TAG, "Background worker is locked, cancelling the background worker")
|
||||
BackgroundWorkerApiImpl.cancelBackgroundWorker(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun lock() {
|
||||
BackgroundWorkerPreferences(ctx).setLocked(true)
|
||||
checkAndEnforceBackgroundLock(ctx)
|
||||
Log.i(TAG, "Background worker is locked")
|
||||
}
|
||||
override fun lock(callback: (Result<Unit>) -> Unit) = dispatch(callback = callback) {
|
||||
BackgroundWorkerPreferences(ctx).setLocked(true)
|
||||
checkAndEnforceBackgroundLock(ctx)
|
||||
Log.i(TAG, "Background worker is locked")
|
||||
}
|
||||
|
||||
override fun unlock() {
|
||||
BackgroundWorkerPreferences(ctx).setLocked(false)
|
||||
Log.i(TAG, "Background worker is unlocked")
|
||||
}
|
||||
override fun unlock(callback: (Result<Unit>) -> Unit) = dispatch(callback = callback) {
|
||||
BackgroundWorkerPreferences(ctx).setLocked(false)
|
||||
Log.i(TAG, "Background worker is unlocked")
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
checkAndEnforceBackgroundLock(binding.applicationContext)
|
||||
engineCount.incrementAndGet()
|
||||
Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount")
|
||||
}
|
||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
checkAndEnforceBackgroundLock(binding.applicationContext)
|
||||
engineCount.incrementAndGet()
|
||||
Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount")
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
engineCount.decrementAndGet()
|
||||
Log.i(TAG, "Flutter engine detached. Attached Engines count: $engineCount")
|
||||
}
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
engineCount.decrementAndGet()
|
||||
Log.i(TAG, "Flutter engine detached. Attached Engines count: $engineCount")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,11 +133,12 @@ private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface BackgroundWorkerFgHostApi {
|
||||
fun enable()
|
||||
fun configure(settings: BackgroundWorkerSettings)
|
||||
fun disable()
|
||||
fun enable(callback: (Result<Unit>) -> Unit)
|
||||
fun configure(settings: BackgroundWorkerSettings, callback: (Result<Unit>) -> Unit)
|
||||
fun disable(callback: (Result<Unit>) -> Unit)
|
||||
|
||||
companion object {
|
||||
/** The codec used by BackgroundWorkerFgHostApi. */
|
||||
@@ -152,13 +153,14 @@ interface BackgroundWorkerFgHostApi {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
api.enable()
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerPigeonUtils.wrapError(exception)
|
||||
api.enable{ result: Result<Unit> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(BackgroundWorkerPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
reply.reply(BackgroundWorkerPigeonUtils.wrapResult(null))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@@ -170,13 +172,14 @@ interface BackgroundWorkerFgHostApi {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val settingsArg = args[0] as BackgroundWorkerSettings
|
||||
val wrapped: List<Any?> = try {
|
||||
api.configure(settingsArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerPigeonUtils.wrapError(exception)
|
||||
api.configure(settingsArg) { result: Result<Unit> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(BackgroundWorkerPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
reply.reply(BackgroundWorkerPigeonUtils.wrapResult(null))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@@ -186,13 +189,14 @@ interface BackgroundWorkerFgHostApi {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
api.disable()
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerPigeonUtils.wrapError(exception)
|
||||
api.disable{ result: Result<Unit> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(BackgroundWorkerPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
reply.reply(BackgroundWorkerPigeonUtils.wrapResult(null))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
|
||||
@@ -215,7 +215,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||
if (foregroundFuture != null && !foregroundFuture.isCancelled && !foregroundFuture.isDone) {
|
||||
try {
|
||||
foregroundFuture.get(500, TimeUnit.MILLISECONDS)
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
// ignored, there is nothing to be done
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import app.alextran.immich.dispatch
|
||||
import io.flutter.embedding.engine.FlutterEngineCache
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -16,16 +17,18 @@ private const val TAG = "BackgroundWorkerApiImpl"
|
||||
class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||
private val ctx: Context = context.applicationContext
|
||||
|
||||
override fun enable() {
|
||||
enqueueMediaObserver(ctx)
|
||||
}
|
||||
override fun enable(callback: (Result<Unit>) -> Unit) =
|
||||
dispatch(callback = callback) { enqueueMediaObserver(ctx) }
|
||||
|
||||
override fun configure(settings: BackgroundWorkerSettings) {
|
||||
override fun configure(
|
||||
settings: BackgroundWorkerSettings,
|
||||
callback: (Result<Unit>) -> Unit
|
||||
) = dispatch(callback = callback) {
|
||||
BackgroundWorkerPreferences(ctx).updateSettings(settings)
|
||||
enqueueMediaObserver(ctx)
|
||||
}
|
||||
|
||||
override fun disable() {
|
||||
override fun disable(callback: (Result<Unit>) -> Unit) = dispatch(callback = callback) {
|
||||
WorkManager.getInstance(ctx).apply {
|
||||
cancelUniqueWork(OBSERVER_WORKER_NAME)
|
||||
cancelUniqueWork(BACKGROUND_WORKER_NAME)
|
||||
@@ -38,7 +41,6 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||
private const val OBSERVER_WORKER_NAME = "immich/MediaObserverV1"
|
||||
const val ENGINE_CACHE_KEY = "immich::background_worker::engine"
|
||||
|
||||
|
||||
fun enqueueMediaObserver(ctx: Context) {
|
||||
val settings = BackgroundWorkerPreferences(ctx).getSettings()
|
||||
val constraints = Constraints.Builder().apply {
|
||||
|
||||
@@ -44,10 +44,11 @@ private open class BackgroundWorkerLockPigeonCodec : StandardMessageCodec() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface BackgroundWorkerLockApi {
|
||||
fun lock()
|
||||
fun unlock()
|
||||
fun lock(callback: (Result<Unit>) -> Unit)
|
||||
fun unlock(callback: (Result<Unit>) -> Unit)
|
||||
|
||||
companion object {
|
||||
/** The codec used by BackgroundWorkerLockApi. */
|
||||
@@ -62,13 +63,14 @@ interface BackgroundWorkerLockApi {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.lock$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
api.lock()
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerLockPigeonUtils.wrapError(exception)
|
||||
api.lock{ result: Result<Unit> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(BackgroundWorkerLockPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
reply.reply(BackgroundWorkerLockPigeonUtils.wrapResult(null))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@@ -78,13 +80,14 @@ interface BackgroundWorkerLockApi {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.unlock$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
api.unlock()
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
BackgroundWorkerLockPigeonUtils.wrapError(exception)
|
||||
api.unlock{ result: Result<Unit> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(BackgroundWorkerLockPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
reply.reply(BackgroundWorkerLockPigeonUtils.wrapResult(null))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
|
||||
@@ -82,9 +82,10 @@ private open class ConnectivityPigeonCodec : StandardMessageCodec() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface ConnectivityApi {
|
||||
fun getCapabilities(): List<NetworkCapability>
|
||||
fun getCapabilities(callback: (Result<List<NetworkCapability>>) -> Unit)
|
||||
|
||||
companion object {
|
||||
/** The codec used by ConnectivityApi. */
|
||||
@@ -100,12 +101,15 @@ interface ConnectivityApi {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities$separatedMessageChannelSuffix", codec, taskQueue)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.getCapabilities())
|
||||
} catch (exception: Throwable) {
|
||||
ConnectivityPigeonUtils.wrapError(exception)
|
||||
api.getCapabilities{ result: Result<List<NetworkCapability>> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(ConnectivityPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(ConnectivityPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.wifi.WifiManager
|
||||
import app.alextran.immich.dispatch
|
||||
|
||||
class ConnectivityApiImpl(context: Context) : ConnectivityApi {
|
||||
private val connectivityManager =
|
||||
@@ -11,7 +12,13 @@ class ConnectivityApiImpl(context: Context) : ConnectivityApi {
|
||||
private val wifiManager =
|
||||
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
|
||||
override fun getCapabilities(): List<NetworkCapability> {
|
||||
|
||||
override fun getCapabilities(callback: (Result<List<NetworkCapability>>) -> Unit) =
|
||||
dispatch(callback = callback) {
|
||||
getCapabilities()
|
||||
}
|
||||
|
||||
private fun getCapabilities(): List<NetworkCapability> {
|
||||
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||
?: return emptyList()
|
||||
|
||||
|
||||
@@ -296,13 +296,13 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface NativeSyncApi {
|
||||
fun shouldFullSync(): Boolean
|
||||
fun getMediaChanges(): SyncDelta
|
||||
fun getMediaChanges(callback: (Result<SyncDelta>) -> Unit)
|
||||
fun checkpointSync()
|
||||
fun clearSyncCheckpoint()
|
||||
fun getAssetIdsForAlbum(albumId: String): List<String>
|
||||
fun getAlbums(): List<PlatformAlbum>
|
||||
fun getAssetsCountSince(albumId: String, timestamp: Long): Long
|
||||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
|
||||
fun getAssetIdsForAlbum(albumId: String, callback: (Result<List<String>>) -> Unit)
|
||||
fun getAlbums(callback: (Result<List<PlatformAlbum>>) -> Unit)
|
||||
fun getAssetsCountSince(albumId: String, timestamp: Long, callback: (Result<Long>) -> Unit)
|
||||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result<List<PlatformAsset>>) -> Unit)
|
||||
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
||||
fun cancelHashing()
|
||||
|
||||
@@ -335,12 +335,15 @@ interface NativeSyncApi {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.getMediaChanges())
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
api.getMediaChanges{ result: Result<SyncDelta> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@@ -384,12 +387,15 @@ interface NativeSyncApi {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val albumIdArg = args[0] as String
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.getAssetIdsForAlbum(albumIdArg))
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
api.getAssetIdsForAlbum(albumIdArg) { result: Result<List<String>> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@@ -399,12 +405,15 @@ interface NativeSyncApi {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$separatedMessageChannelSuffix", codec, taskQueue)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.getAlbums())
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
api.getAlbums{ result: Result<List<PlatformAlbum>> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@@ -417,12 +426,15 @@ interface NativeSyncApi {
|
||||
val args = message as List<Any?>
|
||||
val albumIdArg = args[0] as String
|
||||
val timestampArg = args[1] as Long
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.getAssetsCountSince(albumIdArg, timestampArg))
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
api.getAssetsCountSince(albumIdArg, timestampArg) { result: Result<Long> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
@@ -435,12 +447,15 @@ interface NativeSyncApi {
|
||||
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.getAssetsForAlbum(albumIdArg, updatedTimeCondArg))
|
||||
} catch (exception: Throwable) {
|
||||
MessagesPigeonUtils.wrapError(exception)
|
||||
api.getAssetsForAlbum(albumIdArg, updatedTimeCondArg) { result: Result<List<PlatformAsset>> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
|
||||
@@ -18,7 +18,7 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na
|
||||
// No-op for Android 10 and below
|
||||
}
|
||||
|
||||
override fun getMediaChanges(): SyncDelta {
|
||||
throw IllegalStateException("Method not supported on this Android version.")
|
||||
}
|
||||
override fun getMediaChanges(callback: (Result<SyncDelta>) -> Unit) =
|
||||
callback(Result.failure(IllegalStateException("Method not supported on this Android version.")))
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresExtension
|
||||
import app.alextran.immich.dispatch
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
@@ -47,7 +48,12 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMediaChanges(): SyncDelta {
|
||||
override fun getMediaChanges(callback: (Result<SyncDelta>) -> Unit) =
|
||||
dispatch(callback = callback) {
|
||||
getMediaChanges()
|
||||
}
|
||||
|
||||
private fun getMediaChanges(): SyncDelta {
|
||||
val genMap = getSavedGenerationMap()
|
||||
val currentVolumes = MediaStore.getExternalVolumeNames(ctx)
|
||||
val changed = mutableListOf<PlatformAsset>()
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.database.Cursor
|
||||
import android.provider.MediaStore
|
||||
import android.util.Base64
|
||||
import androidx.core.database.getStringOrNull
|
||||
import app.alextran.immich.dispatch
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -145,7 +146,10 @@ open class NativeSyncApiImplBase(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getAlbums(): List<PlatformAlbum> {
|
||||
fun getAlbums(callback: (Result<List<PlatformAlbum>>) -> Unit) =
|
||||
dispatch(callback = callback) { getAlbums() }
|
||||
|
||||
private fun getAlbums(): List<PlatformAlbum> {
|
||||
val albums = mutableListOf<PlatformAlbum>()
|
||||
val albumsCount = mutableMapOf<String, Int>()
|
||||
|
||||
@@ -192,7 +196,10 @@ open class NativeSyncApiImplBase(context: Context) {
|
||||
.sortedBy { it.id }
|
||||
}
|
||||
|
||||
fun getAssetIdsForAlbum(albumId: String): List<String> {
|
||||
fun getAssetIdsForAlbum(albumId: String, callback: (Result<List<String>>) -> Unit) =
|
||||
dispatch(callback = callback) { getAssetIdsForAlbum(albumId); }
|
||||
|
||||
private fun getAssetIdsForAlbum(albumId: String): List<String> {
|
||||
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
||||
|
||||
return getCursor(
|
||||
@@ -208,15 +215,23 @@ open class NativeSyncApiImplBase(context: Context) {
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
fun getAssetsCountSince(albumId: String, timestamp: Long): Long =
|
||||
fun getAssetsCountSince(albumId: String, timestamp: Long, callback: (Result<Long>) -> Unit) =
|
||||
dispatch(callback = callback) { getAssetsCountSince(albumId, timestamp) }
|
||||
|
||||
private fun getAssetsCountSince(albumId: String, timestamp: Long): Long =
|
||||
getCursor(
|
||||
MediaStore.VOLUME_EXTERNAL,
|
||||
"$BUCKET_SELECTION AND ${MediaStore.Files.FileColumns.DATE_ADDED} > ? AND $MEDIA_SELECTION",
|
||||
arrayOf(albumId, timestamp.toString(), *MEDIA_SELECTION_ARGS),
|
||||
)?.use { cursor -> cursor.count.toLong() } ?: 0L
|
||||
|
||||
fun getAssetsForAlbum(
|
||||
albumId: String,
|
||||
updatedTimeCond: Long?,
|
||||
callback: (Result<List<PlatformAsset>>) -> Unit
|
||||
) = dispatch(callback = callback) { getAssetsForAlbum(albumId, updatedTimeCond) }
|
||||
|
||||
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset> {
|
||||
private fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset> {
|
||||
var selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION"
|
||||
val selectionArgs = mutableListOf(albumId, *MEDIA_SELECTION_ARGS)
|
||||
|
||||
@@ -254,7 +269,7 @@ open class NativeSyncApiImplBase(context: Context) {
|
||||
}.awaitAll()
|
||||
|
||||
callback(Result.success(results))
|
||||
} catch (e: CancellationException) {
|
||||
} catch (_: CancellationException) {
|
||||
callback(
|
||||
Result.failure(
|
||||
FlutterError(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -133,11 +133,14 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = Sync;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B2D27ABE2E84A0FF004DD55B /* Core */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = Core;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F0B57D3D2DF764BD00DC5BCC /* WidgetExtension */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
@@ -247,6 +250,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B2D27ABE2E84A0FF004DD55B /* Core */,
|
||||
B25D37792E72CA15008B6CA7 /* Connectivity */,
|
||||
B21E34A62E5AF9760031FDB9 /* Background */,
|
||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
||||
@@ -332,6 +336,7 @@
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
||||
B2D27ABE2E84A0FF004DD55B /* Core */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
@@ -521,10 +526,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
@@ -553,10 +562,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
|
||||
@@ -179,11 +179,12 @@ class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Senda
|
||||
static let shared = BackgroundWorkerPigeonCodec(readerWriter: BackgroundWorkerPigeonCodecReaderWriter())
|
||||
}
|
||||
|
||||
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol BackgroundWorkerFgHostApi {
|
||||
func enable() throws
|
||||
func configure(settings: BackgroundWorkerSettings) throws
|
||||
func disable() throws
|
||||
func enable(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func configure(settings: BackgroundWorkerSettings, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func disable(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
@@ -195,11 +196,13 @@ class BackgroundWorkerFgHostApiSetup {
|
||||
let enableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
enableChannel.setMessageHandler { _, reply in
|
||||
do {
|
||||
try api.enable()
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.enable { result in
|
||||
switch result {
|
||||
case .success:
|
||||
reply(wrapResult(nil))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -210,11 +213,13 @@ class BackgroundWorkerFgHostApiSetup {
|
||||
configureChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let settingsArg = args[0] as! BackgroundWorkerSettings
|
||||
do {
|
||||
try api.configure(settings: settingsArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.configure(settings: settingsArg) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
reply(wrapResult(nil))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -223,11 +228,13 @@ class BackgroundWorkerFgHostApiSetup {
|
||||
let disableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
disableChannel.setMessageHandler { _, reply in
|
||||
do {
|
||||
try api.disable()
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.disable { result in
|
||||
switch result {
|
||||
case .success:
|
||||
reply(wrapResult(nil))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import BackgroundTasks
|
||||
|
||||
class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
|
||||
|
||||
func enable() throws {
|
||||
BackgroundWorkerApiImpl.scheduleRefreshWorker()
|
||||
BackgroundWorkerApiImpl.scheduleProcessingWorker()
|
||||
print("BackgroundWorkerApiImpl:enable Background worker scheduled")
|
||||
func enable(completion: @escaping (Result<Void, any Error>) -> Void) {
|
||||
dispatch(completion: completion) {
|
||||
BackgroundWorkerApiImpl.scheduleRefreshWorker()
|
||||
BackgroundWorkerApiImpl.scheduleProcessingWorker()
|
||||
print("BackgroundWorkerApiImpl:enable Background worker scheduled")
|
||||
}
|
||||
}
|
||||
|
||||
func configure(settings: BackgroundWorkerSettings) throws {
|
||||
func configure(settings: BackgroundWorkerSettings, completion: @escaping (Result<Void, any Error>) -> Void) {
|
||||
// Android only
|
||||
completion(Result.success(Void()))
|
||||
}
|
||||
|
||||
func disable() throws {
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID);
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID);
|
||||
print("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers")
|
||||
func disable(completion: @escaping (Result<Void, any Error>) -> Void) {
|
||||
dispatch(completion: completion) {
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID);
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID);
|
||||
print("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static let refreshTaskID = "app.alextran.immich.background.refreshUpload"
|
||||
private static let processingTaskID = "app.alextran.immich.background.processingUpload"
|
||||
private static let taskSemaphore = DispatchSemaphore(value: 1)
|
||||
|
||||
@@ -94,9 +94,10 @@ class ConnectivityPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable
|
||||
static let shared = ConnectivityPigeonCodec(readerWriter: ConnectivityPigeonCodecReaderWriter())
|
||||
}
|
||||
|
||||
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol ConnectivityApi {
|
||||
func getCapabilities() throws -> [NetworkCapability]
|
||||
func getCapabilities(completion: @escaping (Result<[NetworkCapability], Error>) -> Void)
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
@@ -115,11 +116,13 @@ class ConnectivityApiSetup {
|
||||
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||
if let api = api {
|
||||
getCapabilitiesChannel.setMessageHandler { _, reply in
|
||||
do {
|
||||
let result = try api.getCapabilities()
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.getCapabilities { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
class ConnectivityApiImpl: ConnectivityApi {
|
||||
func getCapabilities() throws -> [NetworkCapability] {
|
||||
[]
|
||||
func getCapabilities(completion: @escaping (Result<[NetworkCapability], any Error>) -> Void) {
|
||||
completion(Result.success([]))
|
||||
}
|
||||
}
|
||||
|
||||
9
mobile/ios/Runner/Core/ImmichPlugin.swift
Normal file
9
mobile/ios/Runner/Core/ImmichPlugin.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
func dispatch<T>(
|
||||
qos: DispatchQoS.QoSClass = .default,
|
||||
completion: @escaping (Result<T, Error>) -> Void,
|
||||
block: @escaping () throws -> T
|
||||
) {
|
||||
DispatchQueue.global(qos: qos).async {
|
||||
completion(Result { try block() })
|
||||
}
|
||||
}
|
||||
@@ -355,13 +355,13 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol NativeSyncApi {
|
||||
func shouldFullSync() throws -> Bool
|
||||
func getMediaChanges() throws -> SyncDelta
|
||||
func getMediaChanges(completion: @escaping (Result<SyncDelta, Error>) -> Void)
|
||||
func checkpointSync() throws
|
||||
func clearSyncCheckpoint() throws
|
||||
func getAssetIdsForAlbum(albumId: String) throws -> [String]
|
||||
func getAlbums() throws -> [PlatformAlbum]
|
||||
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64
|
||||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
|
||||
func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void)
|
||||
func getAlbums(completion: @escaping (Result<[PlatformAlbum], Error>) -> Void)
|
||||
func getAssetsCountSince(albumId: String, timestamp: Int64, completion: @escaping (Result<Int64, Error>) -> Void)
|
||||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], Error>) -> Void)
|
||||
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||
func cancelHashing() throws
|
||||
}
|
||||
@@ -395,11 +395,13 @@ class NativeSyncApiSetup {
|
||||
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||
if let api = api {
|
||||
getMediaChangesChannel.setMessageHandler { _, reply in
|
||||
do {
|
||||
let result = try api.getMediaChanges()
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.getMediaChanges { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -438,11 +440,13 @@ class NativeSyncApiSetup {
|
||||
getAssetIdsForAlbumChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let albumIdArg = args[0] as! String
|
||||
do {
|
||||
let result = try api.getAssetIdsForAlbum(albumId: albumIdArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.getAssetIdsForAlbum(albumId: albumIdArg) { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -453,11 +457,13 @@ class NativeSyncApiSetup {
|
||||
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||
if let api = api {
|
||||
getAlbumsChannel.setMessageHandler { _, reply in
|
||||
do {
|
||||
let result = try api.getAlbums()
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.getAlbums { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -471,11 +477,13 @@ class NativeSyncApiSetup {
|
||||
let args = message as! [Any?]
|
||||
let albumIdArg = args[0] as! String
|
||||
let timestampArg = args[1] as! Int64
|
||||
do {
|
||||
let result = try api.getAssetsCountSince(albumId: albumIdArg, timestamp: timestampArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.getAssetsCountSince(albumId: albumIdArg, timestamp: timestampArg) { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -489,11 +497,13 @@ class NativeSyncApiSetup {
|
||||
let args = message as! [Any?]
|
||||
let albumIdArg = args[0] as! String
|
||||
let updatedTimeCondArg: Int64? = nilOrValue(args[1])
|
||||
do {
|
||||
let result = try api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -18,6 +18,7 @@ struct AssetWrapper: Hashable, Equatable {
|
||||
}
|
||||
|
||||
class NativeSyncApiImpl: NativeSyncApi {
|
||||
|
||||
private let defaults: UserDefaults
|
||||
private let changeTokenKey = "immich:changeToken"
|
||||
private let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum]
|
||||
@@ -75,7 +76,12 @@ class NativeSyncApiImpl: NativeSyncApi {
|
||||
return false
|
||||
}
|
||||
|
||||
func getAlbums() throws -> [PlatformAlbum] {
|
||||
|
||||
func getAlbums(completion: @escaping (Result<[PlatformAlbum], any Error>) -> Void) {
|
||||
dispatch(qos: .userInitiated, completion: completion, block: getAlbums)
|
||||
}
|
||||
|
||||
private func getAlbums() throws -> [PlatformAlbum] {
|
||||
var albums: [PlatformAlbum] = []
|
||||
|
||||
albumTypes.forEach { type in
|
||||
@@ -112,7 +118,11 @@ class NativeSyncApiImpl: NativeSyncApi {
|
||||
return albums.sorted { $0.id < $1.id }
|
||||
}
|
||||
|
||||
func getMediaChanges() throws -> SyncDelta {
|
||||
func getMediaChanges(completion: @escaping (Result<SyncDelta, any Error>) -> Void) {
|
||||
dispatch(qos: .userInitiated, completion: completion, block: getMediaChanges)
|
||||
}
|
||||
|
||||
private func getMediaChanges() throws -> SyncDelta {
|
||||
guard #available(iOS 16, *) else {
|
||||
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil)
|
||||
}
|
||||
@@ -198,7 +208,11 @@ class NativeSyncApiImpl: NativeSyncApi {
|
||||
return albumAssets
|
||||
}
|
||||
|
||||
func getAssetIdsForAlbum(albumId: String) throws -> [String] {
|
||||
func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], any Error>) -> Void) {
|
||||
dispatch(qos: .userInitiated, completion: completion) { try self.getAssetIdsForAlbum(albumId: albumId) }
|
||||
}
|
||||
|
||||
private func getAssetIdsForAlbum(albumId: String) throws -> [String] {
|
||||
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
|
||||
guard let album = collections.firstObject else {
|
||||
return []
|
||||
@@ -214,7 +228,13 @@ class NativeSyncApiImpl: NativeSyncApi {
|
||||
return ids
|
||||
}
|
||||
|
||||
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 {
|
||||
func getAssetsCountSince(albumId: String, timestamp: Int64, completion: @escaping (Result<Int64, any Error>) -> Void) {
|
||||
dispatch(qos: .userInitiated, completion: completion) {
|
||||
try self.getAssetsCountSince(albumId: albumId, timestamp: timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
private func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 {
|
||||
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
|
||||
guard let album = collections.firstObject else {
|
||||
return 0
|
||||
@@ -228,7 +248,13 @@ class NativeSyncApiImpl: NativeSyncApi {
|
||||
return Int64(assets.count)
|
||||
}
|
||||
|
||||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] {
|
||||
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], any Error>) -> Void) {
|
||||
dispatch(qos: .userInitiated, completion: completion) {
|
||||
try self.getAssetsForAlbum(albumId: albumId, updatedTimeCond: updatedTimeCond)
|
||||
}
|
||||
}
|
||||
|
||||
private func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] {
|
||||
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
|
||||
guard let album = collections.firstObject else {
|
||||
return []
|
||||
|
||||
@@ -20,10 +20,13 @@ class BackgroundWorkerSettings {
|
||||
|
||||
@HostApi()
|
||||
abstract class BackgroundWorkerFgHostApi {
|
||||
@async
|
||||
void enable();
|
||||
|
||||
@async
|
||||
void configure(BackgroundWorkerSettings settings);
|
||||
|
||||
@async
|
||||
void disable();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ import 'package:pigeon/pigeon.dart';
|
||||
)
|
||||
@HostApi()
|
||||
abstract class BackgroundWorkerLockApi {
|
||||
@async
|
||||
void lock();
|
||||
|
||||
@async
|
||||
void unlock();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ enum NetworkCapability { cellular, wifi, vpn, unmetered }
|
||||
|
||||
@HostApi()
|
||||
abstract class ConnectivityApi {
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
List<NetworkCapability> getCapabilities();
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ class HashResult {
|
||||
abstract class NativeSyncApi {
|
||||
bool shouldFullSync();
|
||||
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
SyncDelta getMediaChanges();
|
||||
|
||||
@@ -90,15 +91,19 @@ abstract class NativeSyncApi {
|
||||
|
||||
void clearSyncCheckpoint();
|
||||
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
List<String> getAssetIdsForAlbum(String albumId);
|
||||
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
List<PlatformAlbum> getAlbums();
|
||||
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
int getAssetsCountSince(String albumId, int timestamp);
|
||||
|
||||
@async
|
||||
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
|
||||
List<PlatformAsset> getAssetsForAlbum(String albumId, {int? updatedTimeCond});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user