From 1f243db999a7f8a0769fa5666c609ed20c7e198b Mon Sep 17 00:00:00 2001
From: Der_Googler <54764558+DerGoogler@users.noreply.github.com>
Date: Sat, 17 Aug 2024 17:28:31 +0200
Subject: [PATCH] add downloader
---
.../com/dergoogler/plugin/DownloadPlugin.java | 120 ++++++++++++++++++
app/src/main/res/xml/config.xml | 4 +
src/activitys/ModuleViewActivity/index.tsx | 61 ++++++++-
src/locales/en.ts | 4 +-
src/native/Download.ts | 50 ++++++++
src/native/Environment.ts | 2 +-
src/typings/global.d.ts | 1 +
web/cordova/cordova_plugins.js | 4 +
web/cordova/plugins/download.js | 9 ++
9 files changed, 246 insertions(+), 9 deletions(-)
create mode 100644 app/src/main/java/com/dergoogler/plugin/DownloadPlugin.java
create mode 100644 src/native/Download.ts
create mode 100644 web/cordova/plugins/download.js
diff --git a/app/src/main/java/com/dergoogler/plugin/DownloadPlugin.java b/app/src/main/java/com/dergoogler/plugin/DownloadPlugin.java
new file mode 100644
index 00000000..28fffc49
--- /dev/null
+++ b/app/src/main/java/com/dergoogler/plugin/DownloadPlugin.java
@@ -0,0 +1,120 @@
+package com.dergoogler.plugin;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.topjohnwu.superuser.io.SuFileOutputStream;
+
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class DownloadPlugin extends CordovaPlugin {
+ private static final String TAG = "DownloadPlugin";
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ private final Handler handler = new Handler(Looper.getMainLooper());
+ private CallbackContext downloadCallbackContext = null;
+
+ @Override
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ if (action.equals("start")) {
+ String url = args.getString(0);
+ String dest = args.getString(1);
+ this.downloadCallbackContext = callbackContext;
+ executor.execute(() -> downloadFile(url, dest, callbackContext));
+ return true;
+ }
+ return false;
+ }
+
+ private void downloadFile(String fileUrl, String fileDest, CallbackContext callbackContext) {
+ InputStream input = null;
+ OutputStream output = null;
+ HttpURLConnection connection = null;
+ try {
+ URL url = new URL(fileUrl);
+ connection = (HttpURLConnection) url.openConnection();
+ connection.connect();
+
+ if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ String error = "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage();
+ handler.post(() -> updateError(error));
+ return;
+ }
+
+ int fileLength = connection.getContentLength();
+
+ input = new BufferedInputStream(connection.getInputStream());
+ output = SuFileOutputStream.open(fileDest);
+
+ byte[] data = new byte[4096];
+ long total = 0;
+ int count;
+ while ((count = input.read(data)) != -1) {
+ total += count;
+ if (fileLength > 0) {
+ int progress = (int) (total * 100 / fileLength);
+ JSONObject progressObj = new JSONObject();
+ progressObj.put("type", "downloading");
+ progressObj.put("state", progress);
+ handler.post(() -> updateResult(progressObj));
+ }
+ output.write(data, 0, count);
+ }
+ JSONObject progressObj = new JSONObject();
+ progressObj.put("type", "finished");
+ progressObj.put("state", null);
+ handler.post(() -> updateResult(progressObj));
+ } catch (Exception e) {
+ handler.post(() -> updateError(e.toString()));
+ } finally {
+ try {
+ if (output != null) {
+ output.close();
+ }
+ if (input != null) {
+ input.close();
+ }
+ } catch (Exception ignored) {
+ }
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ private void updateError(String err) {
+ sendUpdate(PluginResult.Status.ERROR, err);
+ }
+
+ private void updateResult(JSONObject line) {
+ sendUpdate(PluginResult.Status.OK, line);
+ }
+
+ private void sendUpdate(PluginResult.Status status, String res) {
+ if (this.downloadCallbackContext != null) {
+ PluginResult result = new PluginResult(status, res);
+ result.setKeepCallback(true);
+ this.downloadCallbackContext.sendPluginResult(result);
+ }
+ }
+
+ private void sendUpdate(PluginResult.Status status, JSONObject res) {
+ if (this.downloadCallbackContext != null) {
+ PluginResult result = new PluginResult(status, res);
+ result.setKeepCallback(true);
+ this.downloadCallbackContext.sendPluginResult(result);
+ }
+ }
+}
diff --git a/app/src/main/res/xml/config.xml b/app/src/main/res/xml/config.xml
index 08196830..9901950b 100644
--- a/app/src/main/res/xml/config.xml
+++ b/app/src/main/res/xml/config.xml
@@ -34,4 +34,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/activitys/ModuleViewActivity/index.tsx b/src/activitys/ModuleViewActivity/index.tsx
index 8aac0919..91251361 100644
--- a/src/activitys/ModuleViewActivity/index.tsx
+++ b/src/activitys/ModuleViewActivity/index.tsx
@@ -31,6 +31,9 @@ import { VersionsTab } from "./tabs/VersionsTab";
import { DropdownButton } from "@Components/DropdownButton";
import { useModuleInfo } from "@Hooks/useModuleInfo";
import { useFormatBytes } from "@Hooks/useFormatBytes";
+import LinearProgress from "@mui/material/LinearProgress";
+import { Download } from "@Native/Download";
+import { Environment } from "@Native/Environment";
function a11yProps(index: number) {
return {
@@ -132,6 +135,10 @@ const ModuleViewActivity = () => {
setValue(newValue);
};
+ const cconfirm = useConfirm();
+
+ const [downloadProgress, setDownloadProgress] = React.useState(0);
+
return (
{
)}
-
+
+ {downloadProgress !== 0 && (
+
+
+
+
+
+ {`${Math.round(downloadProgress)}%`}
+
+
+ )}
+
{
children: strings("download"),
disabled: !latestVersion.zipUrl,
onClick: () => {
- os.open(latestVersion.zipUrl, {
- target: "_blank",
- features: {
- color: theme.palette.primary.main,
- },
- });
+ const lasSeg = new URL(latestVersion.zipUrl).pathname.split("/").pop();
+ const dlPath = Environment.getPublicDir(Environment.DIRECTORY_DOWNLOADS) + "/" + lasSeg;
+ const dl = new Download(latestVersion.zipUrl, dlPath);
+
+ dl.onChange = (obj) => {
+ switch (obj.type) {
+ case "downloading":
+ setDownloadProgress(obj.state);
+ break;
+ case "finished":
+ setDownloadProgress(0);
+ cconfirm({
+ title: strings("download"),
+ description: strings("file_downloaded", { path: dlPath }),
+ })
+ .then(() => {})
+ .catch(() => {});
+
+ break;
+ }
+ };
+
+ dl.onError = (err) => {
+ setDownloadProgress(0);
+ os.toast("finsish: " + err, Toast.LENGTH_SHORT);
+ };
+
+ dl.start();
+
+ // os.open(latestVersion.zipUrl, {
+ // target: "_blank",
+ // features: {
+ // color: theme.palette.primary.main,
+ // },
+ // });
},
},
{
diff --git a/src/locales/en.ts b/src/locales/en.ts
index 5f5fac10..23009c32 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -153,5 +153,7 @@ export const en = {
we_hit_a_brick: "We hit a brick!",
open_settings: "Open settings",
open_logcat: "Open Logcat",
- try_again: "Try again"
+ try_again: "Try again",
+
+ file_downloaded: "File has been downloaded to {path}",
};
diff --git a/src/native/Download.ts b/src/native/Download.ts
new file mode 100644
index 00000000..c4aa4454
--- /dev/null
+++ b/src/native/Download.ts
@@ -0,0 +1,50 @@
+import { Native } from "./Native";
+
+type DownloadState = { type: "downloading"; state: number } | { type: "finished"; state: null };
+
+interface DownloadStart {
+ url: string;
+ dest: string;
+ onChange: ((s: DownloadState) => void) | undefined;
+ onError?: ((err: string) => void) | null;
+}
+
+interface DownloadNative {
+ start(options: DownloadStart): void;
+}
+
+class Download extends Native {
+ private _onError: ((err: string) => void) | null | undefined;
+ private _onChange: ((s: DownloadState) => void) | undefined;
+ private _dest: string;
+ private _url: string;
+
+ public constructor(url: string, dest: string) {
+ super(window.__download__);
+ this._url = url;
+ this._dest = dest;
+ }
+
+ public set onChange(func: DownloadStart["onChange"]) {
+ this._onChange = func;
+ }
+
+ public set onError(func: DownloadStart["onError"]) {
+ this._onError = func;
+ }
+
+ public start(): void {
+ if (this.isAndroid) {
+ if (typeof this._onChange !== "function") throw new TypeError("Download 'onChange' is not a function");
+
+ this.interface.start({
+ url: this._url,
+ dest: this._dest,
+ onChange: this._onChange,
+ onError: this._onError,
+ });
+ }
+ }
+}
+
+export { Download };
diff --git a/src/native/Environment.ts b/src/native/Environment.ts
index 4542ed9a..611e61d9 100644
--- a/src/native/Environment.ts
+++ b/src/native/Environment.ts
@@ -46,4 +46,4 @@ class EnvironmentClass extends Native {
}
}
-const Environment: EnvironmentClass = new EnvironmentClass();
+export const Environment: EnvironmentClass = new EnvironmentClass();
diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts
index 82eb3dd1..82dd0cf3 100644
--- a/src/typings/global.d.ts
+++ b/src/typings/global.d.ts
@@ -95,6 +95,7 @@ declare global {
readonly __terminal__: I;
readonly __chooser__: I;
+ readonly __download__: I;
}
export type MMRLTheme = Theme & {
diff --git a/web/cordova/cordova_plugins.js b/web/cordova/cordova_plugins.js
index 11678bb2..8603460e 100644
--- a/web/cordova/cordova_plugins.js
+++ b/web/cordova/cordova_plugins.js
@@ -12,6 +12,10 @@ cordova.define("cordova/plugin_list", function (require, exports, module) {
id: "com.dergoogler.plugin.terminal",
clobbers: ["__terminal__"],
},
+ {
+ id: "com.dergoogler.plugin.download",
+ clobbers: ["__download__"],
+ },
];
module.exports = plugins.map((plugin) => ({ file: "bundle/c-plugins.js", ...plugin }));
diff --git a/web/cordova/plugins/download.js b/web/cordova/plugins/download.js
new file mode 100644
index 00000000..ad548483
--- /dev/null
+++ b/web/cordova/plugins/download.js
@@ -0,0 +1,9 @@
+cordova.define("com.dergoogler.plugin.download", function (require, exports, module) {
+ const exec = require("cordova/exec");
+
+ module.exports = {
+ start: function (opt) {
+ exec(opt.onChange, opt.onError, "Download", "start", [opt.url, opt.dest]);
+ },
+ };
+});