using Ionic.Zip; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.BL; using Tango.Core; using Tango.Core.DB; using Tango.Core.DI; using Tango.MachineStudio.Common.Web; using Tango.SQLExaminer; using Tango.Transport.Web; using Tango.Core.ExtensionMethods; using Tango.PPC.Common.Publish; using Tango.Settings; using Tango.Core.Components; using System.Text.RegularExpressions; namespace Tango.MachineStudio.Common.Tup { public class TupFileBuilder : ExtendedObject { public event EventHandler Progress; public Task Build(String serialNumber, String filePath) { return Task.Factory.StartNew(() => { String tempDbName = "Tango_TUP"; var tempPackageFolder = TemporaryManager.CreateFolder(); String tempBackupFolder = "C:\\MachineStudioTUP"; String tempBackupFile = Path.Combine(tempBackupFolder, tempDbName + ".bak"); var tempZipFile = TemporaryManager.CreateImaginaryFile(); DbManager dbManager = null; LogManager.Log("Generating tup file..."); LogManager.Log($"Tup file: '{filePath}.'"); LogManager.Log($"Temporary db name: '{tempDbName}'."); LogManager.Log($"Temporary package folder: '{tempPackageFolder}'."); LogManager.Log($"Temporary db backup folder: '{tempBackupFolder}'."); LogManager.Log($"Temporary db backup file: '{tempBackupFile}'."); LogManager.Log($"Temporary zip file: '{tempZipFile}'."); try { LogManager.Log("Initializing..."); OnProgress("Initializing..."); Core.DataSource localDataSource = new Core.DataSource() { Address = "localhost\\SQLEXPRESS", IntegratedSecurity = true, Type = DataSourceType.SQLServer, Catalog = null, }; try { LogManager.Log($"Trying to connect via SQLEXPRESS:\n{localDataSource.ToJsonString()}"); dbManager = DbManager.FromDataSource(localDataSource); } catch (Exception ex) { try { LogManager.Log(ex, "Could not connect using SQLEXPRESS. Trying local DB..."); CmdCommand command = new CmdCommand("sqllocaldb", "start \"MSSQLLocalDB\""); var result = command.Run().Result; command = new CmdCommand("sqllocaldb", "info \"MSSQLLocalDB\""); result = command.Run().Result; String pattern = "np:.+"; Regex reg = new Regex(pattern); var match = reg.Match(result.StandardOutput); String address = match.ToString(); if (address.Contains("np:")) { localDataSource.Address = address; address = address.Trim().Replace("\r", ""); } else { throw new ArgumentException("Could not parse LocalDB address string."); } LogManager.Log($"Trying to connect via LocalDB:\n{localDataSource.ToJsonString()}"); dbManager = DbManager.FromDataSource(localDataSource); } catch (Exception x) { LogManager.Log(x, "Could not find any database service for this operation."); throw x; } } OnProgress("Downloading latest PPC version..."); LogManager.Log("Connecting to machine service..."); MachineStudioWebClient client = TangoIOC.Default.GetInstance(); LogManager.Log("Requesting latest PPC version from machine service..."); var response = client.DownloadLatestPPCVersion(new DownloadLatestPPCVersionRequest() { SerialNumber = serialNumber }).Result; LogManager.Log($"Machine service response:\n{response.ToJsonString()}"); var remoteDataSource = response.DataSource; using (AutoFileDownloader downloader = new AutoFileDownloader(response.BlobAddress, response.CdnAddress, tempZipFile)) { downloader.Progress += (x, e) => { OnProgress($"Downloading latest PPC version '{response.Version}'...", false, e.Current, e.Total); }; downloader.ResolveMode().GetAwaiter().GetResult(); LogManager.Log($"Downloading latest PPC version from: '{downloader.Address}'"); downloader.Download().Wait(); } LogManager.Log("Extracting PPC version package..."); OnProgress("Extracting PPC package..."); using (ZipFile zip = new ZipFile(tempZipFile)) { int currentEntry = 0; zip.ExtractProgress += (x, args) => { if (args.EventType == ZipProgressEventType.Extracting_AfterExtractEntry) { OnProgress("Extracting PPC package...", false, currentEntry++, zip.Entries.Count); } }; zip.ExtractAll(tempPackageFolder); } OnProgress("Extracting version information..."); LogManager.Log("Extracting publish information..."); PublishInfo publishInfo = PublishInfo.FromJson(File.ReadAllText(Path.Combine(tempPackageFolder, "version.json"))); LogManager.Log($"Publish Information:\n{publishInfo}"); LogManager.Log("Modifying publish information to custom tup file..."); publishInfo.IsMachineTupPackage = true; publishInfo.MachineSerialNumber = serialNumber; publishInfo.MachineDeploymentSlot = SettingsManager.Default.GetOrCreate().DeploymentSlot; OnProgress("Creating temporary database..."); LogManager.Log($"Creating temporary db backup directory '{tempBackupFolder}'"); Directory.CreateDirectory(tempBackupFolder); LogManager.Log($"Creating new database: '{tempDbName}'"); //Create temp db dbManager.Create(tempDbName, Path.Combine(tempBackupFolder, tempDbName + ".mdf")); OnProgress("Generating database snapshot..."); LogManager.Log("Starting database synchronization..."); Thread.Sleep(2000); localDataSource.Catalog = tempDbName; ExaminerSequenceConfigurationRunner runner = new ExaminerSequenceConfigurationRunner( Path.Combine(tempPackageFolder, "Provision Scripts", "config.xml"), Path.Combine(tempPackageFolder, "Provision Scripts"), remoteDataSource, localDataSource, serialNumber); runner.ScriptExecuting += (x, item) => { LogManager.Log($"Executing script '{item.FileName}'..."); OnProgress($"{item.Name}..."); }; runner.Log += (x, log) => { LogManager.Log(log); }; runner.Run().GetAwaiter().GetResult(); OnProgress("Generating database snapshot..."); if (File.Exists(tempBackupFile)) { LogManager.Log($"Deleting file '{tempBackupFile}'"); File.Delete(tempBackupFile); } LogManager.Log($"Generating backup for '{tempDbName}' to '{tempBackupFile}'..."); dbManager.Backup(tempDbName, tempBackupFile); OnProgress("Injecting database snapshot to PPC package..."); using (ZipFile zip = new ZipFile(tempZipFile)) { LogManager.Log($"Injecting file '{tempBackupFile}' to original package at '{tempZipFile}'..."); zip.AddFile(tempBackupFile, "/"); LogManager.Log($"Injecting modified publish information..."); zip.UpdateEntry("version.json", publishInfo.ToJson()); zip.Save(); } LogManager.Log($"Copying '{tempZipFile}' to '{filePath}'..."); File.Copy(tempZipFile, filePath, true); OnProgress("Completed", false, 100, 100); LogManager.Log("TUP file generation completed successfully."); } catch (Exception ex) { LogManager.Log(ex, "TUP file generation failed."); OnProgress("Failed", false, 0, 100); throw ex; } finally { LogManager.Log($"Removing '{tempZipFile}'."); tempZipFile.Delete(); LogManager.Log($"Removing '{tempPackageFolder}'."); tempPackageFolder.Delete(); try { LogManager.Log($"Removing database '{tempDbName}'."); dbManager.SetOffline(tempDbName); dbManager.SetOnline(tempDbName); dbManager.Delete(tempDbName); dbManager.Dispose(); } catch (Exception ex) { LogManager.Log(ex, $"Error removing temp database '{tempDbName}'."); } try { LogManager.Log($"Removing '{tempBackupFolder}'."); Directory.Delete(tempBackupFolder, true); } catch (Exception ex) { LogManager.Log(ex, $"Error removing folder '{tempBackupFolder}'."); } } }); } public async Task GetLatestPPCVersion(String serialNumber) { MachineStudioWebClient client = TangoIOC.Default.GetInstance(); var response = await client.DownloadLatestPPCVersion(new DownloadLatestPPCVersionRequest() { SerialNumber = serialNumber }); return response.Version; } private void OnProgress(String message, bool isIntermediate = true, double progress = 0, double total = 100) { Progress?.Invoke(this, new TupFileBuilderProgressEventArgs() { Message = message, IsIntermediate = isIntermediate, Progress = progress, Total = total, }); } } }