Skip to content

Commit

Permalink
add downloader
Browse files Browse the repository at this point in the history
  • Loading branch information
DerGoogler committed Aug 17, 2024
1 parent eae8ffd commit 1f243db
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 9 deletions.
120 changes: 120 additions & 0 deletions app/src/main/java/com/dergoogler/plugin/DownloadPlugin.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
4 changes: 4 additions & 0 deletions app/src/main/res/xml/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@
<feature name="Terminal">
<param name="android-package" value="com.dergoogler.plugin.TerminalPlugin" />
</feature>

<feature name="Download">
<param name="android-package" value="com.dergoogler.plugin.DownloadPlugin" />
</feature>
</widget>
61 changes: 54 additions & 7 deletions src/activitys/ModuleViewActivity/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -132,6 +135,10 @@ const ModuleViewActivity = () => {
setValue(newValue);
};

const cconfirm = useConfirm();

const [downloadProgress, setDownloadProgress] = React.useState(0);

return (
<Page
modifier="noshadow"
Expand Down Expand Up @@ -336,7 +343,18 @@ const ModuleViewActivity = () => {
)}
</Stack>

<Stack direction="row" justifyContent="center" alignItems="center" spacing={1}>
<Stack direction="column" justifyContent="center" alignItems="stretch" spacing={1}>
{downloadProgress !== 0 && (
<Box sx={{ display: "flex", alignItems: "center" }}>
<Box sx={{ width: "100%", mr: 1 }}>
<LinearProgress variant="determinate" value={downloadProgress} />
</Box>
<Box sx={{ minWidth: 35 }}>
<Typography variant="body2" color="text.secondary">{`${Math.round(downloadProgress)}%`}</Typography>
</Box>
</Box>
)}

<DropdownButton
sx={{
width: "100%",
Expand All @@ -352,12 +370,41 @@ const ModuleViewActivity = () => {
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,
// },
// });
},
},
{
Expand Down
4 changes: 3 additions & 1 deletion src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
};
50 changes: 50 additions & 0 deletions src/native/Download.ts
Original file line number Diff line number Diff line change
@@ -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<DownloadNative> {
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 };
2 changes: 1 addition & 1 deletion src/native/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ class EnvironmentClass extends Native<NativeEnvironment> {
}
}

const Environment: EnvironmentClass = new EnvironmentClass();
export const Environment: EnvironmentClass = new EnvironmentClass();
1 change: 1 addition & 0 deletions src/typings/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ declare global {

readonly __terminal__: I;
readonly __chooser__: I;
readonly __download__: I;
}

export type MMRLTheme = Theme & {
Expand Down
4 changes: 4 additions & 0 deletions web/cordova/cordova_plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
Expand Down
9 changes: 9 additions & 0 deletions web/cordova/plugins/download.js
Original file line number Diff line number Diff line change
@@ -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]);
},
};
});

0 comments on commit 1f243db

Please sign in to comment.