Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 2743a908 authored by Simon Chan's avatar Simon Chan
Browse files

feat(bin): add pm.uninstall

parent 023492fb
Loading
Loading
Loading
Loading
+75 −13
Original line number Diff line number Diff line
// cspell:ignore dont
// cspell:ignore instantapp
// cspell:ignore apks
// cspell:ignore versioncode

import type { Adb } from "@yume-chan/adb";
import {
@@ -9,7 +10,11 @@ import {
    escapeArg,
} from "@yume-chan/adb";
import type { Consumable, ReadableStream } from "@yume-chan/stream-extra";
import { ConcatStringStream, DecodeUtf8Stream } from "@yume-chan/stream-extra";
import {
    ConcatStringStream,
    DecodeUtf8Stream,
    SplitStringStream,
} from "@yume-chan/stream-extra";

import { Cmd } from "./cmd.js";

@@ -218,6 +223,23 @@ export interface PackageManagerListPackagesResult {
    uid?: number | undefined;
}

export interface PackageManagerUninstallOptions {
    keepData: boolean;
    user: "all" | "current" | number;
    versionCode: number;
    splitNames: string[];
}

const PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP: Record<
    keyof PackageManagerUninstallOptions,
    string
> = {
    keepData: "-k",
    user: "--user",
    versionCode: "--versionCode",
    splitNames: "",
};

export class PackageManager extends AdbCommandBase {
    #cmd: Cmd;

@@ -297,10 +319,9 @@ export class PackageManager extends AdbCommandBase {
        // so installing a file must use `pm`.
        const args = this.#buildInstallArguments(options);
        args.push(filePath);
        const process = await AdbSubprocessNoneProtocol.raw(
            this.adb,
            args.map(escapeArg).join(" "),
        );
        const process = await this.adb.subprocess.spawn(args.map(escapeArg), {
            protocols: [AdbSubprocessNoneProtocol],
        });

        const output = await process.stdout
            .pipeThrough(new DecodeUtf8Stream())
@@ -348,6 +369,8 @@ export class PackageManager extends AdbCommandBase {
        ]);
    }

    // TODO: install: support split apk formats (`adb install-multiple`)

    static parsePackageListItem(
        line: string,
    ): PackageManagerListPackagesResult {
@@ -399,9 +422,18 @@ export class PackageManager extends AdbCommandBase {
        };
    }

    async listPackages(
    async #cmdOrSubprocess(args: string[]) {
        if (this.#cmd.supportsCmd) {
            args.shift();
            return await this.#cmd.spawn(false, "package", ...args);
        }

        return this.adb.subprocess.spawn(args);
    }

    async *listPackages(
        options?: Partial<PackageManagerListPackagesOptions>,
    ): Promise<PackageManagerListPackagesResult[]> {
    ): AsyncGenerator<PackageManagerListPackagesResult, void, void> {
        const args = this.#buildArguments(
            ["list", "packages"],
            options,
@@ -410,12 +442,42 @@ export class PackageManager extends AdbCommandBase {
        if (options?.filter) {
            args.push(options.filter);
        }
        const output = await this.adb.subprocess.spawnAndWaitLegacy(args);
        return output
            .split("\n")
            .filter((line) => !!line)
            .map((line) => PackageManager.parsePackageListItem(line));

        const process = await this.#cmdOrSubprocess(args);
        const reader = process.stdout
            .pipeThrough(new DecodeUtf8Stream())
            .pipeThrough(new SplitStringStream("\n"))
            .getReader();
        while (true) {
            const { done, value } = await reader.read();
            if (done) {
                break;
            }
            yield PackageManager.parsePackageListItem(value);
        }
    }

    // TODO: install: support split apk formats (`adb install-multiple`)
    async uninstall(
        packageName: string,
        options?: Partial<PackageManagerUninstallOptions>,
    ): Promise<void> {
        const args = this.#buildArguments(
            ["uninstall"],
            options,
            PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP,
        );
        args.push(packageName);
        if (options?.splitNames) {
            args.push(...options.splitNames);
        }

        const process = await this.#cmdOrSubprocess(args);
        const output = await process.stdout
            .pipeThrough(new DecodeUtf8Stream())
            .pipeThrough(new ConcatStringStream())
            .then((output) => output.trim());
        if (output !== "Success") {
            throw new Error(output);
        }
    }
}