From fc52995af55efd0fc1caef9c9f0c02e014150613 Mon Sep 17 00:00:00 2001 From: "Stefan Hegny (hydrografix Consulting GmbH)" Date: Thu, 12 Sep 2024 10:36:22 +0200 Subject: [PATCH 1/7] fix dependency --- plugin.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin.xml b/plugin.xml index 5d91df8..d0a684f 100644 --- a/plugin.xml +++ b/plugin.xml @@ -29,4 +29,5 @@ + From 16222e4f3ccdb0d68e58b255d6267b01ef57c200 Mon Sep 17 00:00:00 2001 From: "Stefan Hegny (hydrografix Consulting GmbH)" Date: Thu, 12 Sep 2024 10:43:27 +0200 Subject: [PATCH 2/7] add copyFile function --- README.md | 12 ++- .../saf_mediastore/SafMediastore.java | 78 ++++++++++++++++++- www/safMediastore.d.ts | 8 +- www/safMediastore.js | 3 +- 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a29a068..7cc6f62 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,16 @@ writeFile(params:{ ``` Writes a file to a specific filename, with the folder and subfolder being optional. The subfolder will be created if it does not exist, and the default folder is the Downloads folder (saved via Mediastore). Returns the content URI. ```data``` is a Base 64 string. +```typescript +copyFile(params:{ + srcfile:string, + filename:string, + folder?:string, + subFolder?:string +}):Promise +``` +Copies srcfile's contents to a specific filename, with the folder and subfolder being optional. The subfolder will be created if it does not exist, and the default folder is the Downloads folder (saved via Mediastore). Returns the content URI. ```srcfile``` is cordova source file name. It may be more memory friendly than handling hundreds of MB of base64. + ```typescript overwriteFile(params:{ uri:string, @@ -81,4 +91,4 @@ To call methods: ```typescript cordova.plugins.safMediastore.(params); //returns a Promise await cordova.plugins.safMediastore.(params); //in an async function -``` \ No newline at end of file +``` diff --git a/src/android/com/customautosys/saf_mediastore/SafMediastore.java b/src/android/com/customautosys/saf_mediastore/SafMediastore.java index 8e61077..2efb2b4 100644 --- a/src/android/com/customautosys/saf_mediastore/SafMediastore.java +++ b/src/android/com/customautosys/saf_mediastore/SafMediastore.java @@ -153,6 +153,82 @@ public boolean readFile(JSONArray args,CallbackContext callbackContext){ } } + public boolean copyFile(JSONArray args,CallbackContext callbackContext){ + try{ + JSONObject params=args.getJSONObject(0); + String filename=params.getString("filename"); + String mimeType=MimeTypeMap.getSingleton().getMimeTypeFromExtension(filename.substring(filename.lastIndexOf('.')+1)); + if(mimeType==null)mimeType="*/*"; + String folder=null; + try{ + if(!params.isNull("folder"))folder=params.getString("folder"); + }catch(Exception e){ + debugLog(e); + } + String subFolder=""; + try{ + if(!params.isNull("subFolder"))subFolder=params.getString("subFolder"); + }catch(Exception e){ + debugLog(e); + } + Uri uri=null; + if(folder!=null&&!folder.trim().equals("")){ + DocumentFile documentFile=DocumentFile.fromTreeUri( + cordovaInterface.getContext(), + Uri.parse(folder) + ); + if(subFolder!=null){ + String subFolders[]=subFolder.split("/"); + for(int i=0;i 0) { + outputStream.write(buff, off, rr); + } + } + callbackContext.success(uri.toString()); + return true; + }catch(Exception e){ + callbackContext.error(debugLog(e)); + return false; + } + } + public boolean writeFile(JSONArray args,CallbackContext callbackContext){ try{ JSONObject params=args.getJSONObject(0); @@ -447,4 +523,4 @@ public String debugLog(String message){ @Override public void onReceiveValue(String value){} -} \ No newline at end of file +} diff --git a/www/safMediastore.d.ts b/www/safMediastore.d.ts index c1d72dc..7ee1b1b 100644 --- a/www/safMediastore.d.ts +++ b/www/safMediastore.d.ts @@ -4,6 +4,12 @@ interface SafMediastore{ openFolder(uri:string):Promise, openFile(uri:string):Promise, readFile(uri:string):Promise, + copyFile(params:{ + data:string, + filename:string, + folder?:string, + subFolder?:string + }):Promise, writeFile(params:{ data:string, filename:string, @@ -30,4 +36,4 @@ interface SafMediastore{ interface CordovaPlugins{ safMediastore:SafMediastore -} \ No newline at end of file +} diff --git a/www/safMediastore.js b/www/safMediastore.js index 8e1699f..0eb8122 100644 --- a/www/safMediastore.js +++ b/www/safMediastore.js @@ -25,6 +25,7 @@ module.exports=(function(){ 'openFile', 'readFile', 'writeFile', + 'copyFile', 'overwriteFile', 'saveFile', 'deleteFile', @@ -33,4 +34,4 @@ module.exports=(function(){ ].forEach(action=>exports[action]=callPromise(action)); return exports; -})(); \ No newline at end of file +})(); From 7cc7cb96faab1f93f73ecd2c3cb64793c01dba76 Mon Sep 17 00:00:00 2001 From: "Stefan Hegny (hydrografix Consulting GmbH)" Date: Thu, 12 Sep 2024 11:54:33 +0200 Subject: [PATCH 3/7] fix handling with no subFolder given --- .../com/customautosys/saf_mediastore/SafMediastore.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/android/com/customautosys/saf_mediastore/SafMediastore.java b/src/android/com/customautosys/saf_mediastore/SafMediastore.java index 2efb2b4..0baad7c 100644 --- a/src/android/com/customautosys/saf_mediastore/SafMediastore.java +++ b/src/android/com/customautosys/saf_mediastore/SafMediastore.java @@ -177,7 +177,7 @@ public boolean copyFile(JSONArray args,CallbackContext callbackContext){ cordovaInterface.getContext(), Uri.parse(folder) ); - if(subFolder!=null){ + if(subFolder!=""){ String subFolders[]=subFolder.split("/"); for(int i=0;i Date: Thu, 12 Sep 2024 13:23:43 +0200 Subject: [PATCH 4/7] improve error handling --- .../com/customautosys/saf_mediastore/SafMediastore.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/android/com/customautosys/saf_mediastore/SafMediastore.java b/src/android/com/customautosys/saf_mediastore/SafMediastore.java index 0baad7c..83b766d 100644 --- a/src/android/com/customautosys/saf_mediastore/SafMediastore.java +++ b/src/android/com/customautosys/saf_mediastore/SafMediastore.java @@ -224,7 +224,8 @@ public boolean copyFile(JSONArray args,CallbackContext callbackContext){ callbackContext.success(uri.toString()); return true; }catch(Exception e){ - callbackContext.error(debugLog(e)); + debugLog(e); + callbackContext.error(e.toString()); return false; } } @@ -292,7 +293,8 @@ public boolean writeFile(JSONArray args,CallbackContext callbackContext){ callbackContext.success(uri.toString()); return true; }catch(Exception e){ - callbackContext.error(debugLog(e)); + debugLog(e); + callbackContext.error(e.toString()); return false; } } From b926bf0d7694e7d216dd198ecbb200cb7e8b9b34 Mon Sep 17 00:00:00 2001 From: "Stefan Hegny (hydrografix Consulting GmbH)" Date: Thu, 12 Sep 2024 13:30:21 +0200 Subject: [PATCH 5/7] doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cc6f62..0b05b00 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Read and save files using the Storage Access Framework and Mediastore -This plugin allows you to read and save files using the Storage Access Framework and Mediastore on Android only. +This plugin allows you to read and save files using the Storage Access Framework and Mediastore on Android only. Added copy from file method and some fixes. ## Available methods From 73128e441ae1224442778a791c9287d8ca5d9e6e Mon Sep 17 00:00:00 2001 From: "Stefan Hegny (hydrografix Consulting GmbH)" Date: Mon, 7 Oct 2024 11:43:15 +0200 Subject: [PATCH 6/7] copy file: throw File exists to enable renaming by caller --- .../saf_mediastore/SafMediastore.java | 94 ++++++++++++++++--- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/src/android/com/customautosys/saf_mediastore/SafMediastore.java b/src/android/com/customautosys/saf_mediastore/SafMediastore.java index 83b766d..369e8b4 100644 --- a/src/android/com/customautosys/saf_mediastore/SafMediastore.java +++ b/src/android/com/customautosys/saf_mediastore/SafMediastore.java @@ -32,6 +32,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.FileNotFoundException; import java.io.StringWriter; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -158,6 +159,10 @@ public boolean copyFile(JSONArray args,CallbackContext callbackContext){ JSONObject params=args.getJSONObject(0); String filename=params.getString("filename"); String mimeType=MimeTypeMap.getSingleton().getMimeTypeFromExtension(filename.substring(filename.lastIndexOf('.')+1)); + boolean forceoverwrite = false; + try { + forceoverwrite = params.getBoolean("overwrite"); + } catch(Exception ex) { ; } if(mimeType==null)mimeType="*/*"; String folder=null; try{ @@ -210,20 +215,79 @@ public boolean copyFile(JSONArray args,CallbackContext callbackContext){ contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_DOWNLOADS+subFolder); uri=contentResolver.insert(MediaStore.Files.getContentUri("external"),contentValues); } - int off = 0; - int len = 0; - int rr = -1; - byte[] buff = new byte[1000000]; - try( - InputStream inputStream=cordovaInterface.getContext().getContentResolver().openInputStream(Uri.parse(params.getString("srcfile"))); - OutputStream outputStream=cordovaInterface.getContext().getContentResolver().openOutputStream(uri,"wt")){ - while ((rr = inputStream.read(buff, off, 1000000)) > 0) { - outputStream.write(buff, off, rr); + /*************/ + InputStream ism = null; + try { + boolean nfound = false; + ism = cordovaInterface.getContext().getContentResolver().openInputStream(uri); + if (ism == null) nfound = true; + else { /* got input stream */ + byte[] b = new byte[2]; + int r = ism.read(b, 0, 2); + if (r < 2) nfound = true; + else { /* at least two bytes in file */ + try { + ism.close(); + }catch(Exception e){debugLog("saf copy exc close read " + e.toString());} + callbackContext.error("File exists"); + //debugLog("E X X X 1"); + return false; + } + //debugLog("E X X X 2"); + try { + ism.close(); + }catch(Exception e){debugLog("saf copy exc close empty " + e.toString());} + } + if (!nfound && !forceoverwrite) { + debugLog("saf copy closed"); + callbackContext.error("File exists"); + return false; + } else { + if (forceoverwrite) { + debugLog("saf copy overwrite forced"); + } + int off = 0; + int len = 0; + int rr = -1; + byte[] buff = new byte[1000000]; + try( + InputStream inputStream=cordovaInterface.getContext().getContentResolver().openInputStream(Uri.parse(params.getString("srcfile"))); + OutputStream outputStream=cordovaInterface.getContext().getContentResolver().openOutputStream(uri,"wt")){ + while ((rr = inputStream.read(buff, off, 1000000)) > 0) { + outputStream.write(buff, off, rr); + } + //debugLog("E X X X 5"); + callbackContext.success(uri.toString()); + return true; + }catch(Exception e) { + debugLog("saf copy exc copy empty " + e.toString()); + callbackContext.error(e.toString()); + return false; + } + } + } + catch (FileNotFoundException e) { + debugLog("saf copy filenotfound " + e.toString()); + int off = 0; + int len = 0; + int rr = -1; + byte[] buff = new byte[1000000]; + try( + InputStream inputStream=cordovaInterface.getContext().getContentResolver().openInputStream(Uri.parse(params.getString("srcfile"))); + OutputStream outputStream=cordovaInterface.getContext().getContentResolver().openOutputStream(uri,"wt")){ + while ((rr = inputStream.read(buff, off, 1000000)) > 0) { + outputStream.write(buff, off, rr); + } + callbackContext.success(uri.toString()); + return true; + }catch(Exception ex2) { + debugLog("saf copy exc copy fnf " + ex2.toString()); + callbackContext.error(ex2.toString()); + return false; } } - callbackContext.success(uri.toString()); - return true; }catch(Exception e){ + debugLog("saf copy exception"); debugLog(e); callbackContext.error(e.toString()); return false; @@ -483,7 +547,7 @@ public String debugLog(Throwable throwable){ throwable.printStackTrace(printWriter); String stackTrace=stringWriter.toString(); Log.d(throwable.getLocalizedMessage(),stackTrace,throwable); - cordovaWebView.getEngine().evaluateJavascript( + /*cordovaWebView.getEngine().evaluateJavascript( "console.log('" +stackTrace.replace( "'", "\\'" @@ -495,7 +559,7 @@ public String debugLog(Throwable throwable){ "\\t" )+"');", this - ); + );*/ printWriter.close(); stringWriter.close(); return stackTrace; @@ -507,7 +571,7 @@ public String debugLog(Throwable throwable){ public String debugLog(String message){ Log.d(getClass().getName(),message); - cordovaWebView.getEngine().evaluateJavascript( + /*cordovaWebView.getEngine().evaluateJavascript( "console.log('" +message.replace( "'", "\\'" @@ -519,7 +583,7 @@ public String debugLog(String message){ "\\t" )+"');", this - ); + );*/ return message; } From c35e0205477389cb8c3a8d5be7e8190ac50540fd Mon Sep 17 00:00:00 2001 From: "Stefan Hegny (hydrografix Consulting GmbH)" Date: Mon, 7 Oct 2024 15:05:04 +0200 Subject: [PATCH 7/7] fix: copy file: throw File exists to enable renaming by caller --- .../com/customautosys/saf_mediastore/SafMediastore.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/android/com/customautosys/saf_mediastore/SafMediastore.java b/src/android/com/customautosys/saf_mediastore/SafMediastore.java index 369e8b4..59df7db 100644 --- a/src/android/com/customautosys/saf_mediastore/SafMediastore.java +++ b/src/android/com/customautosys/saf_mediastore/SafMediastore.java @@ -163,6 +163,7 @@ public boolean copyFile(JSONArray args,CallbackContext callbackContext){ try { forceoverwrite = params.getBoolean("overwrite"); } catch(Exception ex) { ; } + //debugLog(forceoverwrite ? "saf OVERWRITE T " : "saf OVERWRITE F "); if(mimeType==null)mimeType="*/*"; String folder=null; try{ @@ -224,7 +225,7 @@ public boolean copyFile(JSONArray args,CallbackContext callbackContext){ else { /* got input stream */ byte[] b = new byte[2]; int r = ism.read(b, 0, 2); - if (r < 2) nfound = true; + if (r < 2 || forceoverwrite) nfound = true; else { /* at least two bytes in file */ try { ism.close();