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

Few changes #245

Merged
merged 4 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions nodecg-io-android/extension/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ export class Android {

public readonly packageManager: PackageManager;
public readonly contactManager: ContactManager;
public readonly fileManager: FileManager;

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

this.packageManager = new PackageManager(this);
this.contactManager = new ContactManager(this);
this.fileManager = new FileManager(this);
}

/**
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -2582,6 +2590,138 @@ 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<Array<string>> {
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<string> {
return await this.android.rawAdb(["shell", "-b", path]);
}

/**
* 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<void> {
await this.android.rawAdb(["shell", "pull", quote(device), quote(local)]);
}

/**
* 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<void> {
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<string> {
return await this.android.rawAdb(["shell", "readlink", "-fm", quote(path)]);
}

/**
* Gets whether a path exists.
*/
async exists(path: string): Promise<boolean> {
return (await this.android.rawAdbExitCode(["shell", "test", "-e", quote(path)])) === 0;
}

/**
* Gets whether a path is a regular file.
*/
async isfile(path: string): Promise<boolean> {
return (await this.android.rawAdbExitCode(["shell", "test", "-f", quote(path)])) === 0;
}

/**
* Gets whether a path is a directory.
*/
async isdir(path: string): Promise<boolean> {
return (await this.android.rawAdbExitCode(["shell", "test", "-d", quote(path)])) === 0;
}

/**
* Gets whether a path is a symbolic link.
*/
async islink(path: string): Promise<boolean> {
return (await this.android.rawAdbExitCode(["shell", "test", "-L", quote(path)])) === 0;
}

/**
* Gets whether a path is readable by you.
*/
async readable(path: string): Promise<boolean> {
return (await this.android.rawAdbExitCode(["shell", "test", "-r", quote(path)])) === 0;
}

/**
* Gets whether a path is writable by you.
*/
async writable(path: string): Promise<boolean> {
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<string> {
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, "\\");
}
17 changes: 13 additions & 4 deletions nodecg-io-curseforge/extension/curseforgeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
};
Expand Down