feat(file-system): allow copy when opening a File (#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>
This commit is contained in:
Osei Fortune
2023-04-25 00:30:28 -04:00
committed by GitHub
parent 4551da075b
commit 18bba2bc11
7 changed files with 104 additions and 34 deletions

View File

@ -698,3 +698,24 @@ export function test_FolderClear_RemovesEmptySubfolders(done) {
}) })
.catch(done); .catch(done);
} }
export function test_FileCopy(done) {
const now = Date.now();
const tempFile = fs.File.fromPath(fs.path.join(fs.knownFolders.temp().path, `${now}.txt`));
const content = 'Hello World: ' + now;
tempFile.writeTextSync(content);
const tempCopy = fs.File.fromPath(fs.path.join(fs.knownFolders.temp().path, `${now}-copy.txt`));
tempFile
.copy(tempCopy.path)
.then(() => {
TKUnit.assert(tempCopy.size === tempFile.size);
return tempCopy.readText();
})
.then((value) => {
TKUnit.assert(value === content);
return Promise.allSettled([tempFile.remove(), tempCopy.remove()]);
})
.then(() => done())
.catch(done);
}

View File

@ -110,7 +110,12 @@ export function pickFile() {
//const file = File.fromPath(args.intent.getData().toString()); //const file = File.fromPath(args.intent.getData().toString());
//console.log(file); //console.log(file);
//readFile(file); //readFile(file);
copyFile(file); //copyFile(file);
console.time('fromPath: copy');
const f = File.fromPath(file, true);
console.timeEnd('fromPath: copy');
console.log('old path: ', file);
console.log('new path: ', f.path, f.extension, f.size);
} }
}); });
const Intent = android.content.Intent; const Intent = android.content.Intent;

View File

