182 lines
6.8 KiB
C++
182 lines
6.8 KiB
C++
// Lot MAPDL1 (MAPDL1-SPEC-05 v3, etape E5) : telechargement HTTP, controle SHA-256 et pose d'un pack de ville.
|
|
// Aucune dependance a l'interface : la fenetre (LPCityMapsWindow) lit l'etat et reagit a OnChanged.
|
|
//
|
|
// Deroule d'un travail (FLPCityMapsDownload), tout sur le fil de jeu sauf le calcul du SHA-256 et la pose (pool de fils) :
|
|
// 1. Saved/Downloads/<zip> deja present, de la bonne taille : SHA-256 recontrole, puis il sert directement.
|
|
// 2. Sinon Saved/Downloads/<zip>.part (vide apres un echec ou une annulation : deplace dans _rejet/), ecrit en flux par IHttpRequest::SetResponseBodyReceiveStream. Un .part non vide
|
|
// est repris par "Range: bytes=<taille>-" (206 attendu avec un Content-Range qui commence a la taille).
|
|
// Un 200 sur une reprise : le .part est DEPLACE dans Saved/Downloads/_rejet/ et le telechargement repart de zero.
|
|
// 3. 3 essais par URL (attentes 2, 4, 8 s), puis l'URL suivante ; toutes en echec : recours navigateur (NeedsBrowser),
|
|
// surveillance du dossier Telechargements de Windows et selecteur de fichier.
|
|
// 4. SHA-256 (OpenSSL, par blocs de 1 Mio, hors du fil de jeu). Ecart : le fichier est deplace dans _rejet/, URL suivante.
|
|
// 5. "Download only" : le .part devient Saved/Downloads/<zip>. "Full install" : en plus, le zip est controle (libzip), extrait
|
|
// dans Saved/Downloads/<pack>/_pose/, puis pose (Content/Modules/<pack> d'abord, Mods/<pack> en dernier) avec retour en
|
|
// arriere. Une ancienne installation est deplacee dans Saved/Downloads/_ancien/. Rien n'est jamais supprime.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Containers/Ticker.h"
|
|
#include "HAL/ThreadSafeBool.h"
|
|
#include "Interfaces/IHttpRequest.h"
|
|
|
|
/** Ce que le joueur a demande. */
|
|
enum class ELPCityInstallMode : uint8
|
|
{
|
|
DownloadOnly,
|
|
FullInstall
|
|
};
|
|
|
|
enum class ELPCityJobPhase : uint8
|
|
{
|
|
/** Rien en cours : avant le depart, apres une annulation ou une erreur (Message dit laquelle). */
|
|
Idle,
|
|
Downloading,
|
|
WaitingRetry,
|
|
Verifying,
|
|
Installing,
|
|
/** Toutes les URL ont echoue : recours navigateur (ouvrir la page, surveiller Telechargements, choisir un fichier). */
|
|
NeedsBrowser,
|
|
Succeeded
|
|
};
|
|
|
|
/** Donnees de l'entree de l'index utiles au travail (copiees : le travail ne depend pas du sous-systeme). */
|
|
struct FLPCityDownloadJob
|
|
{
|
|
/** Nom d'asset de la ville (journal et messages). */
|
|
FString Ville;
|
|
/** Identifiant du pack : lpm_city_<ville>. */
|
|
FString Pack;
|
|
FString Zip;
|
|
/** 64 chiffres hexadecimaux. */
|
|
FString Sha256;
|
|
int64 Octets = 0;
|
|
TArray<FString> Urls;
|
|
/** Page a ouvrir dans le navigateur en dernier recours (sinon Urls[0]). */
|
|
FString Page;
|
|
};
|
|
|
|
struct FLPCityDownloadState
|
|
{
|
|
ELPCityJobPhase Phase = ELPCityJobPhase::Idle;
|
|
ELPCityInstallMode Mode = ELPCityInstallMode::DownloadOnly;
|
|
int64 BytesDone = 0;
|
|
int64 BytesTotal = 0;
|
|
double BytesPerSecond = 0.0;
|
|
int32 UrlIndex = 0;
|
|
int32 Attempt = 0;
|
|
/** Dernier message en clair (anglais, affiche tel quel par la fenetre). */
|
|
FString Message;
|
|
bool bError = false;
|
|
/** Download only : chemin du zip. Full install : dossier Mods/<pack>. */
|
|
FString ResultPath;
|
|
/** Vrai apres une pose reussie dans cette session. */
|
|
bool bInstalled = false;
|
|
};
|
|
|
|
class FLPCityMapsDownload : public TSharedFromThis<FLPCityMapsDownload>
|
|
{
|
|
public:
|
|
explicit FLPCityMapsDownload(const FLPCityDownloadJob& InJob);
|
|
~FLPCityMapsDownload();
|
|
|
|
/** Appele sur le fil de jeu a chaque changement d'etat ou de progression. */
|
|
TFunction<void()> OnChanged;
|
|
|
|
/** Demarre (ou redemarre apres une annulation ou une erreur) le travail. Sans effet si un travail est en cours. */
|
|
void Start(ELPCityInstallMode InMode);
|
|
|
|
/** Interrompt le telechargement ou le controle en cours. Le .part est garde pour une reprise. Sans effet pendant la pose. */
|
|
void Cancel();
|
|
|
|
/** Recours : surveille le dossier Telechargements de Windows toutes les 2 s (un .zip de la taille exacte, au SHA-256 conforme). */
|
|
void StartWatchingDownloads();
|
|
void StopWatchingDownloads();
|
|
|
|
/** Recours : fichier choisi par le joueur (taille, SHA-256, copie dans Saved/Downloads, puis comme "Download only" ou "Full install"). */
|
|
void UseLocalFile(const FString& SourcePath);
|
|
|
|
/** Telechargement, controle ou pose en cours ? */
|
|
bool IsBusy() const;
|
|
|
|
const FLPCityDownloadJob& GetJob() const { return Job; }
|
|
const FLPCityDownloadState& GetState() const { return State; }
|
|
|
|
/** Saved/Downloads, chemin complet. */
|
|
static FString GetDownloadsDir();
|
|
|
|
/** Dossier Telechargements de Windows (SHGetKnownFolderPath). Faux hors Windows ou en cas d'echec. */
|
|
static bool FindSystemDownloadsFolder(FString& OutFolder);
|
|
|
|
/** Selecteur de fichier Win32 (GetOpenFileNameW). Faux si le joueur annule, ou hors Windows. */
|
|
static bool PickZipFile(FString& OutPath);
|
|
|
|
/** Nom d'hote d'une URL (rien d'autre : ni chemin, ni port, ni identifiants). */
|
|
static FString HostOf(const FString& Url);
|
|
|
|
private:
|
|
/** D'ou vient le fichier controle. */
|
|
enum class EVerifySource : uint8
|
|
{
|
|
/** Le .part qui vient d'etre telecharge. */
|
|
Part,
|
|
/** Saved/Downloads/<zip> deja present. */
|
|
Cache,
|
|
/** Zip trouve dans Telechargements. */
|
|
Watched,
|
|
/** Zip choisi par le joueur. */
|
|
Local
|
|
};
|
|
|
|
FString GetPartPath() const;
|
|
FString GetFinalPath() const;
|
|
void Notify();
|
|
void SetMessage(const FString& Text, bool bIsError);
|
|
|
|
void StartAttempt();
|
|
void HandleRequestProgress(FHttpRequestPtr Request, uint64 BytesSent, uint64 BytesReceived);
|
|
void HandleRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSucceeded);
|
|
void CloseWriter();
|
|
void AbortRequestQuietly();
|
|
void FailAttempt(const FString& Reason);
|
|
void ScheduleRetry(float DelaySeconds);
|
|
bool OnRetryTick(float DeltaTime);
|
|
void NextUrl(const FString& Reason);
|
|
void EnterBrowserRecourse();
|
|
|
|
void BeginVerify(const FString& Path, EVerifySource Source);
|
|
void OnVerified(const FString& Path, EVerifySource Source, bool bReadOk, const FString& Hex, const FString& Error);
|
|
void FinalizePart();
|
|
void CopyExternalToFinal(const FString& SourcePath);
|
|
void OnPackageReady();
|
|
|
|
void BeginInstall();
|
|
void OnInstalled(bool bOk, int32 FilesPlaced, const FString& Error, bool bRollbackOk);
|
|
|
|
bool OnWatchTick(float DeltaTime);
|
|
|
|
/** Deplace un fichier dans Saved/Downloads/_rejet/ (nom horodate). Renvoie la destination, ou vide si le deplacement a echoue. */
|
|
FString RejectFile(const FString& Path, const TCHAR* Why);
|
|
/** Ferme le flux ; un .part de 0 octet est deplace dans _rejet/. Vrai s'il a ete deplace. */
|
|
bool SetAsideEmptyPart();
|
|
|
|
FLPCityDownloadJob Job;
|
|
FLPCityDownloadState State;
|
|
bool bJobValid = true;
|
|
|
|
FHttpRequestPtr Request;
|
|
TSharedPtr<FArchive> PartWriter;
|
|
int64 ResumeFrom = 0;
|
|
bool bCancelRequested = false;
|
|
FString LastFailure;
|
|
|
|
double SpeedTime = 0.0;
|
|
int64 SpeedBytes = 0;
|
|
|
|
FTSTicker::FDelegateHandle RetryHandle;
|
|
FTSTicker::FDelegateHandle WatchHandle;
|
|
TSet<FString> ExaminedDownloads;
|
|
|
|
/** Partage avec les taches du pool de fils : arret du calcul du SHA-256. */
|
|
TSharedRef<FThreadSafeBool, ESPMode::ThreadSafe> CancelFlag;
|
|
};
|