Show Import progress as Dialog

This commit is contained in:
PhilKes 2024-10-29 17:40:29 +01:00
parent df8d91e0f6
commit 014b4f5f11
39 changed files with 457 additions and 231 deletions

View file

@ -1,5 +1,4 @@
import com.ncorti.ktfmt.gradle.tasks.KtfmtFormatTask
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
@ -120,8 +119,11 @@ dependencies {
implementation "com.github.bumptech.glide:glide:4.15.1"
implementation "com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0"
implementation "com.google.code.findbugs:jsr305:3.0.2"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
implementation "org.simpleframework:simple-xml:2.7.1"
implementation("org.simpleframework:simple-xml:2.7.1") {
exclude group: 'xpp3', module: 'xpp3'
}
implementation 'org.jsoup:jsoup:1.18.1'
testImplementation "junit:junit:4.13.2"

View file

@ -22,5 +22,28 @@
-keep class ** extends androidx.navigation.Navigator
-keep class ** implements org.ocpsoft.prettytime.TimeUnit
# SQLCipher
-keep class net.sqlcipher.** { *; }
-keep class net.sqlcipher.database.** { *; }
-keep class net.sqlcipher.database.** { *; }
# SimpleXML
-keepattributes Signature
-keepattributes *Annotation
-keep interface org.simpleframework.xml.core.Label {
public *;
}
-keep class * implements org.simpleframework.xml.core.Label {
public *;
}
-keep interface org.simpleframework.xml.core.Parameter {
public *;
}
-keep class * implements org.simpleframework.xml.core.Parameter {
public *;
}
-keep interface org.simpleframework.xml.core.Extractor {
public *;
}
-keep class * implements org.simpleframework.xml.core.Extractor {
public *;
}

View file

@ -2,10 +2,21 @@ package com.philkes.notallyx.data.imports
import android.app.Application
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.data.model.BaseNote
import java.io.File
interface ExternalImporter {
fun importFrom(uri: Uri, app: Application): Pair<List<BaseNote>, File>
/**
* Parses [BaseNote]s from [source] and copies attached files/images/audios to [destination]
*
* @return List of [BaseNote]s to import + folder containing attached files
*/
fun import(
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>? = null,
): Pair<List<BaseNote>, File>
}

View file

@ -1,3 +1,3 @@
package com.philkes.notallyx.data.imports
class ImportException(val textResId: Int, cause: Throwable? = null) : RuntimeException(cause)
class ImportException(val textResId: Int, cause: Throwable) : RuntimeException(cause)

View file

@ -0,0 +1,17 @@
package com.philkes.notallyx.data.imports
import com.philkes.notallyx.presentation.view.misc.Progress
open class ImportProgress(
current: Int = 0,
total: Int = 0,
inProgress: Boolean = true,
indeterminate: Boolean = false,
val stage: ImportStage = ImportStage.IMPORT_NOTES,
) : Progress(current, total, inProgress, indeterminate)
enum class ImportStage {
IMPORT_NOTES,
EXTRACT_FILES,
IMPORT_FILES,
}

View file

@ -4,45 +4,83 @@ import android.app.Application
import android.net.Uri
import android.util.Log
import androidx.core.net.toUri
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.R
import com.philkes.notallyx.data.DataUtil
import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.imports.evernote.EvernoteImporter
import com.philkes.notallyx.data.imports.google.GoogleKeepImporter
import com.philkes.notallyx.data.model.Audio
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.FileAttachment
import com.philkes.notallyx.data.model.Label
import com.philkes.notallyx.presentation.viewmodel.NotallyModel
import java.io.File
import java.util.concurrent.atomic.AtomicInteger
class NotesImporter(private val app: Application, private val database: NotallyDatabase) {
suspend fun import(uri: Uri, importSource: ImportSource) {
val (notes, importDataFolder) =
when (importSource) {
ImportSource.GOOGLE_KEEP -> GoogleKeepImporter().importFrom(uri, app)
ImportSource.EVERNOTE -> EvernoteImporter().importFrom(uri, app)
}
database.getLabelDao().insert(notes.flatMap { it.labels }.distinct().map { Label(it) })
importFiles(
notes.flatMap { it.files }.distinct(),
importDataFolder,
NotallyModel.FileType.ANY,
)
importFiles(
notes.flatMap { it.images }.distinct(),
importDataFolder,
NotallyModel.FileType.IMAGE,
)
importAudios(notes.flatMap { it.audios }.distinct(), importDataFolder)
database.getBaseNoteDao().insert(notes)
suspend fun import(
uri: Uri,
importSource: ImportSource,
progress: MutableLiveData<ImportProgress>? = null,
): Int {
val tempDir = File(app.cacheDir, IMPORT_CACHE_FOLDER)
if (!tempDir.exists()) {
tempDir.mkdirs()
}
try {
val (notes, importDataFolder) =
try {
when (importSource) {
ImportSource.GOOGLE_KEEP -> GoogleKeepImporter()
ImportSource.EVERNOTE -> EvernoteImporter()
}.import(app, uri, tempDir, progress)
} catch (e: Exception) {
Log.e(TAG, "import: failed", e)
progress?.postValue(ImportProgress(inProgress = false))
throw e
}
database.getLabelDao().insert(notes.flatMap { it.labels }.distinct().map { Label(it) })
val files = notes.flatMap { it.files }.distinct()
val images = notes.flatMap { it.images }.distinct()
val audios = notes.flatMap { it.audios }.distinct()
val totalFiles = files.size + images.size + audios.size
val counter = AtomicInteger(1)
progress?.postValue(
ImportProgress(total = totalFiles, stage = ImportStage.IMPORT_FILES)
)
importFiles(
files,
importDataFolder,
NotallyModel.FileType.ANY,
progress,
totalFiles,
counter,
)
importFiles(
images,
importDataFolder,
NotallyModel.FileType.IMAGE,
progress,
totalFiles,
counter,
)
importAudios(audios, importDataFolder, progress, totalFiles, counter)
database.getBaseNoteDao().insert(notes)
progress?.postValue(ImportProgress(inProgress = false))
return notes.size
} finally {
tempDir.deleteRecursively()
}
}
private suspend fun importFiles(
files: List<FileAttachment>,
sourceFolder: File,
fileType: NotallyModel.FileType,
progress: MutableLiveData<ImportProgress>?,
total: Int?,
counter: AtomicInteger?,
) {
files.forEach { file ->
val uri = File(sourceFolder, file.localName).toUri()
@ -55,27 +93,47 @@ class NotesImporter(private val app: Application, private val database: NotallyD
file.originalName = fileAttachment.originalName
file.mimeType = fileAttachment.mimeType
}
error?.let { Log.d(TAG, "Failed to import: $error") }
error?.let { Log.e(TAG, "Failed to import: $error") }
progress?.postValue(
ImportProgress(
current = counter!!.getAndIncrement(),
total = total!!,
stage = ImportStage.IMPORT_FILES,
)
)
}
}
private suspend fun importAudios(audios: List<Audio>, sourceFolder: File) {
private suspend fun importAudios(
audios: List<Audio>,
sourceFolder: File,
progress: MutableLiveData<ImportProgress>?,
totalFiles: Int,
counter: AtomicInteger,
) {
audios.forEach { originalAudio ->
val file = File(sourceFolder, originalAudio.name)
val audio = DataUtil.addAudio(app, file, false)
originalAudio.name = audio.name
originalAudio.duration = if (audio.duration == 0L) null else audio.duration
originalAudio.timestamp = audio.timestamp
progress?.postValue(
ImportProgress(
current = counter.getAndIncrement(),
total = totalFiles,
stage = ImportStage.IMPORT_FILES,
)
)
}
}
companion object {
private const val TAG = "NotesImporter"
const val IMPORT_CACHE_FOLDER = "imports"
}
}
enum class ImportSource(
val folderName: String,
val displayNameResId: Int,
val mimeType: String,
val helpTextResId: Int,
@ -83,7 +141,6 @@ enum class ImportSource(
val iconResId: Int,
) {
GOOGLE_KEEP(
"googlekeep",
R.string.google_keep,
"application/zip",
R.string.google_keep_help,
@ -91,7 +148,6 @@ enum class ImportSource(
R.drawable.icon_google_keep,
),
EVERNOTE(
"evernote",
R.string.evernote,
"*/*", // 'application/enex+xml' is not recognized
R.string.evernote_help,
@ -99,11 +155,3 @@ enum class ImportSource(
R.drawable.icon_evernote,
),
}
data class NotesImport(
val baseNotes: List<BaseNote>,
val labels: List<Label>,
val files: List<FileAttachment>,
val images: List<FileAttachment>,
val audios: List<Audio>,
)

View file

@ -4,10 +4,12 @@ import android.app.Application
import android.net.Uri
import android.util.Base64
import android.webkit.MimeTypeMap
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.R
import com.philkes.notallyx.data.imports.ExternalImporter
import com.philkes.notallyx.data.imports.ImportException
import com.philkes.notallyx.data.imports.ImportSource
import com.philkes.notallyx.data.imports.ImportProgress
import com.philkes.notallyx.data.imports.ImportStage
import com.philkes.notallyx.data.imports.evernote.EvernoteImporter.Companion.parseTimestamp
import com.philkes.notallyx.data.imports.parseBodyAndSpansFromHtml
import com.philkes.notallyx.data.model.Audio
@ -18,6 +20,7 @@ import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.utils.IO.write
import com.philkes.notallyx.utils.Operations
import java.io.File
import java.io.InputStream
import java.text.SimpleDateFormat
@ -31,25 +34,38 @@ import org.simpleframework.xml.core.Persister
class EvernoteImporter : ExternalImporter {
private val serializer: Serializer = Persister(AnnotationStrategy())
override fun importFrom(uri: Uri, app: Application): Pair<List<BaseNote>, File> {
if (MimeTypeMap.getFileExtensionFromUrl(uri.toString()) != "enex") {
throw ImportException(R.string.invalid_evernote)
}
val tempDir = File(app.cacheDir, ImportSource.EVERNOTE.folderName)
if (!tempDir.exists()) {
tempDir.mkdirs()
override fun import(
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>?,
): Pair<List<BaseNote>, File> {
progress?.postValue(ImportProgress(indeterminate = true))
if (MimeTypeMap.getFileExtensionFromUrl(source.toString()) != "enex") {
throw ImportException(
R.string.invalid_evernote,
IllegalArgumentException("Provided file is not in ENEX format"),
)
}
val evernoteExport: EvernoteExport =
parseExport(app.contentResolver.openInputStream(uri)!!)!!
saveResourcesToFiles(
evernoteExport.notes.flatMap { it.resources }.distinctBy { it.attributes?.fileName },
tempDir,
)
parseExport(app.contentResolver.openInputStream(source)!!)!!
val total = evernoteExport.notes.size
progress?.postValue(ImportProgress(total = total))
var counter = 1
try {
val notes = evernoteExport.notes.map { it.mapToBaseNote() }
return Pair(notes, tempDir)
val notes =
evernoteExport.notes.map {
val note = it.mapToBaseNote()
progress?.postValue(ImportProgress(current = counter++, total = total))
note
}
val resources =
evernoteExport.notes.flatMap { it.resources }.distinctBy { it.attributes?.fileName }
saveResourcesToFiles(app, resources, destination, progress)
return Pair(notes, destination)
} catch (e: Exception) {
throw ImportException(R.string.invalid_evernote)
throw ImportException(R.string.invalid_evernote, e)
}
}
@ -60,11 +76,30 @@ class EvernoteImporter : ExternalImporter {
throw ImportException(R.string.invalid_evernote, e)
}
private fun saveResourcesToFiles(resources: Collection<EvernoteResource>, dir: File) {
resources.forEach {
private fun saveResourcesToFiles(
app: Application,
resources: Collection<EvernoteResource>,
dir: File,
progress: MutableLiveData<ImportProgress>? = null,
) {
progress?.postValue(
ImportProgress(total = resources.size, stage = ImportStage.EXTRACT_FILES)
)
resources.forEachIndexed { idx, it ->
val file = File(dir, it.attributes!!.fileName)
val data = Base64.decode(it.data!!.content.trimStart(), Base64.DEFAULT)
file.write(data)
try {
val data = Base64.decode(it.data!!.content.trimStart(), Base64.DEFAULT)
file.write(data)
} catch (e: Exception) {
Operations.log(app, e)
}
progress?.postValue(
ImportProgress(
current = idx + 1,
total = resources.size,
stage = ImportStage.EXTRACT_FILES,
)
)
}
}

View file

@ -2,10 +2,12 @@ package com.philkes.notallyx.data.imports.google
import android.app.Application
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.R
import com.philkes.notallyx.data.imports.ExternalImporter
import com.philkes.notallyx.data.imports.ImportException
import com.philkes.notallyx.data.imports.ImportSource
import com.philkes.notallyx.data.imports.ImportProgress
import com.philkes.notallyx.data.imports.ImportStage
import com.philkes.notallyx.data.imports.parseBodyAndSpansFromHtml
import com.philkes.notallyx.data.model.Audio
import com.philkes.notallyx.data.model.BaseNote
@ -32,58 +34,72 @@ class GoogleKeepImporter : ExternalImporter {
allowTrailingComma = true
}
override fun importFrom(uri: Uri, app: Application): Pair<List<BaseNote>, File> {
val tempDir = File(app.cacheDir, ImportSource.GOOGLE_KEEP.folderName)
if (!tempDir.exists()) {
tempDir.mkdirs()
}
override fun import(
app: Application,
source: Uri,
destination: File,
progress: MutableLiveData<ImportProgress>?,
): Pair<List<BaseNote>, File> {
progress?.postValue(ImportProgress(indeterminate = true, stage = ImportStage.EXTRACT_FILES))
val dataFolder =
try {
unzip(tempDir, app.contentResolver.openInputStream(uri)!!)
unzip(destination, app.contentResolver.openInputStream(source)!!)
} catch (e: Exception) {
throw ImportException(R.string.invalid_google_keep, e)
}
if (!dataFolder.exists()) {
throw ImportException(R.string.invalid_google_keep)
throw ImportException(
R.string.invalid_google_keep,
RuntimeException("Extracting Takeout.zip failed"),
)
}
val baseNotes =
val noteFiles =
dataFolder
.walk()
.mapNotNull { importFile ->
if (importFile.extension == "json") {
parseToBaseNote(importFile.readText())
} else null
.listFiles { file ->
file.isFile && file.extension.equals("json", ignoreCase = true)
}
?.toList() ?: emptyList()
val total = noteFiles.size
progress?.postValue(ImportProgress(0, total, stage = ImportStage.IMPORT_NOTES))
var counter = 1
val baseNotes =
noteFiles
.mapNotNull { file ->
val baseNote = file.readText().parseToBaseNote()
progress?.postValue(
ImportProgress(counter++, total, stage = ImportStage.IMPORT_NOTES)
)
baseNote
}
.toList()
return Pair(baseNotes, dataFolder)
}
fun parseToBaseNote(jsonString: String): BaseNote {
val keepNote = json.decodeFromString<KeepNote>(jsonString)
fun String.parseToBaseNote(): BaseNote {
val googleKeepNote = json.decodeFromString<GoogleKeepNote>(this)
val (body, spans) =
parseBodyAndSpansFromHtml(
keepNote.textContentHtml,
googleKeepNote.textContentHtml,
paragraphsAsNewLine = true,
brTagsAsNewLine = true,
)
val images =
keepNote.attachments
googleKeepNote.attachments
.filter { it.mimetype.startsWith("image") }
.map { FileAttachment(it.filePath, it.filePath, it.mimetype) }
val files =
keepNote.attachments
googleKeepNote.attachments
.filter { !it.mimetype.startsWith("audio") && !it.mimetype.startsWith("image") }
.map { FileAttachment(it.filePath, it.filePath, it.mimetype) }
val audios =
keepNote.attachments
googleKeepNote.attachments
.filter { it.mimetype.startsWith("audio") }
.map { Audio(it.filePath, 0L, System.currentTimeMillis()) }
val items =
keepNote.listContent.mapIndexed { index, item ->
googleKeepNote.listContent.mapIndexed { index, item ->
ListItem(
body = item.text,
checked = item.isChecked,
@ -95,19 +111,19 @@ class GoogleKeepImporter : ExternalImporter {
return BaseNote(
id = 0L, // Auto-generated
type = if (keepNote.listContent.isNotEmpty()) Type.LIST else Type.NOTE,
type = if (googleKeepNote.listContent.isNotEmpty()) Type.LIST else Type.NOTE,
folder =
when {
keepNote.isTrashed -> Folder.DELETED
keepNote.isArchived -> Folder.ARCHIVED
googleKeepNote.isTrashed -> Folder.DELETED
googleKeepNote.isArchived -> Folder.ARCHIVED
else -> Folder.NOTES
},
color = Color.DEFAULT, // Ignoring color mapping
title = keepNote.title,
pinned = keepNote.isPinned,
timestamp = keepNote.createdTimestampUsec / 1000,
modifiedTimestamp = keepNote.userEditedTimestampUsec / 1000,
labels = keepNote.labels.map { it.name },
title = googleKeepNote.title,
pinned = googleKeepNote.isPinned,
timestamp = googleKeepNote.createdTimestampUsec / 1000,
modifiedTimestamp = googleKeepNote.userEditedTimestampUsec / 1000,
labels = googleKeepNote.labels.map { it.name },
body = body,
spans = spans,
items = items,

View file

@ -4,8 +4,8 @@ import com.philkes.notallyx.data.model.Color
import kotlinx.serialization.Serializable
@Serializable
data class KeepNote(
val attachments: List<KeepAttachment> = listOf(),
data class GoogleKeepNote(
val attachments: List<GoogleKeepAttachment> = listOf(),
val color: String = Color.DEFAULT.name,
val isTrashed: Boolean = false,
val isArchived: Boolean = false,
@ -13,14 +13,14 @@ data class KeepNote(
val textContent: String = "",
val textContentHtml: String = "",
val title: String = "",
val labels: List<KeepLabel> = listOf(),
val labels: List<GoogleKeepLabel> = listOf(),
val userEditedTimestampUsec: Long = System.currentTimeMillis(),
val createdTimestampUsec: Long = System.currentTimeMillis(),
val listContent: List<KeepListItem> = listOf(),
val listContent: List<GoogleKeepListItem> = listOf(),
)
@Serializable data class KeepLabel(val name: String)
@Serializable data class GoogleKeepLabel(val name: String)
@Serializable data class KeepAttachment(val filePath: String, val mimetype: String)
@Serializable data class GoogleKeepAttachment(val filePath: String, val mimetype: String)
@Serializable data class KeepListItem(val text: String, val isChecked: Boolean)
@Serializable data class GoogleKeepListItem(val text: String, val isChecked: Boolean)

View file

@ -48,6 +48,8 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.philkes.notallyx.R
import com.philkes.notallyx.data.imports.ImportProgress
import com.philkes.notallyx.data.imports.ImportStage
import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.SpanRepresentation
import com.philkes.notallyx.data.model.getUrl
@ -279,7 +281,7 @@ fun View.getString(id: Int, vararg formatArgs: String): String {
}
fun View.getQuantityString(id: Int, quantity: Int, vararg formatArgs: Any): String {
return context.resources.getQuantityString(id, quantity, *formatArgs)
return context.getQuantityString(id, quantity, *formatArgs)
}
fun Folder.movedToResId(): Int {
@ -370,11 +372,33 @@ fun MutableLiveData<out Progress>.setupProgressDialog(fragment: Fragment, titleI
)
}
private fun MutableLiveData<out Progress>.setupProgressDialog(
fun MutableLiveData<ImportProgress>.setupImportProgressDialog(fragment: Fragment, titleId: Int) {
setupProgressDialog(
fragment.requireContext(),
fragment.layoutInflater,
fragment.viewLifecycleOwner,
titleId,
) { context, binding, progress ->
val stageStr =
context.getString(
when (progress.stage) {
ImportStage.IMPORT_NOTES -> R.string.imported_notes
ImportStage.EXTRACT_FILES -> R.string.extracted_files
ImportStage.IMPORT_FILES -> R.string.imported_files
}
)
binding.Count.text =
"${context.getString(R.string.count, progress.current, progress.total)} $stageStr"
}
}
private fun <T : Progress> MutableLiveData<T>.setupProgressDialog(
context: Context,
layoutInflater: LayoutInflater,
viewLifecycleOwner: LifecycleOwner,
titleId: Int,
renderProgress: ((context: Context, binding: DialogProgressBinding, progress: T) -> Unit)? =
null,
) {
val dialogBinding = DialogProgressBinding.inflate(layoutInflater)
val dialog =
@ -398,7 +422,10 @@ private fun MutableLiveData<out Progress>.setupProgressDialog(
max = progress.total
setProgressCompat(progress.current, true)
}
Count.text = context.getString(R.string.count, progress.current, progress.total)
if (renderProgress == null) {
Count.text =
context.getString(R.string.count, progress.current, progress.total)
} else renderProgress.invoke(context, this, progress)
}
}
dialog.show()
@ -455,3 +482,7 @@ fun MaterialAlertDialogBuilder.showAndFocus(view: View): AlertDialog {
}
return dialog
}
fun Context.getQuantityString(id: Int, quantity: Int, vararg formatArgs: Any): String {
return resources.getQuantityString(id, quantity, quantity, *formatArgs)
}

View file

@ -41,6 +41,7 @@ import com.philkes.notallyx.presentation.activity.note.EditListActivity
import com.philkes.notallyx.presentation.activity.note.EditNoteActivity
import com.philkes.notallyx.presentation.add
import com.philkes.notallyx.presentation.applySpans
import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.presentation.movedToResId
import com.philkes.notallyx.presentation.view.main.ColorAdapter
import com.philkes.notallyx.presentation.view.misc.MenuDialog
@ -217,7 +218,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
val ids = model.moveBaseNotes(folderTo)
Snackbar.make(
findViewById(R.id.DrawerLayout),
resources.getQuantityString(folderTo.movedToResId(), ids.size, ids.size),
getQuantityString(folderTo.movedToResId(), ids.size),
Snackbar.LENGTH_SHORT,
)
.apply { setAction(R.string.undo) { model.moveBaseNotes(ids, folderFrom) } }

View file

@ -25,6 +25,7 @@ import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.FO
import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.NOTE_ID
import com.philkes.notallyx.presentation.activity.note.EditListActivity
import com.philkes.notallyx.presentation.activity.note.EditNoteActivity
import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.presentation.movedToResId
import com.philkes.notallyx.presentation.view.Constants
import com.philkes.notallyx.presentation.view.main.BaseNoteAdapter
@ -103,7 +104,7 @@ abstract class NotallyFragment : Fragment(), ListItemListener {
val folderTo = Folder.valueOf(data.getStringExtra(FOLDER_TO)!!)
Snackbar.make(
binding!!.root,
resources.getQuantityString(folderTo.movedToResId(), 1, 1),
requireContext().getQuantityString(folderTo.movedToResId(), 1),
Snackbar.LENGTH_SHORT,
)
.apply {

View file

@ -31,6 +31,7 @@ import com.philkes.notallyx.databinding.PreferenceSeekbarBinding
import com.philkes.notallyx.databinding.TextInputDialogBinding
import com.philkes.notallyx.presentation.canAuthenticateWithBiometrics
import com.philkes.notallyx.presentation.checkedTag
import com.philkes.notallyx.presentation.setupImportProgressDialog
import com.philkes.notallyx.presentation.setupProgressDialog
import com.philkes.notallyx.presentation.view.misc.AutoBackup
import com.philkes.notallyx.presentation.view.misc.AutoBackupMax
@ -124,7 +125,7 @@ class SettingsFragment : Fragment() {
binding.ClearData.setOnClickListener { clearData() }
model.exportProgress.setupProgressDialog(this, R.string.exporting_backup)
model.importProgress.setupProgressDialog(this, R.string.importing_backup)
model.importProgress.setupImportProgressDialog(this, R.string.importing_backup)
model.deletionProgress.setupProgressDialog(this, R.string.deleting_files)
binding.GitHub.setOnClickListener { openLink("https://github.com/PhilKes/NotallyX") }

View file

@ -34,6 +34,7 @@ import com.philkes.notallyx.databinding.ActivityEditBinding
import com.philkes.notallyx.presentation.activity.LockedActivity
import com.philkes.notallyx.presentation.add
import com.philkes.notallyx.presentation.displayFormattedTimestamp
import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.presentation.setupProgressDialog
import com.philkes.notallyx.presentation.view.Constants
import com.philkes.notallyx.presentation.view.misc.NotesSorting.autoSortByCreationDate
@ -518,7 +519,7 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
} else {
R.plurals.cant_add_files
}
val title = resources.getQuantityString(message, errors.size, errors.size)
val title = getQuantityString(message, errors.size)
MaterialAlertDialogBuilder(this)
.setTitle(title)
.setView(recyclerView)

View file

@ -15,7 +15,6 @@ import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.core.util.PatternsCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.philkes.notallyx.R
import com.philkes.notallyx.data.model.Type

View file

@ -20,6 +20,7 @@ import com.philkes.notallyx.data.dao.BaseNoteDao
import com.philkes.notallyx.data.dao.CommonDao
import com.philkes.notallyx.data.dao.LabelDao
import com.philkes.notallyx.data.imports.ImportException
import com.philkes.notallyx.data.imports.ImportProgress
import com.philkes.notallyx.data.imports.ImportSource
import com.philkes.notallyx.data.imports.NotesImporter
import com.philkes.notallyx.data.model.Attachment
@ -36,6 +37,7 @@ import com.philkes.notallyx.data.model.Label
import com.philkes.notallyx.data.model.SearchResult
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.applySpans
import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.presentation.view.misc.AutoBackup
import com.philkes.notallyx.presentation.view.misc.ListInfo
import com.philkes.notallyx.presentation.view.misc.Progress
@ -109,7 +111,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
val fileRoot = app.getExternalFilesDirectory()
private val audioRoot = app.getExternalAudioDirectory()
val importProgress = MutableLiveData<Progress>()
val importProgress = MutableLiveData<ImportProgress>()
val exportProgress = MutableLiveData<Progress>()
val deletionProgress = MutableLiveData<Progress>()
@ -219,8 +221,15 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
}
fun importZipBackup(uri: Uri, password: String) {
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Operations.log(app, throwable)
Toast.makeText(app, R.string.invalid_backup, Toast.LENGTH_LONG).show()
}
val backupDir = app.getBackupDir()
viewModelScope.launch { importZip(app, uri, backupDir, password, importProgress) }
viewModelScope.launch(exceptionHandler) {
importZip(app, uri, backupDir, password, importProgress)
}
}
fun importXmlBackup(uri: Uri) {
@ -230,12 +239,15 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
}
viewModelScope.launch(exceptionHandler) {
withContext(Dispatchers.IO) {
val stream = requireNotNull(app.contentResolver.openInputStream(uri))
val backup = XMLUtils.readBackupFromStream(stream)
commonDao.importBackup(backup.first, backup.second)
}
Toast.makeText(app, R.string.imported_backup, Toast.LENGTH_LONG).show()
val importedNotes =
withContext(Dispatchers.IO) {
val stream = requireNotNull(app.contentResolver.openInputStream(uri))
val (baseNotes, labels) = XMLUtils.readBackupFromStream(stream)
commonDao.importBackup(baseNotes, labels)
baseNotes.size
}
val message = app.getQuantityString(R.plurals.imported_notes, importedNotes)
Toast.makeText(app, message, Toast.LENGTH_LONG).show()
}
}
@ -254,8 +266,12 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
Operations.log(app, throwable)
}
viewModelScope.launch(exceptionHandler) {
withContext(Dispatchers.IO) { NotesImporter(app, database).import(uri, importSource) }
Toast.makeText(app, R.string.imported_backup, Toast.LENGTH_LONG).show()
val importedNotes =
withContext(Dispatchers.IO) {
NotesImporter(app, database).import(uri, importSource, importProgress)
}
val message = app.getQuantityString(R.plurals.imported_notes, importedNotes)
Toast.makeText(app, message, Toast.LENGTH_LONG).show()
}
}

View file

@ -13,7 +13,6 @@ import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.displayFormattedTimestamp
import com.philkes.notallyx.presentation.view.misc.TextSize
import com.philkes.notallyx.presentation.widget.WidgetProvider.Companion.getSelectNoteIntent

View file

@ -2,4 +2,8 @@ package com.philkes.notallyx.utils
import com.philkes.notallyx.presentation.viewmodel.NotallyModel
class FileError(val name: String, val description: String, val fileType: NotallyModel.FileType)
data class FileError(
val name: String,
val description: String,
val fileType: NotallyModel.FileType,
)

View file

@ -6,9 +6,9 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.os.Build
import androidx.lifecycle.MutableLiveData
import android.util.Log
import androidx.core.net.toUri
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.data.model.Attachment
import com.philkes.notallyx.data.model.Audio
import com.philkes.notallyx.data.model.FileAttachment
@ -85,7 +85,7 @@ object IO {
val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
val mimeType = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
mimeType != null && hasAudio != null ||
duration != null // If it has audio metadata, it's a valid audio file
duration != null // If it has audio metadata, it's a valid audio file
} catch (e: Exception) {
false // An exception means its not a valid audio file
} finally {

View file

@ -9,6 +9,8 @@ import androidx.core.database.getLongOrNull
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.imports.ImportProgress
import com.philkes.notallyx.data.imports.ImportStage
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.Color
import com.philkes.notallyx.data.model.Converters
@ -16,7 +18,7 @@ import com.philkes.notallyx.data.model.FileAttachment
import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.Label
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.utils.IO.SUBFOLDER_AUDIOS
import com.philkes.notallyx.utils.IO.SUBFOLDER_FILES
import com.philkes.notallyx.utils.IO.SUBFOLDER_IMAGES
@ -46,93 +48,115 @@ object Import {
zipFileUri: Uri,
databaseFolder: File,
zipPassword: String,
importingBackup: MutableLiveData<Progress>? = null,
importingBackup: MutableLiveData<ImportProgress>? = null,
) {
importingBackup?.postValue(Progress(indeterminate = true))
importingBackup?.postValue(ImportProgress(indeterminate = true))
try {
withContext(Dispatchers.IO) {
val stream = requireNotNull(app.contentResolver.openInputStream(zipFileUri))
val tempZipFile = File(databaseFolder, "TEMP.zip")
stream.copyToFile(tempZipFile)
val zipFile = ZipFile(tempZipFile)
if (zipFile.isEncrypted) {
zipFile.setPassword(zipPassword.toCharArray())
}
zipFile.extractFile(
NotallyDatabase.DatabaseName,
databaseFolder.path,
NotallyDatabase.DatabaseName,
)
val database =
SQLiteDatabase.openDatabase(
File(databaseFolder, NotallyDatabase.DatabaseName).path,
null,
SQLiteDatabase.OPEN_READONLY,
)
val labelCursor = database.query("Label", null, null, null, null, null, null)
val baseNoteCursor = database.query("BaseNote", null, null, null, null, null, null)
val labels = labelCursor.toList { cursor -> cursor.toLabel() }
val baseNotes = baseNoteCursor.toList { cursor -> cursor.toBaseNote() }
delay(1000)
val total =
baseNotes.fold(0) { acc, baseNote ->
acc + baseNote.images.size + baseNote.files.size + baseNote.audios.size
val importedNotes =
withContext(Dispatchers.IO) {
val stream = requireNotNull(app.contentResolver.openInputStream(zipFileUri))
val tempZipFile = File(databaseFolder, "TEMP.zip")
stream.copyToFile(tempZipFile)
val zipFile = ZipFile(tempZipFile)
if (zipFile.isEncrypted) {
zipFile.setPassword(zipPassword.toCharArray())
}
importingBackup?.postValue(Progress(0, total))
zipFile.extractFile(
NotallyDatabase.DatabaseName,
databaseFolder.path,
NotallyDatabase.DatabaseName,
)
val current = AtomicInteger(1)
val imageRoot = app.getExternalImagesDirectory()
val fileRoot = app.getExternalFilesDirectory()
val audioRoot = app.getExternalAudioDirectory()
baseNotes.forEach { baseNote ->
importFiles(
app,
baseNote.images,
SUBFOLDER_IMAGES,
imageRoot,
zipFile,
current,
total,
val database =
SQLiteDatabase.openDatabase(
File(databaseFolder, NotallyDatabase.DatabaseName).path,
null,
SQLiteDatabase.OPEN_READONLY,
)
val labelCursor = database.query("Label", null, null, null, null, null, null)
val baseNoteCursor =
database.query("BaseNote", null, null, null, null, null, null)
val labels = labelCursor.toList { cursor -> cursor.toLabel() }
var total = baseNoteCursor.count
var counter = 1
importingBackup?.postValue(ImportProgress(0, total))
val baseNotes =
baseNoteCursor.toList { cursor ->
val baseNote = cursor.toBaseNote()
importingBackup?.postValue(ImportProgress(counter++, total))
baseNote
}
delay(1000)
total =
baseNotes.fold(0) { acc, baseNote ->
acc + baseNote.images.size + baseNote.files.size + baseNote.audios.size
}
importingBackup?.postValue(
ImportProgress(0, total, stage = ImportStage.IMPORT_FILES)
)
importFiles(
app,
baseNote.files,
SUBFOLDER_FILES,
fileRoot,
zipFile,
current,
total,
)
baseNote.audios.forEach { audio ->
try {
val audioFilePath = "$SUBFOLDER_AUDIOS/${audio.name}"
val entry = zipFile.getFileHeader(audioFilePath)
if (entry != null) {
val name = "${UUID.randomUUID()}.m4a"
zipFile.extractFile(audioFilePath, audioRoot!!.path, name)
audio.name = name
val current = AtomicInteger(1)
val imageRoot = app.getExternalImagesDirectory()
val fileRoot = app.getExternalFilesDirectory()
val audioRoot = app.getExternalAudioDirectory()
baseNotes.forEach { baseNote ->
importFiles(
app,
baseNote.images,
SUBFOLDER_IMAGES,
imageRoot,
zipFile,
current,
total,
importingBackup,
)
importFiles(
app,
baseNote.files,
SUBFOLDER_FILES,
fileRoot,
zipFile,
current,
total,
importingBackup,
)
baseNote.audios.forEach { audio ->
try {
val audioFilePath = "$SUBFOLDER_AUDIOS/${audio.name}"
val entry = zipFile.getFileHeader(audioFilePath)
if (entry != null) {
val name = "${UUID.randomUUID()}.m4a"
zipFile.extractFile(audioFilePath, audioRoot!!.path, name)
audio.name = name
}
} catch (exception: Exception) {
Operations.log(app, exception)
} finally {
importingBackup?.postValue(
ImportProgress(
current.getAndIncrement(),
total,
stage = ImportStage.IMPORT_FILES,
)
)
}
} catch (exception: Exception) {
Operations.log(app, exception)
} finally {
importingBackup?.postValue(Progress(current.get(), total))
current.getAndIncrement()
}
}
}
NotallyDatabase.getDatabase(app)
.value
.getCommonDao()
.importBackup(baseNotes, labels)
}
NotallyDatabase.getDatabase(app)
.value
.getCommonDao()
.importBackup(baseNotes, labels)
baseNotes.size
}
databaseFolder.clearDirectory()
Toast.makeText(app, R.string.imported_backup, Toast.LENGTH_LONG).show()
val message = app.getQuantityString(R.plurals.imported_notes, importedNotes)
Toast.makeText(app, message, Toast.LENGTH_LONG).show()
} catch (e: ZipException) {
if (e.type == ZipException.Type.WRONG_PASSWORD) {
Toast.makeText(app, R.string.wrong_password, Toast.LENGTH_LONG).show()
@ -144,7 +168,7 @@ object Import {
Toast.makeText(app, R.string.invalid_backup, Toast.LENGTH_LONG).show()
Operations.log(app, e)
} finally {
importingBackup?.value = Progress(inProgress = false)
importingBackup?.value = ImportProgress(inProgress = false)
}
}
@ -156,7 +180,7 @@ object Import {
zipFile: ZipFile,
current: AtomicInteger,
total: Int,
importingBackup: MutableLiveData<Progress>? = null,
importingBackup: MutableLiveData<ImportProgress>? = null,
) {
files.forEach { file ->
try {
@ -170,8 +194,13 @@ object Import {
} catch (exception: Exception) {
Operations.log(app, exception)
} finally {
importingBackup?.postValue(Progress(current.get(), total))
current.getAndIncrement()
importingBackup?.postValue(
ImportProgress(
current.getAndIncrement(),
total,
stage = ImportStage.IMPORT_FILES,
)
)
}
}
}

View file

@ -112,7 +112,6 @@
<string name="choose_another_folder">Vybrat jinou složku</string>
<string name="cant_find_folder">Složka nebyla nalezena. Možná byla přesunuta nebo smazána.</string>
<string name="invalid_backup">Neplatný soubor zálohy</string>
<string name="imported_backup">Záloha importována</string>
<string name="exporting_backup">Vytváří se záloha</string>
<string name="importing_backup">Importuje se záloha</string>
<string name="calculating">Probíhá výpočet…</string>

View file

@ -139,7 +139,6 @@
<string name="cant_find_folder">Kann den Ordner nicht finden. Er kann verschoben oder gelöscht worden sein</string>
<string name="invalid_backup">Ungültige Backup</string>
<string name="google_keep_help">Um deine Notizen aus Google Notizen zu importieren musst du deine Google Takeout ZIP Datei herunterladen\n\nFalls du das Takeout ZIP schon hast, klicke auf Import und wähle es aus.</string>
<string name="imported_backup">Backup importiert</string>
<string name="exporting_backup">Backup Exportieren</string>
<string name="importing_backup">Backup Importieren</string>
<string name="calculating">Berechne…</string>

View file

@ -68,7 +68,6 @@
<!-- Messages -->
<string name="invalid_backup">Copia de seguridad inválida</string>
<string name="imported_backup">Copia de seguridad importada</string>
<string name="install_an_email">Instale una aplicación de correo electrónico para enviar comentarios</string>
<string name="install_a_browser">Instala un navegador para abrir este enlace</string>

View file

@ -112,7 +112,6 @@
<string name="choose_another_folder">Choisir un autre dossier</string>
<string name="cant_find_folder">Impossible de trouver le dossier. Il a peut-être été déplacé ou supprimé.</string>
<string name="invalid_backup">Sauvegarde invalide</string>
<string name="imported_backup">Sauvegarde importée</string>
<string name="exporting_backup">Exportation de la sauvegarde</string>
<string name="importing_backup">Importation de la sauvegarde</string>
<string name="calculating">Calcul…</string>

View file

@ -67,7 +67,6 @@
<string name="empty_list">Daftar kosong</string>
<string name="cant_open_link">Tidak bisa membuka tautan</string>
<string name="invalid_backup">Cadangan tidak valid</string>
<string name="imported_backup">Cadangan yang diimpor</string>
<string name="something_went_wrong">Telah terjadi kesalahan. Silakan coba lagi.</string>
<string name="install_a_browser">Pasang peramban untuk membuka tautan ini</string>

View file

@ -68,7 +68,6 @@
<string name="empty_list">Lista vuota</string>
<string name="cant_open_link">Impossibile aprire il link</string>
<string name="invalid_backup">Backup invalido</string>
<string name="imported_backup">Backup importato</string>
<string name="something_went_wrong">Qualcosa è andato storto. Per favore riprova.</string>
<!-- Settings Page -->

View file

@ -71,7 +71,6 @@
<string name="cant_open_link">လင့်ကိုမဖွင့်နိုင်ပါ။</string>
<string name="invalid_image">ရုပ်ပုံမှား​နေသည်။</string>
<string name="invalid_backup">အရန်သိမ်းဖိုင်မှားနေသည်။</string>
<string name="imported_backup">အရန်သိမ်းထားသည်များ သွင်းမည်။</string>
<string name="something_went_wrong">တစ်စုံတစ်ခုမှားယွင်း​နေသည်။ထပ်မံကြိုးစားပါ။</string>
<string name="image_format_not_supported">ဤရုပ်ပုံဖိုင်အမျိုးအစားအား လက်မခံပါ။</string>
<string name="install_an_email">တုံ့ပြန်ချက်​ပေးပို့ရန် အီး​မေးလ်အက်ပ် တစ်ခုအရင်သွင်းပါ</string>

View file

@ -110,7 +110,6 @@
<string name="choose_another_folder">Velg en annen mappe</string>
<string name="cant_find_folder">Finner ikke mappen. Den kan ha blitt flyttet eller slettet</string>
<string name="invalid_backup">Ugyldig sikkerhetskopi</string>
<string name="imported_backup">Importert sikkerhetskopi</string>
<string name="exporting_backup">Eksporterer sikkerhetskopi</string>
<string name="importing_backup">Importerer sikkerhetskopi</string>
<string name="calculating">Beregner…</string>

View file

@ -165,7 +165,6 @@
<string name="choose_another_folder">Kies een andere map</string>
<string name="cant_find_folder">Kan map niet vinden. Deze is mogelijk verplaatst of verwijderd</string>
<string name="invalid_backup">Ongeldige back-up</string>
<string name="imported_backup">Geïmporteerde back-up</string>
<string name="exporting_backup">Back-up exporteren</string>
<string name="importing_backup">Back-up importeren</string>
<string name="calculating">Berekenen…</string>

View file

@ -110,7 +110,6 @@
<string name="choose_another_folder">Vel ei anna mappe</string>
<string name="cant_find_folder">Finn ikkje mappa. Den kan ha blitt flytta eller sletta</string>
<string name="invalid_backup">Ugyldeg sikkerheitskopi</string>
<string name="imported_backup">Importert sikkerheitskopi</string>
<string name="exporting_backup">Eksporterer sikkerheitskopi</string>
<string name="importing_backup">Importerer sikkerheitskopi</string>
<string name="calculating">Bereknar…</string>

View file

@ -112,7 +112,6 @@
<string name="choose_another_folder">Wybierz inny katalog</string>
<string name="cant_find_folder">Nie można znaleźć katalogu. Być może został przeniesiony lub usunięty</string>
<string name="invalid_backup">Niewłaściwy plik kopii zapasowej</string>
<string name="imported_backup">Kopia zapasowa została przywrócona</string>
<string name="exporting_backup">Wykonywanie kopii zapasowej</string>
<string name="importing_backup">Przywracanie kopii zapasowej</string>
<string name="calculating">Obliczanie…</string>

View file

@ -72,7 +72,6 @@
<string name="cant_open_link">Nu se poate deschide link-ul</string>
<string name="invalid_image">Imagine invalidă</string>
<string name="invalid_backup">Backup invalid</string>
<string name="imported_backup">Backup importat</string>
<string name="something_went_wrong">Ceva a mers nu cum trebuie. Vă rog să încercați din nou.</string>
<string name="image_format_not_supported">Formatul imaginii nu este acceptat</string>
<string name="install_an_email">Instalați o aplicație de e-mail pentru a trimite feedback</string>

View file

@ -113,7 +113,6 @@
<string name="choose_another_folder">Izberite drugo mapo</string>
<string name="cant_find_folder">Mapa ne obstaja. Morda je bila premaknjena ali izbrisana.</string>
<string name="invalid_backup">Neveljavna varnostna kopija</string>
<string name="imported_backup">Varnostna kopija je bila uvožena</string>
<string name="exporting_backup">Izvažanje varnostne kopije</string>
<string name="importing_backup">Uvažanje varnostne kopije</string>
<string name="calculating">Izračun…</string>

View file

@ -111,7 +111,6 @@
<string name="choose_another_folder">Chọn thư mục khác</string>
<string name="cant_find_folder">Không tìm thấy thư mục. Có lẽ nó đã bị đổi tên hoặc xóa</string>
<string name="invalid_backup">Bản sao lưu không hợp lệ</string>
<string name="imported_backup">Đã nhập bản sao lưu</string>
<string name="exporting_backup">Đã xuất bản sao lưu</string>
<string name="calculating">Đang xử lý…</string>

View file

@ -116,7 +116,6 @@
<string name="choose_another_folder">选择另一个文件夹</string>
<string name="cant_find_folder">找不到该文件夹。可能被移动或删除了。</string>
<string name="invalid_backup">无效的备份</string>
<string name="imported_backup">已导入备份</string>
<!-- Widget -->
<string name="single_note_or_list">单条笔记</string>

View file

@ -186,9 +186,15 @@
<string name="help">Help</string>
<string name="google_keep_help">In order to import your Notes from Google Keep you must download your Google Takeout ZIP file. Click Help to get more information.\n\nIf you already have a Takeout ZIP file, click Import and choose the ZIP file.</string>
<string name="evernote_help">In order to import your Notes from Evernote you must export your Evernote Notebook as ENEX. Click Help to get more information.\n\nIf you already have a ENEX file, click Import and choose it.</string>
<string name="imported_backup">Imported backup</string>
<plurals name="imported_notes">
<item quantity="one">Imported %s Note</item>
<item quantity="other">Imported %s Notes</item>
</plurals>
<string name="exporting_backup">Exporting backup</string>
<string name="importing_backup">Importing backup</string>
<string name="extracted_files">Files extracted</string>
<string name="imported_files">Files imported</string>
<string name="imported_notes">Notes imported</string>
<string name="calculating">Calculating…</string>
<string name="clear_data_message">All Notes, Images, Files, Audios will be permanently deleted</string>

View file

@ -46,7 +46,7 @@ class NotesImporterTest {
private fun testImport(importSource: ImportSource, expectedAmountNotes: Int) {
val importSourceFile = prepareImportSources(importSource)
val importOutputFolder = prepareMediaFolder(importSource)
val importOutputFolder = prepareMediaFolder()
runBlocking {
NotesImporter(application, database).import(importSourceFile.toUri(), importSource)
@ -108,12 +108,13 @@ class NotesImporterTest {
}
}
private fun prepareMediaFolder(importSource: ImportSource): String {
private fun prepareMediaFolder(): String {
val dir =
File.createTempFile("notallyxNotesImporterTest", importSource.folderName).apply {
delete()
mkdirs()
}
File.createTempFile("notallyxNotesImporterTest", NotesImporter.IMPORT_CACHE_FOLDER)
.apply {
delete()
mkdirs()
}
val path = dir.toPath().toString()
ShadowEnvironment.addExternalDir(path)
println("Output folder: $path")
@ -121,8 +122,8 @@ class NotesImporterTest {
}
private fun prepareImportSources(importSource: ImportSource): File {
val tempDir = Files.createTempDirectory("imports-${importSource.folderName}").toFile()
copyTestFilesToTempDir("imports/${importSource.folderName}", tempDir)
val tempDir = Files.createTempDirectory("imports-${importSource.name.lowercase()}").toFile()
copyTestFilesToTempDir("imports/${importSource.name.lowercase()}", tempDir)
println("Input folder: ${tempDir.absolutePath}")
return when (importSource) {
ImportSource.GOOGLE_KEEP -> File(tempDir, "Takeout.zip")

View file

@ -49,7 +49,7 @@ class GoogleKeepImporterTest {
labels = listOf("Label1", "Label2"),
body = "This is some note, nothing special",
)
val actual = importer.parseToBaseNote(json)
val actual = with(importer) { json.parseToBaseNote() }
assertEquals(expected, actual)
}
@ -66,7 +66,7 @@ class GoogleKeepImporterTest {
"""
.trimIndent()
val actual = importer.parseToBaseNote(json)
val actual = with(importer) { json.parseToBaseNote() }
assertThat(actual)
.extracting("title", "folder")
@ -85,7 +85,7 @@ class GoogleKeepImporterTest {
"""
.trimIndent()
val actual = importer.parseToBaseNote(json)
val actual = with(importer) { json.parseToBaseNote() }
assertThat(actual)
.extracting("title", "folder")
@ -103,7 +103,7 @@ class GoogleKeepImporterTest {
"""
.trimIndent()
val actual = importer.parseToBaseNote(json)
val actual = with(importer) { json.parseToBaseNote() }
assertThat(actual).extracting("title", "pinned").containsExactly("Pinned Note", true)
}
@ -128,7 +128,7 @@ class GoogleKeepImporterTest {
"""
.trimIndent()
val actual = importer.parseToBaseNote(json)
val actual = with(importer) { json.parseToBaseNote() }
assertThat(actual)
.extracting("type", "title", "items")
@ -158,7 +158,7 @@ class GoogleKeepImporterTest {
"""
.trimIndent()
val actual = importer.parseToBaseNote(json)
val actual = with(importer) { json.parseToBaseNote() }
assertThat(actual)
.extracting("title", "images")
@ -184,7 +184,7 @@ class GoogleKeepImporterTest {
"""
.trimIndent()
val actual = importer.parseToBaseNote(json)
val actual = with(importer) { json.parseToBaseNote() }
assertThat(actual)
.extracting("title", "files")
@ -210,7 +210,7 @@ class GoogleKeepImporterTest {
"""
.trimIndent()
val actual = importer.parseToBaseNote(json)
val actual = with(importer) { json.parseToBaseNote() }
assertThat(actual.title).isEqualTo("Audio Note")
assertThat(actual.audios[0].name).isEqualTo("audio.3gp")