Android: add general document import with multi-file support
Add three new FFI exports to synchronicity_lib: import_image_document, import_music_track_document, and get_all_document_mime_types. The MIME-type list comes from the same documents::import constants the GUI uses, so Android and GUI share the same source of truth for which files are supported. On Android: add DocumentFilePicker (OpenMultipleDocuments) and an import button to DocumentsScreen. File type is detected via contentResolver.getType and routed to the correct importer. Add IMPORT_ICON = Icons.Default.FileOpen as an app-wide constant; update PdfFilePicker to use it and rename its button text to 'Import PDF'.
This commit is contained in:
@@ -168,6 +168,14 @@ class NativeLib {
|
||||
fun importPdfDocument(fileName: String, pdfBytes: ByteArray): String =
|
||||
uniffi.synchronicity.importPdfDocument(fileName, pdfBytes)
|
||||
|
||||
fun importImageDocument(fileName: String, fileExt: String, imageBytes: ByteArray): String =
|
||||
uniffi.synchronicity.importImageDocument(fileName, fileExt, imageBytes)
|
||||
|
||||
fun importMusicTrackDocument(fileName: String, musicBytes: ByteArray): String =
|
||||
uniffi.synchronicity.importMusicTrackDocument(fileName, musicBytes)
|
||||
|
||||
fun getAllDocumentMimeTypes(): List<String> = uniffi.synchronicity.getAllDocumentMimeTypes()
|
||||
|
||||
fun getDocumentBlobBytes(documentId: String): ByteArray =
|
||||
uniffi.synchronicity.getDocumentBlobBytes(documentId)
|
||||
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.gregshuflin.synchronicity.documents.ui
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import com.gregshuflin.synchronicity.ui.IMPORT_ICON
|
||||
|
||||
private const val TAG = "DocumentFilePicker"
|
||||
|
||||
@Composable
|
||||
fun DocumentFilePicker(
|
||||
mimeTypes: Array<String>,
|
||||
onFilesSelected: (List<Uri>) -> Unit,
|
||||
) {
|
||||
val filePickerLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { uris: List<Uri> ->
|
||||
Log.d(TAG, "Files selected: $uris")
|
||||
if (uris.isNotEmpty()) onFilesSelected(uris)
|
||||
}
|
||||
|
||||
TextButton(onClick = { filePickerLauncher.launch(mimeTypes) }) {
|
||||
Icon(IMPORT_ICON, contentDescription = null)
|
||||
Text("Import")
|
||||
}
|
||||
}
|
||||
+83
-12
@@ -1,6 +1,9 @@
|
||||
package com.gregshuflin.synchronicity.documents.ui
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@@ -11,31 +14,42 @@ import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.gregshuflin.synchronicity.NativeLib
|
||||
import com.gregshuflin.synchronicity.getFileNameFromUri
|
||||
import com.gregshuflin.synchronicity.ui.SubAppScreen
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class DocumentFilter(val type: String, val displayName: String)
|
||||
|
||||
data class DateFilter(val key: String, val displayName: String)
|
||||
|
||||
private const val TAG = "DocumentsScreen"
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DocumentsScreen(onBack: () -> Unit, onDocumentClick: ((String) -> Unit)? = null) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val nativeLib = remember { NativeLib() }
|
||||
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
var selectedDocumentTypes by remember { mutableStateOf(setOf<String>()) }
|
||||
var selectedDateFilter by remember { mutableStateOf<String?>(null) }
|
||||
var documentTypeExpanded by remember { mutableStateOf(false) }
|
||||
var dateFilterExpanded by remember { mutableStateOf(false) }
|
||||
var refreshTrigger by remember { mutableIntStateOf(0) }
|
||||
|
||||
// Load document types from native library
|
||||
// Load document types and supported MIME types from native library
|
||||
val availableDocumentTypes = remember { mutableStateOf<List<DocumentFilter>>(emptyList()) }
|
||||
val supportedMimeTypes = remember { mutableStateOf<Array<String>>(emptyArray()) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
val nativeLib = NativeLib()
|
||||
val documentTypes = nativeLib.getAllDocumentTypes()
|
||||
|
||||
val documentFilters =
|
||||
documentTypes.map { type ->
|
||||
val displayName =
|
||||
@@ -48,14 +62,14 @@ fun DocumentsScreen(onBack: () -> Unit, onDocumentClick: ((String) -> Unit)? = n
|
||||
}
|
||||
DocumentFilter(type, displayName)
|
||||
}
|
||||
|
||||
Log.d(
|
||||
"DocumentsScreen",
|
||||
"Loaded ${documentFilters.size} document types: ${documentFilters.map { it.type }}",
|
||||
)
|
||||
Log.d(TAG, "Loaded ${documentFilters.size} document types: ${documentFilters.map { it.type }}")
|
||||
availableDocumentTypes.value = documentFilters
|
||||
|
||||
val mimeTypes = nativeLib.getAllDocumentMimeTypes()
|
||||
Log.d(TAG, "Supported MIME types: $mimeTypes")
|
||||
supportedMimeTypes.value = mimeTypes.toTypedArray()
|
||||
} catch (e: Exception) {
|
||||
Log.e("DocumentsScreen", "Failed to load document types from native library", e)
|
||||
Log.e(TAG, "Failed to load document types from native library", e)
|
||||
availableDocumentTypes.value = emptyList()
|
||||
}
|
||||
}
|
||||
@@ -72,9 +86,8 @@ fun DocumentsScreen(onBack: () -> Unit, onDocumentClick: ((String) -> Unit)? = n
|
||||
}
|
||||
|
||||
val documents by
|
||||
remember(searchQuery, selectedDocumentTypes, selectedDateFilter) {
|
||||
remember(searchQuery, selectedDocumentTypes, selectedDateFilter, refreshTrigger) {
|
||||
derivedStateOf {
|
||||
val nativeLib = NativeLib()
|
||||
searchAndParseDocuments(
|
||||
nativeLib,
|
||||
query = searchQuery,
|
||||
@@ -88,7 +101,24 @@ fun DocumentsScreen(onBack: () -> Unit, onDocumentClick: ((String) -> Unit)? = n
|
||||
val hasActiveFilters =
|
||||
searchQuery.isNotEmpty() || selectedDocumentTypes.isNotEmpty() || selectedDateFilter != null
|
||||
|
||||
SubAppScreen(title = "Documents", onBack = onBack) {
|
||||
SubAppScreen(
|
||||
title = "Documents",
|
||||
onBack = onBack,
|
||||
actions = {
|
||||
if (supportedMimeTypes.value.isNotEmpty()) {
|
||||
DocumentFilePicker(mimeTypes = supportedMimeTypes.value) { uris: List<Uri> ->
|
||||
scope.launch {
|
||||
uris.forEach { uri ->
|
||||
importDocument(context, nativeLib, uri) { message ->
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
refreshTrigger++
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
// Search bar
|
||||
OutlinedTextField(
|
||||
value = searchQuery,
|
||||
@@ -286,6 +316,47 @@ fun DocumentsScreen(onBack: () -> Unit, onDocumentClick: ((String) -> Unit)? = n
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun importDocument(
|
||||
context: android.content.Context,
|
||||
nativeLib: NativeLib,
|
||||
uri: Uri,
|
||||
onResult: (String) -> Unit,
|
||||
) {
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
val displayName = getFileNameFromUri(context, uri) ?: "Unknown"
|
||||
val stem = displayName.substringBeforeLast(".", displayName)
|
||||
|
||||
try {
|
||||
val bytes = withContext(Dispatchers.IO) {
|
||||
context.contentResolver.openInputStream(uri)?.use { it.readBytes() }
|
||||
?: throw IllegalStateException("Could not read file")
|
||||
}
|
||||
|
||||
val docId = when {
|
||||
mimeType == "application/pdf" -> {
|
||||
Log.i(TAG, "Importing PDF: $displayName")
|
||||
nativeLib.importPdfDocument(stem, bytes)
|
||||
}
|
||||
mimeType?.startsWith("image/") == true -> {
|
||||
val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "jpg"
|
||||
Log.i(TAG, "Importing image: $displayName ($mimeType)")
|
||||
nativeLib.importImageDocument(stem, ext, bytes)
|
||||
}
|
||||
mimeType == "audio/mpeg" -> {
|
||||
Log.i(TAG, "Importing music track: $displayName")
|
||||
nativeLib.importMusicTrackDocument(stem, bytes)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unsupported file type: $mimeType")
|
||||
}
|
||||
|
||||
Log.i(TAG, "Imported $displayName as document $docId")
|
||||
withContext(Dispatchers.Main) { onResult("Imported: $displayName") }
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to import $displayName", e)
|
||||
withContext(Dispatchers.Main) { onResult("Import failed: ${e.message}") }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DocumentOverflowMenu(onSettingsClick: () -> Unit) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -5,14 +5,13 @@ import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.gregshuflin.synchronicity.ui.IconButton
|
||||
import com.gregshuflin.synchronicity.ui.IMPORT_ICON
|
||||
|
||||
@Composable
|
||||
fun PdfFilePicker(
|
||||
onFileSelected: (Uri) -> Unit,
|
||||
icon: ImageVector,
|
||||
text: String = "Select PDF File",
|
||||
text: String = "Import PDF",
|
||||
) {
|
||||
val filePickerLauncher =
|
||||
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) {
|
||||
@@ -23,7 +22,7 @@ fun PdfFilePicker(
|
||||
|
||||
IconButton(
|
||||
onClick = { filePickerLauncher.launch("application/pdf") },
|
||||
icon = icon,
|
||||
icon = IMPORT_ICON,
|
||||
text = text,
|
||||
iconContentDescription = text,
|
||||
)
|
||||
|
||||
@@ -4,8 +4,6 @@ import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.SaveAlt
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -73,8 +71,6 @@ fun PdfLibraryScreen(
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = Icons.Default.SaveAlt,
|
||||
text = "Import PDF to Library",
|
||||
)
|
||||
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.gregshuflin.synchronicity.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FileOpen
|
||||
|
||||
val IMPORT_ICON = Icons.Default.FileOpen
|
||||
+53
@@ -647,6 +647,8 @@ internal object IntegrityCheckingUniffiLib {
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_get_agents(
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_get_all_document_mime_types(
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_get_all_document_types(
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_get_document(
|
||||
@@ -669,6 +671,10 @@ internal object IntegrityCheckingUniffiLib {
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_get_theme_mode(
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_import_image_document(
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_import_music_track_document(
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_import_pdf_document(
|
||||
): Short
|
||||
external fun uniffi_synchronicity_checksum_func_init_android_logging(
|
||||
@@ -754,6 +760,8 @@ external fun uniffi_synchronicity_fn_func_generate_ensemble_key(uniffi_out_err:
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_get_agents(uniffi_out_err: UniffiRustCallStatus,
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_get_all_document_mime_types(uniffi_out_err: UniffiRustCallStatus,
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_get_all_document_types(uniffi_out_err: UniffiRustCallStatus,
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_get_document(`documentId`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus,
|
||||
@@ -776,6 +784,10 @@ external fun uniffi_synchronicity_fn_func_get_seen_nodes(uniffi_out_err: UniffiR
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_get_theme_mode(uniffi_out_err: UniffiRustCallStatus,
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_import_image_document(`fileName`: RustBuffer.ByValue,`fileExt`: RustBuffer.ByValue,`imageBytes`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus,
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_import_music_track_document(`fileName`: RustBuffer.ByValue,`musicBytes`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus,
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_import_pdf_document(`fileName`: RustBuffer.ByValue,`pdfBytes`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus,
|
||||
): RustBuffer.ByValue
|
||||
external fun uniffi_synchronicity_fn_func_init_android_logging(uniffi_out_err: UniffiRustCallStatus,
|
||||
@@ -973,6 +985,9 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) {
|
||||
if (lib.uniffi_synchronicity_checksum_func_get_agents() != 10120.toShort()) {
|
||||
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
||||
}
|
||||
if (lib.uniffi_synchronicity_checksum_func_get_all_document_mime_types() != 33749.toShort()) {
|
||||
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
||||
}
|
||||
if (lib.uniffi_synchronicity_checksum_func_get_all_document_types() != 5653.toShort()) {
|
||||
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
||||
}
|
||||
@@ -1006,6 +1021,12 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) {
|
||||
if (lib.uniffi_synchronicity_checksum_func_get_theme_mode() != 45849.toShort()) {
|
||||
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
||||
}
|
||||
if (lib.uniffi_synchronicity_checksum_func_import_image_document() != 55567.toShort()) {
|
||||
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
||||
}
|
||||
if (lib.uniffi_synchronicity_checksum_func_import_music_track_document() != 43507.toShort()) {
|
||||
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
||||
}
|
||||
if (lib.uniffi_synchronicity_checksum_func_import_pdf_document() != 42164.toShort()) {
|
||||
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
||||
}
|
||||
@@ -2796,6 +2817,16 @@ public typealias FfiConverterTypeTimestamp = FfiConverterString
|
||||
)
|
||||
}
|
||||
|
||||
fun `getAllDocumentMimeTypes`(): List<kotlin.String> {
|
||||
return FfiConverterSequenceString.lift(
|
||||
uniffiRustCall() { _status ->
|
||||
UniffiLib.uniffi_synchronicity_fn_func_get_all_document_mime_types(
|
||||
|
||||
_status)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun `getAllDocumentTypes`(): List<kotlin.String> {
|
||||
return FfiConverterSequenceString.lift(
|
||||
uniffiRustCall() { _status ->
|
||||
@@ -2921,6 +2952,28 @@ public typealias FfiConverterTypeTimestamp = FfiConverterString
|
||||
}
|
||||
|
||||
|
||||
@Throws(SyncException::class) fun `importImageDocument`(`fileName`: kotlin.String, `fileExt`: kotlin.String, `imageBytes`: kotlin.ByteArray): kotlin.String {
|
||||
return FfiConverterString.lift(
|
||||
uniffiRustCallWithError(SyncException) { _status ->
|
||||
UniffiLib.uniffi_synchronicity_fn_func_import_image_document(
|
||||
|
||||
FfiConverterString.lower(`fileName`),FfiConverterString.lower(`fileExt`),FfiConverterByteArray.lower(`imageBytes`),_status)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Throws(SyncException::class) fun `importMusicTrackDocument`(`fileName`: kotlin.String, `musicBytes`: kotlin.ByteArray): kotlin.String {
|
||||
return FfiConverterString.lift(
|
||||
uniffiRustCallWithError(SyncException) { _status ->
|
||||
UniffiLib.uniffi_synchronicity_fn_func_import_music_track_document(
|
||||
|
||||
FfiConverterString.lower(`fileName`),FfiConverterByteArray.lower(`musicBytes`),_status)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Throws(SyncException::class) fun `importPdfDocument`(`fileName`: kotlin.String, `pdfBytes`: kotlin.ByteArray): kotlin.String {
|
||||
return FfiConverterString.lift(
|
||||
uniffiRustCallWithError(SyncException) { _status ->
|
||||
|
||||
@@ -4,7 +4,7 @@ _default:
|
||||
|
||||
|
||||
TEST_ENSEMBLE_KEY := "malformed-datebook doctrine-bovine shrug-omit rigid-playback"
|
||||
TEST_ENSEMBLE_KEY_TWO := "obituary-silencer handcuff-saxophone unaligned-sanitary fancy-lifter"
|
||||
TEST_ENSEMBLE_KEY_TWO := "remnant-bullhorn ripcord-print immunity-caucus islamist-pusher"
|
||||
|
||||
# Build the Rust library for Android
|
||||
[group: "build"]
|
||||
|
||||
@@ -1140,6 +1140,100 @@ pub fn import_pdf_document(file_name: String, pdf_bytes: Vec<u8>) -> Result<Stri
|
||||
})
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn import_image_document(
|
||||
file_name: String,
|
||||
file_ext: String,
|
||||
image_bytes: Vec<u8>,
|
||||
) -> Result<String, SyncError> {
|
||||
let origin_node = local_node_pubkey_required()?;
|
||||
|
||||
let stored = with_blob_store(|bs| {
|
||||
bs.store(&image_bytes, crate::vault::DEFAULT_VAULT_ID)
|
||||
.map_err(|e| -> Box<dyn std::error::Error> { e.into() })
|
||||
})?;
|
||||
|
||||
with_document_store(|store| {
|
||||
let doc = Document::new(DocumentType::Image, None, origin_node);
|
||||
let doc_id = doc.id.clone();
|
||||
store.add_document(doc)?;
|
||||
|
||||
store.upsert_property(&Property::new(
|
||||
doc_id.clone(),
|
||||
PropertyKind::ImageTitle,
|
||||
PropertyValue::Text(file_name),
|
||||
origin_node,
|
||||
))?;
|
||||
|
||||
store.upsert_property(&Property::new(
|
||||
doc_id.clone(),
|
||||
PropertyKind::ImageFileType,
|
||||
PropertyValue::Text(file_ext),
|
||||
origin_node,
|
||||
))?;
|
||||
|
||||
store.add_blob_ref(&BlobRef::new(
|
||||
doc_id.clone(),
|
||||
stored.stored_hash,
|
||||
stored.plaintext_hash,
|
||||
Role::PrimaryContent,
|
||||
crate::vault::DEFAULT_VAULT_ID,
|
||||
image_bytes.len() as u64,
|
||||
))?;
|
||||
|
||||
Ok(doc_id.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn import_music_track_document(
|
||||
file_name: String,
|
||||
music_bytes: Vec<u8>,
|
||||
) -> Result<String, SyncError> {
|
||||
let origin_node = local_node_pubkey_required()?;
|
||||
|
||||
let stored = with_blob_store(|bs| {
|
||||
bs.store(&music_bytes, crate::vault::DEFAULT_VAULT_ID)
|
||||
.map_err(|e| -> Box<dyn std::error::Error> { e.into() })
|
||||
})?;
|
||||
|
||||
with_document_store(|store| {
|
||||
let doc = Document::new(DocumentType::MusicTrack, None, origin_node);
|
||||
let doc_id = doc.id.clone();
|
||||
store.add_document(doc)?;
|
||||
|
||||
store.upsert_property(&Property::new(
|
||||
doc_id.clone(),
|
||||
PropertyKind::MusicTrackTitle,
|
||||
PropertyValue::Text(file_name),
|
||||
origin_node,
|
||||
))?;
|
||||
|
||||
store.upsert_property(&Property::new(
|
||||
doc_id.clone(),
|
||||
PropertyKind::MusicTrackFileType,
|
||||
PropertyValue::Text("mp3".to_string()),
|
||||
origin_node,
|
||||
))?;
|
||||
|
||||
store.add_blob_ref(&BlobRef::new(
|
||||
doc_id.clone(),
|
||||
stored.stored_hash,
|
||||
stored.plaintext_hash,
|
||||
Role::PrimaryContent,
|
||||
crate::vault::DEFAULT_VAULT_ID,
|
||||
music_bytes.len() as u64,
|
||||
))?;
|
||||
|
||||
Ok(doc_id.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn get_all_document_mime_types() -> Vec<String> {
|
||||
crate::documents::import::all_document_mime_types().iter().map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn get_document_blob_bytes(document_id: String) -> Result<Vec<u8>, SyncError> {
|
||||
let blob_ref = with_document_store(|store| {
|
||||
|
||||
Reference in New Issue
Block a user