Standardize coding styles

pull/418/head
NextTurn 2020-07-08 00:00:00 +08:00 committed by Next Turn
parent 885beb89a2
commit 35af3bf78d
63 changed files with 894 additions and 712 deletions

View File

@ -1,8 +1,13 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<ArtifactsDir>$(MSBuildThisFileDirectory)artifacts\</ArtifactsDir> <ArtifactsDir>$(MSBuildThisFileDirectory)artifacts\</ArtifactsDir>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)src\stylecop.json" />
</ItemGroup>
</Project> </Project>

70
src/.editorconfig Normal file
View File

@ -0,0 +1,70 @@
[*.cs]
# SA0001: XML comment analysis is disabled due to project configuration
dotnet_diagnostic.SA0001.severity = none
# SA1114: Parameter list should follow declaration
dotnet_diagnostic.SA1114.severity = none
# SA1124: Do not use regions
dotnet_diagnostic.SA1124.severity = none
# SA1201: Elements should appear in the correct order
dotnet_diagnostic.SA1201.severity = none
# SA1202: Elements should be ordered by access
dotnet_diagnostic.SA1202.severity = none
# SA1401: Fields should be private
dotnet_diagnostic.SA1401.severity = none
# SA1402: File may only contain a single type
dotnet_diagnostic.SA1402.severity = none
# SA1405: Debug.Assert should provide message text
dotnet_diagnostic.SA1405.severity = none
# SA1413: Use trailing comma in multi-line initializers
dotnet_diagnostic.SA1413.severity = none
# SA1600: Elements should be documented
dotnet_diagnostic.SA1600.severity = none
# SA1601: Partial elements should be documented
dotnet_diagnostic.SA1601.severity = none
# SA1602: Enumeration items should be documented
dotnet_diagnostic.SA1602.severity = none
# SA1604: Element documentation should have summary
dotnet_diagnostic.SA1604.severity = none
# SA1606: Element documentation should have summary text
dotnet_diagnostic.SA1606.severity = none
# SA1611: Element parameters should be documented
dotnet_diagnostic.SA1611.severity = none
# SA1615: Element return value should be documented
dotnet_diagnostic.SA1615.severity = none
# SA1618: Generic type parameters should be documented
dotnet_diagnostic.SA1618.severity = none
# SA1623: Property summary documentation should match accessors
dotnet_diagnostic.SA1623.severity = none
# SA1627: Documentation text should not be empty
dotnet_diagnostic.SA1627.severity = none
# SA1629: Documentation text should end with a period
dotnet_diagnostic.SA1629.severity = none
# SA1633: File should have header
dotnet_diagnostic.SA1633.severity = none
# SA1642: Constructor summary documentation should begin with standard text
dotnet_diagnostic.SA1642.severity = none
# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none

View File

@ -1,20 +1,20 @@
using System.Diagnostics; using System.Diagnostics;
namespace winsw.Logging namespace WinSW.Logging
{ {
/// <summary> /// <summary>
/// Implements caching of the WindowsService reference in WinSW. /// Implements caching of the WindowsService reference in WinSW.
/// </summary> /// </summary>
public class WrapperServiceEventLogProvider : IServiceEventLogProvider public class WrapperServiceEventLogProvider : IServiceEventLogProvider
{ {
public WrapperService? service { get; set; } public WrapperService? Service { get; set; }
public EventLog? locate() public EventLog? Locate()
{ {
WrapperService? _service = service; WrapperService? service = this.Service;
if (_service != null && !_service.IsShuttingDown) if (service != null && !service.IsShuttingDown)
{ {
return _service.EventLog; return service.EventLog;
} }
// By default return null // By default return null

View File

@ -16,10 +16,10 @@ using log4net.Appender;
using log4net.Config; using log4net.Config;
using log4net.Core; using log4net.Core;
using log4net.Layout; using log4net.Layout;
using winsw.Logging; using WinSW.Logging;
using winsw.Native; using WinSW.Native;
namespace winsw namespace WinSW
{ {
public static class Program public static class Program
{ {
@ -456,7 +456,6 @@ namespace winsw
default: default:
throw; throw;
} }
} }
} }
@ -530,7 +529,6 @@ namespace winsw
default: default:
throw; throw;
} }
} }
@ -723,7 +721,7 @@ namespace winsw
{ {
Name = "Wrapper event log", Name = "Wrapper event log",
Threshold = eventLogLevel, Threshold = eventLogLevel,
provider = WrapperService.eventLogProvider, Provider = WrapperService.eventLogProvider,
}; };
systemEventLogger.ActivateOptions(); systemEventLogger.ActivateOptions();
appenders.Add(systemEventLogger); appenders.Add(systemEventLogger);

View File

