Skip to content
This repository was archived by the owner on Apr 13, 2025. It is now read-only.

Commit 46a774f

Browse files
authored
Merge pull request #245 from noeppi-noeppi/master
Few changes
2 parents 70e68d6 + 140438d commit 46a774f

File tree

2 files changed

+153
-4
lines changed

2 files changed

+153
-4
lines changed

nodecg-io-android/extension/android.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ export class Android {
2727

2828
public readonly packageManager: PackageManager;
2929
public readonly contactManager: ContactManager;
30+
public readonly fileManager: FileManager;
3031

3132
constructor(private nodecg: NodeCG, device: string) {
3233
this.device = device;
3334
this.connected = false;
3435

3536
this.packageManager = new PackageManager(this);
3637
this.contactManager = new ContactManager(this);
38+
this.fileManager = new FileManager(this);
3739
}
3840

3941
/**
@@ -341,6 +343,9 @@ export class Android {
341343
});
342344
const output = await readableToString(childProcess.stdout, "utf-8");
343345
await onExit(childProcess);
346+
if (childProcess.exitCode !== null && childProcess.exitCode !== 0) {
347+
throw new Error("adb exit code: " + childProcess.exitCode);
348+
}
344349
return output;
345350
}
346351

@@ -351,6 +356,9 @@ export class Android {
351356
});
352357
const output = await readableToBuffer(childProcess.stdout);
353358
await onExit(childProcess);
359+
if (childProcess.exitCode !== null && childProcess.exitCode !== 0) {
360+
throw new Error("adb exit code: " + childProcess.exitCode);
361+
}
354362
return output;
355363
}
356364

@@ -2582,6 +2590,138 @@ export type UsageStats = {
25822590
totalTimeVisible: number;
25832591
};
25842592

2593+
/**
2594+
* Can be used to access files on the device. This mostly depends on parsing the output of
2595+
* shell commands because that gives access to more parts of the file system on a non-rooted
2596+
* device. It seems to be stable between versions and devices. Let's hope...
2597+
*
2598+
* Important: This only works with absolute paths. Using non-absolute paths can lead to
2599+
* unpredictable results.
2600+
*/
2601+
export class FileManager {
2602+
private readonly android: Android;
2603+
2604+
public readonly path: PathManager;
2605+
2606+
constructor(android: Android) {
2607+
this.android = android;
2608+
this.path = new PathManager(android);
2609+
}
2610+
2611+
/**
2612+
* Gets the file names of all entries in a directory. Using non-directory paths may
2613+
* produce unpredictable results.
2614+
*/
2615+
async list(path: string): Promise<Array<string>> {
2616+
return (await this.android.rawAdb(["shell", "ls", "-1", quote(path)]))
2617+
.split("\n")
2618+
.map(unquoteShell)
2619+
.map((e) => e.trim())
2620+
.filter((e) => e !== "")
2621+
.map((e) => (e.endsWith("/") ? e.substring(0, e.length - 1) : e));
2622+
}
2623+
2624+
/**
2625+
* Gets some information about a file.
2626+
*/
2627+
async file(path: string): Promise<string> {
2628+
return await this.android.rawAdb(["shell", "-b", path]);
2629+
}
2630+
2631+
/**
2632+
* Downloads a file from the device. On some platforms, this gets incredibly slow when used on
2633+
* files larger than 6MB.
2634+
*/
2635+
async download(device: string, local: string): Promise<void> {
2636+
await this.android.rawAdb(["shell", "pull", quote(device), quote(local)]);
2637+
}
2638+
2639+
/**
2640+
* Uploads a file to the device. On some platforms, this gets incredibly slow when used on
2641+
* files larger than 6MB.
2642+
*/
2643+
async upload(local: string, device: string): Promise<void> {
2644+
await this.android.rawAdb(["shell", "push", quote(local), quote(device)]);
2645+
}
2646+
}
2647+
2648+
/**
2649+
* See FileManager
2650+
*/
2651+
export class PathManager {
2652+
private readonly android: Android;
2653+
2654+
constructor(android: Android) {
2655+
this.android = android;
2656+
}
2657+
2658+
/**
2659+
* Normalizes a path. For example this will turn `/a/b/../c` into `/a/c`.
2660+
* This method may but doesn't need to resolve symbolic links.
2661+
*/
2662+
async normalize(path: string): Promise<string> {
2663+
return await this.android.rawAdb(["shell", "readlink", "-fm", quote(path)]);
2664+
}
2665+
2666+
/**
2667+
* Gets whether a path exists.
2668+
*/
2669+
async exists(path: string): Promise<boolean> {
2670+
return (await this.android.rawAdbExitCode(["shell", "test", "-e", quote(path)])) === 0;
2671+
}
2672+
2673+
/**
2674+
* Gets whether a path is a regular file.
2675+
*/
2676+
async isfile(path: string): Promise<boolean> {
2677+
return (await this.android.rawAdbExitCode(["shell", "test", "-f", quote(path)])) === 0;
2678+
}
2679+
2680+
/**
2681+
* Gets whether a path is a directory.
2682+
*/
2683+
async isdir(path: string): Promise<boolean> {
2684+
return (await this.android.rawAdbExitCode(["shell", "test", "-d", quote(path)])) === 0;
2685+
}
2686+
2687+
/**
2688+
* Gets whether a path is a symbolic link.
2689+
*/
2690+
async islink(path: string): Promise<boolean> {
2691+
return (await this.android.rawAdbExitCode(["shell", "test", "-L", quote(path)])) === 0;
2692+
}
2693+
2694+
/**
2695+
* Gets whether a path is readable by you.
2696+
*/
2697+
async readable(path: string): Promise<boolean> {
2698+
return (await this.android.rawAdbExitCode(["shell", "test", "-r", quote(path)])) === 0;
2699+
}
2700+
2701+
/**
2702+
* Gets whether a path is writable by you.
2703+
*/
2704+
async writable(path: string): Promise<boolean> {
2705+
return (await this.android.rawAdbExitCode(["shell", "test", "-w", quote(path)])) === 0;
2706+
}
2707+
2708+
/**
2709+
* Gets the link target if a path is a symbolic link or a path that points to the same file if not.
2710+
*/
2711+
async target(path: string): Promise<string> {
2712+
return await this.android.rawAdb(["shell", "readlink", "-f", quote(path)]);
2713+
}
2714+
}
2715+
25852716
function quote(arg: string): string {
25862717
return '"' + arg.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\$/g, "\\$") + '"';
25872718
}
2719+
2720+
function unquoteShell(arg: string): string {
2721+
return arg
2722+
.replace(/\\\$/g, "$")
2723+
.replace(/\\'/g, "'")
2724+
.replace(/\\"/g, '"')
2725+
.replace(/\\ /g, " ")
2726+
.replace(/\\\\/g, "\\");
2727+
}

nodecg-io-curseforge/extension/curseforgeClient.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,8 @@ export type CurseCategorySection = {
763763
initialInclusionPattern: string;
764764
extraIncludePattern: unknown;
765765
/**
766-
* The game category id
766+
* The game category id. This value can be used for `sectionId` in
767+
* a search query.
767768
*/
768769
gameCategoryId: number;
769770
};
@@ -792,11 +793,11 @@ export type CurseCategoryInfo = {
792793
/**
793794
* The parent game category id
794795
*/
795-
parentGameCategoryId: number;
796+
parentGameCategoryId: number | null;
796797
/**
797-
* The root game category id
798+
* The root game category id. For root categories, this is null.
798799
*/
799-
rootGameCategoryId: number;
800+
rootGameCategoryId: number | null;
800801
/**
801802
* The game id the category belongs to
802803
*/
@@ -901,12 +902,20 @@ export type CurseProjectStatus =
901902
| "deleted";
902903

903904
export type CurseSearchQuery = {
905+
/**
906+
* Id of a category to search in. This is not for root categories.
907+
* Root categories should use sectionId instead.
908+
*/
904909
categoryID?: number;
905910
gameId: number;
906911
gameVersion?: string;
907912
index?: number;
908913
pageSize?: number;
909914
searchFilter?: string;
915+
/**
916+
* Id of a category to search in. This is only for root categories.
917+
* Other categories should use categoryID instead.
918+
*/
910919
sectionId?: number;
911920
sort?: number;
912921
};

0 commit comments

Comments
 (0)