refactor: simplify background worker (#21558)

* chore: log hash starting

* chore: android - bump the min worker delay

* remove local sync only task and always enqueue background workers

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2025-09-03 20:27:30 +05:30 committed by GitHub
parent 2f1385a236
commit 9d3f10372d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 266 additions and 542 deletions

View file

@ -61,9 +61,8 @@ private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface BackgroundWorkerFgHostApi { interface BackgroundWorkerFgHostApi {
fun enableSyncWorker() fun enable()
fun enableUploadWorker() fun disable()
fun disableUploadWorker()
companion object { companion object {
/** The codec used by BackgroundWorkerFgHostApi. */ /** The codec used by BackgroundWorkerFgHostApi. */
@ -75,11 +74,11 @@ interface BackgroundWorkerFgHostApi {
fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerFgHostApi?, messageChannelSuffix: String = "") { fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerFgHostApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run { run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableSyncWorker$separatedMessageChannelSuffix", codec) val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable$separatedMessageChannelSuffix", codec)
if (api != null) { if (api != null) {
channel.setMessageHandler { _, reply -> channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try { val wrapped: List<Any?> = try {
api.enableSyncWorker() api.enable()
listOf(null) listOf(null)
} catch (exception: Throwable) { } catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception) BackgroundWorkerPigeonUtils.wrapError(exception)
@ -91,27 +90,11 @@ interface BackgroundWorkerFgHostApi {
} }
} }
run { run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker$separatedMessageChannelSuffix", codec) val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable$separatedMessageChannelSuffix", codec)
if (api != null) { if (api != null) {
channel.setMessageHandler { _, reply -> channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try { val wrapped: List<Any?> = try {
api.enableUploadWorker() api.disable()
listOf(null)
} catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disableUploadWorker$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.disableUploadWorker()
listOf(null) listOf(null)
} catch (exception: Throwable) { } catch (exception: Throwable) {
BackgroundWorkerPigeonUtils.wrapError(exception) BackgroundWorkerPigeonUtils.wrapError(exception)
@ -182,23 +165,6 @@ class BackgroundWorkerFlutterApi(private val binaryMessenger: BinaryMessenger, p
BackgroundWorkerPigeonCodec() BackgroundWorkerPigeonCodec()
} }
} }
fun onLocalSync(maxSecondsArg: Long?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onLocalSync$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(maxSecondsArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(BackgroundWorkerPigeonUtils.createConnectionError(channelName)))
}
}
}
fun onIosUpload(isRefreshArg: Boolean, maxSecondsArg: Long?, callback: (Result<Unit>) -> Unit) fun onIosUpload(isRefreshArg: Boolean, maxSecondsArg: Long?, callback: (Result<Unit>) -> Unit)
{ {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""

View file

@ -16,11 +16,6 @@ import io.flutter.embedding.engine.loader.FlutterLoader
private const val TAG = "BackgroundWorker" private const val TAG = "BackgroundWorker"
enum class BackgroundTaskType {
LOCAL_SYNC,
UPLOAD,
}
class BackgroundWorker(context: Context, params: WorkerParameters) : class BackgroundWorker(context: Context, params: WorkerParameters) :
ListenableWorker(context, params), BackgroundWorkerBgHostApi { ListenableWorker(context, params), BackgroundWorkerBgHostApi {
private val ctx: Context = context.applicationContext private val ctx: Context = context.applicationContext
@ -84,13 +79,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
* This method acts as a bridge between the native Android background task system and Flutter. * This method acts as a bridge between the native Android background task system and Flutter.
*/ */
override fun onInitialized() { override fun onInitialized() {
val taskTypeIndex = inputData.getInt(BackgroundWorkerApiImpl.WORKER_DATA_TASK_TYPE, 0) flutterApi?.onAndroidUpload { handleHostResult(it) }
val taskType = BackgroundTaskType.entries[taskTypeIndex]
when (taskType) {
BackgroundTaskType.LOCAL_SYNC -> flutterApi?.onLocalSync(null) { handleHostResult(it) }
BackgroundTaskType.UPLOAD -> flutterApi?.onAndroidUpload { handleHostResult(it) }
}
} }
override fun close() { override fun close() {

View file

@ -3,10 +3,8 @@ package app.alextran.immich.background
import android.content.Context import android.content.Context
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log import android.util.Log
import androidx.core.content.edit
import androidx.work.BackoffPolicy import androidx.work.BackoffPolicy
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
@ -16,18 +14,13 @@ private const val TAG = "BackgroundUploadImpl"
class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi { class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
private val ctx: Context = context.applicationContext private val ctx: Context = context.applicationContext
override fun enableSyncWorker() {
override fun enable() {
enqueueMediaObserver(ctx) enqueueMediaObserver(ctx)
Log.i(TAG, "Scheduled media observer")
} }
override fun enableUploadWorker() { override fun disable() {
updateUploadEnabled(ctx, true) WorkManager.getInstance(ctx).cancelUniqueWork(OBSERVER_WORKER_NAME)
Log.i(TAG, "Scheduled background upload tasks")
}
override fun disableUploadWorker() {
updateUploadEnabled(ctx, false)
WorkManager.getInstance(ctx).cancelUniqueWork(BACKGROUND_WORKER_NAME) WorkManager.getInstance(ctx).cancelUniqueWork(BACKGROUND_WORKER_NAME)
Log.i(TAG, "Cancelled background upload tasks") Log.i(TAG, "Cancelled background upload tasks")
} }
@ -36,25 +29,14 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
private 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 WORKER_DATA_TASK_TYPE = "taskType"
const val SHARED_PREF_NAME = "Immich::Background"
const val SHARED_PREF_BACKUP_ENABLED = "Background::backup::enabled"
private fun updateUploadEnabled(context: Context, enabled: Boolean) {
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE).edit {
putBoolean(SHARED_PREF_BACKUP_ENABLED, enabled)
}
}
fun enqueueMediaObserver(ctx: Context) { fun enqueueMediaObserver(ctx: Context) {
val constraints = Constraints.Builder() val constraints = Constraints.Builder()
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true) .addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
.setTriggerContentUpdateDelay(5, TimeUnit.SECONDS) .setTriggerContentUpdateDelay(30, TimeUnit.SECONDS)
.setTriggerContentMaxDelay(1, TimeUnit.MINUTES) .setTriggerContentMaxDelay(3, TimeUnit.MINUTES)
.build() .build()
val work = OneTimeWorkRequest.Builder(MediaObserver::class.java) val work = OneTimeWorkRequest.Builder(MediaObserver::class.java)
@ -66,15 +48,13 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
Log.i(TAG, "Enqueued media observer worker with name: $OBSERVER_WORKER_NAME") Log.i(TAG, "Enqueued media observer worker with name: $OBSERVER_WORKER_NAME")
} }
fun enqueueBackgroundWorker(ctx: Context, taskType: BackgroundTaskType) { fun enqueueBackgroundWorker(ctx: Context) {
val constraints = Constraints.Builder().setRequiresBatteryNotLow(true).build() val constraints = Constraints.Builder().setRequiresBatteryNotLow(true).build()
val data = Data.Builder()
data.putInt(WORKER_DATA_TASK_TYPE, taskType.ordinal)
val work = OneTimeWorkRequest.Builder(BackgroundWorker::class.java) val work = OneTimeWorkRequest.Builder(BackgroundWorker::class.java)
.setConstraints(constraints) .setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.setInputData(data.build()).build() .build()
WorkManager.getInstance(ctx) WorkManager.getInstance(ctx)
.enqueueUniqueWork(BACKGROUND_WORKER_NAME, ExistingWorkPolicy.REPLACE, work) .enqueueUniqueWork(BACKGROUND_WORKER_NAME, ExistingWorkPolicy.REPLACE, work)

View file

@ -6,29 +6,17 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
class MediaObserver(context: Context, params: WorkerParameters) : Worker(context, params) { class MediaObserver(context: Context, params: WorkerParameters) : Worker(context, params) {
private val ctx: Context = context.applicationContext private val ctx: Context = context.applicationContext
override fun doWork(): Result { override fun doWork(): Result {
Log.i("MediaObserver", "Content change detected, starting background worker") Log.i("MediaObserver", "Content change detected, starting background worker")
// Re-enqueue itself to listen for future changes
BackgroundWorkerApiImpl.enqueueMediaObserver(ctx)
// Enqueue backup worker only if there are new media changes // Enqueue backup worker only if there are new media changes
if (triggeredContentUris.isNotEmpty()) { if (triggeredContentUris.isNotEmpty()) {
val type = BackgroundWorkerApiImpl.enqueueBackgroundWorker(ctx)
if (isBackupEnabled(ctx)) BackgroundTaskType.UPLOAD else BackgroundTaskType.LOCAL_SYNC
BackgroundWorkerApiImpl.enqueueBackgroundWorker(ctx, type)
}
// Re-enqueue itself to listen for future changes
BackgroundWorkerApiImpl.enqueueMediaObserver(ctx)
return Result.success()
}
private fun isBackupEnabled(context: Context): Boolean {
val prefs =
context.getSharedPreferences(
BackgroundWorkerApiImpl.SHARED_PREF_NAME,
Context.MODE_PRIVATE
)
return prefs.getBoolean(BackgroundWorkerApiImpl.SHARED_PREF_BACKUP_ENABLED, false)
} }
return Result.success()
}
} }

View file

@ -73,9 +73,8 @@ class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Senda
/// Generated protocol from Pigeon that represents a handler of messages from Flutter. /// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol BackgroundWorkerFgHostApi { protocol BackgroundWorkerFgHostApi {
func enableSyncWorker() throws func enable() throws
func enableUploadWorker() throws func disable() throws
func disableUploadWorker() throws
} }
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@ -84,44 +83,31 @@ class BackgroundWorkerFgHostApiSetup {
/// Sets up an instance of `BackgroundWorkerFgHostApi` to handle messages through the `binaryMessenger`. /// Sets up an instance of `BackgroundWorkerFgHostApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: BackgroundWorkerFgHostApi?, messageChannelSuffix: String = "") { static func setUp(binaryMessenger: FlutterBinaryMessenger, api: BackgroundWorkerFgHostApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let enableSyncWorkerChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableSyncWorker\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) let enableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api { if let api = api {
enableSyncWorkerChannel.setMessageHandler { _, reply in enableChannel.setMessageHandler { _, reply in
do { do {
try api.enableSyncWorker() try api.enable()
reply(wrapResult(nil)) reply(wrapResult(nil))
} catch { } catch {
reply(wrapError(error)) reply(wrapError(error))
} }
} }
} else { } else {
enableSyncWorkerChannel.setMessageHandler(nil) enableChannel.setMessageHandler(nil)
} }
let enableUploadWorkerChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) let disableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api { if let api = api {
enableUploadWorkerChannel.setMessageHandler { _, reply in disableChannel.setMessageHandler { _, reply in
do { do {
try api.enableUploadWorker() try api.disable()
reply(wrapResult(nil)) reply(wrapResult(nil))
} catch { } catch {
reply(wrapError(error)) reply(wrapError(error))
} }
} }
} else { } else {
enableUploadWorkerChannel.setMessageHandler(nil) disableChannel.setMessageHandler(nil)
}
let disableUploadWorkerChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disableUploadWorker\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
disableUploadWorkerChannel.setMessageHandler { _, reply in
do {
try api.disableUploadWorker()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
disableUploadWorkerChannel.setMessageHandler(nil)
} }
} }
} }
@ -167,7 +153,6 @@ class BackgroundWorkerBgHostApiSetup {
} }
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
protocol BackgroundWorkerFlutterApiProtocol { protocol BackgroundWorkerFlutterApiProtocol {
func onLocalSync(maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onAndroidUpload(completion: @escaping (Result<Void, PigeonError>) -> Void) func onAndroidUpload(completion: @escaping (Result<Void, PigeonError>) -> Void)
func cancel(completion: @escaping (Result<Void, PigeonError>) -> Void) func cancel(completion: @escaping (Result<Void, PigeonError>) -> Void)
@ -182,24 +167,6 @@ class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol {
var codec: BackgroundWorkerPigeonCodec { var codec: BackgroundWorkerPigeonCodec {
return BackgroundWorkerPigeonCodec.shared return BackgroundWorkerPigeonCodec.shared
} }
func onLocalSync(maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onLocalSync\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage([maxSecondsArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName)))
return
}
if listResponse.count > 1 {
let code: String = listResponse[0] as! String
let message: String? = nilOrValue(listResponse[1])
let details: String? = nilOrValue(listResponse[2])
completion(.failure(PigeonError(code: code, message: message, details: details)))
} else {
completion(.success(()))
}
}
}
func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) { func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload\(messageChannelSuffix)" let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)

View file

@ -1,7 +1,7 @@
import BackgroundTasks import BackgroundTasks
import Flutter import Flutter
enum BackgroundTaskType { case localSync, refreshUpload, processingUpload } enum BackgroundTaskType { case refresh, processing }
/* /*
* DEBUG: Testing Background Tasks in Xcode * DEBUG: Testing Background Tasks in Xcode
@ -9,10 +9,6 @@ enum BackgroundTaskType { case localSync, refreshUpload, processingUpload }
* To test background task functionality during development: * To test background task functionality during development:
* 1. Pause the application in Xcode debugger * 1. Pause the application in Xcode debugger
* 2. In the debugger console, enter one of the following commands: * 2. In the debugger console, enter one of the following commands:
## For local sync (short-running sync):
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"app.alextran.immich.background.localSync"]
## For background refresh (short-running sync): ## For background refresh (short-running sync):
@ -24,8 +20,6 @@ enum BackgroundTaskType { case localSync, refreshUpload, processingUpload }
* To simulate task expiration (useful for testing expiration handlers): * To simulate task expiration (useful for testing expiration handlers):
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"app.alextran.immich.background.localSync"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"app.alextran.immich.background.refreshUpload"] e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"app.alextran.immich.background.refreshUpload"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"app.alextran.immich.background.processingUpload"] e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"app.alextran.immich.background.processingUpload"]
@ -120,17 +114,9 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
* This method acts as a bridge between the native iOS background task system and Flutter. * This method acts as a bridge between the native iOS background task system and Flutter.
*/ */
func onInitialized() throws { func onInitialized() throws {
switch self.taskType { flutterApi?.onIosUpload(isRefresh: self.taskType == .refresh, maxSeconds: maxSeconds.map { Int64($0) }, completion: { result in
case .refreshUpload, .processingUpload: self.handleHostResult(result: result)
flutterApi?.onIosUpload(isRefresh: self.taskType == .refreshUpload, })
maxSeconds: maxSeconds.map { Int64($0) }, completion: { result in
self.handleHostResult(result: result)
})
case .localSync:
flutterApi?.onLocalSync(maxSeconds: maxSeconds.map { Int64($0) }, completion: { result in
self.handleHostResult(result: result)
})
}
} }
/** /**
@ -177,6 +163,10 @@ class BackgroundWorker: BackgroundWorkerBgHostApi {
* - Parameter success: Indicates whether the background task completed successfully * - Parameter success: Indicates whether the background task completed successfully
*/ */
private func complete(success: Bool) { private func complete(success: Bool) {
if(isComplete) {
return
}
isComplete = true isComplete = true
engine.destroyContext() engine.destroyContext()
completionHandler(success) completionHandler(success)

View file

@ -1,77 +1,40 @@
import BackgroundTasks import BackgroundTasks
class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi { class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
func enableSyncWorker() throws {
BackgroundWorkerApiImpl.scheduleLocalSync()
print("BackgroundUploadImpl:enableSyncWorker Local Sync worker scheduled")
}
func enableUploadWorker() throws {
BackgroundWorkerApiImpl.updateUploadEnabled(true)
BackgroundWorkerApiImpl.scheduleRefreshUpload()
BackgroundWorkerApiImpl.scheduleProcessingUpload()
print("BackgroundUploadImpl:enableUploadWorker Scheduled background upload tasks")
}
func disableUploadWorker() throws {
BackgroundWorkerApiImpl.updateUploadEnabled(false)
BackgroundWorkerApiImpl.cancelUploadTasks()
print("BackgroundUploadImpl:disableUploadWorker Disabled background upload tasks")
}
public static let backgroundUploadEnabledKey = "immich:background:backup:enabled"
private static let localSyncTaskID = "app.alextran.immich.background.localSync"
private static let refreshUploadTaskID = "app.alextran.immich.background.refreshUpload"
private static let processingUploadTaskID = "app.alextran.immich.background.processingUpload"
private static func updateUploadEnabled(_ isEnabled: Bool) { func enable() throws {
return UserDefaults.standard.set(isEnabled, forKey: BackgroundWorkerApiImpl.backgroundUploadEnabledKey) BackgroundWorkerApiImpl.scheduleRefreshWorker()
BackgroundWorkerApiImpl.scheduleProcessingWorker()
print("BackgroundUploadImpl:enbale Background worker scheduled")
} }
private static func cancelUploadTasks() { func disable() throws {
BackgroundWorkerApiImpl.updateUploadEnabled(false) BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.refreshTaskID);
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: refreshUploadTaskID); BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundWorkerApiImpl.processingTaskID);
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: processingUploadTaskID); print("BackgroundUploadImpl:disableUploadWorker Disabled background workers")
} }
private static let refreshTaskID = "app.alextran.immich.background.refreshUpload"
private static let processingTaskID = "app.alextran.immich.background.processingUpload"
public static func registerBackgroundWorkers() { public static func registerBackgroundWorkers() {
BGTaskScheduler.shared.register( BGTaskScheduler.shared.register(
forTaskWithIdentifier: processingUploadTaskID, using: nil) { task in forTaskWithIdentifier: processingTaskID, using: nil) { task in
if task is BGProcessingTask { if task is BGProcessingTask {
handleBackgroundProcessing(task: task as! BGProcessingTask) handleBackgroundProcessing(task: task as! BGProcessingTask)
} }
} }
BGTaskScheduler.shared.register( BGTaskScheduler.shared.register(
forTaskWithIdentifier: refreshUploadTaskID, using: nil) { task in forTaskWithIdentifier: refreshTaskID, using: nil) { task in
if task is BGAppRefreshTask { if task is BGAppRefreshTask {
handleBackgroundRefresh(task: task as! BGAppRefreshTask, taskType: .refreshUpload) handleBackgroundRefresh(task: task as! BGAppRefreshTask)
} }
} }
BGTaskScheduler.shared.register(
forTaskWithIdentifier: localSyncTaskID, using: nil) { task in
if task is BGAppRefreshTask {
handleBackgroundRefresh(task: task as! BGAppRefreshTask, taskType: .localSync)
}
}
} }
private static func scheduleLocalSync() { private static func scheduleRefreshWorker() {
let backgroundRefresh = BGAppRefreshTaskRequest(identifier: localSyncTaskID) let backgroundRefresh = BGAppRefreshTaskRequest(identifier: refreshTaskID)
backgroundRefresh.earliestBeginDate = Date(timeIntervalSinceNow: 5 * 60) // 5 mins
do {
try BGTaskScheduler.shared.submit(backgroundRefresh)
} catch {
print("Could not schedule the local sync task \(error.localizedDescription)")
}
}
private static func scheduleRefreshUpload() {
let backgroundRefresh = BGAppRefreshTaskRequest(identifier: refreshUploadTaskID)
backgroundRefresh.earliestBeginDate = Date(timeIntervalSinceNow: 5 * 60) // 5 mins backgroundRefresh.earliestBeginDate = Date(timeIntervalSinceNow: 5 * 60) // 5 mins
do { do {
@ -81,8 +44,8 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
} }
} }
private static func scheduleProcessingUpload() { private static func scheduleProcessingWorker() {
let backgroundProcessing = BGProcessingTaskRequest(identifier: processingUploadTaskID) let backgroundProcessing = BGProcessingTaskRequest(identifier: processingTaskID)
backgroundProcessing.requiresNetworkConnectivity = true backgroundProcessing.requiresNetworkConnectivity = true
backgroundProcessing.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 mins backgroundProcessing.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 mins
@ -94,29 +57,16 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi {
} }
} }
private static func handleBackgroundRefresh(task: BGAppRefreshTask, taskType: BackgroundTaskType) { private static func handleBackgroundRefresh(task: BGAppRefreshTask) {
let maxSeconds: Int? scheduleRefreshWorker()
switch taskType {
case .localSync:
maxSeconds = 15
scheduleLocalSync()
case .refreshUpload:
maxSeconds = 20
scheduleRefreshUpload()
case .processingUpload:
print("Unexpected background refresh task encountered")
return;
}
// Restrict the refresh task to run only for a maximum of (maxSeconds) seconds // Restrict the refresh task to run only for a maximum of (maxSeconds) seconds
runBackgroundWorker(task: task, taskType: taskType, maxSeconds: maxSeconds) runBackgroundWorker(task: task, taskType: .refresh, maxSeconds: 20)
} }
private static func handleBackgroundProcessing(task: BGProcessingTask) { private static func handleBackgroundProcessing(task: BGProcessingTask) {
scheduleProcessingUpload() scheduleProcessingWorker()
// There are no restrictions for processing tasks. Although, the OS could signal expiration at any time // There are no restrictions for processing tasks. Although, the OS could signal expiration at any time
runBackgroundWorker(task: task, taskType: .processingUpload, maxSeconds: nil) runBackgroundWorker(task: task, taskType: .processing, maxSeconds: nil)
} }
/** /**

View file

@ -1,190 +1,189 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>AppGroupId</key> <key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string> <string>$(CUSTOM_GROUP_ID)</string>
<key>BGTaskSchedulerPermittedIdentifiers</key> <key>BGTaskSchedulerPermittedIdentifiers</key>
<array> <array>
<string>app.alextran.immich.background.localSync</string> <string>app.alextran.immich.background.refreshUpload</string>
<string>app.alextran.immich.background.refreshUpload</string> <string>app.alextran.immich.background.processingUpload</string>
<string>app.alextran.immich.background.processingUpload</string> <string>app.alextran.immich.backgroundFetch</string>
<string>app.alextran.immich.backgroundFetch</string> <string>app.alextran.immich.backgroundProcessing</string>
<string>app.alextran.immich.backgroundProcessing</string> </array>
</array> <key>CADisableMinimumFrameDurationOnPhone</key>
<key>CADisableMinimumFrameDurationOnPhone</key> <true/>
<true /> <key>CFBundleDevelopmentRegion</key>
<key>CFBundleDevelopmentRegion</key> <string>$(DEVELOPMENT_LANGUAGE)</string>
<string>$(DEVELOPMENT_LANGUAGE)</string> <key>CFBundleDisplayName</key>
<key>CFBundleDisplayName</key> <string>${PRODUCT_NAME}</string>
<string>${PRODUCT_NAME}</string> <key>CFBundleDocumentTypes</key>
<key>CFBundleDocumentTypes</key> <array>
<array> <dict>
<dict> <key>CFBundleTypeName</key>
<key>CFBundleTypeName</key> <string>ShareHandler</string>
<string>ShareHandler</string> <key>LSHandlerRank</key>
<key>LSHandlerRank</key> <string>Alternate</string>
<string>Alternate</string> <key>LSItemContentTypes</key>
<key>LSItemContentTypes</key> <array>
<array> <string>public.file-url</string>
<string>public.file-url</string> <string>public.image</string>
<string>public.image</string> <string>public.text</string>
<string>public.text</string> <string>public.movie</string>
<string>public.movie</string> <string>public.url</string>
<string>public.url</string> <string>public.data</string>
<string>public.data</string> </array>
</array> </dict>
</dict> </array>
</array> <key>CFBundleExecutable</key>
<key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string>
<string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key>
<key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key>
<key>CFBundleInfoDictionaryVersion</key> <string>6.0</string>
<string>6.0</string> <key>CFBundleLocalizations</key>
<key>CFBundleLocalizations</key> <array>
<array> <string>en</string>
<string>en</string> <string>ar</string>
<string>ar</string> <string>ca</string>
<string>ca</string> <string>cs</string>
<string>cs</string> <string>da</string>
<string>da</string> <string>de</string>
<string>de</string> <string>es</string>
<string>es</string> <string>fi</string>
<string>fi</string> <string>fr</string>
<string>fr</string> <string>he</string>
<string>he</string> <string>hi</string>
<string>hi</string> <string>hu</string>
<string>hu</string> <string>it</string>
<string>it</string> <string>ja</string>
<string>ja</string> <string>ko</string>
<string>ko</string> <string>lv</string>
<string>lv</string> <string>mn</string>
<string>mn</string> <string>nb</string>
<string>nb</string> <string>nl</string>
<string>nl</string> <string>pl</string>
<string>pl</string> <string>pt</string>
<string>pt</string> <string>ro</string>
<string>ro</string> <string>ru</string>
<string>ru</string> <string>sk</string>
<string>sk</string> <string>sl</string>
<string>sl</string> <string>sr</string>
<string>sr</string> <string>sv</string>
<string>sv</string> <string>th</string>
<string>th</string> <string>uk</string>
<string>uk</string> <string>vi</string>
<string>vi</string> <string>zh</string>
<string>zh</string> </array>
</array> <key>CFBundleName</key>
<key>CFBundleName</key> <string>immich_mobile</string>
<string>immich_mobile</string> <key>CFBundlePackageType</key>
<key>CFBundlePackageType</key> <string>APPL</string>
<string>APPL</string> <key>CFBundleShortVersionString</key>
<key>CFBundleShortVersionString</key> <string>1.140.0</string>
<string>1.140.0</string> <key>CFBundleSignature</key>
<key>CFBundleSignature</key> <string>????</string>
<string>????</string> <key>CFBundleURLTypes</key>
<key>CFBundleURLTypes</key> <array>
<array> <dict>
<dict> <key>CFBundleTypeRole</key>
<key>CFBundleTypeRole</key> <string>Editor</string>
<string>Editor</string> <key>CFBundleURLName</key>
<key>CFBundleURLName</key> <string>Share Extension</string>
<string>Share Extension</string> <key>CFBundleURLSchemes</key>
<key>CFBundleURLSchemes</key> <array>
<array> <string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string> </array>
</array> </dict>
</dict> <dict>
<dict> <key>CFBundleTypeRole</key>
<key>CFBundleTypeRole</key> <string>Editor</string>
<string>Editor</string> <key>CFBundleURLName</key>
<key>CFBundleURLName</key> <string>Deep Link</string>
<string>Deep Link</string> <key>CFBundleURLSchemes</key>
<key>CFBundleURLSchemes</key> <array>
<array> <string>immich</string>
<string>immich</string> </array>
</array> </dict>
</dict> </array>
</array> <key>CFBundleVersion</key>
<key>CFBundleVersion</key> <string>219</string>
<string>219</string> <key>FLTEnableImpeller</key>
<key>FLTEnableImpeller</key> <true/>
<true /> <key>ITSAppUsesNonExemptEncryption</key>
<key>ITSAppUsesNonExemptEncryption</key> <false/>
<false /> <key>LSApplicationQueriesSchemes</key>
<key>LSApplicationQueriesSchemes</key> <array>
<array> <string>https</string>
<string>https</string> </array>
</array> <key>LSRequiresIPhoneOS</key>
<key>LSRequiresIPhoneOS</key> <true/>
<true /> <key>LSSupportsOpeningDocumentsInPlace</key>
<key>LSSupportsOpeningDocumentsInPlace</key> <string>No</string>
<string>No</string> <key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<key>MGLMapboxMetricsEnabledSettingShownInApp</key> <true/>
<true /> <key>NSAppTransportSecurity</key>
<key>NSAppTransportSecurity</key> <dict>
<dict> <key>NSAllowsArbitraryLoads</key>
<key>NSAllowsArbitraryLoads</key> <true/>
<true /> </dict>
</dict> <key>NSBonjourServices</key>
<key>NSBonjourServices</key> <array>
<array> <string>_googlecast._tcp</string>
<string>_googlecast._tcp</string> <string>_CC1AD845._googlecast._tcp</string>
<string>_CC1AD845._googlecast._tcp</string> </array>
</array> <key>NSCameraUsageDescription</key>
<key>NSCameraUsageDescription</key> <string>We need to access the camera to let you take beautiful video using this app</string>
<string>We need to access the camera to let you take beautiful video using this app</string> <key>NSFaceIDUsageDescription</key>
<key>NSFaceIDUsageDescription</key> <string>We need to use FaceID to allow access to your locked folder</string>
<string>We need to use FaceID to allow access to your locked folder</string> <key>NSLocalNetworkUsageDescription</key>
<key>NSLocalNetworkUsageDescription</key> <string>We need local network permission to connect to the local server using IP address and
<string>We need local network permission to connect to the local server using IP address and
allow the casting feature to work</string> allow the casting feature to work</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We require this permission to access the local WiFi name for background upload mechanism</string> <string>We require this permission to access the local WiFi name for background upload mechanism</string>
<key>NSLocationUsageDescription</key> <key>NSLocationUsageDescription</key>
<string>We require this permission to access the local WiFi name</string> <string>We require this permission to access the local WiFi name</string>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string>We require this permission to access the local WiFi name</string> <string>We require this permission to access the local WiFi name</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>We need to access the microphone to let you take beautiful video using this app</string> <string>We need to access the microphone to let you take beautiful video using this app</string>
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>
<string>We need to manage backup your photos album</string> <string>We need to manage backup your photos album</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>We need to manage backup your photos album</string> <string>We need to manage backup your photos album</string>
<key>NSUserActivityTypes</key> <key>NSUserActivityTypes</key>
<array> <array>
<string>INSendMessageIntent</string> <string>INSendMessageIntent</string>
</array> </array>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true /> <true/>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>fetch</string> <string>fetch</string>
<string>processing</string> <string>processing</string>
</array> </array>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>UIStatusBarHidden</key> <key>UIStatusBarHidden</key>
<false /> <false/>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UISupportedInterfaceOrientations~ipad</key> <key>UISupportedInterfaceOrientations~ipad</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<true /> <true/>
<key>io.flutter.embedded_views_preview</key> <key>io.flutter.embedded_views_preview</key>
<true /> <true/>
</dict> </dict>
</plist> </plist>

View file

@ -30,11 +30,9 @@ class BackgroundWorkerFgService {
const BackgroundWorkerFgService(this._foregroundHostApi); const BackgroundWorkerFgService(this._foregroundHostApi);
// TODO: Move this call to native side once old timeline is removed // TODO: Move this call to native side once old timeline is removed
Future<void> enableSyncService() => _foregroundHostApi.enableSyncWorker(); Future<void> enable() => _foregroundHostApi.enable();
Future<void> enableUploadService() => _foregroundHostApi.enableUploadWorker(); Future<void> disable() => _foregroundHostApi.disable();
Future<void> disableUploadService() => _foregroundHostApi.disableUploadWorker();
} }
class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
@ -93,30 +91,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
} }
} }
@override
Future<void> onLocalSync(int? maxSeconds) async {
try {
_logger.info('Local background syncing started');
final sw = Stopwatch()..start();
final timeout = maxSeconds != null ? Duration(seconds: maxSeconds) : null;
await _syncAssets(hashTimeout: timeout, syncRemote: false);
sw.stop();
_logger.info("Local sync completed in ${sw.elapsed.inSeconds}s");
} catch (error, stack) {
_logger.severe("Failed to complete local sync", error, stack);
} finally {
await _cleanup();
}
}
/* We do the following on Android upload
* - Sync local assets
* - Hash local assets 3 / 6 minutes
* - Sync remote assets
* - Check and requeue upload tasks
*/
@override @override
Future<void> onAndroidUpload() async { Future<void> onAndroidUpload() async {
try { try {
@ -135,14 +109,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
} }
} }
/* We do the following on background upload
* - Sync local assets
* - Hash local assets
* - Sync remote assets
* - Check and requeue upload tasks
*
* The native side will not send the maxSeconds value for processing tasks
*/
@override @override
Future<void> onIosUpload(bool isRefresh, int? maxSeconds) async { Future<void> onIosUpload(bool isRefresh, int? maxSeconds) async {
try { try {
@ -222,7 +188,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
} }
} }
Future<void> _syncAssets({Duration? hashTimeout, bool syncRemote = true}) async { Future<void> _syncAssets({Duration? hashTimeout}) async {
final futures = <Future<void>>[]; final futures = <Future<void>>[];
final localSyncFuture = _ref.read(backgroundSyncProvider).syncLocal().then((_) async { final localSyncFuture = _ref.read(backgroundSyncProvider).syncLocal().then((_) async {
@ -244,10 +210,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
}); });
futures.add(localSyncFuture); futures.add(localSyncFuture);
if (syncRemote) { futures.add(_ref.read(backgroundSyncProvider).syncRemote());
final remoteSyncFuture = _ref.read(backgroundSyncProvider).syncRemote();
futures.add(remoteSyncFuture);
}
await Future.wait(futures); await Future.wait(futures);
} }

View file

@ -35,6 +35,7 @@ class HashService {
bool get isCancelled => _cancelChecker?.call() ?? false; bool get isCancelled => _cancelChecker?.call() ?? false;
Future<void> hashAssets() async { Future<void> hashAssets() async {
_log.info("Starting hashing of assets");
final Stopwatch stopwatch = Stopwatch()..start(); final Stopwatch stopwatch = Stopwatch()..start();
// Sorted by backupSelection followed by isCloud // Sorted by backupSelection followed by isCloud
final localAlbums = await _localAlbumRepository.getAll( final localAlbums = await _localAlbumRepository.getAll(

View file

@ -16,7 +16,6 @@ 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/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
import 'package:immich_mobile/providers/app_settings.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/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
@ -26,7 +25,6 @@ import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/providers/theme.provider.dart'; import 'package:immich_mobile/providers/theme.provider.dart';
import 'package:immich_mobile/routing/app_navigation_observer.dart'; import 'package:immich_mobile/routing/app_navigation_observer.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/services/deep_link.service.dart'; import 'package:immich_mobile/services/deep_link.service.dart';
import 'package:immich_mobile/services/local_notification.service.dart'; import 'package:immich_mobile/services/local_notification.service.dart';
@ -207,12 +205,9 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
// needs to be delayed so that EasyLocalization is working // needs to be delayed so that EasyLocalization is working
if (Store.isBetaTimelineEnabled) { if (Store.isBetaTimelineEnabled) {
ref.read(backgroundServiceProvider).disableService(); ref.read(backgroundServiceProvider).disableService();
ref.read(driftBackgroundUploadFgService).enableSyncService(); ref.read(driftBackgroundUploadFgService).enable();
if (ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup)) {
ref.read(driftBackgroundUploadFgService).enableUploadService();
}
} else { } else {
ref.read(driftBackgroundUploadFgService).disableUploadService(); ref.read(driftBackgroundUploadFgService).disable();
ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
} }
}); });

View file

@ -8,7 +8,6 @@ import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.widget.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/backup.provider.dart';
import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
@ -43,12 +42,10 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
await ref.read(backgroundSyncProvider).syncRemote(); await ref.read(backgroundSyncProvider).syncRemote();
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
await ref.read(driftBackgroundUploadFgService).enableUploadService();
await ref.read(driftBackupProvider.notifier).startBackup(currentUser.id); await ref.read(driftBackupProvider.notifier).startBackup(currentUser.id);
} }
Future<void> stopBackup() async { Future<void> stopBackup() async {
await ref.read(driftBackgroundUploadFgService).disableUploadService();
await ref.read(driftBackupProvider.notifier).cancel(); await ref.read(driftBackupProvider.notifier).cancel();
} }

View file

@ -79,7 +79,7 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
ref.read(readonlyModeProvider.notifier).setReadonlyMode(false); ref.read(readonlyModeProvider.notifier).setReadonlyMode(false);
await migrateStoreToIsar(ref.read(isarProvider), ref.read(driftProvider)); await migrateStoreToIsar(ref.read(isarProvider), ref.read(driftProvider));
await ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); await ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
await ref.read(driftBackgroundUploadFgService).disableUploadService(); await ref.read(driftBackgroundUploadFgService).disable();
} }
await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta); await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta);

