feat(android): content uri support for File (#9807)

This commit is contained in:
Osei Fortune
2022-03-04 22:56:04 -04:00
committed by GitHub
parent d2f166b832
commit c68d002c9a
9 changed files with 931 additions and 22 deletions

View File

@ -1,6 +1,8 @@
import * as textModule from '../text';
import { getNativeApplication } from '../application';
import type { IFileSystemAccess } from './file-system-access';
let applicationContext: android.content.Context;
function getApplicationContext() {
if (!applicationContext) {
@ -10,7 +12,18 @@ function getApplicationContext() {
return applicationContext;
}
export class FileSystemAccess {
function getOrSetHelper(path: string): org.nativescript.widgets.FileHelper {
return org.nativescript.widgets.FileHelper.fromString(applicationContext, path);
}
function isContentUri(path: string): boolean {
if (typeof path === 'string' && path.startsWith('content:')) {
return true;
}
return false;
}
export class FileSystemAccess implements IFileSystemAccess {
private _pathSeparator = '/';
public getLastModified(path: string): Date {
@ -592,3 +605,313 @@ export class FileSystemAccess {
return result;
}
}
export class FileSystemAccess29 extends FileSystemAccess {
getLastModified(path: string): Date {
if (isContentUri(path)) {
return new Date(getOrSetHelper(path).getLastModified() * 1000);
}
return super.getLastModified(path);
}
getFileSize(path: string): number {
if (isContentUri(path)) {
return getOrSetHelper(path).getSize();
}
return super.getFileSize(path);
}
getParent(path: string, onError?: (error: any) => any): { path: string; name: string } {
if (isContentUri(path)) {
return null;
}
return super.getParent(path, onError);
}
getFile(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string } {
if (isContentUri(path)) {
try {
const file = getOrSetHelper(path);
return {
path,
name: file.getName(),
extension: file.getExtension(),
};
} catch (e) {
if (typeof onError === 'function') {
onError(e);
}
return;
}
}
return super.getFile(path, onError);
}
getFolder(path: string, onError?: (error: any) => any): { path: string; name: string } {
if (isContentUri(path)) {
return null;
}
return super.getFolder(path, onError);
}
getEntities(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string }[] {
if (isContentUri(path)) {
return null;
}
return super.getEntities(path, onError);
}
eachEntity(path: string, onEntity: (entity: { path: string; name: string; extension: string }) => boolean, onError?: (error: any) => any) {
if (isContentUri(path)) {
return null;
}
super.eachEntity(path, onEntity);
}
fileExists(path: string): boolean {
if (isContentUri(path)) {
return org.nativescript.widgets.FileHelper.exists(applicationContext, path);
}
return super.fileExists(path);
}
folderExists(path: string): boolean {
if (isContentUri(path)) {
return null;
}
return super.folderExists(path);
}
deleteFile(path: string, onError?: (error: any) => any) {
if (isContentUri(path)) {
try {
getOrSetHelper(path).delete(applicationContext);
} catch (e) {
onError?.(e);
}
} else {
super.deleteFile(path, onError);
}
}
deleteFolder(path: string, onError?: (error: any) => any) {
if (!isContentUri(path)) {
super.deleteFolder(path, onError);
}
}
emptyFolder(path: string, onError?: (error: any) => any): void {
if (!isContentUri(path)) {
super.emptyFolder(path, onError);
}
}
rename(path: string, newPath: string, onError?: (error: any) => any): void {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
getOrSetHelper(path).renameSync(applicationContext, newPath, callback);
} else {
super.rename(path, newPath, onError);
}
}
public renameAsync(path: string, newPath: string): Promise<any> {
return new Promise<void>((resolve, reject) => {
getOrSetHelper(path).renameSync(
applicationContext,
newPath,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve();
},
onError(error) {
reject(error);
},
})
);
});
}
getDocumentsFolderPath(): string {
return super.getDocumentsFolderPath();
}
getTempFolderPath(): string {
return super.getDocumentsFolderPath();
}
getLogicalRootPath(): string {
return super.getDocumentsFolderPath();
}
getCurrentAppPath(): string {
return super.getDocumentsFolderPath();
}
public readText = this.readTextSync.bind(this);
readTextAsync(path: string, encoding?: any): Promise<string> {
if (isContentUri(path)) {
return new Promise((resolve, reject) => {
getOrSetHelper(path).readText(
applicationContext,
encoding ?? null,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve(result);
},
onError(error) {
reject(error);
},
})
);
});
}
return super.readTextAsync(path, encoding);
}
readTextSync(path: string, onError?: (error: any) => any, encoding?: any): string {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
return getOrSetHelper(path).readTextSync(applicationContext, encoding ?? null, callback);
} else {
return super.readTextSync(path, onError, encoding);
}
}
read = this.readSync.bind(this);
readAsync(path: string): Promise<any> {
if (isContentUri(path)) {
return new Promise((resolve, reject) => {
getOrSetHelper(path).read(
applicationContext,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve(result);
},
onError(error) {
reject(error);
},
})
);
});
}
return super.readAsync(path);
}
readSync(path: string, onError?: (error: any) => any) {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
return getOrSetHelper(path).readSync(applicationContext, callback);
}
return super.readSync(path, onError);
}
writeText = this.writeTextSync.bind(this);
writeTextAsync(path: string, content: string, encoding?: any): Promise<void> {
if (isContentUri(path)) {
return new Promise<void>((resolve, reject) => {
getOrSetHelper(path).writeText(
applicationContext,
content,
encoding ?? null,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve();
},
onError(error) {
reject(error);
},
})
);
});
}
return super.writeTextAsync(path, content, encoding);
}
writeTextSync(path: string, content: string, onError?: (error: any) => any, encoding?: any) {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
getOrSetHelper(path).writeTextSync(applicationContext, content, encoding ?? null, callback);
} else {
super.writeTextSync(path, content, onError);
}
}
write = this.writeSync.bind(this);
writeAsync(path: string, content: any): Promise<void> {
if (isContentUri(path)) {
return new Promise<void>((resolve, reject) => {
getOrSetHelper(path).write(
applicationContext,
content,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve();
},
onError(error) {
reject(error);
},
})
);
});
}
return super.writeAsync(path, content);
}
writeSync(path: string, content: any, onError?: (error: any) => any) {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
getOrSetHelper(path).writeSync(applicationContext, content, callback);
} else {
super.writeSync(path, content, onError);
}
}
getFileExtension(path: string): string {
if (isContentUri(path)) {
return getOrSetHelper(path).getExtension();
}
return super.getFileExtension(path);
}
getPathSeparator(): string {
return super.getPathSeparator();
}
normalizePath(path: string): string {
return super.normalizePath(path);
}
joinPath(left: string, right: string): string {
return super.joinPath(left, right);
}
joinPaths(paths: string[]): string {
return super.joinPaths(paths);
}
}

