mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-03-22 17:32:23 +00:00
- NioTSO client isn't needed because we're using RayLib - Added FreeSO's API server to handle most backend operations
324 lines
16 KiB
C#
Executable file
324 lines
16 KiB
C#
Executable file
using FSO.Server.Api.Core.Models;
|
|
using FSO.Server.Database.DA.Updates;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Threading.Tasks;
|
|
using FSO.Files.Utils;
|
|
using FSO.Server.Common;
|
|
using System.Diagnostics;
|
|
|
|
namespace FSO.Server.Api.Core.Services
|
|
{
|
|
public class GenerateUpdateService
|
|
{
|
|
private static GenerateUpdateService _INSTANCE;
|
|
public static GenerateUpdateService INSTANCE
|
|
{
|
|
get
|
|
{
|
|
if (_INSTANCE == null) _INSTANCE = new GenerateUpdateService();
|
|
return _INSTANCE;
|
|
}
|
|
}
|
|
|
|
private int LastTaskID = 0;
|
|
public Dictionary<int, UpdateGenerationStatus> Tasks = new Dictionary<int, UpdateGenerationStatus>();
|
|
|
|
public UpdateGenerationStatus GetTask(int id)
|
|
{
|
|
UpdateGenerationStatus result;
|
|
lock (Tasks)
|
|
{
|
|
if (!Tasks.TryGetValue(id, out result)) return null;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public UpdateGenerationStatus CreateTask(UpdateCreateModel request)
|
|
{
|
|
UpdateGenerationStatus task;
|
|
lock (Tasks)
|
|
{
|
|
task = new UpdateGenerationStatus(++LastTaskID, request);
|
|
Tasks[LastTaskID] = task;
|
|
}
|
|
Task.Run(() => BuildUpdate(task));
|
|
return task;
|
|
}
|
|
|
|
private void Exec(string cmd)
|
|
{
|
|
var escapedArgs = cmd.Replace("\"", "\\\"");
|
|
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
RedirectStandardOutput = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
WindowStyle = ProcessWindowStyle.Hidden,
|
|
FileName = "/bin/sh",
|
|
Arguments = $"-c \"{escapedArgs}\""
|
|
}
|
|
};
|
|
|
|
process.Start();
|
|
process.WaitForExit();
|
|
}
|
|
|
|
private void ClearFolderPermissions(string folder)
|
|
{
|
|
if (Environment.OSVersion.Platform == PlatformID.Unix)
|
|
Exec($"chmod -R 777 {folder}");
|
|
}
|
|
|
|
private string GetZipFolder(string path)
|
|
{
|
|
var directories = Directory.GetDirectories(path);
|
|
if (directories.Length != 1) return path;
|
|
var files = Directory.GetFiles(path);
|
|
if (files.Length != 0) return path;
|
|
return directories[0];
|
|
}
|
|
|
|
public async Task BuildUpdate(UpdateGenerationStatus status)
|
|
{
|
|
var request = status.Request;
|
|
var api = Api.INSTANCE;
|
|
|
|
try
|
|
{
|
|
status.UpdateStatus(UpdateGenerationStatusCode.PREPARING);
|
|
using (var da = api.DAFactory.Get())
|
|
{
|
|
var baseUpdateKey = "updates/";
|
|
var branch = da.Updates.GetBranch(status.Request.branchID);
|
|
|
|
//reserve update id. may cause race condition, but only one person can update anyways.
|
|
if (request.minorVersion) ++branch.minor_version_number;
|
|
else
|
|
{
|
|
++branch.last_version_number;
|
|
branch.minor_version_number = 0;
|
|
}
|
|
var updateID = branch.last_version_number;
|
|
var minorChar = (branch.minor_version_number == 0) ? "" : ((char)('a' + (branch.minor_version_number - 1))).ToString();
|
|
var versionName = branch.version_format.Replace("#", updateID.ToString()).Replace("@", minorChar);
|
|
var versionText = versionName;
|
|
|
|
var result = new DbUpdate()
|
|
{
|
|
addon_id = branch.addon_id,
|
|
branch_id = branch.branch_id,
|
|
date = DateTime.UtcNow,
|
|
version_name = versionName,
|
|
deploy_after = Epoch.ToDate(status.Request.scheduledEpoch)
|
|
};
|
|
|
|
versionName = versionName.Replace('/', '-');
|
|
|
|
var client = new WebClient();
|
|
//fetch artifacts
|
|
//http://servo.freeso.org/guestAuth/repository/download/FreeSO_TsoClient/.lastSuccessful/client-<>.zip
|
|
//http://servo.freeso.org/guestAuth/repository/download/FreeSO_TsoClient/.lastSuccessful/server-<>.zip
|
|
|
|
int updateWorkID = status.TaskID;
|
|
|
|
var updateDir = "updateTemp/" + updateWorkID + "/";
|
|
try
|
|
{
|
|
Directory.Delete(updateDir, true);
|
|
}
|
|
catch (Exception) { }
|
|
Directory.CreateDirectory(updateDir);
|
|
Directory.CreateDirectory(updateDir + "client/");
|
|
Directory.CreateDirectory(updateDir + "server/");
|
|
|
|
string clientArti = null;
|
|
string serverArti = null;
|
|
if (branch.base_build_url != null)
|
|
{
|
|
status.UpdateStatus(UpdateGenerationStatusCode.DOWNLOADING_CLIENT);
|
|
await client.DownloadFileTaskAsync(new Uri(branch.base_build_url), updateDir + "client.zip");
|
|
clientArti = updateDir + "client.zip";
|
|
}
|
|
if (branch.base_server_build_url != null)
|
|
{
|
|
status.UpdateStatus(UpdateGenerationStatusCode.DOWNLOADING_SERVER);
|
|
await client.DownloadFileTaskAsync(new Uri(branch.base_server_build_url), updateDir + "server.zip");
|
|
serverArti = updateDir + "server.zip";
|
|
}
|
|
|
|
string clientAddon = null;
|
|
string serverAddon = null;
|
|
|
|
if (branch.addon_id != null)
|
|
{
|
|
var addon = da.Updates.GetAddon(branch.addon_id.Value);
|
|
if (addon.addon_zip_url != null)
|
|
{
|
|
status.UpdateStatus(UpdateGenerationStatusCode.DOWNLOADING_CLIENT_ADDON);
|
|
await client.DownloadFileTaskAsync(new Uri(addon.addon_zip_url), updateDir + "clientAddon.zip");
|
|
clientAddon = updateDir + "clientAddon.zip";
|
|
}
|
|
if (addon.server_zip_url != null)
|
|
{
|
|
status.UpdateStatus(UpdateGenerationStatusCode.DOWNLOADING_SERVER_ADDON);
|
|
await client.DownloadFileTaskAsync(new Uri(addon.addon_zip_url), updateDir + "serverAddon.zip");
|
|
serverAddon = updateDir + "serverAddon.zip";
|
|
}
|
|
else
|
|
{
|
|
serverAddon = clientAddon;
|
|
}
|
|
}
|
|
|
|
//last client update.
|
|
var previousUpdate = (branch.current_dist_id == null) ? null : da.Updates.GetUpdate(branch.current_dist_id.Value);
|
|
|
|
//all files downloaded. build the folders.
|
|
//extract the artifact and then our artifact over it.
|
|
if (clientArti != null)
|
|
{
|
|
var clientPath = updateDir + "client/";
|
|
status.UpdateStatus(UpdateGenerationStatusCode.EXTRACTING_CLIENT);
|
|
var clientZip = ZipFile.Open(clientArti, ZipArchiveMode.Read);
|
|
clientZip.ExtractToDirectory(clientPath, true);
|
|
clientZip.Dispose();
|
|
File.Delete(clientArti);
|
|
clientPath = GetZipFolder(clientPath);
|
|
|
|
if (clientAddon != null)
|
|
{
|
|
status.UpdateStatus(UpdateGenerationStatusCode.EXTRACTING_CLIENT_ADDON);
|
|
var addonZip = ZipFile.Open(clientAddon, ZipArchiveMode.Read);
|
|
addonZip.ExtractToDirectory(clientPath, true);
|
|
addonZip.Dispose();
|
|
if (clientAddon != serverAddon) File.Delete(clientAddon);
|
|
}
|
|
//emit version number
|
|
await System.IO.File.WriteAllTextAsync(Path.Combine(clientPath, "version.txt"), versionText);
|
|
if (request.catalog != null)
|
|
{
|
|
await System.IO.File.WriteAllTextAsync(Path.Combine(clientPath, "Content/Objects/catalog_downloads.xml"), request.catalog);
|
|
}
|
|
|
|
string diffZip = null;
|
|
FSOUpdateManifest manifest = null;
|
|
|
|
status.UpdateStatus(UpdateGenerationStatusCode.BUILDING_DIFF);
|
|
if (previousUpdate != null || request.disableIncremental)
|
|
{
|
|
result.last_update_id = previousUpdate.update_id;
|
|
//calculate difference, generate an incremental update manifest + zip
|
|
var prevFile = updateDir + "prev.zip";
|
|
await client.DownloadFileTaskAsync(new Uri(previousUpdate.full_zip), updateDir + "prev.zip");
|
|
var prevZip = ZipFile.Open(prevFile, ZipArchiveMode.Read);
|
|
prevZip.ExtractToDirectory(updateDir + "prev/", true);
|
|
prevZip.Dispose();
|
|
File.Delete(updateDir + "prev.zip");
|
|
|
|
var diffs = DiffGenerator.GetDiffs(Path.GetFullPath(updateDir + "prev/"), Path.GetFullPath(clientPath));
|
|
|
|
status.UpdateStatus(UpdateGenerationStatusCode.BUILDING_INCREMENTAL_UPDATE);
|
|
var toZip = diffs.Where(x => x.DiffType == FileDiffType.Add || x.DiffType == FileDiffType.Modify);
|
|
if (request.contentOnly) toZip = toZip.Where(x => x.Path.Replace('\\', '/').TrimStart('/').StartsWith("Content"));
|
|
if (!request.includeMonogameDelta) toZip = toZip.Where(x => !x.Path.Replace('\\', '/').TrimStart('/').StartsWith("Monogame"));
|
|
//build diff folder
|
|
Directory.CreateDirectory(updateDir + "diff/");
|
|
foreach (var diff in toZip)
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(updateDir + "diff/", diff.Path)));
|
|
System.IO.File.Copy(Path.Combine(clientPath, diff.Path), Path.Combine(updateDir + "diff/", diff.Path));
|
|
}
|
|
diffZip = updateDir + "diffResult.zip";
|
|
ClearFolderPermissions(updateDir + "diff/");
|
|
ZipFile.CreateFromDirectory(updateDir + "diff/", diffZip, CompressionLevel.Optimal, false);
|
|
Directory.Delete(updateDir + "diff/", true);
|
|
manifest = new FSOUpdateManifest() { Diffs = diffs };
|
|
|
|
Directory.Delete(updateDir + "prev/", true);
|
|
}
|
|
else
|
|
{
|
|
if (request.contentOnly) throw new Exception("Invalid request - you cannot make a content only update with no delta.");
|
|
//full update only. generate simple manifest that contains all files (added)
|
|
manifest = new FSOUpdateManifest() { Diffs = new List<FileDiff>() };
|
|
}
|
|
|
|
//pack full client
|
|
if (!request.contentOnly)
|
|
{
|
|
status.UpdateStatus(UpdateGenerationStatusCode.BUILDING_CLIENT);
|
|
var finalClientZip = updateDir + "clientResult.zip";
|
|
ClearFolderPermissions(clientPath);
|
|
ZipFile.CreateFromDirectory(clientPath, finalClientZip, CompressionLevel.Optimal, false);
|
|
Directory.Delete(updateDir + "client/", true);
|
|
|
|
status.UpdateStatus(UpdateGenerationStatusCode.PUBLISHING_CLIENT);
|
|
result.full_zip = await Api.INSTANCE.UpdateUploaderClient.UploadFile($"{baseUpdateKey}client-{versionName}.zip", finalClientZip, versionName);
|
|
}
|
|
status.UpdateStatus(UpdateGenerationStatusCode.PUBLISHING_CLIENT);
|
|
if (diffZip != null)
|
|
{
|
|
result.incremental_zip = await Api.INSTANCE.UpdateUploaderClient.UploadFile($"{baseUpdateKey}incremental-{versionName}.zip", diffZip, versionName);
|
|
}
|
|
await System.IO.File.WriteAllTextAsync(updateDir + "manifest.json", Newtonsoft.Json.JsonConvert.SerializeObject(manifest));
|
|
result.manifest_url = await Api.INSTANCE.UpdateUploaderClient.UploadFile($"{baseUpdateKey}{versionName}.json", updateDir + "manifest.json", versionName);
|
|
}
|
|
|
|
if (serverArti != null && !request.contentOnly)
|
|
{
|
|
var serverPath = updateDir + "server/";
|
|
status.UpdateStatus(UpdateGenerationStatusCode.EXTRACTING_SERVER);
|
|
var serverZip = ZipFile.Open(serverArti, ZipArchiveMode.Read);
|
|
serverZip.ExtractToDirectory(serverPath, true);
|
|
serverZip.Dispose();
|
|
File.Delete(serverArti);
|
|
serverPath = GetZipFolder(serverPath);
|
|
|
|
if (serverAddon != null)
|
|
{
|
|
status.UpdateStatus(UpdateGenerationStatusCode.EXTRACTING_SERVER_ADDON);
|
|
var addonZip = ZipFile.Open(serverAddon, ZipArchiveMode.Read);
|
|
addonZip.ExtractToDirectory(serverPath, true);
|
|
addonZip.Dispose();
|
|
File.Delete(serverAddon);
|
|
}
|
|
//emit version number
|
|
await System.IO.File.WriteAllTextAsync(Path.Combine(serverPath, "version.txt"), versionText);
|
|
if (request.catalog != null)
|
|
{
|
|
await System.IO.File.WriteAllTextAsync(Path.Combine(serverPath, "Content/Objects/catalog_downloads.xml"), request.catalog);
|
|
}
|
|
|
|
status.UpdateStatus(UpdateGenerationStatusCode.BUILDING_SERVER);
|
|
var finalServerZip = updateDir + "serverResult.zip";
|
|
ClearFolderPermissions(serverPath);
|
|
ZipFile.CreateFromDirectory(serverPath, finalServerZip, CompressionLevel.Optimal, false);
|
|
Directory.Delete(updateDir + "server/", true);
|
|
|
|
status.UpdateStatus(UpdateGenerationStatusCode.PUBLISHING_SERVER);
|
|
result.server_zip = await Api.INSTANCE.UpdateUploader.UploadFile($"{baseUpdateKey}server-{versionName}.zip", finalServerZip, versionName);
|
|
} else
|
|
{
|
|
result.server_zip = result.incremental_zip; //same as client, as server uses same content.
|
|
}
|
|
|
|
status.UpdateStatus(UpdateGenerationStatusCode.SCHEDULING_UPDATE);
|
|
var finalID = da.Updates.AddUpdate(result);
|
|
da.Updates.UpdateBranchLatest(branch.branch_id, branch.last_version_number, branch.minor_version_number);
|
|
status.SetResult(result);
|
|
}
|
|
} catch (Exception e)
|
|
{
|
|
status.SetFailure("Update could not be completed." + e.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|