Repository navigation
Expand file tree
/
Copy pathfile-access.ts
More file actions
136 lines (126 loc) · 4.67 KB
/
Copy pathfile-access.ts
File metadata and controls
136 lines (126 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
* Filesystem access for the on-glasses file browser. Arbitrary files outside
* app-specific directories require the "All files access" special permission
* on modern Android (MANAGE_EXTERNAL_STORAGE, granted via a Settings page,
* reasonable for a sideloaded personal tool).
*/
import { Utils } from "@nativescript/core";
declare const android: any;
declare const java: any;
declare const global: any;
const MAX_TEXT_FILE_BYTES = 2_000_000;
const MAX_TEXT_CHARS = 500_000;
export type DirectoryEntry = {
name: string;
path: string;
isDirectory: boolean;
sizeBytes: number;
/** Last-modified time in epoch ms; 0 when unknown. */
modifiedMs: number;
};
export function hasAllFilesAccess(): boolean {
if (!global.isAndroid) return false;
try {
return Boolean(android.os.Environment.isExternalStorageManager());
} catch {
// Pre-R devices have no such concept; the legacy permission suffices.
return true;
}
}
/** Open the system Settings page where the user grants All files access. */
export function requestAllFilesAccess(): void {
const context = Utils.android.getApplicationContext();
const intent = new android.content.Intent(
android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
android.net.Uri.parse(`package:${context.getPackageName()}`),
);
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
export function externalStorageRootPath(): string {
return String(android.os.Environment.getExternalStorageDirectory().getAbsolutePath());
}
/** List a directory, directories first then by name; null when unreadable. */
export function listDirectory(path: string): DirectoryEntry[] | null {
try {
const dir = new java.io.File(path);
if (!dir.isDirectory()) return null;
const files = dir.listFiles();
if (files === null) return null;
const entries: DirectoryEntry[] = [];
for (let index = 0; index < files.length; index++) {
const file = files[index];
entries.push({
name: String(file.getName()),
path: String(file.getAbsolutePath()),
isDirectory: Boolean(file.isDirectory()),
sizeBytes: Number(file.length()),
modifiedMs: Number(file.lastModified()),
});
}
entries.sort((a, b) => {
if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
});
return entries;
} catch (error) {
console.warn(`listDirectory failed for ${path}: ${error}`);
return null;
}
}
/** Stat a single path; null when it does not exist or cannot be checked. */
export function statPath(path: string): DirectoryEntry | null {
try {
const file = new java.io.File(path);
if (!file.exists()) return null;
return {
name: String(file.getName()),
path: String(file.getAbsolutePath()),
isDirectory: Boolean(file.isDirectory()),
sizeBytes: Number(file.length()),
modifiedMs: Number(file.lastModified()),
};
} catch (error) {
console.warn(`statPath failed for ${path}: ${error}`);
return null;
}
}
/** Write UTF-8 text to a file in the public Downloads directory; returns its path or null. */
export function writeTextToDownloads(filename: string, text: string): string | null {
try {
const downloads = android.os.Environment.getExternalStoragePublicDirectory(
android.os.Environment.DIRECTORY_DOWNLOADS,
);
downloads.mkdirs();
const file = new java.io.File(downloads, filename);
const bytes = new java.lang.String(text).getBytes("UTF-8");
// FileOutputStream rather than Files.write to avoid NativeScript varargs /
// overload resolution pitfalls.
const stream = new java.io.FileOutputStream(file);
try {
stream.write(bytes);
} finally {
stream.close();
}
return String(file.getAbsolutePath());
} catch (error) {
console.warn(`writeTextToDownloads failed for ${filename}: ${error}`);
return null;
}
}
/** Read a UTF-8 text file (size-capped); null when unreadable or too large. */
export function readTextFile(path: string): string | null {
try {
const file = new java.io.File(path);
if (!file.isFile() || file.length() > MAX_TEXT_FILE_BYTES) return null;
// File.toPath() rather than Paths.get(path): NativeScript resolves the
// single-string Paths.get call to the (java.net.URI) overload, which
// aborts in JNI.
const bytes = java.nio.file.Files.readAllBytes(file.toPath());
const text = String(new java.lang.String(bytes, "UTF-8"));
return text.length > MAX_TEXT_CHARS ? text.slice(0, MAX_TEXT_CHARS) : text;
} catch (error) {
console.warn(`readTextFile failed for ${path}: ${error}`);
return null;
}
}