View File

@ -1,7 +1,7 @@
/**
* An utility class used to provide methods to access and work with the file system.
*/
export class FileSystemAccess {
export interface IFileSystemAccess {
/**
* Gets the last modified date of a file with a given path.
* @param path Path to the file.
@ -248,3 +248,75 @@ export class FileSystemAccess {
*/
joinPaths(paths: string[]): string;
}
export class FileSystemAccess implements IFileSystemAccess {
getLastModified(path: string): Date;
getFileSize(path: string): number;
getParent(path: string, onError?: (error: any) => any): { path: string; name: string };
getFile(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string };
getFolder(path: string, onError?: (error: any) => any): { path: string; name: string };
getEntities(path: string, onError?: (error: any) => any): Array<{ path: string; name: string; extension: string }>;
eachEntity(path: string, onEntity: (entity: { path: string; name: string; extension: string }) => boolean, onError?: (error: any) => any);
fileExists(path: string): boolean;
folderExists(path: string): boolean;
deleteFile(path: string, onError?: (error: any) => any);
deleteFolder(path: string, onError?: (error: any) => any);
emptyFolder(path: string, onError?: (error: any) => any): void;
rename(path: string, newPath: string, onError?: (error: any) => any): void;
getDocumentsFolderPath(): string;
getTempFolderPath(): string;
getLogicalRootPath(): string;
getCurrentAppPath(): string;
readText(path: string, onError?: (error: any) => any, encoding?: any): string;
readTextAsync(path: string, encoding?: any): Promise<string>;
readTextSync(path: string, onError?: (error: any) => any, encoding?: any): string;
read(path: string, onError?: (error: any) => any): any;
readAsync(path: string): Promise<any>;
readSync(path: string, onError?: (error: any) => any): any;
writeText(path: string, content: string, onError?: (error: any) => any, encoding?: any);
writeTextAsync(path: string, content: string, encoding?: any): Promise<void>;
writeTextSync(path: string, content: string, onError?: (error: any) => any, encoding?: any);
write(path: string, content: any, onError?: (error: any) => any);
writeAsync(path: string, content: any): Promise<void>;
writeSync(path: string, content: any, onError?: (error: any) => any);
getFileExtension(path: string): string;
getPathSeparator(): string;
normalizePath(path: string): string;
joinPath(left: string, right: string): string;
joinPaths(paths: string[]): string;
}
export class FileSystemAccess29 extends FileSystemAccess {}

View File

@ -1,15 +1,19 @@
import { FileSystemAccess } from './file-system-access';
import { IFileSystemAccess, FileSystemAccess, FileSystemAccess29 } from './file-system-access';
import { Device } from '../platform';
// The FileSystemAccess implementation, used through all the APIs.
let fileAccess: FileSystemAccess;
let fileAccess: IFileSystemAccess;
/**
* Returns FileSystemAccess, a shared singleton utility class to provide methods to access and work with the file system. This is used under the hood of all the file system apis in @nativescript/core and provided as a lower level convenience if needed.
* @returns FileSystemAccess
*/
export function getFileAccess(): FileSystemAccess {
export function getFileAccess(): IFileSystemAccess {
if (!fileAccess) {
fileAccess = new FileSystemAccess();
if (global.isAndroid && parseInt(Device.sdkVersion) >= 29) {
fileAccess = new FileSystemAccess29();
} else {
fileAccess = new FileSystemAccess();
}
}
return fileAccess;
@ -161,12 +165,7 @@ export class FileSystemEntity {
}
get lastModified(): Date {
let value = this._lastModified;
if (!this._lastModified) {
value = this._lastModified = getFileAccess().getLastModified(this.path);
}
return value;
return getFileAccess().getLastModified(this.path);
}
}
@ -204,7 +203,7 @@ export class File extends FileSystemEntity {
public read(): Promise<any> {
return new Promise<any>((resolve, reject) => {
try {
this.checkAccess();
this._checkAccess();
} catch (ex) {
reject(ex);
@ -229,7 +228,7 @@ export class File extends FileSystemEntity {
}
public readSync(onError?: (error: any) => any): any {
this.checkAccess();
this._checkAccess();
this._locked = true;
@ -251,7 +250,7 @@ export class File extends FileSystemEntity {
public write(content: any): Promise<void> {
return new Promise<void>((resolve, reject) => {
try {
this.checkAccess();
this._checkAccess();
} catch (ex) {
reject(ex);
@ -276,7 +275,7 @@ export class File extends FileSystemEntity {
}
public writeSync(content: any, onError?: (error: any) => any): void {
this.checkAccess();
this._checkAccess();
try {
this._locked = true;
@ -298,7 +297,7 @@ export class File extends FileSystemEntity {
public readText(encoding?: string): Promise<string> {
return new Promise((resolve, reject) => {
try {
this.checkAccess();
this._checkAccess();
} catch (ex) {
reject(ex);
@ -323,7 +322,7 @@ export class File extends FileSystemEntity {
}
public readTextSync(onError?: (error: any) => any, encoding?: string): string {
this.checkAccess();
this._checkAccess();
this._locked = true;
@ -344,7 +343,7 @@ export class File extends FileSystemEntity {
public writeText(content: string, encoding?: string): Promise<any> {
return new Promise((resolve, reject) => {
try {
this.checkAccess();
this._checkAccess();
} catch (ex) {
reject(ex);
@ -369,7 +368,7 @@ export class File extends FileSystemEntity {
}
public writeTextSync(content: string, onError?: (error: any) => any, encoding?: string): void {
this.checkAccess();
this._checkAccess();
try {
this._locked = true;
@ -388,7 +387,7 @@ export class File extends FileSystemEntity {
}
}
private checkAccess() {
_checkAccess() {
if (this.isLocked) {
throw new Error('Cannot access a locked file.');
}