Skip to content

Commit fa0c3da

Browse files
committed
feat: Offload file change detection to background thread
1 parent 6750d9c commit fa0c3da

File tree

4 files changed

+213
-41
lines changed

4 files changed

+213
-41
lines changed

bun.lock

Lines changed: 8 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/editorFile.js

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -610,26 +610,44 @@ export default class EditorFile {
610610
}
611611

612612
const protocol = Url.getProtocol(this.#uri);
613-
let fs;
613+
const text = this.session.getValue();
614+
614615
if (/s?ftp:/.test(protocol)) {
615616
// if file is a ftp or sftp file, get file content from cached file.
616617
// remove ':' from protocol because cache file of remote files are
617618
// stored as ftp102525465N i.e. protocol + id
619+
// Cache files are local file:// URIs, so native file reading works
618620
const cacheFilename = protocol.slice(0, -1) + this.id;
619-
const cacheFile = Url.join(CACHE_STORAGE, cacheFilename);
620-
fs = fsOperation(cacheFile);
621-
} else {
622-
fs = fsOperation(this.uri);
621+
const cacheFileUri = Url.join(CACHE_STORAGE, cacheFilename);
622+
623+
try {
624+
return await system.compareFileText(cacheFileUri, this.encoding, text);
625+
} catch (error) {
626+
console.error("Native compareFileText failed:", error);
627+
return false;
628+
}
623629
}
624630

631+
if (/^(file|content):/.test(protocol)) {
632+
// file:// and content:// URIs can be handled by native Android code
633+
// Native reads file AND compares in background thread
634+
try {
635+
return await system.compareFileText(this.uri, this.encoding, text);
636+
} catch (error) {
637+
console.error("Native compareFileText failed:", error);
638+
return false;
639+
}
640+
}
641+
642+
// native compares
625643
try {
644+
const fs = fsOperation(this.uri);
626645
const oldText = await fs.readFile(this.encoding);
627-
const text = this.session.getValue();
628646

629-
if (oldText.length !== text.length) return true;
630-
return oldText !== text;
647+
// Offload string comparison to background thread
648+
return await system.compareTexts(oldText, text);
631649
} catch (error) {
632-
window.log("error", error);
650+
console.error(error);
633651
return false;
634652
}
635653
}

src/plugins/system/android/com/foxdebug/system/System.java

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ public boolean execute(
169169
case "decode":
170170
case "encode":
171171
case "copyToUri":
172+
case "compare-file-text":
173+
case "compare-texts":
172174
break;
173175
case "get-configuration":
174176
getConfiguration(callbackContext);
@@ -505,6 +507,12 @@ public void run() {
505507
case "encode":
506508
encode(arg1, arg2, callbackContext);
507509
break;
510+
case "compare-file-text":
511+
compareFileText(arg1, arg2, arg3, callbackContext);
512+
break;
513+
case "compare-texts":
514+
compareTexts(arg1, arg2, callbackContext);
515+
break;
508516
default:
509517
break;
510518
}
@@ -616,6 +624,137 @@ private void encode(
616624
}
617625
}
618626

