diff --git a/README.md b/README.md index d9fb619..725ab21 100644 --- a/README.md +++ b/README.md @@ -240,16 +240,16 @@ const dir = NitroFS.dirname('/path/to/file.txt') // Returns: '/path/to' ``` -#### `basename(path: string, ext?: string): string` +#### `basename(path: string): string` -Get the filename from a path, optionally removing extension. +Get the filename from a path, including the file extension. ```typescript const name = NitroFS.basename('/path/to/file.txt') // Returns: 'file.txt' -const nameWithoutExt = NitroFS.basename('/path/to/file.txt', '.txt') -// Returns: 'file' +const nameWithExt = NitroFS.basename('/path/to/document.pdf') +// Returns: 'document.pdf' ``` #### `extname(path: string): string` diff --git a/android/src/main/java/com/nitrofs/File+Extensions.kt b/android/src/main/java/com/nitrofs/File+Extensions.kt new file mode 100644 index 0000000..dfbca2d --- /dev/null +++ b/android/src/main/java/com/nitrofs/File+Extensions.kt @@ -0,0 +1,14 @@ +package com.nitrofs + +import com.margelo.nitro.nitrofs.NitroFileStat +import java.io.File + +fun File.toNitroFileStat(): NitroFileStat { + return NitroFileStat( + size = length().toDouble(), + isFile = isFile, + isDirectory = isDirectory, + ctime = lastModified().toDouble(), + mtime = lastModified().toDouble() + ) +} \ No newline at end of file diff --git a/android/src/main/java/com/nitrofs/HybridNitroFS.kt b/android/src/main/java/com/nitrofs/HybridNitroFS.kt index 8040431..a341839 100755 --- a/android/src/main/java/com/nitrofs/HybridNitroFS.kt +++ b/android/src/main/java/com/nitrofs/HybridNitroFS.kt @@ -158,9 +158,9 @@ class HybridNitroFS: HybridNitroFSSpec() { } } - override fun basename(path: String, ext: String?): String { + override fun basename(path: String): String { try { - return nitroFsImpl.basename(path, ext) + return nitroFsImpl.basename(path) } catch (e: Exception) { Log.e(TAG, "Error while calling basename(...): ${e.message}") throw Error(e) diff --git a/android/src/main/java/com/nitrofs/NitroFSImpl.kt b/android/src/main/java/com/nitrofs/NitroFSImpl.kt index d45b04c..5790205 100644 --- a/android/src/main/java/com/nitrofs/NitroFSImpl.kt +++ b/android/src/main/java/com/nitrofs/NitroFSImpl.kt @@ -2,6 +2,8 @@ package com.nitrofs import android.content.Context import android.os.Environment +import android.provider.DocumentsContract +import android.provider.OpenableColumns import android.util.Log import android.webkit.MimeTypeMap import com.facebook.react.bridge.ReactApplicationContext @@ -18,14 +20,32 @@ class NitroFSImpl(val context: ReactApplicationContext) { private val nitroFileUploader: NitroFileUploader = NitroFileUploader() private val fileDownloader: FileDownloader = FileDownloader() + private val contentResolver = context.contentResolver + fun exists(path: String): Boolean { - val dir = File(path) - return dir.exists() + val resolved = path.toResolvedPath() ?: return false + return when(resolved) { + is ResolvedPath.FilePath -> resolved.file.exists() + is ResolvedPath.Content -> { + contentResolver.query( + resolved.uri, + arrayOf("_id"), + null, + null, + null + )?.use { cursor -> cursor.moveToFirst() } ?: false + } + } } fun unlink(path: String): Boolean { - val file = File(path) - return file.deleteRecursively() + val resolved = path.toResolvedPath() ?: return false + return when(resolved) { + is ResolvedPath.FilePath -> resolved.file.deleteRecursively() + is ResolvedPath.Content -> { + contentResolver.delete(resolved.uri, null, null) > 0 + } + } } fun writeFile( @@ -129,27 +149,71 @@ class NitroFSImpl(val context: ReactApplicationContext) { srcPath: String, destPath: String ) { - val file = File(srcPath) - val dest = File(destPath) - file.copyTo(dest) + val src = srcPath.toResolvedPath() ?: throw Error("Invalid source path: $srcPath") + val dest = destPath.toResolvedPath() ?: throw Error("Invalid destination path: $destPath") + + try { + src.openInputStream(context)?.use { input -> + dest.openOutputStream(context)?.use { output -> + input.copyTo(output, DEFAULT_BUFFER_SIZE) + } + } + } catch (e: Exception) { + throw Error("Failed to copy file: ${e.message}") + } } fun mkdir(path: String): Boolean { - val file = File(path) - return file.mkdirs() + val resolved = path.toResolvedPath() ?: return false + return when(resolved) { + is ResolvedPath.FilePath -> resolved.file.mkdirs() + is ResolvedPath.Content -> throw Error("Cannot create directory from content URI: $path") + } } fun stat(path: String): NitroFileStat { - val file = File(path) - val stat = NitroFileStat( - size = file.length().toDouble(), - isFile = file.isFile, - isDirectory = file.isDirectory, - ctime = file.lastModified().toDouble(), - mtime = file.lastModified().toDouble() - ) - return stat + val resolved = path.toResolvedPath() ?: throw Error("Invalid path: $path") + return when(resolved) { + is ResolvedPath.FilePath -> resolved.file.toNitroFileStat() + is ResolvedPath.Content -> { + val uri = resolved.uri + val projection = arrayOf( + DocumentsContract.Document.COLUMN_SIZE, + DocumentsContract.Document.COLUMN_MIME_TYPE, + DocumentsContract.Document.COLUMN_LAST_MODIFIED, + ) + contentResolver.query(uri, projection, null, null, null)?.use { cursor -> + if (!cursor.moveToFirst()) { + throw Error("Unable to stat content uri (no row): $path") + } + + fun getString(col: String): String? { + val idx = cursor.getColumnIndex(col) + return if (idx >= 0 && !cursor.isNull(idx)) cursor.getString(idx) else null + } + + fun getLong(col: String): Long? { + val idx = cursor.getColumnIndex(col) + return if (idx >= 0 && !cursor.isNull(idx)) cursor.getLong(idx) else null + } + + val size = getLong(DocumentsContract.Document.COLUMN_SIZE) ?: 0L + val mime = getString(DocumentsContract.Document.COLUMN_MIME_TYPE) ?: contentResolver.getType(uri) + val isDirectory = mime == DocumentsContract.Document.MIME_TYPE_DIR + val isFile = !isDirectory + val lastModified = getLong(DocumentsContract.Document.COLUMN_LAST_MODIFIED) ?: 0L + + NitroFileStat( + size = size.toDouble(), + isFile = isFile, + isDirectory = isDirectory, + ctime = lastModified.toDouble(), + mtime = lastModified.toDouble() + ) + } ?: throw Error("Unable to stat content uri (query failed): $path") + } + } } fun readdir(path: String): Array { @@ -199,14 +263,46 @@ class NitroFSImpl(val context: ReactApplicationContext) { return file.parent ?: "" } - fun basename(path: String, ext: String?): String { - val file = File(path) - return file.nameWithoutExtension + fun basename(path: String): String { + val resolved = path.toResolvedPath() ?: throw Error("Invalid path: $path") + return when(resolved) { + is ResolvedPath.FilePath -> resolved.file.name + is ResolvedPath.Content -> { + val uri = resolved.uri + val fileName = contentResolver.query( + uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null + )?.use { cursor -> + if (cursor.moveToFirst()) { + val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (nameIndex != -1) { + cursor.getString(nameIndex) + } else { + null + } + } else { + null + } + } ?: throw Error("Unable to get file name from content URI: $path") + + fileName + } + } } fun extname(path: String): String { - val file = File(path) - return file.extension + val resolved = path.toResolvedPath() ?: throw Error("Invalid path: $path") + + return when(resolved){ + is ResolvedPath.FilePath -> resolved.file.extension + is ResolvedPath.Content -> { + val mimeType: String? = contentResolver.getType(resolved.uri) + if (mimeType != null) { + MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: throw Error("Unable to get extension from mime type: $mimeType") + } else { + throw Error("Unable to get mime type from content URI: $path") + } + } + } } fun getDocumentDir(): String { diff --git a/android/src/main/java/com/nitrofs/ResolvedPath.kt b/android/src/main/java/com/nitrofs/ResolvedPath.kt new file mode 100644 index 0000000..8746902 --- /dev/null +++ b/android/src/main/java/com/nitrofs/ResolvedPath.kt @@ -0,0 +1,57 @@ +package com.nitrofs + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import java.io.File +import androidx.core.net.toUri +import java.io.InputStream +import java.io.OutputStream + +sealed class ResolvedPath { + data class Content(val uri: Uri) : ResolvedPath() + data class FilePath(val file: File) : ResolvedPath() +} + +fun String.toResolvedPath(): ResolvedPath? { + return when { + startsWith("content://") || startsWith("file://") -> + this.toUri().toResolvedPath() + + else -> ResolvedPath.FilePath(File(this)) + } +} + +fun Uri.toResolvedPath(): ResolvedPath? { + return when (scheme) { + ContentResolver.SCHEME_CONTENT -> + ResolvedPath.Content(this) + + ContentResolver.SCHEME_FILE -> { + val p = path ?: return null + ResolvedPath.FilePath(File(p)) + } + + else -> null + } +} + +fun ResolvedPath.openInputStream(context: Context): InputStream? { + return when (this) { + is ResolvedPath.Content -> context.contentResolver.openInputStream(uri) + is ResolvedPath.FilePath -> if (file.exists()) file.inputStream() else null + } +} + +fun ResolvedPath.openOutputStream(context: Context, append: Boolean = false): OutputStream? { + return when (this) { + is ResolvedPath.Content -> { + val mode = if (append) "wa" else "w" + context.contentResolver.openOutputStream(uri, mode) + } + is ResolvedPath.FilePath -> { + file.parentFile?.mkdirs() + file.outputStream() + } + } +} diff --git a/bun.lock b/bun.lock index 9514c3b..8c39459 100644 --- a/bun.lock +++ b/bun.lock @@ -34,6 +34,7 @@ "dependencies": { "react": "19.1.0", "react-native": "0.81.0", + "react-native-nitro-document-picker": "^1.2.0", "react-native-nitro-modules": "^0.31.5", "react-native-safe-area-context": "^5.6.2", }, @@ -1571,6 +1572,8 @@ "react-native-builder-bob": ["react-native-builder-bob@0.37.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-transform-strict-mode": "^7.24.7", "@babel/preset-env": "^7.25.2", "@babel/preset-flow": "^7.24.7", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "babel-plugin-module-resolver": "^5.0.2", "browserslist": "^4.20.4", "cosmiconfig": "^9.0.0", "cross-spawn": "^7.0.3", "dedent": "^0.7.0", "del": "^6.1.1", "escape-string-regexp": "^4.0.0", "fs-extra": "^10.1.0", "glob": "^8.0.3", "is-git-dirty": "^2.0.1", "json5": "^2.2.1", "kleur": "^4.1.4", "metro-config": "^0.80.9", "prompts": "^2.4.2", "which": "^2.0.2", "yargs": "^17.5.1" }, "bin": { "bob": "bin/bob" } }, "sha512-CkM4csFrYtdGJoRLbPY6V8LBbOxgPZIuM0MkPaiOI2F/ASwxMAzoJu9wBw8Pyvx1p27XnrIEKPyDiTqimJ7xbA=="], + "react-native-nitro-document-picker": ["react-native-nitro-document-picker@1.2.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-p0MM6m4JyEQEto2IaU4BS96Npk4CMfErUNWKGDefbah4TupektuejC/Z/EzxYEltsFofjFdjFq/pvByL8xx58w=="], + "react-native-nitro-fs-example": ["react-native-nitro-fs-example@workspace:example"], "react-native-nitro-modules": ["react-native-nitro-modules@0.31.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-h/IbVsK5IH7JkvseihAoz/o5dy6CafvGo7j4jTvAa+gnxZWFtXQZg8EDvu0en88LFAumKd/pcF20dzxMiNOmug=="], diff --git a/example/App.tsx b/example/App.tsx index 764d733..60430bc 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -34,6 +34,8 @@ const AppContent = () => { getPathInfo, navigateToDirectoryType, base64Encoding, + copyImagesFromDCIMToCache, + pickDocument, } = useFileSystem(); return ( @@ -76,6 +78,8 @@ const AppContent = () => { onCopyItem={copyItem} onRenameItem={renameItem} onBase64Encoding={base64Encoding} + onCopyImagesFromDCIMToCache={copyImagesFromDCIMToCache} + onPickDocument={pickDocument} /> void; onRenameItem: (item: FileItem, newName: string) => void; onBase64Encoding: () => void; + onCopyImagesFromDCIMToCache: () => void; + onPickDocument: () => void; } export const ActionPanel: React.FC = ({ @@ -34,6 +37,8 @@ export const ActionPanel: React.FC = ({ onCopyItem, onRenameItem, onBase64Encoding, + onCopyImagesFromDCIMToCache, + onPickDocument, }) => { const [fileName, setFileName] = useState(''); const [fileContent, setFileContent] = useState(''); @@ -128,14 +133,14 @@ export const ActionPanel: React.FC = ({ Copy Item Rename Item @@ -150,6 +155,32 @@ export const ActionPanel: React.FC = ({ Base64 + + {Platform.OS === 'android' && ( + <> + + + Copy DCIM Images + + + + + )} + + + + Pick a Document + + + ); }; @@ -206,6 +237,9 @@ const styles = StyleSheet.create({ base64Button: { backgroundColor: '#dc3545', }, + copyImagesButton: { + backgroundColor: '#20c997', + }, buttonText: { color: '#fff', fontSize: 14, diff --git a/example/src/hooks/use-file-system.ts b/example/src/hooks/use-file-system.ts index 5e5dfe2..15f20f3 100644 --- a/example/src/hooks/use-file-system.ts +++ b/example/src/hooks/use-file-system.ts @@ -1,397 +1,595 @@ import { useEffect, useState } from 'react'; -import { Alert } from 'react-native'; -import NitroFS from 'react-native-nitro-fs'; +import { Alert, Platform } from 'react-native'; +import NitroFS, { NitroUploadOptions } from 'react-native-nitro-fs'; import { DirectoryType, FileItem } from '../types'; import { getMimeTypeFromExtension } from '../utils/file-utils'; +import { NitroDocumentPicker } from 'react-native-nitro-document-picker'; export const useFileSystem = () => { - const [currentPath, setCurrentPath] = useState(''); - const [files, setFiles] = useState([]); - const [loading, setLoading] = useState(false); - const [uploadProgress, setUploadProgress] = useState(0); - const [downloadProgress, setDownloadProgress] = useState(0); - const [selectedFile, setSelectedFile] = useState(null); - - useEffect(() => { - const initializeApp = async () => { - try { - setLoading(true); - await listFiles(NitroFS.DOCUMENT_DIR); - } catch (error) { - console.error('Error initializing app:', error); - Alert.alert('Error', 'Failed to initialize the app'); - } finally { - setLoading(false); - } - }; - initializeApp(); - }, []); - - - const listFiles = async (path: string) => { - try { - setLoading(true); - const fileList = await NitroFS.readdir(path); - const fileItems: FileItem[] = []; - - for (const file of fileList) { - try { - const stat = await NitroFS.stat(file.path); - fileItems.push({ - name: file.name, - path: file.path, - isDirectory: stat.isDirectory, - size: stat.size, - }); - } catch (error) { - console.error(`Error getting stat for ${file.path}:`, error); - } - } - - setFiles(fileItems); - setCurrentPath(path); - } catch (error) { - console.error('Error listing files:', error); - Alert.alert('Error', 'Failed to list files'); - } finally { - setLoading(false); - } - }; - - const createFile = async (fileName: string, content: string) => { - if (!fileName.trim()) { - Alert.alert('Error', 'Please enter a file name'); - return; - } - - try { - setLoading(true); - const filePath = currentPath ? `${currentPath}/${fileName}` : fileName; - await NitroFS.writeFile(filePath, content || 'Hello from NitroFS!', 'utf8'); - - Alert.alert('Success', 'File created successfully'); - await listFiles(currentPath); - } catch (error) { - console.error('Error creating file:', error); - Alert.alert('Error', 'Failed to create file'); - } finally { - setLoading(false); - } + const [currentPath, setCurrentPath] = useState(''); + const [files, setFiles] = useState([]); + const [loading, setLoading] = useState(false); + const [uploadProgress, setUploadProgress] = useState(0); + const [downloadProgress, setDownloadProgress] = useState(0); + const [selectedFile, setSelectedFile] = useState(null); + + useEffect(() => { + const initializeApp = async () => { + try { + setLoading(true); + await listFiles(NitroFS.DOCUMENT_DIR); + } catch (error) { + console.error('Error initializing app:', error); + Alert.alert('Error', 'Failed to initialize the app'); + } finally { + setLoading(false); + } }; + initializeApp(); + }, []); - const createDirectory = async (dirName: string) => { - if (!dirName.trim()) { - Alert.alert('Error', 'Please enter a directory name'); - return; - } + const listFiles = async (path: string) => { + try { + setLoading(true); + const fileList = await NitroFS.readdir(path); + const fileItems: FileItem[] = []; + for (const file of fileList) { try { - setLoading(true); - const dirPath = currentPath ? `${currentPath}/${dirName}` : dirName; - await NitroFS.mkdir(dirPath); - - Alert.alert('Success', 'Directory created successfully'); - await listFiles(currentPath); + const stat = await NitroFS.stat(file.path); + fileItems.push({ + name: file.name, + path: file.path, + isDirectory: stat.isDirectory, + size: stat.size, + }); } catch (error) { - console.error('Error creating directory:', error); - Alert.alert('Error', 'Failed to create directory'); - } finally { - setLoading(false); + console.error(`Error getting stat for ${file.path}:`, error); } - }; - - const deleteItem = async (item: FileItem) => { - Alert.alert('Confirm Delete', `Are you sure you want to delete "${item.name}"?`, [ - { text: 'Cancel', style: 'cancel' }, - { - text: 'Delete', - style: 'destructive', - onPress: async () => { - try { - setLoading(true); - await NitroFS.unlink(item.path); - Alert.alert('Success', 'Item deleted successfully'); - await listFiles(currentPath); - } catch (error) { - console.error('Error deleting item:', error); - Alert.alert('Error', 'Failed to delete item'); - } finally { - setLoading(false); - } - }, - }, - ]); - }; - - const readFile = async (item: FileItem) => { - try { - setLoading(true); - const content = await NitroFS.readFile(item.path, 'utf8'); - setSelectedFile(item); - Alert.alert('File Content', content.substring(0, 500) + (content.length > 500 ? '...' : '')); - return content; - } catch (error) { - console.error('Error reading file:', error); - Alert.alert('Error', 'Failed to read file'); - return ''; - } finally { - setLoading(false); - } - }; - - const saveFile = async (content: string) => { - if (!selectedFile || selectedFile.isDirectory) { - Alert.alert('Error', 'Please select a file to save'); - return; - } - - try { - setLoading(true); - await NitroFS.writeFile(selectedFile.path, content, 'utf8'); - Alert.alert('Success', 'File saved successfully'); - } catch (error) { - console.error('Error saving file:', error); - Alert.alert('Error', 'Failed to save file'); - } finally { - setLoading(false); - } - }; - - const uploadFile = async () => { - if (!selectedFile) { - Alert.alert('Error', 'Please select a file to upload'); - return; - } - - try { - setLoading(true); - setUploadProgress(0); - - const uploadOptions = { - url: 'https://httpbin.org/post', - method: 'POST' as const, - field: 'file', - }; - - const file = { - name: selectedFile.name, - mimeType: getMimeTypeFromExtension(selectedFile.name), - path: selectedFile.path, - }; - - await NitroFS.uploadFile(file, uploadOptions, (uploadedBytes, totalBytes) => { - const progress = (uploadedBytes / totalBytes) * 100; - setUploadProgress(progress); - }); - - Alert.alert('Success', 'File uploaded successfully'); - setUploadProgress(0); - } catch (error) { - console.error('Error uploading file:', error); - Alert.alert('Error', 'Failed to upload file'); - } finally { - setLoading(false); - } - }; - - const downloadFile = async () => { - try { - setLoading(true); - setDownloadProgress(0); - - const serverUrl = 'https://httpbin.org/bytes/1024'; - const destinationPath = `${NitroFS.DOWNLOAD_DIR}/downloaded_file.txt`; - - const file = await NitroFS.downloadFile(serverUrl, destinationPath, (downloadedBytes, totalBytes) => { - const progress = (downloadedBytes / totalBytes) * 100; - setDownloadProgress(progress); - }); - - Alert.alert('Success', `File downloaded successfully: ${file.name}`); - setDownloadProgress(0); - await listFiles(currentPath); - } catch (error) { - console.error('Error downloading file:', error); - Alert.alert('Error', 'Failed to download file'); - } finally { - setLoading(false); - } - }; - - const navigateToDirectory = async (item: FileItem) => { - if (item.isDirectory) { - await listFiles(item.path); - } - }; - - const navigateBack = async () => { - if (currentPath) { - const parentPath = NitroFS.dirname(currentPath); - await listFiles(parentPath); - } - }; - - const checkExists = async (path: string) => { - try { - const exists = await NitroFS.exists(path); - Alert.alert('File Exists', `${path} ${exists ? 'exists' : 'does not exist'}`); - } catch (error) { - console.error('Error checking existence:', error); - Alert.alert('Error', 'Failed to check file existence'); - } - }; - - const copyItem = async (item: FileItem, newName: string) => { - if (!newName.trim()) { - Alert.alert('Error', 'Please enter a destination name'); - return; - } - - try { - setLoading(true); - const destPath = currentPath ? `${currentPath}/${newName}` : newName; - await NitroFS.copy(item.path, destPath); - - Alert.alert('Success', 'Item copied successfully'); - await listFiles(currentPath); - } catch (error) { - console.error('Error copying item:', error); - Alert.alert('Error', 'Failed to copy item'); - } finally { - setLoading(false); - } - }; - - const renameItem = async (item: FileItem, newName: string) => { - if (!newName.trim()) { - Alert.alert('Error', 'Please enter a new name'); - return; - } - - try { - setLoading(true); - const newPath = currentPath ? `${currentPath}/${newName}` : newName; - await NitroFS.rename(item.path, newPath); - - Alert.alert('Success', 'Item renamed successfully'); - await listFiles(currentPath); - } catch (error) { - console.error('Error renaming item:', error); - Alert.alert('Error', 'Failed to rename item'); - } finally { - setLoading(false); - } - }; - - const getPathInfo = async (path: string) => { - try { - const dirname = NitroFS.dirname(path); - const basename = NitroFS.basename(path); - const extname = NitroFS.extname(path); - - const info = `Path: ${path}\nDirectory: ${dirname}\nFilename: ${basename}\nExtension: ${extname}`; - Alert.alert('Path Information', info); - } catch (error) { - console.error('Error getting path info:', error); - Alert.alert('Error', 'Failed to get path information'); - } - }; - - const navigateToDirectoryType = async (dirType: DirectoryType) => { - try { - setLoading(true); - let path = ''; - switch (dirType) { - case 'BUNDLE': - path = NitroFS.BUNDLE_DIR; - break; - case 'DOCUMENT': - path = NitroFS.DOCUMENT_DIR; - break; - case 'CACHE': - path = NitroFS.CACHE_DIR; - break; - case 'DOWNLOAD': - path = NitroFS.DOWNLOAD_DIR; - break; - case 'DCIM': - path = NitroFS.DCIM_DIR; - break; - case 'PICTURES': - path = NitroFS.PICTURES_DIR; - break; - case 'MOVIES': - path = NitroFS.MOVIES_DIR; - break; - case 'MUSIC': - path = NitroFS.MUSIC_DIR; - break; + } + + setFiles(fileItems); + setCurrentPath(path); + } catch (error) { + console.error('Error listing files:', error); + Alert.alert('Error', 'Failed to list files'); + } finally { + setLoading(false); + } + }; + + const createFile = async (fileName: string, content: string) => { + if (!fileName.trim()) { + Alert.alert('Error', 'Please enter a file name'); + return; + } + + try { + setLoading(true); + const filePath = currentPath ? `${currentPath}/${fileName}` : fileName; + await NitroFS.writeFile( + filePath, + content || 'Hello from NitroFS!', + 'utf8', + ); + + Alert.alert('Success', 'File created successfully'); + await listFiles(currentPath); + } catch (error) { + console.error('Error creating file:', error); + Alert.alert('Error', 'Failed to create file'); + } finally { + setLoading(false); + } + }; + + const createDirectory = async (dirName: string) => { + if (!dirName.trim()) { + Alert.alert('Error', 'Please enter a directory name'); + return; + } + + try { + setLoading(true); + const dirPath = currentPath ? `${currentPath}/${dirName}` : dirName; + await NitroFS.mkdir(dirPath); + + Alert.alert('Success', 'Directory created successfully'); + await listFiles(currentPath); + } catch (error) { + console.error('Error creating directory:', error); + Alert.alert('Error', 'Failed to create directory'); + } finally { + setLoading(false); + } + }; + + const deleteItem = async (item: FileItem) => { + Alert.alert( + 'Confirm Delete', + `Are you sure you want to delete "${item.name}"?`, + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Delete', + style: 'destructive', + onPress: async () => { + try { + setLoading(true); + await NitroFS.unlink(item.path); + Alert.alert('Success', 'Item deleted successfully'); + await listFiles(currentPath); + } catch (error) { + console.error('Error deleting item:', error); + Alert.alert('Error', 'Failed to delete item'); + } finally { + setLoading(false); } - await listFiles(path); - } catch (error) { - console.error('Error navigating to directory:', error); - Alert.alert('Error', 'Failed to navigate to directory'); - } finally { - setLoading(false); + }, + }, + ], + ); + }; + + const readFile = async (item: FileItem) => { + try { + setLoading(true); + const content = await NitroFS.readFile(item.path, 'utf8'); + setSelectedFile(item); + Alert.alert( + 'File Content', + content.substring(0, 500) + (content.length > 500 ? '...' : ''), + ); + return content; + } catch (error) { + console.error('Error reading file:', error); + Alert.alert('Error', 'Failed to read file'); + return ''; + } finally { + setLoading(false); + } + }; + + const saveFile = async (content: string) => { + if (!selectedFile || selectedFile.isDirectory) { + Alert.alert('Error', 'Please select a file to save'); + return; + } + + try { + setLoading(true); + await NitroFS.writeFile(selectedFile.path, content, 'utf8'); + Alert.alert('Success', 'File saved successfully'); + } catch (error) { + console.error('Error saving file:', error); + Alert.alert('Error', 'Failed to save file'); + } finally { + setLoading(false); + } + }; + + const uploadFile = async () => { + if (!selectedFile) { + Alert.alert('Error', 'Please select a file to upload'); + return; + } + + try { + setLoading(true); + setUploadProgress(0); + + const uploadOptions = { + url: 'https://httpbin.org/post', + method: 'POST' as const, + field: 'file', + }; + + const file = { + name: selectedFile.name, + mimeType: getMimeTypeFromExtension(selectedFile.name), + path: selectedFile.path, + }; + + await NitroFS.uploadFile( + file, + uploadOptions, + (uploadedBytes, totalBytes) => { + const progress = (uploadedBytes / totalBytes) * 100; + setUploadProgress(progress); + }, + ); + + Alert.alert('Success', 'File uploaded successfully'); + setUploadProgress(0); + } catch (error) { + console.error('Error uploading file:', error); + Alert.alert('Error', 'Failed to upload file'); + } finally { + setLoading(false); + } + }; + + const downloadFile = async () => { + try { + setLoading(true); + setDownloadProgress(0); + + const serverUrl = 'https://httpbin.org/bytes/1024'; + const destinationPath = `${NitroFS.DOWNLOAD_DIR}/downloaded_file.txt`; + + const file = await NitroFS.downloadFile( + serverUrl, + destinationPath, + (downloadedBytes, totalBytes) => { + const progress = (downloadedBytes / totalBytes) * 100; + setDownloadProgress(progress); + }, + ); + + Alert.alert('Success', `File downloaded successfully: ${file.name}`); + setDownloadProgress(0); + await listFiles(currentPath); + } catch (error) { + console.error('Error downloading file:', error); + Alert.alert('Error', 'Failed to download file'); + } finally { + setLoading(false); + } + }; + + const navigateToDirectory = async (item: FileItem) => { + if (item.isDirectory) { + await listFiles(item.path); + } + }; + + const navigateBack = async () => { + if (currentPath) { + const parentPath = NitroFS.dirname(currentPath); + await listFiles(parentPath); + } + }; + + const checkExists = async (path: string) => { + try { + const exists = await NitroFS.exists(path); + Alert.alert( + 'File Exists', + `${path} ${exists ? 'exists' : 'does not exist'}`, + ); + } catch (error) { + console.error('Error checking existence:', error); + Alert.alert('Error', 'Failed to check file existence'); + } + }; + + const copyItem = async (item: FileItem, newName: string) => { + if (!newName.trim()) { + Alert.alert('Error', 'Please enter a destination name'); + return; + } + + try { + setLoading(true); + const destPath = currentPath ? `${currentPath}/${newName}` : newName; + + // Check if destination already exists + const destExists = await NitroFS.exists(destPath); + if (destExists) { + Alert.alert( + 'Error', + `A file or directory with the name "${newName}" already exists`, + ); + return; + } + + // Ensure parent directory exists + const parentDir = NitroFS.dirname(destPath); + const parentExists = await NitroFS.exists(parentDir); + if (!parentExists) { + await NitroFS.mkdir(parentDir); + } + + await NitroFS.copy(item.path, destPath); + + Alert.alert('Success', 'Item copied successfully'); + await listFiles(currentPath); + } catch (error) { + console.error('Error copying item:', error); + const errorMessage = + error instanceof Error ? error.message : 'Failed to copy item'; + Alert.alert('Error', `Failed to copy item: ${errorMessage}`); + } finally { + setLoading(false); + } + }; + + const renameItem = async (item: FileItem, newName: string) => { + if (!newName.trim()) { + Alert.alert('Error', 'Please enter a new name'); + return; + } + + try { + setLoading(true); + const newPath = currentPath ? `${currentPath}/${newName}` : newName; + await NitroFS.rename(item.path, newPath); + + Alert.alert('Success', 'Item renamed successfully'); + await listFiles(currentPath); + } catch (error) { + console.error('Error renaming item:', error); + Alert.alert('Error', 'Failed to rename item'); + } finally { + setLoading(false); + } + }; + + const getPathInfo = async (path: string) => { + try { + const dirname = NitroFS.dirname(path); + const basename = NitroFS.basename(path); + const extname = NitroFS.extname(path); + + const info = `Path: ${path}\nDirectory: ${dirname}\nFilename: ${basename}\nExtension: ${extname}`; + Alert.alert('Path Information', info); + } catch (error) { + console.error('Error getting path info:', error); + Alert.alert('Error', 'Failed to get path information'); + } + }; + + const navigateToDirectoryType = async (dirType: DirectoryType) => { + try { + setLoading(true); + let path = ''; + switch (dirType) { + case 'BUNDLE': + path = NitroFS.BUNDLE_DIR; + break; + case 'DOCUMENT': + path = NitroFS.DOCUMENT_DIR; + break; + case 'CACHE': + path = NitroFS.CACHE_DIR; + break; + case 'DOWNLOAD': + path = NitroFS.DOWNLOAD_DIR; + break; + case 'DCIM': + path = NitroFS.DCIM_DIR; + break; + case 'PICTURES': + path = NitroFS.PICTURES_DIR; + break; + case 'MOVIES': + path = NitroFS.MOVIES_DIR; + break; + case 'MUSIC': + path = NitroFS.MUSIC_DIR; + break; + } + await listFiles(path); + } catch (error) { + console.error('Error navigating to directory:', error); + Alert.alert('Error', 'Failed to navigate to directory'); + } finally { + setLoading(false); + } + }; + + const base64Encoding = async () => { + try { + setLoading(true); + const Path = `${NitroFS.CACHE_DIR}/base64.bin`; + + // Create base64 encoded content (binary data) + const base64Content = 'SGVsbG8gV29ybGQhIFRoaXMgaXMgYSBiYXNlNjQgdGVzdC4='; // "Hello World! This is a base64 ." + + // Write base64 content + await NitroFS.writeFile(Path, base64Content, 'base64'); + + // Read back as base64 + const readBase64 = await NitroFS.readFile(Path, 'base64'); + + // Read as UTF-8 to verify + const readText = await NitroFS.readFile(Path, 'utf8'); + + Alert.alert( + 'Base64 Success', + `Original base64: ${base64Content}\n\n` + + `Read as base64: ${readBase64}\n\n` + + `Decoded text: ${readText}`, + ); + + // Clean up + await NitroFS.unlink(Path); + } catch (error) { + console.error('Error base64 encoding:', error); + Alert.alert('Error', 'Failed to perform base64 encoding'); + } finally { + setLoading(false); + } + }; + + const copyImagesFromDCIMToCache = async () => { + if (Platform.OS !== 'android') { + Alert.alert('Info', 'This feature is only available on Android'); + return; + } + + try { + setLoading(true); + const dcimPath = NitroFS.DCIM_DIR; + const cachePath = NitroFS.CACHE_DIR; + + // Check if DCIM directory exists + const dcimExists = await NitroFS.exists(dcimPath); + if (!dcimExists) { + Alert.alert('Error', 'DCIM directory does not exist'); + return; + } + + // List all files in DCIM directory + const files = await NitroFS.readdir(dcimPath); + + // Filter for image files + const imageMimeTypes = [ + 'image/jpeg', + 'image/jpg', + 'image/png', + 'image/gif', + 'image/bmp', + 'image/webp', + 'image/heic', + 'image/heif', + ]; + + const imageExtensions = [ + 'jpg', + 'jpeg', + 'png', + 'gif', + 'bmp', + 'webp', + 'heic', + 'heif', + ]; + + const imageFiles = files.filter(file => { + // Check mimeType first + if (imageMimeTypes.includes(file.mimeType.toLowerCase())) { + return true; } - }; - - const base64Encoding = async () => { + // Fallback to extension check + const ext = file.name.split('.').pop()?.toLowerCase(); + return ext && imageExtensions.includes(ext); + }); + + if (imageFiles.length === 0) { + Alert.alert('Info', 'No image files found in DCIM directory'); + return; + } + + let copiedCount = 0; + let failedCount = 0; + const errors: string[] = []; + + // Copy each image file + for (const imageFile of imageFiles) { try { - setLoading(true); - const Path = `${NitroFS.CACHE_DIR}/base64.bin`; - - // Create base64 encoded content (binary data) - const base64Content = 'SGVsbG8gV29ybGQhIFRoaXMgaXMgYSBiYXNlNjQgdGVzdC4='; // "Hello World! This is a base64 ." - - // Write base64 content - await NitroFS.writeFile(Path, base64Content, 'base64'); - - // Read back as base64 - const readBase64 = await NitroFS.readFile(Path, 'base64'); - - // Read as UTF-8 to verify - const readText = await NitroFS.readFile(Path, 'utf8'); - - Alert.alert('Base64 Success', - `Original base64: ${base64Content}\n\n` + - `Read as base64: ${readBase64}\n\n` + - `Decoded text: ${readText}` - ); - - // Clean up - await NitroFS.unlink(Path); + const destinationPath = `${cachePath}/${imageFile.name}`; + + // Check if file already exists in cache + const exists = await NitroFS.exists(destinationPath); + if (exists) { + // Skip if already exists, or you could add a timestamp to make it unique + console.log(`Skipping ${imageFile.name} - already exists in cache`); + continue; + } + + await NitroFS.copyFile(imageFile.path, destinationPath); + copiedCount++; + console.log(`Copied: ${imageFile.name}`); } catch (error) { - console.error('Error base64 encoding:', error); - Alert.alert('Error', 'Failed to perform base64 encoding'); - } finally { - setLoading(false); + failedCount++; + const errorMsg = `Failed to copy ${imageFile.name}: ${error}`; + errors.push(errorMsg); + console.error(errorMsg); } - }; - - return { - currentPath, - files, - loading, - uploadProgress, - downloadProgress, - selectedFile, - setSelectedFile, - listFiles, - createFile, - createDirectory, - deleteItem, - readFile, - saveFile, - uploadFile, - downloadFile, - navigateToDirectory, - navigateBack, - checkExists, - copyItem, - renameItem, - getPathInfo, - navigateToDirectoryType, - base64Encoding, - }; -}; \ No newline at end of file + } + + const message = + `Copied ${copiedCount} image(s) to cache.\n` + + (failedCount > 0 ? `Failed to copy ${failedCount} file(s).` : ''); + + if (errors.length > 0) { + console.error('Copy errors:', errors); + } + + Alert.alert('Copy Complete', message, [{ text: 'OK' }]); + } catch (error) { + console.error('Error copying images from DCIM to cache:', error); + Alert.alert('Error', `Failed to copy images: ${error}`); + } finally { + setLoading(false); + } + }; + + const pickDocument = async () => { + try { + const doc = await NitroDocumentPicker.pick({ + types: ['all'], + multiple: false, + }); + console.log('doc', doc); + // Read metadata + const stat = await NitroFS.stat(doc.uri); + console.log('name', stat, 'size', stat.size); + + // Copy the picked file into NitroFS storage + const dest = `${NitroFS.CACHE_DIR}/${doc.name ?? 'picked.bin'}`; + await NitroFS.copyFile(doc.uri, dest); + + // Verify the copy + const exists = await NitroFS.exists(dest); + console.log('copied?', exists); + + // Get file extension + const extension = NitroFS.extname(doc.uri); + console.log('extension', extension); + + // Get file name + const name = NitroFS.basename(doc.uri); + console.log('name', name); + + // upload the file to the server + const uploadOptions: NitroUploadOptions = { + url: 'https://httpbin.org/post', + method: 'POST', + field: 'file', + }; + + const nitroFile = { + name: name, + mimeType: getMimeTypeFromExtension(extension), + path: dest, + } + await NitroFS.uploadFile(nitroFile, uploadOptions, (uploadedBytes, totalBytes) => { + const progress = (uploadedBytes / totalBytes) * 100; + setUploadProgress(progress); + }); + } catch (error) { + console.error('Error picking document:', error); + Alert.alert('Error', `Failed to pick document: ${error}`); + } + }; + + return { + currentPath, + files, + loading, + uploadProgress, + downloadProgress, + selectedFile, + setSelectedFile, + listFiles, + createFile, + createDirectory, + deleteItem, + readFile, + saveFile, + uploadFile, + downloadFile, + navigateToDirectory, + navigateBack, + checkExists, + copyItem, + renameItem, + getPathInfo, + navigateToDirectoryType, + base64Encoding, + copyImagesFromDCIMToCache, + pickDocument, + }; +}; diff --git a/ios/HybridNitroFs.swift b/ios/HybridNitroFs.swift index 3fb6ded..7e85b64 100755 --- a/ios/HybridNitroFs.swift +++ b/ios/HybridNitroFs.swift @@ -147,9 +147,9 @@ class HybridNitroFS: HybridNitroFSSpec { } } - func basename(path: String, ext: String?) throws -> String { + func basename(path: String) throws -> String { do { - return try nitroFSImpl.basename(path: path, ext: ext) + return try nitroFSImpl.basename(path: path) } catch { os_log("An Error occurred in basename(...): \(error.localizedDescription)") throw RuntimeError.error(withMessage: "An Error occurred in basename(...): \(error.localizedDescription)") diff --git a/ios/NitroFSImpl+Utils.swift b/ios/NitroFSImpl+Utils.swift index f526b41..b94b971 100644 --- a/ios/NitroFSImpl+Utils.swift +++ b/ios/NitroFSImpl+Utils.swift @@ -6,18 +6,44 @@ // import NitroModules +import Foundation extension NitroFSImpl { - func getPath(path: String) throws -> String { - let pathURL: URL + /// Validates if a path string is valid and can be converted to a URL + func isValidPath(_ path: String) -> Bool { + guard !path.isEmpty else { + return false + } + + // Check if it's a file:// URI + if path.starts(with: "file://") { + return URL(string: path) != nil + } + + // For regular paths, check if they can be converted to a file URL + // Empty paths are invalid, but we allow relative paths + return true + } + + /// Converts a path string to a URL, handling both file:// URIs and regular paths + func pathToURL(_ path: String) throws -> URL { + guard isValidPath(path) else { + throw NitroFSError.encodingError(message: "Invalid path: \(path)") + } + if path.starts(with: "file://") { guard let url = URL(string: path) else { - throw RuntimeError.error(withMessage: "Failed to get path url from \(path)") + throw NitroFSError.encodingError(message: "Failed to parse file URI: \(path)") } - pathURL = url + return url } else { - pathURL = URL(fileURLWithPath: path) + return URL(fileURLWithPath: path) } + } + + /// Normalizes a path string to a standard file system path + func getPath(path: String) throws -> String { + let pathURL = try pathToURL(path) return pathURL.path } } diff --git a/ios/NitroFSImpl.swift b/ios/NitroFSImpl.swift index a2c7308..3fbb3db 100644 --- a/ios/NitroFSImpl.swift +++ b/ios/NitroFSImpl.swift @@ -19,31 +19,43 @@ class NitroFSImpl { guard let fileManager else { return false } - return fileManager.fileExists(atPath: path) + guard isValidPath(path) else { + return false + } + // Try to normalize the path, but don't throw if it fails + if let normalizedPath = try? getPath(path: path) { + return fileManager.fileExists(atPath: normalizedPath) + } + return false } func writeFile(path: String, data: String, encoding: NitroFileEncoding) throws { guard fileManager != nil else { throw NitroFSError.unavailable(message: "Failed to write file. FileManager is unavailable") } + + let normalizedPath = try getPath(path: path) if encoding == .base64 { guard let decodedData = Data(base64Encoded: data) else { throw NitroFSError.encodingError(message: "Invalid base64 data") } - try decodedData.write(to: URL(fileURLWithPath: path)) + try decodedData.write(to: URL(fileURLWithPath: normalizedPath)) } else { - try data.write(toFile: path, atomically: true, encoding: getEncoding(nitroEncoding: encoding)) + try data.write(toFile: normalizedPath, atomically: true, encoding: getEncoding(nitroEncoding: encoding)) } } func readFile(path: String, encoding: NitroFileEncoding) throws -> String { + let normalizedPath = try getPath(path: path) + let pathURL = URL(fileURLWithPath: normalizedPath) + if encoding == .base64 { - let data = try Data(contentsOf: URL(fileURLWithPath: path)) + let data = try Data(contentsOf: pathURL) return data.base64EncodedString() } - let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path)) + let fileHandle = try FileHandle(forReadingFrom: pathURL) defer { try? fileHandle.close() } var result = "" @@ -70,7 +82,10 @@ class NitroFSImpl { throw NitroFSError.unavailable(message: "Failed to copy file. FileManager is unavailable") } - try fileManager.copyItem(atPath: source, toPath: destination) + let sourcePath = try getPath(path: source) + let destPath = try getPath(path: destination) + + try fileManager.copyItem(atPath: sourcePath, toPath: destPath) } func unlink(path: String) throws { @@ -85,15 +100,16 @@ class NitroFSImpl { guard let fileManager else { throw NitroFSError.unavailable(message: "Failed to mkdir file. FileManager is unavailable") } - try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + let normalizedPath = try getPath(path: path) + try fileManager.createDirectory(atPath: normalizedPath, withIntermediateDirectories: true, attributes: nil) } func stat(path: String) throws -> NitroFileStat { guard let fileManager else { throw NitroFSError.unavailable(message: "Failed to stat file. FileManager is unavailable") } - let newPath = try getPath(path: path) - let attributes = try fileManager.attributesOfItem(atPath: newPath) + let normalizedPath = try getPath(path: path) + let attributes = try fileManager.attributesOfItem(atPath: normalizedPath) let size = attributes[FileAttributeKey.size] as! Int let mtime = attributes[FileAttributeKey.modificationDate] as! Date let ctime = attributes[FileAttributeKey.creationDate] as! Date @@ -102,8 +118,8 @@ class NitroFSImpl { size: Double(size), ctime: ctime.timeIntervalSince1970, mtime: mtime.timeIntervalSince1970, - isFile: !IsDirectory(path), - isDirectory: IsDirectory(path) + isFile: !IsDirectory(normalizedPath), + isDirectory: IsDirectory(normalizedPath) ) } @@ -111,7 +127,8 @@ class NitroFSImpl { guard let fileManager else { throw NitroFSError.unavailable(message: "Failed to stat file. FileManager is unavailable") } - let pathURL = URL(fileURLWithPath: path) + let normalizedPath = try getPath(path: path) + let pathURL = URL(fileURLWithPath: normalizedPath) let contents = try fileManager.contentsOfDirectory( at: pathURL, includingPropertiesForKeys: [.isDirectoryKey, .contentTypeKey], @@ -162,21 +179,29 @@ class NitroFSImpl { } func rename(oldPath: String, newPath: String) throws { - let oldFile = URL(fileURLWithPath: oldPath) - let newFile = URL(fileURLWithPath: newPath) - try fileManager?.moveItem(at: oldFile, to: newFile) + guard let fileManager else { + throw NitroFSError.unavailable(message: "Failed to rename file. FileManager is unavailable") + } + let normalizedOldPath = try getPath(path: oldPath) + let normalizedNewPath = try getPath(path: newPath) + let oldFile = URL(fileURLWithPath: normalizedOldPath) + let newFile = URL(fileURLWithPath: normalizedNewPath) + try fileManager.moveItem(at: oldFile, to: newFile) } func dirname(path: String) throws -> String { - return URL(fileURLWithPath: path).deletingLastPathComponent().absoluteString + let pathURL = try pathToURL(path) + return pathURL.deletingLastPathComponent().path } func extname(path: String) throws -> String { - return URL(fileURLWithPath: path).pathExtension + let pathURL = try pathToURL(path) + return pathURL.pathExtension } - func basename(path: String, ext: String?) throws -> String { - return URL(fileURLWithPath: path).lastPathComponent + func basename(path: String) throws -> String { + let pathURL = try pathToURL(path) + return pathURL.lastPathComponent } func uploadFile( diff --git a/nitrogen/generated/android/c++/JHybridNitroFSSpec.cpp b/nitrogen/generated/android/c++/JHybridNitroFSSpec.cpp index 4f9f783..b85cb2d 100644 --- a/nitrogen/generated/android/c++/JHybridNitroFSSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridNitroFSSpec.cpp @@ -28,10 +28,10 @@ namespace margelo::nitro::nitrofs { enum class NitroUploadMethod; } #include "JNitroFile.hpp" #include "NitroFileEncoding.hpp" #include "JNitroFileEncoding.hpp" -#include #include "NitroUploadOptions.hpp" #include "JNitroUploadOptions.hpp" #include "NitroUploadMethod.hpp" +#include #include "JNitroUploadMethod.hpp" #include #include "JFunc_void_double_double.hpp" @@ -277,9 +277,9 @@ namespace margelo::nitro::nitrofs { auto __result = method(_javaPart, jni::make_jstring(path)); return __result->toStdString(); } - std::string JHybridNitroFSSpec::basename(const std::string& path, const std::optional& ext) { - static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* path */, jni::alias_ref /* ext */)>("basename"); - auto __result = method(_javaPart, jni::make_jstring(path), ext.has_value() ? jni::make_jstring(ext.value()) : nullptr); + std::string JHybridNitroFSSpec::basename(const std::string& path) { + static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* path */)>("basename"); + auto __result = method(_javaPart, jni::make_jstring(path)); return __result->toStdString(); } std::string JHybridNitroFSSpec::extname(const std::string& path) { diff --git a/nitrogen/generated/android/c++/JHybridNitroFSSpec.hpp b/nitrogen/generated/android/c++/JHybridNitroFSSpec.hpp index 267864b..0a5762f 100644 --- a/nitrogen/generated/android/c++/JHybridNitroFSSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridNitroFSSpec.hpp @@ -72,7 +72,7 @@ namespace margelo::nitro::nitrofs { std::shared_ptr>> readdir(const std::string& path) override; std::shared_ptr> rename(const std::string& oldPath, const std::string& newPath) override; std::string dirname(const std::string& path) override; - std::string basename(const std::string& path, const std::optional& ext) override; + std::string basename(const std::string& path) override; std::string extname(const std::string& path) override; std::shared_ptr> uploadFile(const NitroFile& file, const NitroUploadOptions& uploadOptions, const std::optional>& onProgress) override; std::shared_ptr> downloadFile(const std::string& serverUrl, const std::string& destinationPath, const std::optional>& onProgress) override; diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofs/HybridNitroFSSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofs/HybridNitroFSSpec.kt index 6e4187f..822f7b2 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofs/HybridNitroFSSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofs/HybridNitroFSSpec.kt @@ -122,7 +122,7 @@ abstract class HybridNitroFSSpec: HybridObject() { @DoNotStrip @Keep - abstract fun basename(path: String, ext: String?): String + abstract fun basename(path: String): String @DoNotStrip @Keep diff --git a/nitrogen/generated/ios/NitroFS-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/NitroFS-Swift-Cxx-Bridge.hpp index 641ab89..5ad6404 100644 --- a/nitrogen/generated/ios/NitroFS-Swift-Cxx-Bridge.hpp +++ b/nitrogen/generated/ios/NitroFS-Swift-Cxx-Bridge.hpp @@ -245,33 +245,33 @@ namespace margelo::nitro::nitrofs::bridge::swift { return Func_void_std__vector_NitroFile__Wrapper(std::move(value)); } - // pragma MARK: std::optional + // pragma MARK: std::optional /** - * Specialized version of `std::optional`. + * Specialized version of `std::optional`. */ - using std__optional_std__string_ = std::optional; - inline std::optional create_std__optional_std__string_(const std::string& value) noexcept { - return std::optional(value); + using std__optional_NitroUploadMethod_ = std::optional; + inline std::optional create_std__optional_NitroUploadMethod_(const NitroUploadMethod& value) noexcept { + return std::optional(value); } - inline bool has_value_std__optional_std__string_(const std::optional& optional) noexcept { + inline bool has_value_std__optional_NitroUploadMethod_(const std::optional& optional) noexcept { return optional.has_value(); } - inline std::string get_std__optional_std__string_(const std::optional& optional) noexcept { + inline NitroUploadMethod get_std__optional_NitroUploadMethod_(const std::optional& optional) noexcept { return *optional; } - // pragma MARK: std::optional + // pragma MARK: std::optional /** - * Specialized version of `std::optional`. + * Specialized version of `std::optional`. */ - using std__optional_NitroUploadMethod_ = std::optional; - inline std::optional create_std__optional_NitroUploadMethod_(const NitroUploadMethod& value) noexcept { - return std::optional(value); + using std__optional_std__string_ = std::optional; + inline std::optional create_std__optional_std__string_(const std::string& value) noexcept { + return std::optional(value); } - inline bool has_value_std__optional_NitroUploadMethod_(const std::optional& optional) noexcept { + inline bool has_value_std__optional_std__string_(const std::optional& optional) noexcept { return optional.has_value(); } - inline NitroUploadMethod get_std__optional_NitroUploadMethod_(const std::optional& optional) noexcept { + inline std::string get_std__optional_std__string_(const std::optional& optional) noexcept { return *optional; } diff --git a/nitrogen/generated/ios/c++/HybridNitroFSSpecSwift.hpp b/nitrogen/generated/ios/c++/HybridNitroFSSpecSwift.hpp index 02f89bd..1ffb5e4 100644 --- a/nitrogen/generated/ios/c++/HybridNitroFSSpecSwift.hpp +++ b/nitrogen/generated/ios/c++/HybridNitroFSSpecSwift.hpp @@ -29,9 +29,9 @@ namespace margelo::nitro::nitrofs { enum class NitroUploadMethod; } #include "NitroFileStat.hpp" #include "NitroFile.hpp" #include -#include #include "NitroUploadOptions.hpp" #include "NitroUploadMethod.hpp" +#include #include #include "NitroFS-Swift-Cxx-Umbrella.hpp" @@ -197,8 +197,8 @@ namespace margelo::nitro::nitrofs { auto __value = std::move(__result.value()); return __value; } - inline std::string basename(const std::string& path, const std::optional& ext) override { - auto __result = _swiftPart.basename(path, ext); + inline std::string basename(const std::string& path) override { + auto __result = _swiftPart.basename(path); if (__result.hasError()) [[unlikely]] { std::rethrow_exception(__result.error()); } diff --git a/nitrogen/generated/ios/swift/HybridNitroFSSpec.swift b/nitrogen/generated/ios/swift/HybridNitroFSSpec.swift index 4b8fe5a..23f4d97 100644 --- a/nitrogen/generated/ios/swift/HybridNitroFSSpec.swift +++ b/nitrogen/generated/ios/swift/HybridNitroFSSpec.swift @@ -33,7 +33,7 @@ public protocol HybridNitroFSSpec_protocol: HybridObject { func readdir(path: String) throws -> Promise<[NitroFile]> func rename(oldPath: String, newPath: String) throws -> Promise func dirname(path: String) throws -> String - func basename(path: String, ext: String?) throws -> String + func basename(path: String) throws -> String func extname(path: String) throws -> String func uploadFile(file: NitroFile, uploadOptions: NitroUploadOptions, onProgress: ((_ uploadedBytes: Double, _ totalBytes: Double) -> Void)?) throws -> Promise func downloadFile(serverUrl: String, destinationPath: String, onProgress: ((_ downloadedBytes: Double, _ totalBytes: Double) -> Void)?) throws -> Promise diff --git a/nitrogen/generated/ios/swift/HybridNitroFSSpec_cxx.swift b/nitrogen/generated/ios/swift/HybridNitroFSSpec_cxx.swift index fa718e6..ed378ee 100644 --- a/nitrogen/generated/ios/swift/HybridNitroFSSpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridNitroFSSpec_cxx.swift @@ -381,16 +381,9 @@ open class HybridNitroFSSpec_cxx { } @inline(__always) - public final func basename(path: std.string, ext: bridge.std__optional_std__string_) -> bridge.Result_std__string_ { + public final func basename(path: std.string) -> bridge.Result_std__string_ { do { - let __result = try self.__implementation.basename(path: String(path), ext: { () -> String? in - if bridge.has_value_std__optional_std__string_(ext) { - let __unwrapped = bridge.get_std__optional_std__string_(ext) - return String(__unwrapped) - } else { - return nil - } - }()) + let __result = try self.__implementation.basename(path: String(path)) let __resultCpp = std.string(__result) return bridge.create_Result_std__string_(__resultCpp) } catch (let __error) { diff --git a/nitrogen/generated/shared/c++/HybridNitroFSSpec.hpp b/nitrogen/generated/shared/c++/HybridNitroFSSpec.hpp index 0277cd7..a2c7cce 100644 --- a/nitrogen/generated/shared/c++/HybridNitroFSSpec.hpp +++ b/nitrogen/generated/shared/c++/HybridNitroFSSpec.hpp @@ -28,9 +28,9 @@ namespace margelo::nitro::nitrofs { struct NitroUploadOptions; } #include "NitroFileStat.hpp" #include "NitroFile.hpp" #include -#include #include "NitroUploadOptions.hpp" #include +#include namespace margelo::nitro::nitrofs { @@ -81,7 +81,7 @@ namespace margelo::nitro::nitrofs { virtual std::shared_ptr>> readdir(const std::string& path) = 0; virtual std::shared_ptr> rename(const std::string& oldPath, const std::string& newPath) = 0; virtual std::string dirname(const std::string& path) = 0; - virtual std::string basename(const std::string& path, const std::optional& ext) = 0; + virtual std::string basename(const std::string& path) = 0; virtual std::string extname(const std::string& path) = 0; virtual std::shared_ptr> uploadFile(const NitroFile& file, const NitroUploadOptions& uploadOptions, const std::optional>& onProgress) = 0; virtual std::shared_ptr> downloadFile(const std::string& serverUrl, const std::string& destinationPath, const std::optional>& onProgress) = 0; diff --git a/src/specs/nitro-fs.nitro.ts b/src/specs/nitro-fs.nitro.ts index 231cd2a..a11b282 100755 --- a/src/specs/nitro-fs.nitro.ts +++ b/src/specs/nitro-fs.nitro.ts @@ -98,7 +98,7 @@ export interface NitroFS extends HybridObject<{ ios: 'swift', android: 'kotlin' /** * Get the filename from a path */ - basename(path: string, ext?: string): string + basename(path: string): string /** * Get the file extension from a path */