diff --git a/.github/workflows/discussions_dump.yml b/.github/workflows/discussions_dump.yml new file mode 100644 index 0000000..ad2f916 --- /dev/null +++ b/.github/workflows/discussions_dump.yml @@ -0,0 +1,46 @@ +name: Dump Discussions Workflow + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +jobs: + discussions_dump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: 'package-lock.json' + - env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd ./scripts + npm ci + ls -lahtr + node ./discussions_dump.js + - uses: actions/upload-artifact@v3.1.2 + with: + name: discussions_dump.json + path: ./scripts/discussions_dump.json + if-no-files-found: error + upload_dump_to_s3: + runs-on: ubuntu-latest + needs: [discussions_dump] + # These permissions are needed to interact with GitHub's OIDC Token endpoint. + permissions: + id-token: write + contents: read + steps: + - uses: actions/download-artifact@v2.1.1 + - run: ls -lahtr + - run: pwd + - uses: aws-actions/configure-aws-credentials@v3 + with: + aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_SAD_UPLOADER_ROLE }} + # use recursive: https://github.com/aws/aws-cli/issues/2929 + - run: aws s3 cp discussions_dump.json s3://simple-android-debloater --recursive diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 69f424d..e6f439c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: 'publish' +name: 'Publish Cross Platform Builds' on: push: branches: diff --git a/.gitignore b/.gitignore index f487b3c..14a42be 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* *.db *.db-shm -*.db-wal \ No newline at end of file +*.db-wal +discussions_dump.json \ No newline at end of file diff --git a/README.md b/README.md index 864faa0..8bb1def 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,27 @@ Unlike UAD, this tool is aimed to be beginner friendly so as to not uninstall ap +## Features +### Standard features +- Disabling and Enabling a package +- Auto detect devices and heartbeats +- Customizable prompt settings +- Search and Filter Packages - ## Download +### Features over UAD +- Labels and Discussions powered by Github Discussions + - Can be crowdsourced and moderated + - Refreshed automatically once a day and can be triggered manually + - ![Discussion Screenshot](./static/screenshots/discussion.png) +- Bulk Enable and Disable + - ![Bulk Disable Screenshot](./static/screenshots/bulk_disable_packages.png) +- Export and Import Settings, Results and Other data + - ![Export Screenshot](./static/screenshots/export_packages.png) + - ![Import Screenshot](./static/screenshots/import_packages.png) + + +## Download Goto the latest [Releases Page](https://github.com/thulasi-ram/simple_android_debloater/releases) click on assets and download the installers applicable for your OS. @@ -33,6 +51,9 @@ Screenshots are available in [static](./static/screenshots) directory. - This requires one to enable developer options - In the PC - Make sure to download [ADB Tools](https://developer.android.com/tools/releases/platform-tools#downloads) for your PC. + - ADB need not be in Path. Setting path is tedious in windows. + - Use settings -> custom_adb_path pointing to the downloaded folder + - - ![Settings Screenshot](./static/screenshots/settings.png) [Read More from XDA On Setting up USB Debugging and ABD](https://www.xda-developers.com/install-adb-windows-macos-linux/) @@ -83,8 +104,16 @@ Run rust and node at once: - [x] Adb track device -- [ ] Github discussion for package +- [x] Github discussion for package - [x] ~Prepackage ADB~ Custom ADB Path Instead -- [x] Persist Settings \ No newline at end of file +- [x] Persist Settings + +- [x] Dark Mode + +- [x] Export Packages in CSV / JSON + +- [x] Import CSV and Bulk Enable / Disable + +- [ ] SDK Compatability checks \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7b0c8fd..0c459cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "flowbite-svelte": "^0.40.2", "flowbite-svelte-icons": "^0.3.6", "lru-cache": "^10.0.1", + "papaparse": "^5.4.1", "tailwind-merge": "^1.14.0", "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log" }, @@ -23,6 +24,7 @@ "@sveltejs/adapter-static": "^1.0.0-next.50", "@sveltejs/kit": "^1.20.4", "@tauri-apps/cli": "^1.4.0", + "@types/papaparse": "^5.3.8", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.14", @@ -964,6 +966,21 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/node": { + "version": "20.5.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", + "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", + "dev": true + }, + "node_modules/@types/papaparse": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.8.tgz", + "integrity": "sha512-ArKIEOOWULbhi53wkAiRy1ze4wvrTfhpAj7Yfzva+EkmX2sV8PpFB+xqzJfzXNzK4me95FJH9QZt5NXFVGzOoQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/pug": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz", @@ -2748,6 +2765,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/package.json b/package.json index 1c2896b..01b0216 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@sveltejs/adapter-static": "^1.0.0-next.50", "@sveltejs/kit": "^1.20.4", "@tauri-apps/cli": "^1.4.0", + "@types/papaparse": "^5.3.8", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.14", @@ -42,6 +43,7 @@ "flowbite-svelte": "^0.40.2", "flowbite-svelte-icons": "^0.3.6", "lru-cache": "^10.0.1", + "papaparse": "^5.4.1", "tailwind-merge": "^1.14.0", "tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log" } diff --git a/scripts/discussions_dump.js b/scripts/discussions_dump.js new file mode 100644 index 0000000..6426bac --- /dev/null +++ b/scripts/discussions_dump.js @@ -0,0 +1,131 @@ +// https://stepzen.com/blog/consume-graphql-in-javascript +// https://stackoverflow.com/questions/42938472/what-is-the-reason-for-having-edges-and-nodes-in-a-connection-in-your-graphql-sc +// https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions +import fetch from 'node-fetch'; +import fs from 'fs'; + +async function getDiscussions(no_of_discussions, next_cursor) { + const reqData = JSON.stringify({ + query: `query($no_of_discussions:Int!, $next_cursor: String) { + repository(owner: "thulasi-ram", name: "simple_android_debloater") { + discussions(first: $no_of_discussions, after: $next_cursor) { + # type: DiscussionConnection + totalCount # Int! + + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + + nodes { + # type: Discussion + id + title + closed + body + bodyHTML + url + answer { + body + bodyHTML + } + labels(first: 100) { + totalCount + nodes { + name + description + } + } + + } + } + } + }`, + variables: `{ + "no_of_discussions": ${no_of_discussions}, + "next_cursor": ${next_cursor == null ? null : `"${next_cursor}"`} + }` + }); + + const response = await fetch('https://api.github.com/graphql', { + method: 'post', + body: reqData, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': reqData.length, + Authorization: 'bearer ' + process.env.GH_TOKEN, + 'User-Agent': 'Node' + } + }); + const json = await response.json(); + + let data = json.data; + + if (!data) { + console.log(response); + console.log(json); + throw new Error('Error in response json'); + } + + const discussions = data?.repository?.discussions; + + if (!discussions) { + console.log(JSON.stringify(data)); + } + + return discussions; +} + +async function getAllDiscussions() { + // https://stackoverflow.com/questions/71952373/how-to-run-graphql-query-in-a-loop-until-condition-is-not-matched + + let hasNext = true; + let nextCursor = null; + let allDiscussions = []; + while (hasNext) { + let discussionsData = await getDiscussions(1, nextCursor); + if (!discussionsData) { + break; + } + hasNext = discussionsData.pageInfo.hasNextPage; + nextCursor = discussionsData.pageInfo.endCursor; + allDiscussions.push(...discussionsData.nodes); + } + + allDiscussions = allDiscussions.map((d) => parseDiscussion(d)).filter((d) => d != null); + let discussionsDump = JSON.stringify(allDiscussions); + console.log(discussionsDump); + + fs.writeFile('discussions_dump.json', discussionsDump, 'utf8', function (err) { + if (err) throw err; + console.log('Dumped json'); + }); +} + +const FILTER_BY_PACKAGE_LABEL = 'pkg'; + +function parseDiscussion(discussion) { + if (!discussion) { + return null; + } + + const labelCount = discussion.labels?.totalCount; + if (labelCount && labelCount > 100) { + console.error(`"discussoon ${discussion} has more than 100 labels"`); + } + const labels = discussion.labels?.nodes; + if (!labels) { + return null; + } + + let matched = labels.filter((l) => l.name === FILTER_BY_PACKAGE_LABEL); + if (!matched) { + return null; + } + discussion.labels = labels; + return discussion; +} + +getAllDiscussions(); diff --git a/scripts/package-lock.json b/scripts/package-lock.json new file mode 100644 index 0000000..aab82aa --- /dev/null +++ b/scripts/package-lock.json @@ -0,0 +1,100 @@ +{ + "name": "scripts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scripts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "node-fetch": "^3.3.2" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 0000000..84e3637 --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,15 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "node-fetch": "^3.3.2" + }, + "type": "module" +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index fe2d7c1..fc8c3fe 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -177,7 +177,7 @@ dependencies = [ "futures-lite", "rustix 0.37.23", "signal-hook", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -395,6 +395,9 @@ name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "cairo-rs" @@ -874,7 +877,7 @@ dependencies = [ "rustc_version", "toml 0.7.6", "vswhom", - "winreg", + "winreg 0.11.0", ] [[package]] @@ -927,7 +930,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -948,7 +951,7 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1004,7 +1007,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall 0.2.16", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1499,6 +1502,25 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1578,7 +1600,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1606,12 +1628,72 @@ dependencies = [ "itoa 1.0.8", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "http-range" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.8", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1738,9 +1820,15 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + [[package]] name = "is-terminal" version = "0.4.9" @@ -1749,7 +1837,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix 0.38.8", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2026,6 +2114,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2050,7 +2144,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2310,6 +2404,16 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "open" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" +dependencies = [ + "pathdiff", + "windows-sys 0.42.0", +] + [[package]] name = "openssl" version = "0.10.56" @@ -2430,6 +2534,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2642,7 +2752,7 @@ dependencies = [ "libc", "log", "pin-project-lite", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2893,6 +3003,45 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "reqwest" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +dependencies = [ + "base64 0.21.2", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg 0.50.0", +] + [[package]] name = "rfd" version = "0.10.0" @@ -2965,7 +3114,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys 0.3.8", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2978,7 +3127,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.5", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3014,7 +3163,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3132,6 +3281,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.8", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.0.0" @@ -3777,6 +3938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e" dependencies = [ "anyhow", + "bytes", "cocoa", "dirs-next", "embed_plist", @@ -3792,9 +3954,12 @@ dependencies = [ "notify-rust", "objc", "once_cell", + "open", "percent-encoding", "rand 0.8.5", "raw-window-handle", + "regex", + "reqwest", "rfd", "semver", "serde", @@ -3848,6 +4013,7 @@ dependencies = [ "png", "proc-macro2", "quote", + "regex", "semver", "serde", "serde_json", @@ -3989,7 +4155,7 @@ dependencies = [ "fastrand", "redox_syscall 0.3.5", "rustix 0.37.23", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4100,7 +4266,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4114,6 +4280,16 @@ dependencies = [ "syn 2.0.25", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -4125,6 +4301,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.11" @@ -4168,6 +4358,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -4240,6 +4436,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -4421,6 +4623,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -4499,6 +4710,19 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.64" @@ -4693,6 +4917,21 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4708,12 +4947,12 @@ version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] @@ -4723,6 +4962,12 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" @@ -4741,6 +4986,12 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" @@ -4759,6 +5010,12 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.0" @@ -4777,6 +5034,12 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.0" @@ -4795,12 +5058,24 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" @@ -4819,6 +5094,12 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -4844,6 +5125,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wry" version = "0.24.3" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e652155..5c19316 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ dotenvy= "0.15.7" [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.4.0", features = [ "path-all", "app-all", "notification-all", "devtools"] } +tauri = { version = "1.4.0", features = [ "fs-read-file", "fs-write-file", "dialog-all", "shell-open", "http-request", "path-all", "app-all", "notification-all", "devtools"] } thiserror = "1.0" anyhow = "*" tokio = { version = "*", features = ["full"] } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 33bc662..61dc0a1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -17,18 +17,17 @@ use std::{env, time::Duration}; use anyhow::anyhow; use config::Config; -use err::{ResultOkPrintErrExt, IntoSADError}; +use err::{IntoSADError, ResultOkPrintErrExt}; use events::{Event, PackageEvent}; +use futures; use log::error; use packages::Package; use serde::{Deserialize, Serialize}; use sqlx::SqlitePool; -use futures; use tauri::Manager; use tokio::{sync::mpsc, time}; - use devices::Device; use sad::SADError; use tauri_plugin_log::{fern::colors::ColoredLevelConfig, LogTarget}; @@ -97,7 +96,7 @@ async fn main() { ]) .setup(|app| { let app_handle = app.handle(); - + let init_db_fut = async move { let conn = db::init(&app_handle).await.expect("unable to init db"); let app_state: tauri::State = app_handle.state(); @@ -250,7 +249,7 @@ async fn adb_disable_clear_and_stop_package( user_id: &str, pkg: &str, app: tauri::State<'_, App>, -) -> Result<(), SADError> { +) -> Result { let config = app.config().await.into_sad_error("unable to get config")?; let acl = packages::ADBTerminalImpl::new(config.custom_adb_path); @@ -279,11 +278,10 @@ async fn adb_disable_clear_and_stop_package( .send(Box::new(pe)) .await .ok_or_print_err("error emitting"); + return Ok(p.clone()); } } } - - return Ok(()); } #[tauri::command] @@ -292,7 +290,7 @@ async fn adb_enable_package( user_id: &str, pkg: &str, app: tauri::State<'_, App>, -) -> Result<(), SADError> { +) -> Result { let config = app.config().await.into_sad_error("unable to get config")?; let acl = packages::ADBTerminalImpl::new(config.custom_adb_path); @@ -316,11 +314,10 @@ async fn adb_enable_package( .send(Box::new(pe)) .await .ok_or_print_err("error emitting"); + return Ok(p.clone()); } } } - - return Ok(()); } #[tauri::command] @@ -368,7 +365,10 @@ async fn get_config(app: tauri::State<'_, App>) -> Result) -> Re let mut cache = app.cache.lock().await; let svc = config::SqliteImpl { db: db_conn }; - let res = svc.update_default_config(config).await.into_sad_error("unable to update config")?; + let res = svc + .update_default_config(config) + .await + .into_sad_error("unable to update config")?; cache.set_config(res); return Ok(()); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 569d5c0..188264b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,74 +1,89 @@ { - "$schema": "../node_modules/@tauri-apps/cli/schema.json", - "build": { - "beforeBuildCommand": "npm run build", - "beforeDevCommand": "npm run dev", - "devPath": "http://localhost:5173", - "distDir": "../build" - }, - "package": { - "productName": "Simple Android Debloater", - "version": "0.3.0" - }, - "tauri": { - "allowlist": { - "app": { - "all": true - }, - "path": { - "all": true - }, - "notification": { - "all": true - } - }, - "bundle": { - "active": true, - "category": "DeveloperTool", - "copyright": "", - "deb": { - "depends": [] - }, - "externalBin": [], - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "identifier": "com.ahiravan.simple-android-debloater", - "longDescription": "", - "macOS": { - "entitlements": null, - "exceptionDomain": "", - "frameworks": [], - "providerShortName": null, - "signingIdentity": null - }, - "resources": [], - "shortDescription": "", - "targets": "all", - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "" - } - }, - "security": { - "csp": null - }, - "updater": { - "active": false - }, - "windows": [ - { - "fullscreen": true, - "height": 600, - "resizable": true, - "title": "Simple Android Debloater", - "width": 800 - } - ] - } + "$schema": "../node_modules/@tauri-apps/cli/schema.json", + "build": { + "beforeBuildCommand": "npm run build", + "beforeDevCommand": "npm run dev", + "devPath": "http://localhost:5173", + "distDir": "../build" + }, + "package": { + "productName": "Simple Android Debloater", + "version": "0.4.0" + }, + "tauri": { + "allowlist": { + "app": { + "all": true + }, + "dialog": { + "all": true + }, + "fs": { + "readFile": true, + "writeFile": true, + "scope": ["**"] + }, + "http": { + "request": true, + "scope": ["https://d31d7prv3kbkn6.cloudfront.net/*"] + }, + "path": { + "all": true + }, + "notification": { + "all": true + }, + "shell": { + "open": true + } + }, + "bundle": { + "active": true, + "category": "DeveloperTool", + "copyright": "", + "deb": { + "depends": [] + }, + "externalBin": [], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "identifier": "com.ahiravan.simple-android-debloater", + "longDescription": "", + "macOS": { + "entitlements": null, + "exceptionDomain": "", + "frameworks": [], + "providerShortName": null, + "signingIdentity": null + }, + "resources": [], + "shortDescription": "", + "targets": "all", + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "security": { + "csp": null + }, + "updater": { + "active": false + }, + "windows": [ + { + "fullscreen": true, + "height": 600, + "resizable": true, + "title": "Simple Android Debloater", + "width": 800 + } + ] + } } diff --git a/src/app.html b/src/app.html index effe0d0..453acf6 100644 --- a/src/app.html +++ b/src/app.html @@ -6,7 +6,7 @@ %sveltekit.head% - +
%sveltekit.body%
diff --git a/src/lib/BreadCrumbs.svelte b/src/lib/BreadCrumbs.svelte index c36431c..c705343 100644 --- a/src/lib/BreadCrumbs.svelte +++ b/src/lib/BreadCrumbs.svelte @@ -15,7 +15,7 @@ {#each crumbs as { href, name }, i} - + {@const innerSpanClass = isLastItem(crumbs, i) ? 'font-medium' : ''} {name} diff --git a/src/lib/Sidebar.svelte b/src/lib/Sidebar.svelte index e665d1a..e7cf0c6 100644 --- a/src/lib/Sidebar.svelte +++ b/src/lib/Sidebar.svelte @@ -1,8 +1,13 @@ @@ -15,16 +20,38 @@ - + + + + + + + + + + + + + + + + + + diff --git a/src/lib/config/stores.ts b/src/lib/config/stores.ts index 480c351..1f32979 100644 --- a/src/lib/config/stores.ts +++ b/src/lib/config/stores.ts @@ -27,7 +27,6 @@ export function createConfigStore() { init: async () => { try { let res: Config = await invoke('get_config'); - console.info('res', res); set(res); } catch (error) { console.error(error); diff --git a/src/lib/devices/DevicesSidebarItems.svelte b/src/lib/devices/DevicesSidebarItems.svelte index 6a75570..3594ccd 100644 --- a/src/lib/devices/DevicesSidebarItems.svelte +++ b/src/lib/devices/DevicesSidebarItems.svelte @@ -35,10 +35,10 @@ let defaultClass = 'text-sm p-1 grid grid-rows-2 grid-cols-none grid-flow-col items-center border'; - let disconnectedClass = defaultClass + " text-gray-300" + let disconnectedClass = defaultClass + " text-gray-300 dark:text-gray-500 dark:border-gray-500" let activeClass = - 'grid grid-rows-2 grid-cols-none grid-flow-col items-center text-base font-normal text-gray-900 bg-red-200 dark:bg-red-700 rounded-lg dark:text-white hover:bg-red-100 dark:hover:bg-red-700'; + 'grid grid-rows-2 grid-cols-none grid-flow-col items-center text-base font-normal text-gray-900 bg-primary-200 dark:bg-transparent dark:border-primary-500 rounded-lg dark:text-white hover:bg-primary-100 dark:hover:bg-gray-700'; let nonActiveClass = 'grid grid-rows-2 grid-cols-none grid-flow-col items-center text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700'; @@ -74,11 +74,11 @@ {/each} {:else} - + diff --git a/src/lib/devices/RefreshDevicesButton.svelte b/src/lib/devices/RefreshDevicesButton.svelte index 5b7fd61..b331edc 100644 --- a/src/lib/devices/RefreshDevicesButton.svelte +++ b/src/lib/devices/RefreshDevicesButton.svelte @@ -19,7 +19,7 @@ } - +
Package State
+
+ {#each filterPackageStates as fs} {fs[0]} @@ -106,12 +136,43 @@
Package Type
+
+ {#each filterPackageTypes as ft} {ft[0]} {/each}
+ +
+
Label Types
+
+
+ {#each Object.entries($packageLabelsStore) as [labelName, labelDesc]} + {@const labelID = `label-cb-${labelName}`} + + + {labelName} + + + + + {/each} +
+
diff --git a/src/lib/packages/PackageCSVEnablerDisabler.svelte b/src/lib/packages/PackageCSVEnablerDisabler.svelte new file mode 100644 index 0000000..a87938c --- /dev/null +++ b/src/lib/packages/PackageCSVEnablerDisabler.svelte @@ -0,0 +1,195 @@ + + + + {#await packagesPromise()} + Parsing CSV File... + {:then} +
+ +
+ + + + Packages + Status + + + {#each Object.entries(packagesStatusMap) as [p, status]} + + {p} + {status} + + {/each} + +
+ {:catch error} + {error} + {/await} + + +
+ + + This is a potentially dangerous action and can brick the device. I agree to process. + + + +
+ {#if processingCount > 0} + + {:else} + + {/if} + +
+ + +
+
+ + diff --git a/src/lib/packages/PackagesList.svelte b/src/lib/packages/PackagesList.svelte index 228bc59..23d5f14 100644 --- a/src/lib/packages/PackagesList.svelte +++ b/src/lib/packages/PackagesList.svelte @@ -11,22 +11,26 @@ import { onDestroy, onMount } from 'svelte'; import type { Unsubscriber } from 'svelte/motion'; - import { filteredPackages } from './stores'; + import { filteredPackages, packageDiscussionsStore } from './stores'; + import { sadErrorStore } from '$lib/error/stores'; + import { IconChevronDown, IconChevronUp } from '@tabler/icons-svelte'; import { configStore } from '../config/stores'; import { disablePackage, + discussion_create_url, enablePackage, fetchPackagesIfEmptySubscription, installPackage, packageEventListener } from './PackagesList'; - import { sadErrorStore } from '$lib/error/stores'; + import { loadPackageDiscussions } from './discussions'; let unsub: Unsubscriber = () => {}; onMount(() => { unsub = fetchPackagesIfEmptySubscription(); packageEventListener(); + loadPackageDiscussions(); }); onDestroy(unsub); @@ -36,7 +40,7 @@ let disablePackageName = ''; function disablePackageButton(pkg: string) { if (!$configStore) { - sadErrorStore.setError("config is not loaded yet"); + sadErrorStore.setError('config is not loaded yet'); return; } if ($configStore.prompt_disable_package) { @@ -46,6 +50,16 @@ return disablePackage(pkg); } } + + $: selectedRowPkgName = ''; + + function toggleSeletcedRowPkgName(pkg: string) { + if (selectedRowPkgName !== pkg) { + selectedRowPkgName = pkg; + } else { + selectedRowPkgName = ''; + } + }
@@ -65,10 +79,28 @@
- +
{#each $filteredPackages as pkg} - + {@const pkgDiscussion = $packageDiscussionsStore[pkg.name]} + {@const isDiscussionRowActive = selectedRowPkgName === pkg.name} + {@const discussionRowActiveClass = isDiscussionRowActive + ? 'border-b-0 bg-gray-100 dark:bg-gray-700' + : ''} + + toggleSeletcedRowPkgName(pkg.name)} + class="cursor-pointer {discussionRowActiveClass}" + > + + {#if !isDiscussionRowActive} + + {:else} + + {/if} + {pkg.name} {#if pkg.ptype == 'system'} @@ -78,7 +110,8 @@ {/if}

{pkg.package_prefix}

- + + {#if pkg.state == 'Enabled'}
diff --git a/src/lib/packages/PackagesList.ts b/src/lib/packages/PackagesList.ts index 33b004a..2e2a6a4 100644 --- a/src/lib/packages/PackagesList.ts +++ b/src/lib/packages/PackagesList.ts @@ -9,9 +9,15 @@ import { adb_install_package, adb_list_packages } from './adb'; -import type { DeviceUserPackage } from './models'; +import type { DeviceUserPackage, Package } from './models'; import { packagesStore } from './stores'; +export const discussion_create_url = (pkg: Package) => { + // https://eric.blog/2016/01/08/prefilling-github-issues/#:~:text=Creating%20issues&text=As%20long%20as%20you're,using%20and%20testing%20your%20software. + let body = encodeURI(''); + return `https://github.com/thulasi-ram/simple_android_debloater/discussions/new?category=packages&title=${pkg.name}&body=${body}`; +}; + export function fetchPackagesIfEmptySubscription(): Unsubscriber { const unsub = selectedUserStore.subscribe((su) => { if (su) { diff --git a/src/lib/packages/RefreshPackagesButton.svelte b/src/lib/packages/RefreshPackagesButton.svelte index a0bfa18..9f44ec3 100644 --- a/src/lib/packages/RefreshPackagesButton.svelte +++ b/src/lib/packages/RefreshPackagesButton.svelte @@ -23,7 +23,7 @@ } - diff --git a/src/lib/packages/adb.ts b/src/lib/packages/adb.ts index ff53053..642b52a 100644 --- a/src/lib/packages/adb.ts +++ b/src/lib/packages/adb.ts @@ -1,5 +1,5 @@ import { invoke } from '@tauri-apps/api/tauri'; -import { info } from "tauri-plugin-log-api"; +import { info } from 'tauri-plugin-log-api'; import type { DeviceUserPackages, Package } from './models'; export async function adb_list_packages( @@ -18,21 +18,25 @@ export async function adb_list_packages( export async function adb_disable_package(deviceId: string, userId: string, pkg: string) { info(`invoking disable - ${userId} - ${pkg}`); - await invoke('adb_disable_clear_and_stop_package', { + let dpkg: Package = await invoke('adb_disable_clear_and_stop_package', { deviceId: deviceId, userId: userId, pkg: pkg }); + + return dpkg; } export async function adb_enable_package(deviceId: string, userId: string, pkg: string) { info(`invoking enable - ${userId} - ${pkg}`); - await invoke('adb_enable_package', { + let epkg: Package = await invoke('adb_enable_package', { deviceId: deviceId, userId: userId, pkg: pkg }); + + return epkg; } export async function adb_install_package(deviceId: string, userId: string, pkg: string) { diff --git a/src/lib/packages/discussions.ts b/src/lib/packages/discussions.ts new file mode 100644 index 0000000..e00126b --- /dev/null +++ b/src/lib/packages/discussions.ts @@ -0,0 +1,23 @@ +import { fetch } from '@tauri-apps/api/http'; +import type { PackageDiscussion, PackageDiscussions } from './models'; +import { packageDiscussionsStore } from './stores'; + +export const DISCUSSIONS_DUMP_URL = 'https://d31d7prv3kbkn6.cloudfront.net/discussions_dump.json'; + +export async function getPackageDiscussions(): Promise { + const response = await fetch(DISCUSSIONS_DUMP_URL, { + method: 'GET', + timeout: 30 + }); + let pdiscussions: PackageDiscussions = {}; + for (let pd of response.data) { + pdiscussions[pd.title] = pd; + } + + return pdiscussions; +} + +export async function loadPackageDiscussions() { + const packageDiscussions = await getPackageDiscussions(); + packageDiscussionsStore.set(packageDiscussions); +} diff --git a/src/lib/packages/models.ts b/src/lib/packages/models.ts index ff1b826..4b4c0ff 100644 --- a/src/lib/packages/models.ts +++ b/src/lib/packages/models.ts @@ -17,7 +17,24 @@ export type DeviceUserPackage = { package: Package; }; - export type Config = { prompt_disable_package: boolean; -} \ No newline at end of file +}; + +export type Label = { + name: string; + description: string; +}; + +export type PackageDiscussion = { + id: string; + title: string; + closed: boolean; + body: string; + bodyHTML: string; + answer: any; + labels: Label[]; + url: string; +}; + +export type PackageDiscussions = Record; diff --git a/src/lib/packages/stores.ts b/src/lib/packages/stores.ts index b12e592..b0b25bf 100644 --- a/src/lib/packages/stores.ts +++ b/src/lib/packages/stores.ts @@ -3,7 +3,7 @@ import { setErrorModal } from '$lib/error'; import { selectedUserStore } from '$lib/users/stores'; import { derived, get, writable, type Writable } from 'svelte/store'; import { info } from 'tauri-plugin-log-api'; -import type { Package } from './models'; +import type { Package, PackageDiscussions } from './models'; function createPackagesStore() { const store = writable>({}); @@ -55,7 +55,7 @@ function createPackagesStore() { export const packagesStore = createPackagesStore(); -const packagesKey = (deviceId: string | undefined, userId: string | undefined): string | null => { +export const packagesKey = (deviceId: string | undefined, userId: string | undefined): string | null => { if (!deviceId || !userId) { info(`pkey is null ${deviceId} ${userId}`); return null; @@ -78,3 +78,15 @@ export const currentPackagesStore = derived( export const filteredPackages: Writable = writable([]); export const searchTermStore = writable(''); +export const packageDiscussionsStore: Writable = writable({}); + +export const packageLabelsStore = derived([packageDiscussionsStore], ([$packageDiscussionsStore]) => { + let labels: Record = {}; + + for (let [_, pkgdiscussion] of Object.entries($packageDiscussionsStore)) { + for (let l of pkgdiscussion.labels) { + labels[l.name] = l.description; + } + } + return labels; +}); diff --git a/src/lib/users/UsersDropdown.svelte b/src/lib/users/UsersDropdown.svelte index 3f94639..d6883ce 100644 --- a/src/lib/users/UsersDropdown.svelte +++ b/src/lib/users/UsersDropdown.svelte @@ -26,16 +26,16 @@
+

Exports packages as a flattened CSV

+
+ +
+ +

Exports packages as JSON for advanced usecases

+
+
+ +

Export Settings as JSON

+
+
+ +

Open url to discssions_dump.json

+
+
diff --git a/src/routes/export/export.ts b/src/routes/export/export.ts new file mode 100644 index 0000000..10f1bf2 --- /dev/null +++ b/src/routes/export/export.ts @@ -0,0 +1,122 @@ +import { configStore } from '$lib/config/stores'; +import type { Device } from '$lib/devices/models'; +import { devicesWithUsersStore, selectedDeviceStore } from '$lib/devices/stores'; +import type { Package } from '$lib/packages/models'; +import { currentPackagesStore, packagesKey, packagesStore } from '$lib/packages/stores'; +import type { User } from '$lib/users/models'; +import { selectedUserStore } from '$lib/users/stores'; +import { CSV_DIALOG_FILTER, JSON_DIALOG_FILTER } from '$lib/utils'; +import { save } from '@tauri-apps/api/dialog'; +import { writeTextFile } from '@tauri-apps/api/fs'; +import Papa from 'papaparse'; +import { get } from 'svelte/store'; + +type DeviceUserExport = { + device_id: string | undefined; + device_name: string | undefined; + user_id: string | undefined; + user_name: string | undefined; +}; + +type PackageExportCSV = Package & DeviceUserExport; + +type UserWithPackages = { + user: User; + packages: Package[]; +}; + +type DeviceWithUserPackages = { + device: Device; + users: UserWithPackages[]; +}; + +type PackageExportJSON = DeviceWithUserPackages[]; + +async function savePackagesCSV(data: PackageExportCSV[]) { + const savePath = await save({ + title: 'Save Packages Export CSV', + filters: [CSV_DIALOG_FILTER] + }); + if (!savePath) return; + + const fcontent = Papa.unparse(data); + + await writeTextFile(savePath, fcontent); +} + +async function savePackagesJSON(data: PackageExportJSON) { + const savePath = await save({ + title: 'Save Packages Export JSON', + filters: [JSON_DIALOG_FILTER] + }); + if (!savePath) return; + + const fcontent = JSON.stringify(data); + + await writeTextFile(savePath, fcontent); +} + +export async function exportPackagesCSV() { + let device = get(selectedDeviceStore); + if (!device) { + throw new Error('device must be selected for export'); + } + + let user = get(selectedUserStore); + if (!user) { + throw new Error('user must be selected for export'); + } + + let packages = get(currentPackagesStore); + + let exportablePackages: PackageExportCSV[] = packages.map((p) => { + return { + name: p.name, + ptype: p.ptype, + state: p.state, + package_prefix: p.package_prefix, + device_id: device?.device.id, + device_name: device?.device.name, + user_id: user?.id, + user_name: user?.name + }; + }); + + await savePackagesCSV(exportablePackages); +} + +export async function exportPackagesJSON() { + let deviceWithUsers = get(devicesWithUsersStore); + + let allPackages = get(packagesStore); + + let exportabledeviceWithUsers: DeviceWithUserPackages[] = Object.entries(deviceWithUsers).map( + ([_, du]) => { + return { + device: du.device, + users: du.users.map((u) => { + let pkey = packagesKey(du.device.id, u.id); + let packages = pkey ? allPackages[pkey] : []; + return { + user: u, + packages: packages || [] + }; + }) + }; + } + ); + + await savePackagesJSON(exportabledeviceWithUsers); +} + +export async function exportAndSaveSettingsJSON() { + const savePath = await save({ + title: 'Save Packages Export JSON', + filters: [JSON_DIALOG_FILTER] + }); + if (!savePath) return; + + const fcontent = JSON.stringify(get(configStore)); + + await writeTextFile(savePath, fcontent); +} diff --git a/src/routes/import/+page.svelte b/src/routes/import/+page.svelte new file mode 100644 index 0000000..81f826a --- /dev/null +++ b/src/routes/import/+page.svelte @@ -0,0 +1,103 @@ + + + + + + +
+
+ + +

Import settings json previously exported

+
+ +
+ + +

Bulk Disables Packages

+
+ +
+ + +

Bulk Enable Packages

+
+
diff --git a/src/routes/import/import.ts b/src/routes/import/import.ts new file mode 100644 index 0000000..fd76725 --- /dev/null +++ b/src/routes/import/import.ts @@ -0,0 +1,94 @@ +import type { Config } from '$lib/config/models'; +import { configStore } from '$lib/config/stores'; +import { CSV_DIALOG_FILTER, JSON_DIALOG_FILTER } from '$lib/utils'; +import { open, type DialogFilter, save } from '@tauri-apps/api/dialog'; +import { readTextFile, writeTextFile } from '@tauri-apps/api/fs'; +import Papa, { type ParseResult } from 'papaparse'; + +export async function importSettingsJSON() { + const openPath = await open({ + directory: false, + multiple: false, + title: 'Settings JSON', + filters: [JSON_DIALOG_FILTER] + }); + + if (!openPath) return; + + if (Array.isArray(openPath)) { + throw new Error('multiple selections for settings.json is not supported'); + } + + const fcontent = await readTextFile(openPath as string); + + const config: Config = JSON.parse(fcontent); + configStore.set(config); +} + +export async function openDialongSingleFile( + title: string, + filters: DialogFilter[] +): Promise { + const openPath = await open({ + directory: false, + multiple: false, + title: title, + filters: filters + }); + + if (!openPath) return null; + + if (Array.isArray(openPath)) { + throw new Error('multiple selections for settings.json is not supported'); + } + + return openPath as string; +} + +export type PackageNames = string[]; + +type PackageRow = { + package: string; +}; + +export async function openAndpParseCSVToJson(title: string) { + let fpath: string = ''; + + let openfpath = await openDialongSingleFile(title, [CSV_DIALOG_FILTER]); + + if (!openfpath) { + return; + } + + fpath = openfpath; + + return async () => { + const fcontent = await readTextFile(fpath); + let parseResult: ParseResult = Papa.parse(fcontent, { header: true }); + if (parseResult.errors.length > 1) { + throw new Error(`unable to parse csv ${parseResult.errors}`); + } + let packageNames: PackageNames = []; + for (let p of parseResult.data) { + packageNames.push(p['package']); + } + + return packageNames; + }; +} + +export type PackageResult = { + package: string; + result: string; +}; + +export async function savePackagesResultsCSV(data: PackageResult[]) { + const savePath = await save({ + title: 'Save Packages Results CSV', + filters: [CSV_DIALOG_FILTER] + }); + if (!savePath) return; + const fcontent = Papa.unparse(data); + + await writeTextFile(savePath, fcontent); +} diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index 9e5e3b5..f689618 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -73,7 +73,7 @@
-