627+
/**
628+
* Compares file content with provided text.
629+
* This method runs in a background thread to avoid blocking the UI.
630+
*
631+
* @param fileUri The URI of the file to read (file:// or content://)
632+
* @param encoding The character encoding to use when reading the file
633+
* @param currentText The text to compare against the file content
634+
* @param callback Returns 1 if texts are different, 0 if same
635+
*/
636+
private void compareFileText(
637+
String fileUri,
638+
String encoding,
639+
String currentText,
640+
CallbackContext callback
641+
) {
642+
try {
643+
if (fileUri == null || fileUri.isEmpty()) {
644+
callback.error("File URI is required");
645+
return;
646+
}
647+
648+
if (encoding == null || encoding.isEmpty()) {
649+
encoding = "UTF-8";
650+
}
651+
652+
if (!Charset.isSupported(encoding)) {
653+
callback.error("Charset not supported: " + encoding);
654+
return;
655+
}
656+
657+
Uri uri = Uri.parse(fileUri);
658+
InputStream inputStream = null;
659+
String fileContent;
660+
661+
try {
662+
// Handle both file:// and content:// URIs
663+
if ("file".equalsIgnoreCase(uri.getScheme())) {
664+
File file = new File(uri.getPath());
665+
if (!file.exists()) {
666+
callback.error("File does not exist");
667+
return;
668+
}
669+
inputStream = new FileInputStream(file);
670+
} else {
671+
inputStream = context.getContentResolver().openInputStream(uri);
672+
}
673+
674+
if (inputStream == null) {
675+
callback.error("Cannot open file");
676+
return;
677+
}
678+
679+
// Read file content with specified encoding
680+
Charset charset = Charset.forName(encoding);
681+
byte[] bytes = new byte[inputStream.available()];
682+
int totalRead = 0;
683+
int bytesRead;
684+
685+
// Read in chunks to handle large files
686+
java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream();
687+
byte[] chunk = new byte[8192];
688+
while ((bytesRead = inputStream.read(chunk)) != -1) {
689+
buffer.write(chunk, 0, bytesRead);
690+
}
691+
bytes = buffer.toByteArray();
692+
693+
CharBuffer charBuffer = charset.decode(ByteBuffer.wrap(bytes));
694+
fileContent = charBuffer.toString();
695+
696+
} finally {
697+
if (inputStream != null) {
698+
try {
699+
inputStream.close();
700+
} catch (IOException ignored) {}
701+
}
702+
}
703+
704+
// check length first
705+
if (fileContent.length() != currentText.length()) {
706+
callback.success(1); // Changed
707+
return;
708+
}
709+
710+
// Full comparison
711+
if (fileContent.equals(currentText)) {
712+
callback.success(0); // Not changed
713+
} else {
714+
callback.success(1); // Changed
715+
}
716+
717+
} catch (Exception e) {
718+
callback.error(e.toString());
719+
}
720+
}
721+
722+
/**
723+
* Compares two text strings.
724+
* This method runs in a background thread to avoid blocking the UI
725+
* for large string comparisons.
726+
*
727+
* @param text1 First text to compare
728+
* @param text2 Second text to compare
729+
* @param callback Returns 1 if texts are different, 0 if same
730+
*/
731+
private void compareTexts(
732+
String text1,
733+
String text2,
734+
CallbackContext callback
735+
) {
736+
try {
737+
if (text1 == null) text1 = "";
738+
if (text2 == null) text2 = "";
739+
740+
// check length first
741+
if (text1.length() != text2.length()) {
742+
callback.success(1); // Changed
743+
return;
744+
}
745+
746+
// Full comparison
747+
if (text1.equals(text2)) {
748+
callback.success(0); // Not changed
749+
} else {
750+
callback.success(1); // Changed
751+
}
752+
753+
} catch (Exception e) {
754+
callback.error(e.toString());
755+
}
756+
}
757+
619758
private void getAvailableEncodings(CallbackContext callback) {
620759
try {
621760
Map < String, Charset > charsets = Charset.availableCharsets();

src/plugins/system/www/plugin.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,44 @@ module.exports = {
155155
},
156156
getGlobalSetting: function (key, onSuccess, onFail) {
157157
cordova.exec(onSuccess, onFail, 'System', 'get-global-setting', [key]);
158+
},
159+
/**
160+
* Compare file content with provided text in a background thread.
161+
* @param {string} fileUri - The URI of the file to read
162+
* @param {string} encoding - The character encoding to use
163+
* @param {string} currentText - The text to compare against
164+
* @returns {Promise<boolean>} - Resolves to true if content differs, false if same
165+
*/
166+
compareFileText: function (fileUri, encoding, currentText) {
167+
return new Promise((resolve, reject) => {
168+
cordova.exec(
169+
function(result) {
170+
resolve(result === 1);
171+
},
172+
reject,
173+
'System',
174+
'compare-file-text',
175+
[fileUri, encoding, currentText]
176+
);
177+
});
178+
},
179+
/**
180+
* Compare two text strings in a background thread.
181+
* @param {string} text1 - First text to compare
182+
* @param {string} text2 - Second text to compare
183+
* @returns {Promise<boolean>} - Resolves to true if texts differ, false if same
184+
*/
185+
compareTexts: function (text1, text2) {
186+
return new Promise((resolve, reject) => {
187+
cordova.exec(
188+
function(result) {
189+
resolve(result === 1);
190+
},
191+
reject,
192+
'System',
193+
'compare-texts',
194+
[text1, text2]
195+
);
196+
});
158197
}
159198
};

0 commit comments

Comments
 (0)