From 121a80b6b988cfcc8486f82d2b23cf33508e6ca6 Mon Sep 17 00:00:00 2001 From: Aditya Rajput Date: Wed, 11 Mar 2026 10:45:14 +0530 Subject: [PATCH 1/2] Migrate database non-destructively At least, mostly: executions' verb information will be lost. --- .../3.json | 86 +++++++++++++++++++ .../fileflow/data/AppContainer.kt | 11 ++- .../adityarajput/fileflow/data/Converters.kt | 13 ++- .../fileflow/data/FileFlowDatabase.kt | 13 ++- .../co/adityarajput/fileflow/data/Verb.kt | 9 ++ .../fileflow/data/models/Action.kt | 7 +- .../fileflow/data/models/Execution.kt | 5 +- .../views/components/ImproperRulesetDialog.kt | 2 +- .../views/screens/ExecutionsScreen.kt | 2 +- .../fileflow/views/screens/RulesScreen.kt | 2 +- metadata/en-US/changelogs/4.txt | 4 +- 11 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/3.json create mode 100644 app/src/main/java/co/adityarajput/fileflow/data/Verb.kt diff --git a/app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/3.json b/app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/3.json new file mode 100644 index 0000000..04e35bb --- /dev/null +++ b/app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/3.json @@ -0,0 +1,86 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "6517669d413b15b9fd2c38215b1fe3be", + "entities": [ + { + "tableName": "rules", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`action` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `executions` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "action", + "columnName": "action", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "executions", + "columnName": "executions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "executions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`fileName` TEXT NOT NULL, `verb` TEXT NOT NULL DEFAULT 'MOVE', `timestamp` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "fileName", + "columnName": "fileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "verb", + "columnName": "verb", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'MOVE'" + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6517669d413b15b9fd2c38215b1fe3be')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt b/app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt index 1e49bfe..95f8bf5 100644 --- a/app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt +++ b/app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt @@ -1,7 +1,6 @@ package co.adityarajput.fileflow.data import android.content.Context -import co.adityarajput.fileflow.R import co.adityarajput.fileflow.data.models.Action import co.adityarajput.fileflow.data.models.Execution import co.adityarajput.fileflow.data.models.Rule @@ -56,27 +55,27 @@ class AppContainer(private val context: Context) { repository.upsert( Execution( "TubularData-20251201_113745.zip", - R.string.move, + Verb.MOVE, System.currentTimeMillis() - 86400000L * 66, ), Execution( "TubularData-20260101_141634.zip", - R.string.move, + Verb.MOVE, System.currentTimeMillis() - 86400000L * 35, ), Execution( "TubularData-20260201_160604.zip", - R.string.move, + Verb.MOVE, System.currentTimeMillis() - 86400000L * 4, ), Execution( "AntennaPodBackup-2026-02-02.db", - R.string.copy, + Verb.COPY, System.currentTimeMillis() - 86400000L * 3, ), Execution( "AntennaPodBackup-2026-02-05.db", - R.string.copy, + Verb.COPY, System.currentTimeMillis() - 60_000L * 5, ), ) diff --git a/app/src/main/java/co/adityarajput/fileflow/data/Converters.kt b/app/src/main/java/co/adityarajput/fileflow/data/Converters.kt index 763a907..893cb1a 100644 --- a/app/src/main/java/co/adityarajput/fileflow/data/Converters.kt +++ b/app/src/main/java/co/adityarajput/fileflow/data/Converters.kt @@ -3,6 +3,7 @@ package co.adityarajput.fileflow.data import androidx.room.TypeConverter import co.adityarajput.fileflow.data.models.Action import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject class Converters { @TypeConverter @@ -12,6 +13,16 @@ class Converters { fun toAction(value: String) = try { Json.decodeFromString(value) } catch (_: Exception) { - Action.entries[0] + try { + Json.decodeFromString( + Json.encodeToString( + Json.parseToJsonElement(value) + .jsonObject.toMap() + .filterKeys { it != "title" }, + ), + ) + } catch (_: Exception) { + Action.entries[0] + } } } diff --git a/app/src/main/java/co/adityarajput/fileflow/data/FileFlowDatabase.kt b/app/src/main/java/co/adityarajput/fileflow/data/FileFlowDatabase.kt index 51d8fc9..a1773c5 100644 --- a/app/src/main/java/co/adityarajput/fileflow/data/FileFlowDatabase.kt +++ b/app/src/main/java/co/adityarajput/fileflow/data/FileFlowDatabase.kt @@ -2,15 +2,26 @@ package co.adityarajput.fileflow.data import android.content.Context import androidx.room.* +import androidx.room.migration.AutoMigrationSpec import co.adityarajput.fileflow.data.models.Execution import co.adityarajput.fileflow.data.models.Rule -@Database([Rule::class, Execution::class], version = 2, autoMigrations = [AutoMigration(1, 2)]) +@Database( + [Rule::class, Execution::class], + version = 3, + autoMigrations = [ + AutoMigration(1, 2), + AutoMigration(2, 3, FileFlowDatabase.DeleteEColumnAV::class), + ], +) @TypeConverters(Converters::class) abstract class FileFlowDatabase : RoomDatabase() { abstract fun ruleDao(): RuleDao abstract fun executionDao(): ExecutionDao + @DeleteColumn("executions", "actionVerb") + class DeleteEColumnAV : AutoMigrationSpec + companion object { @Volatile private var instance: FileFlowDatabase? = null diff --git a/app/src/main/java/co/adityarajput/fileflow/data/Verb.kt b/app/src/main/java/co/adityarajput/fileflow/data/Verb.kt new file mode 100644 index 0000000..6342beb --- /dev/null +++ b/app/src/main/java/co/adityarajput/fileflow/data/Verb.kt @@ -0,0 +1,9 @@ +package co.adityarajput.fileflow.data + +import co.adityarajput.fileflow.R + +enum class Verb(val resource: Int) { + MOVE(R.string.move), + COPY(R.string.copy), + DELETE_STALE(R.string.delete_stale), +} diff --git a/app/src/main/java/co/adityarajput/fileflow/data/models/Action.kt b/app/src/main/java/co/adityarajput/fileflow/data/models/Action.kt index a28a165..d0786d2 100644 --- a/app/src/main/java/co/adityarajput/fileflow/data/models/Action.kt +++ b/app/src/main/java/co/adityarajput/fileflow/data/models/Action.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import co.adityarajput.fileflow.R +import co.adityarajput.fileflow.data.Verb import co.adityarajput.fileflow.utils.FileSuperlative import co.adityarajput.fileflow.utils.getGetDirectoryFromUri import co.adityarajput.fileflow.utils.toShortHumanReadableTime @@ -19,7 +20,7 @@ sealed class Action { abstract val srcFileNamePattern: String abstract val scanSubdirectories: Boolean - abstract val verb: Int + abstract val verb: Verb abstract val phrase: Int @Composable @@ -45,7 +46,7 @@ sealed class Action { val superlative: FileSuperlative = FileSuperlative.LATEST, val preserveStructure: Boolean = scanSubdirectories, ) : Action() { - override val verb get() = if (keepOriginal) R.string.copy else R.string.move + override val verb get() = if (keepOriginal) Verb.COPY else Verb.MOVE override val phrase = R.string.move_phrase @@ -73,7 +74,7 @@ sealed class Action { val retentionDays: Int = 30, override val scanSubdirectories: Boolean = false, ) : Action() { - override val verb get() = R.string.delete_stale + override val verb get() = Verb.DELETE_STALE override val phrase = R.string.delete_stale_phrase diff --git a/app/src/main/java/co/adityarajput/fileflow/data/models/Execution.kt b/app/src/main/java/co/adityarajput/fileflow/data/models/Execution.kt index 0a2e1d1..e3265e8 100644 --- a/app/src/main/java/co/adityarajput/fileflow/data/models/Execution.kt +++ b/app/src/main/java/co/adityarajput/fileflow/data/models/Execution.kt @@ -1,13 +1,16 @@ package co.adityarajput.fileflow.data.models +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import co.adityarajput.fileflow.data.Verb @Entity(tableName = "executions") data class Execution( val fileName: String, - val actionVerb: Int, + @ColumnInfo(defaultValue = "MOVE") + val verb: Verb, val timestamp: Long = System.currentTimeMillis(), diff --git a/app/src/main/java/co/adityarajput/fileflow/views/components/ImproperRulesetDialog.kt b/app/src/main/java/co/adityarajput/fileflow/views/components/ImproperRulesetDialog.kt index 34204de..fee0c6b 100644 --- a/app/src/main/java/co/adityarajput/fileflow/views/components/ImproperRulesetDialog.kt +++ b/app/src/main/java/co/adityarajput/fileflow/views/components/ImproperRulesetDialog.kt @@ -51,7 +51,7 @@ fun ImproperRulesetDialog( rulesToBeMigrated.forEach { Tile( it.action.srcFileNamePattern, - stringResource(it.action.verb), + stringResource(it.action.verb.resource), if (!it.enabled) stringResource(R.string.disabled) else pluralStringResource( R.plurals.execution, diff --git a/app/src/main/java/co/adityarajput/fileflow/views/screens/ExecutionsScreen.kt b/app/src/main/java/co/adityarajput/fileflow/views/screens/ExecutionsScreen.kt index a4d0b51..9f09f60 100644 --- a/app/src/main/java/co/adityarajput/fileflow/views/screens/ExecutionsScreen.kt +++ b/app/src/main/java/co/adityarajput/fileflow/views/screens/ExecutionsScreen.kt @@ -63,7 +63,7 @@ fun ExecutionsScreen( items(state.value.executions!!, { it.id }) { Tile( it.fileName, - stringResource(it.actionVerb), + stringResource(it.verb.resource), stringResource( R.string.ago, (System.currentTimeMillis() - it.timestamp).toShortHumanReadableTime(), diff --git a/app/src/main/java/co/adityarajput/fileflow/views/screens/RulesScreen.kt b/app/src/main/java/co/adityarajput/fileflow/views/screens/RulesScreen.kt index e132810..d787389 100644 --- a/app/src/main/java/co/adityarajput/fileflow/views/screens/RulesScreen.kt +++ b/app/src/main/java/co/adityarajput/fileflow/views/screens/RulesScreen.kt @@ -93,7 +93,7 @@ fun RulesScreen( items(state.value.rules!!, { it.id }) { Tile( it.action.srcFileNamePattern, - stringResource(it.action.verb), + stringResource(it.action.verb.resource), if (!it.enabled) stringResource(R.string.disabled) else pluralStringResource( R.plurals.execution, diff --git a/metadata/en-US/changelogs/4.txt b/metadata/en-US/changelogs/4.txt index 83f5223..6926094 100644 --- a/metadata/en-US/changelogs/4.txt +++ b/metadata/en-US/changelogs/4.txt @@ -4,6 +4,8 @@ Rules can now target protected folders (like "Download") if special "all files" access is granted. -The new "DELETE_STALE" action helps get rid of unused/untouched files. Note: "Staleness" is calculated based on when the file was last modified, not when it was last accessed. +The new "DELETE_STALE" action helps get rid of unused/untouched files. + +IMPORTANT: "Staleness" is calculated based on when the file was last modified, not when it was last accessed. Finally, rules can now recursively scan (and re-create) subfolders. From e1c4cc3d21ff84543bf55c7051012902ca20dd6b Mon Sep 17 00:00:00 2001 From: Aditya Rajput Date: Wed, 11 Mar 2026 10:56:42 +0530 Subject: [PATCH 2/2] Correctly constrain folder picker UI --- .../fileflow/views/components/FolderPickerBottomSheet.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/co/adityarajput/fileflow/views/components/FolderPickerBottomSheet.kt b/app/src/main/java/co/adityarajput/fileflow/views/components/FolderPickerBottomSheet.kt index 6808d9a..b0c2bfe 100644 --- a/app/src/main/java/co/adityarajput/fileflow/views/components/FolderPickerBottomSheet.kt +++ b/app/src/main/java/co/adityarajput/fileflow/views/components/FolderPickerBottomSheet.kt @@ -49,7 +49,9 @@ fun FolderPickerBottomSheet(viewModel: UpsertRuleViewModel) { ) HorizontalDivider() LazyColumn( - Modifier.padding(dimensionResource(R.dimen.padding_medium)), + Modifier + .weight(1f) + .padding(dimensionResource(R.dimen.padding_medium)), verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)), ) { if (currentDir.parentFile?.canRead() ?: false) {