Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ FileFlow scans your files periodically and organizes them according to your rule
- **Actions** - Choose what to do to your files ⚙
1. Copy, move, or rename files 📁
2. Delete stale files 🗑
- **Schedule** - Choose when and how often rules run ⏰
- **History** - Recent executions are stored (locally) ⏳
- **Free, open-source & private**
- No ads, subscriptions, or in-app purchases 🆓
Expand Down
5 changes: 3 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ android {
applicationId = "co.adityarajput.fileflow"
minSdk = 29
targetSdk = 36
versionCode = 5
versionName = "1.3.0"
versionCode = 6
versionName = "1.4.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -93,6 +93,7 @@ dependencies {
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
implementation(libs.aboutlibraries.compose)
implementation(libs.cron.utils)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
92 changes: 92 additions & 0 deletions app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "fe2af7f88a055f816417fe0ebace9f2a",
"entities": [
{
"tableName": "rules",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`action` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `executions` INTEGER NOT NULL, `interval` INTEGER DEFAULT 3600000, `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": "interval",
"columnName": "interval",
"affinity": "INTEGER",
"defaultValue": "3600000"
},
{
"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, 'fe2af7f88a055f816417fe0ebace9f2a')"
]
}
}
98 changes: 98 additions & 0 deletions app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "5fa205cd80eec0e4d09c32130309c044",
"entities": [
{
"tableName": "rules",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`action` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `executions` INTEGER NOT NULL, `interval` INTEGER DEFAULT 3600000, `cronString` TEXT DEFAULT 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": "interval",
"columnName": "interval",
"affinity": "INTEGER",
"defaultValue": "3600000"
},
{
"fieldPath": "cronString",
"columnName": "cronString",
"affinity": "TEXT",
"defaultValue": "NULL"
},
{
"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, '5fa205cd80eec0e4d09c32130309c044')"
]
}
}
9 changes: 8 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
android:allowBackup="${allowBackup}"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:requestLegacyExternalStorage="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name_launcher"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FileFlow">
Expand All @@ -35,5 +35,12 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".AlarmReceiver"
android:exported="false">
<intent-filter>
<action android:name="co.adityarajput.fileflow.EXECUTE_RULE" />
</intent-filter>
</receiver>
</application>
</manifest>
35 changes: 35 additions & 0 deletions app/src/main/java/co/adityarajput/fileflow/AlarmReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package co.adityarajput.fileflow

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import co.adityarajput.fileflow.data.AppContainer
import co.adityarajput.fileflow.data.models.Execution
import co.adityarajput.fileflow.utils.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Logger.d("AlarmReceiver", "Received intent with action: ${intent.action}")

if (intent.action == Constants.ACTION_EXECUTE_RULE) {
CoroutineScope(Dispatchers.IO).launch {
val repository = AppContainer(context).repository
val rule = repository.rule(intent.getIntExtra(Constants.EXTRA_RULE_ID, -1))

if (rule == null || !rule.enabled)
return@launch

Logger.d("Worker", "Executing $rule")
rule.action.execute(context) {
repository.registerExecution(
rule,
Execution(it, rule.action.verb),
)
}
}
}
}
}
5 changes: 5 additions & 0 deletions app/src/main/java/co/adityarajput/fileflow/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ object Constants {
const val BRIGHTNESS = "brightness"

const val WORKER_NAME = "fileflow_worker"
const val ACTION_EXECUTE_RULE = "co.adityarajput.fileflow.EXECUTE_RULE"
const val EXTRA_RULE_ID = "extra_rule_id"
const val MAX_CRON_EXECUTIONS_PER_HOUR = 4

const val LOG_SIZE = 100

const val ONE_HOUR_IN_MILLIS = 3_600_000L
}
21 changes: 7 additions & 14 deletions app/src/main/java/co/adityarajput/fileflow/FileFlowApplication.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package co.adityarajput.fileflow

import android.app.Application
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import co.adityarajput.fileflow.data.AppContainer
import co.adityarajput.fileflow.services.Worker
import co.adityarajput.fileflow.utils.isDebugBuild
import java.util.concurrent.TimeUnit
import co.adityarajput.fileflow.utils.scheduleWork
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class FileFlowApplication : Application() {
lateinit var container: AppContainer
Expand All @@ -22,14 +21,8 @@ class FileFlowApplication : Application() {
container.seedDemoData()
}

WorkManager.getInstance(this).enqueueUniquePeriodicWork(
Constants.WORKER_NAME,
ExistingPeriodicWorkPolicy.KEEP,
PeriodicWorkRequestBuilder<Worker>(
// INFO: While debugging, use a shorter interval
if (isDebugBuild()) 15 else 60,
TimeUnit.MINUTES,
).build(),
)
CoroutineScope(Dispatchers.IO).launch {
scheduleWork()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ class AppContainer(private val context: Context) {

fun seedDemoData() {
runBlocking {
if (
repository.rules().first().isEmpty() &&
repository.executions().first().isEmpty()
) {
if (repository.rules().first().isEmpty()) {
repository.upsert(
Rule(
Action.MOVE(
Expand All @@ -31,6 +28,8 @@ class AppContainer(private val context: Context) {
overwriteExisting = true,
),
executions = 2,
interval = null,
cronString = "00 10 * * 0",
),
Rule(
Action.MOVE(
Expand All @@ -42,6 +41,7 @@ class AppContainer(private val context: Context) {
overwriteExisting = true,
),
executions = 3,
interval = null,
),
Rule(
Action.DELETE_STALE(
Expand All @@ -50,6 +50,7 @@ class AppContainer(private val context: Context) {
scanSubdirectories = true,
),
enabled = false,
interval = 86_400_000,
),
)
repository.upsert(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import co.adityarajput.fileflow.data.models.Rule

@Database(
[Rule::class, Execution::class],
version = 3,
version = 5,
autoMigrations = [
AutoMigration(1, 2),
AutoMigration(2, 3, FileFlowDatabase.DeleteEColumnAV::class),
AutoMigration(3, 4),
AutoMigration(4, 5),
],
)
@TypeConverters(Converters::class)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/co/adityarajput/fileflow/data/Repository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class Repository(

fun rules() = ruleDao.list()

fun rule(id: Int) = ruleDao.get(id)

fun executions() = executionDao.list()

suspend fun registerExecution(rule: Rule, execution: Execution) {
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/co/adityarajput/fileflow/data/RuleDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ interface RuleDao {
@Query("SELECT * from rules ORDER BY id ASC")
fun list(): Flow<List<Rule>>

@Query("SELECT * from rules WHERE id = :id")
fun get(id: Int): Rule?

@Query("UPDATE rules SET executions = executions + 1 WHERE id = :id")
suspend fun registerExecution(id: Int)

Expand Down
Loading
Loading