diff --git a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor
index b2180d43..2ae44820 100644
--- a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor
@@ -94,8 +94,9 @@ else
}
}
- private void Upgrade()
+ private async Task Upgrade()
{
+ await InstallationService.Upgrade();
NavigationManager.NavigateTo(NavigateUrl(Reload.Application));
}
diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs
index 84c9c2d7..46966d70 100644
--- a/Oqtane.Server/Controllers/InstallationController.cs
+++ b/Oqtane.Server/Controllers/InstallationController.cs
@@ -279,7 +279,7 @@ namespace Oqtane.Controllers
public GenericResponse Upgrade()
{
var response = new GenericResponse { Success = true, Message = "" };
- InstallationManager.InstallPackages("Framework", true);
+ InstallationManager.UpgradeFramework();
return response;
}
}
diff --git a/Oqtane.Server/Infrastructure/IInstallationManager.cs b/Oqtane.Server/Infrastructure/IInstallationManager.cs
index 8b4fdf33..52afc3f8 100644
--- a/Oqtane.Server/Infrastructure/IInstallationManager.cs
+++ b/Oqtane.Server/Infrastructure/IInstallationManager.cs
@@ -3,6 +3,7 @@
public interface IInstallationManager
{
void InstallPackages(string Folders, bool Restart);
+ void UpgradeFramework();
void RestartApplication();
}
}
diff --git a/Oqtane.Server/Infrastructure/InstallationManager.cs b/Oqtane.Server/Infrastructure/InstallationManager.cs
index 7d6bd394..d6b99661 100644
--- a/Oqtane.Server/Infrastructure/InstallationManager.cs
+++ b/Oqtane.Server/Infrastructure/InstallationManager.cs
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting;
using System.Xml;
using Oqtane.Shared;
using System;
+using System.Diagnostics;
namespace Oqtane.Infrastructure
{
@@ -110,6 +111,98 @@ namespace Oqtane.Infrastructure
}
}
+ public void UpgradeFramework()
+ {
+ string folder = Path.Combine(environment.WebRootPath, "Framework");
+ if (Directory.Exists(folder))
+ {
+ string upgradepackage = "";
+
+ // iterate through packages
+ foreach (string packagename in Directory.GetFiles(folder, "Oqtane.Framework.*.nupkg"))
+ {
+ 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 version
+ XmlNode node = doc.SelectSingleNode("/package/metadata/version");
+ if (node != null)
+ {
+ frameworkversion = node.InnerText;
+ }
+ reader.Close();
+ }
+ }
+
+ // ensure package version is higher than current framework
+ if (frameworkversion != "" && Version.Parse(Constants.Version).CompareTo(Version.Parse(frameworkversion)) < 0)
+ {
+ upgradepackage = packagename;
+ }
+ else
+ {
+ File.Delete(packagename);
+ }
+ }
+ }
+
+ if (upgradepackage != "")
+ {
+ FinishUpgrade(upgradepackage);
+ }
+ }
+ }
+
+ private void FinishUpgrade(string packagename)
+ {
+ string folder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
+
+ // check if upgrade application exists
+ if (File.Exists(Path.Combine(folder, "Oqtane.Upgrade.exe")))
+ {
+ // unzip package
+ using (ZipArchive archive = ZipFile.OpenRead(packagename))
+ {
+ foreach (ZipArchiveEntry entry in archive.Entries)
+ {
+ string filename = Path.GetFileName(entry.FullName);
+ if (Path.GetExtension(filename) == ".dll")
+ {
+ entry.ExtractToFile(Path.Combine(Path.Combine(environment.WebRootPath, "Framework"), filename), true);
+ }
+ }
+ }
+
+ // delete package
+ File.Delete(packagename);
+
+ // run upgrade application
+ var process = new Process();
+ process.StartInfo.FileName = Path.Combine(folder, "Oqtane.Upgrade.exe");
+ process.StartInfo.Arguments = "";
+ process.StartInfo.ErrorDialog = false;
+ process.StartInfo.UseShellExecute = false;
+ process.StartInfo.CreateNoWindow = true;
+ process.StartInfo.RedirectStandardOutput = false;
+ process.StartInfo.RedirectStandardError = false;
+ process.Start();
+ process.Dispose();
+
+ // stop application so upgrade application can proceed
+ RestartApplication();
+ }
+ }
+
public void RestartApplication()
{
HostApplicationLifetime.StopApplication();
diff --git a/Oqtane.Server/app_offline.bak b/Oqtane.Server/app_offline.bak
new file mode 100644
index 00000000..94be8d84
--- /dev/null
+++ b/Oqtane.Server/app_offline.bak
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Oqtane
+
+
+
+
+
+
Please Wait... Upgrade In Progress....
+

+
+
+
+
diff --git a/Oqtane.Upgrade/Oqtane.Upgrade.csproj b/Oqtane.Upgrade/Oqtane.Upgrade.csproj
new file mode 100644
index 00000000..0ddddf67
--- /dev/null
+++ b/Oqtane.Upgrade/Oqtane.Upgrade.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ netcoreapp3.0
+
+
+
+ ..\Oqtane.Server\bin\Release\
+
+
+ ..\Oqtane.Server\bin\Debug\
+
+
+
diff --git a/Oqtane.Upgrade/Program.cs b/Oqtane.Upgrade/Program.cs
new file mode 100644
index 00000000..f9ec133c
--- /dev/null
+++ b/Oqtane.Upgrade/Program.cs
@@ -0,0 +1,129 @@
+using System.IO;
+using System.Reflection;
+using System.Threading;
+
+namespace Oqtane.Upgrade
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
+ // assumes that the application executable must be deployed to the /bin of the Oqtane.Server project
+ if (binfolder.Contains("Oqtane.Server\\bin"))
+ {
+ // ie. Oqtane.Server\bin\Debug\netcoreapp3.0\Oqtane.Upgrade.exe
+ string rootfolder = Directory.GetParent(binfolder).Parent.Parent.FullName;
+ string deployfolder = Path.Combine(rootfolder, "wwwroot\\Framework");
+
+ // take the app offline
+ if (File.Exists(Path.Combine(rootfolder, "app_offline.bak")))
+ {
+ File.Move(Path.Combine(rootfolder, "app_offline.bak"), Path.Combine(rootfolder, "app_offline.htm"));
+ }
+
+ if (Directory.Exists(deployfolder))
+ {
+ string filename;
+ string[] files = Directory.GetFiles(deployfolder);
+ if (CanAccessFiles(files, binfolder))
+ {
+ // backup the files
+ foreach (string file in files)
+ {
+ filename = Path.Combine(binfolder, Path.GetFileName(file));
+ if (File.Exists(filename))
+ {
+ File.Move(filename, filename.Replace(".dll", ".bak"));
+ }
+ }
+
+ // copy the new files
+ bool success = true;
+ try
+ {
+ foreach (string file in files)
+ {
+ filename = Path.Combine(binfolder, Path.GetFileName(file));
+ // delete the file from the /bin if it exists
+ if (File.Exists(filename))
+ {
+ File.Delete(filename);
+ }
+ // copy the new file to the /bin
+ File.Move(Path.Combine(deployfolder, Path.GetFileName(file)), filename);
+ }
+ }
+ catch
+ {
+ // an error occurred deleting or moving a file
+ success = false;
+ }
+
+ // restore on failure
+ if (!success)
+ {
+ foreach (string file in files)
+ {
+ filename = Path.Combine(binfolder, Path.GetFileName(file));
+ if (File.Exists(filename))
+ {
+ File.Move(filename, filename.Replace(".bak", ".dll"));
+ }
+ }
+ }
+ }
+ }
+
+ // bring the app back online
+ if (File.Exists(Path.Combine(rootfolder, "app_offline.htm")))
+ {
+ File.Move(Path.Combine(rootfolder, "app_offline.htm"), Path.Combine(rootfolder, "app_offline.bak"));
+ }
+ }
+
+ return;
+ }
+
+ private static bool CanAccessFiles(string[] files, string folder)
+ {
+ // ensure files are not locked by another process - the shutdownTimeLimit defines the duration for app shutdown
+ bool canaccess = true;
+ FileStream stream = null;
+ int i = 0;
+ while (i < (files.Length - 1) && canaccess)
+ {
+ string filepath = Path.Combine(folder, Path.GetFileName(files[i]));
+ int attempts = 0;
+ bool locked = true;
+ // try up to 30 times
+ while (attempts < 30 && locked == true)
+ {
+ try
+ {
+ stream = System.IO.File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.None);
+ locked = false;
+ }
+ catch // file is locked by another process
+ {
+ Thread.Sleep(1000); // wait 1 second
+ }
+ finally
+ {
+ if (stream != null)
+ {
+ stream.Close();
+ }
+ }
+ attempts += 1;
+ }
+ if (locked && canaccess)
+ {
+ canaccess = false;
+ }
+ i += 1;
+ }
+ return canaccess;
+ }
+ }
+}
diff --git a/Oqtane.sln b/Oqtane.sln
index dd568f30..e3a35ec0 100644
--- a/Oqtane.sln
+++ b/Oqtane.sln
@@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Client", "Oqtane.Cli
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Shared", "Oqtane.Shared\Oqtane.Shared.csproj", "{19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Upgrade", "Oqtane.Upgrade\Oqtane.Upgrade.csproj", "{2E8C6889-37CF-4C8D-88B1-505547F25098}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -34,6 +36,12 @@ Global
{19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}.Release|Any CPU.Build.0 = Release|Any CPU
{19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}.Wasm|Any CPU.ActiveCfg = Wasm|Any CPU
{19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}.Wasm|Any CPU.Build.0 = Wasm|Any CPU
+ {2E8C6889-37CF-4C8D-88B1-505547F25098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E8C6889-37CF-4C8D-88B1-505547F25098}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E8C6889-37CF-4C8D-88B1-505547F25098}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E8C6889-37CF-4C8D-88B1-505547F25098}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2E8C6889-37CF-4C8D-88B1-505547F25098}.Wasm|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E8C6889-37CF-4C8D-88B1-505547F25098}.Wasm|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE