source code checkin

This commit is contained in:
Brett Hewitson 2021-01-07 16:23:23 +10:00
parent 5f8fb2c825
commit 7e57b72e35
675 changed files with 168433 additions and 0 deletions

View file

@ -0,0 +1,53 @@
using ServerManagerTool.Common.Interfaces;
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace ServerManagerTool.Common.Lib
{
/// <summary>
/// This class ensures the following:
/// 1. All work items run in the order posted.
/// 2. Work items run on a background thread.
/// 3. Work items do not overlap
/// 4. If requested, the completion status of a work item is returned via a task.
/// </summary>
public class ActionQueue : IAsyncDisposable
{
public ActionBlock<Action> workQueue;
public ActionQueue(TaskScheduler scheduler = null)
{
this.workQueue = new ActionBlock<Action>(a => a.Invoke(), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1, TaskScheduler = scheduler ?? TaskScheduler.Default });
}
public Task<T> PostAction<T>(Func<T> action)
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
this.workQueue.Post(() =>
{
try
{
var result = action.Invoke();
Task.Run(() => tcs.TrySetResult(result));
}
catch(Exception ex)
{
Task.Run(() => tcs.TrySetException(ex));
}
});
return tcs.Task;
}
public Task PostAction(Action action)
{
return PostAction(() => { action.Invoke(); return true; });
}
public async Task DisposeAsync()
{
await PostAction(() => this.workQueue.Complete());
}
}
}

View file

@ -0,0 +1,21 @@
using System.Windows;
namespace ServerManagerTool.Common.Lib
{
public class BindingProxy : Freezable
{
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
}
}

View file

@ -0,0 +1,32 @@
using System.Windows;
using System.Windows.Controls;
namespace ServerManagerTool.Common.Lib
{
public static class BrowserBehavior
{
public static readonly DependencyProperty HtmlProperty = DependencyProperty.RegisterAttached("Html", typeof(string), typeof(BrowserBehavior), new FrameworkPropertyMetadata(OnHtmlChanged));
[AttachedPropertyBrowsableForType(typeof(WebBrowser))]
public static string GetHtml(WebBrowser d)
{
return (string)d.GetValue(HtmlProperty);
}
public static void SetHtml(WebBrowser d, string value)
{
d.SetValue(HtmlProperty, value);
}
static void OnHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
return;
if (d is WebBrowser wb)
{
wb.NavigateToString(e.NewValue as string);
}
}
}
}

View file

@ -0,0 +1,15 @@
using System;
namespace ServerManagerTool.Common.Lib
{
public class CommandListener : IDisposable
{
public Action<ConsoleCommand> Callback { get; set; }
public Action<CommandListener> DisposeAction { get; set; }
public void Dispose()
{
DisposeAction(this);
}
}
}

View file

@ -0,0 +1,18 @@
using ServerManagerTool.Common.Enums;
using System.Collections.Generic;
namespace ServerManagerTool.Common.Lib
{
public class ConsoleCommand
{
public ConsoleStatus status;
public string rawCommand;
public string command;
public string args;
public bool suppressCommand;
public bool suppressOutput;
public IEnumerable<string> lines = new string[0];
}
}

View file

