From 7534c52b1eca9e112ad1e3ab54b526fce6c51fd3 Mon Sep 17 00:00:00 2001 From: noeppi_noeppi Date: Wed, 18 Aug 2021 17:02:52 +0200 Subject: [PATCH 1/3] Little updates to android and curseforge service --- nodecg-io-android/extension/android.ts | 131 ++++++++++++++++++ .../extension/curseforgeClient.ts | 17 ++- 2 files changed, 144 insertions(+), 4 deletions(-) diff --git a/nodecg-io-android/extension/android.ts b/nodecg-io-android/extension/android.ts index 5264dc46f..5f6da1763 100644 --- a/nodecg-io-android/extension/android.ts +++ b/nodecg-io-android/extension/android.ts @@ -27,6 +27,7 @@ export class Android { public readonly packageManager: PackageManager; public readonly contactManager: ContactManager; + public readonly fileManager: FileManager; constructor(private nodecg: NodeCG, device: string) { this.device = device; @@ -34,6 +35,7 @@ export class Android { this.packageManager = new PackageManager(this); this.contactManager = new ContactManager(this); + this.fileManager = new FileManager(this); } /** @@ -341,6 +343,9 @@ export class Android { }); const output = await readableToString(childProcess.stdout, "utf-8"); await onExit(childProcess); + if (childProcess.exitCode !== null && childProcess.exitCode != 0) { + throw new Error("adb exit code: " + childProcess.exitCode) + } return output; } @@ -351,6 +356,9 @@ export class Android { }); const output = await readableToBuffer(childProcess.stdout); await onExit(childProcess); + if (childProcess.exitCode !== null && childProcess.exitCode != 0) { + throw new Error("adb exit code: " + childProcess.exitCode) + } return output; } @@ -2582,6 +2590,129 @@ export type UsageStats = { totalTimeVisible: number; }; +/** + * Can be used to access files on the device. This mostly depends on parsing the output of + * shell commands because that gives access to more parts of the file system on a non-rooted + * device. It seems to be stable between versions and devices. Let's hope... + * + * Important: This only works with absolute paths. Using non-absolute paths can lead to + * unpredictable results. + */ +export class FileManager { + private readonly android: Android; + + public readonly path: PathManager + + constructor(android: Android) { + this.android = android; + this.path = new PathManager(android); + } + + /** + * Gets the file names of all entries in a directory. Using non-directory paths may + * produce unpredictable results. + */ + async list(path: string): Promise> { + return (await this.android.rawAdb(['shell', 'ls', '-1', quote(path)])).split('\n').map(unquoteShell) + .map(e => e.trim()).filter(e => e != '').map(e => e.endsWith("/") ? e.substring(0, e.length - 1) : e) + } + + /** + * Gets some information about a file. + */ + async file(path: string): Promise { + return await this.android.rawAdb(['shell', '-b', path]) + } + + /** + * Downloads a file from the device. On some platforms, this gerts incredibly slow when used on + * files larger than 6MB. + */ + async download(device: string, local: string): Promise { + await this.android.rawAdb(['shell', 'pull', quote(device), quote(local)]) + } + + /** + * Uploads a file to the device. On some platforms, this gerts incredibly slow when used on + * files larger than 6MB. + */ + async upload(local: string, device: string): Promise { + await this.android.rawAdb(['shell', 'push', quote(local), quote(device)]) + } +} + +/** + * See FileManager + */ +export class PathManager { + private readonly android: Android; + + constructor(android: Android) { + this.android = android; + } + + /** + * Normalizes a path. For example this will turn `/a/b/../c` into `/a/c`. + * This method may but doesn't need to resolve symbolic links. + */ + async normalize(path: string): Promise { + return await this.android.rawAdb(['shell', 'readlink', '-fm', quote(path)]) + } + + /** + * Gets whether a path exists. + */ + async exists(path: string): Promise { + return (await this.android.rawAdbExitCode(['shell', 'test', '-e', quote(path)])) == 0 + } + + /** + * Gets whether a path is a regular file. + */ + async isfile(path: string): Promise { + return (await this.android.rawAdbExitCode(['shell', 'test', '-f', quote(path)])) == 0 + } + + /** + * Gets whether a path is a directory. + */ + async isdir(path: string): Promise { + return (await this.android.rawAdbExitCode(['shell', 'test', '-d', quote(path)])) == 0 + } + + /** + * Gets whether a path is a symbolic link. + */ + async islink(path: string): Promise { + return (await this.android.rawAdbExitCode(['shell', 'test', '-L', quote(path)])) == 0 + } + + /** + * Gets whether a path is readable by you. + */ + async readable(path: string): Promise { + return (await this.android.rawAdbExitCode(['shell', 'test', '-r', quote(path)])) == 0 + } + + /** + * Gets whether a path is writable by you. + */ + async writable(path: string): Promise { + return (await this.android.rawAdbExitCode(['shell', 'test', '-w', quote(path)])) == 0 + } + + /** + * Gets the link target if a path is a symbolic link or a path that points to the same file if not. + */ + async target(path: string): Promise { + return await this.android.rawAdb(['shell', 'readlink', '-f', quote(path)]) + } +} + function quote(arg: string): string { return '"' + arg.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\$/g, "\\$") + '"'; } + +function unquoteShell(arg: string): string { + return arg.replace(/\\\$/g, "$").replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\ /g, " ").replace(/\\\\/g, "\\"); +} \ No newline at end of file diff --git a/nodecg-io-curseforge/extension/curseforgeClient.ts b/nodecg-io-curseforge/extension/curseforgeClient.ts index 13a0688da..101252d46 100644 --- a/nodecg-io-curseforge/extension/curseforgeClient.ts +++ b/nodecg-io-curseforge/extension/curseforgeClient.ts @@ -763,7 +763,8 @@ export type CurseCategorySection = { initialInclusionPattern: string; extraIncludePattern: unknown; /** - * The game category id + * The game category id. This value can be used for `sectionId` in + * a search query. */ gameCategoryId: number; }; @@ -792,11 +793,11 @@ export type CurseCategoryInfo = { /** * The parent game category id */ - parentGameCategoryId: number; + parentGameCategoryId: number | null; /** - * The root game category id + * The root game category id. For root categories, this is null. */ - rootGameCategoryId: number; + rootGameCategoryId: number | null; /** * The game id the category belongs to */ @@ -901,12 +902,20 @@ export type CurseProjectStatus = | "deleted"; export type CurseSearchQuery = { + /** + * Id of a category to search in. This is not for root categories. + * Root categories should use sectionId instead. + */ categoryID?: number; gameId: number; gameVersion?: string; index?: number; pageSize?: number; searchFilter?: string; + /** + * Id of a category to search in. This is only for root categories. + * Other categories should use categoryID instead. + */ sectionId?: number; sort?: number; }; From d750aef1ad5f2bbe11b19f00a19c447170d739c5 Mon Sep 17 00:00:00 2001 From: noeppi_noeppi Date: Wed, 18 Aug 2021 17:05:42 +0200 Subject: [PATCH 2/3] Lint & Format --- nodecg-io-android/extension/android.ts | 59 +++++++++++++++----------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/nodecg-io-android/extension/android.ts b/nodecg-io-android/extension/android.ts index 56bfbb5be..ca09883ef 100644 --- a/nodecg-io-android/extension/android.ts +++ b/nodecg-io-android/extension/android.ts @@ -343,8 +343,8 @@ export class Android { }); const output = await readableToString(childProcess.stdout, "utf-8"); await onExit(childProcess); - if (childProcess.exitCode !== null && childProcess.exitCode != 0) { - throw new Error("adb exit code: " + childProcess.exitCode) + if (childProcess.exitCode !== null && childProcess.exitCode !== 0) { + throw new Error("adb exit code: " + childProcess.exitCode); } return output; } @@ -356,8 +356,8 @@ export class Android { }); const output = await readableToBuffer(childProcess.stdout); await onExit(childProcess); - if (childProcess.exitCode !== null && childProcess.exitCode != 0) { - throw new Error("adb exit code: " + childProcess.exitCode) + if (childProcess.exitCode !== null && childProcess.exitCode !== 0) { + throw new Error("adb exit code: " + childProcess.exitCode); } return output; } @@ -2594,14 +2594,14 @@ export type UsageStats = { * Can be used to access files on the device. This mostly depends on parsing the output of * shell commands because that gives access to more parts of the file system on a non-rooted * device. It seems to be stable between versions and devices. Let's hope... - * + * * Important: This only works with absolute paths. Using non-absolute paths can lead to * unpredictable results. */ export class FileManager { private readonly android: Android; - - public readonly path: PathManager + + public readonly path: PathManager; constructor(android: Android) { this.android = android; @@ -2613,15 +2613,19 @@ export class FileManager { * produce unpredictable results. */ async list(path: string): Promise> { - return (await this.android.rawAdb(['shell', 'ls', '-1', quote(path)])).split('\n').map(unquoteShell) - .map(e => e.trim()).filter(e => e != '').map(e => e.endsWith("/") ? e.substring(0, e.length - 1) : e) + return (await this.android.rawAdb(["shell", "ls", "-1", quote(path)])) + .split("\n") + .map(unquoteShell) + .map((e) => e.trim()) + .filter((e) => e !== "") + .map((e) => (e.endsWith("/") ? e.substring(0, e.length - 1) : e)); } /** * Gets some information about a file. */ async file(path: string): Promise { - return await this.android.rawAdb(['shell', '-b', path]) + return await this.android.rawAdb(["shell", "-b", path]); } /** @@ -2629,15 +2633,15 @@ export class FileManager { * files larger than 6MB. */ async download(device: string, local: string): Promise { - await this.android.rawAdb(['shell', 'pull', quote(device), quote(local)]) + await this.android.rawAdb(["shell", "pull", quote(device), quote(local)]); } - + /** * Uploads a file to the device. On some platforms, this gerts incredibly slow when used on * files larger than 6MB. */ async upload(local: string, device: string): Promise { - await this.android.rawAdb(['shell', 'push', quote(local), quote(device)]) + await this.android.rawAdb(["shell", "push", quote(local), quote(device)]); } } @@ -2646,7 +2650,7 @@ export class FileManager { */ export class PathManager { private readonly android: Android; - + constructor(android: Android) { this.android = android; } @@ -2656,56 +2660,56 @@ export class PathManager { * This method may but doesn't need to resolve symbolic links. */ async normalize(path: string): Promise { - return await this.android.rawAdb(['shell', 'readlink', '-fm', quote(path)]) + return await this.android.rawAdb(["shell", "readlink", "-fm", quote(path)]); } /** * Gets whether a path exists. */ async exists(path: string): Promise { - return (await this.android.rawAdbExitCode(['shell', 'test', '-e', quote(path)])) == 0 + return (await this.android.rawAdbExitCode(["shell", "test", "-e", quote(path)])) === 0; } /** * Gets whether a path is a regular file. */ async isfile(path: string): Promise { - return (await this.android.rawAdbExitCode(['shell', 'test', '-f', quote(path)])) == 0 + return (await this.android.rawAdbExitCode(["shell", "test", "-f", quote(path)])) === 0; } /** * Gets whether a path is a directory. */ async isdir(path: string): Promise { - return (await this.android.rawAdbExitCode(['shell', 'test', '-d', quote(path)])) == 0 + return (await this.android.rawAdbExitCode(["shell", "test", "-d", quote(path)])) === 0; } /** * Gets whether a path is a symbolic link. */ async islink(path: string): Promise { - return (await this.android.rawAdbExitCode(['shell', 'test', '-L', quote(path)])) == 0 + return (await this.android.rawAdbExitCode(["shell", "test", "-L", quote(path)])) === 0; } /** * Gets whether a path is readable by you. */ async readable(path: string): Promise { - return (await this.android.rawAdbExitCode(['shell', 'test', '-r', quote(path)])) == 0 + return (await this.android.rawAdbExitCode(["shell", "test", "-r", quote(path)])) === 0; } /** * Gets whether a path is writable by you. */ async writable(path: string): Promise { - return (await this.android.rawAdbExitCode(['shell', 'test', '-w', quote(path)])) == 0 + return (await this.android.rawAdbExitCode(["shell", "test", "-w", quote(path)])) === 0; } - + /** * Gets the link target if a path is a symbolic link or a path that points to the same file if not. */ async target(path: string): Promise { - return await this.android.rawAdb(['shell', 'readlink', '-f', quote(path)]) + return await this.android.rawAdb(["shell", "readlink", "-f", quote(path)]); } } @@ -2714,5 +2718,10 @@ function quote(arg: string): string { } function unquoteShell(arg: string): string { - return arg.replace(/\\\$/g, "$").replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\ /g, " ").replace(/\\\\/g, "\\"); -} \ No newline at end of file + return arg + .replace(/\\\$/g, "$") + .replace(/\\'/g, "'") + .replace(/\\"/g, '"') + .replace(/\\ /g, " ") + .replace(/\\\\/g, "\\"); +} From 140438df7ece58955ac0e5dc12d82908848cfa0b Mon Sep 17 00:00:00 2001 From: noeppi_noeppi <63002502+noeppi-noeppi@users.noreply.github.com> Date: Wed, 18 Aug 2021 18:49:42 +0200 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Daniel Huber --- nodecg-io-android/extension/android.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodecg-io-android/extension/android.ts b/nodecg-io-android/extension/android.ts index ca09883ef..4d1a7256c 100644 --- a/nodecg-io-android/extension/android.ts +++ b/nodecg-io-android/extension/android.ts @@ -2629,7 +2629,7 @@ export class FileManager { } /** - * Downloads a file from the device. On some platforms, this gerts incredibly slow when used on + * Downloads a file from the device. On some platforms, this gets incredibly slow when used on * files larger than 6MB. */ async download(device: string, local: string): Promise { @@ -2637,7 +2637,7 @@ export class FileManager { } /** - * Uploads a file to the device. On some platforms, this gerts incredibly slow when used on + * Uploads a file to the device. On some platforms, this gets incredibly slow when used on * files larger than 6MB. */ async upload(local: string, device: string): Promise {