Files
desktop/app/providers/UpdateProvider/UpdateProvider.tsx

175 lines
6.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createContext, useEffect, useRef, useState } from "react";
import { PacketRequestUpdate } from "../ProtocolProvider/protocol/packets/packet.requestupdate";
import { useSender } from "../ProtocolProvider/useSender";
import { usePacket } from "../ProtocolProvider/usePacket";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { useFileStorage } from "@/app/hooks/useFileStorage";
import { APPLICATION_ARCH, APPLICATION_PLATFROM, CORE_VERSION } from "@/app/constants";
import { APP_VERSION } from "@/app/version";
export interface UpdateProviderProps {
children: React.ReactNode;
}
export enum UpdateStatus {
DOWNLOADING,
COMPILE,
READY_FOR_RESTART,
NO_UPDATES,
KERNEL_UPDATE_NEED,
APP_UPDATE_AVAILABLE
}
/**
* Ответ от сервера обновлений на запрос наличия обновлений. Содержит информацию о том, какие обновления есть, и ссылки на них
*/
export interface UpdateServerResponse {
version: string;
platform: string;
arch: string;
kernel_update_required: boolean;
service_pack_url: string;
kernel_url: string;
}
export interface UpdateContextValue {
updateStatus: UpdateStatus;
downloadProgress: number;
kernelUpdateUrl: string;
checkForUpdates: () => void;
downloadLastApplicationUpdate: () => void;
restartAppForUpdateApply: () => void;
}
export const UpdateProviderContext = createContext<UpdateContextValue | null>(null);
/**
* Провайдер для управления обновлениями приложения. При инициализации
* запрашвиает сервер обновлений с основного сервера и получает актуальные версии с сервера обновлений
*/
export function UpdateProvider(props: UpdateProviderProps) {
const send = useSender();
const {info, error} = useConsoleLogger('UpdateProvider');
const updateServerRef = useRef<string | null>(null);
const [updateStatus, setUpdateStatus] = useState<UpdateStatus>(UpdateStatus.NO_UPDATES);
const [downloadProgress, setDownloadProgress] = useState<number>(0);
const [kernelUpdateUrl, setKernelUpdateUrl] = useState<string>("");
const [appUpdateUrl, setAppUpdateUrl] = useState<string>("");
const [appActualVersion, setAppActualVersion] = useState<string>("");
const {writeFile} = useFileStorage();
useEffect(() => {
let packet = new PacketRequestUpdate();
/**
* Клиент хочет получить сервер обновлений с основного сервера
*/
packet.setUpdateServer("");
send(packet);
}, []);
usePacket(0xA, (packet : PacketRequestUpdate) => {
updateServerRef.current = packet.getUpdateServer();
info(`Update server ${updateServerRef.current}`);
checkForUpdates();
}, []);
const checkForUpdates = async () => {
if(updateServerRef.current == null){
/**
* SDU еще не определен
*/
return;
}
/**
* Запрашиваем обновления с SDU сервера
*/
let response = await fetch
(`${updateServerRef.current}/updates/get?app=${APP_VERSION}&kernel=${CORE_VERSION}&arch=${APPLICATION_ARCH}&platform=${APPLICATION_PLATFROM}`).catch((e) => {
error("Failed to check for updates: " + e.message);
});
if(!response || response.status != 200){
error("Failed to check for updates, SDU unavailable");
return;
}
let updateInfo : UpdateServerResponse = await response.json();
console.info("Update info: ", updateInfo);
if(updateInfo.kernel_update_required){
/**
* Чтобы дальше получать обновления приложения нужно сначала обновить ядро
*/
setUpdateStatus(UpdateStatus.KERNEL_UPDATE_NEED);
setKernelUpdateUrl(updateInfo.kernel_url);
info("Kernel update needed for next application updates");
return;
}
if(updateInfo.service_pack_url != null){
/**
* Доступно обновление приложения, которое можно скачать и установить
*/
setUpdateStatus(UpdateStatus.APP_UPDATE_AVAILABLE);
setAppUpdateUrl(updateInfo.service_pack_url);
setAppActualVersion(updateInfo.version);
info("Application update available");
return;
}
}
const downloadLastApplicationUpdate = async () => {
if(updateStatus != UpdateStatus.APP_UPDATE_AVAILABLE){
return;
}
setUpdateStatus(UpdateStatus.DOWNLOADING);
const xhr = new XMLHttpRequest();
xhr.open("GET", updateServerRef.current + appUpdateUrl, true);
xhr.responseType = "blob";
xhr.onprogress = (event) => {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
setDownloadProgress(Math.round(percentComplete));
}
};
xhr.onload = async () => {
if (xhr.status === 200) {
setUpdateStatus(UpdateStatus.COMPILE);
const blob : Blob = xhr.response;
let bundleName = `bundle ${appActualVersion}.zip`;
await writeFile(bundleName, Buffer.from(await blob.arrayBuffer()));
info("Update downloaded, starting compiler...");
await window.electron.ipcRenderer.invoke('update:installServiceUpdate', bundleName);
info("Update compiled successfully.");
setTimeout(() => {
setUpdateStatus(UpdateStatus.READY_FOR_RESTART);
}, 10000);
}
}
xhr.onerror = () => {
error("Error downloading update");
setUpdateStatus(UpdateStatus.APP_UPDATE_AVAILABLE);
}
xhr.send();
}
const restartAppForUpdateApply = () => {
if(updateStatus != UpdateStatus.READY_FOR_RESTART){
return;
}
window.electron.ipcRenderer.invoke('update:restartApp');
}
return (
<UpdateProviderContext.Provider value={{
updateStatus,
downloadProgress,
kernelUpdateUrl,
checkForUpdates,
downloadLastApplicationUpdate,
restartAppForUpdateApply
}}>
{props.children}
</UpdateProviderContext.Provider>
);
}