mirror of
https://github.com/immich-app/immich.git
synced 2025-12-08 17:23:14 +03:00
Compare commits
2 Commits
ben/tree-a
...
fix/restri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9029ec5bb6 | ||
|
|
02456a148e |
@@ -43,8 +43,8 @@ class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, ImmichPl
|
||||
|
||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
super.onAttachedToEngine(binding)
|
||||
checkAndEnforceBackgroundLock(binding.applicationContext)
|
||||
engineCount.incrementAndGet()
|
||||
checkAndEnforceBackgroundLock(binding.applicationContext)
|
||||
Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount")
|
||||
}
|
||||
|
||||
|
||||
@@ -295,12 +295,12 @@ class BackgroundWorkerFlutterApi(private val binaryMessenger: BinaryMessenger, p
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onAndroidUpload(callback: (Result<Unit>) -> Unit)
|
||||
fun onAndroidUpload(maxMinutesArg: Long?, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(null) {
|
||||
channel.send(listOf(maxMinutesArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
|
||||
@@ -107,7 +107,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
|
||||
* This method acts as a bridge between the native Android background task system and Flutter.
|
||||
*/
|
||||
override fun onInitialized() {
|
||||
flutterApi?.onAndroidUpload { handleHostResult(it) }
|
||||
flutterApi?.onAndroidUpload(maxMinutesArg = 20) { handleHostResult(it) }
|
||||
}
|
||||
|
||||
// TODO: Move this to a separate NotificationManager class
|
||||
|
||||
@@ -5,8 +5,10 @@ import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import io.flutter.embedding.engine.FlutterEngineCache
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -18,6 +20,7 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||
|
||||
override fun enable() {
|
||||
enqueueMediaObserver(ctx)
|
||||
enqueuePeriodicWorker(ctx)
|
||||
}
|
||||
|
||||
override fun saveNotificationMessage(title: String, body: String) {
|
||||
@@ -27,12 +30,14 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||
override fun configure(settings: BackgroundWorkerSettings) {
|
||||
BackgroundWorkerPreferences(ctx).updateSettings(settings)
|
||||
enqueueMediaObserver(ctx)
|
||||
enqueuePeriodicWorker(ctx)
|
||||
}
|
||||
|
||||
override fun disable() {
|
||||
WorkManager.getInstance(ctx).apply {
|
||||
cancelUniqueWork(OBSERVER_WORKER_NAME)
|
||||
cancelUniqueWork(BACKGROUND_WORKER_NAME)
|
||||
cancelUniqueWork(PERIODIC_WORKER_NAME)
|
||||
}
|
||||
Log.i(TAG, "Cancelled background upload tasks")
|
||||
}
|
||||
@@ -40,6 +45,7 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||
companion object {
|
||||
private const val BACKGROUND_WORKER_NAME = "immich/BackgroundWorkerV1"
|
||||
private const val OBSERVER_WORKER_NAME = "immich/MediaObserverV1"
|
||||
private const val PERIODIC_WORKER_NAME = "immich/PeriodicBackgroundWorkerV1"
|
||||
const val ENGINE_CACHE_KEY = "immich::background_worker::engine"
|
||||
|
||||
|
||||
@@ -55,7 +61,7 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||
setRequiresCharging(settings.requiresCharging)
|
||||
}.build()
|
||||
|
||||
val work = OneTimeWorkRequest.Builder(MediaObserver::class.java)
|
||||
val work = OneTimeWorkRequestBuilder<MediaObserver>()
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
WorkManager.getInstance(ctx)
|
||||
@@ -67,10 +73,30 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi {
|
||||
)
|
||||
}
|
||||
|
||||
fun enqueuePeriodicWorker(ctx: Context) {
|
||||
val settings = BackgroundWorkerPreferences(ctx).getSettings()
|
||||
val constraints = Constraints.Builder().apply {
|
||||
setRequiresCharging(settings.requiresCharging)
|
||||
}.build()
|
||||
|
||||
val work =
|
||||
PeriodicWorkRequestBuilder<PeriodicWorker>(
|
||||
1,
|
||||
TimeUnit.HOURS,
|
||||
15,
|
||||
TimeUnit.MINUTES
|
||||
).setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(ctx)
|
||||
.enqueueUniquePeriodicWork(PERIODIC_WORKER_NAME, ExistingPeriodicWorkPolicy.UPDATE, work)
|
||||
|
||||
Log.i(TAG, "Enqueued periodic background worker with name: $PERIODIC_WORKER_NAME")
|
||||
}
|
||||
|
||||
fun enqueueBackgroundWorker(ctx: Context) {
|
||||
val constraints = Constraints.Builder().setRequiresBatteryNotLow(true).build()
|
||||
|
||||
val work = OneTimeWorkRequest.Builder(BackgroundWorker::class.java)
|
||||
val work = OneTimeWorkRequestBuilder<BackgroundWorker>()
|
||||
.setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
|
||||
.build()
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package app.alextran.immich.background
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
|
||||
class PeriodicWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
|
||||
private val ctx: Context = context.applicationContext
|
||||
|
||||
override fun doWork(): Result {
|
||||
Log.i("PeriodicWorker", "Periodic worker triggered, starting background worker")
|
||||
BackgroundWorkerApiImpl.enqueueBackgroundWorker(ctx)
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
@@ -295,7 +295,7 @@ class BackgroundWorkerBgHostApiSetup {
|
||||
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
|
||||
protocol BackgroundWorkerFlutterApiProtocol {
|
||||
func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
|
||||
func onAndroidUpload(completion: @escaping (Result<Void, PigeonError>) -> Void)
|
||||
func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
|
||||
func cancel(completion: @escaping (Result<Void, PigeonError>) -> Void)
|
||||
}
|
||||
class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol {
|
||||
@@ -326,10 +326,10 @@ class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
func onAndroidUpload(completion: @escaping (Result<Void, PigeonError>) -> Void) {
|
||||
func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
|
||||
let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload\(messageChannelSuffix)"
|
||||
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
|
||||
channel.sendMessage(nil) { response in
|
||||
channel.sendMessage([maxMinutesArg] as [Any?]) { response in
|
||||
guard let listResponse = response as? [Any?] else {
|
||||
completion(.failure(createConnectionError(withChannelName: channelName)))
|
||||
return
|
||||
|
||||
@@ -122,46 +122,54 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onAndroidUpload() async {
|
||||
_logger.info('Android background processing started');
|
||||
final sw = Stopwatch()..start();
|
||||
try {
|
||||
if (!await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6))) {
|
||||
_logger.warning("Remote sync did not complete successfully, skipping backup");
|
||||
return;
|
||||
}
|
||||
await _handleBackup();
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Failed to complete Android background processing", error, stack);
|
||||
} finally {
|
||||
sw.stop();
|
||||
_logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s");
|
||||
await _cleanup();
|
||||
}
|
||||
Future<void> onAndroidUpload(int? maxMinutes) async {
|
||||
final hashTimeout = Duration(minutes: _isBackupEnabled ? 3 : 6);
|
||||
final backupTimeout = maxMinutes != null ? Duration(minutes: maxMinutes - 1) : null;
|
||||
return _backgroundLoop(
|
||||
hashTimeout: hashTimeout,
|
||||
backupTimeout: backupTimeout,
|
||||
debugLabel: 'Android background upload',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onIosUpload(bool isRefresh, int? maxSeconds) async {
|
||||
_logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s');
|
||||
final hashTimeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6);
|
||||
final backupTimeout = maxSeconds != null ? Duration(seconds: maxSeconds - 1) : null;
|
||||
return _backgroundLoop(hashTimeout: hashTimeout, backupTimeout: backupTimeout, debugLabel: 'iOS background upload');
|
||||
}
|
||||
|
||||
Future<void> _backgroundLoop({
|
||||
required Duration hashTimeout,
|
||||
required Duration? backupTimeout,
|
||||
required String debugLabel,
|
||||
}) async {
|
||||
_logger.info(
|
||||
'$debugLabel started hashTimeout: ${hashTimeout.inSeconds}s, backupTimeout: ${backupTimeout?.inMinutes ?? '~'}m',
|
||||
);
|
||||
final sw = Stopwatch()..start();
|
||||
try {
|
||||
final timeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6);
|
||||
if (!await _syncAssets(hashTimeout: timeout)) {
|
||||
if (!await _syncAssets(hashTimeout: hashTimeout)) {
|
||||
_logger.warning("Remote sync did not complete successfully, skipping backup");
|
||||
return;
|
||||
}
|
||||
|
||||
final backupFuture = _handleBackup();
|
||||
if (maxSeconds != null) {
|
||||
await backupFuture.timeout(Duration(seconds: maxSeconds - 1), onTimeout: () {});
|
||||
if (backupTimeout != null) {
|
||||
await backupFuture.timeout(
|
||||
backupTimeout,
|
||||
onTimeout: () {
|
||||
_cancellationToken.cancel();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await backupFuture;
|
||||
}
|
||||
} catch (error, stack) {
|
||||
_logger.severe("Failed to complete iOS background upload", error, stack);
|
||||
_logger.severe("Failed to complete $debugLabel", error, stack);
|
||||
} finally {
|
||||
sw.stop();
|
||||
_logger.info("iOS background upload completed in ${sw.elapsed.inSeconds}s");
|
||||
_logger.info("$debugLabel completed in ${sw.elapsed.inSeconds}s");
|
||||
await _cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
10
mobile/lib/platform/background_worker_api.g.dart
generated
10
mobile/lib/platform/background_worker_api.g.dart
generated
@@ -273,7 +273,7 @@ abstract class BackgroundWorkerFlutterApi {
|
||||
|
||||
Future<void> onIosUpload(bool isRefresh, int? maxSeconds);
|
||||
|
||||
Future<void> onAndroidUpload();
|
||||
Future<void> onAndroidUpload(int? maxMinutes);
|
||||
|
||||
Future<void> cancel();
|
||||
|
||||
@@ -327,8 +327,14 @@ abstract class BackgroundWorkerFlutterApi {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
assert(
|
||||
message != null,
|
||||
'Argument for dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload was null.',
|
||||
);
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final int? arg_maxMinutes = (args[0] as int?);
|
||||
try {
|
||||
await api.onAndroidUpload();
|
||||
await api.onAndroidUpload(arg_maxMinutes);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
|
||||
@@ -47,7 +47,7 @@ abstract class BackgroundWorkerFlutterApi {
|
||||
|
||||
// Android Only: Called when the Android background upload is triggered
|
||||
@async
|
||||
void onAndroidUpload();
|
||||
void onAndroidUpload(int? maxMinutes);
|
||||
|
||||
@async
|
||||
void cancel();
|
||||
|
||||
Reference in New Issue
Block a user