From 22d289d6cb2a2fe63b9a6b8d6e8c1832bda7db65 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 7 Apr 2026 04:12:22 -0500 Subject: [PATCH] Replace java.io.File in ExportCSVTask with UserFile abstraction --- .../org/isoron/uhabits/activities/HabitsDirFinder.kt | 6 ++++-- .../kotlin/org/isoron/platform/io/Files.kt | 6 ++++++ .../org/isoron/uhabits/core/tasks/ExportCSVTask.kt | 12 +++++------- .../ui/screens/habits/list/ListHabitsBehavior.kt | 3 ++- .../ui/screens/habits/show/ShowHabitMenuPresenter.kt | 3 ++- .../jvmMain/java/org/isoron/platform/io/JavaFiles.kt | 4 ++++ .../ui/screens/habits/list/ListHabitsBehaviorTest.kt | 5 +++-- .../habits/show/ShowHabitMenuPresenterTest.kt | 3 ++- 8 files changed, 28 insertions(+), 14 deletions(-) rename uhabits-core/src/{jvmMain/java => commonMain/kotlin}/org/isoron/uhabits/core/tasks/ExportCSVTask.kt (85%) rename uhabits-core/src/{jvmMain/java => commonMain/kotlin}/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt (98%) rename uhabits-core/src/{jvmMain/java => commonMain/kotlin}/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt (98%) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt index 293246ff..a538256b 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt @@ -19,6 +19,8 @@ package org.isoron.uhabits.activities import me.tatarka.inject.annotations.Inject +import org.isoron.platform.io.JavaUserFile +import org.isoron.platform.io.UserFile import org.isoron.uhabits.AndroidDirFinder import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter @@ -28,7 +30,7 @@ class HabitsDirFinder( private val androidDirFinder: AndroidDirFinder ) : ShowHabitMenuPresenter.System, ListHabitsBehavior.DirFinder { - override fun getCSVOutputDir(): String { - return androidDirFinder.getFilesDir("CSV")!!.absolutePath + override fun getCSVOutputDir(): UserFile { + return JavaUserFile(androidDirFinder.getFilesDir("CSV")!!.toPath()) } } diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/io/Files.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/io/Files.kt index f24ea153..018ae484 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/io/Files.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/io/Files.kt @@ -87,6 +87,12 @@ interface UserFile { * Reads the first [limit] bytes from the file. */ suspend fun readBytes(limit: Int): ByteArray + + /** + * Returns a [UserFile] whose path is [child] resolved against this file's + * parent directory (or this file itself, if it represents a directory). + */ + fun resolve(child: String): UserFile } /** diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/tasks/ExportCSVTask.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/ExportCSVTask.kt similarity index 85% rename from uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/tasks/ExportCSVTask.kt rename to uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/ExportCSVTask.kt index 025fd17f..0cb804de 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/tasks/ExportCSVTask.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/tasks/ExportCSVTask.kt @@ -19,16 +19,16 @@ package org.isoron.uhabits.core.tasks import kotlinx.coroutines.runBlocking +import org.isoron.platform.io.UserFile import org.isoron.platform.time.getToday import org.isoron.uhabits.core.io.HabitsCSVExporter import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.HabitList -import java.io.File class ExportCSVTask( private val habitList: HabitList, private val selectedHabits: List, - private val outputDir: String, + private val outputDir: UserFile, private val listener: ExportCSVListener ) : Task { private var archiveFilename: String? = null @@ -37,11 +37,9 @@ class ExportCSVTask( val exporter = HabitsCSVExporter(habitList, selectedHabits) val bytes = runBlocking { exporter.writeArchive() } val date = getToday().toCSVString() - val dir = File(outputDir) - dir.mkdirs() - val zipFile = File(dir, "Loop Habits CSV $date.zip") - zipFile.writeBytes(bytes) - archiveFilename = zipFile.absolutePath + val zipFile = outputDir.resolve("Loop Habits CSV $date.zip") + runBlocking { zipFile.writeBytes(bytes) } + archiveFilename = zipFile.pathString } catch (e: Exception) { e.printStackTrace() } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt similarity index 98% rename from uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt rename to uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt index b4d460bb..6ef0b4dd 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.core.ui.screens.habits.list import me.tatarka.inject.annotations.Inject +import org.isoron.platform.io.UserFile import org.isoron.platform.time.LocalDate import org.isoron.platform.time.getToday import org.isoron.uhabits.core.commands.CommandRunner @@ -151,7 +152,7 @@ open class ListHabitsBehavior( } interface DirFinder { - fun getCSVOutputDir(): String + fun getCSVOutputDir(): UserFile } interface Screen { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt similarity index 98% rename from uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt rename to uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt index 477f5bb3..6300fbe4 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt @@ -18,6 +18,7 @@ */ package org.isoron.uhabits.core.ui.screens.habits.show +import org.isoron.platform.io.UserFile import org.isoron.platform.time.getToday import org.isoron.uhabits.core.commands.ArchiveHabitsCommand import org.isoron.uhabits.core.commands.CommandRunner @@ -126,6 +127,6 @@ class ShowHabitMenuPresenter( } interface System { - fun getCSVOutputDir(): String + fun getCSVOutputDir(): UserFile } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaFiles.kt b/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaFiles.kt index d4d2bcde..ac8bd0f9 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaFiles.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaFiles.kt @@ -99,6 +99,10 @@ class JavaUserFile(val path: Path) : UserFile { return if (n <= 0) ByteArray(0) else buf.copyOf(n) } } + + override fun resolve(child: String): UserFile { + return JavaUserFile(path.resolve(child)) + } } @Suppress("NewApi") diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt index 858b0302..0aa1459e 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt @@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.list import org.apache.commons.io.FileUtils import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.core.IsEqual.equalTo +import org.isoron.platform.io.JavaUserFile import org.isoron.platform.time.getToday import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.models.Entry @@ -92,7 +93,7 @@ class ListHabitsBehaviorTest : BaseUnitTest() { @Throws(Exception::class) fun testOnExportCSV() { val outputDir = Files.createTempDirectory("CSV").toFile() - whenever(dirFinder.getCSVOutputDir()).thenReturn(outputDir.absolutePath) + whenever(dirFinder.getCSVOutputDir()).thenReturn(JavaUserFile(outputDir.toPath())) behavior.onExportCSV() verify(screen).showSendFileScreen(any()) assertThat(FileUtils.listFiles(outputDir, null, false).size, equalTo(1)) @@ -104,7 +105,7 @@ class ListHabitsBehaviorTest : BaseUnitTest() { fun testOnExportCSV_fail() { val outputDir = Files.createTempDirectory("CSV").toFile() outputDir.setWritable(false) - whenever(dirFinder.getCSVOutputDir()).thenReturn(outputDir.absolutePath) + whenever(dirFinder.getCSVOutputDir()).thenReturn(JavaUserFile(outputDir.toPath())) behavior.onExportCSV() verify(screen).showMessage(ListHabitsBehavior.Message.COULD_NOT_EXPORT) assertTrue(outputDir.delete()) diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.kt index ff042936..a4ca3022 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.kt @@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show import org.apache.commons.io.FileUtils import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat +import org.isoron.platform.io.JavaUserFile import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.models.Habit import org.junit.Test @@ -61,7 +62,7 @@ class ShowHabitMenuPresenterTest : BaseUnitTest() { @Throws(Exception::class) fun testOnExport() { val outputDir = Files.createTempDirectory("CSV").toFile() - whenever(system.getCSVOutputDir()).thenReturn(outputDir.absolutePath) + whenever(system.getCSVOutputDir()).thenReturn(JavaUserFile(outputDir.toPath())) menu.onExportCSV() assertThat(FileUtils.listFiles(outputDir, null, false).size, equalTo(1)) FileUtils.deleteDirectory(outputDir)