diff --git a/README.md b/README.md index 53ebceffa..fd0b971d3 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ If you are interested in making a tauri-app, please visit the [documentation web - [x] renameFile - rename a file - [x] copyFile - copy a file to a new destination - [x] writeFile - write file to local filesystem +- [x] writeBinaryFile - write binary file to local filesystem - [x] readBinaryFile - read binary file from local filesystem - [x] readTextFile - read text file from local filesystem diff --git a/cli/tauri.js/api/fs/index.js b/cli/tauri.js/api/fs/index.js index 803c94f0a..5d3e80132 100644 --- a/cli/tauri.js/api/fs/index.js +++ b/cli/tauri.js/api/fs/index.js @@ -39,6 +39,56 @@ function writeFile (file, options = {}) { return tauri.writeFile(file, options) } +const CHUNK_SIZE = 65536; + +/** + * convert an Uint8Array to ascii string + * + * @param {Uint8Array} arr + * @return {String} + */ +function uint8ArrayToString(arr) { + if (arr.length < CHUNK_SIZE) { + return String.fromCharCode.apply(null, arr) + } + + let result = '' + const arrLen = arr.length + for (let i = 0; i < arrLen; i++) { + const chunk = arr.subarray(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE) + result += String.fromCharCode.apply(null, chunk) + } + return result +} + +/** + * convert an ArrayBuffer to base64 encoded string + * + * @param {ArrayBuffer} buffer + * @return {String} + */ +function arrayBufferToBase64(buffer) { + const str = uint8ArrayToString(new Uint8Array(buffer)) + return btoa(str) +} + +/** + * writes a binary file + * + * @param {Object} file + * @param {String} file.path path of the file + * @param {ArrayBuffer} file.contents contents of the file + * @param {Object} [options] configuration object + * @param {BaseDirectory} [options.dir] base directory + * @return {Promise} + */ +function writeBinaryFile(file, options = {}) { + return tauri.writeBinaryFile({ + ...file, + contents: arrayBufferToBase64(file.contents) + }, options) +} + /** * @typedef {Object} FileEntry * @property {String} path @@ -131,6 +181,7 @@ export { readTextFile, readBinaryFile, writeFile, + writeBinaryFile, readDir, createDir, removeDir, diff --git a/cli/tauri.js/templates/tauri.esm.js b/cli/tauri.js/templates/tauri.esm.js index a3b50931f..ad3eecc49 100644 --- a/cli/tauri.js/templates/tauri.esm.js +++ b/cli/tauri.js/templates/tauri.esm.js @@ -205,6 +205,28 @@ window.tauri = { <% } %> }, +<% if (ctx.dev) { %> + /** + * @name writeBinaryFile + * @description Write a binary file to the Local Filesystem. + * Permissions based on the app's PID owner + * @param {Object} cfg + * @param {String} cfg.file + * @param {String|Binary} cfg.contents + */ +<% } %> + writeBinaryFile (cfg) { + <% if (tauri.whitelist.writeBinaryFile === true || tauri.whitelist.all === true) { %> + Object.freeze(cfg) + this.invoke({ cmd: 'writeBinaryFile', file: cfg.file, contents: cfg.contents }) + <% } else { %> + <% if (ctx.dev) { %> + __whitelistWarning('writeBinaryFile') + <% } %> + return __reject + <% } %> + }, + <% if (ctx.dev) { %> /** * @name listFiles diff --git a/cli/tauri.js/templates/tauri.js b/cli/tauri.js/templates/tauri.js index e5e67a3b7..ed9c91ba1 100644 --- a/cli/tauri.js/templates/tauri.js +++ b/cli/tauri.js/templates/tauri.js @@ -43,7 +43,7 @@ switch (navigator.platform) { case "Win32": case "Win64": break; - default: + default: window.external = this invoke = function (x) { window.webkit.messageHandlers.external.postMessage(x); @@ -320,6 +320,37 @@ switch (navigator.platform) { <% } %> }, + <% if (ctx.dev) { %> + /** + * @name writeBinaryFile + * @description Write a binary file to the Local Filesystem. + * Permissions based on the app's PID owner + * @param {Object} cfg + * @param {String} cfg.file + * @param {String|Binary} cfg.contents + * @param {Object} [options] + * @param {BaseDirectory} [options.dir] + */ + <% } %> + writeBinaryFile: function writeBinaryFile(cfg, options) { + <% if (tauri.whitelist.writeBinaryFile === true || tauri.whitelist.all === true) { %> + if (_typeof(cfg) === 'object') { + Object.freeze(cfg); + } + return this.promisified({ + cmd: 'writeBinaryFile', + file: cfg.file, + contents: cfg.contents, + options: options + }); + <% } else { %> + <% if (ctx.dev) { %> + return __whitelistWarning('writeBinaryFile') + <% } %> + return __reject() + <% } %> + }, + <% if (ctx.dev) { %> /** * @name readDir @@ -343,8 +374,7 @@ switch (navigator.platform) { <% if (ctx.dev) { %> return __whitelistWarning('readDir') <% } %> - return __reject() - <% } %> + <% } %> }, <% if (ctx.dev) { %> diff --git a/tauri/src/endpoints.rs b/tauri/src/endpoints.rs index c3e22330d..2924c7eab 100644 --- a/tauri/src/endpoints.rs +++ b/tauri/src/endpoints.rs @@ -52,6 +52,16 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra } => { file_system::write_file(webview, file, contents, options, callback, error); } + #[cfg(any(feature = "all-api", feature = "write-binary-file"))] + WriteBinaryFile { + file, + contents, + options, + callback, + error, + } => { + file_system::write_binary_file(webview, file, contents, options, callback, error); + } #[cfg(any(feature = "all-api", feature = "read-dir"))] ReadDir { path, diff --git a/tauri/src/endpoints/cmd.rs b/tauri/src/endpoints/cmd.rs index 8604c7e50..ace5912ae 100644 --- a/tauri/src/endpoints/cmd.rs +++ b/tauri/src/endpoints/cmd.rs @@ -65,6 +65,14 @@ pub enum Cmd { callback: String, error: String, }, + #[cfg(any(feature = "all-api", feature = "write-binary-file"))] + WriteBinaryFile { + file: String, + contents: String, + options: Option, + callback: String, + error: String, + }, #[cfg(any(feature = "all-api", feature = "read-dir"))] ReadDir { path: String, diff --git a/tauri/src/endpoints/file_system.rs b/tauri/src/endpoints/file_system.rs index 8bf4fadff..8cce17b77 100644 --- a/tauri/src/endpoints/file_system.rs +++ b/tauri/src/endpoints/file_system.rs @@ -194,6 +194,34 @@ pub fn write_file( ); } +pub fn write_binary_file( + webview: &mut WebView<'_, T>, + file: String, + contents: String, + options: Option, + callback: String, + error: String, +) { + crate::execute_promise( + webview, + move || { + base64::decode(contents) + .map_err(|e| e.into()) + .and_then(|c| { + File::create(resolve_path(file, options.and_then(|o| o.dir))?) + .map_err(|e| e.into()) + .and_then(|mut f| { + f.write_all(&c) + .map_err(|err| err.into()) + .map(|_| "".to_string()) + }) + }) + }, + callback, + error, + ); +} + pub fn read_text_file( webview: &mut WebView<'_, T>, path: String,