diff --git a/SmartStoreNET.Tasks.Targets b/SmartStoreNET.Tasks.Targets
index c9547c7eca..317cce21ad 100644
--- a/SmartStoreNET.Tasks.Targets
+++ b/SmartStoreNET.Tasks.Targets
@@ -148,8 +148,8 @@
-
-
+
diff --git a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs
new file mode 100644
index 0000000000..296ee8e931
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using SmartStore.Core.Domain.Logging;
+using SmartStore.Core.Domain.Customers;
+using System.Diagnostics;
+
+namespace SmartStore.Core.Logging
+{
+ public class TraceLogger : DisposableObject, ILogger
+ {
+ private readonly TraceSource _traceSource;
+
+ public TraceLogger() : this("SmartStore.log")
+ {
+ }
+
+ public TraceLogger(string fileName)
+ {
+ Guard.ArgumentNotEmpty(() => fileName);
+
+ _traceSource = new TraceSource("SmartStore");
+ _traceSource.Switch = new SourceSwitch("LogSwitch", "Error");
+ _traceSource.Listeners.Remove("Default");
+
+ var console = new ConsoleTraceListener(false);
+ console.Filter = new EventTypeFilter(SourceLevels.All);
+ console.Name = "console";
+
+ var textListener = new TextWriterTraceListener(fileName);
+ textListener.Filter = new EventTypeFilter(SourceLevels.All);
+ textListener.TraceOutputOptions = TraceOptions.DateTime;
+
+ _traceSource.Listeners.Add(console);
+ _traceSource.Listeners.Add(textListener);
+
+ // Allow the trace source to send messages to
+ // listeners for all event types. Currently only
+ // error messages or higher go to the listeners.
+ // Messages must get past the source switch to
+ // get to the listeners, regardless of the settings
+ // for the listeners.
+ _traceSource.Switch.Level = SourceLevels.All;
+ }
+
+ public bool IsEnabled(LogLevel level)
+ {
+ return true;
+ }
+
+ public void DeleteLog(Log log)
+ {
+ // not supported
+ }
+
+ public void ClearLog()
+ {
+ // not supported
+ }
+
+ public void ClearLog(DateTime toUtc, LogLevel logLevel)
+ {
+ // not supported
+ }
+
+ public IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, string message, LogLevel? logLevel, int pageIndex, int pageSize, int minFrequency)
+ {
+ // not supported
+ return null;
+ }
+
+ public Log GetLogById(int logId)
+ {
+ // not supported
+ return null;
+ }
+
+ public IList GetLogByIds(int[] logIds)
+ {
+ // not supported
+ return null;
+ }
+
+ public Log InsertLog(LogContext context)
+ {
+ var type = LogLevelToEventType(context.LogLevel);
+ _traceSource.TraceEvent(type, (int)type, "{0}: {1}".FormatCurrent(type.ToString().ToUpper(), context.ShortMessage));
+ return null;
+ }
+
+ public Log InsertLog(LogLevel logLevel, string shortMessage, string fullMessage = "", Customer customer = null)
+ {
+ var context = new LogContext()
+ {
+ LogLevel = logLevel,
+ ShortMessage = shortMessage,
+ FullMessage = fullMessage,
+ Customer = customer
+ };
+
+ return InsertLog(context);
+ }
+
+ private TraceEventType LogLevelToEventType(LogLevel level)
+ {
+ switch (level)
+ {
+ case LogLevel.Debug:
+ return TraceEventType.Verbose;
+ case LogLevel.Error:
+ return TraceEventType.Error;
+ case LogLevel.Fatal:
+ return TraceEventType.Critical;
+ case LogLevel.Warning:
+ return TraceEventType.Warning;
+ default:
+ return TraceEventType.Information;
+ }
+ }
+
+ protected override void OnDispose(bool disposing)
+ {
+ _traceSource.Flush();
+ _traceSource.Close();
+ }
+ }
+}
diff --git a/src/Libraries/SmartStore.Core/Packaging/FolderUpdater.cs b/src/Libraries/SmartStore.Core/Packaging/FolderUpdater.cs
index f37d06f7e6..314eb5d61c 100644
--- a/src/Libraries/SmartStore.Core/Packaging/FolderUpdater.cs
+++ b/src/Libraries/SmartStore.Core/Packaging/FolderUpdater.cs
@@ -3,13 +3,14 @@
using System.Linq;
using System.IO;
using SmartStore.Core.Logging;
+using SmartStore.Utilities;
namespace SmartStore.Core.Packaging
{
public interface IFolderUpdater
{
- void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder);
+ void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder, params string[] ignorePatterns);
void Restore(DirectoryInfo backupfolder, DirectoryInfo existingFolder);
}
@@ -28,14 +29,15 @@ public FolderUpdater(ILogger logger)
_logger = logger;
}
- public void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder)
+ public void Backup(DirectoryInfo existingFolder, DirectoryInfo backupfolder, params string[] ignorePatterns)
{
- CopyFolder(GetFolderContent(existingFolder), backupfolder);
+ var ignores = ignorePatterns.Select(x => new Wildcard(x));
+ CopyFolder(GetFolderContent(existingFolder, ignores), backupfolder);
}
public void Restore(DirectoryInfo backupfolder, DirectoryInfo existingFolder)
{
- CopyFolder(GetFolderContent(backupfolder), existingFolder);
+ CopyFolder(GetFolderContent(backupfolder, Enumerable.Empty()), existingFolder);
}
private void CopyFolder(FolderContent source, DirectoryInfo dest)
@@ -74,26 +76,34 @@ private void CopyFile(DirectoryInfo sourceFolder, string fileName, DirectoryInfo
File.Copy(sourceFile.FullName, destFile.FullName, true);
}
- private FolderContent GetFolderContent(DirectoryInfo folder)
+ private FolderContent GetFolderContent(DirectoryInfo folder, IEnumerable ignores)
{
var files = new List();
- GetFolderContent(folder, "", files);
+ GetFolderContent(folder, "", files, ignores);
return new FolderContent { Folder = folder, Files = files };
}
- private void GetFolderContent(DirectoryInfo folder, string prefix, List files)
+ private void GetFolderContent(DirectoryInfo folder, string prefix, List files, IEnumerable ignores)
{
if (!folder.Exists)
return;
+ if (ignores.Any(w => w.IsMatch(prefix)))
+ return;
+
foreach (var file in folder.GetFiles())
{
- files.Add(Path.Combine(prefix, file.Name));
+ var path = Path.Combine(prefix, file.Name);
+ var ignore = ignores.Any(w => w.IsMatch(path));
+ if (!ignore)
+ {
+ files.Add(path);
+ }
}
foreach (var child in folder.GetDirectories())
{
- GetFolderContent(child, Path.Combine(prefix, child.Name), files);
+ GetFolderContent(child, Path.Combine(prefix, child.Name), files, ignores);
}
}
diff --git a/src/Libraries/SmartStore.Core/Packaging/NuGet/NugetLogger.cs b/src/Libraries/SmartStore.Core/Packaging/NuGet/NugetLogger.cs
index 4936076516..325ad372c6 100644
--- a/src/Libraries/SmartStore.Core/Packaging/NuGet/NugetLogger.cs
+++ b/src/Libraries/SmartStore.Core/Packaging/NuGet/NugetLogger.cs
@@ -19,6 +19,10 @@ public void Log(MessageLevel level, string message, params object[] args)
switch (level)
{
case MessageLevel.Debug:
+ //_logger.Debug(String.Format(message, args));
+ break;
+ case MessageLevel.Error:
+ _logger.Error(String.Format(message, args));
break;
case MessageLevel.Info:
_logger.Information(String.Format(message, args));
@@ -31,7 +35,7 @@ public void Log(MessageLevel level, string message, params object[] args)
public FileConflictResolution ResolveFileConflict(string message)
{
- return FileConflictResolution.Ignore;
+ return FileConflictResolution.OverwriteAll;
}
}
}
diff --git a/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs b/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs
index 0d76c7c43b..b97e4e1423 100644
--- a/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs
+++ b/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs
@@ -78,6 +78,14 @@ public PackageInfo Install(Stream packageStream, string location, string applica
return InstallPackage(package, packageRepository, location, applicationPath);
}
+ ///
+ /// Tries to install the package
+ ///
+ /// The package to install
+ /// The repository
+ /// The virtual location of the package file, usually ~/App_Data
+ /// The virtual app root path, usually ~/
+ /// An instance of type
protected PackageInfo InstallPackage(IPackage package, IPackageRepository packageRepository, string location, string applicationPath)
{
@@ -145,7 +153,6 @@ protected PackageInfo InstallPackage(IPackage package, IPackageRepository packag
/// The package information.
protected PackageInfo ExecuteInstall(IPackage package, IPackageRepository packageRepository, string sourceLocation, string targetPath)
{
- // this logger is used to render NuGet's log on the notifier
var logger = new NugetLogger(_logger);
var project = new FileBasedProjectSystem(targetPath) { Logger = logger };
diff --git a/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs
new file mode 100644
index 0000000000..dc09f0428c
--- /dev/null
+++ b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.IO;
+using SmartStore.Utilities;
+using SmartStore.Utilities.Threading;
+using SmartStore.Core.Logging;
+using Log = SmartStore.Core.Logging;
+using NuGet;
+using NuGetPackageManager = NuGet.PackageManager;
+using SmartStore.Core.Data;
+
+namespace SmartStore.Core.Packaging
+{
+
+ internal sealed class AppUpdater : DisposableObject
+ {
+ private const string UpdatePackagePath = "~/App_Data/Update";
+
+ private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
+ private TraceLogger _logger;
+
+ public bool TryUpdate()
+ {
+ // NEVER EVER (!!!) make an attempt to auto-update in a dev environment!!!!!!!
+ if (CommonHelper.IsDevEnvironment)
+ return false;
+
+ using (_rwLock.GetUpgradeableReadLock())
+ {
+ try
+ {
+ string packagePath = null;
+ var package = FindPackage(out packagePath);
+
+ if (package == null)
+ return false;
+
+ if (!ValidatePackage(package))
+ return false;
+
+ if (!CheckEnvironment())
+ return false;
+
+ using (_rwLock.GetWriteLock())
+ {
+ Backup();
+
+ var info = ExecuteUpdate(package);
+
+ if (info != null)
+ {
+ var newPath = packagePath + ".applied";
+ if (File.Exists(newPath))
+ {
+ File.Delete(packagePath);
+ }
+ else
+ {
+ File.Move(packagePath, newPath);
+ }
+ }
+
+ return info != null;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.Error("An error occured while updating the application: {0}".FormatCurrent(ex.Message), ex);
+ return false;
+ }
+ }
+ }
+
+ private TraceLogger CreateLogger(IPackage package)
+ {
+ var logFile = Path.Combine(CommonHelper.MapPath(UpdatePackagePath, false), "Updater.{0}.log".FormatInvariant(package.Version.ToString()));
+ return new TraceLogger(logFile);
+ }
+
+ private IPackage FindPackage(out string path)
+ {
+ path = null;
+ var dir = CommonHelper.MapPath(UpdatePackagePath, false);
+ var files = Directory.GetFiles(dir, "SmartStore.*.nupkg", SearchOption.TopDirectoryOnly);
+
+ // TODO: allow more than one package in folder and return newest
+ if (files == null || files.Length == 0 || files.Length > 1)
+ return null;
+
+ IPackage package = null;
+
+ try
+ {
+ path = files[0];
+ package = new ZipPackage(files[0]);
+ _logger = CreateLogger(package);
+ _logger.Information("Found update package '{0}'".FormatInvariant(package.GetFullName()));
+ return package;
+ }
+ catch { }
+
+ return null;
+ }
+
+ private bool ValidatePackage(IPackage package)
+ {
+ if (package.Id != "SmartStore")
+ return false;
+
+ var currentVersion = new SemanticVersion(SmartStoreVersion.FullVersion);
+ return package.Version > currentVersion;
+ }
+
+ private bool CheckEnvironment()
+ {
+ // TODO: Check it :-)
+ return true;
+ }
+
+ private void Backup()
+ {
+ var source = new DirectoryInfo(CommonHelper.MapPath("~/"));
+
+ var tempPath = CommonHelper.MapPath("~/App_Data/_Backup/App/SmartStore");
+ string localTempPath = null;
+ for (int i = 0; i < 50; i++)
+ {
+ localTempPath = tempPath + (i == 0 ? "" : "." + i.ToString());
+ if (!Directory.Exists(localTempPath))
+ {
+ Directory.CreateDirectory(localTempPath);
+ break;
+ }
+ localTempPath = null;
+ }
+
+ if (localTempPath == null)
+ {
+ var exception = new SmartException("Too many backups in '{0}'.".FormatInvariant(tempPath));
+ _logger.Error(exception.Message, exception);
+ throw exception;
+ }
+
+ var backupFolder = new DirectoryInfo(localTempPath);
+ var folderUpdater = new FolderUpdater(_logger);
+ folderUpdater.Backup(source, backupFolder, "App_Data", "Media");
+
+ _logger.Information("Backup successfully created in folder '{0}'.".FormatInvariant(localTempPath));
+ }
+
+ private PackageInfo ExecuteUpdate(IPackage package)
+ {
+ var appPath = CommonHelper.MapPath("~/");
+
+ var logger = new NugetLogger(_logger);
+
+ var project = new FileBasedProjectSystem(appPath) { Logger = logger };
+
+ var nullRepository = new NullSourceRepository();
+
+ var projectManager = new ProjectManager(
+ nullRepository,
+ new DefaultPackagePathResolver(appPath),
+ project,
+ nullRepository
+ ) { Logger = logger };
+
+ // Perform the update
+ projectManager.AddPackageReference(package, true, false);
+
+ var info = new PackageInfo
+ {
+ Id = package.Id,
+ Name = package.Title ?? package.Id,
+ Version = package.Version.ToString(),
+ Type = "App",
+ Path = appPath
+ };
+
+ _logger.Information("Update '{0}' successfully executed.".FormatInvariant(info.Name));
+
+ return info;
+ }
+
+ protected override void OnDispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_logger != null)
+ {
+ _logger.Dispose();
+ _logger = null;
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs
index e31e9aa5ba..2536a4a931 100644
--- a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs
+++ b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs
@@ -16,6 +16,7 @@
using SmartStore.Core.ComponentModel;
using SmartStore.Core.Infrastructure.DependencyManagement;
using SmartStore.Core.Plugins;
+using SmartStore.Core.Packaging;
using SmartStore.Utilities;
using SmartStore.Utilities.Threading;
@@ -84,6 +85,13 @@ public static IEnumerable IncompatiblePlugins
///
public static void Initialize()
{
+ var updater = new AppUpdater();
+ if (updater.TryUpdate())
+ {
+ // [...]
+ }
+ updater.Dispose();
+
// adding a process-specific environment path (either bin/x86 or bin/amd64)
// ensures that unmanaged native dependencies can be resolved successfully.
SetPrivateEnvPath();
@@ -97,7 +105,6 @@ public static void Initialize()
var pluginFolderPath = CommonHelper.MapPath(_pluginsPath);
_shadowCopyFolder = new DirectoryInfo(CommonHelper.MapPath(_shadowCopyPath));
- //var referencedPlugins = new List();
var incompatiblePlugins = new List();
_clearShadowDirectoryOnStartup = !String.IsNullOrEmpty(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]) &&
diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj
index 9824345db5..8d4f3589ee 100644
--- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj
+++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj
@@ -240,6 +240,7 @@
+
@@ -551,6 +552,7 @@
+
diff --git a/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs b/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs
index 96756bdf2c..a41650e535 100644
--- a/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs
+++ b/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs
@@ -66,21 +66,14 @@ public static string MapPath(string path, bool findAppRoot = true)
// not hosted. For example, running in unit tests or EF tooling
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
path = path.Replace("~/", "").TrimStart('/').Replace('/', '\\');
-
+
var testPath = Path.Combine(baseDirectory, path);
if (findAppRoot /* && !Directory.Exists(testPath)*/)
{
// most likely we're in unit tests or design-mode (EF migration scaffolding)...
// find solution root directory first
- var dir = Directory.GetParent(baseDirectory);
- while (true)
- {
- if (dir == null || IsSolutionRoot(dir))
- break;
-
- dir = dir.Parent;
- }
+ var dir = FindSolutionRoot(baseDirectory);
// concat the web root
if (dir != null)
@@ -94,6 +87,42 @@ public static string MapPath(string path, bool findAppRoot = true)
}
}
+ public static bool IsDevEnvironment
+ {
+ get
+ {
+ if (!HostingEnvironment.IsHosted)
+ return true;
+
+ if (HostingEnvironment.IsDevelopmentEnvironment)
+ return true;
+
+ if (System.Diagnostics.Debugger.IsAttached)
+ return true;
+
+ // if there's a 'SmartStore.NET.sln' in one of the parent folders,
+ // then we're likely in a dev environment
+ if (FindSolutionRoot(HostingEnvironment.MapPath("~/")) != null)
+ return true;
+
+ return false;
+ }
+ }
+
+ private static DirectoryInfo FindSolutionRoot(string currentDir)
+ {
+ var dir = Directory.GetParent(currentDir);
+ while (true)
+ {
+ if (dir == null || IsSolutionRoot(dir))
+ break;
+
+ dir = dir.Parent;
+ }
+
+ return dir;
+ }
+
private static bool IsSolutionRoot(DirectoryInfo dir)
{
return File.Exists(Path.Combine(dir.FullName, "SmartStoreNET.sln"));
diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml
index fbe9453c79..30169a1173 100644
--- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml
+++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml
@@ -32,7 +32,7 @@
{