View file

@ -59,9 +59,9 @@ class BackgroundWorkerFgHostApi {
final String pigeonVar_messageChannelSuffix; final String pigeonVar_messageChannelSuffix;
Future<void> enableSyncWorker() async { Future<void> enable() async {
final String pigeonVar_channelName = final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableSyncWorker$pigeonVar_messageChannelSuffix'; 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
@ -82,32 +82,9 @@ class BackgroundWorkerFgHostApi {
} }
} }
Future<void> enableUploadWorker() async { Future<void> disable() async {
final String pigeonVar_channelName = final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enableUploadWorker$pigeonVar_messageChannelSuffix'; 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable$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> disableUploadWorker() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disableUploadWorker$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
@ -192,8 +169,6 @@ class BackgroundWorkerBgHostApi {
abstract class BackgroundWorkerFlutterApi { abstract class BackgroundWorkerFlutterApi {
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec(); static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
Future<void> onLocalSync(int? maxSeconds);
Future<void> onIosUpload(bool isRefresh, int? maxSeconds); Future<void> onIosUpload(bool isRefresh, int? maxSeconds);
Future<void> onAndroidUpload(); Future<void> onAndroidUpload();
@ -206,35 +181,6 @@ abstract class BackgroundWorkerFlutterApi {
String messageChannelSuffix = '', String messageChannelSuffix = '',
}) { }) {
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
{
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onLocalSync$messageChannelSuffix',
pigeonChannelCodec,
binaryMessenger: binaryMessenger,
);
if (api == null) {
pigeonVar_channel.setMessageHandler(null);
} else {
pigeonVar_channel.setMessageHandler((Object? message) async {
assert(
message != null,
'Argument for dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onLocalSync was null.',
);
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_maxSeconds = (args[0] as int?);
try {
await api.onLocalSync(arg_maxSeconds);
return wrapResponse(empty: true);
} on PlatformException catch (e) {
return wrapResponse(error: e);
} catch (e) {
return wrapResponse(
error: PlatformException(code: 'error', message: e.toString()),
);
}
});
}
}
{ {
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload$messageChannelSuffix', 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload$messageChannelSuffix',

View file

@ -13,12 +13,9 @@ import 'package:pigeon/pigeon.dart';
) )
@HostApi() @HostApi()
abstract class BackgroundWorkerFgHostApi { abstract class BackgroundWorkerFgHostApi {
void enableSyncWorker(); void enable();
void enableUploadWorker(); void disable();
// Disables the background upload service
void disableUploadWorker();
} }
@HostApi() @HostApi()
@ -32,10 +29,6 @@ abstract class BackgroundWorkerBgHostApi {
@FlutterApi() @FlutterApi()
abstract class BackgroundWorkerFlutterApi { abstract class BackgroundWorkerFlutterApi {
// Android & iOS: Called when the local sync is triggered
@async
void onLocalSync(int? maxSeconds);
// iOS Only: Called when the iOS background upload is triggered // iOS Only: Called when the iOS background upload is triggered
@async @async
void onIosUpload(bool isRefresh, int? maxSeconds); void onIosUpload(bool isRefresh, int? maxSeconds);