using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Xml;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class RenderJobManager : MonoBehaviour
{
[Header("Serveur")]
[SerializeField]
private string nextJobUrl =
"https://ukitchenit.com/aidesign/render/next-job.php";
[SerializeField]
[Min(1f)]
private float verificationInterval = 3f;
[SerializeField]
[Min(0f)]
private float delayBeforeOpening = 2f;
[Header("Démarrage")]
[SerializeField]
private bool startAutomatically = false;
[Header("Interface")]
[SerializeField]
private Button searchButton;
[SerializeField]
private TMP_Text searchButtonText;
[SerializeField]
private TMP_Text statusText;
private bool searchEnabled;
private bool requestInProgress;
private bool jobFound;
/// Job en cours de rendu ("" si aucun). Lu par PDFCreate pour téléverser le PDF
/// et appeler CompleteJob une fois le design terminé.
public static string CurrentJobId = "";
private Coroutine searchCoroutine;
private string downloadFolder;
private void Awake()
{
downloadFolder = Path.Combine(
Application.persistentDataPath,
"RenderJobs"
);
try
{
Directory.CreateDirectory(downloadFolder);
}
catch (Exception exception)
{
Debug.LogException(exception);
SetStatus(
"Erreur : impossible de créer le dossier RenderJobs."
);
}
if (searchButton != null)
{
searchButton.onClick.AddListener(
ToggleJobSearch
);
}
UpdateButtonText();
}
private void Start()
{
if (startAutomatically)
{
StartJobSearch();
}
else
{
SetStatus("Recherche de jobs arrêtée.");
}
}
private void OnDestroy()
{
if (searchButton != null)
{
searchButton.onClick.RemoveListener(
ToggleJobSearch
);
}
}
// Source de vérité : la coroutine tourne-t-elle réellement ? On ne se fie PAS à searchEnabled
// seul, qui peut rester à true si Unity tue la coroutine (objet désactivé) sans passer par
// StopJobSearch — c'est ce désync qui obligeait à cliquer deux fois.
private bool IsSearching => searchCoroutine != null;
public void ToggleJobSearch()
{
if (IsSearching)
{
StopJobSearch();
}
else
{
StartJobSearch();
}
}
public void StartJobSearch()
{
if (IsSearching)
{
return; // déjà réellement en cours
}
// Repart d'un état propre, quels que soient les drapeaux laissés par le job précédent.
jobFound = false;
requestInProgress = false;
searchEnabled = true;
searchCoroutine = StartCoroutine(
SearchJobsLoop()
);
UpdateButtonText();
SetStatus("Recherche de jobs active.");
}
public void StopJobSearch()
{
searchEnabled = false;
jobFound = false;
if (searchCoroutine != null)
{
StopCoroutine(searchCoroutine);
searchCoroutine = null;
}
requestInProgress = false;
UpdateButtonText();
SetStatus("Recherche de jobs arrêtée.");
}
// Relance propre après un job (appelée par PDFCreate) : garantit une recherche réellement
// active en un seul appel, sans dépendre de l'état antérieur.
public void RestartJobSearch()
{
StopJobSearch();
StartJobSearch();
}
private IEnumerator SearchJobsLoop()
{
while (searchEnabled && !jobFound)
{
if (!requestInProgress)
{
yield return CheckNextJob();
}
if (searchEnabled && !jobFound)
{
yield return new WaitForSeconds(
verificationInterval
);
}
}
searchCoroutine = null;
}
private IEnumerator CheckNextJob()
{
requestInProgress = true;
string computerName =
UnityWebRequest.EscapeURL(
Environment.MachineName
);
string requestUrl =
nextJobUrl +
"?computer=" +
computerName +
"&nocache=" +
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
Debug.Log(
"[RenderJobManager] Vérification : " +
requestUrl
);
using UnityWebRequest request =
UnityWebRequest.Get(requestUrl);
request.timeout = 30;
request.SetRequestHeader(
"Cache-Control",
"no-cache"
);
yield return request.SendWebRequest();
Debug.Log("===========================");
Debug.Log(request.downloadHandler.text);
Debug.Log("===========================");
string responseText =
request.downloadHandler != null
? request.downloadHandler.text
: string.Empty;
Debug.Log(
"[RenderJobManager] Réponse PHP : " +
responseText
);
if (
request.result !=
UnityWebRequest.Result.Success
)
{
SetStatus(
"Erreur serveur : " +
request.error
);
Debug.LogError(
"[RenderJobManager] HTTP " +
request.responseCode +
" | " +
request.error +
" | " +
responseText
);
requestInProgress = false;
yield break;
}
NextJobResponse response = null;
string parsingError = null;
try
{
response =
JsonUtility.FromJson(
responseText
);
}
catch (Exception exception)
{
parsingError = exception.Message;
Debug.LogException(exception);
}
if (response == null)
{
SetStatus(
"Réponse JSON invalide : " +
parsingError
);
requestInProgress = false;
yield break;
}
if (!response.success)
{
SetStatus(
"Erreur serveur : " +
response.error
);
requestInProgress = false;
yield break;
}
if (
response.job == null ||
string.IsNullOrWhiteSpace(
response.job.fileName
) ||
string.IsNullOrWhiteSpace(
response.job.downloadUrl
)
)
{
SetStatus("Aucun job en attente.");
requestInProgress = false;
yield break;
}
requestInProgress = false;
jobFound = true;
searchEnabled = false;
CurrentJobId = response.job.jobId;
UpdateButtonText();
SetStatus(
"JOB TROUVÉ : " +
response.job.fileName
);
Debug.Log(
"[RenderJobManager] Job trouvé : " +
response.job.jobId +
" | Fichier : " +
response.job.fileName +
" | URL : " +
response.job.downloadUrl
);
if (delayBeforeOpening > 0f)
{
yield return new WaitForSeconds(
delayBeforeOpening
);
}
yield return DownloadAndOpenJob(
response.job
);
}
private IEnumerator DownloadAndOpenJob(
RenderJob job
)
{
string safeFileName =
Path.GetFileName(job.fileName);
if (
string.IsNullOrWhiteSpace(safeFileName) ||
!safeFileName.EndsWith(
".udt",
StringComparison.OrdinalIgnoreCase
)
)
{
SetStatus(
"Erreur : nom de fichier UDT invalide."
);
yield break;
}
string localFilePath =
Path.Combine(
downloadFolder,
safeFileName
);
string temporaryFilePath =
localFilePath + ".download";
DeleteFileIfExists(
temporaryFilePath
);
SetStatus(
"Téléchargement : " +
safeFileName
);
using UnityWebRequest downloadRequest =
UnityWebRequest.Get(
job.downloadUrl
);
downloadRequest.downloadHandler =
new DownloadHandlerFile(
temporaryFilePath
);
downloadRequest.timeout = 120;
downloadRequest.SetRequestHeader(
"Cache-Control",
"no-cache"
);
yield return downloadRequest.SendWebRequest();
if (
downloadRequest.result !=
UnityWebRequest.Result.Success
)
{
DeleteFileIfExists(
temporaryFilePath
);
SetStatus(
"Erreur de téléchargement : " +
downloadRequest.error
);
Debug.LogError(
"[RenderJobManager] Téléchargement échoué : " +
downloadRequest.responseCode +
" | " +
downloadRequest.error
);
yield break;
}
bool fileSaved = false;
string saveError = null;
try
{
if (File.Exists(localFilePath))
{
File.Delete(localFilePath);
}
File.Move(
temporaryFilePath,
localFilePath
);
fileSaved = true;
}
catch (Exception exception)
{
saveError = exception.Message;
Debug.LogException(exception);
}
if (!fileSaved)
{
DeleteFileIfExists(
temporaryFilePath
);
SetStatus(
"Erreur de sauvegarde : " +
saveError
);
yield break;
}
Debug.Log(
"[RenderJobManager] Fichier téléchargé : " +
localFilePath
);
SetStatus(
"Ouverture : " +
safeFileName
);
bool fileOpened = false;
string openingError = null;
try
{
OpenUdtFile(
localFilePath,
safeFileName
);
fileOpened = true;
}
catch (Exception exception)
{
openingError = exception.Message;
Debug.LogException(exception);
}
if (!fileOpened)
{
SetStatus(
"Erreur d’ouverture : " +
openingError
);
yield break;
}
SetStatus(
"Fichier ouvert : " +
safeFileName
);
Debug.Log(
"[RenderJobManager] Fichier ouvert avec succès : " +
safeFileName
);
}
private void OpenUdtFile(
string localFilePath,
string fileName
)
{
Debug.Log(
"[RenderJobManager] Chemin local : " +
localFilePath
);
Debug.Log(
"[RenderJobManager] Nom du fichier : " +
fileName
);
// Même principe que Load_WEB.LoadfromServer : lire le XML puis LoadXML.SetGLOBAL.
// Les exceptions remontent au try/catch de DownloadAndOpenJob.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(
File.ReadAllText(localFilePath, Encoding.UTF8)
);
GameObject loadPnl = Get.o2("HIDER", "LOADpnl");
if (loadPnl != null)
{
loadPnl.GetComponent().alpha = 0;
}
WaitCircle.Setting(true, TRANS.This("M_Download"));
// Le nom passé à LoadXML doit contenir "ai_" : c'est lui qui pose _G.FlieCategory="ai",
// ce qui déclenche ensuite ProcessAi -> PDF -> upload -> email -> CompleteJob.
LoadXML.SetGLOBAL(
xmlDoc,
AiFileName(fileName)
);
}
// Force le mode AI pour les jobs de rendu.
private static string AiFileName(string fileName)
{
string name =
Path.GetFileNameWithoutExtension(fileName);
return name.IndexOf(
"ai_",
StringComparison.OrdinalIgnoreCase
) >= 0
? name
: "ai_" + name;
}
private void UpdateButtonText()
{
if (searchButtonText == null)
{
return;
}
if (jobFound)
{
searchButtonText.text =
"JOB TROUVÉ";
return;
}
searchButtonText.text =
IsSearching
? "ARRÊTER LA RECHERCHE"
: "ACTIVER LA RECHERCHE";
}
private void SetStatus(
string message
)
{
if (statusText != null)
{
statusText.text = message;
}
Debug.Log(
"[RenderJobManager] " +
message
);
}
private static void DeleteFileIfExists(
string filePath
)
{
try
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
catch (Exception exception)
{
Debug.LogWarning(
"[RenderJobManager] Impossible de supprimer " +
filePath +
" : " +
exception.Message
);
}
}
[Serializable]
private class NextJobResponse
{
public bool success;
public RenderJob job;
public string error;
public string message;
}
[Serializable]
private class RenderJob
{
public string jobId;
public string fileName;
public string downloadUrl;
}
}