Prepare QSfera production release

This commit is contained in:
Курнат Андрей
2026-06-08 19:57:40 +03:00
parent 2315f25754
commit 43caef7a6a
27 changed files with 1159 additions and 42 deletions
@@ -30,6 +30,7 @@ import eu.qsfera.android.R
import eu.qsfera.android.db.PreferenceManager.PREF__CAMERA_UPLOADS_DEFAULT_PATH
import eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfiguration
import eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfiguration.Companion.encodeSourcePaths
import eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfiguration.Companion.initialSyncTimestamp
import eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfiguration.Companion.pictureUploadsName
import eu.qsfera.android.domain.automaticuploads.model.UploadBehavior
import eu.qsfera.android.domain.automaticuploads.usecases.GetPictureUploadsConfigurationStreamUseCase
@@ -181,7 +182,7 @@ class SettingsPictureUploadsViewModel(
SavePictureUploadsConfigurationUseCase.Params(
composePictureUploadsConfiguration(
sourcePath = encodeSourcePaths(updatedSourcePaths),
timestamp = System.currentTimeMillis().takeIf { updatedSourcePaths != previousSourcePaths }
timestamp = initialSyncTimestamp.takeIf { updatedSourcePaths != previousSourcePaths }
)
)
)
@@ -220,9 +221,9 @@ class SettingsPictureUploadsViewModel(
behavior = behavior ?: UploadBehavior.COPY,
sourcePath = sourcePath.orEmpty(),
uploadPath = uploadPath ?: PREF__CAMERA_UPLOADS_DEFAULT_PATH,
wifiOnly = wifiOnly ?: false,
wifiOnly = wifiOnly ?: true,
chargingOnly = chargingOnly ?: false,
lastSyncTimestamp = timestamp ?: System.currentTimeMillis(),
lastSyncTimestamp = timestamp ?: initialSyncTimestamp,
name = _pictureUploads.value?.name ?: pictureUploadsName,
spaceId = spaceId,
).also {
@@ -30,6 +30,7 @@ import eu.qsfera.android.R
import eu.qsfera.android.db.PreferenceManager.PREF__CAMERA_UPLOADS_DEFAULT_PATH
import eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfiguration
import eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfiguration.Companion.encodeSourcePaths
import eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfiguration.Companion.initialSyncTimestamp
import eu.qsfera.android.domain.automaticuploads.model.FolderBackUpConfiguration.Companion.videoUploadsName
import eu.qsfera.android.domain.automaticuploads.model.UploadBehavior
import eu.qsfera.android.domain.automaticuploads.usecases.GetVideoUploadsConfigurationStreamUseCase
@@ -181,7 +182,7 @@ class SettingsVideoUploadsViewModel(
SaveVideoUploadsConfigurationUseCase.Params(
composeVideoUploadsConfiguration(
sourcePath = encodeSourcePaths(updatedSourcePaths),
timestamp = System.currentTimeMillis().takeIf { updatedSourcePaths != previousSourcePaths }
timestamp = initialSyncTimestamp.takeIf { updatedSourcePaths != previousSourcePaths }
)
)
)
@@ -221,9 +222,9 @@ class SettingsVideoUploadsViewModel(
behavior = behavior ?: UploadBehavior.COPY,
sourcePath = sourcePath.orEmpty(),
uploadPath = uploadPath ?: PREF__CAMERA_UPLOADS_DEFAULT_PATH,
wifiOnly = wifiOnly ?: false,
wifiOnly = wifiOnly ?: true,
chargingOnly = chargingOnly ?: false,
lastSyncTimestamp = timestamp ?: System.currentTimeMillis(),
lastSyncTimestamp = timestamp ?: initialSyncTimestamp,
name = _videoUploads.value?.name ?: videoUploadsName,
spaceId = spaceId,
).also {
@@ -42,6 +42,9 @@ import eu.qsfera.android.workers.OldLogsCollectorWorker
import eu.qsfera.android.workers.RemoveLocallyFilesWithLastUsageOlderThanGivenTimeWorker
import eu.qsfera.android.workers.UploadFileFromContentUriWorker
import eu.qsfera.android.workers.UploadFileFromFileSystemWorker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.TimeUnit
@@ -90,28 +93,30 @@ class WorkManagerProvider(
* concurrent scans or redundant enqueues on rapid foreground/background switches.
*/
fun enqueueImmediateAutomaticUploadsWorker() {
val wm = WorkManager.getInstance(context)
CoroutineScope(Dispatchers.IO).launch {
val wm = WorkManager.getInstance(context)
val periodicRunning = wm.getWorkInfosForUniqueWork(AutomaticUploadsWorker.AUTOMATIC_UPLOADS_WORKER)
.get().any { it.state == WorkInfo.State.RUNNING }
val immediateRunning = wm.getWorkInfosForUniqueWork(AutomaticUploadsWorker.IMMEDIATE_UPLOADS_WORKER)
.get().any { it.state == WorkInfo.State.RUNNING }
val periodicRunning = wm.getWorkInfosForUniqueWork(AutomaticUploadsWorker.AUTOMATIC_UPLOADS_WORKER)
.get().any { it.state == WorkInfo.State.RUNNING }
val immediateRunning = wm.getWorkInfosForUniqueWork(AutomaticUploadsWorker.IMMEDIATE_UPLOADS_WORKER)
.get().any { it.state == WorkInfo.State.RUNNING }
if (periodicRunning || immediateRunning) {
Timber.d("Automatic uploads worker already running, skipping immediate run")
return
if (periodicRunning || immediateRunning) {
Timber.d("Automatic uploads worker already running, skipping immediate run")
return@launch
}
val immediateWorker = OneTimeWorkRequestBuilder<AutomaticUploadsWorker>()
.addTag(AutomaticUploadsWorker.IMMEDIATE_UPLOADS_WORKER)
.setInitialDelay(AutomaticUploadsWorker.WRITE_SAFETY_BUFFER_MS, TimeUnit.MILLISECONDS)
.build()
wm.enqueueUniqueWork(
AutomaticUploadsWorker.IMMEDIATE_UPLOADS_WORKER,
ExistingWorkPolicy.KEEP,
immediateWorker
)
}
val immediateWorker = OneTimeWorkRequestBuilder<AutomaticUploadsWorker>()
.addTag(AutomaticUploadsWorker.IMMEDIATE_UPLOADS_WORKER)
.setInitialDelay(AutomaticUploadsWorker.WRITE_SAFETY_BUFFER_MS, java.util.concurrent.TimeUnit.MILLISECONDS)
.build()
wm.enqueueUniqueWork(
AutomaticUploadsWorker.IMMEDIATE_UPLOADS_WORKER,
ExistingWorkPolicy.KEEP,
immediateWorker
)
}
fun enqueueOldLogsCollectorWorker() {
@@ -128,8 +128,13 @@ class AutomaticUploadsWorker(
private fun checkSourcePathsAreValidUrisOrThrowException(sourcePaths: List<String>) {
sourcePaths.forEach { sourcePath ->
val sourceUri: Uri = sourcePath.toUri()
DocumentFile.fromTreeUri(applicationContext, sourceUri)
val hasPersistedReadPermission = appContext.contentResolver.persistedUriPermissions
.any { uriPermission -> uriPermission.uri == sourceUri && uriPermission.isReadPermission }
val documentTree = DocumentFile.fromTreeUri(applicationContext, sourceUri)
?: throw IllegalArgumentException("Source path is not a valid tree URI: $sourcePath")
if (!hasPersistedReadPermission || !documentTree.canRead()) {
throw IllegalArgumentException("Source path is not readable: $sourcePath")
}
}
}