diff --git a/src/lib/editorFile.js b/src/lib/editorFile.js index 3ae657299..bf44701dc 100644 --- a/src/lib/editorFile.js +++ b/src/lib/editorFile.js @@ -612,39 +612,55 @@ export default class EditorFile { const protocol = Url.getProtocol(this.#uri); const text = this.session.getValue(); + // Helper for JS-based comparison (used as fallback) + const jsCompare = async (fileUri) => { + const fs = fsOperation(fileUri); + const oldText = await fs.readFile(this.encoding); + return await system.compareTexts(oldText, text); + }; + if (/s?ftp:/.test(protocol)) { - // if file is a ftp or sftp file, get file content from cached file. - // remove ':' from protocol because cache file of remote files are - // stored as ftp102525465N i.e. protocol + id - // Cache files are local file:// URIs, so native file reading works + // FTP/SFTP files use cached local file const cacheFilename = protocol.slice(0, -1) + this.id; const cacheFileUri = Url.join(CACHE_STORAGE, cacheFilename); try { return await system.compareFileText(cacheFileUri, this.encoding, text); } catch (error) { - console.error("Native compareFileText failed:", error); - return false; + console.error( + "Native compareFileText failed, using JS fallback:", + error, + ); + try { + return await jsCompare(cacheFileUri); + } catch (fallbackError) { + console.error(fallbackError); + return false; + } } } if (/^(file|content):/.test(protocol)) { - // file:// and content:// URIs can be handled by native Android code - // Native reads file AND compares in background thread + // file:// and content:// URIs - try native first, fallback to JS try { return await system.compareFileText(this.uri, this.encoding, text); } catch (error) { - console.error("Native compareFileText failed:", error); - return false; + console.error( + "Native compareFileText failed, using JS fallback:", + error, + ); + try { + return await jsCompare(this.uri); + } catch (fallbackError) { + console.error(fallbackError); + return false; + } } } + // Other protocols - JS reads file, native compares strings try { - const fs = fsOperation(this.uri); - const oldText = await fs.readFile(this.encoding); - - // Offload string comparison to background thread - return await system.compareTexts(oldText, text); + return await jsCompare(this.uri); } catch (error) { console.error(error); return false; diff --git a/src/plugins/system/android/com/foxdebug/system/System.java b/src/plugins/system/android/com/foxdebug/system/System.java index 520653b2c..7ddf6c547 100644 --- a/src/plugins/system/android/com/foxdebug/system/System.java +++ b/src/plugins/system/android/com/foxdebug/system/System.java @@ -89,6 +89,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; +import android.provider.DocumentsContract; import android.provider.Settings; import androidx.core.content.ContextCompat; @@ -681,11 +682,32 @@ private void compareFileText( Path path = file.toPath(); fileContent = new String(Files.readAllBytes(path), charset); - } else { - // Handle content:// URIs + } else if ("content".equalsIgnoreCase(uri.getScheme())) { + // Handle content:// URIs (including SAF tree URIs) InputStream inputStream = null; try { - inputStream = context.getContentResolver().openInputStream(uri); + String uriString = fileUri; + Uri resolvedUri = uri; + + // Check if this is a SAF tree URI with :: separator + if (uriString.contains("::")) { + try { + // Split into tree URI and document ID + String[] parts = uriString.split("::", 2); + String treeUriStr = parts[0]; + String docId = parts[1]; + + // Build document URI directly from tree URI and document ID + Uri treeUri = Uri.parse(treeUriStr); + resolvedUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, docId); + } catch (Exception e) { + callback.error("SAF_FALLBACK: Invalid SAF URI format - " + e.getMessage()); + return; + } + } + + // Try to open the resolved URI + inputStream = context.getContentResolver().openInputStream(resolvedUri); if (inputStream == null) { callback.error("Cannot open file"); @@ -710,6 +732,9 @@ private void compareFileText( } catch (IOException ignored) {} } } + } else { + callback.error("Unsupported URI scheme: " + uri.getScheme()); + return; } // check length first