Skip to content

Commit 18bba2b

Browse files
feat(file-system): allow copy when opening a File (NativeScript#10274)
* feat: add copy file to file-system * feat(file-system): allow temp copy of files opened with File.fromPath * chore: remove log * chore: remove log * fix: only copy if true --------- Co-authored-by: Nathan Walker <walkerrunpdx@gmail.com>
1 parent 4551da0 commit 18bba2b

File tree

7 files changed

+103
-33
lines changed

7 files changed

+103
-33
lines changed

apps/automated/src/file-system/file-system-tests.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,3 +698,24 @@ export function test_FolderClear_RemovesEmptySubfolders(done) {
698698
})
699699
.catch(done);
700700
}
701+
702+
export function test_FileCopy(done) {
703+
const now = Date.now();
704+
const tempFile = fs.File.fromPath(fs.path.join(fs.knownFolders.temp().path, `${now}.txt`));
705+
const content = 'Hello World: ' + now;
706+
tempFile.writeTextSync(content);
707+
const tempCopy = fs.File.fromPath(fs.path.join(fs.knownFolders.temp().path, `${now}-copy.txt`));
708+
tempFile
709+
.copy(tempCopy.path)
710+
.then(() => {
711+
TKUnit.assert(tempCopy.size === tempFile.size);
712+
return tempCopy.readText();
713+
})
714+
.then((value) => {
715+
TKUnit.assert(value === content);
716+
717+
return Promise.allSettled([tempFile.remove(), tempCopy.remove()]);
718+
})
719+
.then(() => done())
720+
.catch(done);
721+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,12 @@ export function pickFile() {
110110
//const file = File.fromPath(args.intent.getData().toString());
111111
//console.log(file);
112112
//readFile(file);
113-
copyFile(file);
113+
//copyFile(file);
114+
console.time('fromPath: copy');
115+
const f = File.fromPath(file, true);
116+
console.timeEnd('fromPath: copy');
117+
console.log('old path: ', file);
118+
console.log('new path: ', f.path, f.extension, f.size);
114119
}
115120
});
116121
const Intent = android.content.Intent;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function getApplicationContext() {
1515
}
1616

1717
function getOrSetHelper(path: string): org.nativescript.widgets.FileHelper {
18-
return org.nativescript.widgets.FileHelper.fromString(applicationContext, path);
18+
return org.nativescript.widgets.FileHelper.fromString(getApplicationContext(), path);
1919
}
2020