@ -0,0 +1,179 @@
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace ServerManagerTool.Common.Lib
{
public static class SortBehavior
{
public static readonly DependencyProperty CanUserSortColumnsProperty = DependencyProperty.RegisterAttached("CanUserSortColumns", typeof(bool), typeof(SortBehavior), new FrameworkPropertyMetadata(OnCanUserSortColumnsChanged));
public static readonly DependencyProperty CanUseSortProperty = DependencyProperty.RegisterAttached("CanUseSort", typeof(bool), typeof(SortBehavior), new FrameworkPropertyMetadata(true));
public static readonly DependencyProperty SortDirectionProperty = DependencyProperty.RegisterAttached("SortDirection", typeof(ListSortDirection?), typeof(SortBehavior));
public static readonly DependencyProperty SortExpressionProperty = DependencyProperty.RegisterAttached("SortExpression", typeof(string), typeof(SortBehavior));
public static readonly DependencyProperty IsDefaultSortProperty = DependencyProperty.RegisterAttached("IsDefaultSort", typeof(bool), typeof(SortBehavior), new FrameworkPropertyMetadata(false));
[AttachedPropertyBrowsableForType(typeof(ListView))]
public static bool GetCanUserSortColumns(ListView element)
{
return (bool)(element?.GetValue(CanUserSortColumnsProperty) ?? false);
}
[AttachedPropertyBrowsableForType(typeof(ListView))]
public static void SetCanUserSortColumns(ListView element, bool value)
{
element?.SetValue(CanUserSortColumnsProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static bool GetCanUseSort(GridViewColumn element)
{
return (bool)(element?.GetValue(CanUseSortProperty) ?? false);
}
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static void SetCanUseSort(GridViewColumn element, bool value)
{
element?.SetValue(CanUseSortProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static ListSortDirection? GetSortDirection(GridViewColumn element)
{
return (ListSortDirection?)element.GetValue(SortDirectionProperty);
}
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static void SetSortDirection(GridViewColumn element, ListSortDirection? value)
{
element?.SetValue(SortDirectionProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static string GetSortExpression(GridViewColumn element)
{
return (string)element?.GetValue(SortExpressionProperty);
}
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static void SetSortExpression(GridViewColumn element, string value)
{
element?.SetValue(SortExpressionProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static bool GetIsDefaultSort(GridViewColumn element)
{
return (bool)(element?.GetValue(IsDefaultSortProperty) ?? false);
}
[AttachedPropertyBrowsableForType(typeof(GridViewColumn))]
public static void SetIsDefaultSort(GridViewColumn element, bool value)
{
element?.SetValue(IsDefaultSortProperty, value);
}
private static void OnCanUserSortColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listView = (ListView)d;
if ((bool)e.NewValue)
{
listView.AddHandler(GridViewColumnHeader.ClickEvent, (RoutedEventHandler)OnColumnHeaderClick);
if (listView.IsLoaded)
{
DoInitialSort(listView);
}
else
{
listView.Loaded += OnLoaded;
}
}
else
{
listView.RemoveHandler(GridViewColumnHeader.ClickEvent, (RoutedEventHandler)OnColumnHeaderClick);
}
}
private static void OnLoaded(object sender, RoutedEventArgs e)
{
var listView = (ListView)e.Source;
listView.Loaded -= OnLoaded;
DoInitialSort(listView);
}
private static void DoInitialSort(ListView listView)
{
var gridView = (GridView)listView.View;
var column = gridView.Columns.FirstOrDefault(c => GetIsDefaultSort(c));
if (column != null)
{
DoSort(listView, column);
}
}
private static void OnColumnHeaderClick(object sender, RoutedEventArgs e)
{
var columnHeader = e.OriginalSource as GridViewColumnHeader;
if (columnHeader != null && GetCanUseSort(columnHeader.Column))
{
DoSort((ListView)e.Source, columnHeader.Column);
}
}
private static void DoSort(ListView listView, GridViewColumn newColumn)
{
var gridView = (GridView)listView.View;
var sortDescriptions = listView.Items.SortDescriptions;
var newDirection = ListSortDirection.Ascending;
var propertyPath = ResolveSortExpression(newColumn);
if (propertyPath != null)
{
if (sortDescriptions.Count > 0)
{
if (sortDescriptions[0].PropertyName == propertyPath)
{
newDirection = GetSortDirection(newColumn) == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
}
else
{
foreach (var column in gridView.Columns.Where(c => GetSortDirection(c) != null))
{
SetSortDirection(column, null);
}
}
sortDescriptions.Clear();
}
sortDescriptions.Add(new SortDescription(propertyPath, newDirection));
SetSortDirection(newColumn, newDirection);
// check if there is a default sort column
var defaultColumn = gridView.Columns.FirstOrDefault(c => GetIsDefaultSort(c));
if (defaultColumn != null)
{
var defaultPropertyPath = ResolveSortExpression(defaultColumn);
if (defaultPropertyPath != null && !defaultPropertyPath.Equals(propertyPath))
{
sortDescriptions.Add(new SortDescription(defaultPropertyPath, ListSortDirection.Ascending));
}
}
}
}
private static string ResolveSortExpression(GridViewColumn column)
{
var propertyPath = GetSortExpression(column);
if (propertyPath == null)
{
var binding = column.DisplayMemberBinding as Binding;
return binding != null ? binding.Path.Path : null;
}
return propertyPath;
}
}
}

View file

@ -0,0 +1,31 @@
using System.Net;
namespace ServerManagerTool.Common.Lib
{
public class NetworkAdapterEntry
{
public NetworkAdapterEntry(IPAddress address, string description)
{
this.IPAddress = address.ToString();
this.Description = description;
}
public NetworkAdapterEntry(string address, string description)
{
this.IPAddress = address;
this.Description = description;
}
public string IPAddress
{
get;
set;
}
public string Description
{
get;
set;
}
}
}

View file

@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace ServerManagerTool.Common.Lib
{
public sealed class PropertyChangeNotifier : DependencyObject, IDisposable
{
#region Member Variables
private WeakReference _propertySource;
#endregion // Member Variables
#region Constructor
public PropertyChangeNotifier(DependencyObject propertySource, string path)
: this(propertySource, new PropertyPath(path))
{
}
public PropertyChangeNotifier(DependencyObject propertySource, DependencyProperty property)
: this(propertySource, new PropertyPath(property))
{
}
public PropertyChangeNotifier(DependencyObject propertySource, DependencyProperty property, DependencyPropertyChangedEventHandler handler)
: this(propertySource, property)
{
this.ValueChanged += handler;
}
public PropertyChangeNotifier(DependencyObject propertySource, PropertyPath property)
{
if (null == propertySource)
throw new ArgumentNullException("propertySource");
if (null == property)
throw new ArgumentNullException("property");
this._propertySource = new WeakReference(propertySource);
Binding binding = new Binding();
binding.Path = property;
binding.Mode = BindingMode.OneWay;
binding.Source = propertySource;
BindingOperations.SetBinding(this, ValueProperty, binding);
}
public static IEnumerable<PropertyChangeNotifier> GetNotifiers(DependencyObject propertySource, IEnumerable<DependencyProperty> properties, DependencyPropertyChangedEventHandler handler)
{
foreach(var property in properties)
{
var notifier = new PropertyChangeNotifier(propertySource, property, handler);
yield return notifier;
}
}
#endregion // Constructor
#region PropertySource
public DependencyObject PropertySource
{
get
{
try
{
// note, it is possible that accessing the target property
// will result in an exception so ive wrapped this check
// in a try catch
return this._propertySource.IsAlive
? this._propertySource.Target as DependencyObject
: null;
}
catch
{
return null;
}
}
}
#endregion // PropertySource
#region Value
/// <summary>
/// Identifies the <see cref=”Value”/> dependency property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value),
typeof(object), typeof(PropertyChangeNotifier), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnPropertyChanged)));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PropertyChangeNotifier notifier = (PropertyChangeNotifier)d;
if (null != notifier.ValueChanged)
{
notifier.ValueChanged(notifier.PropertySource, e);
}
}
/// <summary>
/// Returns/sets the value of the property
/// </summary>
/// <seealso cref=”ValueProperty”/>
[Description("Returns/sets the value of the property")]
[Category("Behavior")]
[Bindable(true)]
public object Value
{
get
{
return (object)this.GetValue(PropertyChangeNotifier.ValueProperty);
}
set
{
this.SetValue(PropertyChangeNotifier.ValueProperty, value);
}
}
#endregion //Value
#region Events
public event DependencyPropertyChangedEventHandler ValueChanged;
#endregion // Events
#region IDisposable Members
public void Dispose()
{
BindingOperations.ClearBinding(this, ValueProperty);
}
#endregion
}
}

View file

@ -0,0 +1,84 @@
using System;
using System.Windows.Input;
namespace ServerManagerTool.Common.Lib
{
public class RelayCommand<T> : ICommand
{
#region Fields
readonly Action<T> _execute = null;
readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
/// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
///<summary>
///Defines the method that determines whether the command can execute in its current state.
///</summary>
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
///<returns>
///true if this command can be executed; otherwise, false.
///</returns>
public bool CanExecute(object parameter)
{
try
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
catch (Exception)
{
return false;
}
}
///<summary>
///Occurs when changes occur that affect whether or not the command should execute.
///</summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
///<summary>
///Defines the method to be called when the command is invoked.
///</summary>
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}
}

View file

@ -0,0 +1,24 @@
using ServerManagerTool.Common.Utils;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
namespace ServerManagerTool.Common.Lib
{
public static class ServerUpdater
{
public static Task<bool> UpgradeServerAsync(string steamCmdFile, string steamCmdArgs, string workingDirectory, string username, SecureString password, string serverInstallDirectory, DataReceivedEventHandler outputHandler, CancellationToken cancellationToken, ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal)
{
Directory.CreateDirectory(serverInstallDirectory);
return ProcessUtils.RunProcessAsync(steamCmdFile, steamCmdArgs, string.Empty, workingDirectory, username, password, outputHandler, cancellationToken, windowStyle);
}
public static Task<bool> UpgradeModsAsync(string steamCmdFile, string steamCmdArgs, string workingDirectory, string username, SecureString password, DataReceivedEventHandler outputHandler, CancellationToken cancellationToken, ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal)
{
return ProcessUtils.RunProcessAsync(steamCmdFile, steamCmdArgs, string.Empty, workingDirectory, username, password, outputHandler, cancellationToken, windowStyle);
}
}
}

View file

@ -0,0 +1,196 @@
using ServerManagerTool.Common.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace ServerManagerTool.Common.Lib
{
public delegate void ProgressDelegate(int progress, string message, bool newLine = true);
public class SteamCmdUpdater
{
public const string OUTPUT_PREFIX = "[UPDATER]";
public struct Update
{
public Update(string statusKey, float completionPercent)
{
this.StatusKey = statusKey;
this.CompletionPercent = completionPercent;
this.Cancelled = false;
this.FailureText = null;
}
public Update SetFailed(string failureText)
{
this.FailureText = failureText;
return this;
}
public static Update AsCompleted(string statusKey)
{
return new Update { StatusKey = statusKey, CompletionPercent = 100, Cancelled = false };
}
public static Update AsCancelled(string statusKey)
{
return new Update { StatusKey = statusKey, CompletionPercent = 100, Cancelled = true };
}
public string StatusKey;
public float CompletionPercent;
public bool Cancelled;
public string FailureText;
}
enum Status
{
CleaningSteamCmd,
DownloadingSteamCmd,
UnzippingSteamCmd,
RunningSteamCmd,
InstallSteamCmdComplete,
Complete,
Cancelled
}
Dictionary<Status, Update> statuses = new Dictionary<Status, Update>()
{
{ Status.CleaningSteamCmd, new Update("AutoUpdater_Status_CleaningSteamCmd", 0) },
{ Status.DownloadingSteamCmd, new Update("AutoUpdater_Status_DownloadingSteamCmd", 10) },
{ Status.UnzippingSteamCmd, new Update("AutoUpdater_Status_UnzippingSteamCmd", 30) },
{ Status.RunningSteamCmd, new Update("AutoUpdater_Status_RunningSteamCmd", 50) },
{ Status.InstallSteamCmdComplete, new Update("AutoUpdater_Status_InstallSteamCmdComplete", 80) },
{ Status.Complete, Update.AsCompleted("AutoUpdater_Status_Complete") },
{ Status.Cancelled, Update.AsCancelled("AutoUpdater_Status_Cancelled") }
};
public static string GetSteamCmdFile(string dataPath) => IOUtils.NormalizePath(Path.Combine(dataPath, CommonConfig.Default.SteamCmdRelativePath, CommonConfig.Default.SteamCmdExeFile));
public async Task ReinstallSteamCmdAsync(string dataPath, IProgress<Update> reporter, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(dataPath))
return;
try
{
reporter?.Report(statuses[Status.CleaningSteamCmd]);
string steamCmdDirectory = Path.Combine(dataPath, CommonConfig.Default.SteamCmdRelativePath);
if (Directory.Exists(steamCmdDirectory))
{
Directory.Delete(steamCmdDirectory, true);
}
await Task.Delay(5000);
await InstallSteamCmdAsync(dataPath, reporter, cancellationToken);
reporter?.Report(statuses[Status.InstallSteamCmdComplete]);
reporter?.Report(statuses[Status.Complete]);
}
catch (TaskCanceledException)
{
reporter?.Report(statuses[Status.Cancelled]);
}
catch (Exception ex)
{
reporter?.Report(statuses[Status.Complete].SetFailed(ex.ToString()));
}
}
private async Task InstallSteamCmdAsync(string dataPath, IProgress<Update> reporter, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(dataPath))
return;
string steamCmdDirectory = Path.Combine(dataPath, CommonConfig.Default.SteamCmdRelativePath);
if (!Directory.Exists(steamCmdDirectory))
{
Directory.CreateDirectory(steamCmdDirectory);
}
reporter?.Report(statuses[Status.DownloadingSteamCmd]);
// Get SteamCmd.exe if necessary
string steamCmdPath = Path.Combine(steamCmdDirectory, CommonConfig.Default.SteamCmdExeFile);
if (!File.Exists(steamCmdPath))
{
// download the SteamCMD zip file
var steamZipPath = Path.Combine(steamCmdDirectory, CommonConfig.Default.SteamCmdZipFile);
using (var webClient = new WebClient())
{
using (var cancelRegistration = cancellationToken.Register(webClient.CancelAsync))
{
await webClient.DownloadFileTaskAsync(CommonConfig.Default.SteamCmdUrl, steamZipPath);
}
}
// Unzip the downloaded file
reporter?.Report(statuses[Status.UnzippingSteamCmd]);
ZipFile.ExtractToDirectory(steamZipPath, steamCmdDirectory);
File.Delete(steamZipPath);
// Run the SteamCmd updater
reporter?.Report(statuses[Status.RunningSteamCmd]);
var arguments = SteamUtils.BuildSteamCmdArguments(false, CommonConfig.Default.SteamCmdInstallArgs);
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = steamCmdPath,
Arguments = arguments,
UseShellExecute = false,
};
var process = Process.Start(startInfo);
process.EnableRaisingEvents = true;
var ts = new TaskCompletionSource<bool>();
using (var cancelRegistration = cancellationToken.Register(() =>
{
try
{
process.Kill();
}
finally
{
ts.TrySetCanceled();
}
}))
{
process.Exited += (s, e) =>
{
ts.TrySetResult(process.ExitCode == 0);
};
await ts.Task;
}
}
return;
}
public async void UpdateSteamCmdAsync(string dataPath, IProgress<Update> reporter, CancellationToken cancellationToken)
{
try
{
await InstallSteamCmdAsync(dataPath, reporter, cancellationToken);
reporter?.Report(statuses[Status.InstallSteamCmdComplete]);
reporter?.Report(statuses[Status.Complete]);
}
catch (TaskCanceledException)
{
reporter?.Report(statuses[Status.Cancelled]);
}
catch(Exception ex)
{
reporter?.Report(statuses[Status.Complete].SetFailed(ex.ToString()));
}
}
}
}

View file

@ -0,0 +1,29 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Linq;
using System.Reflection;
namespace ServerManagerTool.Common.Lib
{
public class UserScopedSettingContractResolver : DefaultContractResolver
{
public static readonly UserScopedSettingContractResolver Instance = new UserScopedSettingContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
var customAttributes = member.CustomAttributes?.ToArray() ?? new CustomAttributeData[0];
if (customAttributes.Any(a => a.AttributeType == typeof(System.Configuration.UserScopedSettingAttribute)))
{
property.ShouldSerialize = instance => { return property.PropertyType.IsValueType || property.PropertyType == typeof(string); };
}
else
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}
}
}