diff --git a/apps/toolbox/src/pages/fs-helper.ts b/apps/toolbox/src/pages/fs-helper.ts
index 8924d5852..a4ca20d51 100644
--- a/apps/toolbox/src/pages/fs-helper.ts
+++ b/apps/toolbox/src/pages/fs-helper.ts
@@ -109,7 +109,8 @@ export function pickFile() {
const file = args.intent.getData().toString();
//const file = File.fromPath(args.intent.getData().toString());
//console.log(file);
- readFile(file);
+ //readFile(file);
+ copyFile(file);
}
});
const Intent = android.content.Intent;
@@ -172,6 +173,41 @@ function saveFile(selected, readSync) {
console.log('==== SAVE END =========');
}
+function copyFile(file) {
+ const picked = File.fromPath(file);
+ const ext = picked.extension;
+ const name = picked.name.replace(`.${ext}`, '');
+ const tempCopy = File.fromPath(path.join(knownFolders.temp().path, `${name}-copy.${ext}`));
+
+ // const done = picked
+ // .copySync(tempCopy.path);
+ // console.log('done: ' + done + '\n' + 'Original path: ' + picked.path + '\n' + 'Copied to: ' + tempCopy.path + '\n' + 'Original size: ' + picked.size + '\n' + 'Copy size: ' + tempCopy.size + '\n');
+
+ picked
+ .copy(tempCopy.path)
+ .then((done) => {
+ console.log('done: ' + done + '\n' + 'Original path: ' + picked.path + '\n' + 'Copied to: ' + tempCopy.path + '\n' + 'Original size: ' + picked.size + '\n' + 'Copy size: ' + tempCopy.size + '\n');
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+}
+
+export function copyTest() {
+ const now = Date.now();
+ const tempFile = File.fromPath(path.join(knownFolders.temp().path, `${now}.txt`));
+ tempFile.writeTextSync('Hello World: ' + now);
+ const tempCopy = File.fromPath(path.join(knownFolders.temp().path, `${now}-copy.txt`));
+ tempFile
+ .copy(tempCopy.path)
+ .then((done) => {
+ console.log('done: ' + done + '\n' + 'Original path: ' + tempFile.path + '\n' + 'Copied to: ' + tempCopy.path + '\n' + 'Original size: ' + tempFile.size + '\n' + 'Copy size: ' + tempCopy.size + '\n');
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+}
+
function getFileNameFromContent(content: string) {
const file = getFileAccess().getFile(content);
return decodeURIComponent(file.name).split('/').pop().toLowerCase();
diff --git a/apps/toolbox/src/pages/fs-helper.xml b/apps/toolbox/src/pages/fs-helper.xml
index 000d09a5d..7b0970cd1 100644
--- a/apps/toolbox/src/pages/fs-helper.xml
+++ b/apps/toolbox/src/pages/fs-helper.xml
@@ -4,5 +4,6 @@
+
diff --git a/packages/core/file-system/file-system-access.android.ts b/packages/core/file-system/file-system-access.android.ts
index 8b96c6358..7b0c8682b 100644
--- a/packages/core/file-system/file-system-access.android.ts
+++ b/packages/core/file-system/file-system-access.android.ts
@@ -254,6 +254,42 @@ export class FileSystemAccess implements IFileSystemAccess {
return this.getLogicalRootPath() + '/app';
}
+ public copy = this.copySync.bind(this);
+
+ public copySync(src: string, dest: string, onError?: (error: any) => any) {
+ try {
+ return org.nativescript.widgets.Async.File.copySync(src, dest, getApplicationContext());
+ } catch (error) {
+ if (onError) {
+ onError(exception);
+ }
+ }
+
+ return false;
+ }
+
+ public copyAsync(src: string, dest: string): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ org.nativescript.widgets.Async.File.copy(
+ src,
+ dest,
+ new org.nativescript.widgets.Async.CompleteCallback({
+ onComplete: (result: boolean) => {
+ resolve(result);
+ },
+ onError: (err) => {
+ reject(new Error(err));
+ },
+ }),
+ getApplicationContext()
+ );
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+ }
+
public readBuffer = this.readBufferSync.bind(this);
public readBufferAsync(path: string): Promise {
diff --git a/packages/core/file-system/file-system-access.d.ts b/packages/core/file-system/file-system-access.d.ts
index cfd6f74e5..a6b9668da 100644
--- a/packages/core/file-system/file-system-access.d.ts
+++ b/packages/core/file-system/file-system-access.d.ts
@@ -2,6 +2,32 @@
* An utility class used to provide methods to access and work with the file system.
*/
export interface IFileSystemAccess {
+ /**
+ * Copies a file to a given path.
+ * @param src The path to the source file.
+ * @param dest The path to the destination file.
+ * @param onError (optional) A callback function to use if any error occurs.
+ * Returns a Promise with a boolean.
+ */
+ copy(src: string, dest: string, onError?: (error: any) => any): any;
+
+ /**
+ * Copies a file to a given path.
+ * @param src The path to the source file.
+ * @param dest The path to the destination file.
+ * Returns a Promise with a boolean.
+ */
+ copyAsync(src: string, dest: string): Promise;
+
+ /**
+ * Copies a file to a given path.
+ * @param src The path to the source file.
+ * @param dest The path to the destination file.
+ * @param onError (optional) A callback function to use if any error occurs.
+ * Returns a Promise with a boolean.
+ */
+ copySync(src: string, dest: string, onError?: (error: any) => any): any;
+
/**
* Gets the last modified date of a file with a given path.
* @param path Path to the file.
@@ -256,6 +282,12 @@ export interface IFileSystemAccess {
}
export class FileSystemAccess implements IFileSystemAccess {
+ copy(src: string, dest: string, onError?: (error: any) => any): boolean;
+
+ copySync(src: string, dest: string, onError?: (error: any) => any): boolean;
+
+ copyAsync(src: string, dest: string): Promise;
+
getLastModified(path: string): Date;
getFileSize(path: string): number;
diff --git a/packages/core/file-system/file-system-access.ios.ts b/packages/core/file-system/file-system-access.ios.ts
index 952a162b3..0258a8fbc 100644
--- a/packages/core/file-system/file-system-access.ios.ts
+++ b/packages/core/file-system/file-system-access.ios.ts
@@ -260,6 +260,65 @@ export class FileSystemAccess {
return iOSNativeHelper.getCurrentAppPath();
}
+ public copy = this.copySync.bind(this);
+
+ public copySync(src: string, dest: string, onError?: (error: any) => any) {
+ const fileManager = NSFileManager.defaultManager;
+ try {
+ return fileManager.copyItemAtPathToPathError(src, dest);
+ } catch (error) {
+ if (error.message.indexOf('exists') > -1) {
+ // check the size of file if empty remove then try copying again
+ // this could be zero due to using File.fromPath passing in a new file
+ let didRemove = false;
+ try {
+ didRemove = fileManager.removeItemAtPathError(dest);
+ return fileManager.copyItemAtPathToPathError(src, dest);
+ } catch (error) {
+ if (onError) {
+ if (didRemove) {
+ onError(error);
+ } else {
+ onError(exception);
+ }
+ }
+ }
+ }
+ if (onError) {
+ onError(exception);
+ }
+ }
+
+ return false;
+ }
+
+ public copyAsync(src: string, dest: string): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ NSData.dataWithContentsOfFileCompletion(src, (data) => {
+ if (!data) {
+ reject(new Error("Failed to read file at path '" + src));
+ } else {
+ data.writeToFileAtomicallyCompletion(dest, true, () => {
+ if (this.fileExists(dest)) {
+ const size = this.getFileSize(dest);
+ if (size === data.length) {
+ resolve(true);
+ } else {
+ reject(new Error("Failed to write file at path '" + dest));
+ }
+ } else {
+ reject(new Error("Failed to write file at path '" + dest));
+ }
+ });
+ }
+ });
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+ }
+
public readText = this.readTextSync.bind(this);
public readTextAsync(path: string, encoding?: any) {
diff --git a/packages/core/file-system/index.ts b/packages/core/file-system/index.ts
index a92226d0c..ad11ae806 100644
--- a/packages/core/file-system/index.ts
+++ b/packages/core/file-system/index.ts
@@ -211,6 +211,53 @@ export class File extends FileSystemEntity {
return getFileAccess().getFileSize(this.path);
}
+ public copy(dest: string): Promise {
+ return new Promise((resolve, reject) => {
+ try {
+ this._checkAccess();
+ } catch (ex) {
+ reject(ex);
+
+ return;
+ }
+
+ this._locked = true;
+
+ getFileAccess()
+ .copyAsync(this.path, dest)
+ .then(
+ (result) => {
+ resolve(result);
+ this._locked = false;
+ },
+ (error) => {
+ reject(error);
+ this._locked = false;
+ }
+ );
+ });
+ }
+
+ public copySync(dest: string, onError?: (error: any) => any): any {
+ this._checkAccess();
+
+ this._locked = true;
+
+ const that = this;
+ const localError = (error) => {
+ that._locked = false;
+ if (onError) {
+ onError(error);
+ }
+ };
+
+ const content = getFileAccess().copySync(this.path, dest, localError);
+
+ this._locked = false;
+
+ return content;
+ }
+
public read(): Promise {
return new Promise((resolve, reject) => {
try {
diff --git a/packages/core/platforms/android/widgets-release.aar b/packages/core/platforms/android/widgets-release.aar
index 0c1e97b59..946770ab9 100644
Binary files a/packages/core/platforms/android/widgets-release.aar and b/packages/core/platforms/android/widgets-release.aar differ
diff --git a/packages/types-android/src/lib/android/org.nativescript.widgets.d.ts b/packages/types-android/src/lib/android/org.nativescript.widgets.d.ts
index 71e57f5bd..57312ed9c 100644
--- a/packages/types-android/src/lib/android/org.nativescript.widgets.d.ts
+++ b/packages/types-android/src/lib/android/org.nativescript.widgets.d.ts
@@ -29,6 +29,10 @@
}
export module File {
+ export function copySync(src: string, dest: string, context: android.content.Context): boolean;
+ export function copy(src: java.io.InputStream, dest: java.io.OutputStream, callback: org.nativescript.widgets.Async.CompleteCallback, context: any): void;
+ export function copySync(src: java.io.InputStream, dest: java.io.OutputStream, context: any): boolean;
+ export function copy(src: string, dest: string, callback: org.nativescript.widgets.Async.CompleteCallback, context: android.content.Context): void;
export function readText(path: string, encoding: string, callback: CompleteCallback, context: any);
export function read(path: string, callback: CompleteCallback, context: any);
export function readBuffer(param0: string, param1: org.nativescript.widgets.Async.CompleteCallback, param2: any): void;
diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Async.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Async.java
index e04fc181e..a6b9320d5 100644
--- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Async.java
+++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Async.java
@@ -5,6 +5,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
import android.os.Looper;
import android.util.Base64;
import android.util.Log;
@@ -13,6 +14,7 @@ import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -29,6 +31,8 @@ import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -595,6 +599,94 @@ public class Async {
public static class File {
+ public static boolean copySync(final String src, final String dest, final Context context) throws Exception {
+ InputStream is;
+ OutputStream os;
+
+ if(src.startsWith("content://")){
+ is = context.getContentResolver().openInputStream(Uri.parse(src));
+ }else is = new FileInputStream(new java.io.File(src));
+
+ if(dest.startsWith("content://")){
+ os = context.getContentResolver().openOutputStream(Uri.parse(dest));
+ }else os = new FileOutputStream(new java.io.File(dest));
+
+ return copySync(is, os, context);
+ }
+
+ public static boolean copySync(final InputStream src, final OutputStream dest, final Object context) throws Exception {
+ ReadableByteChannel isc = java.nio.channels.Channels.newChannel(src);
+ WritableByteChannel osc = java.nio.channels.Channels.newChannel(dest);
+
+ int size = src.available();
+
+ int written = fastChannelCopy(isc, osc);
+
+ return size == written;
+ }
+
+ public static void copy(final String src, final String dest, final CompleteCallback callback, final Context context) {
+ try {
+ InputStream is;
+ OutputStream os;
+
+ if(src.startsWith("content://")){
+ is = context.getContentResolver().openInputStream(Uri.parse(src));
+ }else is = new FileInputStream(new java.io.File(src));
+
+ if(dest.startsWith("content://")){
+ os = context.getContentResolver().openOutputStream(Uri.parse(dest));
+ }else os = new FileOutputStream(new java.io.File(dest));
+
+ copy(is, os, callback, context);
+ }catch (Exception exception){
+ callback.onError(exception.getMessage(), context);
+ }
+ }
+
+ private static int fastChannelCopy(final ReadableByteChannel src,
+ final WritableByteChannel dest) throws IOException {
+ int written = 0;
+ final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
+ while (src.read(buffer) != -1) {
+ // prepare the buffer to be drained
+ buffer.flip();
+ // write to the channel, may block
+ written += dest.write(buffer);
+ // If partial transfer, shift remainder down
+ // If buffer is empty, same as doing clear()
+ buffer.compact();
+ }
+ // EOF will leave buffer in fill state
+ buffer.flip();
+ // make sure the buffer is fully drained.
+ while (buffer.hasRemaining()) {
+ written += dest.write(buffer);
+ }
+ return written;
+ }
+
+
+ public static void copy(final InputStream src, final OutputStream dest, final CompleteCallback callback, final Object context) {
+ final android.os.Handler mHandler = new android.os.Handler(Looper.myLooper());
+ threadPoolExecutor().execute((Runnable) () -> {
+
+ try (InputStream is = src; OutputStream os = dest){
+ ReadableByteChannel isc = java.nio.channels.Channels.newChannel(is);
+ WritableByteChannel osc = java.nio.channels.Channels.newChannel(os);
+
+ int size = src.available();
+
+ int written = fastChannelCopy(isc, osc);
+
+ mHandler.post(() -> callback.onComplete(size == written, context));
+
+ } catch (Exception e) {
+ mHandler.post(() -> callback.onError(e.getMessage(), context));
+ }
+ });
+ }
+
public static void readText(final String path, final String encoding, final CompleteCallback callback, final Object context) {
final android.os.Handler mHandler = new android.os.Handler(Looper.myLooper());
threadPoolExecutor().execute(new Runnable() {