mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
fix: prevent background worker when main app is running (#22252)
* fix: prevent background worker only when the main app is actively running * handle ref disposals better --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
b33e8abcdd
commit
35b62cd016
15 changed files with 418 additions and 95 deletions
15
mise.toml
15
mise.toml
|
|
@ -327,6 +327,7 @@ depends = [
|
||||||
"mobile:pigeon:native-sync",
|
"mobile:pigeon:native-sync",
|
||||||
"mobile:pigeon:thumbnail",
|
"mobile:pigeon:thumbnail",
|
||||||
"mobile:pigeon:background-worker",
|
"mobile:pigeon:background-worker",
|
||||||
|
"mobile:pigeon:background-worker-lock",
|
||||||
"mobile:pigeon:connectivity",
|
"mobile:pigeon:connectivity",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -430,6 +431,20 @@ run = [
|
||||||
"dart format lib/platform/background_worker_api.g.dart",
|
"dart format lib/platform/background_worker_api.g.dart",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tasks."mobile:pigeon:background-worker-lock"]
|
||||||
|
description = "Generate background worker lock API pigeon code"
|
||||||
|
dir = "mobile"
|
||||||
|
hide = true
|
||||||
|
sources = ["pigeon/background_worker_lock_api.dart"]
|
||||||
|
outputs = [
|
||||||
|
"lib/platform/background_worker_lock_api.g.dart",
|
||||||
|
"android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt",
|
||||||
|
]
|
||||||
|
run = [
|
||||||
|
"dart run pigeon --input pigeon/background_worker_lock_api.dart",
|
||||||
|
"dart format lib/platform/background_worker_lock_api.g.dart",
|
||||||
|
]
|
||||||
|
|
||||||
[tasks."mobile:pigeon:connectivity"]
|
[tasks."mobile:pigeon:connectivity"]
|
||||||
description = "Generate connectivity API pigeon code"
|
description = "Generate connectivity API pigeon code"
|
||||||
dir = "mobile"
|
dir = "mobile"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import android.os.ext.SdkExtensions
|
||||||
import app.alextran.immich.background.BackgroundEngineLock
|
import app.alextran.immich.background.BackgroundEngineLock
|
||||||
import app.alextran.immich.background.BackgroundWorkerApiImpl
|
import app.alextran.immich.background.BackgroundWorkerApiImpl
|
||||||
import app.alextran.immich.background.BackgroundWorkerFgHostApi
|
import app.alextran.immich.background.BackgroundWorkerFgHostApi
|
||||||
|
import app.alextran.immich.background.BackgroundWorkerLockApi
|
||||||
import app.alextran.immich.connectivity.ConnectivityApi
|
import app.alextran.immich.connectivity.ConnectivityApi
|
||||||
import app.alextran.immich.connectivity.ConnectivityApiImpl
|
import app.alextran.immich.connectivity.ConnectivityApiImpl
|
||||||
import app.alextran.immich.images.ThumbnailApi
|
import app.alextran.immich.images.ThumbnailApi
|
||||||
|
|
@ -24,11 +25,9 @@ class MainActivity : FlutterFragmentActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun registerPlugins(ctx: Context, flutterEngine: FlutterEngine) {
|
fun registerPlugins(ctx: Context, flutterEngine: FlutterEngine) {
|
||||||
flutterEngine.plugins.add(BackgroundServicePlugin())
|
|
||||||
flutterEngine.plugins.add(HttpSSLOptionsPlugin())
|
|
||||||
flutterEngine.plugins.add(BackgroundEngineLock())
|
|
||||||
|
|
||||||
val messenger = flutterEngine.dartExecutor.binaryMessenger
|
val messenger = flutterEngine.dartExecutor.binaryMessenger
|
||||||
|
val backgroundEngineLockImpl = BackgroundEngineLock(ctx)
|
||||||
|
BackgroundWorkerLockApi.setUp(messenger, backgroundEngineLockImpl)
|
||||||
val nativeSyncApiImpl =
|
val nativeSyncApiImpl =
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) < 1) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) < 1) {
|
||||||
NativeSyncApiImpl26(ctx)
|
NativeSyncApiImpl26(ctx)
|
||||||
|
|
@ -39,6 +38,10 @@ class MainActivity : FlutterFragmentActivity() {
|
||||||
ThumbnailApi.setUp(messenger, ThumbnailsImpl(ctx))
|
ThumbnailApi.setUp(messenger, ThumbnailsImpl(ctx))
|
||||||
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
|
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
|
||||||
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
|
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
|
||||||
|
|
||||||
|
flutterEngine.plugins.add(BackgroundServicePlugin())
|
||||||
|
flutterEngine.plugins.add(HttpSSLOptionsPlugin())
|
||||||
|
flutterEngine.plugins.add(backgroundEngineLockImpl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,50 @@
|
||||||
package app.alextran.immich.background
|
package app.alextran.immich.background
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.work.WorkManager
|
|
||||||
import io.flutter.embedding.engine.FlutterEngineCache
|
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
private const val TAG = "BackgroundEngineLock"
|
private const val TAG = "BackgroundEngineLock"
|
||||||
|
|
||||||
class BackgroundEngineLock : FlutterPlugin {
|
class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, FlutterPlugin {
|
||||||
companion object {
|
private val ctx: Context = context.applicationContext
|
||||||
const val ENGINE_CACHE_KEY = "immich::background_worker::engine"
|
|
||||||
var engineCount = AtomicInteger(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
companion object {
|
||||||
// work manager task is running while the main app is opened, cancel the worker
|
|
||||||
if (engineCount.incrementAndGet() > 1 && FlutterEngineCache.getInstance()
|
private var engineCount = AtomicInteger(0)
|
||||||
.get(ENGINE_CACHE_KEY) != null
|
|
||||||
) {
|
private fun checkAndEnforceBackgroundLock(ctx: Context) {
|
||||||
WorkManager.getInstance(binding.applicationContext)
|
// work manager task is running while the main app is opened, cancel the worker
|
||||||
.cancelUniqueWork(BackgroundWorkerApiImpl.BACKGROUND_WORKER_NAME)
|
if (BackgroundWorkerPreferences(ctx).isLocked() &&
|
||||||
FlutterEngineCache.getInstance().remove(ENGINE_CACHE_KEY)
|
engineCount.get() > 1 &&
|
||||||
|
BackgroundWorkerApiImpl.isBackgroundWorkerRunning()
|
||||||
|
) {
|
||||||
|
Log.i(TAG, "Background worker is locked, cancelling the background worker")
|
||||||
|
BackgroundWorkerApiImpl.cancelBackgroundWorker(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
override fun lock() {
|
||||||
engineCount.decrementAndGet()
|
BackgroundWorkerPreferences(ctx).setLocked(true)
|
||||||
Log.i(TAG, "Flutter engine detached. Attached Engines count: $engineCount")
|
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 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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||||
|
|
||||||
loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
||||||
engine = FlutterEngine(ctx)
|
engine = FlutterEngine(ctx)
|
||||||
FlutterEngineCache.getInstance().remove(BackgroundEngineLock.ENGINE_CACHE_KEY);
|
FlutterEngineCache.getInstance().put(BackgroundWorkerApiImpl.ENGINE_CACHE_KEY, engine!!)
|
||||||
FlutterEngineCache.getInstance()
|
|
||||||
.put(BackgroundEngineLock.ENGINE_CACHE_KEY, engine!!)
|
|
||||||
|
|
||||||
// Register custom plugins
|
// Register custom plugins
|
||||||
MainActivity.registerPlugins(ctx, engine!!)
|
MainActivity.registerPlugins(ctx, engine!!)
|
||||||
|
|
@ -192,9 +190,9 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||||
isComplete = true
|
isComplete = true
|
||||||
engine?.destroy()
|
engine?.destroy()
|
||||||
engine = null
|
engine = null
|
||||||
FlutterEngineCache.getInstance().remove(BackgroundEngineLock.ENGINE_CACHE_KEY);
|
|
||||||
flutterApi = null
|
flutterApi = null
|
||||||
notificationManager.cancel(NOTIFICATION_ID)
|
notificationManager.cancel(NOTIFICATION_ID)
|
||||||
|
FlutterEngineCache.getInstance().remove(BackgroundWorkerApiImpl.ENGINE_CACHE_KEY)
|
||||||
waitForForegroundPromotion()
|
waitForForegroundPromotion()
|
||||||
completionHandler.set(success)
|
completionHandler.set(success)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package app.alextran.immich.background
|
package app.alextran.immich.background
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
|
|
@ -9,6 +8,7 @@ import androidx.work.Constraints
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import io.flutter.embedding.engine.FlutterEngineCache
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private const val TAG = "BackgroundWorkerApiImpl"
|
private const val TAG = "BackgroundWorkerApiImpl"
|
||||||
|
|
@ -34,8 +34,10 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val BACKGROUND_WORKER_NAME = "immich/BackgroundWorkerV1"
|
private const val BACKGROUND_WORKER_NAME = "immich/BackgroundWorkerV1"
|
||||||
private const val OBSERVER_WORKER_NAME = "immich/MediaObserverV1"
|
private const val OBSERVER_WORKER_NAME = "immich/MediaObserverV1"
|
||||||
|
const val ENGINE_CACHE_KEY = "immich::background_worker::engine"
|
||||||
|
|
||||||
|
|
||||||
fun enqueueMediaObserver(ctx: Context) {
|
fun enqueueMediaObserver(ctx: Context) {
|
||||||
val settings = BackgroundWorkerPreferences(ctx).getSettings()
|
val settings = BackgroundWorkerPreferences(ctx).getSettings()
|
||||||
|
|
@ -73,35 +75,18 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||||
|
|
||||||
Log.i(TAG, "Enqueued background worker with name: $BACKGROUND_WORKER_NAME")
|
Log.i(TAG, "Enqueued background worker with name: $BACKGROUND_WORKER_NAME")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BackgroundWorkerPreferences(private val ctx: Context) {
|
fun isBackgroundWorkerRunning(): Boolean {
|
||||||
companion object {
|
// Easier to check if the engine is cached as we always cache the engine when starting the worker
|
||||||
private const val SHARED_PREF_NAME = "Immich::BackgroundWorker"
|
// and remove it when the worker is finished
|
||||||
private const val SHARED_PREF_MIN_DELAY_KEY = "BackgroundWorker::minDelaySeconds"
|
return FlutterEngineCache.getInstance().get(ENGINE_CACHE_KEY) != null
|
||||||
private const val SHARED_PREF_REQUIRE_CHARGING_KEY = "BackgroundWorker::requireCharging"
|
}
|
||||||
|
|
||||||
private const val DEFAULT_MIN_DELAY_SECONDS = 30L
|
fun cancelBackgroundWorker(ctx: Context) {
|
||||||
private const val DEFAULT_REQUIRE_CHARGING = false
|
WorkManager.getInstance(ctx).cancelUniqueWork(BACKGROUND_WORKER_NAME)
|
||||||
}
|
FlutterEngineCache.getInstance().remove(ENGINE_CACHE_KEY)
|
||||||
|
|
||||||
private val sp: SharedPreferences by lazy {
|
Log.i(TAG, "Cancelled background upload task")
|
||||||
ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateSettings(settings: BackgroundWorkerSettings) {
|
|
||||||
sp.edit().apply {
|
|
||||||
putLong(SHARED_PREF_MIN_DELAY_KEY, settings.minimumDelaySeconds)
|
|
||||||
putBoolean(SHARED_PREF_REQUIRE_CHARGING_KEY, settings.requiresCharging)
|
|
||||||
apply()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSettings(): BackgroundWorkerSettings {
|
|
||||||
return BackgroundWorkerSettings(
|
|
||||||
minimumDelaySeconds = sp.getLong(SHARED_PREF_MIN_DELAY_KEY, DEFAULT_MIN_DELAY_SECONDS),
|
|
||||||
requiresCharging = sp.getBoolean(SHARED_PREF_REQUIRE_CHARGING_KEY, DEFAULT_REQUIRE_CHARGING),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Autogenerated from Pigeon (v26.0.0), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||||
|
|
||||||
|
package app.alextran.immich.background
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.flutter.plugin.common.BasicMessageChannel
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.MessageCodec
|
||||||
|
import io.flutter.plugin.common.StandardMethodCodec
|
||||||
|
import io.flutter.plugin.common.StandardMessageCodec
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
private object BackgroundWorkerLockPigeonUtils {
|
||||||
|
|
||||||
|
fun wrapResult(result: Any?): List<Any?> {
|
||||||
|
return listOf(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wrapError(exception: Throwable): List<Any?> {
|
||||||
|
return if (exception is FlutterError) {
|
||||||
|
listOf(
|
||||||
|
exception.code,
|
||||||
|
exception.message,
|
||||||
|
exception.details
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
exception.javaClass.simpleName,
|
||||||
|
exception.toString(),
|
||||||
|
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private open class BackgroundWorkerLockPigeonCodec : StandardMessageCodec() {
|
||||||
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
|
return super.readValueOfType(type, buffer)
|
||||||
|
}
|
||||||
|
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||||
|
super.writeValue(stream, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
|
interface BackgroundWorkerLockApi {
|
||||||
|
fun lock()
|
||||||
|
fun unlock()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by BackgroundWorkerLockApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
BackgroundWorkerLockPigeonCodec()
|
||||||
|
}
|
||||||
|
/** Sets up an instance of `BackgroundWorkerLockApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@JvmOverloads
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerLockApi?, messageChannelSuffix: String = "") {
|
||||||
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
|
run {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package app.alextran.immich.background
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
class BackgroundWorkerPreferences(private val ctx: Context) {
|
||||||
|
companion object {
|
||||||
|
const val SHARED_PREF_NAME = "Immich::BackgroundWorker"
|
||||||
|
private const val SHARED_PREF_MIN_DELAY_KEY = "BackgroundWorker::minDelaySeconds"
|
||||||
|
private const val SHARED_PREF_REQUIRE_CHARGING_KEY = "BackgroundWorker::requireCharging"
|
||||||
|
private const val SHARED_PREF_LOCK_KEY = "BackgroundWorker::isLocked"
|
||||||
|
|
||||||
|
private const val DEFAULT_MIN_DELAY_SECONDS = 30L
|
||||||
|
private const val DEFAULT_REQUIRE_CHARGING = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private val sp: SharedPreferences by lazy {
|
||||||
|
ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSettings(settings: BackgroundWorkerSettings) {
|
||||||
|
sp.edit {
|
||||||
|
putLong(SHARED_PREF_MIN_DELAY_KEY, settings.minimumDelaySeconds)
|
||||||
|
putBoolean(SHARED_PREF_REQUIRE_CHARGING_KEY, settings.requiresCharging)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSettings(): BackgroundWorkerSettings {
|
||||||
|
return BackgroundWorkerSettings(
|
||||||
|
minimumDelaySeconds = sp.getLong(SHARED_PREF_MIN_DELAY_KEY, DEFAULT_MIN_DELAY_SECONDS),
|
||||||
|
requiresCharging = sp.getBoolean(
|
||||||
|
SHARED_PREF_REQUIRE_CHARGING_KEY,
|
||||||
|
DEFAULT_REQUIRE_CHARGING
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLocked(paused: Boolean) {
|
||||||
|
sp.edit {
|
||||||
|
putBoolean(SHARED_PREF_LOCK_KEY, paused)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isLocked(): Boolean {
|
||||||
|
return sp.getBoolean(SHARED_PREF_LOCK_KEY, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -10,11 +10,13 @@ import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||||
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
|
|
@ -58,7 +60,7 @@ class BackgroundWorkerFgService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
late final ProviderContainer _ref;
|
ProviderContainer? _ref;
|
||||||
final Isar _isar;
|
final Isar _isar;
|
||||||
final Drift _drift;
|
final Drift _drift;
|
||||||
final DriftLogger _driftLogger;
|
final DriftLogger _driftLogger;
|
||||||
|
|
@ -83,29 +85,31 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
BackgroundWorkerFlutterApi.setUp(this);
|
BackgroundWorkerFlutterApi.setUp(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _isBackupEnabled => _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
bool get _isBackupEnabled => _ref?.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup) ?? false;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
try {
|
try {
|
||||||
HttpSSLOptions.apply(applyNative: false);
|
HttpSSLOptions.apply(applyNative: false);
|
||||||
|
|
||||||
await Future.wait([
|
await Future.wait(
|
||||||
loadTranslations(),
|
[
|
||||||
workerManager.init(dynamicSpawning: true),
|
loadTranslations(),
|
||||||
_ref.read(authServiceProvider).setOpenApiServiceEndpoint(),
|
workerManager.init(dynamicSpawning: true),
|
||||||
// Initialize the file downloader
|
_ref?.read(authServiceProvider).setOpenApiServiceEndpoint(),
|
||||||
FileDownloader().configure(
|
// Initialize the file downloader
|
||||||
globalConfig: [
|
FileDownloader().configure(
|
||||||
// maxConcurrent: 6, maxConcurrentByHost(server):6, maxConcurrentByGroup: 3
|
globalConfig: [
|
||||||
(Config.holdingQueue, (6, 6, 3)),
|
// maxConcurrent: 6, maxConcurrentByHost(server):6, maxConcurrentByGroup: 3
|
||||||
// On Android, if files are larger than 256MB, run in foreground service
|
(Config.holdingQueue, (6, 6, 3)),
|
||||||
(Config.runInForegroundIfFileLargerThan, 256),
|
// On Android, if files are larger than 256MB, run in foreground service
|
||||||
],
|
(Config.runInForegroundIfFileLargerThan, 256),
|
||||||
),
|
],
|
||||||
FileDownloader().trackTasksInGroup(kDownloadGroupLivePhoto, markDownloadedComplete: false),
|
),
|
||||||
FileDownloader().trackTasks(),
|
FileDownloader().trackTasksInGroup(kDownloadGroupLivePhoto, markDownloadedComplete: false),
|
||||||
_ref.read(fileMediaRepositoryProvider).enableBackgroundAccess(),
|
FileDownloader().trackTasks(),
|
||||||
]);
|
_ref?.read(fileMediaRepositoryProvider).enableBackgroundAccess(),
|
||||||
|
].nonNulls,
|
||||||
|
);
|
||||||
|
|
||||||
configureFileDownloaderNotifications();
|
configureFileDownloaderNotifications();
|
||||||
|
|
||||||
|
|
@ -178,15 +182,17 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _cleanup() async {
|
Future<void> _cleanup() async {
|
||||||
if (_isCleanedUp) {
|
// If ref is null, it means the service was never initialized properly
|
||||||
|
if (_isCleanedUp || _ref == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final backgroundSyncManager = _ref.read(backgroundSyncProvider);
|
|
||||||
final nativeSyncApi = _ref.read(nativeSyncApiProvider);
|
|
||||||
_isCleanedUp = true;
|
_isCleanedUp = true;
|
||||||
_ref.dispose();
|
final backgroundSyncManager = _ref?.read(backgroundSyncProvider);
|
||||||
|
final nativeSyncApi = _ref?.read(nativeSyncApiProvider);
|
||||||
|
_ref?.dispose();
|
||||||
|
_ref = null;
|
||||||
|
|
||||||
_cancellationToken.cancel();
|
_cancellationToken.cancel();
|
||||||
_logger.info("Cleaning up background worker");
|
_logger.info("Cleaning up background worker");
|
||||||
|
|
@ -199,14 +205,14 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
Store.dispose(),
|
Store.dispose(),
|
||||||
_drift.close(),
|
_drift.close(),
|
||||||
_driftLogger.close(),
|
_driftLogger.close(),
|
||||||
backgroundSyncManager.cancel(),
|
backgroundSyncManager?.cancel(),
|
||||||
nativeSyncApi.cancelHashing(),
|
nativeSyncApi?.cancelHashing(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_isar.isOpen) {
|
if (_isar.isOpen) {
|
||||||
cleanupFutures.add(_isar.close());
|
cleanupFutures.add(_isar.close());
|
||||||
}
|
}
|
||||||
await Future.wait(cleanupFutures);
|
await Future.wait(cleanupFutures.nonNulls);
|
||||||
_logger.info("Background worker resources cleaned up");
|
_logger.info("Background worker resources cleaned up");
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
dPrint(() => 'Failed to cleanup background worker: $error with stack: $stack');
|
dPrint(() => 'Failed to cleanup background worker: $error with stack: $stack');
|
||||||
|
|
@ -216,14 +222,18 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
Future<void> _handleBackup() async {
|
Future<void> _handleBackup() async {
|
||||||
await runZonedGuarded(
|
await runZonedGuarded(
|
||||||
() async {
|
() async {
|
||||||
if (!_isBackupEnabled || _isCleanedUp) {
|
if (_isCleanedUp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_isBackupEnabled) {
|
||||||
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
|
_logger.info("[_handleBackup 1] Backup is disabled. Skipping backup routine");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.info("[_handleBackup 2] Enqueuing assets for backup from the background service");
|
_logger.info("[_handleBackup 2] Enqueuing assets for backup from the background service");
|
||||||
|
|
||||||
final currentUser = _ref.read(currentUserProvider);
|
final currentUser = _ref?.read(currentUserProvider);
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
_logger.warning("[_handleBackup 3] No current user found. Skipping backup from background");
|
_logger.warning("[_handleBackup 3] No current user found. Skipping backup from background");
|
||||||
return;
|
return;
|
||||||
|
|
@ -231,19 +241,18 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
|
|
||||||
_logger.info("[_handleBackup 4] Resume backup from background");
|
_logger.info("[_handleBackup 4] Resume backup from background");
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
return _ref.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
|
return _ref?.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
final canPing = await _ref.read(serverInfoServiceProvider).ping();
|
final canPing = await _ref?.read(serverInfoServiceProvider).ping() ?? false;
|
||||||
if (!canPing) {
|
if (!canPing) {
|
||||||
_logger.warning("[_handleBackup 5] Server is not reachable. Skipping backup from background");
|
_logger.warning("[_handleBackup 5] Server is not reachable. Skipping backup from background");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final networkCapabilities = await _ref.read(connectivityApiProvider).getCapabilities();
|
final networkCapabilities = await _ref?.read(connectivityApiProvider).getCapabilities() ?? [];
|
||||||
|
|
||||||
return _ref
|
return _ref
|
||||||
.read(uploadServiceProvider)
|
?.read(uploadServiceProvider)
|
||||||
.startBackupWithHttpClient(currentUser.id, networkCapabilities.hasWifi, _cancellationToken);
|
.startBackupWithHttpClient(currentUser.id, networkCapabilities.hasWifi, _cancellationToken);
|
||||||
},
|
},
|
||||||
(error, stack) {
|
(error, stack) {
|
||||||
|
|
@ -253,18 +262,18 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _syncAssets({Duration? hashTimeout}) async {
|
Future<void> _syncAssets({Duration? hashTimeout}) async {
|
||||||
await _ref.read(backgroundSyncProvider).syncLocal();
|
await _ref?.read(backgroundSyncProvider).syncLocal();
|
||||||
if (_isCleanedUp) {
|
if (_isCleanedUp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _ref.read(backgroundSyncProvider).syncRemote();
|
await _ref?.read(backgroundSyncProvider).syncRemote();
|
||||||
if (_isCleanedUp) {
|
if (_isCleanedUp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hashFuture = _ref.read(backgroundSyncProvider).hashAssets();
|
var hashFuture = _ref?.read(backgroundSyncProvider).hashAssets();
|
||||||
if (hashTimeout != null) {
|
if (hashTimeout != null && hashFuture != null) {
|
||||||
hashFuture = hashFuture.timeout(
|
hashFuture = hashFuture.timeout(
|
||||||
hashTimeout,
|
hashTimeout,
|
||||||
onTimeout: () {
|
onTimeout: () {
|
||||||
|
|
@ -277,6 +286,23 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BackgroundWorkerLockService {
|
||||||
|
final BackgroundWorkerLockApi _hostApi;
|
||||||
|
const BackgroundWorkerLockService(this._hostApi);
|
||||||
|
|
||||||
|
Future<void> lock() async {
|
||||||
|
if (CurrentPlatform.isAndroid) {
|
||||||
|
return _hostApi.lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unlock() async {
|
||||||
|
if (CurrentPlatform.isAndroid) {
|
||||||
|
return _hostApi.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Native entry invoked from the background worker. If renaming or moving this to a different
|
/// Native entry invoked from the background worker. If renaming or moving this to a different
|
||||||
/// library, make sure to update the entry points and URI in native workers as well
|
/// library, make sure to update the entry points and URI in native workers as well
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,11 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/constants/locales.dart';
|
import 'package:immich_mobile/constants/locales.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
|
|
@ -32,6 +34,7 @@ import 'package:immich_mobile/theme/dynamic_theme.dart';
|
||||||
import 'package:immich_mobile/theme/theme_data.dart';
|
import 'package:immich_mobile/theme/theme_data.dart';
|
||||||
import 'package:immich_mobile/utils/bootstrap.dart';
|
import 'package:immich_mobile/utils/bootstrap.dart';
|
||||||
import 'package:immich_mobile/utils/cache/widgets_binding.dart';
|
import 'package:immich_mobile/utils/cache/widgets_binding.dart';
|
||||||
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:immich_mobile/utils/http_ssl_options.dart';
|
import 'package:immich_mobile/utils/http_ssl_options.dart';
|
||||||
import 'package:immich_mobile/utils/licenses.dart';
|
import 'package:immich_mobile/utils/licenses.dart';
|
||||||
import 'package:immich_mobile/utils/migration.dart';
|
import 'package:immich_mobile/utils/migration.dart';
|
||||||
|
|
@ -39,10 +42,10 @@ import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:timezone/data/latest.dart';
|
import 'package:timezone/data/latest.dart';
|
||||||
import 'package:worker_manager/worker_manager.dart';
|
import 'package:worker_manager/worker_manager.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
ImmichWidgetsBinding();
|
ImmichWidgetsBinding();
|
||||||
|
unawaited(BackgroundWorkerLockService(BackgroundWorkerLockApi()).lock());
|
||||||
final (isar, drift, logDb) = await Bootstrap.initDB();
|
final (isar, drift, logDb) = await Bootstrap.initDB();
|
||||||
await Bootstrap.initDomain(isar, drift, logDb);
|
await Bootstrap.initDomain(isar, drift, logDb);
|
||||||
await initApp();
|
await initApp();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
|
||||||
97
mobile/lib/platform/background_worker_lock_api.g.dart
generated
Normal file
97
mobile/lib/platform/background_worker_lock_api.g.dart
generated
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
// Autogenerated from Pigeon (v26.0.0), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
PlatformException _createConnectionError(String channelName) {
|
||||||
|
return PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel: "$channelName".',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
|
const _PigeonCodec();
|
||||||
|
@override
|
||||||
|
void writeValue(WriteBuffer buffer, Object? value) {
|
||||||
|
if (value is int) {
|
||||||
|
buffer.putUint8(4);
|
||||||
|
buffer.putInt64(value);
|
||||||
|
} else {
|
||||||
|
super.writeValue(buffer, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||||
|
switch (type) {
|
||||||
|
default:
|
||||||
|
return super.readValueOfType(type, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackgroundWorkerLockApi {
|
||||||
|
/// Constructor for [BackgroundWorkerLockApi]. The [binaryMessenger] named argument is
|
||||||
|
/// available for dependency injection. If it is left null, the default
|
||||||
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
|
BackgroundWorkerLockApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
|
|
||||||
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
|
Future<void> lock() async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.lock$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
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 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unlock() async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.unlock$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
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 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
|
import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/memory.provider.dart';
|
import 'package:immich_mobile/providers/memory.provider.dart';
|
||||||
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
|
@ -138,6 +139,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||||
|
|
||||||
Future<void> _handleBetaTimelineResume() async {
|
Future<void> _handleBetaTimelineResume() async {
|
||||||
_ref.read(backupProvider.notifier).cancelBackup();
|
_ref.read(backupProvider.notifier).cancelBackup();
|
||||||
|
unawaited(_ref.read(backgroundWorkerLockServiceProvider).lock());
|
||||||
|
|
||||||
// Give isolates time to complete any ongoing database transactions
|
// Give isolates time to complete any ongoing database transactions
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
@ -209,6 +211,9 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||||
_pauseOperation = Completer<void>();
|
_pauseOperation = Completer<void>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (Store.isBetaTimelineEnabled) {
|
||||||
|
unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock());
|
||||||
|
}
|
||||||
await _performPause();
|
await _performPause();
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe("Error during app pause", e, stackTrace);
|
_log.severe("Error during app pause", e, stackTrace);
|
||||||
|
|
@ -240,6 +245,10 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
||||||
Future<void> handleAppDetached() async {
|
Future<void> handleAppDetached() async {
|
||||||
state = AppLifeCycleEnum.detached;
|
state = AppLifeCycleEnum.detached;
|
||||||
|
|
||||||
|
if (Store.isBetaTimelineEnabled) {
|
||||||
|
unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock());
|
||||||
|
}
|
||||||
|
|
||||||
// Flush logs before closing database
|
// Flush logs before closing database
|
||||||
try {
|
try {
|
||||||
LogService.I.flush();
|
LogService.I.flush();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
||||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||||
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/thumbnail_api.g.dart';
|
import 'package:immich_mobile/platform/thumbnail_api.g.dart';
|
||||||
|
|
||||||
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
||||||
|
|
||||||
|
final backgroundWorkerLockServiceProvider = Provider<BackgroundWorkerLockService>(
|
||||||
|
(_) => BackgroundWorkerLockService(BackgroundWorkerLockApi()),
|
||||||
|
);
|
||||||
|
|
||||||
final nativeSyncApiProvider = Provider<NativeSyncApi>((_) => NativeSyncApi());
|
final nativeSyncApiProvider = Provider<NativeSyncApi>((_) => NativeSyncApi());
|
||||||
|
|
||||||
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
|
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@ pigeon:
|
||||||
dart run pigeon --input pigeon/native_sync_api.dart
|
dart run pigeon --input pigeon/native_sync_api.dart
|
||||||
dart run pigeon --input pigeon/thumbnail_api.dart
|
dart run pigeon --input pigeon/thumbnail_api.dart
|
||||||
dart run pigeon --input pigeon/background_worker_api.dart
|
dart run pigeon --input pigeon/background_worker_api.dart
|
||||||
|
dart run pigeon --input pigeon/background_worker_lock_api.dart
|
||||||
dart run pigeon --input pigeon/connectivity_api.dart
|
dart run pigeon --input pigeon/connectivity_api.dart
|
||||||
dart format lib/platform/native_sync_api.g.dart
|
dart format lib/platform/native_sync_api.g.dart
|
||||||
dart format lib/platform/thumbnail_api.g.dart
|
dart format lib/platform/thumbnail_api.g.dart
|
||||||
dart format lib/platform/background_worker_api.g.dart
|
dart format lib/platform/background_worker_api.g.dart
|
||||||
|
dart format lib/platform/background_worker_lock_api.g.dart
|
||||||
dart format lib/platform/connectivity_api.g.dart
|
dart format lib/platform/connectivity_api.g.dart
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
|
|
|
||||||
17
mobile/pigeon/background_worker_lock_api.dart
Normal file
17
mobile/pigeon/background_worker_lock_api.dart
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:pigeon/pigeon.dart';
|
||||||
|
|
||||||
|
@ConfigurePigeon(
|
||||||
|
PigeonOptions(
|
||||||
|
dartOut: 'lib/platform/background_worker_lock_api.g.dart',
|
||||||
|
kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt',
|
||||||
|
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.background', includeErrorClass: false),
|
||||||
|
dartOptions: DartOptions(),
|
||||||
|
dartPackageName: 'immich_mobile',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@HostApi()
|
||||||
|
abstract class BackgroundWorkerLockApi {
|
||||||
|
void lock();
|
||||||
|
|
||||||
|
void unlock();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue