Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#8: Separate user-defined properties into dedicated storage #18

Merged
merged 14 commits into from
Oct 25, 2023
9 changes: 2 additions & 7 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,8 @@ dependencies {
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'

//implementation 'com.github.ARK-Builders:ark-filepicker:b2bfa01ea7'

implementation 'com.github.ark-builders:ark-filepicker:b2bfa01ea7'
implementation 'space.taran:arklib:0.1.0-SNAPSHOT-7df9a4e581'

implementation 'ch.acra:acra-http:5.9.5'
implementation 'ch.acra:acra-dialog:5.9.5'
implementation 'dev.arkbuilders:arkfilepicker:0.1.1'
implementation 'dev.arkbuilders:arklib:0.3.3'

implementation 'androidx.preference:preference:1.2.0'
implementation "com.google.dagger:hilt-android:2.42"
Expand Down
11 changes: 8 additions & 3 deletions app/src/main/java/space/taran/arkmemo/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package space.taran.arkmemo
import android.app.Application
import android.content.Context
import dagger.hilt.android.HiltAndroidApp
import dev.arkbuilders.arklib.initArkLib
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand All @@ -11,13 +12,17 @@ import org.acra.config.httpSender
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import dev.arkbuilders.arkfilepicker.folders.FoldersRepo
import space.taran.arkmemo.space.taran.arkmemo.utils.Config

@HiltAndroidApp
class App: Application() {

override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
override fun onCreate() {
super.onCreate()
System.loadLibrary("arklib")
initArkLib()
FoldersRepo.init(this)
initAcra()
}

Expand All @@ -41,4 +46,4 @@ class App: Application() {
}
}
}
}
}
3 changes: 2 additions & 1 deletion app/src/main/java/space/taran/arkmemo/data/ResourceMeta.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package space.taran.arkmemo.data

import dev.arkbuilders.arklib.ResourceId
import java.nio.file.attribute.FileTime

data class ResourceMeta(
val id: Long,
val id: ResourceId,
val name: String,
val extension: String,
val modified: FileTime,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package space.taran.arkmemo.data.repositories

import android.util.Log
import dev.arkbuilders.arklib.computeId
import dev.arkbuilders.arklib.data.index.RootIndex
import dev.arkbuilders.arklib.user.properties.Properties
import dev.arkbuilders.arklib.user.properties.PropertiesStorage
import dev.arkbuilders.arklib.user.properties.PropertiesStorageRepo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.job
import kotlinx.coroutines.withContext
import space.taran.arkmemo.data.ResourceMeta
import space.taran.arkmemo.models.TextNote
import java.nio.file.Files
import java.nio.file.Path
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.io.path.deleteIfExists
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.io.path.fileSize
import kotlin.io.path.forEachLine
import kotlin.io.path.getLastModifiedTime
import kotlin.io.path.name
import kotlin.io.path.writeLines

@Singleton
class TextNotesRepo @Inject constructor() {

private val _textNotes = MutableStateFlow(listOf<TextNote>())
val textNotes: StateFlow<List<TextNote>> = _textNotes
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not keep state in repository layer. Repsitory's responsibility is containing logic to access to which data source. Please move the state to ViewModel and perform update the state itself there


private val iODispatcher = Dispatchers.IO

private lateinit var propertiesStorage: PropertiesStorage
private lateinit var propertiesStorageRepo: PropertiesStorageRepo

private lateinit var root: Path

suspend fun init(root: Path, scope: CoroutineScope) {
this.root = root
propertiesStorageRepo = PropertiesStorageRepo(scope)
propertiesStorage = propertiesStorageRepo.provide(RootIndex.provide(root))
}

suspend fun save(note: TextNote?) {
if (note != null) {
add(note)
write(note)
}
}
shubertm marked this conversation as resolved.
Show resolved Hide resolved

suspend fun delete(note: TextNote) = withContext(iODispatcher) {
val path = root.resolve("${note.meta?.name}")
remove(note)
delete(path)
propertiesStorage.remove(note.meta?.id!!)
propertiesStorage.persist()
Log.d("text-repo", "${note.meta?.name!!} has been deleted")
}

suspend fun read() = withContext(Dispatchers.IO) {
val notes = mutableListOf<TextNote>()
Files.list(root).forEach { path ->
if (path.fileName.extension == NOTE_EXT) {
try {
val data = StringBuilder()
path.forEachLine {
data.appendLine(it)
}
val size = path.fileSize()
val id = computeId(size, path)
val meta = ResourceMeta(
id,
path.fileName.name,
path.extension,
path.getLastModifiedTime(),
size
)
val titles = propertiesStorage.getProperties(id).titles
val content = TextNote.Content(titles.elementAt(0), data.toString())
val note = TextNote(content, meta)
notes.add(note)
} catch (e: Exception) {
e.printStackTrace()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exceptions can occur here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial implementation was throwing IOException, since we changed to new kotlin extension function forEachLine I think we can remove the block

}
}
Log.d("text-repo", "${notes.size} text note resources found")
_textNotes.value = notes
}

private suspend fun write(note: TextNote) = withContext(Dispatchers.IO) {
shubertm marked this conversation as resolved.
Show resolved Hide resolved
val path = root.resolve("${DUMMY_FILENAME}.${NOTE_EXT}")
if (!path.exists()) {
try {
val lines = note.content.data.split(NEWLINE)
path.writeLines(lines)
val size = path.fileSize()
val id = computeId(size, path)
val properties = Properties(setOf(note.content.title), setOf())

Log.d("text-repo", "initial resource name ${path.name}")

propertiesStorage.setProperties(id, properties)
propertiesStorage.persist()

val newPath = root.resolve("$id.$NOTE_EXT")
if (!newPath.exists()) {
if (path.toFile().renameTo(newPath.toFile())) {
kirillt marked this conversation as resolved.
Show resolved Hide resolved
note.meta = ResourceMeta(
id,
newPath.fileName.name,
newPath.extension,
newPath.getLastModifiedTime(),
size
)
Log.d("notes-repo", "resource renamed to ${newPath.name} successfully")
} else delete(path)
}
} catch (e: Exception) {
e.printStackTrace()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can any exceptions really occur here? If try-catch is necessary, then better to log at warning level, not just print.

@hieuwu Please correct me if I'm wrong.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path.deleteIfExists() inside delete method will throw IOException. I think that's why he wrap it in try catch block.

}
this.coroutineContext.job
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we return an job object when we don't do anything with it? Using an Unit function is good enough in this case

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using it locally, just forgot to remove it :)

}

private fun delete(path: Path) {
path.deleteIfExists()
}

private fun add(note: TextNote) {
val notes = textNotes.value.toMutableList()
notes.add(note)
_textNotes.value = notes
}

private fun remove(note: TextNote) {
val notes = textNotes.value.toMutableList()
notes.remove(note)
_textNotes.value = notes
}
}

private const val NOTE_EXT = "note"
private const val DUMMY_FILENAME = "Note"
private const val NEWLINE = "\n"

This file was deleted.

Loading