oqtane.framework/Oqtane.Server/Infrastructure/InstallationManager.cs
2021-06-03 08:37:56 -04:00

315 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Xml;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Oqtane.Shared;
// ReSharper disable AssignNullToNotNullAttribute
namespace Oqtane.Infrastructure
{
public class InstallationManager : IInstallationManager
{
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IWebHostEnvironment _environment;
public InstallationManager(IHostApplicationLifetime hostApplicationLifetime, IWebHostEnvironment environment)
{
_hostApplicationLifetime = hostApplicationLifetime;
_environment = environment;
}
public void InstallPackages()
{
if (!InstallPackages(_environment.WebRootPath, _environment.ContentRootPath))
{
// error installing packages
}
}
public static bool InstallPackages(string webRootPath, string contentRootPath)
{
bool install = false;
string binPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
string sourceFolder = Path.Combine(contentRootPath, "Packages");
if (!Directory.Exists(sourceFolder))
{
Directory.CreateDirectory(sourceFolder);
}
// move packages to secure /Packages folder
foreach (var folder in "Modules,Themes,Packages".Split(","))
{
foreach(var file in Directory.GetFiles(Path.Combine(webRootPath, folder), "*.nupkg*"))
{
var destinationFile = Path.Combine(sourceFolder, Path.GetFileName(file));
if (File.Exists(destinationFile))
{
File.Delete(destinationFile);
}
if (destinationFile.ToLower().EndsWith(".nupkg.bak"))
{
// leave a copy in the current folder as it is distributed with the core framework
File.Copy(file, destinationFile);
}
else
{
// move to destination
File.Move(file, destinationFile);
}
}
}
// iterate through Nuget packages in source folder
foreach (string packagename in Directory.GetFiles(sourceFolder, "*.nupkg"))
{
// iterate through files
using (ZipArchive archive = ZipFile.OpenRead(packagename))
{
string frameworkversion = "";
// locate nuspec
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.ToLower().EndsWith(".nuspec"))
{
// open nuspec
XmlTextReader reader = new XmlTextReader(entry.Open());
reader.Namespaces = false; // remove namespace
XmlDocument doc = new XmlDocument();
doc.Load(reader);
// get framework dependency
XmlNode node = doc.SelectSingleNode("/package/metadata/dependencies/dependency[@id='Oqtane.Framework']");
if (node != null)
{
frameworkversion = node.Attributes["version"].Value;
}
reader.Close();
break;
}
}
// if compatible with framework version
if (frameworkversion == "" || Version.Parse(Constants.Version).CompareTo(Version.Parse(frameworkversion)) >= 0)
{
List<string> assets = new List<string>();
bool manifest = false;
// packages are in form of name.1.0.0.nupkg or name.culture.1.0.0.nupkg
string name = Path.GetFileNameWithoutExtension(packagename);
string[] segments = name?.Split('.');
// remove version information from name
if (segments != null) name = string.Join('.', segments, 0, segments.Length - 3);
// deploy to appropriate locations
foreach (ZipArchiveEntry entry in archive.Entries)
{
string filename = "";
// evaluate entry root folder
switch (entry.FullName.Split('/')[0])
{
case "lib": // lib/net5.0/...
filename = ExtractFile(entry, binPath, 2);
break;
case "wwwroot": // wwwroot/...
filename = ExtractFile(entry, webRootPath, 1);
break;
case "runtimes": // runtimes/name/...
filename = ExtractFile(entry, binPath, 0);
break;
case "ref": // ref/net5.0/...
filename = ExtractFile(entry, Path.Combine(binPath, "ref"), 2);
break;
}
if (filename != "")
{
// ContentRootPath does not use different case for folder names as other framework methods
filename = Regex.Replace(filename, Regex.Escape(contentRootPath), "", RegexOptions.IgnoreCase);
assets.Add(filename);
if (!manifest && Path.GetFileName(filename) == name + ".log")
{
manifest = true;
}
}
}
// save dynamic list of assets
if (!manifest && assets.Count != 0)
{
string manifestpath = Path.Combine(sourceFolder, name + ".log");
if (File.Exists(manifestpath))
{
File.Delete(manifestpath);
}
if (!Directory.Exists(Path.GetDirectoryName(manifestpath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(manifestpath));
}
File.WriteAllText(manifestpath, JsonSerializer.Serialize(assets, new JsonSerializerOptions { WriteIndented = true }));
}
}
}
// remove package
File.Delete(packagename);
install = true;
}
return install;
}
private static string ExtractFile(ZipArchiveEntry entry, string folder, int ignoreLeadingSegments)
{
string[] segments = entry.FullName.Split('/'); // ZipArchiveEntries always use unix path separator
string filename = Path.Combine(folder, string.Join(Path.DirectorySeparatorChar, segments, ignoreLeadingSegments, segments.Length - ignoreLeadingSegments));
try
{
if (!Directory.Exists(Path.GetDirectoryName(filename)))
{
Directory.CreateDirectory(Path.GetDirectoryName(filename));
}
entry.ExtractToFile(filename, true);
}
catch
{
// an error occurred extracting the file
filename = "";
}
return filename;
}
public bool UninstallPackage(string PackageName)
{
if (File.Exists(Path.Combine(_environment.WebRootPath, "Packages", PackageName + ".log")))
{
// use manifest to clean up file resources
List<string> assets = JsonSerializer.Deserialize<List<string>>(File.ReadAllText(Path.Combine(_environment.WebRootPath, "Packages", PackageName + ".log")));
assets.Reverse();
foreach (string asset in assets)
{
// legacy support for assets that were stored as absolute paths
string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset;
if (File.Exists(filepath))
{
File.Delete(filepath);
if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any())
{
Directory.Delete(Path.GetDirectoryName(filepath));
}
}
}
return true;
}
return false;
}
public void UpgradeFramework()
{
string folder = Path.Combine(_environment.ContentRootPath, "Packages");
if (Directory.Exists(folder))
{
// get package with highest version
string packagename = "";
string[] packages = Directory.GetFiles(folder, Constants.PackageId + ".*.nupkg");
if (packages.Length > 0)
{
packagename = packages[packages.Length - 1]; // use highest version
}
if (packagename != "")
{
// verify package version
string packageversion = "";
string packageurl = "";
using (ZipArchive archive = ZipFile.OpenRead(packagename))
{
// locate nuspec
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.ToLower().EndsWith(".nuspec"))
{
// open nuspec
XmlTextReader reader = new XmlTextReader(entry.Open());
reader.Namespaces = false; // remove namespace
XmlDocument doc = new XmlDocument();
doc.Load(reader);
// get framework version
XmlNode node = doc.SelectSingleNode("/package/metadata/version");
if (node != null)
{
packageversion = node.InnerText;
}
node = doc.SelectSingleNode("/package/metadata/projectUrl");
if (node != null)
{
packageurl = node.InnerText;
}
reader.Close();
break;
}
}
}
// ensure package version is greater than or equal to current framework version
if (packageversion != "" && Version.Parse(Constants.Version).CompareTo(Version.Parse(packageversion)) <= 0 && packageurl != "")
{
// install Oqtane.Framework and Oqtane.Updater nuget packages
InstallPackages();
// download upgrade zip package
var client = new WebClient();
Uri uri = new Uri(packageurl);
string upgradepackage = Path.Combine(folder, uri.Segments[uri.Segments.Length - 1]);
client.DownloadFile(packageurl, upgradepackage);
// install Oqtane.Upgrade zip package
if (File.Exists(upgradepackage))
{
FinishUpgrade();
}
}
}
}
}
private void FinishUpgrade()
{
// check if updater application exists
string Updater = Constants.UpdaterPackageId + ".dll";
string folder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (folder == null || !File.Exists(Path.Combine(folder, Updater))) return;
// run updater application
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
WorkingDirectory = folder,
FileName = "dotnet",
Arguments = Path.Combine(folder, Updater) + " \"" + _environment.ContentRootPath + "\" \"" + _environment.WebRootPath + "\"",
UseShellExecute = false,
ErrorDialog = false,
CreateNoWindow = true,
RedirectStandardOutput = false,
RedirectStandardError = false
};
process.Start();
};
}
public void RestartApplication()
{
_hostApplicationLifetime.StopApplication();
}
}
}