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 @@ {
- + @userName @@ -41,7 +41,7 @@