Prepare QSfera production release
This commit is contained in:
@@ -14,6 +14,18 @@ def envValue(String primary, String legacy = null) {
|
||||
return legacy ? System.getenv(legacy) : null
|
||||
}
|
||||
|
||||
def releaseSigningEnv = [
|
||||
[primary: 'QSFERA_RELEASE_KEYSTORE', legacy: 'OC_RELEASE_KEYSTORE'],
|
||||
[primary: 'QSFERA_RELEASE_KEYSTORE_PASSWORD', legacy: 'OC_RELEASE_KEYSTORE_PASSWORD'],
|
||||
[primary: 'QSFERA_RELEASE_KEY_ALIAS', legacy: 'OC_RELEASE_KEY_ALIAS'],
|
||||
[primary: 'QSFERA_RELEASE_KEY_PASSWORD', legacy: 'OC_RELEASE_KEY_PASSWORD'],
|
||||
]
|
||||
|
||||
def missingReleaseSigningEnvKeys = {
|
||||
releaseSigningEnv.findAll { !envValue(it.primary, it.legacy) }
|
||||
.collect { "${it.primary} (${it.legacy} legacy fallback)" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Data and domain modules
|
||||
implementation project(':qsferaDomain')
|
||||
@@ -123,8 +135,8 @@ android {
|
||||
|
||||
testInstrumentationRunner "eu.qsfera.android.utils.OCTestAndroidJUnitRunner"
|
||||
|
||||
versionCode = 32
|
||||
versionName = "1.3.4"
|
||||
versionCode = 34
|
||||
versionName = "1.3.6"
|
||||
|
||||
buildConfigField "String", gitRemote, "\"" + getGitOriginRemote() + "\""
|
||||
buildConfigField "String", commitSHA1, "\"" + getLatestGitHash() + "\""
|
||||
@@ -235,6 +247,20 @@ android {
|
||||
testNamespace "eu.qsfera.android.test"
|
||||
}
|
||||
|
||||
gradle.taskGraph.whenReady { taskGraph ->
|
||||
def releaseBuildRequested = taskGraph.allTasks.any { task ->
|
||||
def taskName = task.name.toLowerCase()
|
||||
taskName.endsWith("release") &&
|
||||
(taskName.startsWith("assemble") || taskName.startsWith("bundle") || taskName.startsWith("package"))
|
||||
}
|
||||
def missingSigningEnvKeys = missingReleaseSigningEnvKeys()
|
||||
if (releaseBuildRequested && !missingSigningEnvKeys.isEmpty()) {
|
||||
throw new GradleException(
|
||||
"Release builds must be signed. Missing env vars: ${missingSigningEnvKeys.join(', ')}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Updates output file names of a given variant to format
|
||||
// [appName].[variant.versionName].[QSFERA_BUILD_NUMBER]-[variant.name].apk.
|
||||
//
|
||||
|
||||
+4
-3
@@ -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 {
|
||||
|
||||
+4
-3
@@ -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 {
|
||||
|
||||
+24
-19
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user