2121
function isContentUri(path: string): boolean {
@@ -766,6 +766,7 @@ export class FileSystemAccess29 extends FileSystemAccess {
766766
if (isContentUri(path)) {
767767
try {
768768
const file = getOrSetHelper(path);
769+
769770
return {
770771
path,
771772
name: file.getName(),

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,27 @@ export class File extends FileSystemEntity {
8080
*/
8181
isLocked: boolean;
8282

83+
/**
84+
* Copies a file to a given path.
85+
* @param dest The path to the destination file.
86+
* Returns a Promise with a boolean.
87+
*/
88+
copy(dest: string): Promise<boolean>;
89+
90+
/**
91+
* Copies a file to a given path.
92+
* @param dest The path to the destination file.
93+
* @param onError (optional) A callback function to use if any error occurs.
94+
* Returns a Promise with a boolean.
95+
*/
96+
copySync(dest: string, onError?: (error: any) => any): any;
97+
8398
/**
8499
* Gets or creates a File entity at the specified path.
85100
* @param path The path to get/create the file at.
101+
* @param copy An optional value when set, copies the content-uri to a temp file enabling the legacy behaviour
86102
*/
87-
static fromPath(path: string): File;
103+
static fromPath(path: string, copy?: boolean): File;
88104

89105
/**
90106
* Reads the content of the file as a string using the specified encoding (defaults to UTF-8).

packages/core/file-system/index.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IFileSystemAccess, FileSystemAccess, FileSystemAccess29 } from './file-system-access';
22
import { SDK_VERSION } from '../utils/constants';
3+
import { getNativeApplication } from '../application';
34
// The FileSystemAccess implementation, used through all the APIs.
45
let fileAccess: IFileSystemAccess;
56

@@ -180,12 +181,39 @@ export class FileSystemEntity {
180181
}
181182
}
182183

184+
let applicationContext;
185+
function getApplicationContext() {
186+
if (!applicationContext) {
187+
applicationContext = (<android.app.Application>getNativeApplication()).getApplicationContext();
188+
}
189+
190+
return applicationContext;
191+
}
192+
183193
export class File extends FileSystemEntity {
184-
public static fromPath(path: string) {
194+
public static fromPath(path: string, copy: boolean = false) {
185195
const onError = function (error) {
186196
throw error;
187197
};
188198

199+
if (global.isAndroid && copy) {
200+
if (path.startsWith('content:')) {
201+
const fileInfo = getFileAccess().getFile(path, onError);
202+
// falls back to creating a temp file without a known extension.
203+
if (!fileInfo) {
204+
const tempFile = `${knownFolders.temp().path}/${java.util.UUID.randomUUID().toString()}`;
205+
org.nativescript.widgets.Async.File.copySync(path, tempFile, getApplicationContext());
206+
path = tempFile;
207+
} else {
208+
const ext = fileInfo.extension;
209+
const name = `${fileInfo.name.replace(`.${ext}`, '')}.${ext}`;
210+
const tempFile = `${knownFolders.temp().path}/${name}`;
211+
getFileAccess().copySync(path, tempFile);
212+
path = tempFile;
213+
}
214+
}
215+
}
216+
189217
const fileInfo = getFileAccess().getFile(path, onError);
190218
if (!fileInfo) {
191219
return undefined;
Binary file not shown.

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

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,8 @@ public interface Callback {
5353
}
5454

5555
private static boolean isExternalStorageDocument(Uri uri) {
56-
return false;
57-
// return "com.android.externalstorage.documents".equals(uri
58-
// .getAuthority());
56+
return "com.android.externalstorage.documents".equals(uri
57+
.getAuthority());
5958
}
6059

6160
private static @Nullable
@@ -132,7 +131,9 @@ File getFile(Context context, Uri uri) {
132131
String path = split[1];
133132

134133
if ("primary".equals(type)) {
135-
String[] parts = Uri.decode(uri.toString()).split(":" + path + "/");
134+
int nameIndex = path.lastIndexOf("/");
135+
String seg = path.substring(0, nameIndex);
136+
String[] parts = Uri.decode(uri.toString()).split(":" + seg);
136137
String file = Environment.getExternalStorageDirectory() + "/" + path + "/" + parts[1];
137138
return new File(file);
138139
} else {
@@ -178,22 +179,21 @@ FileHelper fromUri(Context context, Uri contentUri) {
178179
return null;
179180
}
180181

181-
int sizeIndex = cursor.getColumnIndex(
182-
MediaStore.MediaColumns.SIZE
183-
);
184-
185-
int nameIndex = cursor.getColumnIndex(
186-
MediaStore.MediaColumns.DISPLAY_NAME
187-
);
188-
189-
int lastModifiedIndex = cursor.getColumnIndex(
190-
MediaStore.MediaColumns.DATE_MODIFIED
191-
);
192-
193-
194182
boolean moved = cursor.moveToFirst();
195183
FileHelper helper = null;
196184
if (moved) {
185+
int sizeIndex = cursor.getColumnIndex(
186+
MediaStore.MediaColumns.SIZE
187+
);
188+
189+
int nameIndex = cursor.getColumnIndex(
190+
MediaStore.MediaColumns.DISPLAY_NAME
191+
);
192+
193+
int lastModifiedIndex = cursor.getColumnIndex(
194+
MediaStore.MediaColumns.DATE_MODIFIED
195+
);
196+
197197
helper = new FileHelper(uri);
198198
helper.size = cursor.getLong(sizeIndex);
199199
helper.name = cursor.getString(nameIndex);
@@ -244,22 +244,21 @@ private void updateInternal(Context context, boolean force) {
244244
return;
245245
}
246246

247-
int sizeIndex = cursor.getColumnIndex(
248-
MediaStore.MediaColumns.SIZE
249-
);
250-
251-
int nameIndex = cursor.getColumnIndex(
252-
MediaStore.MediaColumns.DISPLAY_NAME
253-
);
247+
boolean moved = cursor.moveToFirst();
254248

255-
int lastModifiedIndex = cursor.getColumnIndex(
256-
MediaStore.MediaColumns.DATE_MODIFIED
257-
);
249+
if (moved) {
250+
int sizeIndex = cursor.getColumnIndex(
251+
MediaStore.MediaColumns.SIZE
252+
);
258253

254+
int nameIndex = cursor.getColumnIndex(
255+
MediaStore.MediaColumns.DISPLAY_NAME
256+
);
259257

260-
boolean moved = cursor.moveToFirst();
258+
int lastModifiedIndex = cursor.getColumnIndex(
259+
MediaStore.MediaColumns.DATE_MODIFIED
260+
);
261261

262-
if (moved) {
263262
size = cursor.getLong(sizeIndex);
264263
name = cursor.getString(nameIndex);
265264
mime = context.getContentResolver().getType(uri);

0 commit comments

Comments
 (0)