175 lines
6.7 KiB
TypeScript
175 lines
6.7 KiB
TypeScript
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>
|
||
);
|
||
} |