@ -2,7 +2,7 @@
using System.ServiceProcess; using System.ServiceProcess;
using TimeoutException = System.ServiceProcess.TimeoutException; using TimeoutException = System.ServiceProcess.TimeoutException;
namespace winsw namespace WinSW
{ {
internal static class ServiceControllerExtension internal static class ServiceControllerExtension
{ {

View File

@ -1,6 +1,6 @@
using System; using System;
namespace winsw namespace WinSW
{ {
internal sealed class UserException : Exception internal sealed class UserException : Exception
{ {

View File

@ -8,36 +8,37 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using log4net; using log4net;
using winsw.Extensions; using WinSW.Extensions;
using winsw.Logging; using WinSW.Logging;
using winsw.Native; using WinSW.Native;
using winsw.Util; using WinSW.Util;
namespace winsw namespace WinSW
{ {
public class WrapperService : ServiceBase, EventLogger public class WrapperService : ServiceBase, IEventLogger
{ {
private ServiceApis.SERVICE_STATUS _wrapperServiceStatus; private ServiceApis.SERVICE_STATUS wrapperServiceStatus;
private readonly Process _process = new Process(); private readonly Process process = new Process();
private readonly ServiceDescriptor _descriptor; private readonly ServiceDescriptor descriptor;
private Dictionary<string, string>? _envs; private Dictionary<string, string>? envs;
internal WinSWExtensionManager ExtensionManager { get; private set; } internal WinSWExtensionManager ExtensionManager { get; }
private static readonly ILog Log = LogManager.GetLogger( private static readonly ILog Log = LogManager.GetLogger(
#if NETCOREAPP #if NETCOREAPP
Assembly.GetExecutingAssembly(), Assembly.GetExecutingAssembly(),
#endif #endif
"WinSW"); "WinSW");
internal static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider(); internal static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider();
/// <summary> /// <summary>
/// Indicates to the watch dog thread that we are going to terminate the process, /// Indicates to the watch dog thread that we are going to terminate the process,
/// so don't try to kill us when the child exits. /// so don't try to kill us when the child exits.
/// </summary> /// </summary>
private bool _orderlyShutdown; private bool orderlyShutdown;
private bool _systemShuttingdown; private bool systemShuttingdown;
/// <summary> /// <summary>
/// Version of Windows service wrapper /// Version of Windows service wrapper
@ -50,24 +51,25 @@ namespace winsw
/// <summary> /// <summary>
/// Indicates that the system is shutting down. /// Indicates that the system is shutting down.
/// </summary> /// </summary>
public bool IsShuttingDown => _systemShuttingdown; public bool IsShuttingDown => this.systemShuttingdown;
public WrapperService(ServiceDescriptor descriptor) public WrapperService(ServiceDescriptor descriptor)
{ {
_descriptor = descriptor; this.descriptor = descriptor;
ServiceName = _descriptor.Id; this.ServiceName = this.descriptor.Id;
ExtensionManager = new WinSWExtensionManager(_descriptor); this.ExtensionManager = new WinSWExtensionManager(this.descriptor);
CanShutdown = true; this.CanShutdown = true;
CanStop = true; this.CanStop = true;
CanPauseAndContinue = false; this.CanPauseAndContinue = false;
AutoLog = true; this.AutoLog = true;
_systemShuttingdown = false; this.systemShuttingdown = false;
// Register the event log provider // Register the event log provider
eventLogProvider.service = this; eventLogProvider.Service = this;
} }
public WrapperService() : this(new ServiceDescriptor()) public WrapperService()
: this(new ServiceDescriptor())
{ {
} }
@ -77,9 +79,11 @@ namespace winsw
/// </summary> /// </summary>
private void HandleFileCopies() private void HandleFileCopies()
{ {
var file = _descriptor.BasePath + ".copies"; var file = this.descriptor.BasePath + ".copies";
if (!File.Exists(file)) if (!File.Exists(file))
{
return; // nothing to handle return; // nothing to handle
}
try try
{ {
@ -87,15 +91,15 @@ namespace winsw
string? line; string? line;
while ((line = tr.ReadLine()) != null) while ((line = tr.ReadLine()) != null)
{ {
LogEvent("Handling copy: " + line); this.LogEvent("Handling copy: " + line);
string[] tokens = line.Split('>'); string[] tokens = line.Split('>');
if (tokens.Length > 2) if (tokens.Length > 2)
{ {
LogEvent("Too many delimiters in " + line); this.LogEvent("Too many delimiters in " + line);
continue; continue;
} }
MoveFile(tokens[0], tokens[1]); this.MoveFile(tokens[0], tokens[1]);
} }
} }
finally finally
@ -115,7 +119,7 @@ namespace winsw
} }
catch (IOException e) catch (IOException e)
{ {
LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message); this.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message);
} }
} }
@ -125,21 +129,21 @@ namespace winsw
/// <returns>Log Handler, which should be used for the spawned process</returns> /// <returns>Log Handler, which should be used for the spawned process</returns>
private LogHandler CreateExecutableLogHandler() private LogHandler CreateExecutableLogHandler()
{ {
string logDirectory = _descriptor.LogDirectory; string logDirectory = this.descriptor.LogDirectory;
if (!Directory.Exists(logDirectory)) if (!Directory.Exists(logDirectory))
{ {
Directory.CreateDirectory(logDirectory); Directory.CreateDirectory(logDirectory);
} }
LogHandler logAppender = _descriptor.LogHandler; LogHandler logAppender = this.descriptor.LogHandler;
logAppender.EventLogger = this; logAppender.EventLogger = this;
return logAppender; return logAppender;
} }
public void LogEvent(string message) public void LogEvent(string message)
{ {
if (_systemShuttingdown) if (this.systemShuttingdown)
{ {
/* NOP - cannot call EventLog because of shutdown. */ /* NOP - cannot call EventLog because of shutdown. */
} }
@ -147,7 +151,7 @@ namespace winsw
{ {
try try
{ {
EventLog.WriteEntry(message); this.EventLog.WriteEntry(message);
} }
catch (Exception e) catch (Exception e)
{ {
@ -158,7 +162,7 @@ namespace winsw
public void LogEvent(string message, EventLogEntryType type) public void LogEvent(string message, EventLogEntryType type)
{ {
if (_systemShuttingdown) if (this.systemShuttingdown)
{ {
/* NOP - cannot call EventLog because of shutdown. */ /* NOP - cannot call EventLog because of shutdown. */
} }
@ -166,7 +170,7 @@ namespace winsw
{ {
try try
{ {
EventLog.WriteEntry(message, type); this.EventLog.WriteEntry(message, type);
} }
catch (Exception e) catch (Exception e)
{ {
@ -177,24 +181,18 @@ namespace winsw
protected override void OnStart(string[] args) protected override void OnStart(string[] args)
{ {
_envs = _descriptor.EnvironmentVariables; this.envs = this.descriptor.EnvironmentVariables;
// TODO: Disabled according to security concerns in https://github.com/kohsuke/winsw/issues/54
// Could be restored, but unlikely it's required in event logs at all this.HandleFileCopies();
/**
foreach (string key in _envs.Keys)
{
LogEvent("envar " + key + '=' + _envs[key]);
}*/
HandleFileCopies();
// handle downloads // handle downloads
List<Download> downloads = _descriptor.Downloads; List<Download> downloads = this.descriptor.Downloads;
Task[] tasks = new Task[downloads.Count]; Task[] tasks = new Task[downloads.Count];
for (int i = 0; i < downloads.Count; i++) for (int i = 0; i < downloads.Count; i++)
{ {
Download download = downloads[i]; Download download = downloads[i];
string downloadMessage = $"Downloading: {download.From} to {download.To}. failOnError={download.FailOnError.ToString()}"; string downloadMessage = $"Downloading: {download.From} to {download.To}. failOnError={download.FailOnError.ToString()}";
LogEvent(downloadMessage); this.LogEvent(downloadMessage);
Log.Info(downloadMessage); Log.Info(downloadMessage);
tasks[i] = download.PerformAsync(); tasks[i] = download.PerformAsync();
} }
@ -213,7 +211,7 @@ namespace winsw
Download download = downloads[i]; Download download = downloads[i];
string errorMessage = $"Failed to download {download.From} to {download.To}"; string errorMessage = $"Failed to download {download.From} to {download.To}";
AggregateException exception = tasks[i].Exception!; AggregateException exception = tasks[i].Exception!;
LogEvent($"{errorMessage}. {exception.Message}"); this.LogEvent($"{errorMessage}. {exception.Message}");
Log.Error(errorMessage, exception); Log.Error(errorMessage, exception);
// TODO: move this code into the download logic // TODO: move this code into the download logic
@ -227,15 +225,15 @@ namespace winsw
throw new AggregateException(exceptions); throw new AggregateException(exceptions);
} }
string? startArguments = _descriptor.StartArguments; string? startArguments = this.descriptor.StartArguments;
if (startArguments is null) if (startArguments is null)
{ {
startArguments = _descriptor.Arguments; startArguments = this.descriptor.Arguments;
} }
else else
{ {
startArguments += " " + _descriptor.Arguments; startArguments += " " + this.descriptor.Arguments;
} }
// Converting newlines, line returns, tabs into a single // Converting newlines, line returns, tabs into a single
@ -243,18 +241,18 @@ namespace winsw
// in the xml for readability. // in the xml for readability.
startArguments = Regex.Replace(startArguments, @"\s*[\n\r]+\s*", " "); startArguments = Regex.Replace(startArguments, @"\s*[\n\r]+\s*", " ");
LogEvent("Starting " + _descriptor.Executable + ' ' + startArguments); this.LogEvent("Starting " + this.descriptor.Executable + ' ' + startArguments);
Log.Info("Starting " + _descriptor.Executable + ' ' + startArguments); Log.Info("Starting " + this.descriptor.Executable + ' ' + startArguments);
// Load and start extensions // Load and start extensions
ExtensionManager.LoadExtensions(); this.ExtensionManager.LoadExtensions();
ExtensionManager.FireOnWrapperStarted(); this.ExtensionManager.FireOnWrapperStarted();
LogHandler executableLogHandler = CreateExecutableLogHandler(); LogHandler executableLogHandler = this.CreateExecutableLogHandler();
StartProcess(_process, startArguments, _descriptor.Executable, executableLogHandler, true); this.StartProcess(this.process, startArguments, this.descriptor.Executable, executableLogHandler, true);
ExtensionManager.FireOnProcessStarted(_process); this.ExtensionManager.FireOnProcessStarted(this.process);
_process.StandardInput.Close(); // nothing for you to read! this.process.StandardInput.Close(); // nothing for you to read!
} }
protected override void OnShutdown() protected override void OnShutdown()
@ -263,8 +261,8 @@ namespace winsw
try try
{ {
_systemShuttingdown = true; this.systemShuttingdown = true;
StopIt(); this.StopIt();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -278,7 +276,7 @@ namespace winsw
try try
{ {
StopIt(); this.StopIt();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -295,18 +293,18 @@ namespace winsw
/// </summary> /// </summary>
private void StopIt() private void StopIt()
{ {
string? stopArguments = _descriptor.StopArguments; string? stopArguments = this.descriptor.StopArguments;
LogEvent("Stopping " + _descriptor.Id); this.LogEvent("Stopping " + this.descriptor.Id);
Log.Info("Stopping " + _descriptor.Id); Log.Info("Stopping " + this.descriptor.Id);
_orderlyShutdown = true; this.orderlyShutdown = true;
if (stopArguments is null) if (stopArguments is null)
{ {
try try
{ {
Log.Debug("ProcessKill " + _process.Id); Log.Debug("ProcessKill " + this.process.Id);
ProcessHelper.StopProcessTree(_process, _descriptor.StopTimeout); ProcessHelper.StopProcessTree(this.process, this.descriptor.StopTimeout);
ExtensionManager.FireOnProcessTerminated(_process); this.ExtensionManager.FireOnProcessTerminated(this.process);
} }
catch (InvalidOperationException) catch (InvalidOperationException)
{ {
@ -315,48 +313,48 @@ namespace winsw
} }
else else
{ {
SignalShutdownPending(); this.SignalShutdownPending();
stopArguments += " " + _descriptor.Arguments; stopArguments += " " + this.descriptor.Arguments;
Process stopProcess = new Process(); Process stopProcess = new Process();
string? executable = _descriptor.StopExecutable; string? executable = this.descriptor.StopExecutable;
executable ??= _descriptor.Executable; executable ??= this.descriptor.Executable;
// TODO: Redirect logging to Log4Net once https://github.com/kohsuke/winsw/pull/213 is integrated // TODO: Redirect logging to Log4Net once https://github.com/kohsuke/winsw/pull/213 is integrated
StartProcess(stopProcess, stopArguments, executable, null, false); this.StartProcess(stopProcess, stopArguments, executable, null, false);
Log.Debug("WaitForProcessToExit " + _process.Id + "+" + stopProcess.Id); Log.Debug("WaitForProcessToExit " + this.process.Id + "+" + stopProcess.Id);
WaitForProcessToExit(_process); this.WaitForProcessToExit(this.process);
WaitForProcessToExit(stopProcess); this.WaitForProcessToExit(stopProcess);
} }
// Stop extensions // Stop extensions
ExtensionManager.FireBeforeWrapperStopped(); this.ExtensionManager.FireBeforeWrapperStopped();
if (_systemShuttingdown && _descriptor.BeepOnShutdown) if (this.systemShuttingdown && this.descriptor.BeepOnShutdown)
{ {
Console.Beep(); Console.Beep();
} }
Log.Info("Finished " + _descriptor.Id); Log.Info("Finished " + this.descriptor.Id);
} }
private void WaitForProcessToExit(Process processoWait) private void WaitForProcessToExit(Process processoWait)
{ {
SignalShutdownPending(); this.SignalShutdownPending();
int effectiveProcessWaitSleepTime; int effectiveProcessWaitSleepTime;
if (_descriptor.SleepTime.TotalMilliseconds > int.MaxValue) if (this.descriptor.SleepTime.TotalMilliseconds > int.MaxValue)
{ {
Log.Warn("The requested sleep time " + _descriptor.SleepTime.TotalMilliseconds + "is greater that the max value " + Log.Warn("The requested sleep time " + this.descriptor.SleepTime.TotalMilliseconds + "is greater that the max value " +
int.MaxValue + ". The value will be truncated"); int.MaxValue + ". The value will be truncated");
effectiveProcessWaitSleepTime = int.MaxValue; effectiveProcessWaitSleepTime = int.MaxValue;
} }
else else
{ {
effectiveProcessWaitSleepTime = (int)_descriptor.SleepTime.TotalMilliseconds; effectiveProcessWaitSleepTime = (int)this.descriptor.SleepTime.TotalMilliseconds;
} }
try try
@ -365,7 +363,7 @@ namespace winsw
while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime)) while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
{ {
SignalShutdownPending(); this.SignalShutdownPending();
// WriteEvent("WaitForProcessToExit [repeat]"); // WriteEvent("WaitForProcessToExit [repeat]");
} }
} }
@ -380,27 +378,27 @@ namespace winsw
private void SignalShutdownPending() private void SignalShutdownPending()
{ {
int effectiveWaitHint; int effectiveWaitHint;
if (_descriptor.WaitHint.TotalMilliseconds > int.MaxValue) if (this.descriptor.WaitHint.TotalMilliseconds > int.MaxValue)
{ {
Log.Warn("The requested WaitHint value (" + _descriptor.WaitHint.TotalMilliseconds + " ms) is greater that the max value " + Log.Warn("The requested WaitHint value (" + this.descriptor.WaitHint.TotalMilliseconds + " ms) is greater that the max value " +
int.MaxValue + ". The value will be truncated"); int.MaxValue + ". The value will be truncated");
effectiveWaitHint = int.MaxValue; effectiveWaitHint = int.MaxValue;
} }
else else
{ {
effectiveWaitHint = (int)_descriptor.WaitHint.TotalMilliseconds; effectiveWaitHint = (int)this.descriptor.WaitHint.TotalMilliseconds;
} }
RequestAdditionalTime(effectiveWaitHint); this.RequestAdditionalTime(effectiveWaitHint);
} }
private void SignalShutdownComplete() private void SignalShutdownComplete()
{ {
IntPtr handle = ServiceHandle; IntPtr handle = this.ServiceHandle;
_wrapperServiceStatus.CheckPoint++; this.wrapperServiceStatus.CheckPoint++;
// WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint); // WriteEvent("SignalShutdownComplete " + wrapperServiceStatus.checkPoint + ":" + wrapperServiceStatus.waitHint);
_wrapperServiceStatus.CurrentState = ServiceControllerStatus.Stopped; this.wrapperServiceStatus.CurrentState = ServiceControllerStatus.Stopped;
ServiceApis.SetServiceStatus(handle, _wrapperServiceStatus); ServiceApis.SetServiceStatus(handle, this.wrapperServiceStatus);
} }
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler, bool redirectStdin) private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler, bool redirectStdin)
@ -411,25 +409,28 @@ namespace winsw
string msg = processToStart.Id + " - " + processToStart.StartInfo.FileName + " " + processToStart.StartInfo.Arguments; string msg = processToStart.Id + " - " + processToStart.StartInfo.FileName + " " + processToStart.StartInfo.Arguments;
try try
{ {
if (_orderlyShutdown) if (this.orderlyShutdown)
{ {
LogEvent("Child process [" + msg + "] terminated with " + proc.ExitCode, EventLogEntryType.Information); this.LogEvent("Child process [" + msg + "] terminated with " + proc.ExitCode, EventLogEntryType.Information);
} }
else else
{ {
LogEvent("Child process [" + msg + "] finished with " + proc.ExitCode, EventLogEntryType.Warning); this.LogEvent("Child process [" + msg + "] finished with " + proc.ExitCode, EventLogEntryType.Warning);
// if we finished orderly, report that to SCM. // if we finished orderly, report that to SCM.
// by not reporting unclean shutdown, we let Windows SCM to decide if it wants to // by not reporting unclean shutdown, we let Windows SCM to decide if it wants to
// restart the service automatically // restart the service automatically
if (proc.ExitCode == 0) if (proc.ExitCode == 0)
SignalShutdownComplete(); {
this.SignalShutdownComplete();
}
Environment.Exit(proc.ExitCode); Environment.Exit(proc.ExitCode);
} }
} }
catch (InvalidOperationException ioe) catch (InvalidOperationException ioe)
{ {
LogEvent("WaitForExit " + ioe.Message); this.LogEvent("WaitForExit " + ioe.Message);
} }
finally finally
{ {
@ -442,13 +443,13 @@ namespace winsw
processToStart: processToStart, processToStart: processToStart,
executable: executable, executable: executable,
arguments: arguments, arguments: arguments,
envVars: _envs, envVars: this.envs,
workingDirectory: _descriptor.WorkingDirectory, workingDirectory: this.descriptor.WorkingDirectory,
priority: _descriptor.Priority, priority: this.descriptor.Priority,
callback: OnProcessCompleted, callback: OnProcessCompleted,
logHandler: logHandler, logHandler: logHandler,
redirectStdin: redirectStdin, redirectStdin: redirectStdin,
hideWindow: _descriptor.HideWindow); hideWindow: this.descriptor.HideWindow);
} }
} }
} }

View File

@ -5,56 +5,80 @@ using System.IO;
using System.ServiceProcess; using System.ServiceProcess;
using System.Xml; using System.Xml;
namespace winsw.Configuration namespace WinSW.Configuration
{ {
/// <summary> /// <summary>
/// Default WinSW settings /// Default WinSW settings
/// </summary> /// </summary>
public sealed class DefaultWinSWSettings : IWinSWConfiguration public sealed class DefaultWinSWSettings : IWinSWConfiguration
{ {
public string Id => throw new InvalidOperationException(nameof(Id) + " must be specified."); public string Id => throw new InvalidOperationException(nameof(this.Id) + " must be specified.");
public string Caption => throw new InvalidOperationException(nameof(Caption) + " must be specified.");
public string Description => throw new InvalidOperationException(nameof(Description) + " must be specified."); public string Caption => throw new InvalidOperationException(nameof(this.Caption) + " must be specified.");
public string Executable => throw new InvalidOperationException(nameof(Executable) + " must be specified.");
public string Description => throw new InvalidOperationException(nameof(this.Description) + " must be specified.");
public string Executable => throw new InvalidOperationException(nameof(this.Executable) + " must be specified.");
public bool HideWindow => false; public bool HideWindow => false;
public string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName; public string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName;
// Installation // Installation
public bool AllowServiceAcountLogonRight => false; public bool AllowServiceAcountLogonRight => false;
public string? ServiceAccountPassword => null; public string? ServiceAccountPassword => null;
public string? ServiceAccountUserName => null; public string? ServiceAccountUserName => null;
public Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0]; public Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0];
public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1); public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1);
// Executable management // Executable management
public string Arguments => string.Empty; public string Arguments => string.Empty;
public string? StartArguments => null; public string? StartArguments => null;
public string? StopExecutable => null; public string? StopExecutable => null;
public string? StopArguments => null; public string? StopArguments => null;
public string WorkingDirectory => Path.GetDirectoryName(ExecutablePath)!;
public string WorkingDirectory => Path.GetDirectoryName(this.ExecutablePath)!;
public ProcessPriorityClass Priority => ProcessPriorityClass.Normal; public ProcessPriorityClass Priority => ProcessPriorityClass.Normal;
public TimeSpan StopTimeout => TimeSpan.FromSeconds(15); public TimeSpan StopTimeout => TimeSpan.FromSeconds(15);
// Service management // Service management
public ServiceStartMode StartMode => ServiceStartMode.Automatic; public ServiceStartMode StartMode => ServiceStartMode.Automatic;
public bool DelayedAutoStart => false; public bool DelayedAutoStart => false;
public string[] ServiceDependencies => new string[0]; public string[] ServiceDependencies => new string[0];
public TimeSpan WaitHint => TimeSpan.FromSeconds(15); public TimeSpan WaitHint => TimeSpan.FromSeconds(15);
public TimeSpan SleepTime => TimeSpan.FromSeconds(1); public TimeSpan SleepTime => TimeSpan.FromSeconds(1);
public bool Interactive => false; public bool Interactive => false;
// Logging // Logging
public string LogDirectory => Path.GetDirectoryName(ExecutablePath)!; public string LogDirectory => Path.GetDirectoryName(this.ExecutablePath)!;
public string LogMode => "append"; public string LogMode => "append";
public bool OutFileDisabled => false; public bool OutFileDisabled => false;
public bool ErrFileDisabled => false; public bool ErrFileDisabled => false;
public string OutFilePattern => ".out.log"; public string OutFilePattern => ".out.log";
public string ErrFilePattern => ".err.log"; public string ErrFilePattern => ".err.log";
// Environment // Environment
public List<Download> Downloads => new List<Download>(0); public List<Download> Downloads => new List<Download>(0);
public Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(0); public Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(0);
// Misc // Misc

View File

@ -4,49 +4,69 @@ using System.Diagnostics;
using System.ServiceProcess; using System.ServiceProcess;
using System.Xml; using System.Xml;
namespace winsw.Configuration namespace WinSW.Configuration
{
public interface IWinSWConfiguration
{ {
// TODO: Document the parameters && refactor // TODO: Document the parameters && refactor
public interface IWinSWConfiguration
{
string Id { get; } string Id { get; }
string Caption { get; } string Caption { get; }
string Description { get; } string Description { get; }
string Executable { get; } string Executable { get; }
string ExecutablePath { get; } string ExecutablePath { get; }
bool HideWindow { get; } bool HideWindow { get; }
// Installation // Installation
bool AllowServiceAcountLogonRight { get; } bool AllowServiceAcountLogonRight { get; }
string? ServiceAccountPassword { get; } string? ServiceAccountPassword { get; }
string? ServiceAccountUserName { get; } string? ServiceAccountUserName { get; }
Native.SC_ACTION[] FailureActions { get; } Native.SC_ACTION[] FailureActions { get; }
TimeSpan ResetFailureAfter { get; } TimeSpan ResetFailureAfter { get; }
// Executable management // Executable management
string Arguments { get; } string Arguments { get; }
string? StartArguments { get; } string? StartArguments { get; }
string? StopExecutable { get; } string? StopExecutable { get; }
string? StopArguments { get; } string? StopArguments { get; }
string WorkingDirectory { get; } string WorkingDirectory { get; }
ProcessPriorityClass Priority { get; } ProcessPriorityClass Priority { get; }
TimeSpan StopTimeout { get; } TimeSpan StopTimeout { get; }
// Service management // Service management
ServiceStartMode StartMode { get; } ServiceStartMode StartMode { get; }
string[] ServiceDependencies { get; } string[] ServiceDependencies { get; }
TimeSpan WaitHint { get; } TimeSpan WaitHint { get; }
TimeSpan SleepTime { get; } TimeSpan SleepTime { get; }
bool Interactive { get; } bool Interactive { get; }
// Logging // Logging
string LogDirectory { get; } string LogDirectory { get; }
// TODO: replace by enum // TODO: replace by enum
string LogMode { get; } string LogMode { get; }
// Environment // Environment
List<Download> Downloads { get; } List<Download> Downloads { get; }
Dictionary<string, string> EnvironmentVariables { get; } Dictionary<string, string> EnvironmentVariables { get; }
// Misc // Misc

View File

@ -5,9 +5,9 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using log4net; using log4net;
using winsw.Util; using WinSW.Util;
namespace winsw namespace WinSW
{ {
/// <summary> /// <summary>
/// Specify the download activities prior to the launch. /// Specify the download activities prior to the launch.
@ -17,9 +17,9 @@ namespace winsw
{ {
public enum AuthType public enum AuthType
{ {
none = 0, None = 0,
sspi, Sspi,
basic Basic
} }
private static readonly ILog Logger = LogManager.GetLogger(typeof(Download)); private static readonly ILog Logger = LogManager.GetLogger(typeof(Download));
@ -33,7 +33,7 @@ namespace winsw
public readonly bool FailOnError; public readonly bool FailOnError;
public readonly string? Proxy; public readonly string? Proxy;
public string ShortId => $"(download from {From})"; public string ShortId => $"(download from {this.From})";
#if NET461 #if NET461
static Download() static Download()
@ -48,20 +48,20 @@ namespace winsw
string from, string from,
string to, string to,
bool failOnError = false, bool failOnError = false,
AuthType auth = AuthType.none, AuthType auth = AuthType.None,
string? username = null, string? username = null,
string? password = null, string? password = null,
bool unsecureAuth = false, bool unsecureAuth = false,
string? proxy = null) string? proxy = null)
{ {
From = from; this.From = from;
To = to; this.To = to;
FailOnError = failOnError; this.FailOnError = failOnError;
Proxy = proxy; this.Proxy = proxy;
Auth = auth; this.Auth = auth;
Username = username; this.Username = username;
Password = password; this.Password = password;
UnsecureAuth = unsecureAuth; this.UnsecureAuth = unsecureAuth;
} }
/// <summary> /// <summary>
@ -71,36 +71,36 @@ namespace winsw
/// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid</exception> /// <exception cref="InvalidDataException">The required attribute is missing or the configuration is invalid</exception>
internal Download(XmlElement n) internal Download(XmlElement n)
{ {
From = XmlHelper.SingleAttribute<string>(n, "from"); this.From = XmlHelper.SingleAttribute<string>(n, "from");
To = XmlHelper.SingleAttribute<string>(n, "to"); this.To = XmlHelper.SingleAttribute<string>(n, "to");
// All arguments below are optional // All arguments below are optional
FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false); this.FailOnError = XmlHelper.SingleAttribute(n, "failOnError", false);
Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null); this.Proxy = XmlHelper.SingleAttribute<string>(n, "proxy", null);
Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.none); this.Auth = XmlHelper.EnumAttribute(n, "auth", AuthType.None);
Username = XmlHelper.SingleAttribute<string>(n, "user", null); this.Username = XmlHelper.SingleAttribute<string>(n, "user", null);
Password = XmlHelper.SingleAttribute<string>(n, "password", null); this.Password = XmlHelper.SingleAttribute<string>(n, "password", null);
UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false); this.UnsecureAuth = XmlHelper.SingleAttribute(n, "unsecureAuth", false);
if (Auth == AuthType.basic) if (this.Auth == AuthType.Basic)
{ {
// Allow it only for HTTPS or for UnsecureAuth // Allow it only for HTTPS or for UnsecureAuth
if (!From.StartsWith("https:") && !UnsecureAuth) if (!this.From.StartsWith("https:") && !this.UnsecureAuth)
{ {
throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + ShortId + throw new InvalidDataException("Warning: you're sending your credentials in clear text to the server " + this.ShortId +
"If you really want this you must enable 'unsecureAuth' in the configuration"); "If you really want this you must enable 'unsecureAuth' in the configuration");
} }
// Also fail if there is no user/password // Also fail if there is no user/password
if (Username is null) if (this.Username is null)
{ {
throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + ShortId); throw new InvalidDataException("Basic Auth is enabled, but username is not specified " + this.ShortId);
} }
if (Password is null) if (this.Password is null)
{ {
throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + ShortId); throw new InvalidDataException("Basic Auth is enabled, but password is not specified " + this.ShortId);
} }
} }
} }
@ -121,10 +121,10 @@ namespace winsw
/// </exception> /// </exception>
public async Task PerformAsync() public async Task PerformAsync()
{ {
WebRequest request = WebRequest.Create(From); WebRequest request = WebRequest.Create(this.From);
if (!string.IsNullOrEmpty(Proxy)) if (!string.IsNullOrEmpty(this.Proxy))
{ {
CustomProxyInformation proxyInformation = new CustomProxyInformation(Proxy); CustomProxyInformation proxyInformation = new CustomProxyInformation(this.Proxy!);
if (proxyInformation.Credentials != null) if (proxyInformation.Credentials != null)
{ {
request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials); request.Proxy = new WebProxy(proxyInformation.ServerAddress, false, null, proxyInformation.Credentials);
@ -135,35 +135,35 @@ namespace winsw
} }
} }
switch (Auth) switch (this.Auth)
{ {
case AuthType.none: case AuthType.None:
// Do nothing // Do nothing
break; break;
case AuthType.sspi: case AuthType.Sspi:
request.UseDefaultCredentials = true; request.UseDefaultCredentials = true;
request.PreAuthenticate = true; request.PreAuthenticate = true;
request.Credentials = CredentialCache.DefaultCredentials; request.Credentials = CredentialCache.DefaultCredentials;
break; break;
case AuthType.basic: case AuthType.Basic:
SetBasicAuthHeader(request, Username!, Password!); this.SetBasicAuthHeader(request, this.Username!, this.Password!);
break; break;
default: default:
throw new WebException("Code defect. Unsupported authentication type: " + Auth); throw new WebException("Code defect. Unsupported authentication type: " + this.Auth);
} }
bool supportsIfModifiedSince = false; bool supportsIfModifiedSince = false;
if (request is HttpWebRequest httpRequest && File.Exists(To)) if (request is HttpWebRequest httpRequest && File.Exists(this.To))
{ {
supportsIfModifiedSince = true; supportsIfModifiedSince = true;
httpRequest.IfModifiedSince = File.GetLastWriteTime(To); httpRequest.IfModifiedSince = File.GetLastWriteTime(this.To);
} }
DateTime lastModified = default; DateTime lastModified = default;
string tmpFilePath = To + ".tmp"; string tmpFilePath = this.To + ".tmp";
try try
{ {
using (WebResponse response = await request.GetResponseAsync()) using (WebResponse response = await request.GetResponseAsync())
@ -178,18 +178,18 @@ namespace winsw
await responseStream.CopyToAsync(tmpStream); await responseStream.CopyToAsync(tmpStream);
} }
FileHelper.MoveOrReplaceFile(To + ".tmp", To); FileHelper.MoveOrReplaceFile(this.To + ".tmp", this.To);
if (supportsIfModifiedSince) if (supportsIfModifiedSince)
{ {
File.SetLastWriteTime(To, lastModified); File.SetLastWriteTime(this.To, lastModified);
} }
} }
catch (WebException e) catch (WebException e)
{ {
if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified) if (supportsIfModifiedSince && ((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.NotModified)
{ {
Logger.Info($"Skipped downloading unmodified resource '{From}'"); Logger.Info($"Skipped downloading unmodified resource '{this.From}'");
} }
else else
{ {
@ -201,8 +201,9 @@ namespace winsw
public class CustomProxyInformation public class CustomProxyInformation
{ {
public string ServerAddress { get; set; } public string ServerAddress { get; }
public NetworkCredential? Credentials { get; set; }
public NetworkCredential? Credentials { get; }
public CustomProxyInformation(string proxy) public CustomProxyInformation(string proxy)
{ {
@ -216,12 +217,12 @@ namespace winsw
string username = completeCredsStr.Substring(0, credsSeparator); string username = completeCredsStr.Substring(0, credsSeparator);
string password = completeCredsStr.Substring(credsSeparator + 1); string password = completeCredsStr.Substring(credsSeparator + 1);
Credentials = new NetworkCredential(username, password); this.Credentials = new NetworkCredential(username, password);
ServerAddress = proxy.Replace(completeCredsStr + "@", ""); this.ServerAddress = proxy.Replace(completeCredsStr + "@", null);
} }
else else
{ {
ServerAddress = proxy; this.ServerAddress = proxy;
} }
} }
} }

View File

@ -1,6 +1,6 @@
using System.Xml; using System.Xml;
namespace winsw.Extensions namespace WinSW.Extensions
{ {
public abstract class AbstractWinSWExtension : IWinSWExtension public abstract class AbstractWinSWExtension : IWinSWExtension
{ {

View File

@ -1,23 +1,23 @@
using System; using System;
namespace winsw.Extensions namespace WinSW.Extensions
{ {
public class ExtensionException : WinSWException public class ExtensionException : WinSWException
{ {
public string ExtensionId { get; private set; } public string ExtensionId { get; }
public ExtensionException(string extensionName, string message) public ExtensionException(string extensionName, string message)
: base(message) : base(message)
{ {
ExtensionId = extensionName; this.ExtensionId = extensionName;
} }
public ExtensionException(string extensionName, string message, Exception innerException) public ExtensionException(string extensionName, string message, Exception innerException)
: base(message, innerException) : base(message, innerException)
{ {
ExtensionId = extensionName; this.ExtensionId = extensionName;
} }
public override string Message => ExtensionId + ": " + base.Message; public override string Message => this.ExtensionId + ": " + base.Message;
} }
} }

View File

@ -1,12 +0,0 @@
namespace winsw.Extensions
{
/// <summary>
/// This attribute is used to identify extension points within the code
/// </summary>
/// <remarks>
/// Each extension point implements its own entry type.
/// </remarks>
class ExtensionPointAttribute
{
}
}

View File

@ -1,6 +1,6 @@
using System.Xml; using System.Xml;
namespace winsw.Extensions namespace WinSW.Extensions
{ {
/// <summary> /// <summary>
/// Interface for Win Service Wrapper Extension /// Interface for Win Service Wrapper Extension

View File

@ -1,7 +1,7 @@
using System.Xml; using System.Xml;
using winsw.Util; using WinSW.Util;
namespace winsw.Extensions namespace WinSW.Extensions
{ {
/// <summary> /// <summary>
/// Describes WinSW extensions in <see cref="IWinSWExtension"/> /// Describes WinSW extensions in <see cref="IWinSWExtension"/>
@ -14,23 +14,23 @@ namespace winsw.Extensions
/// <summary> /// <summary>
/// Unique extension ID /// Unique extension ID
/// </summary> /// </summary>
public string Id { get; private set; } public string Id { get; }
/// <summary> /// <summary>
/// Exception is enabled /// Exception is enabled
/// </summary> /// </summary>
public bool Enabled { get; private set; } public bool Enabled { get; }
/// <summary> /// <summary>
/// Extension classname /// Extension classname
/// </summary> /// </summary>
public string ClassName { get; private set; } public string ClassName { get; }
private WinSWExtensionDescriptor(string id, string className, bool enabled) private WinSWExtensionDescriptor(string id, string className, bool enabled)
{ {
Id = id; this.Id = id;
Enabled = enabled; this.Enabled = enabled;
ClassName = className; this.ClassName = className;
} }
public static WinSWExtensionDescriptor FromXml(XmlElement node) public static WinSWExtensionDescriptor FromXml(XmlElement node)

View File

@ -3,20 +3,20 @@ using System.Collections.Generic;
using System.Xml; using System.Xml;
using log4net; using log4net;
namespace winsw.Extensions namespace WinSW.Extensions
{ {
public class WinSWExtensionManager public class WinSWExtensionManager
{ {
public Dictionary<string, IWinSWExtension> Extensions { get; private set; } public Dictionary<string, IWinSWExtension> Extensions { get; }
public ServiceDescriptor ServiceDescriptor { get; private set; } public ServiceDescriptor ServiceDescriptor { get; }
private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager)); private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager));
public WinSWExtensionManager(ServiceDescriptor serviceDescriptor) public WinSWExtensionManager(ServiceDescriptor serviceDescriptor)
{ {
ServiceDescriptor = serviceDescriptor; this.ServiceDescriptor = serviceDescriptor;
Extensions = new Dictionary<string, IWinSWExtension>(); this.Extensions = new Dictionary<string, IWinSWExtension>();
} }
/// <summary> /// <summary>
@ -27,7 +27,7 @@ namespace winsw.Extensions
/// <exception cref="Exception">Start failure</exception> /// <exception cref="Exception">Start failure</exception>
public void FireOnWrapperStarted() public void FireOnWrapperStarted()
{ {
foreach (var ext in Extensions) foreach (var ext in this.Extensions)
{ {
try try
{ {
@ -47,7 +47,7 @@ namespace winsw.Extensions
/// </summary> /// </summary>
public void FireBeforeWrapperStopped() public void FireBeforeWrapperStopped()
{ {
foreach (var ext in Extensions) foreach (var ext in this.Extensions)
{ {
try try
{ {
@ -66,7 +66,7 @@ namespace winsw.Extensions
/// <param name="process">Process</param> /// <param name="process">Process</param>
public void FireOnProcessStarted(System.Diagnostics.Process process) public void FireOnProcessStarted(System.Diagnostics.Process process)
{ {
foreach (var ext in Extensions) foreach (var ext in this.Extensions)
{ {
try try
{ {
@ -85,7 +85,7 @@ namespace winsw.Extensions
/// <param name="process">Process</param> /// <param name="process">Process</param>
public void FireOnProcessTerminated(System.Diagnostics.Process process) public void FireOnProcessTerminated(System.Diagnostics.Process process)
{ {
foreach (var ext in Extensions) foreach (var ext in this.Extensions)
{ {
try try
{ {
@ -107,10 +107,10 @@ namespace winsw.Extensions
/// <exception cref="Exception">Loading failure</exception> /// <exception cref="Exception">Loading failure</exception>
public void LoadExtensions() public void LoadExtensions()
{ {
var extensionIds = ServiceDescriptor.ExtensionIds; var extensionIds = this.ServiceDescriptor.ExtensionIds;
foreach (string extensionId in extensionIds) foreach (string extensionId in extensionIds)
{ {
LoadExtension(extensionId); this.LoadExtension(extensionId);
} }
} }
@ -122,12 +122,12 @@ namespace winsw.Extensions
/// <exception cref="Exception">Loading failure</exception> /// <exception cref="Exception">Loading failure</exception>
private void LoadExtension(string id) private void LoadExtension(string id)
{ {
if (Extensions.ContainsKey(id)) if (this.Extensions.ContainsKey(id))
{ {
throw new ExtensionException(id, "Extension has been already loaded"); throw new ExtensionException(id, "Extension has been already loaded");
} }
XmlNode? extensionsConfig = ServiceDescriptor.ExtensionsConfiguration; XmlNode? extensionsConfig = this.ServiceDescriptor.ExtensionsConfiguration;
XmlElement? configNode = extensionsConfig is null ? null : extensionsConfig.SelectSingleNode("extension[@id='" + id + "'][1]") as XmlElement; XmlElement? configNode = extensionsConfig is null ? null : extensionsConfig.SelectSingleNode("extension[@id='" + id + "'][1]") as XmlElement;
if (configNode is null) if (configNode is null)
{ {
@ -137,11 +137,11 @@ namespace winsw.Extensions
var descriptor = WinSWExtensionDescriptor.FromXml(configNode); var descriptor = WinSWExtensionDescriptor.FromXml(configNode);
if (descriptor.Enabled) if (descriptor.Enabled)
{ {
IWinSWExtension extension = CreateExtensionInstance(descriptor.Id, descriptor.ClassName); IWinSWExtension extension = this.CreateExtensionInstance(descriptor.Id, descriptor.ClassName);
extension.Descriptor = descriptor; extension.Descriptor = descriptor;
try try
{ {
extension.Configure(ServiceDescriptor, configNode); extension.Configure(this.ServiceDescriptor, configNode);
} }
catch (Exception ex) catch (Exception ex)
{ // Consider any unexpected exception as fatal { // Consider any unexpected exception as fatal
@ -149,7 +149,7 @@ namespace winsw.Extensions
throw ex; throw ex;
} }
Extensions.Add(id, extension); this.Extensions.Add(id, extension);
Log.Info("Extension loaded: " + id); Log.Info("Extension loaded: " + id);
} }
else else

View File

@ -1,14 +1,13 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO.Compression;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Threading; using System.Threading;
using winsw.Util; using WinSW.Util;
namespace winsw namespace WinSW
{ {
// ReSharper disable once InconsistentNaming public interface IEventLogger
public interface EventLogger
{ {
void LogEvent(string message); void LogEvent(string message);
@ -20,14 +19,13 @@ namespace winsw
/// </summary> /// </summary>
public abstract class LogHandler public abstract class LogHandler
{ {
// ReSharper disable once InconsistentNaming public abstract void Log(StreamReader outputReader, StreamReader errorReader);
public abstract void log(StreamReader outputReader, StreamReader errorReader);
/// <summary> /// <summary>
/// Error and information about logging should be reported here. /// Error and information about logging should be reported here.
/// </summary> /// </summary>
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. #pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public EventLogger EventLogger { get; set; } public IEventLogger EventLogger { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. #pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
/// <summary> /// <summary>
@ -56,7 +54,7 @@ namespace winsw
} }
catch (IOException e) catch (IOException e)
{ {
EventLogger.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message); this.EventLogger.LogEvent("Failed to move :" + sourceFileName + " to " + destFileName + " because " + e.Message);
} }
} }
} }
@ -66,22 +64,26 @@ namespace winsw
/// </summary> /// </summary>
public abstract class AbstractFileLogAppender : LogHandler public abstract class AbstractFileLogAppender : LogHandler
{ {
protected string BaseLogFileName { get; private set; } protected string BaseLogFileName { get; }
protected bool OutFileDisabled { get; private set; }
protected bool ErrFileDisabled { get; private set; } protected bool OutFileDisabled { get; }
protected string OutFilePattern { get; private set; }
protected string ErrFilePattern { get; private set; } protected bool ErrFileDisabled { get; }
protected string OutFilePattern { get; }
protected string ErrFilePattern { get; }
protected AbstractFileLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) protected AbstractFileLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
{ {
BaseLogFileName = Path.Combine(logDirectory, baseName); this.BaseLogFileName = Path.Combine(logDirectory, baseName);
OutFileDisabled = outFileDisabled; this.OutFileDisabled = outFileDisabled;
OutFilePattern = outFilePattern; this.OutFilePattern = outFilePattern;
ErrFileDisabled = errFileDisabled; this.ErrFileDisabled = errFileDisabled;
ErrFilePattern = errFilePattern; this.ErrFilePattern = errFilePattern;
} }
public override void log(StreamReader outputReader, StreamReader errorReader) public override void Log(StreamReader outputReader, StreamReader errorReader)
{ {
if (this.OutFileDisabled) if (this.OutFileDisabled)
{ {
@ -111,26 +113,28 @@ namespace winsw
public abstract class SimpleLogAppender : AbstractFileLogAppender public abstract class SimpleLogAppender : AbstractFileLogAppender
{ {
public FileMode FileMode { get; private set; } public FileMode FileMode { get; }
public string OutputLogFileName { get; private set; }
public string ErrorLogFileName { get; private set; } public string OutputLogFileName { get; }
public string ErrorLogFileName { get; }
protected SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) protected SimpleLogAppender(string logDirectory, string baseName, FileMode fileMode, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{ {
FileMode = fileMode; this.FileMode = fileMode;
OutputLogFileName = BaseLogFileName + ".out.log"; this.OutputLogFileName = this.BaseLogFileName + ".out.log";
ErrorLogFileName = BaseLogFileName + ".err.log"; this.ErrorLogFileName = this.BaseLogFileName + ".err.log";
} }
protected override void LogOutput(StreamReader outputReader) protected override void LogOutput(StreamReader outputReader)
{ {
new Thread(() => CopyStream(outputReader, CreateWriter(new FileStream(OutputLogFileName, FileMode)))).Start(); new Thread(() => this.CopyStream(outputReader, this.CreateWriter(new FileStream(this.OutputLogFileName, this.FileMode)))).Start();
} }
protected override void LogError(StreamReader errorReader) protected override void LogError(StreamReader errorReader)
{ {
new Thread(() => CopyStream(errorReader, CreateWriter(new FileStream(ErrorLogFileName, FileMode)))).Start(); new Thread(() => this.CopyStream(errorReader, this.CreateWriter(new FileStream(this.ErrorLogFileName, this.FileMode)))).Start();
} }
} }
@ -155,7 +159,7 @@ namespace winsw
/// </summary> /// </summary>
public class IgnoreLogAppender : LogHandler public class IgnoreLogAppender : LogHandler
{ {
public override void log(StreamReader outputReader, StreamReader errorReader) public override void Log(StreamReader outputReader, StreamReader errorReader)
{ {
outputReader.Dispose(); outputReader.Dispose();
errorReader.Dispose(); errorReader.Dispose();
@ -164,24 +168,25 @@ namespace winsw
public class TimeBasedRollingLogAppender : AbstractFileLogAppender public class TimeBasedRollingLogAppender : AbstractFileLogAppender
{ {
public string Pattern { get; private set; } public string Pattern { get; }
public int Period { get; private set; }
public int Period { get; }
public TimeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, string pattern, int period) public TimeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, string pattern, int period)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{ {
Pattern = pattern; this.Pattern = pattern;
Period = period; this.Period = period;
} }
protected override void LogOutput(StreamReader outputReader) protected override void LogOutput(StreamReader outputReader)
{ {
new Thread(() => CopyStreamWithDateRotation(outputReader, OutFilePattern)).Start(); new Thread(() => this.CopyStreamWithDateRotation(outputReader, this.OutFilePattern)).Start();
} }
protected override void LogError(StreamReader errorReader) protected override void LogError(StreamReader errorReader)
{ {
new Thread(() => CopyStreamWithDateRotation(errorReader, ErrFilePattern)).Start(); new Thread(() => this.CopyStreamWithDateRotation(errorReader, this.ErrFilePattern)).Start();
} }
/// <summary> /// <summary>
@ -189,17 +194,17 @@ namespace winsw
/// </summary> /// </summary>
private void CopyStreamWithDateRotation(StreamReader reader, string ext) private void CopyStreamWithDateRotation(StreamReader reader, string ext)
{ {
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(Pattern, Period); PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.Pattern, this.Period);
periodicRollingCalendar.init(); periodicRollingCalendar.Init();
StreamWriter writer = CreateWriter(new FileStream(BaseLogFileName + "_" + periodicRollingCalendar.format + ext, FileMode.Append)); StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Append));
string? line; string? line;
while ((line = reader.ReadLine()) != null) while ((line = reader.ReadLine()) != null)
{ {
if (periodicRollingCalendar.shouldRoll) if (periodicRollingCalendar.ShouldRoll)
{ {
writer.Dispose(); writer.Dispose();
writer = CreateWriter(new FileStream(BaseLogFileName + "_" + periodicRollingCalendar.format + ext, FileMode.Create)); writer = this.CreateWriter(new FileStream(this.BaseLogFileName + "_" + periodicRollingCalendar.Format + ext, FileMode.Create));
} }
writer.WriteLine(line); writer.WriteLine(line);
@ -212,37 +217,35 @@ namespace winsw
public class SizeBasedRollingLogAppender : AbstractFileLogAppender public class SizeBasedRollingLogAppender : AbstractFileLogAppender
{ {
// ReSharper disable once InconsistentNaming public const int BytesPerKB = 1024;
public static int BYTES_PER_KB = 1024; public const int BytesPerMB = 1024 * BytesPerKB;
// ReSharper disable once InconsistentNaming public const int DefaultSizeThreshold = 10 * BytesPerMB; // roll every 10MB.
public static int BYTES_PER_MB = 1024 * BYTES_PER_KB; public const int DefaultFilesToKeep = 8;
// ReSharper disable once InconsistentNaming
public static int DEFAULT_SIZE_THRESHOLD = 10 * BYTES_PER_MB; // roll every 10MB.
// ReSharper disable once InconsistentNaming
public static int DEFAULT_FILES_TO_KEEP = 8;
public int SizeTheshold { get; private set; } public int SizeThreshold { get; }
public int FilesToKeep { get; private set; } public int FilesToKeep { get; }
public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, int sizeThreshold, int filesToKeep) public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern, int sizeThreshold, int filesToKeep)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{ {
SizeTheshold = sizeThreshold; this.SizeThreshold = sizeThreshold;
FilesToKeep = filesToKeep; this.FilesToKeep = filesToKeep;
} }
public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern) public SizeBasedRollingLogAppender(string logDirectory, string baseName, bool outFileDisabled, bool errFileDisabled, string outFilePattern, string errFilePattern)
: this(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern, DEFAULT_SIZE_THRESHOLD, DEFAULT_FILES_TO_KEEP) { } : this(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern, DefaultSizeThreshold, DefaultFilesToKeep)
{
}
protected override void LogOutput(StreamReader outputReader) protected override void LogOutput(StreamReader outputReader)
{ {
new Thread(() => CopyStreamWithRotation(outputReader, OutFilePattern)).Start(); new Thread(() => this.CopyStreamWithRotation(outputReader, this.OutFilePattern)).Start();
} }
protected override void LogError(StreamReader errorReader) protected override void LogError(StreamReader errorReader)
{ {
new Thread(() => CopyStreamWithRotation(errorReader, ErrFilePattern)).Start(); new Thread(() => this.CopyStreamWithRotation(errorReader, this.ErrFilePattern)).Start();
} }
/// <summary> /// <summary>
@ -250,41 +253,45 @@ namespace winsw
/// </summary> /// </summary>
private void CopyStreamWithRotation(StreamReader reader, string ext) private void CopyStreamWithRotation(StreamReader reader, string ext)
{ {
StreamWriter writer = CreateWriter(new FileStream(BaseLogFileName + ext, FileMode.Append)); StreamWriter writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Append));
long fileLength = new FileInfo(BaseLogFileName + ext).Length; long fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
string? line; string? line;
while ((line = reader.ReadLine()) != null) while ((line = reader.ReadLine()) != null)
{ {
int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char); int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char);
if (fileLength + lengthToWrite > SizeTheshold) if (fileLength + lengthToWrite > this.SizeThreshold)
{ {
writer.Dispose(); writer.Dispose();
try try
{ {
for (int j = FilesToKeep; j >= 1; j--) for (int j = this.FilesToKeep; j >= 1; j--)
{ {
string dst = BaseLogFileName + "." + (j - 1) + ext; string dst = this.BaseLogFileName + "." + (j - 1) + ext;
string src = BaseLogFileName + "." + (j - 2) + ext; string src = this.BaseLogFileName + "." + (j - 2) + ext;
if (File.Exists(dst)) if (File.Exists(dst))
{
File.Delete(dst); File.Delete(dst);
if (File.Exists(src))
File.Move(src, dst);
} }
File.Move(BaseLogFileName + ext, BaseLogFileName + ".0" + ext); if (File.Exists(src))
{
File.Move(src, dst);
}
}
File.Move(this.BaseLogFileName + ext, this.BaseLogFileName + ".0" + ext);
} }
catch (IOException e) catch (IOException e)
{ {
EventLogger.LogEvent("Failed to roll log: " + e.Message); this.EventLogger.LogEvent("Failed to roll log: " + e.Message);
} }
// even if the log rotation fails, create a new one, or else // even if the log rotation fails, create a new one, or else
// we'll infinitely try to roll. // we'll infinitely try to roll.
writer = CreateWriter(new FileStream(BaseLogFileName + ext, FileMode.Create)); writer = this.CreateWriter(new FileStream(this.BaseLogFileName + ext, FileMode.Create));
fileLength = new FileInfo(BaseLogFileName + ext).Length; fileLength = new FileInfo(this.BaseLogFileName + ext).Length;
} }
writer.WriteLine(line); writer.WriteLine(line);
@ -306,26 +313,35 @@ namespace winsw
{ {
} }
public override void log(StreamReader outputReader, StreamReader errorReader) public override void Log(StreamReader outputReader, StreamReader errorReader)
{ {
if (!OutFileDisabled) if (!this.OutFileDisabled)
MoveFile(OutputLogFileName, OutputLogFileName + ".old"); {
this.MoveFile(this.OutputLogFileName, this.OutputLogFileName + ".old");
}
if (!ErrFileDisabled) if (!this.ErrFileDisabled)
MoveFile(ErrorLogFileName, ErrorLogFileName + ".old"); {
this.MoveFile(this.ErrorLogFileName, this.ErrorLogFileName + ".old");
}
base.log(outputReader, errorReader); base.Log(outputReader, errorReader);
} }
} }
public class RollingSizeTimeLogAppender : AbstractFileLogAppender public class RollingSizeTimeLogAppender : AbstractFileLogAppender
{ {
public static int BYTES_PER_KB = 1024; public const int BytesPerKB = 1024;
public int SizeTheshold { get; private set; }
public string FilePattern { get; private set; } public int SizeThreshold { get; }
public TimeSpan? AutoRollAtTime { get; private set; }
public int? ZipOlderThanNumDays { get; private set; } public string FilePattern { get; }
public string ZipDateFormat { get; private set; }
public TimeSpan? AutoRollAtTime { get; }
public int? ZipOlderThanNumDays { get; }
public string ZipDateFormat { get; }
public RollingSizeTimeLogAppender( public RollingSizeTimeLogAppender(
string logDirectory, string logDirectory,
@ -341,21 +357,21 @@ namespace winsw
string zipdateformat) string zipdateformat)
: base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern) : base(logDirectory, baseName, outFileDisabled, errFileDisabled, outFilePattern, errFilePattern)
{ {
SizeTheshold = sizeThreshold; this.SizeThreshold = sizeThreshold;
FilePattern = filePattern; this.FilePattern = filePattern;
AutoRollAtTime = autoRollAtTime; this.AutoRollAtTime = autoRollAtTime;
ZipOlderThanNumDays = zipolderthannumdays; this.ZipOlderThanNumDays = zipolderthannumdays;
ZipDateFormat = zipdateformat; this.ZipDateFormat = zipdateformat;
} }
protected override void LogOutput(StreamReader outputReader) protected override void LogOutput(StreamReader outputReader)
{ {
new Thread(() => CopyStreamWithRotation(outputReader, OutFilePattern)).Start(); new Thread(() => this.CopyStreamWithRotation(outputReader, this.OutFilePattern)).Start();
} }
protected override void LogError(StreamReader errorReader) protected override void LogError(StreamReader errorReader)
{ {
new Thread(() => CopyStreamWithRotation(errorReader, ErrFilePattern)).Start(); new Thread(() => this.CopyStreamWithRotation(errorReader, this.ErrFilePattern)).Start();
} }
private void CopyStreamWithRotation(StreamReader reader, string extension) private void CopyStreamWithRotation(StreamReader reader, string extension)
@ -363,18 +379,18 @@ namespace winsw
// lock required as the timer thread and the thread that will write to the stream could try and access the file stream at the same time // lock required as the timer thread and the thread that will write to the stream could try and access the file stream at the same time
var fileLock = new object(); var fileLock = new object();
var baseDirectory = Path.GetDirectoryName(BaseLogFileName)!; var baseDirectory = Path.GetDirectoryName(this.BaseLogFileName)!;
var baseFileName = Path.GetFileName(BaseLogFileName); var baseFileName = Path.GetFileName(this.BaseLogFileName);
var logFile = BaseLogFileName + extension; var logFile = this.BaseLogFileName + extension;
var writer = CreateWriter(new FileStream(logFile, FileMode.Append)); var writer = this.CreateWriter(new FileStream(logFile, FileMode.Append));
var fileLength = new FileInfo(logFile).Length; var fileLength = new FileInfo(logFile).Length;
// We auto roll at time is configured then we need to create a timer and wait until time is elasped and roll the file over // We auto roll at time is configured then we need to create a timer and wait until time is elasped and roll the file over
if (AutoRollAtTime is TimeSpan autoRollAtTime) if (this.AutoRollAtTime is TimeSpan autoRollAtTime)
{ {
// Run at start // Run at start
var tickTime = SetupRollTimer(autoRollAtTime); var tickTime = this.SetupRollTimer(autoRollAtTime);
var timer = new System.Timers.Timer(tickTime); var timer = new System.Timers.Timer(tickTime);
timer.Elapsed += (s, e) => timer.Elapsed += (s, e) =>
{ {
@ -386,25 +402,25 @@ namespace winsw
writer.Dispose(); writer.Dispose();
var now = DateTime.Now.AddDays(-1); var now = DateTime.Now.AddDays(-1);
var nextFileNumber = GetNextFileNumber(extension, baseDirectory, baseFileName, now); var nextFileNumber = this.GetNextFileNumber(extension, baseDirectory, baseFileName, now);
var nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(FilePattern), nextFileNumber, extension)); var nextFileName = Path.Combine(baseDirectory, string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension));
File.Move(logFile, nextFileName); File.Move(logFile, nextFileName);
writer = CreateWriter(new FileStream(logFile, FileMode.Create)); writer = this.CreateWriter(new FileStream(logFile, FileMode.Create));
fileLength = new FileInfo(logFile).Length; fileLength = new FileInfo(logFile).Length;
} }
// Next day so check if file can be zipped // Next day so check if file can be zipped
ZipFiles(baseDirectory, extension, baseFileName); this.ZipFiles(baseDirectory, extension, baseFileName);
} }
catch (Exception ex) catch (Exception ex)
{ {
EventLogger.LogEvent($"Failed to to trigger auto roll at time event due to: {ex.Message}"); this.EventLogger.LogEvent($"Failed to to trigger auto roll at time event due to: {ex.Message}");
} }
finally finally
{ {
// Recalculate the next interval // Recalculate the next interval
timer.Interval = SetupRollTimer(autoRollAtTime); timer.Interval = this.SetupRollTimer(autoRollAtTime);
timer.Start(); timer.Start();
} }
}; };
@ -417,26 +433,26 @@ namespace winsw
lock (fileLock) lock (fileLock)
{ {
int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char); int lengthToWrite = (line.Length + Environment.NewLine.Length) * sizeof(char);
if (fileLength + lengthToWrite > SizeTheshold) if (fileLength + lengthToWrite > this.SizeThreshold)
{ {
try try
{ {
// roll file // roll file
var now = DateTime.Now; var now = DateTime.Now;
var nextFileNumber = GetNextFileNumber(extension, baseDirectory, baseFileName, now); var nextFileNumber = this.GetNextFileNumber(extension, baseDirectory, baseFileName, now);
var nextFileName = var nextFileName = Path.Combine(
Path.Combine(baseDirectory, baseDirectory,
string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(FilePattern), nextFileNumber, extension)); string.Format("{0}.{1}.#{2:D4}{3}", baseFileName, now.ToString(this.FilePattern), nextFileNumber, extension));
File.Move(logFile, nextFileName); File.Move(logFile, nextFileName);
// even if the log rotation fails, create a new one, or else // even if the log rotation fails, create a new one, or else
// we'll infinitely try to roll. // we'll infinitely try to roll.
writer = CreateWriter(new FileStream(logFile, FileMode.Create)); writer = this.CreateWriter(new FileStream(logFile, FileMode.Create));
fileLength = new FileInfo(logFile).Length; fileLength = new FileInfo(logFile).Length;
} }
catch (Exception e) catch (Exception e)
{ {
EventLogger.LogEvent($"Failed to roll size time log: {e.Message}"); this.EventLogger.LogEvent($"Failed to roll size time log: {e.Message}");
} }
} }
@ -451,28 +467,32 @@ namespace winsw
private void ZipFiles(string directory, string fileExtension, string zipFileBaseName) private void ZipFiles(string directory, string fileExtension, string zipFileBaseName)
{ {
if (ZipOlderThanNumDays is null || ZipOlderThanNumDays <= 0) if (this.ZipOlderThanNumDays is null || this.ZipOlderThanNumDays <= 0)
{
return; return;
}
try try
{ {
foreach (string path in Directory.GetFiles(directory, "*" + fileExtension)) foreach (string path in Directory.GetFiles(directory, "*" + fileExtension))
{ {
var fileInfo = new FileInfo(path); var fileInfo = new FileInfo(path);
if (fileInfo.LastWriteTimeUtc >= DateTime.UtcNow.AddDays(-ZipOlderThanNumDays.Value)) if (fileInfo.LastWriteTimeUtc >= DateTime.UtcNow.AddDays(-this.ZipOlderThanNumDays.Value))
{
continue; continue;
}
string sourceFileName = Path.GetFileName(path); string sourceFileName = Path.GetFileName(path);
string zipFilePattern = fileInfo.LastAccessTimeUtc.ToString(ZipDateFormat); string zipFilePattern = fileInfo.LastAccessTimeUtc.ToString(this.ZipDateFormat);
string zipFilePath = Path.Combine(directory, $"{zipFileBaseName}.{zipFilePattern}.zip"); string zipFilePath = Path.Combine(directory, $"{zipFileBaseName}.{zipFilePattern}.zip");
ZipOneFile(path, sourceFileName, zipFilePath); this.ZipOneFile(path, sourceFileName, zipFilePath);
File.Delete(path); File.Delete(path);
} }
} }
catch (Exception e) catch (Exception e)
{ {
EventLogger.LogEvent($"Failed to Zip files. Error {e.Message}"); this.EventLogger.LogEvent($"Failed to Zip files. Error {e.Message}");
} }
} }
@ -490,7 +510,7 @@ namespace winsw
} }
catch (Exception e) catch (Exception e)
{ {
EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}"); this.EventLogger.LogEvent($"Failed to Zip the File {sourceFilePath}. Error {e.Message}");
} }
finally finally
{ {
@ -510,7 +530,9 @@ namespace winsw
autoRollAtTime.Seconds, autoRollAtTime.Seconds,
0); 0);
if (nowTime > scheduledTime) if (nowTime > scheduledTime)
{
scheduledTime = scheduledTime.AddDays(1); scheduledTime = scheduledTime.AddDays(1);
}
double tickTime = (scheduledTime - DateTime.Now).TotalMilliseconds; double tickTime = (scheduledTime - DateTime.Now).TotalMilliseconds;
return tickTime; return tickTime;
@ -519,7 +541,7 @@ namespace winsw
private int GetNextFileNumber(string ext, string baseDirectory, string baseFileName, DateTime now) private int GetNextFileNumber(string ext, string baseDirectory, string baseFileName, DateTime now)
{ {
var nextFileNumber = 0; var nextFileNumber = 0;
var files = Directory.GetFiles(baseDirectory, string.Format("{0}.{1}.#*{2}", baseFileName, now.ToString(FilePattern), ext)); var files = Directory.GetFiles(baseDirectory, string.Format("{0}.{1}.#*{2}", baseFileName, now.ToString(this.FilePattern), ext));
if (files.Length == 0) if (files.Length == 0)
{ {
nextFileNumber = 1; nextFileNumber = 1;
@ -533,12 +555,13 @@ namespace winsw
var filenameOnly = Path.GetFileNameWithoutExtension(f); var filenameOnly = Path.GetFileNameWithoutExtension(f);
var hashIndex = filenameOnly.IndexOf('#'); var hashIndex = filenameOnly.IndexOf('#');
var lastNumberAsString = filenameOnly.Substring(hashIndex + 1, 4); var lastNumberAsString = filenameOnly.Substring(hashIndex + 1, 4);
// var lastNumberAsString = filenameOnly.Substring(filenameOnly.Length - 4, 4);
if (int.TryParse(lastNumberAsString, out int lastNumber)) if (int.TryParse(lastNumberAsString, out int lastNumber))
{ {
if (lastNumber > nextFileNumber) if (lastNumber > nextFileNumber)
{
nextFileNumber = lastNumber; nextFileNumber = lastNumber;
} }
}
else else
{ {
throw new IOException($"File {f} does not follow the pattern provided"); throw new IOException($"File {f} does not follow the pattern provided");
@ -551,7 +574,9 @@ namespace winsw
} }
if (nextFileNumber == 0) if (nextFileNumber == 0)
{
throw new IOException("Cannot roll the file because matching pattern not found"); throw new IOException("Cannot roll the file because matching pattern not found");
}
nextFileNumber++; nextFileNumber++;
} }

View File

@ -1,6 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
namespace winsw.Logging namespace WinSW.Logging
{ {
/// <summary> /// <summary>
/// Indicates that the class may reference the event log /// Indicates that the class may reference the event log
@ -11,6 +11,6 @@ namespace winsw.Logging
/// Locates Event Log for the service. /// Locates Event Log for the service.
/// </summary> /// </summary>
/// <returns>Event Log or null if it is not avilable</returns> /// <returns>Event Log or null if it is not avilable</returns>
EventLog? locate(); EventLog? Locate();
} }
} }

View File

@ -2,7 +2,7 @@
using log4net.Appender; using log4net.Appender;
using log4net.Core; using log4net.Core;
namespace winsw.Logging namespace WinSW.Logging
{ {
/// <summary> /// <summary>
/// Implementes service Event log appender for log4j. /// Implementes service Event log appender for log4j.
@ -11,18 +11,18 @@ namespace winsw.Logging
public class ServiceEventLogAppender : AppenderSkeleton public class ServiceEventLogAppender : AppenderSkeleton
{ {
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. #pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public IServiceEventLogProvider provider { get; set; } public IServiceEventLogProvider Provider { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. #pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
override protected void Append(LoggingEvent loggingEvent) protected override void Append(LoggingEvent loggingEvent)
{ {
EventLog? eventLog = provider.locate(); EventLog? eventLog = this.Provider.Locate();
// We write the event iff the provider is ready // We write the event iff the provider is ready
eventLog?.WriteEntry(loggingEvent.RenderedMessage, toEventLogEntryType(loggingEvent.Level)); eventLog?.WriteEntry(loggingEvent.RenderedMessage, ToEventLogEntryType(loggingEvent.Level));
} }
private static EventLogEntryType toEventLogEntryType(Level level) private static EventLogEntryType ToEventLogEntryType(Level level)
{ {
if (level.Value >= Level.Error.Value) if (level.Value >= Level.Error.Value)
{ {

View File

@ -1,6 +1,8 @@
using System.Runtime.InteropServices; #pragma warning disable SA1310 // Field names should not contain underscore
namespace winsw.Native using System.Runtime.InteropServices;
namespace WinSW.Native
{ {
internal static class ConsoleApis internal static class ConsoleApis
{ {

View File

@ -1,7 +1,9 @@
using System; #pragma warning disable SA1310 // Field names should not contain underscore
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace winsw.Native namespace WinSW.Native
{ {
internal static class CredentialApis internal static class CredentialApis
{ {

View File

@ -1,4 +1,6 @@
namespace winsw.Native #pragma warning disable SA1310 // Field names should not contain underscore
namespace WinSW.Native
{ {
internal static class Errors internal static class Errors
{ {

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace winsw.Native namespace WinSW.Native
{ {
internal static class HandleApis internal static class HandleApis
{ {

View File

@ -1,4 +1,4 @@
namespace winsw.Native namespace WinSW.Native
{ {
internal static class Libraries internal static class Libraries
{ {

View File

@ -1,8 +1,10 @@
using System; #pragma warning disable SA1310 // Field names should not contain underscore
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Principal; using System.Security.Principal;
namespace winsw.Native namespace WinSW.Native
{ {
internal static class ProcessApis internal static class ProcessApis
{ {
@ -46,12 +48,14 @@ namespace winsw.Native
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct PROCESS_BASIC_INFORMATION internal unsafe struct PROCESS_BASIC_INFORMATION
{ {
#pragma warning disable SA1306 // Field names should begin with lower-case letter
private readonly IntPtr Reserved1; private readonly IntPtr Reserved1;
private readonly IntPtr PebBaseAddress; private readonly IntPtr PebBaseAddress;
private readonly IntPtr Reserved2_1; private readonly IntPtr Reserved2_1;
private readonly IntPtr Reserved2_2; private readonly IntPtr Reserved2_2;
internal readonly IntPtr UniqueProcessId; internal readonly IntPtr UniqueProcessId;
internal readonly IntPtr InheritedFromUniqueProcessId; internal readonly IntPtr InheritedFromUniqueProcessId;
#pragma warning restore SA1306 // Field names should begin with lower-case letter
} }
internal struct PROCESS_INFORMATION internal struct PROCESS_INFORMATION

View File

@ -1,9 +1,9 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static winsw.Native.SecurityApis; using static WinSW.Native.SecurityApis;
namespace winsw.Native namespace WinSW.Native
{ {
internal static class Security internal static class Security
{ {

View File

@ -1,7 +1,9 @@
using System; #pragma warning disable SA1310 // Field names should not contain underscore
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace winsw.Native namespace WinSW.Native
{ {
internal static class SecurityApis internal static class SecurityApis
{ {

View File

@ -3,9 +3,9 @@ using System.ComponentModel;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.ServiceProcess; using System.ServiceProcess;
using System.Text; using System.Text;
using static winsw.Native.ServiceApis; using static WinSW.Native.ServiceApis;
namespace winsw.Native namespace WinSW.Native
{ {
public enum SC_ACTION_TYPE public enum SC_ACTION_TYPE
{ {
@ -198,7 +198,8 @@ namespace winsw.Native
{ {
fixed (SC_ACTION* actionsPtr = actions) fixed (SC_ACTION* actionsPtr = actions)
{ {
if (!ChangeServiceConfig2(this.handle, if (!ChangeServiceConfig2(
this.handle,
ServiceConfigInfoLevels.FAILURE_ACTIONS, ServiceConfigInfoLevels.FAILURE_ACTIONS,
new SERVICE_FAILURE_ACTIONS new SERVICE_FAILURE_ACTIONS
{ {

View File

@ -4,7 +4,7 @@ using System.Security.AccessControl;
using System.ServiceProcess; using System.ServiceProcess;
using System.Text; using System.Text;
namespace winsw.Native namespace WinSW.Native
{ {
internal static class ServiceApis internal static class ServiceApis
{ {

View File

@ -2,7 +2,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace winsw.Native namespace WinSW.Native
{ {
internal static class Throw internal static class Throw
{ {

View File

@ -3,10 +3,14 @@ namespace System.Diagnostics.CodeAnalysis
{ {
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary> /// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
internal sealed class AllowNullAttribute : Attribute { } internal sealed class AllowNullAttribute : Attribute
{
}
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary> /// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
internal sealed class MaybeNullAttribute : Attribute { } internal sealed class MaybeNullAttribute : Attribute
{
}
} }
#endif #endif

View File

@ -1,30 +1,26 @@
using System; using System;
// ReSharper disable InconsistentNaming namespace WinSW
namespace winsw
{ {
/** // This is largely borrowed from the logback Rolling Calendar.
* This is largely borrowed from the logback Rolling Calendar.
**/
public class PeriodicRollingCalendar public class PeriodicRollingCalendar
{ {
private readonly string _format; private readonly string format;
private readonly long _period; private readonly long period;
private DateTime _currentRoll; private DateTime currentRoll;
private DateTime _nextRoll; private DateTime nextRoll;
public PeriodicRollingCalendar(string format, long period) public PeriodicRollingCalendar(string format, long period)
{ {
_format = format; this.format = format;
_period = period; this.period = period;
_currentRoll = DateTime.Now; this.currentRoll = DateTime.Now;
} }
public void init() public void Init()
{ {
periodicityType = determinePeriodicityType(); this.Periodicity = this.DeterminePeriodicityType();
_nextRoll = nextTriggeringTime(_currentRoll, _period); this.nextRoll = this.NextTriggeringTime(this.currentRoll, this.period);
} }
public enum PeriodicityType public enum PeriodicityType
@ -37,23 +33,23 @@ namespace winsw
TOP_OF_DAY TOP_OF_DAY
} }
private static readonly PeriodicityType[] VALID_ORDERED_LIST = private static readonly PeriodicityType[] ValidOrderedList =
{ {
PeriodicityType.TOP_OF_MILLISECOND, PeriodicityType.TOP_OF_SECOND, PeriodicityType.TOP_OF_MINUTE, PeriodicityType.TOP_OF_HOUR, PeriodicityType.TOP_OF_DAY PeriodicityType.TOP_OF_MILLISECOND, PeriodicityType.TOP_OF_SECOND, PeriodicityType.TOP_OF_MINUTE, PeriodicityType.TOP_OF_HOUR, PeriodicityType.TOP_OF_DAY
}; };
private PeriodicityType determinePeriodicityType() private PeriodicityType DeterminePeriodicityType()
{ {
PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(_format, _period); PeriodicRollingCalendar periodicRollingCalendar = new PeriodicRollingCalendar(this.format, this.period);
DateTime epoch = new DateTime(1970, 1, 1); DateTime epoch = new DateTime(1970, 1, 1);
foreach (PeriodicityType i in VALID_ORDERED_LIST) foreach (PeriodicityType i in ValidOrderedList)
{ {
string r0 = epoch.ToString(_format); string r0 = epoch.ToString(this.format);
periodicRollingCalendar.periodicityType = i; periodicRollingCalendar.Periodicity = i;
DateTime next = periodicRollingCalendar.nextTriggeringTime(epoch, 1); DateTime next = periodicRollingCalendar.NextTriggeringTime(epoch, 1);
string r1 = next.ToString(_format); string r1 = next.ToString(this.format);
if (r0 != r1) if (r0 != r1)
{ {
@ -64,7 +60,7 @@ namespace winsw
return PeriodicityType.ERRONEOUS; return PeriodicityType.ERRONEOUS;
} }
private DateTime nextTriggeringTime(DateTime input, long increment) => periodicityType switch private DateTime NextTriggeringTime(DateTime input, long increment) => this.Periodicity switch
{ {
PeriodicityType.TOP_OF_MILLISECOND => PeriodicityType.TOP_OF_MILLISECOND =>
new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond) new DateTime(input.Year, input.Month, input.Day, input.Hour, input.Minute, input.Second, input.Millisecond)
@ -86,20 +82,20 @@ namespace winsw
new DateTime(input.Year, input.Month, input.Day) new DateTime(input.Year, input.Month, input.Day)
.AddDays(increment), .AddDays(increment),
_ => throw new Exception("invalid periodicity type: " + periodicityType), _ => throw new Exception("invalid periodicity type: " + this.Periodicity),
}; };
public PeriodicityType periodicityType { get; set; } public PeriodicityType Periodicity { get; set; }
public bool shouldRoll public bool ShouldRoll
{ {
get get
{ {
DateTime now = DateTime.Now; DateTime now = DateTime.Now;
if (now > _nextRoll) if (now > this.nextRoll)
{ {
_currentRoll = now; this.currentRoll = now;
_nextRoll = nextTriggeringTime(now, _period); this.nextRoll = this.NextTriggeringTime(now, this.period);
return true; return true;
} }
@ -107,6 +103,6 @@ namespace winsw
} }
} }
public string format => _currentRoll.ToString(_format); public string Format => this.currentRoll.ToString(this.format);
} }
} }

View File

@ -5,18 +5,17 @@ using System.IO;
using System.ServiceProcess; using System.ServiceProcess;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using winsw.Configuration; using WinSW.Configuration;
using winsw.Native; using WinSW.Native;
using winsw.Util; using WinSW.Util;
namespace winsw namespace WinSW
{ {
/// <summary> /// <summary>
/// In-memory representation of the configuration file. /// In-memory representation of the configuration file.
/// </summary> /// </summary>
public class ServiceDescriptor : IWinSWConfiguration public class ServiceDescriptor : IWinSWConfiguration
{ {
// ReSharper disable once InconsistentNaming
protected readonly XmlDocument dom = new XmlDocument(); protected readonly XmlDocument dom = new XmlDocument();
private readonly Dictionary<string, string> environmentVariables; private readonly Dictionary<string, string> environmentVariables;
@ -45,29 +44,35 @@ namespace winsw
// find co-located configuration xml. We search up to the ancestor directories to simplify debugging, // find co-located configuration xml. We search up to the ancestor directories to simplify debugging,
// as well as trimming off ".vshost" suffix (which is used during debugging) // as well as trimming off ".vshost" suffix (which is used during debugging)
// Get the first parent to go into the recursive loop // Get the first parent to go into the recursive loop
string p = ExecutablePath; string p = this.ExecutablePath;
string baseName = Path.GetFileNameWithoutExtension(p); string baseName = Path.GetFileNameWithoutExtension(p);
if (baseName.EndsWith(".vshost")) if (baseName.EndsWith(".vshost"))
{
baseName = baseName.Substring(0, baseName.Length - 7); baseName = baseName.Substring(0, baseName.Length - 7);
}
DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(p)); DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(p));
while (true) while (true)
{ {
if (File.Exists(Path.Combine(d.FullName, baseName + ".xml"))) if (File.Exists(Path.Combine(d.FullName, baseName + ".xml")))
{
break; break;
}
if (d.Parent is null) if (d.Parent is null)
{
throw new FileNotFoundException("Unable to locate " + baseName + ".xml file within executable directory or any parents"); throw new FileNotFoundException("Unable to locate " + baseName + ".xml file within executable directory or any parents");
}
d = d.Parent; d = d.Parent;
} }
BaseName = baseName; this.BaseName = baseName;
BasePath = Path.Combine(d.FullName, BaseName); this.BasePath = Path.Combine(d.FullName, this.BaseName);
try try
{ {
dom.Load(BasePath + ".xml"); this.dom.Load(this.BasePath + ".xml");
} }
catch (XmlException e) catch (XmlException e)
{ {
@ -78,13 +83,13 @@ namespace winsw
Environment.SetEnvironmentVariable("BASE", d.FullName); Environment.SetEnvironmentVariable("BASE", d.FullName);
// ditto for ID // ditto for ID
Environment.SetEnvironmentVariable("SERVICE_ID", Id); Environment.SetEnvironmentVariable("SERVICE_ID", this.Id);
// New name // New name
Environment.SetEnvironmentVariable(WinSWSystem.ENVVAR_NAME_EXECUTABLE_PATH, ExecutablePath); Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameExecutablePath, this.ExecutablePath);
// Also inject system environment variables // Also inject system environment variables
Environment.SetEnvironmentVariable(WinSWSystem.ENVVAR_NAME_SERVICE_ID, Id); Environment.SetEnvironmentVariable(WinSWSystem.EnvVarNameServiceId, this.Id);
this.environmentVariables = this.LoadEnvironmentVariables(); this.environmentVariables = this.LoadEnvironmentVariables();
} }
@ -101,8 +106,7 @@ namespace winsw
this.environmentVariables = this.LoadEnvironmentVariables(); this.environmentVariables = this.LoadEnvironmentVariables();
} }
// ReSharper disable once InconsistentNaming public static ServiceDescriptor FromXml(string xml)
public static ServiceDescriptor FromXML(string xml)
{ {
var dom = new XmlDocument(); var dom = new XmlDocument();
dom.LoadXml(xml); dom.LoadXml(xml);
@ -111,21 +115,23 @@ namespace winsw
private string SingleElement(string tagName) private string SingleElement(string tagName)
{ {
return SingleElement(tagName, false)!; return this.SingleElement(tagName, false)!;
} }
private string? SingleElement(string tagName, bool optional) private string? SingleElement(string tagName, bool optional)
{ {
XmlNode? n = dom.SelectSingleNode("//" + tagName); XmlNode? n = this.dom.SelectSingleNode("//" + tagName);
if (n is null && !optional) if (n is null && !optional)
{
throw new InvalidDataException("<" + tagName + "> is missing in configuration XML"); throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
}
return n is null ? null : Environment.ExpandEnvironmentVariables(n.InnerText); return n is null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
} }
private bool SingleBoolElement(string tagName, bool defaultValue) private bool SingleBoolElement(string tagName, bool defaultValue)
{ {
XmlNode? e = dom.SelectSingleNode("//" + tagName); XmlNode? e = this.dom.SelectSingleNode("//" + tagName);
return e is null ? defaultValue : bool.Parse(e.InnerText); return e is null ? defaultValue : bool.Parse(e.InnerText);
} }
@ -139,8 +145,8 @@ namespace winsw
private TimeSpan SingleTimeSpanElement(XmlNode parent, string tagName, TimeSpan defaultValue) private TimeSpan SingleTimeSpanElement(XmlNode parent, string tagName, TimeSpan defaultValue)
{ {
string? value = SingleElement(tagName, true); string? value = this.SingleElement(tagName, true);
return value is null ? defaultValue : ParseTimeSpan(value); return value is null ? defaultValue : this.ParseTimeSpan(value);
} }
private TimeSpan ParseTimeSpan(string v) private TimeSpan ParseTimeSpan(string v)
@ -175,14 +181,14 @@ namespace winsw
/// <summary> /// <summary>
/// Path to the executable. /// Path to the executable.
/// </summary> /// </summary>
public string Executable => SingleElement("executable"); public string Executable => this.SingleElement("executable");
public bool HideWindow => SingleBoolElement("hidewindow", Defaults.HideWindow); public bool HideWindow => this.SingleBoolElement("hidewindow", Defaults.HideWindow);
/// <summary> /// <summary>
/// Optionally specify a different Path to an executable to shutdown the service. /// Optionally specify a different Path to an executable to shutdown the service.
/// </summary> /// </summary>
public string? StopExecutable => SingleElement("stopexecutable", true); public string? StopExecutable => this.SingleElement("stopexecutable", true);
/// <summary> /// <summary>
/// <c>arguments</c> or multiple optional <c>argument</c> elements which overrule the arguments element. /// <c>arguments</c> or multiple optional <c>argument</c> elements which overrule the arguments element.
@ -191,14 +197,14 @@ namespace winsw
{ {
get get
{ {
string? arguments = AppendTags("argument", null); string? arguments = this.AppendTags("argument", null);
if (!(arguments is null)) if (!(arguments is null))
{ {
return arguments; return arguments;
} }
XmlNode? argumentsNode = dom.SelectSingleNode("//arguments"); XmlNode? argumentsNode = this.dom.SelectSingleNode("//arguments");
return argumentsNode is null ? Defaults.Arguments : Environment.ExpandEnvironmentVariables(argumentsNode.InnerText); return argumentsNode is null ? Defaults.Arguments : Environment.ExpandEnvironmentVariables(argumentsNode.InnerText);
} }
@ -211,14 +217,14 @@ namespace winsw
{ {
get get
{ {
string? startArguments = AppendTags("startargument", null); string? startArguments = this.AppendTags("startargument", null);
if (!(startArguments is null)) if (!(startArguments is null))
{ {
return startArguments; return startArguments;
} }
XmlNode? startArgumentsNode = dom.SelectSingleNode("//startarguments"); XmlNode? startArgumentsNode = this.dom.SelectSingleNode("//startarguments");
return startArgumentsNode is null ? null : Environment.ExpandEnvironmentVariables(startArgumentsNode.InnerText); return startArgumentsNode is null ? null : Environment.ExpandEnvironmentVariables(startArgumentsNode.InnerText);
} }
@ -231,14 +237,14 @@ namespace winsw
{ {
get get
{ {
string? stopArguments = AppendTags("stopargument", null); string? stopArguments = this.AppendTags("stopargument", null);
if (!(stopArguments is null)) if (!(stopArguments is null))
{ {
return stopArguments; return stopArguments;
} }
XmlNode? stopArgumentsNode = dom.SelectSingleNode("//stoparguments"); XmlNode? stopArgumentsNode = this.dom.SelectSingleNode("//stoparguments");
return stopArgumentsNode is null ? null : Environment.ExpandEnvironmentVariables(stopArgumentsNode.InnerText); return stopArgumentsNode is null ? null : Environment.ExpandEnvironmentVariables(stopArgumentsNode.InnerText);
} }
@ -248,7 +254,7 @@ namespace winsw
{ {
get get
{ {
var wd = SingleElement("workingdirectory", true); var wd = this.SingleElement("workingdirectory", true);
return string.IsNullOrEmpty(wd) ? Defaults.WorkingDirectory : wd!; return string.IsNullOrEmpty(wd) ? Defaults.WorkingDirectory : wd!;
} }
} }
@ -257,7 +263,7 @@ namespace winsw
{ {
get get
{ {
XmlNode? argumentNode = ExtensionsConfiguration; XmlNode? argumentNode = this.ExtensionsConfiguration;
XmlNodeList? extensions = argumentNode?.SelectNodes("extension"); XmlNodeList? extensions = argumentNode?.SelectNodes("extension");
if (extensions is null) if (extensions is null)
{ {
@ -274,7 +280,7 @@ namespace winsw
} }
} }
public XmlNode? ExtensionsConfiguration => dom.SelectSingleNode("//extensions"); public XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions");
/// <summary> /// <summary>
/// Combines the contents of all the elements of the given name, /// Combines the contents of all the elements of the given name,
@ -282,7 +288,7 @@ namespace winsw
/// </summary> /// </summary>
private string? AppendTags(string tagName, string? defaultValue = null) private string? AppendTags(string tagName, string? defaultValue = null)
{ {
XmlNode? argumentNode = dom.SelectSingleNode("//" + tagName); XmlNode? argumentNode = this.dom.SelectSingleNode("//" + tagName);
if (argumentNode is null) if (argumentNode is null)
{ {
return defaultValue; return defaultValue;
@ -290,7 +296,7 @@ namespace winsw
StringBuilder arguments = new StringBuilder(); StringBuilder arguments = new StringBuilder();
XmlNodeList argumentNodeList = dom.SelectNodes("//" + tagName); XmlNodeList argumentNodeList = this.dom.SelectNodes("//" + tagName);
for (int i = 0; i < argumentNodeList.Count; i++) for (int i = 0; i < argumentNodeList.Count; i++)
{ {
arguments.Append(' '); arguments.Append(' ');
@ -325,7 +331,7 @@ namespace winsw
{ {
get get
{ {
XmlNode? loggingNode = dom.SelectSingleNode("//logpath"); XmlNode? loggingNode = this.dom.SelectSingleNode("//logpath");
return loggingNode is null return loggingNode is null
? Defaults.LogDirectory ? Defaults.LogDirectory
@ -340,7 +346,7 @@ namespace winsw
string? mode = null; string? mode = null;
// first, backward compatibility with older configuration // first, backward compatibility with older configuration
XmlElement? e = (XmlElement?)dom.SelectSingleNode("//logmode"); XmlElement? e = (XmlElement?)this.dom.SelectSingleNode("//logmode");
if (e != null) if (e != null)
{ {
mode = e.InnerText; mode = e.InnerText;
@ -348,10 +354,12 @@ namespace winsw
else else
{ {
// this is more modern way, to support nested elements as configuration // this is more modern way, to support nested elements as configuration
e = (XmlElement?)dom.SelectSingleNode("//log"); e = (XmlElement?)this.dom.SelectSingleNode("//log");
if (e != null) if (e != null)
{
mode = e.GetAttribute("mode"); mode = e.GetAttribute("mode");
} }
}
return mode ?? Defaults.LogMode; return mode ?? Defaults.LogMode;
} }
@ -361,21 +369,21 @@ namespace winsw
{ {
get get
{ {
XmlNode? loggingName = dom.SelectSingleNode("//logname"); XmlNode? loggingName = this.dom.SelectSingleNode("//logname");
return loggingName is null ? BaseName : Environment.ExpandEnvironmentVariables(loggingName.InnerText); return loggingName is null ? this.BaseName : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
} }
} }
public bool OutFileDisabled => SingleBoolElement("outfiledisabled", Defaults.OutFileDisabled); public bool OutFileDisabled => this.SingleBoolElement("outfiledisabled", Defaults.OutFileDisabled);
public bool ErrFileDisabled => SingleBoolElement("errfiledisabled", Defaults.ErrFileDisabled); public bool ErrFileDisabled => this.SingleBoolElement("errfiledisabled", Defaults.ErrFileDisabled);
public string OutFilePattern public string OutFilePattern
{ {
get get
{ {
XmlNode? loggingName = dom.SelectSingleNode("//outfilepattern"); XmlNode? loggingName = this.dom.SelectSingleNode("//outfilepattern");
return loggingName is null ? Defaults.OutFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText); return loggingName is null ? Defaults.OutFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
} }
@ -385,7 +393,7 @@ namespace winsw
{ {
get get
{ {
XmlNode? loggingName = dom.SelectSingleNode("//errfilepattern"); XmlNode? loggingName = this.dom.SelectSingleNode("//errfilepattern");
return loggingName is null ? Defaults.ErrFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText); return loggingName is null ? Defaults.ErrFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
} }
@ -395,25 +403,25 @@ namespace winsw
{ {
get get
{ {
XmlElement? e = (XmlElement?)dom.SelectSingleNode("//logmode"); XmlElement? e = (XmlElement?)this.dom.SelectSingleNode("//logmode");
// this is more modern way, to support nested elements as configuration // this is more modern way, to support nested elements as configuration
e ??= (XmlElement?)dom.SelectSingleNode("//log")!; // WARNING: NRE e ??= (XmlElement?)this.dom.SelectSingleNode("//log")!; // WARNING: NRE
int sizeThreshold; int sizeThreshold;
switch (LogMode) switch (this.LogMode)
{ {
case "rotate": case "rotate":
return new SizeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern); return new SizeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "none": case "none":
return new IgnoreLogAppender(); return new IgnoreLogAppender();
case "reset": case "reset":
return new ResetLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern); return new ResetLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll": case "roll":
return new RollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern); return new RollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll-by-time": case "roll-by-time":
XmlNode? patternNode = e.SelectSingleNode("pattern"); XmlNode? patternNode = e.SelectSingleNode("pattern");
@ -423,19 +431,19 @@ namespace winsw
} }
var pattern = patternNode.InnerText; var pattern = patternNode.InnerText;
int period = SingleIntElement(e, "period", 1); int period = this.SingleIntElement(e, "period", 1);
return new TimeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, pattern, period); return new TimeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, pattern, period);
case "roll-by-size": case "roll-by-size":
sizeThreshold = SingleIntElement(e, "sizeThreshold", 10 * 1024) * SizeBasedRollingLogAppender.BYTES_PER_KB; sizeThreshold = this.SingleIntElement(e, "sizeThreshold", 10 * 1024) * SizeBasedRollingLogAppender.BytesPerKB;
int keepFiles = SingleIntElement(e, "keepFiles", SizeBasedRollingLogAppender.DEFAULT_FILES_TO_KEEP); int keepFiles = this.SingleIntElement(e, "keepFiles", SizeBasedRollingLogAppender.DefaultFilesToKeep);
return new SizeBasedRollingLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, sizeThreshold, keepFiles); return new SizeBasedRollingLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, sizeThreshold, keepFiles);
case "append": case "append":
return new DefaultLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern); return new DefaultLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern);
case "roll-by-size-time": case "roll-by-size-time":
sizeThreshold = SingleIntElement(e, "sizeThreshold", 10 * 1024) * RollingSizeTimeLogAppender.BYTES_PER_KB; sizeThreshold = this.SingleIntElement(e, "sizeThreshold", 10 * 1024) * RollingSizeTimeLogAppender.BytesPerKB;
XmlNode? filePatternNode = e.SelectSingleNode("pattern"); XmlNode? filePatternNode = e.SelectSingleNode("pattern");
if (filePatternNode is null) if (filePatternNode is null)
{ {
@ -448,7 +456,9 @@ namespace winsw
{ {
// validate it // validate it
if (!TimeSpan.TryParse(autoRollAtTimeNode.InnerText, out TimeSpan autoRollAtTimeValue)) if (!TimeSpan.TryParse(autoRollAtTimeNode.InnerText, out TimeSpan autoRollAtTimeValue))
{
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but autoRollAtTime does not match the TimeSpan format HH:mm:ss found in configuration XML."); throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but autoRollAtTime does not match the TimeSpan format HH:mm:ss found in configuration XML.");
}
autoRollAtTime = autoRollAtTimeValue; autoRollAtTime = autoRollAtTimeValue;
} }
@ -459,7 +469,9 @@ namespace winsw
{ {
// validate it // validate it
if (!int.TryParse(zipolderthannumdaysNode.InnerText, out int zipolderthannumdaysValue)) if (!int.TryParse(zipolderthannumdaysNode.InnerText, out int zipolderthannumdaysValue))
{
throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but zipOlderThanNumDays does not match the int format found in configuration XML."); throw new InvalidDataException("Roll-Size-Time Based rolling policy is specified but zipOlderThanNumDays does not match the int format found in configuration XML.");
}
zipolderthannumdays = zipolderthannumdaysValue; zipolderthannumdays = zipolderthannumdaysValue;
} }
@ -467,10 +479,10 @@ namespace winsw
XmlNode? zipdateformatNode = e.SelectSingleNode("zipDateFormat"); XmlNode? zipdateformatNode = e.SelectSingleNode("zipDateFormat");
string zipdateformat = zipdateformatNode is null ? "yyyyMM" : zipdateformatNode.InnerText; string zipdateformat = zipdateformatNode is null ? "yyyyMM" : zipdateformatNode.InnerText;
return new RollingSizeTimeLogAppender(LogDirectory, LogName, OutFileDisabled, ErrFileDisabled, OutFilePattern, ErrFilePattern, sizeThreshold, filePatternNode.InnerText, autoRollAtTime, zipolderthannumdays, zipdateformat); return new RollingSizeTimeLogAppender(this.LogDirectory, this.LogName, this.OutFileDisabled, this.ErrFileDisabled, this.OutFilePattern, this.ErrFilePattern, sizeThreshold, filePatternNode.InnerText, autoRollAtTime, zipolderthannumdays, zipdateformat);
default: default:
throw new InvalidDataException("Undefined logging mode: " + LogMode); throw new InvalidDataException("Undefined logging mode: " + this.LogMode);
} }
} }
} }
@ -482,7 +494,7 @@ namespace winsw
{ {
get get
{ {
XmlNodeList? nodeList = dom.SelectNodes("//depend"); XmlNodeList? nodeList = this.dom.SelectNodes("//depend");
if (nodeList is null) if (nodeList is null)
{ {
return Defaults.ServiceDependencies; return Defaults.ServiceDependencies;
@ -498,11 +510,11 @@ namespace winsw
} }
} }
public string Id => SingleElement("id"); public string Id => this.SingleElement("id");
public string Caption => SingleElement("name"); public string Caption => this.SingleElement("name");
public string Description => SingleElement("description"); public string Description => this.SingleElement("description");
/// <summary> /// <summary>
/// Start mode of the Service /// Start mode of the Service
@ -511,9 +523,11 @@ namespace winsw
{ {
get get
{ {
string? p = SingleElement("startmode", true); string? p = this.SingleElement("startmode", true);
if (p is null) if (p is null)
{
return Defaults.StartMode; return Defaults.StartMode;
}
try try
{ {
@ -536,32 +550,32 @@ namespace winsw
/// True if the service should be installed with the DelayedAutoStart flag. /// True if the service should be installed with the DelayedAutoStart flag.
/// This setting will be applyed only during the install command and only when the Automatic start mode is configured. /// This setting will be applyed only during the install command and only when the Automatic start mode is configured.
/// </summary> /// </summary>
public bool DelayedAutoStart => dom.SelectSingleNode("//delayedAutoStart") != null; public bool DelayedAutoStart => this.dom.SelectSingleNode("//delayedAutoStart") != null;
/// <summary> /// <summary>
/// True if the service should beep when finished on shutdown. /// True if the service should beep when finished on shutdown.
/// This doesn't work on some OSes. See http://msdn.microsoft.com/en-us/library/ms679277%28VS.85%29.aspx /// This doesn't work on some OSes. See http://msdn.microsoft.com/en-us/library/ms679277%28VS.85%29.aspx
/// </summary> /// </summary>
public bool BeepOnShutdown => dom.SelectSingleNode("//beeponshutdown") != null; public bool BeepOnShutdown => this.dom.SelectSingleNode("//beeponshutdown") != null;
/// <summary> /// <summary>
/// The estimated time required for a pending stop operation (default 15 secs). /// The estimated time required for a pending stop operation (default 15 secs).
/// Before the specified amount of time has elapsed, the service should make its next call to the SetServiceStatus function /// Before the specified amount of time has elapsed, the service should make its next call to the SetServiceStatus function
/// with either an incremented checkPoint value or a change in currentState. (see http://msdn.microsoft.com/en-us/library/ms685996.aspx) /// with either an incremented checkPoint value or a change in currentState. (see http://msdn.microsoft.com/en-us/library/ms685996.aspx)
/// </summary> /// </summary>
public TimeSpan WaitHint => SingleTimeSpanElement(dom, "waithint", Defaults.WaitHint); public TimeSpan WaitHint => this.SingleTimeSpanElement(this.dom, "waithint", Defaults.WaitHint);
/// <summary> /// <summary>
/// The time before the service should make its next call to the SetServiceStatus function /// The time before the service should make its next call to the SetServiceStatus function
/// with an incremented checkPoint value (default 1 sec). /// with an incremented checkPoint value (default 1 sec).
/// Do not wait longer than the wait hint. A good interval is one-tenth of the wait hint but not less than 1 second and not more than 10 seconds. /// Do not wait longer than the wait hint. A good interval is one-tenth of the wait hint but not less than 1 second and not more than 10 seconds.
/// </summary> /// </summary>
public TimeSpan SleepTime => SingleTimeSpanElement(dom, "sleeptime", Defaults.SleepTime); public TimeSpan SleepTime => this.SingleTimeSpanElement(this.dom, "sleeptime", Defaults.SleepTime);
/// <summary> /// <summary>
/// True if the service can interact with the desktop. /// True if the service can interact with the desktop.
/// </summary> /// </summary>
public bool Interactive => dom.SelectSingleNode("//interactive") != null; public bool Interactive => this.dom.SelectSingleNode("//interactive") != null;
/// <summary> /// <summary>
/// Environment variable overrides /// Environment variable overrides
@ -576,7 +590,7 @@ namespace winsw
{ {
get get
{ {
XmlNodeList? nodeList = dom.SelectNodes("//download"); XmlNodeList? nodeList = this.dom.SelectNodes("//download");
if (nodeList is null) if (nodeList is null)
{ {
return Defaults.Downloads; return Defaults.Downloads;
@ -599,7 +613,7 @@ namespace winsw
{ {
get get
{ {
XmlNodeList? childNodes = dom.SelectNodes("//onfailure"); XmlNodeList? childNodes = this.dom.SelectNodes("//onfailure");
if (childNodes is null) if (childNodes is null)
{ {
return new SC_ACTION[0]; return new SC_ACTION[0];
@ -618,18 +632,18 @@ namespace winsw
_ => throw new Exception("Invalid failure action: " + action) _ => throw new Exception("Invalid failure action: " + action)
}; };
XmlAttribute? delay = node.Attributes["delay"]; XmlAttribute? delay = node.Attributes["delay"];
result[i] = new SC_ACTION(type, delay != null ? ParseTimeSpan(delay.Value) : TimeSpan.Zero); result[i] = new SC_ACTION(type, delay != null ? this.ParseTimeSpan(delay.Value) : TimeSpan.Zero);
} }
return result; return result;
} }
} }
public TimeSpan ResetFailureAfter => SingleTimeSpanElement(dom, "resetfailure", Defaults.ResetFailureAfter); public TimeSpan ResetFailureAfter => this.SingleTimeSpanElement(this.dom, "resetfailure", Defaults.ResetFailureAfter);
protected string? GetServiceAccountPart(string subNodeName) protected string? GetServiceAccountPart(string subNodeName)
{ {
XmlNode? node = dom.SelectSingleNode("//serviceaccount"); XmlNode? node = this.dom.SelectSingleNode("//serviceaccount");
if (node != null) if (node != null)
{ {
@ -643,13 +657,13 @@ namespace winsw
return null; return null;
} }
public string? ServiceAccountPrompt => GetServiceAccountPart("prompt")?.ToLowerInvariant(); public string? ServiceAccountPrompt => this.GetServiceAccountPart("prompt")?.ToLowerInvariant();
protected string? AllowServiceLogon => GetServiceAccountPart("allowservicelogon"); protected string? AllowServiceLogon => this.GetServiceAccountPart("allowservicelogon");
public string? ServiceAccountPassword => GetServiceAccountPart("password"); public string? ServiceAccountPassword => this.GetServiceAccountPart("password");
public string? ServiceAccountUserName => GetServiceAccountPart("username"); public string? ServiceAccountUserName => this.GetServiceAccountPart("username");
public bool HasServiceAccount() public bool HasServiceAccount()
{ {
@ -660,9 +674,9 @@ namespace winsw
{ {
get get
{ {
if (AllowServiceLogon != null) if (this.AllowServiceLogon != null)
{ {
if (bool.TryParse(AllowServiceLogon, out bool parsedvalue)) if (bool.TryParse(this.AllowServiceLogon, out bool parsedvalue))
{ {
return parsedvalue; return parsedvalue;
} }
@ -675,7 +689,7 @@ namespace winsw
/// <summary> /// <summary>
/// Time to wait for the service to gracefully shutdown the executable before we forcibly kill it /// Time to wait for the service to gracefully shutdown the executable before we forcibly kill it
/// </summary> /// </summary>
public TimeSpan StopTimeout => SingleTimeSpanElement(dom, "stoptimeout", Defaults.StopTimeout); public TimeSpan StopTimeout => this.SingleTimeSpanElement(this.dom, "stoptimeout", Defaults.StopTimeout);
/// <summary> /// <summary>
/// Desired process priority or null if not specified. /// Desired process priority or null if not specified.
@ -684,19 +698,21 @@ namespace winsw
{ {
get get
{ {
string? p = SingleElement("priority", true); string? p = this.SingleElement("priority", true);
if (p is null) if (p is null)
{
return Defaults.Priority; return Defaults.Priority;
}
return (ProcessPriorityClass)Enum.Parse(typeof(ProcessPriorityClass), p, true); return (ProcessPriorityClass)Enum.Parse(typeof(ProcessPriorityClass), p, true);
} }
} }
public string? SecurityDescriptor => SingleElement("securityDescriptor", true); public string? SecurityDescriptor => this.SingleElement("securityDescriptor", true);
private Dictionary<string, string> LoadEnvironmentVariables() private Dictionary<string, string> LoadEnvironmentVariables()
{ {
XmlNodeList nodeList = dom.SelectNodes("//env"); XmlNodeList nodeList = this.dom.SelectNodes("//env");
Dictionary<string, string> environment = new Dictionary<string, string>(nodeList.Count); Dictionary<string, string> environment = new Dictionary<string, string>(nodeList.Count);
for (int i = 0; i < nodeList.Count; i++) for (int i = 0; i < nodeList.Count; i++)
{ {

View File

@ -6,7 +6,7 @@ using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#endif #endif
namespace winsw.Util namespace WinSW.Util
{ {
public static class FileHelper public static class FileHelper
{ {

View File

@ -4,9 +4,9 @@ using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using log4net; using log4net;
using static winsw.Native.ProcessApis; using static WinSW.Native.ProcessApis;
namespace winsw.Util namespace WinSW.Util
{ {
/// <summary> /// <summary>
/// Provides helper classes for Process Management /// Provides helper classes for Process Management
@ -73,8 +73,6 @@ namespace winsw.Util
/// Stops the process. /// Stops the process.
/// If the process cannot be stopped within the stop timeout, it gets killed /// If the process cannot be stopped within the stop timeout, it gets killed
/// </summary> /// </summary>
/// <param name="pid">PID of the process</param>
/// <param name="stopTimeout">Stop timeout</param>
public static void StopProcess(Process process, TimeSpan stopTimeout) public static void StopProcess(Process process, TimeSpan stopTimeout)
{ {
Logger.Info("Stopping process " + process.Id); Logger.Info("Stopping process " + process.Id);
@ -97,6 +95,7 @@ namespace winsw.Util
{ {
Logger.Warn("Process " + process.Id + " did not respond to Ctrl+C signal - Killing as fallback"); Logger.Warn("Process " + process.Id + " did not respond to Ctrl+C signal - Killing as fallback");
} }
process.Kill(); process.Kill();
} }
catch (Exception ex) catch (Exception ex)
@ -118,8 +117,6 @@ namespace winsw.Util
/// Terminate process and its children. /// Terminate process and its children.
/// By default the child processes get terminated first. /// By default the child processes get terminated first.
/// </summary> /// </summary>
/// <param name="pid">Process PID</param>
/// <param name="stopTimeout">Stop timeout (for each process)</param>
public static void StopProcessTree(Process process, TimeSpan stopTimeout) public static void StopProcessTree(Process process, TimeSpan stopTimeout)
{ {
StopProcess(process, stopTimeout); StopProcess(process, stopTimeout);
@ -135,14 +132,14 @@ namespace winsw.Util
/// Once the process exits, the callback will be invoked. /// Once the process exits, the callback will be invoked.
/// </summary> /// </summary>
/// <param name="processToStart">Process object to be used</param> /// <param name="processToStart">Process object to be used</param>
/// <param name="arguments">Arguments to be passed</param>
/// <param name="executable">Executable, which should be invoked</param> /// <param name="executable">Executable, which should be invoked</param>
/// <param name="arguments">Arguments to be passed</param>
/// <param name="envVars">Additional environment variables</param> /// <param name="envVars">Additional environment variables</param>
/// <param name="workingDirectory">Working directory</param> /// <param name="workingDirectory">Working directory</param>
/// <param name="priority">Priority</param> /// <param name="priority">Priority</param>
/// <param name="callback">Completion callback. If null, the completion won't be monitored</param> /// <param name="callback">Completion callback. If null, the completion won't be monitored</param>
/// <param name="logHandler">Log handler. If enabled, logs will be redirected to the process and then reported</param>
/// <param name="redirectStdin">Redirect standard input</param> /// <param name="redirectStdin">Redirect standard input</param>
/// <param name="logHandler">Log handler. If enabled, logs will be redirected to the process and then reported</param>
public static void StartProcessAndCallbackForExit( public static void StartProcessAndCallbackForExit(
Process processToStart, Process processToStart,
string? executable = null, string? executable = null,
@ -191,7 +188,7 @@ namespace winsw.Util
if (logHandler != null) if (logHandler != null)
{ {
Logger.Debug("Forwarding logs of the process " + processToStart + " to " + logHandler); Logger.Debug("Forwarding logs of the process " + processToStart + " to " + logHandler);
logHandler.log(processToStart.StandardOutput, processToStart.StandardError); logHandler.Log(processToStart.StandardOutput, processToStart.StandardError);
} }
// monitor the completion of the process // monitor the completion of the process

View File

@ -4,9 +4,9 @@ using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using log4net; using log4net;
using winsw.Native; using WinSW.Native;
namespace winsw.Util namespace WinSW.Util
{ {
internal static class SignalHelper internal static class SignalHelper
{ {

View File

@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Xml; using System.Xml;
namespace winsw.Util namespace WinSW.Util
{ {
public class XmlHelper public class XmlHelper
{ {
@ -19,7 +19,9 @@ namespace winsw.Util
{ {
XmlNode? n = node.SelectSingleNode(tagName); XmlNode? n = node.SelectSingleNode(tagName);
if (n is null && !optional) if (n is null && !optional)
{
throw new InvalidDataException("<" + tagName + "> is missing in configuration XML"); throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
}
return n is null ? null : Environment.ExpandEnvironmentVariables(n.InnerText); return n is null ? null : Environment.ExpandEnvironmentVariables(n.InnerText);
} }
@ -36,7 +38,9 @@ namespace winsw.Util
{ {
XmlNode? n = node.SelectSingleNode(tagName); XmlNode? n = node.SelectSingleNode(tagName);
if (n is null && !optional) if (n is null && !optional)
{
throw new InvalidDataException("<" + tagName + "> is missing in configuration XML"); throw new InvalidDataException("<" + tagName + "> is missing in configuration XML");
}
return n; return n;
} }
@ -69,7 +73,9 @@ namespace winsw.Util
public static TAttributeType SingleAttribute<TAttributeType>(XmlElement node, string attributeName, [AllowNull] TAttributeType defaultValue) public static TAttributeType SingleAttribute<TAttributeType>(XmlElement node, string attributeName, [AllowNull] TAttributeType defaultValue)
{ {
if (!node.HasAttribute(attributeName)) if (!node.HasAttribute(attributeName))
{
return defaultValue; return defaultValue;
}
string rawValue = node.GetAttribute(attributeName); string rawValue = node.GetAttribute(attributeName);
string substitutedValue = Environment.ExpandEnvironmentVariables(rawValue); string substitutedValue = Environment.ExpandEnvironmentVariables(rawValue);
@ -90,7 +96,9 @@ namespace winsw.Util
where TAttributeType : struct where TAttributeType : struct
{ {
if (!node.HasAttribute(attributeName)) if (!node.HasAttribute(attributeName))
{
return defaultValue; return defaultValue;
}
string rawValue = node.GetAttribute(attributeName); string rawValue = node.GetAttribute(attributeName);
string substitutedValue = Environment.ExpandEnvironmentVariables(rawValue); string substitutedValue = Environment.ExpandEnvironmentVariables(rawValue);
@ -100,7 +108,8 @@ namespace winsw.Util
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
throw new InvalidDataException("Cannot parse <" + attributeName + "> Enum value from string '" + substitutedValue + throw new InvalidDataException(
"Cannot parse <" + attributeName + "> Enum value from string '" + substitutedValue +
"'. Enum type: " + typeof(TAttributeType), ex); "'. Enum type: " + typeof(TAttributeType), ex);
} }
} }

View File

@ -11,6 +11,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="log4net" Version="2.0.8" /> <PackageReference Include="log4net" Version="2.0.8" />
<PackageReference Include="stylecop.analyzers" Version="1.2.0-beta.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'"> <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">

View File

@ -1,15 +1,17 @@
using System; using System;
namespace winsw namespace WinSW
{ {
public class WinSWException : Exception public class WinSWException : Exception
{ {
public WinSWException(string message) public WinSWException(string message)
: base(message) : base(message)
{ } {
}
public WinSWException(string message, Exception innerException) public WinSWException(string message, Exception innerException)
: base(message, innerException) : base(message, innerException)
{ } {
}
} }
} }

View File

@ -1,4 +1,4 @@
namespace winsw namespace WinSW
{ {
/// <summary> /// <summary>
/// Class, which contains generic information about WinSW runtime. /// Class, which contains generic information about WinSW runtime.
@ -9,17 +9,17 @@
/// <summary> /// <summary>
/// Prefix for all environment variables being injected for WinSW /// Prefix for all environment variables being injected for WinSW
/// </summary> /// </summary>
public static readonly string SYSTEM_EVNVVAR_PREFIX = "WINSW_"; public static readonly string SystemEnvVarPrefix = "WINSW_";
/// <summary> /// <summary>
/// Variable, which points to the service ID. /// Variable, which points to the service ID.
/// It may be used to determine runaway processes. /// It may be used to determine runaway processes.
/// </summary> /// </summary>
public static string ENVVAR_NAME_SERVICE_ID => SYSTEM_EVNVVAR_PREFIX + "SERVICE_ID"; public static string EnvVarNameServiceId => SystemEnvVarPrefix + "SERVICE_ID";
/// <summary> /// <summary>
/// Variable, which specifies path to the executable being launched by WinSW. /// Variable, which specifies path to the executable being launched by WinSW.
/// </summary> /// </summary>
public static string ENVVAR_NAME_EXECUTABLE_PATH => SYSTEM_EVNVVAR_PREFIX + "EXECUTABLE"; public static string EnvVarNameExecutablePath => SystemEnvVarPrefix + "EXECUTABLE";
} }
} }

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace winsw.Plugins.RunawayProcessKiller namespace WinSW.Plugins.RunawayProcessKiller
{ {
public partial class RunawayProcessKillerExtension public partial class RunawayProcessKillerExtension
{ {

View File

@ -1,15 +1,14 @@
using System; using System;
using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using log4net; using log4net;
using winsw.Extensions; using WinSW.Extensions;
using winsw.Util; using WinSW.Util;
using static winsw.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods; using static WinSW.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods;
namespace winsw.Plugins.RunawayProcessKiller namespace WinSW.Plugins.RunawayProcessKiller
{ {
public partial class RunawayProcessKillerExtension : AbstractWinSWExtension public partial class RunawayProcessKillerExtension : AbstractWinSWExtension
{ {
@ -177,12 +176,13 @@ namespace winsw.Plugins.RunawayProcessKiller
{ {
// We expect the upper logic to process any errors // We expect the upper logic to process any errors
// TODO: a better parser API for types would be useful // TODO: a better parser API for types would be useful
Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!; this.Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!;
StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!)); this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!));
ServiceId = descriptor.Id; this.ServiceId = descriptor.Id;
// TODO: Consider making it documented // TODO: Consider making it documented
var checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true); var checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true);
CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable); this.CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable);
} }
/// <summary> /// <summary>
@ -193,16 +193,16 @@ namespace winsw.Plugins.RunawayProcessKiller
{ {
// Read PID file from the disk // Read PID file from the disk
int pid; int pid;
if (File.Exists(Pidfile)) if (File.Exists(this.Pidfile))
{ {
string pidstring; string pidstring;
try try
{ {
pidstring = File.ReadAllText(Pidfile); pidstring = File.ReadAllText(this.Pidfile);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error("Cannot read PID file from " + Pidfile, ex); Logger.Error("Cannot read PID file from " + this.Pidfile, ex);
return; return;
} }
@ -212,13 +212,13 @@ namespace winsw.Plugins.RunawayProcessKiller
} }
catch (FormatException e) catch (FormatException e)
{ {
Logger.Error("Invalid PID file number in '" + Pidfile + "'. The runaway process won't be checked", e); Logger.Error("Invalid PID file number in '" + this.Pidfile + "'. The runaway process won't be checked", e);
return; return;
} }
} }
else else
{ {
Logger.Warn("The requested PID file '" + Pidfile + "' does not exist. The runaway process won't be checked"); Logger.Warn("The requested PID file '" + this.Pidfile + "' does not exist. The runaway process won't be checked");
return; return;
} }
@ -236,9 +236,9 @@ namespace winsw.Plugins.RunawayProcessKiller
} }
// Ensure the process references the service // Ensure the process references the service
string expectedEnvVarName = WinSWSystem.ENVVAR_NAME_SERVICE_ID; string expectedEnvVarName = WinSWSystem.EnvVarNameServiceId;
string? affiliatedServiceId = ReadEnvironmentVariable(proc.Handle, expectedEnvVarName); string? affiliatedServiceId = ReadEnvironmentVariable(proc.Handle, expectedEnvVarName);
if (affiliatedServiceId is null && CheckWinSWEnvironmentVariable) if (affiliatedServiceId is null && this.CheckWinSWEnvironmentVariable)
{ {
Logger.Warn("The process " + pid + " has no " + expectedEnvVarName + " environment variable defined. " Logger.Warn("The process " + pid + " has no " + expectedEnvVarName + " environment variable defined. "
+ "The process has not been started by WinSW, hence it won't be terminated."); + "The process has not been started by WinSW, hence it won't be terminated.");
@ -247,10 +247,10 @@ namespace winsw.Plugins.RunawayProcessKiller
} }
// Check the service ID value // Check the service ID value
if (CheckWinSWEnvironmentVariable && !ServiceId.Equals(affiliatedServiceId)) if (this.CheckWinSWEnvironmentVariable && !this.ServiceId.Equals(affiliatedServiceId))
{ {
Logger.Warn("The process " + pid + " has been started by Windows service with ID='" + affiliatedServiceId + "'. " Logger.Warn("The process " + pid + " has been started by Windows service with ID='" + affiliatedServiceId + "'. "
+ "It is another service (current service id is '" + ServiceId + "'), hence the process won't be terminated."); + "It is another service (current service id is '" + this.ServiceId + "'), hence the process won't be terminated.");
return; return;
} }
@ -258,7 +258,7 @@ namespace winsw.Plugins.RunawayProcessKiller
StringBuilder bldr = new StringBuilder("Stopping the runaway process (pid="); StringBuilder bldr = new StringBuilder("Stopping the runaway process (pid=");
bldr.Append(pid); bldr.Append(pid);
bldr.Append(") and its children. Environment was "); bldr.Append(") and its children. Environment was ");
if (!CheckWinSWEnvironmentVariable) if (!this.CheckWinSWEnvironmentVariable)
{ {
bldr.Append("not "); bldr.Append("not ");
} }
@ -275,17 +275,16 @@ namespace winsw.Plugins.RunawayProcessKiller
/// <summary> /// <summary>
/// Records the started process PID for the future use in OnStart() after the restart. /// Records the started process PID for the future use in OnStart() after the restart.
/// </summary> /// </summary>
/// <param name="process"></param>
public override void OnProcessStarted(Process process) public override void OnProcessStarted(Process process)
{ {
Logger.Info("Recording PID of the started process:" + process.Id + ". PID file destination is " + Pidfile); Logger.Info("Recording PID of the started process:" + process.Id + ". PID file destination is " + this.Pidfile);
try try
{ {
File.WriteAllText(Pidfile, process.Id.ToString()); File.WriteAllText(this.Pidfile, process.Id.ToString());
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error("Cannot update the PID file " + Pidfile, ex); Logger.Error("Cannot update the PID file " + this.Pidfile, ex);
} }
} }
} }

View File

@ -1,6 +1,6 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace winsw.Plugins.SharedDirectoryMapper namespace WinSW.Plugins.SharedDirectoryMapper
{ {
internal static class NativeMethods internal static class NativeMethods
{ {

View File

@ -2,15 +2,15 @@
using System.ComponentModel; using System.ComponentModel;
using System.Xml; using System.Xml;
using log4net; using log4net;
using winsw.Extensions; using WinSW.Extensions;
using winsw.Util; using WinSW.Util;
using static winsw.Plugins.SharedDirectoryMapper.NativeMethods; using static WinSW.Plugins.SharedDirectoryMapper.NativeMethods;
namespace winsw.Plugins.SharedDirectoryMapper namespace WinSW.Plugins.SharedDirectoryMapper
{ {
public class SharedDirectoryMapper : AbstractWinSWExtension public class SharedDirectoryMapper : AbstractWinSWExtension
{ {
private readonly List<SharedDirectoryMapperConfig> _entries = new List<SharedDirectoryMapperConfig>(); private readonly List<SharedDirectoryMapperConfig> entries = new List<SharedDirectoryMapperConfig>();
public override string DisplayName => "Shared Directory Mapper"; public override string DisplayName => "Shared Directory Mapper";
@ -23,7 +23,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
public SharedDirectoryMapper(bool enableMapping, string directoryUNC, string driveLabel) public SharedDirectoryMapper(bool enableMapping, string directoryUNC, string driveLabel)
{ {
SharedDirectoryMapperConfig config = new SharedDirectoryMapperConfig(enableMapping, driveLabel, directoryUNC); SharedDirectoryMapperConfig config = new SharedDirectoryMapperConfig(enableMapping, driveLabel, directoryUNC);
this._entries.Add(config); this.entries.Add(config);
} }
public override void Configure(ServiceDescriptor descriptor, XmlNode node) public override void Configure(ServiceDescriptor descriptor, XmlNode node)
@ -36,7 +36,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
if (mapNodes[i] is XmlElement mapElement) if (mapNodes[i] is XmlElement mapElement)
{ {
var config = SharedDirectoryMapperConfig.FromXml(mapElement); var config = SharedDirectoryMapperConfig.FromXml(mapElement);
this._entries.Add(config); this.entries.Add(config);
} }
} }
} }
@ -44,7 +44,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
public override void OnWrapperStarted() public override void OnWrapperStarted()
{ {
foreach (SharedDirectoryMapperConfig config in this._entries) foreach (SharedDirectoryMapperConfig config in this.entries)
{ {
string label = config.Label; string label = config.Label;
string uncPath = config.UNCPath; string uncPath = config.UNCPath;
@ -72,7 +72,7 @@ namespace winsw.Plugins.SharedDirectoryMapper
public override void BeforeWrapperStopped() public override void BeforeWrapperStopped()
{ {
foreach (SharedDirectoryMapperConfig config in this._entries) foreach (SharedDirectoryMapperConfig config in this.entries)
{ {
string label = config.Label; string label = config.Label;
if (config.EnableMapping) if (config.EnableMapping)

View File

@ -1,22 +1,24 @@
using System.Xml; using System.Xml;
using winsw.Util; using WinSW.Util;
namespace winsw.Plugins.SharedDirectoryMapper namespace WinSW.Plugins.SharedDirectoryMapper
{ {
/// <summary> /// <summary>
/// Stores configuration entries for SharedDirectoryMapper extension. /// Stores configuration entries for SharedDirectoryMapper extension.
/// </summary> /// </summary>
public class SharedDirectoryMapperConfig public class SharedDirectoryMapperConfig
{ {
public bool EnableMapping { get; set; } public bool EnableMapping { get; }
public string Label { get; set; }
public string UNCPath { get; set; } public string Label { get; }
public string UNCPath { get; }
public SharedDirectoryMapperConfig(bool enableMapping, string label, string uncPath) public SharedDirectoryMapperConfig(bool enableMapping, string label, string uncPath)
{ {
EnableMapping = enableMapping; this.EnableMapping = enableMapping;
Label = label; this.Label = label;
UNCPath = uncPath; this.UNCPath = uncPath;
} }
public static SharedDirectoryMapperConfig FromXml(XmlElement node) public static SharedDirectoryMapperConfig FromXml(XmlElement node)

View File

@ -1,8 +1,7 @@
using System; using System;
using winsw;
using Xunit; using Xunit;
namespace winswTests namespace WinSW.Tests
{ {
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal sealed class ElevatedFactAttribute : FactAttribute internal sealed class ElevatedFactAttribute : FactAttribute

View File

@ -1,11 +1,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Xml; using System.Xml;
using winsw; using WinSW.Tests.Util;
using winswTests.Util;
using Xunit; using Xunit;
namespace winswTests.Configuration namespace WinSW.Tests.Configuration
{ {
/// <summary> /// <summary>
/// Tests example configuration files. /// Tests example configuration files.

View File

@ -1,9 +1,8 @@
using System.IO; using System.IO;
using winsw; using WinSW.Tests.Util;
using winswTests.Util;
using Xunit; using Xunit;
namespace winswTests namespace WinSW.Tests
{ {
public class DownloadConfigTests public class DownloadConfigTests
{ {
@ -15,14 +14,14 @@ namespace winswTests
{ {
// Roundtrip data // Roundtrip data
Download d = new Download(From, To); Download d = new Download(From, To);
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithDownload(d) .WithDownload(d)
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd); var loaded = this.GetSingleEntry(sd);
// Check default values // Check default values
Assert.False(loaded.FailOnError); Assert.False(loaded.FailOnError);
Assert.Equal(Download.AuthType.none, loaded.Auth); Assert.Equal(Download.AuthType.None, loaded.Auth);
Assert.Null(loaded.Username); Assert.Null(loaded.Username);
Assert.Null(loaded.Password); Assert.Null(loaded.Password);
Assert.False(loaded.UnsecureAuth); Assert.False(loaded.UnsecureAuth);
@ -32,15 +31,15 @@ namespace winswTests
public void Roundtrip_BasicAuth() public void Roundtrip_BasicAuth()
{ {
// Roundtrip data // Roundtrip data
Download d = new Download(From, To, true, Download.AuthType.basic, "aUser", "aPassword", true); Download d = new Download(From, To, true, Download.AuthType.Basic, "aUser", "aPassword", true);
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithDownload(d) .WithDownload(d)
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd); var loaded = this.GetSingleEntry(sd);
// Check default values // Check default values
Assert.True(loaded.FailOnError); Assert.True(loaded.FailOnError);
Assert.Equal(Download.AuthType.basic, loaded.Auth); Assert.Equal(Download.AuthType.Basic, loaded.Auth);
Assert.Equal("aUser", loaded.Username); Assert.Equal("aUser", loaded.Username);
Assert.Equal("aPassword", loaded.Password); Assert.Equal("aPassword", loaded.Password);
Assert.True(loaded.UnsecureAuth); Assert.True(loaded.UnsecureAuth);
@ -50,15 +49,15 @@ namespace winswTests
public void Roundtrip_SSPI() public void Roundtrip_SSPI()
{ {
// Roundtrip data // Roundtrip data
Download d = new Download(From, To, false, Download.AuthType.sspi); Download d = new Download(From, To, false, Download.AuthType.Sspi);
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithDownload(d) .WithDownload(d)
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd); var loaded = this.GetSingleEntry(sd);
// Check default values // Check default values
Assert.False(loaded.FailOnError); Assert.False(loaded.FailOnError);
Assert.Equal(Download.AuthType.sspi, loaded.Auth); Assert.Equal(Download.AuthType.Sspi, loaded.Auth);
Assert.Null(loaded.Username); Assert.Null(loaded.Username);
Assert.Null(loaded.Password); Assert.Null(loaded.Password);
Assert.False(loaded.UnsecureAuth); Assert.False(loaded.UnsecureAuth);
@ -72,22 +71,22 @@ namespace winswTests
public void RejectBasicAuth_With_UnsecureProtocol(string protocolPrefix) public void RejectBasicAuth_With_UnsecureProtocol(string protocolPrefix)
{ {
string unsecureFrom = protocolPrefix + "myServer.com:8080/file.txt"; string unsecureFrom = protocolPrefix + "myServer.com:8080/file.txt";
var d = new Download(unsecureFrom, To, auth: Download.AuthType.basic, username: "aUser", password: "aPassword"); var d = new Download(unsecureFrom, To, auth: Download.AuthType.Basic, username: "aUser", password: "aPassword");
AssertInitializationFails(d, "Warning: you're sending your credentials in clear text to the server"); this.AssertInitializationFails(d, "Warning: you're sending your credentials in clear text to the server");
} }
[Fact] [Fact]
public void RejectBasicAuth_Without_Username() public void RejectBasicAuth_Without_Username()
{ {
var d = new Download(From, To, auth: Download.AuthType.basic, username: null, password: "aPassword"); var d = new Download(From, To, auth: Download.AuthType.Basic, username: null, password: "aPassword");
AssertInitializationFails(d, "Basic Auth is enabled, but username is not specified"); this.AssertInitializationFails(d, "Basic Auth is enabled, but username is not specified");
} }
[Fact] [Fact]
public void RejectBasicAuth_Without_Password() public void RejectBasicAuth_Without_Password()
{ {
var d = new Download(From, To, auth: Download.AuthType.basic, username: "aUser", password: null); var d = new Download(From, To, auth: Download.AuthType.Basic, username: "aUser", password: null);
AssertInitializationFails(d, "Basic Auth is enabled, but password is not specified"); this.AssertInitializationFails(d, "Basic Auth is enabled, but password is not specified");
} }
/// <summary> /// <summary>
@ -100,11 +99,11 @@ namespace winswTests
{ {
Download d = new Download(From, To, failOnError); Download d = new Download(From, To, failOnError);
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithDownload(d) .WithDownload(d)
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd); var loaded = this.GetSingleEntry(sd);
Assert.Equal(From, loaded.From); Assert.Equal(From, loaded.From);
Assert.Equal(To, loaded.To); Assert.Equal(To, loaded.To);
Assert.Equal(failOnError, loaded.FailOnError); Assert.Equal(failOnError, loaded.FailOnError);
@ -116,11 +115,11 @@ namespace winswTests
[Fact] [Fact]
public void Download_FailOnError_Undefined() public void Download_FailOnError_Undefined()
{ {
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\"/>") .WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\"/>")
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd); var loaded = this.GetSingleEntry(sd);
Assert.False(loaded.FailOnError); Assert.False(loaded.FailOnError);
} }
@ -131,22 +130,22 @@ namespace winswTests
[InlineData("Sspi")] [InlineData("Sspi")]
public void AuthType_Is_CaseInsensitive(string authType) public void AuthType_Is_CaseInsensitive(string authType)
{ {
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"" + authType + "\"/>") .WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"" + authType + "\"/>")
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
var loaded = GetSingleEntry(sd); var loaded = this.GetSingleEntry(sd);
Assert.Equal(Download.AuthType.sspi, loaded.Auth); Assert.Equal(Download.AuthType.Sspi, loaded.Auth);
} }
[Fact] [Fact]
public void Should_Fail_On_Unsupported_AuthType() public void Should_Fail_On_Unsupported_AuthType()
{ {
// TODO: will need refactoring once all fields are being parsed on startup // TODO: will need refactoring once all fields are being parsed on startup
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"digest\"/>") .WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"digest\"/>")
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
var e = Assert.Throws<InvalidDataException>(() => GetSingleEntry(sd)); var e = Assert.Throws<InvalidDataException>(() => this.GetSingleEntry(sd));
Assert.StartsWith("Cannot parse <auth> Enum value from string 'digest'", e.Message); Assert.StartsWith("Cannot parse <auth> Enum value from string 'digest'", e.Message);
} }
@ -188,11 +187,11 @@ namespace winswTests
private void AssertInitializationFails(Download download, string expectedMessagePart = null) private void AssertInitializationFails(Download download, string expectedMessagePart = null)
{ {
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithDownload(download) .WithDownload(download)
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
var e = Assert.Throws<InvalidDataException>(() => GetSingleEntry(sd)); var e = Assert.Throws<InvalidDataException>(() => this.GetSingleEntry(sd));
Assert.StartsWith(expectedMessagePart, e.Message); Assert.StartsWith(expectedMessagePart, e.Message);
} }
} }

View File

@ -4,11 +4,10 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using winsw; using WinSW.Tests.Util;
using winswTests.Util;
using Xunit; using Xunit;
namespace winswTests namespace WinSW.Tests
{ {
public class DownloadTests : IDisposable public class DownloadTests : IDisposable
{ {
@ -107,7 +106,7 @@ namespace winswTests
await this.TestClientServerAsync( await this.TestClientServerAsync(
async (source, dest) => async (source, dest) =>
{ {
await new Download(source, dest, false, Download.AuthType.none).PerformAsync(); await new Download(source, dest, false, Download.AuthType.None).PerformAsync();
Assert.Equal(this.contents, File.ReadAllBytes(dest)); Assert.Equal(this.contents, File.ReadAllBytes(dest));
}, },
context => context =>
@ -132,7 +131,7 @@ namespace winswTests
await this.TestClientServerAsync( await this.TestClientServerAsync(
async (source, dest) => async (source, dest) =>
{ {
await new Download(source, dest, false, Download.AuthType.basic, username, password, true).PerformAsync(); await new Download(source, dest, false, Download.AuthType.Basic, username, password, true).PerformAsync();
Assert.Equal(this.contents, File.ReadAllBytes(dest)); Assert.Equal(this.contents, File.ReadAllBytes(dest));
}, },
context => context =>

View File

@ -1,6 +1,6 @@
using System; using System;
namespace winswTests.Extensions namespace WinSW.Tests.Extensions
{ {
/// <summary> /// <summary>
/// Base class for testing of WinSW Extensions. /// Base class for testing of WinSW Extensions.

View File

@ -1,20 +1,19 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using winsw; using WinSW.Extensions;
using winsw.Extensions; using WinSW.Plugins.RunawayProcessKiller;
using winsw.Plugins.RunawayProcessKiller; using WinSW.Tests.Util;
using winsw.Util; using WinSW.Util;
using winswTests.Util;
using Xunit; using Xunit;
namespace winswTests.Extensions namespace WinSW.Tests.Extensions
{ {
public class RunawayProcessKillerExtensionTest : ExtensionTestBase public class RunawayProcessKillerExtensionTest : ExtensionTestBase
{ {
readonly ServiceDescriptor _testServiceDescriptor; private readonly ServiceDescriptor testServiceDescriptor;
readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension)); private readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension));
public RunawayProcessKillerExtensionTest() public RunawayProcessKillerExtensionTest()
{ {
@ -27,19 +26,19 @@ $@"<service>
<arguments>-Xrs -jar \""%BASE%\slave.jar\"" -jnlpUrl ...</arguments> <arguments>-Xrs -jar \""%BASE%\slave.jar\"" -jnlpUrl ...</arguments>
<log mode=""roll""></log> <log mode=""roll""></log>
<extensions> <extensions>
<extension enabled=""true"" className=""{testExtension}"" id=""killRunawayProcess""> <extension enabled=""true"" className=""{this.testExtension}"" id=""killRunawayProcess"">
<pidfile>foo/bar/pid.txt</pidfile> <pidfile>foo/bar/pid.txt</pidfile>
<stopTimeout>5000</stopTimeout> <stopTimeout>5000</stopTimeout>
</extension> </extension>
</extensions> </extensions>
</service>"; </service>";
_testServiceDescriptor = ServiceDescriptor.FromXML(seedXml); this.testServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
} }
[Fact] [Fact]
public void LoadExtensions() public void LoadExtensions()
{ {
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor); WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
manager.LoadExtensions(); manager.LoadExtensions();
_ = Assert.Single(manager.Extensions); _ = Assert.Single(manager.Extensions);
@ -53,7 +52,7 @@ $@"<service>
[Fact] [Fact]
public void StartStopExtension() public void StartStopExtension()
{ {
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor); WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
manager.LoadExtensions(); manager.LoadExtensions();
manager.FireOnWrapperStarted(); manager.FireOnWrapperStarted();
manager.FireBeforeWrapperStopped(); manager.FireBeforeWrapperStopped();
@ -72,14 +71,14 @@ $@"<service>
ps.Arguments = "/c pause"; ps.Arguments = "/c pause";
ps.UseShellExecute = false; ps.UseShellExecute = false;
ps.RedirectStandardOutput = true; ps.RedirectStandardOutput = true;
ps.EnvironmentVariables[WinSWSystem.ENVVAR_NAME_SERVICE_ID] = winswId; ps.EnvironmentVariables[WinSWSystem.EnvVarNameServiceId] = winswId;
proc.Start(); proc.Start();
try try
{ {
// Generate extension and ensure that the roundtrip is correct // Generate extension and ensure that the roundtrip is correct
var pidfile = Path.Combine(tmpDir, "process.pid"); var pidfile = Path.Combine(tmpDir, "process.pid");
var sd = ConfigXmlBuilder.create(id: winswId) var sd = ConfigXmlBuilder.Create(id: winswId)
.WithRunawayProcessKiller(new RunawayProcessKillerExtension(pidfile), extensionId) .WithRunawayProcessKiller(new RunawayProcessKillerExtension(pidfile), extensionId)
.ToServiceDescriptor(); .ToServiceDescriptor();
WinSWExtensionManager manager = new WinSWExtensionManager(sd); WinSWExtensionManager manager = new WinSWExtensionManager(sd);

View File

@ -1,15 +1,14 @@
using winsw; using WinSW.Extensions;
using winsw.Extensions; using WinSW.Plugins.SharedDirectoryMapper;
using winsw.Plugins.SharedDirectoryMapper;
using Xunit; using Xunit;
namespace winswTests.Extensions namespace WinSW.Tests.Extensions
{ {
public class SharedDirectoryMapperConfigTest : ExtensionTestBase public class SharedDirectoryMapperConfigTest : ExtensionTestBase
{ {
readonly ServiceDescriptor _testServiceDescriptor; private readonly ServiceDescriptor testServiceDescriptor;
readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(SharedDirectoryMapper)); private readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(SharedDirectoryMapper));
public SharedDirectoryMapperConfigTest() public SharedDirectoryMapperConfigTest()
{ {
@ -22,13 +21,13 @@ $@"<service>
<arguments>-Xrs -jar \""%BASE%\slave.jar\"" -jnlpUrl ...</arguments> <arguments>-Xrs -jar \""%BASE%\slave.jar\"" -jnlpUrl ...</arguments>
<log mode=""roll""></log> <log mode=""roll""></log>
<extensions> <extensions>
<extension enabled=""true"" className=""{testExtension}"" id=""mapNetworDirs""> <extension enabled=""true"" className=""{this.testExtension}"" id=""mapNetworDirs"">
<mapping> <mapping>
<map enabled=""false"" label=""N:"" uncpath=""\\UNC""/> <map enabled=""false"" label=""N:"" uncpath=""\\UNC""/>
<map enabled=""false"" label=""M:"" uncpath=""\\UNC2""/> <map enabled=""false"" label=""M:"" uncpath=""\\UNC2""/>
</mapping> </mapping>
</extension> </extension>
<extension enabled=""true"" className=""{testExtension}"" id=""mapNetworDirs2""> <extension enabled=""true"" className=""{this.testExtension}"" id=""mapNetworDirs2"">
<mapping> <mapping>
<map enabled=""false"" label=""X:"" uncpath=""\\UNC""/> <map enabled=""false"" label=""X:"" uncpath=""\\UNC""/>
<map enabled=""false"" label=""Y:"" uncpath=""\\UNC2""/> <map enabled=""false"" label=""Y:"" uncpath=""\\UNC2""/>
@ -36,13 +35,13 @@ $@"<service>
</extension> </extension>
</extensions> </extensions>
</service>"; </service>";
_testServiceDescriptor = ServiceDescriptor.FromXML(seedXml); this.testServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
} }
[Fact] [Fact]
public void LoadExtensions() public void LoadExtensions()
{ {
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor); WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
manager.LoadExtensions(); manager.LoadExtensions();
Assert.Equal(2, manager.Extensions.Count); Assert.Equal(2, manager.Extensions.Count);
} }
@ -50,7 +49,7 @@ $@"<service>
[Fact] [Fact]
public void StartStopExtension() public void StartStopExtension()
{ {
WinSWExtensionManager manager = new WinSWExtensionManager(_testServiceDescriptor); WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
manager.LoadExtensions(); manager.LoadExtensions();
manager.FireOnWrapperStarted(); manager.FireOnWrapperStarted();
manager.FireBeforeWrapperStopped(); manager.FireBeforeWrapperStopped();

View File

@ -3,10 +3,10 @@ using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using winsw.Plugins.SharedDirectoryMapper; using WinSW.Plugins.SharedDirectoryMapper;
using Xunit; using Xunit;
namespace winswTests.Extensions namespace WinSW.Tests.Extensions
{ {
// TODO: Assert.Throws<ExtensionException> // TODO: Assert.Throws<ExtensionException>
public class SharedDirectoryMapperTests public class SharedDirectoryMapperTests

View File

@ -1,10 +1,9 @@
using System; using System;
using System.ServiceProcess; using System.ServiceProcess;
using winsw; using WinSW.Tests.Util;
using winswTests.Util;
using Xunit; using Xunit;
namespace winswTests namespace WinSW.Tests
{ {
public class MainTest public class MainTest
{ {
@ -47,6 +46,7 @@ namespace winswTests
Assert.Contains("start", cliOut); Assert.Contains("start", cliOut);
Assert.Contains("help", cliOut); Assert.Contains("help", cliOut);
Assert.Contains("version", cliOut); Assert.Contains("version", cliOut);
// TODO: check all commands after the migration of ccommands to enum // TODO: check all commands after the migration of ccommands to enum
} }

View File

@ -1,22 +1,21 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.ServiceProcess; using System.ServiceProcess;
using winsw; using WinSW.Tests.Util;
using winswTests.Util;
using Xunit; using Xunit;
namespace winswTests namespace WinSW.Tests
{ {
public class ServiceDescriptorTests public class ServiceDescriptorTests
{ {
private ServiceDescriptor _extendedServiceDescriptor;
private const string ExpectedWorkingDirectory = @"Z:\Path\SubPath"; private const string ExpectedWorkingDirectory = @"Z:\Path\SubPath";
private const string Username = "User"; private const string Username = "User";
private const string Password = "Password"; private const string Password = "Password";
private const string Domain = "Domain"; private const string Domain = "Domain";
private const string AllowServiceAccountLogonRight = "true"; private const string AllowServiceAccountLogonRight = "true";
private ServiceDescriptor extendedServiceDescriptor;
public ServiceDescriptorTests() public ServiceDescriptorTests()
{ {
string seedXml = string seedXml =
@ -35,13 +34,13 @@ $@"<service>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory> <workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath> <logpath>C:\logs</logpath>
</service>"; </service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml); this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
} }
[Fact] [Fact]
public void DefaultStartMode() public void DefaultStartMode()
{ {
Assert.Equal(ServiceStartMode.Automatic, _extendedServiceDescriptor.StartMode); Assert.Equal(ServiceStartMode.Automatic, this.extendedServiceDescriptor.StartMode);
} }
[Fact] [Fact]
@ -65,8 +64,8 @@ $@"<service>
<logpath>C:\logs</logpath> <logpath>C:\logs</logpath>
</service>"; </service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml); this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Throws<ArgumentException>(() => _extendedServiceDescriptor.StartMode); Assert.Throws<ArgumentException>(() => this.extendedServiceDescriptor.StartMode);
} }
[Fact] [Fact]
@ -90,47 +89,47 @@ $@"<service>
<logpath>C:\logs</logpath> <logpath>C:\logs</logpath>
</service>"; </service>";
_extendedServiceDescriptor = ServiceDescriptor.FromXML(seedXml); this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(ServiceStartMode.Manual, _extendedServiceDescriptor.StartMode); Assert.Equal(ServiceStartMode.Manual, this.extendedServiceDescriptor.StartMode);
} }
[Fact] [Fact]
public void VerifyWorkingDirectory() public void VerifyWorkingDirectory()
{ {
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory); Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(ExpectedWorkingDirectory, _extendedServiceDescriptor.WorkingDirectory); Assert.Equal(ExpectedWorkingDirectory, this.extendedServiceDescriptor.WorkingDirectory);
} }
[Fact] [Fact]
public void VerifyServiceLogonRight() public void VerifyServiceLogonRight()
{ {
Assert.True(_extendedServiceDescriptor.AllowServiceAcountLogonRight); Assert.True(this.extendedServiceDescriptor.AllowServiceAcountLogonRight);
} }
[Fact] [Fact]
public void VerifyUsername() public void VerifyUsername()
{ {
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory); Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(Domain + "\\" + Username, _extendedServiceDescriptor.ServiceAccountUserName); Assert.Equal(Domain + "\\" + Username, this.extendedServiceDescriptor.ServiceAccountUserName);
} }
[Fact] [Fact]
public void VerifyPassword() public void VerifyPassword()
{ {
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + _extendedServiceDescriptor.WorkingDirectory); Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(Password, _extendedServiceDescriptor.ServiceAccountPassword); Assert.Equal(Password, this.extendedServiceDescriptor.ServiceAccountPassword);
} }
[Fact] [Fact]
public void Priority() public void Priority()
{ {
var sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>normal</priority></service>"); var sd = ServiceDescriptor.FromXml("<service><id>test</id><priority>normal</priority></service>");
Assert.Equal(ProcessPriorityClass.Normal, sd.Priority); Assert.Equal(ProcessPriorityClass.Normal, sd.Priority);
sd = ServiceDescriptor.FromXML("<service><id>test</id><priority>idle</priority></service>"); sd = ServiceDescriptor.FromXml("<service><id>test</id><priority>idle</priority></service>");
Assert.Equal(ProcessPriorityClass.Idle, sd.Priority); Assert.Equal(ProcessPriorityClass.Idle, sd.Priority);
sd = ServiceDescriptor.FromXML("<service><id>test</id></service>"); sd = ServiceDescriptor.FromXml("<service><id>test</id></service>");
Assert.Equal(ProcessPriorityClass.Normal, sd.Priority); Assert.Equal(ProcessPriorityClass.Normal, sd.Priority);
} }
@ -140,7 +139,7 @@ $@"<service>
const string seedXml = "<service>" const string seedXml = "<service>"
+ "<stoptimeout>60sec</stoptimeout>" + "<stoptimeout>60sec</stoptimeout>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(TimeSpan.FromSeconds(60), serviceDescriptor.StopTimeout); Assert.Equal(TimeSpan.FromSeconds(60), serviceDescriptor.StopTimeout);
} }
@ -151,7 +150,7 @@ $@"<service>
const string seedXml = "<service>" const string seedXml = "<service>"
+ "<stoptimeout>10min</stoptimeout>" + "<stoptimeout>10min</stoptimeout>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(TimeSpan.FromMinutes(10), serviceDescriptor.StopTimeout); Assert.Equal(TimeSpan.FromMinutes(10), serviceDescriptor.StopTimeout);
} }
@ -162,7 +161,7 @@ $@"<service>
const string seedXml = "<service>" const string seedXml = "<service>"
+ "<logname>MyTestApp</logname>" + "<logname>MyTestApp</logname>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal("MyTestApp", serviceDescriptor.LogName); Assert.Equal("MyTestApp", serviceDescriptor.LogName);
} }
@ -173,7 +172,7 @@ $@"<service>
const string seedXml = "<service>" const string seedXml = "<service>"
+ "<outfiledisabled>true</outfiledisabled>" + "<outfiledisabled>true</outfiledisabled>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.True(serviceDescriptor.OutFileDisabled); Assert.True(serviceDescriptor.OutFileDisabled);
} }
@ -184,7 +183,7 @@ $@"<service>
const string seedXml = "<service>" const string seedXml = "<service>"
+ "<errfiledisabled>true</errfiledisabled>" + "<errfiledisabled>true</errfiledisabled>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.True(serviceDescriptor.ErrFileDisabled); Assert.True(serviceDescriptor.ErrFileDisabled);
} }
@ -195,7 +194,7 @@ $@"<service>
const string seedXml = "<service>" const string seedXml = "<service>"
+ "<outfilepattern>.out.test.log</outfilepattern>" + "<outfilepattern>.out.test.log</outfilepattern>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(".out.test.log", serviceDescriptor.OutFilePattern); Assert.Equal(".out.test.log", serviceDescriptor.OutFilePattern);
} }
@ -206,7 +205,7 @@ $@"<service>
const string seedXml = "<service>" const string seedXml = "<service>"
+ "<errfilepattern>.err.test.log</errfilepattern>" + "<errfilepattern>.err.test.log</errfilepattern>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(".err.test.log", serviceDescriptor.ErrFilePattern); Assert.Equal(".err.test.log", serviceDescriptor.ErrFilePattern);
} }
@ -222,12 +221,12 @@ $@"<service>
+ "</log>" + "</log>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service"; serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as SizeBasedRollingLogAppender; var logHandler = serviceDescriptor.LogHandler as SizeBasedRollingLogAppender;
Assert.NotNull(logHandler); Assert.NotNull(logHandler);
Assert.Equal(112 * 1024, logHandler.SizeTheshold); Assert.Equal(112 * 1024, logHandler.SizeThreshold);
Assert.Equal(113, logHandler.FilesToKeep); Assert.Equal(113, logHandler.FilesToKeep);
} }
@ -242,7 +241,7 @@ $@"<service>
+ "</log>" + "</log>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service"; serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as TimeBasedRollingLogAppender; var logHandler = serviceDescriptor.LogHandler as TimeBasedRollingLogAppender;
@ -263,12 +262,12 @@ $@"<service>
+ "</log>" + "</log>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service"; serviceDescriptor.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as RollingSizeTimeLogAppender; var logHandler = serviceDescriptor.LogHandler as RollingSizeTimeLogAppender;
Assert.NotNull(logHandler); Assert.NotNull(logHandler);
Assert.Equal(10240 * 1024, logHandler.SizeTheshold); Assert.Equal(10240 * 1024, logHandler.SizeThreshold);
Assert.Equal("yyyy-MM-dd", logHandler.FilePattern); Assert.Equal("yyyy-MM-dd", logHandler.FilePattern);
Assert.Equal((TimeSpan?)new TimeSpan(0, 0, 0), logHandler.AutoRollAtTime); Assert.Equal((TimeSpan?)new TimeSpan(0, 0, 0), logHandler.AutoRollAtTime);
} }
@ -284,7 +283,7 @@ $@"<service>
+ "<allowservicelogon>true1</allowservicelogon>" + "<allowservicelogon>true1</allowservicelogon>"
+ "</serviceaccount>" + "</serviceaccount>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.False(serviceDescriptor.AllowServiceAcountLogonRight); Assert.False(serviceDescriptor.AllowServiceAcountLogonRight);
} }
@ -298,14 +297,14 @@ $@"<service>
+ "<password>" + Password + "</password>" + "<password>" + Password + "</password>"
+ "</serviceaccount>" + "</serviceaccount>"
+ "</service>"; + "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(seedXml); var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.False(serviceDescriptor.AllowServiceAcountLogonRight); Assert.False(serviceDescriptor.AllowServiceAcountLogonRight);
} }
[Fact] [Fact]
public void VerifyWaitHint_FullXML() public void VerifyWaitHint_FullXML()
{ {
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithTag("waithint", "20 min") .WithTag("waithint", "20 min")
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromMinutes(20), sd.WaitHint); Assert.Equal(TimeSpan.FromMinutes(20), sd.WaitHint);
@ -317,7 +316,7 @@ $@"<service>
[Fact] [Fact]
public void VerifyWaitHint_XMLWithoutVersion() public void VerifyWaitHint_XMLWithoutVersion()
{ {
var sd = ConfigXmlBuilder.create(printXMLVersion: false) var sd = ConfigXmlBuilder.Create(printXMLVersion: false)
.WithTag("waithint", "21 min") .WithTag("waithint", "21 min")
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromMinutes(21), sd.WaitHint); Assert.Equal(TimeSpan.FromMinutes(21), sd.WaitHint);
@ -326,7 +325,7 @@ $@"<service>
[Fact] [Fact]
public void VerifyWaitHint_XMLWithoutComment() public void VerifyWaitHint_XMLWithoutComment()
{ {
var sd = ConfigXmlBuilder.create(xmlComment: null) var sd = ConfigXmlBuilder.Create(xmlComment: null)
.WithTag("waithint", "22 min") .WithTag("waithint", "22 min")
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromMinutes(22), sd.WaitHint); Assert.Equal(TimeSpan.FromMinutes(22), sd.WaitHint);
@ -335,7 +334,7 @@ $@"<service>
[Fact] [Fact]
public void VerifyWaitHint_XMLWithoutVersionAndComment() public void VerifyWaitHint_XMLWithoutVersionAndComment()
{ {
var sd = ConfigXmlBuilder.create(xmlComment: null, printXMLVersion: false) var sd = ConfigXmlBuilder.Create(xmlComment: null, printXMLVersion: false)
.WithTag("waithint", "23 min") .WithTag("waithint", "23 min")
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromMinutes(23), sd.WaitHint); Assert.Equal(TimeSpan.FromMinutes(23), sd.WaitHint);
@ -344,21 +343,21 @@ $@"<service>
[Fact] [Fact]
public void VerifySleepTime() public void VerifySleepTime()
{ {
var sd = ConfigXmlBuilder.create().WithTag("sleeptime", "3 hrs").ToServiceDescriptor(true); var sd = ConfigXmlBuilder.Create().WithTag("sleeptime", "3 hrs").ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromHours(3), sd.SleepTime); Assert.Equal(TimeSpan.FromHours(3), sd.SleepTime);
} }
[Fact] [Fact]
public void VerifyResetFailureAfter() public void VerifyResetFailureAfter()
{ {
var sd = ConfigXmlBuilder.create().WithTag("resetfailure", "75 sec").ToServiceDescriptor(true); var sd = ConfigXmlBuilder.Create().WithTag("resetfailure", "75 sec").ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromSeconds(75), sd.ResetFailureAfter); Assert.Equal(TimeSpan.FromSeconds(75), sd.ResetFailureAfter);
} }
[Fact] [Fact]
public void VerifyStopTimeout() public void VerifyStopTimeout()
{ {
var sd = ConfigXmlBuilder.create().WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true); var sd = ConfigXmlBuilder.Create().WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromSeconds(35), sd.StopTimeout); Assert.Equal(TimeSpan.FromSeconds(35), sd.StopTimeout);
} }
@ -368,14 +367,14 @@ $@"<service>
[Fact] [Fact]
public void Arguments_LegacyParam() public void Arguments_LegacyParam()
{ {
var sd = ConfigXmlBuilder.create().WithTag("arguments", "arg").ToServiceDescriptor(true); var sd = ConfigXmlBuilder.Create().WithTag("arguments", "arg").ToServiceDescriptor(true);
Assert.Equal("arg", sd.Arguments); Assert.Equal("arg", sd.Arguments);
} }
[Fact] [Fact]
public void Arguments_NewParam_Single() public void Arguments_NewParam_Single()
{ {
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithTag("argument", "--arg1=2") .WithTag("argument", "--arg1=2")
.ToServiceDescriptor(true); .ToServiceDescriptor(true);
Assert.Equal(" --arg1=2", sd.Arguments); Assert.Equal(" --arg1=2", sd.Arguments);
@ -384,7 +383,7 @@ $@"<service>
[Fact] [Fact]
public void Arguments_NewParam_MultipleArgs() public void Arguments_NewParam_MultipleArgs()
{ {
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithTag("argument", "--arg1=2") .WithTag("argument", "--arg1=2")
.WithTag("argument", "--arg2=123") .WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null") .WithTag("argument", "--arg3=null")
@ -398,7 +397,7 @@ $@"<service>
[Fact] [Fact]
public void Arguments_Bothparam_Priorities() public void Arguments_Bothparam_Priorities()
{ {
var sd = ConfigXmlBuilder.create() var sd = ConfigXmlBuilder.Create()
.WithTag("arguments", "--arg1=2 --arg2=3") .WithTag("arguments", "--arg1=2 --arg2=3")
.WithTag("argument", "--arg2=123") .WithTag("argument", "--arg2=123")
.WithTag("argument", "--arg3=null") .WithTag("argument", "--arg3=null")
@ -411,7 +410,7 @@ $@"<service>
[InlineData(false)] [InlineData(false)]
public void DelayedStart_RoundTrip(bool enabled) public void DelayedStart_RoundTrip(bool enabled)
{ {
var bldr = ConfigXmlBuilder.create(); var bldr = ConfigXmlBuilder.Create();
if (enabled) if (enabled)
{ {
bldr = bldr.WithDelayedAutoStart(); bldr = bldr.WithDelayedAutoStart();

View File

@ -1,9 +1,8 @@
using System; using System;
using System.IO; using System.IO;
using winsw;
using Xunit; using Xunit;
namespace winswTests.Util namespace WinSW.Tests.Util
{ {
/// <summary> /// <summary>
/// Helper for WinSW CLI testing /// Helper for WinSW CLI testing
@ -25,7 +24,7 @@ $@"<service>
<logpath>C:\winsw\logs</logpath> <logpath>C:\winsw\logs</logpath>
</service>"; </service>";
public static readonly ServiceDescriptor DefaultServiceDescriptor = ServiceDescriptor.FromXML(SeedXml); public static readonly ServiceDescriptor DefaultServiceDescriptor = ServiceDescriptor.FromXml(SeedXml);
/// <summary> /// <summary>
/// Runs a simle test, which returns the output CLI /// Runs a simle test, which returns the output CLI
@ -115,13 +114,13 @@ $@"<service>
public Exception Exception { get; } public Exception Exception { get; }
public bool HasException => Exception != null; public bool HasException => this.Exception != null;
public CLITestResult(string output, string error, Exception exception = null) public CLITestResult(string output, string error, Exception exception = null)
{ {
Out = output; this.Out = output;
Error = error; this.Error = error;
Exception = exception; this.Exception = exception;
} }
} }
} }

View File

@ -1,36 +1,45 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using winsw; using WinSW.Plugins.RunawayProcessKiller;
using winsw.Plugins.RunawayProcessKiller; using WinSW.Tests.Extensions;
using winswTests.Extensions;
namespace winswTests.Util namespace WinSW.Tests.Util
{ {
/// <summary> /// <summary>
/// Configuration XML builder, which simplifies testing of WinSW Configuration file. /// Configuration XML builder, which simplifies testing of WinSW Configuration file.
/// </summary> /// </summary>
class ConfigXmlBuilder internal class ConfigXmlBuilder
{ {
public string Name { get; set; } public string Name { get; set; }
public string Id { get; set; } public string Id { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Executable { get; set; } public string Executable { get; set; }
public bool PrintXMLVersion { get; set; }
public string XMLComment { get; set; } public bool PrintXmlVersion { get; set; }
public List<string> ExtensionXmls { get; private set; }
public string XmlComment { get; set; }
public List<string> ExtensionXmls { get; }
private readonly List<string> configEntries; private readonly List<string> configEntries;
// TODO: Switch to the initializer? // TODO: Switch to the initializer?
private ConfigXmlBuilder() private ConfigXmlBuilder()
{ {
configEntries = new List<string>(); this.configEntries = new List<string>();
ExtensionXmls = new List<string>(); this.ExtensionXmls = new List<string>();
} }
public static ConfigXmlBuilder create(string id = null, string name = null, public static ConfigXmlBuilder Create(
string description = null, string executable = null, bool printXMLVersion = true, string id = null,
string name = null,
string description = null,
string executable = null,
bool printXMLVersion = true,
string xmlComment = "") string xmlComment = "")
{ {
var config = new ConfigXmlBuilder(); var config = new ConfigXmlBuilder();
@ -38,43 +47,43 @@ namespace winswTests.Util
config.Name = name ?? "MyApp Service"; config.Name = name ?? "MyApp Service";
config.Description = description ?? "MyApp Service (powered by WinSW)"; config.Description = description ?? "MyApp Service (powered by WinSW)";
config.Executable = executable ?? "%BASE%\\myExecutable.exe"; config.Executable = executable ?? "%BASE%\\myExecutable.exe";
config.PrintXMLVersion = printXMLVersion; config.PrintXmlVersion = printXMLVersion;
config.XMLComment = (xmlComment != null && xmlComment.Length == 0) config.XmlComment = (xmlComment != null && xmlComment.Length == 0)
? "Just a sample configuration file generated by the test suite" ? "Just a sample configuration file generated by the test suite"
: xmlComment; : xmlComment;
return config; return config;
} }
public string ToXMLString(bool dumpConfig = false) public string ToXmlString(bool dumpConfig = false)
{ {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
if (PrintXMLVersion) if (this.PrintXmlVersion)
{ {
// TODO: The encoding is generally wrong // TODO: The encoding is generally wrong
str.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); str.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
} }
if (XMLComment != null) if (this.XmlComment != null)
{ {
str.AppendFormat("<!--{0}-->\n", XMLComment); str.AppendFormat("<!--{0}-->\n", this.XmlComment);
} }
str.Append("<service>\n"); str.Append("<service>\n");
str.AppendFormat(" <id>{0}</id>\n", Id); str.AppendFormat(" <id>{0}</id>\n", this.Id);
str.AppendFormat(" <name>{0}</name>\n", Name); str.AppendFormat(" <name>{0}</name>\n", this.Name);
str.AppendFormat(" <description>{0}</description>\n", Description); str.AppendFormat(" <description>{0}</description>\n", this.Description);
str.AppendFormat(" <executable>{0}</executable>\n", Executable); str.AppendFormat(" <executable>{0}</executable>\n", this.Executable);
foreach (string entry in configEntries) foreach (string entry in this.configEntries)
{ {
// We do not care much about pretty formatting here // We do not care much about pretty formatting here
str.AppendFormat(" {0}\n", entry); str.AppendFormat(" {0}\n", entry);
} }
// Extensions // Extensions
if (ExtensionXmls.Count > 0) if (this.ExtensionXmls.Count > 0)
{ {
str.Append(" <extensions>\n"); str.Append(" <extensions>\n");
foreach (string xml in ExtensionXmls) foreach (string xml in this.ExtensionXmls)
{ {
str.Append(xml); str.Append(xml);
} }
@ -95,18 +104,18 @@ namespace winswTests.Util
public ServiceDescriptor ToServiceDescriptor(bool dumpConfig = false) public ServiceDescriptor ToServiceDescriptor(bool dumpConfig = false)
{ {
return ServiceDescriptor.FromXML(ToXMLString(dumpConfig)); return ServiceDescriptor.FromXml(this.ToXmlString(dumpConfig));
} }
public ConfigXmlBuilder WithRawEntry(string entry) public ConfigXmlBuilder WithRawEntry(string entry)
{ {
configEntries.Add(entry); this.configEntries.Add(entry);
return this; return this;
} }
public ConfigXmlBuilder WithTag(string tagName, string value) public ConfigXmlBuilder WithTag(string tagName, string value)
{ {
return WithRawEntry(string.Format("<{0}>{1}</{0}>", tagName, value)); return this.WithRawEntry(string.Format("<{0}>{1}</{0}>", tagName, value));
} }
public ConfigXmlBuilder WithRunawayProcessKiller(RunawayProcessKillerExtension ext, string extensionId = "killRunawayProcess", bool enabled = true) public ConfigXmlBuilder WithRunawayProcessKiller(RunawayProcessKillerExtension ext, string extensionId = "killRunawayProcess", bool enabled = true)
@ -118,7 +127,7 @@ namespace winswTests.Util
str.AppendFormat(" <stopTimeout>{0}</stopTimeout>\n", ext.StopTimeout.TotalMilliseconds); str.AppendFormat(" <stopTimeout>{0}</stopTimeout>\n", ext.StopTimeout.TotalMilliseconds);
str.AppendFormat(" <checkWinSWEnvironmentVariable>{0}</checkWinSWEnvironmentVariable>\n", ext.CheckWinSWEnvironmentVariable); str.AppendFormat(" <checkWinSWEnvironmentVariable>{0}</checkWinSWEnvironmentVariable>\n", ext.CheckWinSWEnvironmentVariable);
str.Append(" </extension>\n"); str.Append(" </extension>\n");
ExtensionXmls.Add(str.ToString()); this.ExtensionXmls.Add(str.ToString());
return this; return this;
} }
@ -129,10 +138,10 @@ namespace winswTests.Util
xml.Append($"<download from=\"{download.From}\" to=\"{download.To}\" failOnError=\"{download.FailOnError}\""); xml.Append($"<download from=\"{download.From}\" to=\"{download.To}\" failOnError=\"{download.FailOnError}\"");
// Authentication // Authentication
if (download.Auth != Download.AuthType.none) if (download.Auth != Download.AuthType.None)
{ {
xml.Append($" auth=\"{download.Auth}\""); xml.Append($" auth=\"{download.Auth}\"");
if (download.Auth == Download.AuthType.basic) if (download.Auth == Download.AuthType.Basic)
{ {
string username = download.Username; string username = download.Username;
if (username != null) if (username != null)
@ -155,12 +164,12 @@ namespace winswTests.Util
xml.Append("/>"); xml.Append("/>");
return WithRawEntry(xml.ToString()); return this.WithRawEntry(xml.ToString());
} }
public ConfigXmlBuilder WithDelayedAutoStart() public ConfigXmlBuilder WithDelayedAutoStart()
{ {
return WithRawEntry("<delayedAutoStart/>"); return this.WithRawEntry("<delayedAutoStart/>");
} }
} }
} }

View File

@ -1,6 +1,6 @@
using System; using System;
namespace winswTests.Util namespace WinSW.Tests.Util
{ {
internal static class DateTimeExtensions internal static class DateTimeExtensions
{ {

View File

@ -1,9 +1,9 @@
using System; using System;
using System.IO; using System.IO;
namespace winswTests.Util namespace WinSW.Tests.Util
{ {
class FilesystemTestHelper internal class FilesystemTestHelper
{ {
/// <summary> /// <summary>
/// Creates a temporary directory for testing. /// Creates a temporary directory for testing.

View File

@ -1,16 +1,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using winsw; using WinSW.Configuration;
using winsw.Configuration;
using Xunit; using Xunit;
namespace winswTests.Util namespace WinSW.Tests.Util
{ {
public static class ServiceDescriptorAssert public static class ServiceDescriptorAssert
{ {
// TODO: convert to Extension attributes once the .NET dependency is upgraded // TODO: convert to Extension attributes once the .NET dependency is upgraded
// BTW there is a way to get them working in .NET2, but KISS // BTW there is a way to get them working in .NET2, but KISS
public static void AssertPropertyIsDefault(ServiceDescriptor desc, string property) public static void AssertPropertyIsDefault(ServiceDescriptor desc, string property)
{ {
PropertyInfo actualProperty = typeof(ServiceDescriptor).GetProperty(property); PropertyInfo actualProperty = typeof(ServiceDescriptor).GetProperty(property);

7
src/stylecop.json Normal file
View File

@ -0,0 +1,7 @@
{
"settings": {
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace"
}
}
}

View File

@ -1,12 +1,13 @@
Microsoft Visual Studio Solution File, Format Version 12.00 
# Visual Studio 2013 Microsoft Visual Studio Solution File, Format Version 12.00
VisualStudioVersion = 12.0.31101.0 # Visual Studio Version 16
VisualStudioVersion = 16.0.30128.74
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "winsw", "Core\ServiceWrapper\winsw.csproj", "{0DE77F55-ADE5-43C1-999A-0BC81153B039}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "winswTests", "Test\winswTests\winswTests.csproj", "{93843402-842B-44B4-B303-AEE829BE0B43}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedDirectoryMapper", "Plugins\SharedDirectoryMapper\SharedDirectoryMapper.csproj", "{CA5C71DB-C5A8-4C27-BF83-8E6DAED9D6B5}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{077C2CEC-B687-4B53-86E9-C1A1BF5554E5}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{077C2CEC-B687-4B53-86E9-C1A1BF5554E5}"
EndProject EndProject
@ -14,12 +15,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{BC4A
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5297623A-1A95-4F89-9AAE-DA634081EC86}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5297623A-1A95-4F89-9AAE-DA634081EC86}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinSWCore", "Core\WinSWCore\WinSWCore.csproj", "{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunawayProcessKiller", "Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj", "{57284B7A-82A4-407A-B706-EBEA6BF8EA13}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RunawayProcessKiller", "Plugins\RunawayProcessKiller\RunawayProcessKiller.csproj", "{57284B7A-82A4-407A-B706-EBEA6BF8EA13}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AA414F46-B863-473A-A0E0-C2971B3396AE}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AA414F46-B863-473A-A0E0-C2971B3396AE}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
..\samples\sample-complete.xml = ..\samples\sample-complete.xml ..\samples\sample-complete.xml = ..\samples\sample-complete.xml
..\samples\sample-minimal.xml = ..\samples\sample-minimal.xml ..\samples\sample-minimal.xml = ..\samples\sample-minimal.xml
EndProjectSection EndProjectSection
@ -83,4 +85,7 @@ Global
{9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06} = {5297623A-1A95-4F89-9AAE-DA634081EC86} {9D0C63E2-B6FF-4A85-BD36-B3E5D7F27D06} = {5297623A-1A95-4F89-9AAE-DA634081EC86}
{57284B7A-82A4-407A-B706-EBEA6BF8EA13} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D} {57284B7A-82A4-407A-B706-EBEA6BF8EA13} = {BC4AD891-E87E-4F30-867C-FD8084A29E5D}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F9F8AFEA-196D-4041-8441-FABC3B730F9E}
EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,3 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SW/@EntryIndexedValue">SW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UNC/@EntryIndexedValue">UNC</s:String></wpf:ResourceDictionary>