@ -15,7 +15,7 @@ function getApplicationContext() {
} }
function getOrSetHelper(path: string): org.nativescript.widgets.FileHelper { function getOrSetHelper(path: string): org.nativescript.widgets.FileHelper {
return org.nativescript.widgets.FileHelper.fromString(applicationContext, path); return org.nativescript.widgets.FileHelper.fromString(getApplicationContext(), path);
} }
function isContentUri(path: string): boolean { function isContentUri(path: string): boolean {
@ -766,6 +766,7 @@ export class FileSystemAccess29 extends FileSystemAccess {
if (isContentUri(path)) { if (isContentUri(path)) {
try { try {
const file = getOrSetHelper(path); const file = getOrSetHelper(path);
return { return {
path, path,
name: file.getName(), name: file.getName(),

View File

@ -80,11 +80,27 @@ export class File extends FileSystemEntity {
*/ */
isLocked: boolean; isLocked: boolean;
/**
* Copies a file to a given path.
* @param dest The path to the destination file.
* Returns a Promise with a boolean.
*/
copy(dest: string): Promise<boolean>;
/**
* Copies a file to a given path.
* @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(dest: string, onError?: (error: any) => any): any;
/** /**
* Gets or creates a File entity at the specified path. * Gets or creates a File entity at the specified path.
* @param path The path to get/create the file at. * @param path The path to get/create the file at.
* @param copy An optional value when set, copies the content-uri to a temp file enabling the legacy behaviour
*/ */
static fromPath(path: string): File; static fromPath(path: string, copy?: boolean): File;
/** /**
* Reads the content of the file as a string using the specified encoding (defaults to UTF-8). * Reads the content of the file as a string using the specified encoding (defaults to UTF-8).

View File

@ -1,5 +1,6 @@
import { IFileSystemAccess, FileSystemAccess, FileSystemAccess29 } from './file-system-access'; import { IFileSystemAccess, FileSystemAccess, FileSystemAccess29 } from './file-system-access';
import { SDK_VERSION } from '../utils/constants'; import { SDK_VERSION } from '../utils/constants';
import { getNativeApplication } from '../application';
// The FileSystemAccess implementation, used through all the APIs. // The FileSystemAccess implementation, used through all the APIs.
let fileAccess: IFileSystemAccess; let fileAccess: IFileSystemAccess;
@ -180,12 +181,39 @@ export class FileSystemEntity {
} }
} }
let applicationContext;
function getApplicationContext() {
if (!applicationContext) {
applicationContext = (<android.app.Application>getNativeApplication()).getApplicationContext();
}
return applicationContext;
}
export class File extends FileSystemEntity { export class File extends FileSystemEntity {
public static fromPath(path: string) { public static fromPath(path: string, copy: boolean = false) {
const onError = function (error) { const onError = function (error) {
throw error; throw error;
}; };
if (global.isAndroid && copy) {
if (path.startsWith('content:')) {
const fileInfo = getFileAccess().getFile(path, onError);
// falls back to creating a temp file without a known extension.
if (!fileInfo) {
const tempFile = `${knownFolders.temp().path}/${java.util.UUID.randomUUID().toString()}`;
org.nativescript.widgets.Async.File.copySync(path, tempFile, getApplicationContext());
path = tempFile;
} else {
const ext = fileInfo.extension;
const name = `${fileInfo.name.replace(`.${ext}`, '')}.${ext}`;
const tempFile = `${knownFolders.temp().path}/${name}`;
getFileAccess().copySync(path, tempFile);
path = tempFile;
}
}
}
const fileInfo = getFileAccess().getFile(path, onError); const fileInfo = getFileAccess().getFile(path, onError);
if (!fileInfo) { if (!fileInfo) {
return undefined; return undefined;

View File

@ -53,9 +53,8 @@ public class FileHelper {
} }
private static boolean isExternalStorageDocument(Uri uri) { private static boolean isExternalStorageDocument(Uri uri) {
return false; return "com.android.externalstorage.documents".equals(uri
// return "com.android.externalstorage.documents".equals(uri .getAuthority());
// .getAuthority());
} }
private static @Nullable private static @Nullable
@ -132,7 +131,9 @@ public class FileHelper {
String path = split[1]; String path = split[1];
if ("primary".equals(type)) { if ("primary".equals(type)) {
String[] parts = Uri.decode(uri.toString()).split(":" + path + "/"); int nameIndex = path.lastIndexOf("/");
String seg = path.substring(0, nameIndex);
String[] parts = Uri.decode(uri.toString()).split(":" + seg);
String file = Environment.getExternalStorageDirectory() + "/" + path + "/" + parts[1]; String file = Environment.getExternalStorageDirectory() + "/" + path + "/" + parts[1];
return new File(file); return new File(file);
} else { } else {
@ -178,6 +179,9 @@ public class FileHelper {
return null; return null;
} }
boolean moved = cursor.moveToFirst();
FileHelper helper = null;
if (moved) {
int sizeIndex = cursor.getColumnIndex( int sizeIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.SIZE MediaStore.MediaColumns.SIZE
); );
@ -190,10 +194,6 @@ public class FileHelper {
MediaStore.MediaColumns.DATE_MODIFIED MediaStore.MediaColumns.DATE_MODIFIED
); );
boolean moved = cursor.moveToFirst();
FileHelper helper = null;
if (moved) {
helper = new FileHelper(uri); helper = new FileHelper(uri);
helper.size = cursor.getLong(sizeIndex); helper.size = cursor.getLong(sizeIndex);
helper.name = cursor.getString(nameIndex); helper.name = cursor.getString(nameIndex);
@ -244,6 +244,9 @@ public class FileHelper {
return; return;
} }
boolean moved = cursor.moveToFirst();
if (moved) {
int sizeIndex = cursor.getColumnIndex( int sizeIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.SIZE MediaStore.MediaColumns.SIZE
); );
@ -256,10 +259,6 @@ public class FileHelper {
MediaStore.MediaColumns.DATE_MODIFIED MediaStore.MediaColumns.DATE_MODIFIED
); );
boolean moved = cursor.moveToFirst();
if (moved) {
size = cursor.getLong(sizeIndex); size = cursor.getLong(sizeIndex);
name = cursor.getString(nameIndex); name = cursor.getString(nameIndex);
mime = context.getContentResolver().getType(uri); mime = context.getContentResolver().getType(uri);