Skip to content
Open
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
14 changes: 13 additions & 1 deletion app/src/main/java/com/philkes/notallyx/data/NotallyDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ abstract class NotallyDatabase : RoomDatabase() {
}
}

fun clearInstance(context: Context) {
val preferences = NotallyXPreferences.getInstance(context)
instance?.value?.biometricLockObserver?.let {
preferences.biometricLock.removeObserver(it)
}
instance?.value?.dataInPublicFolderObserver?.let {
preferences.dataInPublicFolder.removeObserver(it)
}
instance?.value?.close()
instance = null
}

private var testInstance: NotallyDatabase? = null

private fun getTestDatabase(context: ContextWrapper): NotallyDatabase {
Expand Down Expand Up @@ -162,7 +174,7 @@ abstract class NotallyDatabase : RoomDatabase() {
Migration8,
Migration9,
Migration10,
Migration11
Migration11,
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
System.loadLibrary("sqlcipher")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ interface BaseNoteDao {

@Insert suspend fun insert(baseNotes: List<BaseNote>): List<Long>

@Update suspend fun updateAll(baseNotes: List<BaseNote>)

@Update(entity = BaseNote::class) suspend fun update(labelsInBaseNotes: List<LabelsInBaseNote>)

@Query("SELECT COUNT(*) FROM BaseNote") fun count(): Int

@Query("DELETE FROM BaseNote") suspend fun deleteAll()

@Query("DELETE FROM BaseNote WHERE id = :id") suspend fun delete(id: Long)

@Query("DELETE FROM BaseNote WHERE id IN (:ids)") suspend fun delete(ids: LongArray)
Expand Down
48 changes: 38 additions & 10 deletions app/src/main/java/com/philkes/notallyx/data/dao/CommonDao.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.philkes.notallyx.data.dao

import android.util.Log
import androidx.room.Dao
import androidx.room.Transaction
import com.philkes.notallyx.data.NotallyDatabase
Expand Down Expand Up @@ -46,7 +47,11 @@ abstract class CommonDao(private val database: NotallyDatabase) {
}

@Transaction
open suspend fun importBackup(baseNotes: List<BaseNote>, labels: List<Label>): ImportResult {
open suspend fun importBackup(
baseNotes: List<BaseNote>,
labels: List<Label>,
readCorrupted: Int,
): ImportResult {
val dao = database.getBaseNoteDao()
// Insert notes, splitting oversized text notes instead of truncating
var insertedCount = 0
Expand All @@ -70,7 +75,11 @@ abstract class CommonDao(private val database: NotallyDatabase) {
labelDao.insert(
labels.mapIndexed { index, label -> Label(label.value, maxOrder + 1 + index) }
)
return ImportResult(inserted = insertedCount, duplicates = duplicates)
return ImportResult(
inserted = insertedCount,
duplicates = duplicates,
corruptedNotes = readCorrupted,
)
}

/**
Expand All @@ -83,14 +92,15 @@ abstract class CommonDao(private val database: NotallyDatabase) {
baseNotes: List<BaseNote>,
originalIds: List<Long>,
labels: List<Label>,
readCorrupted: Int,
): ImportResult {
val baseNoteDao = database.getBaseNoteDao()

// 1) Insert notes with splitting; build mapping from original id -> first-part new id
val idMap = HashMap<Long, Long>(originalIds.size)
// Keep all inserted note ids with their spans for remapping pass
val insertedParts = ArrayList<Pair<Long, List<SpanRepresentation>>>()

var corrupted = readCorrupted
Comment thread
coderabbitai[bot] marked this conversation as resolved.
var insertedCount = 0
var duplicates = 0
for (i in baseNotes.indices) {
Expand All @@ -105,11 +115,21 @@ abstract class CommonDao(private val database: NotallyDatabase) {
continue
}
val (firstId, parts) =
if (original.type == Type.NOTE && original.body.length > MAX_BODY_CHAR_LENGTH) {
NoteSplitUtils.splitAndInsertForImport(original, baseNoteDao)
} else {
val newId = baseNoteDao.insert(original.copy(id = 0))
Pair(newId, listOf(Pair(newId, original.spans)))
try {
if (original.type == Type.NOTE && original.body.length > MAX_BODY_CHAR_LENGTH) {
NoteSplitUtils.splitAndInsertForImport(original, baseNoteDao)
} else {
val newId = baseNoteDao.insert(original.copy(id = 0))
Pair(newId, listOf(Pair(newId, original.spans)))
}
} catch (e: Exception) {
Log.e(
"Import",
"Failed to import note ${original.title} (id: ${original.id}), skipping it as corrupted",
e,
)
corrupted++
continue
}
val oldId = originalIds.getOrNull(i)
if (oldId != null) idMap[oldId] = firstId
Expand All @@ -134,7 +154,11 @@ abstract class CommonDao(private val database: NotallyDatabase) {
} else span
}
if (changed) {
baseNoteDao.updateSpans(noteId, updated)
try {
baseNoteDao.updateSpans(noteId, updated)
} catch (e: Exception) {
Log.e("Import", "Failed to update spans for noteId: ${noteId})", e)
}
}
}

Expand All @@ -143,7 +167,11 @@ abstract class CommonDao(private val database: NotallyDatabase) {
labelDaoForRemap.insert(
labels.mapIndexed { index, label -> Label(label.value, maxOrderForRemap + 1 + index) }
)
return ImportResult(inserted = insertedCount, duplicates = duplicates)
return ImportResult(
inserted = insertedCount,
duplicates = duplicates,
corruptedNotes = corrupted,
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Application
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.presentation.view.misc.Progress
import java.io.File

interface ExternalImporter {
Expand All @@ -18,6 +19,6 @@ interface ExternalImporter {
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>? = null,
progress: MutableLiveData<Progress>? = null,
): Pair<List<BaseNote>, File?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.dao.BaseNoteDao.Companion.MAX_BODY_CHAR_LENGTH
import com.philkes.notallyx.data.imports.evernote.EvernoteImporter
import com.philkes.notallyx.data.imports.google.GoogleKeepImporter
import com.philkes.notallyx.data.imports.json.JsonImporter
import com.philkes.notallyx.data.imports.quillpad.QuillpadImporter
import com.philkes.notallyx.data.imports.txt.JsonImporter
import com.philkes.notallyx.data.imports.txt.PlainTextImporter
import com.philkes.notallyx.data.model.Audio
import com.philkes.notallyx.data.model.FileAttachment
import com.philkes.notallyx.data.model.Label
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.data.model.toText
import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.presentation.viewmodel.NotallyModel
import com.philkes.notallyx.utils.MIME_TYPE_ZIP
import com.philkes.notallyx.utils.NoteSplitUtils
Expand All @@ -27,14 +28,14 @@ import com.philkes.notallyx.utils.backup.importImage
import java.io.File
import java.util.concurrent.atomic.AtomicInteger

data class ImportResult(val inserted: Int, val duplicates: Int)
data class ImportResult(val inserted: Int, val duplicates: Int, val corruptedNotes: Int)

class NotesImporter(private val app: Application, private val database: NotallyDatabase) {

suspend fun import(
uri: Uri,
importSource: ImportSource,
progress: MutableLiveData<ImportProgress>? = null,
progress: MutableLiveData<Progress>? = null,
): ImportResult {
val tempDir = File(app.cacheDir, IMPORT_CACHE_FOLDER)
if (!tempDir.exists()) {
Expand Down Expand Up @@ -80,24 +81,35 @@ class NotesImporter(private val app: Application, private val database: NotallyD
// Insert notes with split handling for oversized text notes, skipping duplicates
val dao = database.getBaseNoteDao()
var insertedCount = 0
var corruptedCount = 0
val totalCandidates = notes.size
notes.forEach { note ->
val dup = findDuplicateId(note)
if (dup == null) {
if (note.type == Type.NOTE && note.body.length > MAX_BODY_CHAR_LENGTH) {
// Split into parts, preserving spans and adding navigation links
NoteSplitUtils.splitAndInsertForImport(note, dao)
} else {
// Regular insert; ensure id is auto-generated
dao.insert(note.copy(id = 0))
try {
if (note.type == Type.NOTE && note.body.length > MAX_BODY_CHAR_LENGTH) {
// Split into parts, preserving spans and adding navigation links
NoteSplitUtils.splitAndInsertForImport(note, dao)
} else {
// Regular insert; ensure id is auto-generated
dao.insert(note.copy(id = 0))
}
insertedCount++
} catch (e: Exception) {
Log.e(
"Import",
"Failed to import note ${note.title} (id: ${note.id}), skipping it as corrupted",
e,
)
corruptedCount++
}
insertedCount++
}
}
progress?.postValue(ImportProgress(inProgress = false))
return ImportResult(
inserted = insertedCount,
duplicates = (totalCandidates - insertedCount),
duplicates = (totalCandidates - insertedCount - corruptedCount),
corruptedNotes = corruptedCount,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} finally {
tempDir.deleteRecursively()
Expand All @@ -108,7 +120,7 @@ class NotesImporter(private val app: Application, private val database: NotallyD
files: List<FileAttachment>,
sourceFolder: File,
fileType: NotallyModel.FileType,
progress: MutableLiveData<ImportProgress>?,
progress: MutableLiveData<Progress>?,
total: Int?,
counter: AtomicInteger?,
) {
Expand Down Expand Up @@ -136,7 +148,7 @@ class NotesImporter(private val app: Application, private val database: NotallyD
private suspend fun importAudios(
audios: List<Audio>,
sourceFolder: File,
progress: MutableLiveData<ImportProgress>?,
progress: MutableLiveData<Progress>?,
totalFiles: Int,
counter: AtomicInteger,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.data.model.NoteViewMode
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.utils.log
import com.philkes.notallyx.utils.startsWithAnyOf
import com.philkes.notallyx.utils.write
Expand All @@ -39,7 +40,7 @@ class EvernoteImporter : ExternalImporter {
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>?,
progress: MutableLiveData<Progress>?,
): Pair<List<BaseNote>, File> {
progress?.postValue(ImportProgress(indeterminate = true))
if (MimeTypeMap.getFileExtensionFromUrl(source.toString()) != "enex") {
Expand Down Expand Up @@ -81,7 +82,7 @@ class EvernoteImporter : ExternalImporter {
app: Application,
resources: Collection<EvernoteResource>,
dir: File,
progress: MutableLiveData<ImportProgress>? = null,
progress: MutableLiveData<Progress>? = null,
) {
progress?.postValue(
ImportProgress(total = resources.size, stage = ImportStage.EXTRACT_FILES)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.data.model.NoteViewMode
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.utils.listFilesRecursive
import com.philkes.notallyx.utils.log
import java.io.File
Expand All @@ -41,7 +42,7 @@ class GoogleKeepImporter : ExternalImporter {
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>?,
progress: MutableLiveData<Progress>?,
): Pair<List<BaseNote>, File> {
progress?.postValue(ImportProgress(indeterminate = true, stage = ImportStage.EXTRACT_FILES))
val dataFolder =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.philkes.notallyx.data.imports.txt
package com.philkes.notallyx.data.imports.json

import android.app.Application
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.data.imports.ExternalImporter
import com.philkes.notallyx.data.imports.ImportProgress
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.toBaseNote
import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.utils.MIME_TYPE_JSON
import java.io.BufferedReader
import java.io.File
Expand All @@ -19,7 +19,7 @@ class JsonImporter : ExternalImporter {
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>?,
progress: MutableLiveData<Progress>?,
): Pair<List<BaseNote>, File?> {
val notes = mutableListOf<BaseNote>()
fun readJsonFiles(file: DocumentFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.data.model.NoteViewMode
import com.philkes.notallyx.data.model.Reminder
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.utils.getMimeType
import com.philkes.notallyx.utils.moveAllFiles
import com.philkes.notallyx.utils.toMillis
Expand Down Expand Up @@ -45,7 +46,7 @@ class QuillpadImporter : ExternalImporter {
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>?,
progress: MutableLiveData<Progress>?,
): Pair<List<BaseNote>, File> {
progress?.postValue(ImportProgress(indeterminate = true, stage = ImportStage.EXTRACT_FILES))
val dataFolder =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.data.imports.ExternalImporter
import com.philkes.notallyx.data.imports.ImportProgress
import com.philkes.notallyx.data.imports.markdown.parseBodyAndSpansFromMarkdown
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.data.model.NoteViewMode
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.presentation.viewmodel.ExportMimeType
import com.philkes.notallyx.utils.MIME_TYPE_JSON
import com.philkes.notallyx.utils.log
Expand All @@ -24,7 +24,7 @@ class PlainTextImporter : ExternalImporter {
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>?,
progress: MutableLiveData<Progress>?,
): Pair<List<BaseNote>, File?> {
val notes = mutableListOf<BaseNote>()
fun readTxtFiles(file: DocumentFile) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.philkes.notallyx.data.model

import android.app.Dialog
import androidx.lifecycle.MutableLiveData
import java.util.Collections
import java.util.concurrent.atomic.AtomicBoolean

/** Helper to register Converter errors while accessing the database. */
object ConverterErrorReporter {
val enabled = AtomicBoolean(true)
val errors = MutableLiveData<Throwable?>(null)
val activeDialogs = Collections.synchronizedSet(mutableSetOf<Dialog>())

fun reportError(throwable: Throwable) {
if (enabled.get() && errors.value == null) {
Comment thread
Crustack marked this conversation as resolved.
errors.postValue(throwable)
}
}

fun registerDialog(dialog: Dialog) {
activeDialogs.add(dialog)
// Auto-remove when it's dismissed naturally
dialog.setOnDismissListener { activeDialogs.remove(dialog) }
}

fun dismissAllDialogs() {
activeDialogs.toList().forEach { it.dismiss() }
synchronized(activeDialogs) { activeDialogs.clear() }
errors.postValue(null)
}
Comment thread
Crustack marked this conversation as resolved.
}
Loading