mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 02:54:11 +08:00
feat(android): content uri support for File (#9807)
This commit is contained in:
@ -17,6 +17,7 @@
|
||||
<Button text="touch-animations" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
|
||||
<Button text="vector-image" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
|
||||
<Button text="visibility-vs-hidden" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
|
||||
<Button text="fs-helper" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</StackLayout>
|
||||
|
73
apps/toolbox/src/pages/fs-helper.ts
Normal file
73
apps/toolbox/src/pages/fs-helper.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { Page, EventData, Application, File } from '@nativescript/core';
|
||||
|
||||
let page: Page;
|
||||
|
||||
export function navigatingTo(args: EventData) {
|
||||
page = <Page>args.object;
|
||||
}
|
||||
|
||||
export function createRandom(args) {
|
||||
if (global.isAndroid) {
|
||||
try {
|
||||
const activity = Application.android.foregroundActivity as androidx.appcompat.app.AppCompatActivity;
|
||||
const selection = [android.provider.MediaStore.MediaColumns.DISPLAY_NAME, android.provider.MediaStore.MediaColumns._ID];
|
||||
// testing with downloads as rename only works with a well know collection downloads/audio/photos/videos API 29+
|
||||
let cursor = activity.getContentResolver().query(android.provider.MediaStore.Downloads.getContentUri('external'), selection, null, null);
|
||||
|
||||
let uri;
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
const index = cursor.getColumnIndex(selection[0]);
|
||||
const name = cursor.getString(index);
|
||||
if (name === 'ns_tmp.txt') {
|
||||
const idIndex = cursor.getColumnIndex(selection[1]);
|
||||
const id = cursor.getLong(idIndex);
|
||||
uri = android.net.Uri.parse(`${android.provider.MediaStore.Downloads.getContentUri('external').toString()}/${id}`);
|
||||
cursor.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
const values = new android.content.ContentValues();
|
||||
values.put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, 'ns_tmp.txt');
|
||||
values.put(android.provider.MediaStore.MediaColumns.MIME_TYPE, 'text/plain');
|
||||
uri = activity.getContentResolver().insert(android.provider.MediaStore.Downloads.getContentUri('external'), values);
|
||||
}
|
||||
|
||||
doWork(uri.toString());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doWork(path: string) {
|
||||
try {
|
||||
const file = File.fromPath(path) as File;
|
||||
console.log('name: ', file.name);
|
||||
console.log('path: ', file.path);
|
||||
console.log('parent: ', file.parent);
|
||||
console.log('size: ', file.size);
|
||||
console.log('lastModified: ', file.lastModified);
|
||||
console.log('extension: ', file.extension);
|
||||
if (file.size > 0) {
|
||||
console.log('current text: ', file.readTextSync());
|
||||
} else {
|
||||
file.writeTextSync('Hello World');
|
||||
console.log('after write: ', file.readTextSync());
|
||||
console.log('after write size: ', file.size);
|
||||
}
|
||||
|
||||
file.renameSync(`ns_temp_${Date.now()}.txt`);
|
||||
|
||||
console.log('rename: ', file.name);
|
||||
console.log('rename lastModified: ', file.lastModified);
|
||||
|
||||
file.removeSync();
|
||||
|
||||
console.log('deleted ?', !File.exists(path));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
6
apps/toolbox/src/pages/fs-helper.xml
Normal file
6
apps/toolbox/src/pages/fs-helper.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
|
||||
|
||||
<StackLayout>
|
||||
<Button text="Create Random" tap="createRandom" />
|
||||
</StackLayout>
|
||||
</Page>
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {}
|
||||
|
@ -1,16 +1,20 @@
|
||||
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) {
|
||||
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.');
|
||||
}
|
||||
|
Binary file not shown.
@ -635,6 +635,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
declare module org {
|
||||
export module nativescript {
|
||||
export module widgets {
|
||||
export class FileHelper {
|
||||
public static class: java.lang.Class<org.nativescript.widgets.FileHelper>;
|
||||
public readText(param0: globalAndroid.content.Context, param1: string, param2: org.nativescript.widgets.FileHelper.Callback): void;
|
||||
public writeSync(param0: globalAndroid.content.Context, param1: androidNative.Array<number>, param2: org.nativescript.widgets.FileHelper.Callback): void;
|
||||
public static fromString(param1: globalAndroid.content.Context, param0: string): org.nativescript.widgets.FileHelper;
|
||||
public writeText(param0: globalAndroid.content.Context, param1: string, param2: string, param3: org.nativescript.widgets.FileHelper.Callback): void;
|
||||
public writeTextSync(param0: globalAndroid.content.Context, param1: string, param2: string, param3: org.nativescript.widgets.FileHelper.Callback): void;
|
||||
public copyToFileSync(param0: globalAndroid.content.Context, param1: java.io.File, param2: org.nativescript.widgets.FileHelper.Callback): boolean;
|
||||
public getName(): string;
|
||||
public read(param0: globalAndroid.content.Context, param1: org.nativescript.widgets.FileHelper.Callback): void;
|
||||
public copyToFile(param0: globalAndroid.content.Context, param1: java.io.File, param2: org.nativescript.widgets.FileHelper.Callback): void;
|
||||
public static fromUri(param0: globalAndroid.content.Context, param1: globalAndroid.net.Uri): org.nativescript.widgets.FileHelper;
|
||||
public readSync(param0: globalAndroid.content.Context, param1: org.nativescript.widgets.FileHelper.Callback): androidNative.Array<number>;
|
||||
public write(param0: globalAndroid.content.Context, param1: androidNative.Array<number>, param2: org.nativescript.widgets.FileHelper.Callback): void;
|
||||
public getSize(): number;
|
||||
public getMime(): string;
|
||||
public readTextSync(param0: globalAndroid.content.Context, param1: string, param2: org.nativescript.widgets.FileHelper.Callback): string;
|
||||
public delete(param0: globalAndroid.content.Context): boolean;
|
||||
public static exists(param0: globalAndroid.content.Context, param1: string): boolean;
|
||||
public static exists(param0: globalAndroid.content.Context, param1: globalAndroid.net.Uri): boolean;
|
||||
public getExtension(): string;
|
||||
public getLastModified(): number;
|
||||
public renameSync(param0: globalAndroid.content.Context, param1: string, param2: org.nativescript.widgets.FileHelper.Callback): string;
|
||||
public rename(param0: globalAndroid.content.Context, param1: string, param2: org.nativescript.widgets.FileHelper.Callback): string;
|
||||
}
|
||||
export module FileHelper {
|
||||
export class Callback {
|
||||
public static class: java.lang.Class<org.nativescript.widgets.FileHelper.Callback>;
|
||||
/**
|
||||
* Constructs a new instance of the org.nativescript.widgets.FileHelper$Callback interface with the provided implementation. An empty constructor exists calling super() when extending the interface class.
|
||||
*/
|
||||
public constructor(implementation: {
|
||||
onError(param0: java.lang.Exception): void;
|
||||
onSuccess(param0: any): void;
|
||||
});
|
||||
public constructor();
|
||||
public onError(param0: java.lang.Exception): void;
|
||||
public onSuccess(param0: any): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module org {
|
||||
export module nativescript {
|
||||
export module widgets {
|
||||
|
@ -0,0 +1,388 @@
|
||||
package org.nativescript.widgets;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class FileHelper {
|
||||
private Uri uri;
|
||||
private long size;
|
||||
private String name;
|
||||
private String mime;
|
||||
private long lastModified;
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private Handler handler;
|
||||
|
||||
public interface Callback {
|
||||
void onError(Exception exception);
|
||||
|
||||
void onSuccess(@Nullable Object result);
|
||||
}
|
||||
|
||||
FileHelper(Uri uri) {
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public static boolean exists(Context context, String string) {
|
||||
try {
|
||||
return exists(context, Uri.parse(string));
|
||||
} catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean exists(Context context, Uri uri) {
|
||||
Cursor cursor = context.getContentResolver()
|
||||
.query(uri, null, null, null, null);
|
||||
|
||||
boolean exists = cursor.moveToFirst();
|
||||
cursor.close();
|
||||
return exists;
|
||||
}
|
||||
|
||||
public static @Nullable
|
||||
FileHelper fromString(Context context, String string) {
|
||||
try {
|
||||
return fromUri(context, Uri.parse(string));
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static @Nullable
|
||||
FileHelper fromUri(Context context, Uri uri) {
|
||||
Cursor cursor = context.getContentResolver()
|
||||
.query(uri, null, null, null, null);
|
||||
|
||||
int sizeIndex = cursor.getColumnIndex(
|
||||
MediaStore.MediaColumns.SIZE
|
||||
);
|
||||
|
||||
int nameIndex = cursor.getColumnIndex(
|
||||
MediaStore.MediaColumns.DISPLAY_NAME
|
||||
);
|
||||
|
||||
int lastModifiedIndex = cursor.getColumnIndex(
|
||||
MediaStore.MediaColumns.DATE_MODIFIED
|
||||
);
|
||||
|
||||
|
||||
boolean moved = cursor.moveToFirst();
|
||||
FileHelper helper = null;
|
||||
if (moved) {
|
||||
helper = new FileHelper(uri);
|
||||
helper.size = cursor.getLong(sizeIndex);
|
||||
helper.name = cursor.getString(nameIndex);
|
||||
helper.mime = context.getContentResolver().getType(uri);
|
||||
helper.lastModified = cursor.getLong(lastModifiedIndex);
|
||||
}
|
||||
cursor.close();
|
||||
return helper;
|
||||
}
|
||||
|
||||
private void updateInternal(Context context) {
|
||||
updateInternal(context, true);
|
||||
}
|
||||
|
||||
private void updateInternal(Context context, boolean force) {
|
||||
|
||||
if (force) {
|
||||
// trigger db update
|
||||
ContentValues values = new ContentValues();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
context.getContentResolver().update(uri, values, null);
|
||||
} else {
|
||||
context.getContentResolver().update(uri, values, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
Cursor cursor = context.getContentResolver()
|
||||
.query(uri, null, null, null, null);
|
||||
|
||||
int sizeIndex = cursor.getColumnIndex(
|
||||
MediaStore.MediaColumns.SIZE
|
||||
);
|
||||
|
||||
int nameIndex = cursor.getColumnIndex(
|
||||
MediaStore.MediaColumns.DISPLAY_NAME
|
||||
);
|
||||
|
||||
int lastModifiedIndex = cursor.getColumnIndex(
|
||||
MediaStore.MediaColumns.DATE_MODIFIED
|
||||
);
|
||||
|
||||
|
||||
boolean moved = cursor.moveToFirst();
|
||||
|
||||
if (moved) {
|
||||
size = cursor.getLong(sizeIndex);
|
||||
name = cursor.getString(nameIndex);
|
||||
mime = context.getContentResolver().getType(uri);
|
||||
lastModified = cursor.getLong(lastModifiedIndex);
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getMime() {
|
||||
if (mime == null) {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
return mime;
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
if (mime == null) {
|
||||
return "";
|
||||
}
|
||||
return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
|
||||
}
|
||||
|
||||
public long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
private byte[] readSyncInternal(Context context) throws Exception {
|
||||
InputStream is = context.getContentResolver().openInputStream(uri);
|
||||
byte[] array = new byte[(int) size];
|
||||
is.read(array);
|
||||
is.close();
|
||||
return array;
|
||||
}
|
||||
|
||||
public @Nullable
|
||||
byte[] readSync(Context context, @Nullable Callback callback) {
|
||||
try {
|
||||
return readSyncInternal(context);
|
||||
} catch (Exception e) {
|
||||
if (callback != null) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void read(Context context, Callback callback) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
byte[] result = readSyncInternal(context);
|
||||
handler.post(() -> callback.onSuccess(result));
|
||||
} catch (Exception e) {
|
||||
handler.post(() -> callback.onError(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String readTextSyncInternal(Context context, @Nullable String encoding) throws Exception {
|
||||
String characterSet = encoding;
|
||||
if (characterSet == null) {
|
||||
characterSet = "UTF-8";
|
||||
}
|
||||
|
||||
InputStream is = context.getContentResolver().openInputStream(uri);
|
||||
InputStreamReader isr = new InputStreamReader(is, characterSet);
|
||||
BufferedReader reader = new BufferedReader(isr);
|
||||
char[] buf = new char[is.available()];
|
||||
reader.read(buf);
|
||||
reader.close();
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
public String readTextSync(Context context, @Nullable String encoding, @Nullable Callback callback) {
|
||||
try {
|
||||
return readTextSyncInternal(context, encoding);
|
||||
} catch (Exception e) {
|
||||
if (callback != null) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void readText(Context context, @Nullable String encoding, Callback callback) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
String result = readTextSyncInternal(context, encoding);
|
||||
handler.post(() -> callback.onSuccess(result));
|
||||
} catch (Exception e) {
|
||||
handler.post(() -> callback.onError(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void writeSyncInternal(Context context, byte[] content) throws Exception {
|
||||
OutputStream os = context.getContentResolver().openOutputStream(uri);
|
||||
os.write(content, 0, content.length);
|
||||
os.flush();
|
||||
os.close();
|
||||
updateInternal(context);
|
||||
}
|
||||
|
||||
public void writeSync(Context context, byte[] content, @Nullable Callback callback) {
|
||||
try {
|
||||
writeSyncInternal(context, content);
|
||||
} catch (Exception e) {
|
||||
if (callback != null) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void write(Context context, byte[] content, Callback callback) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
writeSyncInternal(context, content);
|
||||
handler.post(() -> callback.onSuccess(null));
|
||||
} catch (Exception e) {
|
||||
handler.post(() -> callback.onError(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void writeTextSyncInternal(Context context, String content, @Nullable String encoding) throws Exception {
|
||||
OutputStream os = context.getContentResolver().openOutputStream(uri);
|
||||
String characterSet = encoding;
|
||||
if (characterSet == null) {
|
||||
characterSet = "UTF-8";
|
||||
}
|
||||
OutputStreamWriter osw = new OutputStreamWriter(os, characterSet);
|
||||
osw.write(content);
|
||||
osw.flush();
|
||||
osw.close();
|
||||
updateInternal(context);
|
||||
}
|
||||
|
||||
public void writeTextSync(Context context, String content, @Nullable String encoding, @Nullable Callback callback) {
|
||||
try {
|
||||
writeTextSyncInternal(context, content, encoding);
|
||||
} catch (Exception e) {
|
||||
if (callback != null) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeText(Context context, String content, @Nullable String encoding, Callback callback) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
writeTextSyncInternal(context, content, encoding);
|
||||
handler.post(() -> callback.onSuccess(null));
|
||||
} catch (Exception e) {
|
||||
handler.post(() -> callback.onError(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void copyToFileInternal(InputStream is, OutputStream os) throws Exception {
|
||||
int read;
|
||||
byte[] buf = new byte[1024];
|
||||
while ((read = is.read(buf)) != -1) {
|
||||
os.write(buf, 0, read);
|
||||
}
|
||||
is.close();
|
||||
os.flush();
|
||||
os.close();
|
||||
}
|
||||
|
||||
private void copyToFileInternal(Context context, File file) throws Exception {
|
||||
InputStream is = context.getContentResolver().openInputStream(uri);
|
||||
FileOutputStream os = new FileOutputStream(file);
|
||||
copyToFileInternal(is, os);
|
||||
}
|
||||
|
||||
public boolean copyToFileSync(Context context, File file, @Nullable Callback callback) {
|
||||
boolean completed = false;
|
||||
try {
|
||||
copyToFileInternal(context, file);
|
||||
completed = true;
|
||||
} catch (Exception e) {
|
||||
if (callback != null) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
return completed;
|
||||
}
|
||||
|
||||
public void copyToFile(Context context, File file, Callback callback) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
copyToFileInternal(context, file);
|
||||
handler.post(() -> callback.onSuccess(true));
|
||||
} catch (Exception e) {
|
||||
handler.post(() -> callback.onError(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean delete(Context context) {
|
||||
try {
|
||||
return context.getContentResolver().delete(uri, null, null) > 0;
|
||||
} catch (SecurityException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void renameInternal(Context context, String newName) throws Exception {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.MediaColumns.DISPLAY_NAME, newName);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
context.getContentResolver().update(uri, values, null);
|
||||
} else {
|
||||
context.getContentResolver().update(uri, values, null, null);
|
||||
}
|
||||
|
||||
updateInternal(context, false);
|
||||
}
|
||||
|
||||
public void renameSync(Context context, String newName, @Nullable Callback callback) {
|
||||
try {
|
||||
renameInternal(context, newName);
|
||||
} catch (Exception e) {
|
||||
if (callback != null) {
|
||||
callback.onError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void rename(Context context, String newName, Callback callback) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
renameInternal(context, newName);
|
||||
handler.post(() -> callback.onSuccess(null));
|
||||
} catch (Exception e) {
|
||||
handler.post(() -> callback.onError(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user