Skip to content

Commit c63a50a

Browse files
authored
feat(file): copy sync and async support (#10273)
1 parent 1b17e23 commit c63a50a

File tree

9 files changed

+308
-1
lines changed

9 files changed

+308
-1
lines changed

apps/toolbox/src/pages/fs-helper.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ export function pickFile() {
109109
const file = args.intent.getData().toString();
110110
//const file = File.fromPath(args.intent.getData().toString());
111111
//console.log(file);
112-
readFile(file);
112+
//readFile(file);
113+
copyFile(file);
113114
}
114115
});
115116
const Intent = android.content.Intent;
@@ -172,6 +173,41 @@ function saveFile(selected, readSync) {
172173
console.log('==== SAVE END =========');
173174
}
174175

176+
function copyFile(file) {
177+
const picked = File.fromPath(file);
178+
const ext = picked.extension;
179+
const name = picked.name.replace(`.${ext}`, '');
180+
const tempCopy = File.fromPath(path.join(knownFolders.temp().path, `${name}-copy.${ext}`));
181+
182+
// const done = picked
183+
// .copySync(tempCopy.path);
184+
// 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');
185+
186+
picked
187+
.copy(tempCopy.path)
188+
.then((done) => {
189+
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');
190+
})
191+
.catch((error) => {
192+
console.log(error);
193+
});
194+
}
195+
196+
export function copyTest() {
197+
const now = Date.now();
198+
const tempFile = File.fromPath(path.join(knownFolders.temp().path, `${now}.txt`));
199+
tempFile.writeTextSync('Hello World: ' + now);
200+
const tempCopy = File.fromPath(path.join(knownFolders.temp().path, `${now}-copy.txt`));
201+
tempFile
202+
.copy(tempCopy.path)
203+
.then((done) => {
204+
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');
205+
})
206+
.catch((error) => {
207+
console.log(error);
208+
});
209+
}
210+
175211
function getFileNameFromContent(content: string) {
176212
const file = getFileAccess().getFile(content);
177213
return decodeURIComponent(file.name).split('/').pop().toLowerCase();

apps/toolbox/src/pages/fs-helper.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
<Button text="Create Random" tap="createRandom" />
55
<Button text="Pick File" tap="pickFile" />
66
<Button text="Pick Multiple Files" tap="pickFiles" />
7+
<Button text="Test Copy" tap="copyTest" />
78
</StackLayout>
89
</Page>

packages/core/file-system/file-system-access.android.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,42 @@ export class FileSystemAccess implements IFileSystemAccess {
254254
return this.getLogicalRootPath() + '/app';
255255
}
256256

257+
public copy = this.copySync.bind(this);
258+
259+
public copySync(src: string, dest: string, onError?: (error: any) => any) {
260+
try {
261+
return org.nativescript.widgets.Async.File.copySync(src, dest, getApplicationContext());
262+
} catch (error) {
263+
if (onError) {
264+
onError(exception);
265+
}
266+
}
267+
268+
return false;
269+
}
270+
271+
public copyAsync(src: string, dest: string): Promise<boolean> {
272+
return new Promise<boolean>((resolve, reject) => {
273+
try {
274+
org.nativescript.widgets.Async.File.copy(
275+
src,
276+
dest,
277+
new org.nativescript.widgets.Async.CompleteCallback({
278+
onComplete: (result: boolean) => {
279+
resolve(result);
280+
},
281+
onError: (err) => {
282+
reject(new Error(err));
283+
},
284+
}),
285+
getApplicationContext()
286+
);
287+
} catch (ex) {
288+
reject(ex);
289+
}
290+
});
291+
}
292+
257293
public readBuffer = this.readBufferSync.bind(this);
258294

259295
public readBufferAsync(path: string): Promise<ArrayBuffer> {

packages/core/file-system/file-system-access.d.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,32 @@
22
* An utility class used to provide methods to access and work with the file system.
33
*/
44
export interface IFileSystemAccess {
5+
/**
6+
* Copies a file to a given path.
7+
* @param src The path to the source file.
8+
* @param dest The path to the destination file.
9+
* @param onError (optional) A callback function to use if any error occurs.
10+
* Returns a Promise with a boolean.
11+
*/
12+
copy(src: string, dest: string, onError?: (error: any) => any): any;
13+
14+
/**
15+
* Copies a file to a given path.
16+
* @param src The path to the source file.
17+
* @param dest The path to the destination file.
18+
* Returns a Promise with a boolean.
19+
*/
20+
copyAsync(src: string, dest: string): Promise<any>;
21+
22+
/**
23+
* Copies a file to a given path.
24+
* @param src The path to the source file.
25+
* @param dest The path to the destination file.
26+
* @param onError (optional) A callback function to use if any error occurs.
27+
* Returns a Promise with a boolean.
28+
*/
29+
copySync(src: string, dest: string, onError?: (error: any) => any): any;
30+
531
/**
632
* Gets the last modified date of a file with a given path.
733
* @param path Path to the file.
@@ -256,6 +282,12 @@ export interface IFileSystemAccess {
256282
}
257283

258284
export class FileSystemAccess implements IFileSystemAccess {
285+
copy(src: string, dest: string, onError?: (error: any) => any): boolean;
286+
287+
copySync(src: string, dest: string, onError?: (error: any) => any): boolean;
288+
289+
copyAsync(src: string, dest: string): Promise<boolean>;
290+
259291
getLastModified(path: string): Date;
260292

261293
getFileSize(path: string): number;

packages/core/file-system/file-system-access.ios.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,65 @@ export class FileSystemAccess {
260260
return iOSNativeHelper.getCurrentAppPath();
261261
}
262262

263+
public copy = this.copySync.bind(this);
264+
265+
public copySync(src: string, dest: string, onError?: (error: any) => any) {
266+
const fileManager = NSFileManager.defaultManager;
267+
try {
268+
return fileManager.copyItemAtPathToPathError(src, dest);
269+
} catch (error) {
270+
if (error.message.indexOf('exists') > -1) {
271+
// check the size of file if empty remove then try copying again
272+
// this could be zero due to using File.fromPath passing in a new file
273+
let didRemove = false;
274+
try {
275+
didRemove = fileManager.removeItemAtPathError(dest);
276+
return fileManager.copyItemAtPathToPathError(src, dest);
277+
} catch (error) {
278+
if (onError) {
279+
if (didRemove) {
280+
onError(error);
281+
} else {
282+
onError(exception);
283+
}
284+
}
285+
}
286+
}
287+
if (onError) {
288+
onError(exception);
289+
}
290+
}
291+
292+
return false;
293+
}
294+
295+
public copyAsync(src: string, dest: string): Promise<boolean> {
296+
return new Promise<boolean>((resolve, reject) => {
297+
try {
298+
NSData.dataWithContentsOfFileCompletion(src, (data) => {
299+
if (!data) {
300+
reject(new Error("Failed to read file at path '" + src));
301+
} else {
302+
data.writeToFileAtomicallyCompletion(dest, true, () => {
303+
if (this.fileExists(dest)) {
304+
const size = this.getFileSize(dest);
305+
if (size === data.length) {
306+
resolve(true);
307+
} else {
308+
reject(new Error("Failed to write file at path '" + dest));
309+
}
310+
} else {
311+
reject(new Error("Failed to write file at path '" + dest));
312+
}
313+
});
314+
}
315+
});
316+
} catch (ex) {
317+
reject(ex);
318+
}
319+
});
320+
}
321+
263322
public readText = this.readTextSync.bind(this);
264323

265324
public readTextAsync(path: string, encoding?: any) {

packages/core/file-system/index.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,53 @@ export class File extends FileSystemEntity {
211211
return getFileAccess().getFileSize(this.path);
212212
}
213213

214+
public copy(dest: string): Promise<boolean> {
215+
return new Promise<boolean>((resolve, reject) => {
216+
try {
217+
this._checkAccess();
218+
} catch (ex) {
219+
reject(ex);
220+
221+
return;
222+
}
223+
224+
this._locked = true;
225+
226+
getFileAccess()
227+
.copyAsync(this.path, dest)
228+
.then(
229+
(result) => {
230+
resolve(result);
231+
this._locked = false;
232+
},
233+
(error) => {
234+
reject(error);
235+
this._locked = false;
236+
}
237+
);
238+
});
239+
}
240+
241+
public copySync(dest: string, onError?: (error: any) => any): any {
242+
this._checkAccess();
243+
244+
this._locked = true;
245+
246+
const that = this;
247+
const localError = (error) => {
248+
that._locked = false;
249+
if (onError) {
250+
onError(error);
251+
}
252+
};
253+
254+
const content = getFileAccess().copySync(this.path, dest, localError);
255+
256+
this._locked = false;
257+
258+
return content;
259+
}
260+
214261
public read(): Promise<any> {
215262
return new Promise<any>((resolve, reject) => {
216263
try {
Binary file not shown.

packages/types-android/src/lib/android/org.nativescript.widgets.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
}
3030

3131
export module File {
32+
export function copySync(src: string, dest: string, context: android.content.Context): boolean;
33+
export function copy(src: java.io.InputStream, dest: java.io.OutputStream, callback: org.nativescript.widgets.Async.CompleteCallback, context: any): void;
34+
export function copySync(src: java.io.InputStream, dest: java.io.OutputStream, context: any): boolean;
35+
export function copy(src: string, dest: string, callback: org.nativescript.widgets.Async.CompleteCallback, context: android.content.Context): void;
3236
export function readText(path: string, encoding: string, callback: CompleteCallback, context: any);
3337
export function read(path: string, callback: CompleteCallback, context: any);
3438
export function readBuffer(param0: string, param1: org.nativescript.widgets.Async.CompleteCallback, param2: any): void;

packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Async.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.graphics.Bitmap;
66
import android.graphics.BitmapFactory;
77
import android.graphics.drawable.BitmapDrawable;
8+
import android.net.Uri;
89
import android.os.Looper;
910
import android.util.Base64;
1011
import android.util.Log;
@@ -13,6 +14,7 @@
1314
import java.io.ByteArrayOutputStream;
1415
import java.io.Closeable;
1516
import java.io.DataInputStream;
17+
import java.io.File;
1618
import java.io.FileInputStream;
1719
import java.io.FileNotFoundException;
1820
import java.io.FileOutputStream;
@@ -29,6 +31,8 @@
2931
import java.nio.ByteBuffer;
3032
import java.nio.CharBuffer;
3133
import java.nio.channels.FileChannel;
34+
import java.nio.channels.ReadableByteChannel;
35+
import java.nio.channels.WritableByteChannel;
3236
import java.util.ArrayList;
3337
import java.util.List;
3438
import java.util.Locale;
@@ -595,6 +599,94 @@ private void closeOpenedStreams(Stack<Closeable> streams) throws IOException {
595599

596600
public static class File {
597601

602+
public static boolean copySync(final String src, final String dest, final Context context) throws Exception {
603+
InputStream is;
604+
OutputStream os;
605+
606+
if(src.startsWith("content://")){
607+
is = context.getContentResolver().openInputStream(Uri.parse(src));
608+
}else is = new FileInputStream(new java.io.File(src));
609+
610+
if(dest.startsWith("content://")){
611+
os = context.getContentResolver().openOutputStream(Uri.parse(dest));
612+
}else os = new FileOutputStream(new java.io.File(dest));
613+
614+
return copySync(is, os, context);
615+
}
616+
617+
public static boolean copySync(final InputStream src, final OutputStream dest, final Object context) throws Exception {
618+
ReadableByteChannel isc = java.nio.channels.Channels.newChannel(src);
619+
WritableByteChannel osc = java.nio.channels.Channels.newChannel(dest);
620+
621+
int size = src.available();
622+
623+
int written = fastChannelCopy(isc, osc);
624+
625+
return size == written;
626+
}
627+
628+
public static void copy(final String src, final String dest, final CompleteCallback callback, final Context context) {
629+
try {
630+
InputStream is;
631+
OutputStream os;
632+
633+
if(src.startsWith("content://")){
634+
is = context.getContentResolver().openInputStream(Uri.parse(src));
635+
}else is = new FileInputStream(new java.io.File(src));
636+
637+
if(dest.startsWith("content://")){
638+
os = context.getContentResolver().openOutputStream(Uri.parse(dest));
639+
}else os = new FileOutputStream(new java.io.File(dest));
640+
641+
copy(is, os, callback, context);
642+
}catch (Exception exception){
643+
callback.onError(exception.getMessage(), context);
644+
}
645+
}
646+
647+
private static int fastChannelCopy(final ReadableByteChannel src,
648+
final WritableByteChannel dest) throws IOException {
649+
int written = 0;
650+
final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
651+
while (src.read(buffer) != -1) {
652+
// prepare the buffer to be drained
653+
buffer.flip();
654+
// write to the channel, may block
655+
written += dest.write(buffer);
656+
// If partial transfer, shift remainder down
657+
// If buffer is empty, same as doing clear()
658+
buffer.compact();
659+
}
660+
// EOF will leave buffer in fill state
661+
buffer.flip();
662+
// make sure the buffer is fully drained.
663+
while (buffer.hasRemaining()) {
664+
written += dest.write(buffer);
665+
}
666+
return written;
667+
}
668+
669+
670+
public static void copy(final InputStream src, final OutputStream dest, final CompleteCallback callback, final Object context) {
671+
final android.os.Handler mHandler = new android.os.Handler(Looper.myLooper());
672+
threadPoolExecutor().execute((Runnable) () -> {
673+
674+
try (InputStream is = src; OutputStream os = dest){
675+
ReadableByteChannel isc = java.nio.channels.Channels.newChannel(is);
676+
WritableByteChannel osc = java.nio.channels.Channels.newChannel(os);
677+
678+
int size = src.available();
679+
680+
int written = fastChannelCopy(isc, osc);
681+
682+
mHandler.post(() -> callback.onComplete(size == written, context));
683+
684+
} catch (Exception e) {
685+
mHandler.post(() -> callback.onError(e.getMessage(), context));
686+
}
687+
});
688+
}
689+
598690
public static void readText(final String path, final String encoding, final CompleteCallback callback, final Object context) {
599691
final android.os.Handler mHandler = new android.os.Handler(Looper.myLooper());
600692
threadPoolExecutor().execute(new Runnable() {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy