Skip to content
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
11 changes: 10 additions & 1 deletion src/renderer/components/batch-progress/use-batch-progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@ const useBatchProgress = (
initState: BatchProgressState = defaultBatchProgressState
): [BatchProgressState, typeof setBatchProgressState] => {
const [batchProgressState, setState] = useState<BatchProgressState>(initState);
function setBatchProgressState(state: Partial<BatchProgressState>) {
function setBatchProgressState(
state: Partial<BatchProgressState> | ((state: BatchProgressState) => Partial<BatchProgressState>)
) {
if (typeof state === "function") {
setState(s => ({
...s,
...state(s)
}));
return;
}
setState(s => ({
...s,
...state,
Expand Down
61 changes: 58 additions & 3 deletions src/renderer/components/modals/general/about/download-update.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import Settings from "@renderer/modules/settings";
import useDownloadUpdate from "./use-download-update";

const DownloadButton: React.FC<{
downloadError: Error | null,
downloadedFilePath: string | undefined,
progressStatus: BatchTaskStatus,
onClickStart: () => void,
onClickPause: () => void,
}> = ({
downloadError,
downloadedFilePath,
progressStatus,
onClickStart,
Expand Down Expand Up @@ -52,6 +54,13 @@ const DownloadButton: React.FC<{
content = translate("modals.about.updateApp.operationButton.showItemInDir");
}

if (downloadError) {
variant = "info";
iconClassName = "bi bi-arrow-repeat me-1";
handleClick = onClickStart;
content = translate("common.retry")
}

return (
<Button
size="sm"
Expand Down Expand Up @@ -79,24 +88,40 @@ const DownloadUpdate: React.FC<DownloadUpgradeProps> = ({
const {
cachedFilePath,
cachedProgressState,
fetchUpdate,
downloadLatestVersion,
background,
} = useDownloadUpdate();
const [downloadError, setDownloadError] = useState<Error | null>(null)
const [downloadedFilePath, setDownloadFilePath] = useState(cachedFilePath);

// download
const handleProgress = (loaded: number, total: number): boolean => {
setBatchProgressState({
total: total,
finished: loaded,
errored: 0,
});
return isGoOn.current;
}

const handleError = (err: Error) => {
if (err.toString().includes("aborted")) {
return;
}
setDownloadError(err);
setBatchProgressState(s => ({
status: BatchTaskStatus.Ended,
finished: 0,
errored: s.finished,
}));
};

const handleStart = () => {
if (batchProgressState.status === BatchTaskStatus.Running) {
return;
}
setDownloadError(null);
setBatchProgressState({
status: BatchTaskStatus.Running,
});
Expand All @@ -109,7 +134,8 @@ const DownloadUpdate: React.FC<DownloadUpgradeProps> = ({
status: BatchTaskStatus.Ended,
});
setDownloadFilePath(filePath);
});
})
.catch(handleError);
};

const handlePause = () => {
Expand All @@ -119,11 +145,16 @@ const DownloadUpdate: React.FC<DownloadUpgradeProps> = ({
});
};

const handleManuelDownload = async () => {
const {downloadPageUrl} = await fetchUpdate();
await shell.openExternal(downloadPageUrl);
}

// store and restore state
useMount(() => {
if (cachedProgressState) {
setBatchProgressState(cachedProgressState);
if (cachedProgressState.status !== BatchTaskStatus.Standby) {
if (cachedProgressState.status === BatchTaskStatus.Running) {
handleStart();
return;
}
Expand All @@ -139,8 +170,24 @@ const DownloadUpdate: React.FC<DownloadUpgradeProps> = ({
return (
<div>
<div className="d-flex justify-content-between align-items-center">
<div>{translate("modals.about.updateApp.foundLatest")} <span className="text-primary">v{version}</span></div>
<div className="me-auto">
{translate("modals.about.updateApp.foundLatest")}
<span className="text-primary ms-1">v{version}</span>
</div>
{
downloadError &&
<Button
className="me-1"
size="sm"
variant="link"
onClick={handleManuelDownload}
>
{translate("modals.about.updateApp.downloadManually")}
<i className="bi bi-box-arrow-up-right ms-1"/>
</Button>
}
<DownloadButton
downloadError={downloadError}
downloadedFilePath={downloadedFilePath}
progressStatus={batchProgressState.status}
onClickStart={handleStart}
Expand All @@ -164,13 +211,21 @@ const DownloadUpdate: React.FC<DownloadUpgradeProps> = ({
downloadedFilePath &&
<small className="text-success">{translate("common.downloaded")}</small>
}
{
downloadError &&
<small className="text-danger">{translate("common.failed")}</small>
}
<BatchProgress
status={batchProgressState.status}
total={batchProgressState.total}
finished={batchProgressState.finished}
errored={batchProgressState.errored}
isPercent
/>
{
downloadError &&
<small className="text-danger">{downloadError.toString()}</small>
}
</>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// this file cached download state to keep job can run background.
import {downloadLatestVersion} from "@renderer/modules/update-app";
import {fetchUpdate, downloadLatestVersion} from "@renderer/modules/update-app";
import {BatchProgressState} from "@renderer/components/batch-progress";

let downloadPromise: ReturnType<typeof downloadLatestVersion> | undefined;
Expand Down Expand Up @@ -38,6 +38,7 @@ const useDownloadUpdate = () => {
return {
cachedFilePath,
cachedProgressState,
fetchUpdate,
downloadLatestVersion: _downloadLatestVersion,
background,
};
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/modules/i18n/lang/dict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export default interface Dictionary {
refreshing: string,
refreshed: string,
interrupt: string,
retry: string,
retrying: string,
notFound: string,
noObjectSelected: string,
noDomainToGet: string,
Expand Down Expand Up @@ -477,6 +479,7 @@ export default interface Dictionary {
alreadyLatest: string,
foundLatest: string,
changeLogsTitle: string,
downloadManually: string,
operationButton: {
start: string,
resume: string,
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/modules/i18n/lang/en-us.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const dict: Dictionary = {
refreshing: "Refreshing",
refreshed: "Refreshed",
interrupt: "Interrupt",
retry: "Retry",
retrying: "Retrying",
notFound: "Page Not Found!",
noObjectSelected: "No object selected",
noDomainToGet: "No domain to get object",
Expand Down Expand Up @@ -475,6 +477,7 @@ const dict: Dictionary = {
alreadyLatest: "You're up to date!",
foundLatest: "A new version is available!",
changeLogsTitle: "Changes:",
downloadManually: "Download Manually",
operationButton: {
start: "Download",
resume: "Resume",
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/modules/i18n/lang/ja-jp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const dict: Dictionary = {
refreshing: "更新中",
refreshed: "正常に更新",
interrupt: "中断",
retry: "リトライ",
retrying: "リトライ中",
notFound: "未発見",
noObjectSelected: "操作可能なオブジェクトがない",
noDomainToGet: "オブジェクトを取得するためのドメインがない",
Expand Down Expand Up @@ -474,6 +476,7 @@ const dict: Dictionary = {
alreadyLatest: "既に最新バージョンです!",
foundLatest: "新しいバージョンを見つけました",
changeLogsTitle: "リリースノート:",
downloadManually: "手動ダウンロード",
operationButton: {
start: "更新を開始",
resume: "ダウンロードを続行",
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/modules/i18n/lang/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const dict: Dictionary = {
refreshing: "刷新中",
refreshed: "已刷新",
interrupt: "中断",
retry: "重试",
retrying: "重试中",
notFound: "来到了无牛问津的地方",
noObjectSelected: "未选择操作对象",
noDomainToGet: "没有可用的域名获取对象",
Expand Down Expand Up @@ -474,6 +476,7 @@ const dict: Dictionary = {
alreadyLatest: "已是最新版!",
foundLatest: "发现新版本",
changeLogsTitle: "主要更新:",
downloadManually: "手动下载",
operationButton: {
start: "开始下载",
resume: "继续下载",
Expand Down
105 changes: 79 additions & 26 deletions src/renderer/modules/update-app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@ import {upgrade} from "@renderer/customize";

const CACHE_DURATION = Duration.Hour;

let latestVersion: string = "";
let latestDownloadUrl: string = "";
let lastCheckTimestamp: number = 0;
interface UpdateInfo {
referer: string,
downloadPageUrl: string,
latestVersion: string,
latestDownloadUrl: string,
lastCheckTimestamp: number,
}

let cachedUpdateInfo: UpdateInfo = {
referer: "",
downloadPageUrl: "",
latestVersion: "",
latestDownloadUrl: "",
lastCheckTimestamp: 0,
}

function compareVersion(current: string, latest: string) {
const arr = current.split(".");
Expand All @@ -33,27 +45,60 @@ function compareVersion(current: string, latest: string) {
}

export async function fetchReleaseNote(version: string): Promise<string> {
const resp = await fetch(`${upgrade.release_notes_url}${version}.md`);
if (Math.floor(resp.status / 100) !== 2) {
const resp = await new Promise<request.Response>((resolve, reject) => {
request.get(
{
url: `${upgrade.release_notes_url}${version}.md`,
},
(error, response) => {
if(error) {
reject(error);
return;
}
resolve(response);
});
});
if (Math.floor(resp.statusCode / 100) !== 2) {
return "Not Found";
}
return resp.text();
return resp.body;
}


export async function fetchUpdate(): Promise<UpdateInfo> {
if (Date.now() - cachedUpdateInfo.lastCheckTimestamp <= CACHE_DURATION) {
return cachedUpdateInfo;
}
const resp = await new Promise<request.Response>((resolve, reject) => {
request.get(
{
url: upgrade.check_url,
},
(error, response) => {
if (error || Math.floor(response.statusCode / 100) !== 2) {
reject(error);
return;
}
resolve(response);
}
);
});
const respJson = JSON.parse(resp.body);
cachedUpdateInfo = {
referer: respJson.referer,
downloadPageUrl: respJson["download_page"],
latestVersion: respJson.version,
latestDownloadUrl: respJson.downloads[process.platform][process.arch],
lastCheckTimestamp: 0,
};
return cachedUpdateInfo;
}

/**
* return null if there isn't a new version
*/
export async function fetchLatestVersion(currentVersion: string): Promise<string | null> {
if (Date.now() - lastCheckTimestamp <= CACHE_DURATION) {
return compareVersion(currentVersion, latestVersion) < 0
? latestVersion
: null;
}
const resp = await fetch(upgrade.check_url);
const respJson = await resp.json();
lastCheckTimestamp = Date.now();
latestVersion = respJson.version;
latestDownloadUrl = respJson.downloads[process.platform][process.arch];
const {latestVersion} = await fetchUpdate();
return compareVersion(currentVersion, latestVersion) < 0
? latestVersion
: null;
Expand Down Expand Up @@ -102,9 +147,7 @@ export async function downloadLatestVersion({
// if return false will stop download
onProgress: (loaded: number, total: number) => boolean,
}): Promise<string> {
if (!latestDownloadUrl || Date.now() - lastCheckTimestamp <= CACHE_DURATION) {
await fetchLatestVersion("");
}
const {latestDownloadUrl, referer} = await fetchUpdate();

const fileName = decodeURIComponent(path.basename(latestDownloadUrl));
const to = path.join(toDirectory, fileName);
Expand All @@ -125,7 +168,12 @@ export async function downloadLatestVersion({

// get remote file info
const response = await new Promise<Response>((resolve, reject) => {
request.head(latestDownloadUrl)
request.head({
url: latestDownloadUrl,
headers: {
"Referer": referer,
},
})
.on("error", reject)
.on("response", resolve);
});
Expand All @@ -146,16 +194,21 @@ export async function downloadLatestVersion({

// download file
let downloadedSize = tempFileSize;
const fileWriter = fs.createWriteStream(downloadedFilePath, {
// old code is `flags: ...`. maybe it's a mistake
// mode: fs.constants.O_CREAT | fs.constants.O_WRONLY | fs.constants.O_NONBLOCK,
start: downloadedSize,
});
await new Promise((resolve, reject) => {
const fileWriter = fs.createWriteStream(downloadedFilePath, {
start: downloadedSize,
});
fileWriter.on("error", err => {
if (req) {
req.abort();
}
reject(err);
});
const req = request({
url: latestDownloadUrl,
headers: {
"Range": `bytes=${downloadedSize}-`
"Range": `bytes=${downloadedSize}-`,
"Referer": referer,
},
});
req.on("error", reject)
Expand Down