diff --git a/MP.AppAuth/DTO/UploadResult.cs b/MP.AppAuth/DTO/UploadResult.cs new file mode 100644 index 00000000..b025ccb3 --- /dev/null +++ b/MP.AppAuth/DTO/UploadResult.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MP.AppAuth.DTO +{ + + public class UploadResult + { + public bool Uploaded { get; set; } + public string FileName { get; set; } = ""; + public string StoredFileName { get; set; } = ""; + public int ErrorCode { get; set; } + } +} diff --git a/MP.AppAuth/MeasureUtils.cs b/MP.AppAuth/MeasureUtils.cs new file mode 100644 index 00000000..24b8f0e8 --- /dev/null +++ b/MP.AppAuth/MeasureUtils.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MP.AppAuth +{ + public class MeasureUtils + { + #region Public Fields + + public static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + + #endregion Public Fields + + #region Public Methods + + /// + /// Calcola dimensione file automaticamwente secondo dimensione + /// + /// + /// + /// + /// + public static string SizeSuffix(Int64 value, int decimalPlaces = 1) + { + if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); } + if (value < 0) { return "-" + SizeSuffix(-value, decimalPlaces); } + if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); } + + // mag is 0 for bytes, 1 for KB, 2, for MB, etc. + int mag = (int)Math.Log(value, 1024); + + // 1L << (mag * 10) == 2 ^ (10 * mag) [i.e. the number of bytes in the unit + // corresponding to mag] + decimal adjustedSize = (decimal)value / (1L << (mag * 10)); + + // make adjustment when the value is large enough that it would round up to 1000 or more + if (Math.Round(adjustedSize, decimalPlaces) >= 1000) + { + mag += 1; + adjustedSize /= 1024; + } + + return string.Format("{0:n" + decimalPlaces + "} {1}", + adjustedSize, + SizeSuffixes[mag]); + } + + #endregion Public Methods + } +} diff --git a/MP.Data/MP.Data.csproj b/MP.Data/MP.Data.csproj index 247dff73..8115b184 100644 --- a/MP.Data/MP.Data.csproj +++ b/MP.Data/MP.Data.csproj @@ -20,7 +20,7 @@ - + @@ -33,7 +33,7 @@ - - + + \ No newline at end of file diff --git a/MP.Land/Data/SyncService.cs b/MP.Land/Data/SyncService.cs index aa22bf70..a8417301 100644 --- a/MP.Land/Data/SyncService.cs +++ b/MP.Land/Data/SyncService.cs @@ -2,37 +2,55 @@ using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using MP.AppAuth.DTO; using MP.AppAuth.Models; using Newtonsoft.Json; +using NLog; using RestSharp; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using System.Web; +using static Egw.Core.LiManObj; using static System.Net.Mime.MediaTypeNames; namespace MP.Land.Data { public class SyncService { + #region Public Constructors + /// /// Init classe /// /// - /// - public SyncService(IConfiguration configuration, ILogger logger) + public SyncService(IConfiguration configuration) { - _logger = logger; _configuration = configuration; } - private static ILogger _logger { get; set; } = null!; - private static IConfiguration? _configuration; + #endregion Public Constructors + + #region Public Properties + + public List AKVList { get; set; } = new List(); + public string Applicazione { get; set; } = ""; /// - /// URL dell'API x chiamate gestione licenze + /// Codice cliente/installazione /// - private static string apiUrl = "https://liman.egalware.com/ELM.API/"; + public string Installazione { get; set; } = ""; + + /// + /// Master key licenza principale + /// + public string MasterKey { get; set; } = ""; + + #endregion Public Properties + + #region Public Methods /// /// Stato server gestione licenze @@ -55,30 +73,7 @@ namespace MP.Land.Data } return await Task.FromResult(answ); } - public List AKVList { get; set; } = new List(); - public string Applicazione { get; set; } = ""; - /// - /// Codice cliente/installazione - /// - public string Installazione { get; set; } = ""; - /// - /// Cerca di recuperare valore string da elenco AKV - /// - /// Chiave AKV richiesta - /// - protected string getAVKStr(string varReq) - { - string answ = ""; - if (AKVList != null && AKVList.Count > 0) - { - var currRec = AKVList.Where(x => x.NomeVar == varReq).FirstOrDefault(); - if (currRec != null) - { - answ = $"{currRec.ValString}"; - } - } - return answ; - } + /// /// Init della classe con variabili di base da Redis/DB /// @@ -92,11 +87,6 @@ namespace MP.Land.Data return fatto; } - /// - /// Master key licenza principale - /// - public string MasterKey { get; set; } = ""; - /// /// Lista record AnagKeyVal /// @@ -159,5 +149,106 @@ namespace MP.Land.Data } return await Task.FromResult(answ); } + + /// + /// Invio file zip di backup al server centrale + /// + /// Cod Applicazione + /// CLiente / Installazione + /// Invia richiesta UnZip + /// Invia richiesta Approvazione + /// ZipFile da inviare + /// + public async Task SendZipFile(string CodApp, string CodInst, bool DoUnzip, bool ForceApprov, FileInfo ZipFile) + { + bool answ = false; + try + { + // client chiamate rest + var client = new RestClient(restOptStd); + // Chiamo il metodo! + var actReq = new RestRequest($"/api/filesave/zipbackup", Method.Post); + actReq.AddParameter("CodApp", $"{CodApp}"); + actReq.AddParameter("CodInst", $"{CodInst}"); + actReq.AddParameter("DoUnzip", $"{DoUnzip}"); + actReq.AddParameter("ForceApprov", $"{ForceApprov}"); + actReq.AddFile("ZipFile", ZipFile.FullName); + actReq.AddHeader("Content-Type", "multipart/form-data"); + // effettuo vera chiamata + var currResp = await client.ExecuteAsync(actReq); + if (currResp.StatusCode == System.Net.HttpStatusCode.OK && currResp.Content != null) + { + // mi restituisce esito upload + var currList = JsonConvert.DeserializeObject>(currResp.Content); + if (currList != null) + { + // se contiene anche la richiesta è ok... + var recUpd = currList.FirstOrDefault(x => x.FileName == ZipFile.Name); + if (recUpd != null) + { + answ = recUpd.Uploaded; + } + } + } + } + catch (Exception exc) + { + Log.Error($"Eccezione in fase gestione REST services SendZipFile{Environment.NewLine}{exc}"); + } + + return answ; + } + + #endregion Public Methods + + #region Protected Methods + + /// + /// Cerca di recuperare valore string da elenco AKV + /// + /// Chiave AKV richiesta + /// + protected string getAVKStr(string varReq) + { + string answ = ""; + if (AKVList != null && AKVList.Count > 0) + { + var currRec = AKVList.Where(x => x.NomeVar == varReq).FirstOrDefault(); + if (currRec != null) + { + answ = $"{currRec.ValString}"; + } + } + return answ; + } + + #endregion Protected Methods + + #region Private Fields + + private static IConfiguration? _configuration; + + /// + /// URL dell'API x chiamate gestione licenze + /// +#if DEBUG + private static string apiUrl = "https://localhost:5003/"; +#else + private static string apiUrl = "https://liman.egalware.com/ELM.API/"; +#endif + + /// + /// Classe logger + /// + private static Logger Log = LogManager.GetCurrentClassLogger(); + + /// + /// Conf client RestSharp standard: + /// - base URI al sito + /// - timeout 1 min + /// + private static RestClientOptions restOptStd = new RestClientOptions { Timeout = TimeSpan.FromSeconds(60), BaseUrl = new Uri(apiUrl) }; + + #endregion Private Fields } -} +} \ No newline at end of file diff --git a/MP.Land/MP.Land.csproj b/MP.Land/MP.Land.csproj index 9cf91011..7963d028 100644 --- a/MP.Land/MP.Land.csproj +++ b/MP.Land/MP.Land.csproj @@ -3,7 +3,7 @@ net6.0 MP.Land - 6.16.2409.1212 + 6.16.2410.1815 @@ -55,6 +55,7 @@ + @@ -63,9 +64,15 @@ + + PreserveNewest + Always + + PreserveNewest + diff --git a/MP.Land/Pages/About.razor b/MP.Land/Pages/About.razor index ffa93c15..1dc4795c 100644 --- a/MP.Land/Pages/About.razor +++ b/MP.Land/Pages/About.razor @@ -1,8 +1,4 @@ @page "/About" -@using MP.Land.Data - -@inject MessageService AppMService -@inject LicenseService LicServ
diff --git a/MP.Land/Pages/About.razor.cs b/MP.Land/Pages/About.razor.cs index be225631..c18f570c 100644 --- a/MP.Land/Pages/About.razor.cs +++ b/MP.Land/Pages/About.razor.cs @@ -1,11 +1,21 @@ +using Microsoft.AspNetCore.Components; +using MP.Land.Data; using NLog; using System; +using System.Net.NetworkInformation; using System.Threading.Tasks; namespace MP.Land.Pages { public partial class About { + + + + [Inject] + protected MessageService AppMService { get; set; } = null!; + [Inject] + protected LicenseService LicServ{get;set;}=null!; #region Protected Methods protected override async Task OnInitializedAsync() diff --git a/MP.Land/Pages/UpdateManager.razor b/MP.Land/Pages/UpdateManager.razor index a8069297..07c81203 100644 --- a/MP.Land/Pages/UpdateManager.razor +++ b/MP.Land/Pages/UpdateManager.razor @@ -14,22 +14,23 @@

Scaricare uno o più pacchetti selezionando dall'elenco riportato.

+
- ALL Packages + ALL Packages
- +
@if (showUpdate) {
- -
-
+ +
+
@outMessages
-
+
@if (showProgress) {
diff --git a/MP.Land/Pages/UpdateManager.razor.cs b/MP.Land/Pages/UpdateManager.razor.cs index 535798a2..d7c17bd7 100644 --- a/MP.Land/Pages/UpdateManager.razor.cs +++ b/MP.Land/Pages/UpdateManager.razor.cs @@ -1,11 +1,16 @@ -using Microsoft.AspNetCore.Components; +using ICSharpCode.SharpZipLib.Zip; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using MP.AppAuth; using MP.Land.Data; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; +using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; namespace MP.Land.Pages @@ -14,6 +19,32 @@ namespace MP.Land.Pages { #region Public Methods + /// + /// Procedura di aggiunta folder a ZipFile, ricorsiva + /// + /// + /// + /// + public void AddFolderToZip(ZipFile f, string root, string folder) + { + string relative = folder.Substring(root.Length); + if (relative.Length > 0) + { + f.AddDirectory(relative); + } + + foreach (string file in Directory.GetFiles(folder)) + { + relative = file.Substring(root.Length); + f.Add(file, relative); + } + + foreach (string subFolder in Directory.GetDirectories(folder)) + { + this.AddFolderToZip(f, root, subFolder); + } + } + public void Dispose() { ListRecords = null; @@ -30,7 +61,7 @@ namespace MP.Land.Pages protected int totalCount = 0; - protected double TotalMb = 0; + protected long TotalMb = 0; protected UpdateMan updateManAuth = new UpdateMan("SWDownloader", "viaD@nte16"); @@ -55,10 +86,28 @@ namespace MP.Land.Pages protected bool showProgress { get; set; } = false; protected bool showUpdate { get; set; } = false; + [Inject] + protected SyncService SyncServ { get; set; } = null!; + #endregion Protected Properties #region Protected Methods + /// + /// Restituisce size calcolata + /// + /// + /// + protected string CalcSize(long origSize) + { + return MeasureUtils.SizeSuffix(origSize, 1); + } + + protected void CloseProgUpdate() + { + showUpdate = false; + } + /// /// Cicla su tutti i record ed effettua il download /// @@ -68,7 +117,7 @@ namespace MP.Land.Pages showProgress = true; showUpdate = true; outMessages = $"Iniziato download per {numTot} packages"; - StateHasChanged(); + await InvokeAsync(StateHasChanged); percLoading = 0; TotalMb = 0; numDone = 0; @@ -90,20 +139,16 @@ namespace MP.Land.Pages numTot = authList.Count; foreach (var item in authList) { - long size = 0; - size = await scaricaSingolo(item); - TotalMb += (double)size / (1024 * 1024); - outMessages = $"Scaricati {numDone}/{numTot} packages | {TotalMb:N2} Mb"; - StateHasChanged(); - await Task.Delay(1); + ScaricaSingolo(item); + await InvokeAsync(StateHasChanged); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; - outMessages = $"Effettuato download di {numDone} update | {TotalMb:N2} Mb in {ts.TotalSeconds:N2} s"; - StateHasChanged(); + outMessages = $"Effettuato download di {numDone} update | {CalcSize(TotalMb)} in {ts.TotalSeconds:N2} s"; + await InvokeAsync(StateHasChanged); await Task.Delay(1000); showProgress = false; - StateHasChanged(); + await InvokeAsync(StateHasChanged); } protected string localPath(string localRepo) @@ -130,6 +175,66 @@ namespace MP.Land.Pages await Task.Delay(1); } + /// + /// Cicla su tutti i record applicativi, crea ZIP cifrato + upload su LiMan + /// + protected async Task UploadBackupConfig() + { + // init progress... + showProgress = true; + showUpdate = true; + outMessages = $"Inizio preparazione per upload configurazioni di {numTot} packages"; + await InvokeAsync(StateHasChanged); + percLoading = 0; + TotalMb = 0; + numDone = 0; + int numIOB = 0; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + // ciclo su tutti quelli con licenza valida... + List rawList = ListRecords + .Where(x => !string.IsNullOrEmpty(x.LicenseKey)).ToList(); + List authList = new List(); + + // ciclo SOLO tra quelli davvero autorizzati... + foreach (var item in rawList) + { + if (LicServ.checkLicenseActive(item.LicenseKey)) + { + authList.Add(item); + } + } + // numero app + IOB + DB + ZIP + numTot = authList.Count + 3; + + // recupero conf files + foreach (var item in authList) + { + // cerco i file di conf e li copio + RecuperaConf(item); + await Task.Delay(10); + await InvokeAsync(StateHasChanged); + } + // recupero i file IOB + RecuperaIobConf(); + // salvo Tab Db come json + SaveDbConfAsJson(); + + // ora creo il file zip + PrepareZip(); + // effettuo upload + await UploadZip(); + + // concludo! + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + outMessages = $"Effettuato Upload backup configurazioni per {numDone} app + {numIOB} IOB | {CalcSize(TotalMb)} | {ts.TotalSeconds:N2} s"; + await InvokeAsync(StateHasChanged); + await Task.Delay(1000); + showProgress = false; + await InvokeAsync(StateHasChanged); + } + #endregion Protected Methods #region Private Fields @@ -138,9 +243,165 @@ namespace MP.Land.Pages #endregion Private Fields +#if DEBUG + + #region Private Properties + + private DirectoryInfo AppDir => new DirectoryInfo(Path.Combine("\\\\iis01.egalware.com", "c$\\inetpub\\wwwroot\\MP\\LAND")); +#else + private DirectoryInfo AppDir => new DirectoryInfo(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)); +#endif + + /// + /// Nome del file ZIP da gestire + /// + private string zFileName => Path.Combine(AppDir.FullName, "temp", "zip", "MAPO.zip"); + + #endregion Private Properties + #region Private Methods - private async Task scaricaSingolo(AppAuth.Models.UpdMan item) + /// + /// Preparazione ZIP con password = AuthKey + /// + private void PrepareZip() + { + string srcPath = Path.Combine(AppDir.FullName, "temp", "orig"); + string destPath = Path.Combine(AppDir.FullName, "temp", "zip"); + if (!Directory.Exists(destPath)) + { + Directory.CreateDirectory(destPath); + } + using (ZipFile zipFile = ZipFile.Create(zFileName)) + { + zipFile.Password = LicServ.MasterKey; + zipFile.BeginUpdate(); + AddFolderToZip(zipFile, srcPath, srcPath); + zipFile.CommitUpdate(); + zipFile.Close(); + } + } + + /// + /// Recupera fdile di conf da app indicata + /// + /// + private void RecuperaConf(AppAuth.Models.UpdMan item) + { + long size = 0; + if (item.IsAuth) + { + string dstDir = Path.Combine(AppDir.FullName, "temp", "orig", item.PackName); + if (!Directory.Exists(dstDir)) + { + Directory.CreateDirectory(dstDir); + } + string srcDir = Path.Combine(AppDir.Parent.FullName, item.PackName); + // recupero elenco files tipo appsettings*.json + if (Directory.Exists(srcDir)) + { + var dirInfo = new DirectoryInfo(srcDir); + // recupero files CORE + List fileList = dirInfo.GetFiles("appsettings*.json").ToList(); + // recupero files Framework + List fileListFram = dirInfo.GetFiles("web*.config").ToList(); + // merge... + foreach (var fileFrm in fileListFram) + { + fileList.Add(fileFrm); + } + // procedo! + foreach (var file in fileList) + { + string fileDestPath = Path.Combine(dstDir, file.Name); + file.CopyTo(fileDestPath, true); + size += file.Length; + } + numDone++; + percLoading = 100 * numDone / numTot; + TotalMb += size; + outMessages = $"Configurazioni preparate: {numDone}/{numTot} | {CalcSize(TotalMb)}"; + } + } + } + + /// + /// recupera tutte le conf IOB trasferite all'MP-IO dai vari IOB-WIN + /// + private void RecuperaIobConf() + { + long size = 0; + + string dstDir = Path.Combine(AppDir.FullName, "temp", "orig", "IOB"); + if (!Directory.Exists(dstDir)) + { + Directory.CreateDirectory(dstDir); + } + string srcIobDir = Path.Combine(AppDir.Parent.FullName, "IO", "fileUpload"); + // recupero elenco files tipo appsettings*.json + if (Directory.Exists(srcIobDir)) + { + var dirInfo = new DirectoryInfo(srcIobDir); + // recupero files CORE + List fileList = dirInfo.GetFiles().ToList(); + // procedo! + foreach (var file in fileList) + { + string fileDestPath = Path.Combine(dstDir, file.Name); + file.CopyTo(fileDestPath, true); + size += file.Length; + } + numDone++; + percLoading = 100 * numDone / numTot; + TotalMb += size; + outMessages = $"Configurazioni preparate: {numDone}/{numTot} | {CalcSize(TotalMb)}"; + } + } + + /// + /// Salvataggio tabelle di configurazione specifica come tracciati json + /// + /// + private void SaveDbConfAsJson() + { + long size = 0; + + string dstDir = Path.Combine(AppDir.FullName, "temp", "orig", "DB"); + if (!Directory.Exists(dstDir)) + { + Directory.CreateDirectory(dstDir); + } + // va fatta 1:1 per ogni tabella + +#if false + string srcIobDir = Path.Combine(AppDir.Parent.FullName, "IO", "fileUpload"); + // recupero elenco files tipo appsettings*.json + if (Directory.Exists(srcIobDir)) + { + var dirInfo = new DirectoryInfo(srcIobDir); + // recupero files CORE + List fileList = dirInfo.GetFiles().ToList(); + // procedo! + foreach (var file in fileList) + { + string fileDestPath = Path.Combine(dstDir, file.Name); + file.CopyTo(fileDestPath, true); + size += file.Length; + } + } +#endif + + numDone++; + percLoading = 100 * numDone / numTot; + TotalMb += size; + outMessages = $"Configurazioni preparate: {numDone}/{numTot} | {CalcSize(TotalMb)}"; + } + + /// + /// Effettua download di una singola app + /// + /// + private void ScaricaSingolo(AppAuth.Models.UpdMan item) { long size = 0; if (item.IsAuth) @@ -153,7 +414,21 @@ namespace MP.Land.Pages } numDone++; percLoading = 100 * numDone / numTot; - return await Task.FromResult(size); + TotalMb += size; + outMessages = $"Scaricati {numDone}/{numTot} packages | {CalcSize(TotalMb)}"; + } + + /// + /// Invio zip al server LiMan remoto + /// + /// + /// + private async Task UploadZip() + { + // chiamo SendZipFile di SyncService... + FileInfo zFileInfo = new FileInfo(zFileName); + var fatto = await SyncServ.SendZipFile(LicServ.Applicazione, LicServ.Installazione, true, false, zFileInfo); + return fatto; } #endregion Private Methods diff --git a/MP.Land/Resources/ChangeLog.html b/MP.Land/Resources/ChangeLog.html index e51289da..06b7ca50 100644 --- a/MP.Land/Resources/ChangeLog.html +++ b/MP.Land/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo Tablet MAPO - DotNet6 -

Versione: 6.16.2409.1212

+

Versione: 6.16.2410.1815


Note di rilascio:
    diff --git a/MP.Land/Resources/VersNum.txt b/MP.Land/Resources/VersNum.txt index aa071951..aa74da27 100644 --- a/MP.Land/Resources/VersNum.txt +++ b/MP.Land/Resources/VersNum.txt @@ -1 +1 @@ -6.16.2409.1212 +6.16.2410.1815 diff --git a/MP.Land/Resources/manifest.xml b/MP.Land/Resources/manifest.xml index c61f05d5..63a924b1 100644 --- a/MP.Land/Resources/manifest.xml +++ b/MP.Land/Resources/manifest.xml @@ -1,6 +1,6 @@ - 6.16.2409.1212 + 6.16.2410.1815 https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/MP.Land.zip https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/ChangeLog.html false diff --git a/MP.Land/temp/.placeholder b/MP.Land/temp/.placeholder new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/MP.Land/temp/.placeholder @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MP.SPEC/Components/ListODL.razor b/MP.SPEC/Components/ListODL.razor index 94945c58..1c031d67 100644 --- a/MP.SPEC/Components/ListODL.razor +++ b/MP.SPEC/Components/ListODL.razor @@ -199,123 +199,12 @@ else
-