mirror of
https://github.com/tribufu/ServerManagers
synced 2026-05-06 15:17:34 +00:00
source code checkin
This commit is contained in:
parent
5f8fb2c825
commit
7e57b72e35
675 changed files with 168433 additions and 0 deletions
36
src/ConanServerManager/Lib/BranchSnapshot.cs
Normal file
36
src/ConanServerManager/Lib/BranchSnapshot.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class BranchSnapshot
|
||||
{
|
||||
public string BranchName = string.Empty;
|
||||
public string BranchPassword = string.Empty;
|
||||
}
|
||||
|
||||
public class BranchSnapshotComparer : IEqualityComparer<BranchSnapshot>
|
||||
{
|
||||
public bool Equals(BranchSnapshot x, BranchSnapshot y)
|
||||
{
|
||||
//Check whether the compared objects reference the same data.
|
||||
if (Object.ReferenceEquals(x, y)) return true;
|
||||
|
||||
//Check whether any of the compared objects is null.
|
||||
if (x is null || y is null)
|
||||
return false;
|
||||
|
||||
//Check whether the snapshot' properties are equal.
|
||||
return x.BranchName == y.BranchName;
|
||||
}
|
||||
|
||||
public int GetHashCode(BranchSnapshot snapshot)
|
||||
{
|
||||
//Check whether the object is null
|
||||
if (snapshot is null) return 0;
|
||||
|
||||
//Get hash code for the Name field if it is not null.
|
||||
return snapshot.BranchName == null ? 0 : snapshot.BranchName.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/ConanServerManager/Lib/Events/ProfileEventArgs.cs
Normal file
14
src/ConanServerManager/Lib/Events/ProfileEventArgs.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ProfileEventArgs : EventArgs
|
||||
{
|
||||
public ProfileEventArgs(ServerProfile profile)
|
||||
{
|
||||
Profile = profile;
|
||||
}
|
||||
|
||||
public ServerProfile Profile;
|
||||
}
|
||||
}
|
||||
98
src/ConanServerManager/Lib/GameData.cs
Normal file
98
src/ConanServerManager/Lib/GameData.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using ServerManagerTool.Common.Model;
|
||||
using ServerManagerTool.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using WPFSharp.Globalizer;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public static class GameData
|
||||
{
|
||||
public static string MainDataFolder = Path.Combine(Environment.CurrentDirectory, Config.Default.GameDataRelativePath);
|
||||
public static string UserDataFolder = Path.Combine(Config.Default.DataPath, Config.Default.GameDataRelativePath);
|
||||
|
||||
private static MainGameData gameData = null;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// read static game data
|
||||
GameDataUtils.ReadAllData(out gameData, MainDataFolder, Config.Default.GameDataExtension, Config.Default.GameDataApplication);
|
||||
|
||||
// read user game data
|
||||
MainGameData userGameData = new MainGameData();
|
||||
if (!UserDataFolder.Equals(MainDataFolder, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
GameDataUtils.ReadAllData(out userGameData, UserDataFolder, Config.Default.GameDataExtension, Config.Default.GameDataApplication, true);
|
||||
}
|
||||
|
||||
// game maps
|
||||
gameData.GameMaps.AddRange(userGameData.GameMaps);
|
||||
|
||||
if (gameData.GameMaps.Count > 0)
|
||||
{
|
||||
var maps = gameMaps.ToList();
|
||||
maps.AddRange(gameData.GameMaps.ConvertAll(item => new ComboBoxItem { ValueMember = item.ClassName, DisplayMember = item.Description }));
|
||||
|
||||
gameMaps = maps.ToArray();
|
||||
}
|
||||
|
||||
// branches
|
||||
gameData.Branches.AddRange(userGameData.Branches);
|
||||
|
||||
if (gameData.Branches.Count > 0)
|
||||
{
|
||||
var allBranches = branches.ToList();
|
||||
allBranches.AddRange(gameData.Branches.ConvertAll(item => new ComboBoxItem { ValueMember = item.BranchName, DisplayMember = item.Description }));
|
||||
|
||||
branches = allBranches.ToArray();
|
||||
}
|
||||
|
||||
// server regions
|
||||
gameData.ServerRegions.AddRange(userGameData.ServerRegions);
|
||||
|
||||
if (gameData.ServerRegions.Count > 0)
|
||||
{
|
||||
var allServerRegions = serverRegions.ToList();
|
||||
allServerRegions.AddRange(gameData.ServerRegions.ConvertAll(item => new ComboBoxItem { ValueMember = item.RegionNumber, DisplayMember = item.Description }));
|
||||
|
||||
serverRegions = allServerRegions.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static string FriendlyNameForClass(string className, bool returnNullIfNotFound = false) => string.IsNullOrWhiteSpace(className) ? (returnNullIfNotFound ? null : string.Empty) : GlobalizedApplication.Instance.GetResourceString(className) ?? (returnNullIfNotFound ? null : className);
|
||||
|
||||
#region Game Maps
|
||||
private static ComboBoxItem[] gameMaps = new ComboBoxItem[]
|
||||
{
|
||||
new ComboBoxItem { ValueMember="", DisplayMember="" },
|
||||
};
|
||||
|
||||
public static IEnumerable<ComboBoxItem> GetGameMaps() => gameMaps.Select(m => m.Duplicate());
|
||||
|
||||
public static string FriendlyMapNameForClass(string className, bool returnEmptyIfNotFound = false) => string.IsNullOrWhiteSpace(className) ? string.Empty : GlobalizedApplication.Instance.GetResourceString(className) ?? gameData?.GameMaps?.FirstOrDefault(i => i.ClassName.Equals(className))?.Description ?? (returnEmptyIfNotFound ? string.Empty : className);
|
||||
|
||||
public static string MapSaveNameForClass(string className, bool returnEmptyIfNotFound = false) => string.IsNullOrWhiteSpace(className) ? string.Empty : gameData?.GameMaps?.FirstOrDefault(i => i.ClassName.Equals(className))?.SaveFileName ?? (returnEmptyIfNotFound ? string.Empty : className);
|
||||
#endregion
|
||||
|
||||
#region Branches
|
||||
private static ComboBoxItem[] branches = new[]
|
||||
{
|
||||
new ComboBoxItem { ValueMember="", DisplayMember=FriendlyNameForClass(Config.Default.DefaultServerBranchName) },
|
||||
};
|
||||
|
||||
public static IEnumerable<ComboBoxItem> GetBranches() => branches.Select(d => d.Duplicate());
|
||||
|
||||
public static string FriendlyBranchName(string branchName, bool returnEmptyIfNotFound = false) => string.IsNullOrWhiteSpace(branchName) ? string.Empty : GlobalizedApplication.Instance.GetResourceString(branchName) ?? gameData?.Branches?.FirstOrDefault(i => i.BranchName.Equals(branchName))?.Description ?? (returnEmptyIfNotFound ? string.Empty : branchName);
|
||||
#endregion
|
||||
|
||||
#region Server Regions
|
||||
private static ComboBoxItem[] serverRegions = new ComboBoxItem[0];
|
||||
|
||||
public static IEnumerable<ComboBoxItem> GetServerRegions() => serverRegions.Select(d => d.Duplicate());
|
||||
|
||||
public static string FriendlyServerRegionName(string regionNumber, bool returnEmptyIfNotFound = false) => string.IsNullOrWhiteSpace(regionNumber) ? string.Empty : GlobalizedApplication.Instance.GetResourceString($"ServerRegion_{regionNumber}") ?? gameData?.ServerRegions?.FirstOrDefault(i => i.RegionNumber.Equals(regionNumber))?.Description ?? (returnEmptyIfNotFound ? string.Empty : regionNumber);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
242
src/ConanServerManager/Lib/Model/ModDetail.cs
Normal file
242
src/ConanServerManager/Lib/Model/ModDetail.cs
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
using ServerManagerTool.Common.Model;
|
||||
using ServerManagerTool.Common.Utils;
|
||||
using ServerManagerTool.Utils;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using WPFSharp.Globalizer;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ModDetail : DependencyObject
|
||||
{
|
||||
private readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance;
|
||||
|
||||
public static readonly DependencyProperty AppIdProperty = DependencyProperty.Register(nameof(AppId), typeof(string), typeof(ModDetail), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty IndexProperty = DependencyProperty.Register(nameof(Index), typeof(int), typeof(ModDetail), new PropertyMetadata(0));
|
||||
public static readonly DependencyProperty IsFirstProperty = DependencyProperty.Register(nameof(IsFirst), typeof(bool), typeof(ModDetail), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsLastProperty = DependencyProperty.Register(nameof(IsLast), typeof(bool), typeof(ModDetail), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty LastWriteTimeProperty = DependencyProperty.Register(nameof(LastWriteTime), typeof(DateTime), typeof(ModDetail), new PropertyMetadata(DateTime.MinValue));
|
||||
public static readonly DependencyProperty LastTimeUpdatedProperty = DependencyProperty.Register(nameof(LastTimeUpdated), typeof(int), typeof(ModDetail), new PropertyMetadata(0));
|
||||
public static readonly DependencyProperty ModIdProperty = DependencyProperty.Register(nameof(ModId), typeof(string), typeof(ModDetail), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty ModTypeProperty = DependencyProperty.Register(nameof(ModType), typeof(string), typeof(ModDetail), new PropertyMetadata(ModUtils.MODTYPE_UNKNOWN));
|
||||
public static readonly DependencyProperty ModTypeStringProperty = DependencyProperty.Register(nameof(ModTypeString), typeof(string), typeof(ModDetail), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty ModUrlProperty = DependencyProperty.Register(nameof(ModUrl), typeof(string), typeof(ModDetail), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty TimeUpdatedProperty = DependencyProperty.Register(nameof(TimeUpdated), typeof(int), typeof(ModDetail), new PropertyMetadata(0));
|
||||
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(ModDetail), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty IsValidProperty = DependencyProperty.Register(nameof(IsValid), typeof(bool), typeof(ModDetail), new PropertyMetadata(false));
|
||||
|
||||
public string AppId
|
||||
{
|
||||
get { return (string)GetValue(AppIdProperty); }
|
||||
set { SetValue(AppIdProperty, value); }
|
||||
}
|
||||
|
||||
public int Index
|
||||
{
|
||||
get { return (int)GetValue(IndexProperty); }
|
||||
set { SetValue(IndexProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsFirst
|
||||
{
|
||||
get { return (bool)GetValue(IsFirstProperty); }
|
||||
set { SetValue(IsFirstProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsLast
|
||||
{
|
||||
get { return (bool)GetValue(IsLastProperty); }
|
||||
set { SetValue(IsLastProperty, value); }
|
||||
}
|
||||
|
||||
public DateTime LastWriteTime
|
||||
{
|
||||
get { return (DateTime)GetValue(LastWriteTimeProperty); }
|
||||
set { SetValue(LastWriteTimeProperty, value); }
|
||||
}
|
||||
|
||||
public int LastTimeUpdated
|
||||
{
|
||||
get { return (int)GetValue(LastTimeUpdatedProperty); }
|
||||
set { SetValue(LastTimeUpdatedProperty, value); }
|
||||
}
|
||||
|
||||
public string ModId
|
||||
{
|
||||
get { return (string)GetValue(ModIdProperty); }
|
||||
set { SetValue(ModIdProperty, value); }
|
||||
}
|
||||
|
||||
public string ModType
|
||||
{
|
||||
get { return (string)GetValue(ModTypeProperty); }
|
||||
set
|
||||
{
|
||||
SetValue(ModTypeProperty, value);
|
||||
SetModTypeString();
|
||||
}
|
||||
}
|
||||
|
||||
public string ModTypeString
|
||||
{
|
||||
get { return (string)GetValue(ModTypeStringProperty); }
|
||||
set { SetValue(ModTypeStringProperty, value); }
|
||||
}
|
||||
|
||||
public int TimeUpdated
|
||||
{
|
||||
get { return (int)GetValue(TimeUpdatedProperty); }
|
||||
set { SetValue(TimeUpdatedProperty, value); }
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return (string)GetValue(TitleProperty); }
|
||||
set
|
||||
{
|
||||
SetValue(TitleProperty, value);
|
||||
|
||||
TitleFilterString = value?.ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get { return (bool)GetValue(IsValidProperty); }
|
||||
set { SetValue(IsValidProperty, value); }
|
||||
}
|
||||
|
||||
|
||||
public bool IsValidModType => !string.IsNullOrWhiteSpace(ModType) && (ModType.Equals(ModUtils.MODTYPE_MAP) || ModType.Equals(ModUtils.MODTYPE_MOD));
|
||||
|
||||
public string LastWriteTimeString => LastWriteTime == DateTime.MinValue ? string.Empty : LastWriteTime.ToString();
|
||||
|
||||
public string LastWriteTimeSortString => LastWriteTime == DateTime.MinValue ? string.Empty : LastWriteTime.ToString("yyyyMMdd_HHmmss");
|
||||
|
||||
public string MapName { get; set; }
|
||||
|
||||
public string ModUrl => $"http://steamcommunity.com/sharedfiles/filedetails/?id={ModId}";
|
||||
|
||||
public string TimeUpdatedString => TimeUpdated <= 0 ? string.Empty : DateTimeUtils.UnixTimeStampToDateTime(TimeUpdated).ToString();
|
||||
|
||||
public string TimeUpdatedSortString => TimeUpdated <= 0 ? string.Empty : DateTimeUtils.UnixTimeStampToDateTime(TimeUpdated).ToString("yyyyMMdd_HHmmss");
|
||||
|
||||
public string TitleFilterString
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool UpToDate => !IsValid && TimeUpdated == -1 || LastTimeUpdated > 0 && LastTimeUpdated == TimeUpdated;
|
||||
|
||||
public long FolderSize { get; set; }
|
||||
|
||||
public string FolderSizeString
|
||||
{
|
||||
get
|
||||
{
|
||||
// GB
|
||||
var divisor = Math.Pow(1024, 3);
|
||||
if (FolderSize > divisor)
|
||||
return $"{FolderSize / divisor:N2} GB";
|
||||
|
||||
// MB
|
||||
divisor = Math.Pow(1024, 2);
|
||||
if (FolderSize > divisor)
|
||||
return $"{FolderSize / divisor:N2} MB";
|
||||
|
||||
// KB
|
||||
divisor = Math.Pow(1024, 1);
|
||||
if (FolderSize > divisor)
|
||||
return $"{FolderSize / divisor:N2} KB";
|
||||
|
||||
return $"{FolderSize} B";
|
||||
}
|
||||
}
|
||||
|
||||
public void PopulateExtended(string modsRootFolder)
|
||||
{
|
||||
var modExtended = new ModDetailExtended(ModId);
|
||||
modExtended.PopulateExtended(modsRootFolder);
|
||||
PopulateExtended(modExtended);
|
||||
}
|
||||
|
||||
public void PopulateExtended(ModDetailExtended extended)
|
||||
{
|
||||
LastTimeUpdated = extended.LastTimeUpdated;
|
||||
LastWriteTime = extended.LastWriteTime;
|
||||
MapName = extended.MapName;
|
||||
ModType = extended.ModType;
|
||||
FolderSize = extended.FolderSize;
|
||||
}
|
||||
|
||||
public void SetModTypeString()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ModType))
|
||||
ModTypeString = _globalizer.GetResourceString("ModType_Unknown");
|
||||
|
||||
switch (ModType)
|
||||
{
|
||||
case ModUtils.MODTYPE_MAP:
|
||||
ModTypeString = _globalizer.GetResourceString("ModType_Map");
|
||||
break;
|
||||
|
||||
case ModUtils.MODTYPE_MOD:
|
||||
ModTypeString = _globalizer.GetResourceString("ModType_Mod");
|
||||
break;
|
||||
|
||||
default:
|
||||
if (string.IsNullOrWhiteSpace(AppId))
|
||||
ModTypeString = _globalizer.GetResourceString("ModType_Unknown");
|
||||
else
|
||||
ModTypeString = _globalizer.GetResourceString("ModType_NotDownloaded");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static ModDetail GetModDetail(PublishedFileDetail detail)
|
||||
{
|
||||
var result = new ModDetail()
|
||||
{
|
||||
AppId = detail.creator_app_id,
|
||||
ModId = detail.publishedfileid,
|
||||
TimeUpdated = detail.time_updated,
|
||||
Title = detail.title,
|
||||
IsValid = true,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ModDetail GetModDetail(WorkshopFileDetail detail)
|
||||
{
|
||||
var result = new ModDetail()
|
||||
{
|
||||
AppId = detail.creator_appid,
|
||||
ModId = detail.publishedfileid,
|
||||
TimeUpdated = detail.time_updated,
|
||||
Title = detail.title,
|
||||
IsValid = true,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ModDetail GetModDetail(WorkshopFileItem detail)
|
||||
{
|
||||
var result = new ModDetail()
|
||||
{
|
||||
AppId = detail.AppId,
|
||||
ModId = detail.WorkshopId,
|
||||
TimeUpdated = detail.TimeUpdated,
|
||||
Title = detail.Title,
|
||||
IsValid = true,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ModId} - {Title}";
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/ConanServerManager/Lib/Model/ModDetailExtended.cs
Normal file
61
src/ConanServerManager/Lib/Model/ModDetailExtended.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using ServerManagerTool.Utils;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ModDetailExtended
|
||||
{
|
||||
public ModDetailExtended(string modId)
|
||||
{
|
||||
ModId = modId;
|
||||
}
|
||||
|
||||
public string MapName { get; set; }
|
||||
|
||||
private string ModId { get; set; }
|
||||
|
||||
public string ModType { get; set; }
|
||||
|
||||
public DateTime LastWriteTime { get; set; }
|
||||
|
||||
public int LastTimeUpdated { get; set; }
|
||||
|
||||
public long FolderSize { get; set; }
|
||||
|
||||
public void PopulateExtended(string modsRootFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
FolderSize = 0;
|
||||
LastWriteTime = DateTime.MinValue;
|
||||
ModType = ModUtils.MODTYPE_UNKNOWN;
|
||||
MapName = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(modsRootFolder) || !Directory.Exists(modsRootFolder))
|
||||
return;
|
||||
|
||||
var modFileName = $"{ModId}.pak";
|
||||
var modFile = Path.Combine(modsRootFolder, modFileName);
|
||||
if (!string.IsNullOrWhiteSpace(modFile) && File.Exists(modFile))
|
||||
{
|
||||
var file = new FileInfo(modFile);
|
||||
LastWriteTime = file.LastWriteTime;
|
||||
FolderSize += file.Length;
|
||||
ModType = ModUtils.MODTYPE_MOD;
|
||||
}
|
||||
|
||||
var timeFileName = $"{ModId}.txt";
|
||||
var modTimeFile = Path.Combine(modsRootFolder, timeFileName);
|
||||
if (!string.IsNullOrWhiteSpace(modTimeFile) && File.Exists(modTimeFile))
|
||||
{
|
||||
LastTimeUpdated = ModUtils.GetModLatestTime(modTimeFile);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
200
src/ConanServerManager/Lib/Model/ModDetailList.cs
Normal file
200
src/ConanServerManager/Lib/Model/ModDetailList.cs
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
using ServerManagerTool.Common.Model;
|
||||
using ServerManagerTool.Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ModDetailList : ObservableCollection<ModDetail>
|
||||
{
|
||||
public bool AnyUnknownModTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Any(m => !m.IsValidModType);
|
||||
}
|
||||
}
|
||||
|
||||
public new void Add(ModDetail mod)
|
||||
{
|
||||
if (mod == null || this.Any(m => m.ModId.Equals(mod.ModId)))
|
||||
return;
|
||||
|
||||
base.Add(mod);
|
||||
SetPublishedFileIndex();
|
||||
}
|
||||
|
||||
public void AddRange(ModDetail[] mods)
|
||||
{
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
if (mod == null || this.Any(m => m.ModId.Equals(mod.ModId)))
|
||||
continue;
|
||||
base.Add(mod);
|
||||
}
|
||||
SetPublishedFileIndex();
|
||||
}
|
||||
|
||||
public new void Insert(int index, ModDetail mod)
|
||||
{
|
||||
if (mod == null || this.Any(m => m.ModId.Equals(mod.ModId)))
|
||||
return;
|
||||
|
||||
base.Insert(index, mod);
|
||||
SetPublishedFileIndex();
|
||||
}
|
||||
|
||||
public void Move(ModDetail mod, int newIndex)
|
||||
{
|
||||
if (mod == null)
|
||||
return;
|
||||
|
||||
var index = base.IndexOf(mod);
|
||||
if (index <= 0)
|
||||
return;
|
||||
|
||||
base.Move(index, newIndex);
|
||||
SetPublishedFileIndex();
|
||||
}
|
||||
|
||||
public void MoveDown(ModDetail mod)
|
||||
{
|
||||
if (mod == null)
|
||||
return;
|
||||
|
||||
var index = base.IndexOf(mod);
|
||||
if (index >= base.Count - 1)
|
||||
return;
|
||||
|
||||
base.Move(index, index + 1);
|
||||
SetPublishedFileIndex();
|
||||
}
|
||||
|
||||
public void MoveUp(ModDetail mod)
|
||||
{
|
||||
if (mod == null)
|
||||
return;
|
||||
|
||||
var index = base.IndexOf(mod);
|
||||
if (index <= 0)
|
||||
return;
|
||||
|
||||
base.Move(index, index - 1);
|
||||
SetPublishedFileIndex();
|
||||
}
|
||||
|
||||
public void PopulateExtended(string modsRootFolder)
|
||||
{
|
||||
var results = new Dictionary<ModDetail, ModDetailExtended>();
|
||||
foreach (var mod in this)
|
||||
{
|
||||
results.Add(mod, new ModDetailExtended(mod.ModId));
|
||||
}
|
||||
|
||||
Parallel.ForEach(results, kvp => kvp.Value.PopulateExtended(modsRootFolder));
|
||||
|
||||
foreach (var kvp in results)
|
||||
{
|
||||
kvp.Key.PopulateExtended(kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public new bool Remove(ModDetail mod)
|
||||
{
|
||||
if (mod == null)
|
||||
return false;
|
||||
|
||||
var removed = base.Remove(mod);
|
||||
|
||||
SetPublishedFileIndex();
|
||||
return removed;
|
||||
}
|
||||
|
||||
public void SetPublishedFileIndex()
|
||||
{
|
||||
foreach (var mod in this)
|
||||
{
|
||||
mod.Index = base.IndexOf(mod) + 1;
|
||||
mod.IsFirst = false;
|
||||
mod.IsLast = false;
|
||||
}
|
||||
|
||||
if (this.Count == 0)
|
||||
return;
|
||||
|
||||
this[0].IsFirst = true;
|
||||
this[base.Count - 1].IsLast = true;
|
||||
}
|
||||
|
||||
public bool GetModStrings(out string mapString, out string modIdString)
|
||||
{
|
||||
mapString = null;
|
||||
modIdString = string.Empty;
|
||||
|
||||
var delimiter = "";
|
||||
foreach (var mod in this)
|
||||
{
|
||||
switch (mod.ModType)
|
||||
{
|
||||
case ModUtils.MODTYPE_MOD:
|
||||
default:
|
||||
modIdString += $"{delimiter}{mod.ModId}";
|
||||
delimiter = ",";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ModDetailList GetModDetails(List<string> modIdList, string modsRootFolder, WorkshopFileList workshopFiles, PublishedFileDetailsResponse response)
|
||||
{
|
||||
var result = new ModDetailList();
|
||||
|
||||
if (modIdList != null)
|
||||
{
|
||||
foreach (var modId in modIdList)
|
||||
{
|
||||
var temp = workshopFiles?.FirstOrDefault(w => w.WorkshopId.Equals(modId));
|
||||
|
||||
result.Add(new ModDetail()
|
||||
{
|
||||
AppId = temp?.AppId ?? string.Empty,
|
||||
ModId = modId,
|
||||
TimeUpdated = -1,
|
||||
Title = temp?.Title ?? "Mod name not available",
|
||||
IsValid = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (response?.publishedfiledetails != null)
|
||||
{
|
||||
foreach (var item in result)
|
||||
{
|
||||
var temp = response.publishedfiledetails.FirstOrDefault(w => w.publishedfileid.Equals(item.ModId));
|
||||
|
||||
if (temp != null)
|
||||
{
|
||||
item.AppId = temp?.creator_app_id ?? string.Empty;
|
||||
item.ModId = temp?.publishedfileid ?? item.ModId;
|
||||
item.TimeUpdated = temp?.time_updated ?? item.TimeUpdated;
|
||||
item.Title = temp?.title ?? item.Title;
|
||||
item.IsValid = temp?.creator_app_id != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.SetPublishedFileIndex();
|
||||
result.PopulateExtended(modsRootFolder);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(ModDetailList)} - {Count}";
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/ConanServerManager/Lib/Model/PlayerListParameters.cs
Normal file
34
src/ConanServerManager/Lib/Model/PlayerListParameters.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using System.Windows;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class PlayerListParameters : DependencyObject
|
||||
{
|
||||
public static readonly DependencyProperty ProfileNameProperty = DependencyProperty.Register(nameof(ProfileName), typeof(string), typeof(PlayerListParameters), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty MaxPlayersProperty = DependencyProperty.Register(nameof(MaxPlayers), typeof(int), typeof(PlayerListParameters), new PropertyMetadata(0));
|
||||
|
||||
public string ProfileName
|
||||
{
|
||||
get { return (string)GetValue(ProfileNameProperty); }
|
||||
set { SetValue(ProfileNameProperty, value); }
|
||||
}
|
||||
|
||||
public string ProfileId { get; set; }
|
||||
|
||||
public string InstallDirectory { get; set; }
|
||||
|
||||
public string GameFile { get; set; }
|
||||
|
||||
public Server Server { get; set; }
|
||||
|
||||
public Rect WindowExtents { get; set; }
|
||||
|
||||
public string WindowTitle { get; set; }
|
||||
|
||||
public int MaxPlayers
|
||||
{
|
||||
get { return (int)GetValue(MaxPlayersProperty); }
|
||||
set { SetValue(MaxPlayersProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/ConanServerManager/Lib/Model/RconParameters.cs
Normal file
32
src/ConanServerManager/Lib/Model/RconParameters.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System.Net;
|
||||
using System.Windows;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class RconParameters : PlayerListParameters
|
||||
{
|
||||
public string RconHost { get; set; }
|
||||
|
||||
public IPAddress RconHostIP
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
var ipAddresses = Dns.GetHostAddresses(RconHost);
|
||||
if (ipAddresses.Length > 0)
|
||||
return ipAddresses[0].MapToIPv4();
|
||||
}
|
||||
catch {}
|
||||
|
||||
return IPAddress.None;
|
||||
}
|
||||
}
|
||||
|
||||
public int RconPort { get; set; }
|
||||
|
||||
public string RconPassword { get; set; }
|
||||
|
||||
public double PlayerListWidth { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using ServerManagerTool.Common.Attibutes;
|
||||
using ServerManagerTool.Enums;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class IniFileEntryAttribute : BaseIniFileEntryAttribute
|
||||
{
|
||||
public IniFileEntryAttribute(IniFiles file, IniSections section, ServerProfileCategory category, string key = "")
|
||||
: base(file, section, category, key)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/ConanServerManager/Lib/Serialization/IniFiles.cs
Normal file
9
src/ConanServerManager/Lib/Serialization/IniFiles.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public enum IniFiles
|
||||
{
|
||||
Engine,
|
||||
Game,
|
||||
ServerSettings,
|
||||
}
|
||||
}
|
||||
20
src/ConanServerManager/Lib/Serialization/IniSections.cs
Normal file
20
src/ConanServerManager/Lib/Serialization/IniSections.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public enum IniSections
|
||||
{
|
||||
// Engine.ini
|
||||
Engine_OnlineSubsystem,
|
||||
Engine_OnlineSubsystemSteam,
|
||||
Engine_URL,
|
||||
|
||||
// Game.ini
|
||||
Game_GameSession,
|
||||
Game_RconPlugin,
|
||||
|
||||
// ServerSettings.ini
|
||||
ServerSettings_ServerSettings,
|
||||
|
||||
// Misc
|
||||
Custom,
|
||||
}
|
||||
}
|
||||
46
src/ConanServerManager/Lib/Serialization/SystemIniFile.cs
Normal file
46
src/ConanServerManager/Lib/Serialization/SystemIniFile.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using ServerManagerTool.Common.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class SystemIniFile : BaseSystemIniFile
|
||||
{
|
||||
public static readonly Dictionary<Enum, string> IniFileNames = new Dictionary<Enum, string>
|
||||
{
|
||||
{ IniFiles.Engine, Config.Default.ServerEngineConfigFile },
|
||||
{ IniFiles.Game, Config.Default.ServerGameConfigFile },
|
||||
{ IniFiles.ServerSettings, Config.Default.ServerSettingsConfigFile },
|
||||
};
|
||||
|
||||
public static readonly Dictionary<Enum, string> IniSectionNames = new Dictionary<Enum, string>
|
||||
{
|
||||
// Engine sections, used by the server manager
|
||||
{ IniSections.Engine_OnlineSubsystem, "OnlineSubsystem" },
|
||||
{ IniSections.Engine_OnlineSubsystemSteam, "OnlineSubsystemSteam" },
|
||||
{ IniSections.Engine_URL, "URL" },
|
||||
|
||||
// Engine sections, not used by server manager
|
||||
|
||||
// Game sections, used by the server manager
|
||||
{ IniSections.Game_GameSession, "/Script/Engine.GameSession" },
|
||||
{ IniSections.Game_RconPlugin, "RconPlugin" },
|
||||
|
||||
// Game sections, not used by server manager
|
||||
|
||||
// Server Settings sections, used by the server manager
|
||||
{ IniSections.ServerSettings_ServerSettings, "ServerSettings" },
|
||||
|
||||
// Server Settings sections, not used by server manager
|
||||
};
|
||||
|
||||
public override Dictionary<Enum, string> FileNames => IniFileNames;
|
||||
|
||||
public override Dictionary<Enum, string> SectionNames => IniSectionNames;
|
||||
|
||||
public SystemIniFile(string iniPath)
|
||||
: base(iniPath)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/ConanServerManager/Lib/Server.cs
Normal file
105
src/ConanServerManager/Lib/Server.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
using ServerManagerTool.Common.Lib;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class Server : DependencyObject, IDisposable
|
||||
{
|
||||
public static readonly DependencyProperty ProfileProperty = DependencyProperty.Register(nameof(Profile), typeof(ServerProfile), typeof(Server), new PropertyMetadata((ServerProfile)null));
|
||||
public static readonly DependencyProperty RuntimeProperty = DependencyProperty.Register(nameof(Runtime), typeof(ServerRuntime), typeof(Server), new PropertyMetadata((ServerRuntime)null));
|
||||
public static readonly DependencyProperty SelectedProperty = DependencyProperty.Register(nameof(Selected), typeof(bool), typeof(Server), new PropertyMetadata(false));
|
||||
|
||||
public ServerProfile Profile
|
||||
{
|
||||
get { return (ServerProfile)GetValue(ProfileProperty); }
|
||||
protected set { SetValue(ProfileProperty, value); }
|
||||
}
|
||||
|
||||
public ServerRuntime Runtime
|
||||
{
|
||||
get { return (ServerRuntime)GetValue(RuntimeProperty); }
|
||||
protected set { SetValue(RuntimeProperty, value); }
|
||||
}
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get { return (bool)GetValue(SelectedProperty); }
|
||||
set { SetValue(SelectedProperty, value); }
|
||||
}
|
||||
|
||||
private Server(ServerProfile profile)
|
||||
{
|
||||
InitializeFromProfile(profile);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Profile.DestroyServerFilesWatcher();
|
||||
|
||||
this.Runtime.StatusUpdate -= Runtime_StatusUpdate;
|
||||
this.Runtime.Dispose();
|
||||
}
|
||||
|
||||
private void Runtime_StatusUpdate(object sender, EventArgs eventArgs)
|
||||
{
|
||||
this.Profile.LastInstalledVersion = this.Runtime.Version.ToString();
|
||||
}
|
||||
|
||||
public void ImportFromPath(string path, ServerProfile profile = null)
|
||||
{
|
||||
var loadedProfile = ServerProfile.LoadFrom(path, profile);
|
||||
if (loadedProfile != null)
|
||||
InitializeFromProfile(loadedProfile);
|
||||
}
|
||||
|
||||
private void InitializeFromProfile(ServerProfile profile)
|
||||
{
|
||||
if (profile == null)
|
||||
return;
|
||||
|
||||
this.Profile = profile;
|
||||
this.Runtime = new ServerRuntime();
|
||||
this.Runtime.AttachToProfile(this.Profile).Wait();
|
||||
|
||||
this.Runtime.StatusUpdate += Runtime_StatusUpdate;
|
||||
}
|
||||
|
||||
public static Server FromPath(string path)
|
||||
{
|
||||
var loadedProfile = ServerProfile.LoadFrom(path);
|
||||
if (loadedProfile == null)
|
||||
return null;
|
||||
return new Server(loadedProfile);
|
||||
}
|
||||
|
||||
public static Server FromDefaults()
|
||||
{
|
||||
var loadedProfile = ServerProfile.FromDefaults();
|
||||
if (loadedProfile == null)
|
||||
return null;
|
||||
return new Server(loadedProfile);
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
await this.Runtime.AttachToProfile(this.Profile);
|
||||
await this.Runtime.StartAsync();
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
await this.Runtime.StopAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> UpgradeAsync(CancellationToken cancellationToken, bool updateServer, BranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback)
|
||||
{
|
||||
await this.Runtime.AttachToProfile(this.Profile);
|
||||
var success = await this.Runtime.UpgradeAsync(cancellationToken, updateServer, branch, validate, updateMods, progressCallback);
|
||||
this.Profile.LastInstalledVersion = this.Runtime.Version.ToString();
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
3147
src/ConanServerManager/Lib/ServerApp.cs
Normal file
3147
src/ConanServerManager/Lib/ServerApp.cs
Normal file
File diff suppressed because it is too large
Load diff
135
src/ConanServerManager/Lib/ServerManager.cs
Normal file
135
src/ConanServerManager/Lib/ServerManager.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
using ServerManagerTool.Common.Model;
|
||||
using ServerManagerTool.Common.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is responsible for managing all of the servers the tool knows about.
|
||||
/// </summary>
|
||||
public class ServerManager : DependencyObject
|
||||
{
|
||||
static ServerManager()
|
||||
{
|
||||
ServerManager.Instance = new ServerManager();
|
||||
}
|
||||
|
||||
public static ServerManager Instance
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ServersProperty = DependencyProperty.Register(nameof(Servers), typeof(SortableObservableCollection<Server>), typeof(ServerManager), new PropertyMetadata(new SortableObservableCollection<Server>()));
|
||||
|
||||
public SortableObservableCollection<Server> Servers
|
||||
{
|
||||
get { return (SortableObservableCollection<Server>)GetValue(ServersProperty); }
|
||||
set { SetValue(ServersProperty, value); }
|
||||
}
|
||||
|
||||
public ServerManager()
|
||||
{
|
||||
this.Servers.CollectionChanged += Servers_CollectionChanged;
|
||||
}
|
||||
|
||||
void Servers_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if(e.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
foreach(Server server in e.OldItems)
|
||||
{
|
||||
server.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int AddFromPath(string path)
|
||||
{
|
||||
var server = Server.FromPath(path);
|
||||
if (server == null)
|
||||
return this.Servers.Count - 1;
|
||||
|
||||
this.Servers.Add(server);
|
||||
return this.Servers.Count - 1;
|
||||
}
|
||||
|
||||
public int AddNew()
|
||||
{
|
||||
var server = Server.FromDefaults();
|
||||
if (server == null)
|
||||
return this.Servers.Count - 1;
|
||||
|
||||
this.Servers.Add(server);
|
||||
return this.Servers.Count - 1;
|
||||
}
|
||||
|
||||
public void Remove(Server server, bool deleteProfile)
|
||||
{
|
||||
if (server == null)
|
||||
return;
|
||||
|
||||
// save the profile before deleting, just in case something needed has changed
|
||||
if (server.Profile != null)
|
||||
server.Profile.Save(false, false, null);
|
||||
|
||||
if (deleteProfile)
|
||||
{
|
||||
var profileFile = server.Profile?.GetProfileFile();
|
||||
if (!string.IsNullOrWhiteSpace(profileFile) && File.Exists(profileFile))
|
||||
{
|
||||
// set the file permissions
|
||||
SecurityUtils.SetFileOwnershipForAllUsers(profileFile);
|
||||
try
|
||||
{
|
||||
File.Delete(profileFile);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
var profileIniDir = server.Profile?.GetProfileConfigDir_Old();
|
||||
if (!string.IsNullOrWhiteSpace(profileIniDir) && Directory.Exists(profileIniDir))
|
||||
{
|
||||
// set the folder permissions
|
||||
SecurityUtils.SetDirectoryOwnershipForAllUsers(profileIniDir);
|
||||
try
|
||||
{
|
||||
Directory.Delete(profileIniDir, true);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
server.Runtime?.DeleteFirewallRules();
|
||||
server.Dispose();
|
||||
|
||||
this.Servers.Remove(server);
|
||||
}
|
||||
|
||||
public void CheckProfiles()
|
||||
{
|
||||
var serverIds = new Dictionary<string, bool>();
|
||||
foreach (var server in Servers)
|
||||
{
|
||||
if (server == null || server.Profile == null)
|
||||
continue;
|
||||
|
||||
while (serverIds.ContainsKey(server.Profile.ProfileID))
|
||||
{
|
||||
server.Profile.ResetProfileId();
|
||||
}
|
||||
|
||||
serverIds.Add(server.Profile.ProfileID, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void SortServers()
|
||||
{
|
||||
Servers.Sort(s => s.Profile?.SortKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
235
src/ConanServerManager/Lib/ServerPlayers.cs
Normal file
235
src/ConanServerManager/Lib/ServerPlayers.cs
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
using ConanData;
|
||||
using NLog;
|
||||
using ServerManagerTool.Common.Model;
|
||||
using ServerManagerTool.Common.Utils;
|
||||
using ServerManagerTool.Enums;
|
||||
using ServerManagerTool.Lib.ViewModel;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ServerPlayers : DependencyObject
|
||||
{
|
||||
private const int PLAYER_LIST_INTERVAL = 5000;
|
||||
|
||||
public event EventHandler PlayersCollectionUpdated;
|
||||
|
||||
public static readonly DependencyProperty PlayersProperty = DependencyProperty.Register(nameof(Players), typeof(SortableObservableCollection<PlayerInfo>), typeof(ServerPlayers), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty CountPlayersProperty = DependencyProperty.Register(nameof(CountPlayers), typeof(int), typeof(ServerPlayers), new PropertyMetadata(0));
|
||||
public static readonly DependencyProperty CountInvalidPlayersProperty = DependencyProperty.Register(nameof(CountInvalidPlayers), typeof(int), typeof(ServerPlayers), new PropertyMetadata(0));
|
||||
public static readonly DependencyProperty CountOnlinePlayersProperty = DependencyProperty.Register(nameof(CountOnlinePlayers), typeof(int), typeof(ServerPlayers), new PropertyMetadata(0));
|
||||
|
||||
private readonly ConcurrentDictionary<string, PlayerInfo> _players = new ConcurrentDictionary<string, PlayerInfo>();
|
||||
private readonly object _updatePlayerCollectionLock = new object();
|
||||
private CancellationTokenSource _cancellationTokenSource = null;
|
||||
private PlayerListParameters _playerListParameters;
|
||||
|
||||
private Logger _allLogger;
|
||||
private Logger _eventLogger;
|
||||
private Logger _debugLogger;
|
||||
private Logger _errorLogger;
|
||||
private bool _disposed = false;
|
||||
|
||||
public ServerPlayers(PlayerListParameters parameters)
|
||||
{
|
||||
this.Players = new SortableObservableCollection<PlayerInfo>();
|
||||
|
||||
_playerListParameters = parameters;
|
||||
|
||||
_allLogger = App.GetProfileLogger(_playerListParameters.ProfileId, "PlayerList_All", LogLevel.Info, LogLevel.Info);
|
||||
_eventLogger = App.GetProfileLogger(_playerListParameters.ProfileId, "PlayerList_Event", LogLevel.Info, LogLevel.Info);
|
||||
_debugLogger = App.GetProfileLogger(_playerListParameters.ProfileId, "PlayerList_Debug", LogLevel.Trace, LogLevel.Debug);
|
||||
_errorLogger = App.GetProfileLogger(_playerListParameters.ProfileId, "PlayerList_Error", LogLevel.Error, LogLevel.Fatal);
|
||||
|
||||
UpdatePlayersAsync().DoNotWait();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public SortableObservableCollection<PlayerInfo> Players
|
||||
{
|
||||
get { return (SortableObservableCollection<PlayerInfo>)GetValue(PlayersProperty); }
|
||||
set { SetValue(PlayersProperty, value); }
|
||||
}
|
||||
|
||||
public int CountPlayers
|
||||
{
|
||||
get { return (int)GetValue(CountPlayersProperty); }
|
||||
set { SetValue(CountPlayersProperty, value); }
|
||||
}
|
||||
|
||||
public int CountInvalidPlayers
|
||||
{
|
||||
get { return (int)GetValue(CountInvalidPlayersProperty); }
|
||||
set { SetValue(CountInvalidPlayersProperty, value); }
|
||||
}
|
||||
|
||||
public int CountOnlinePlayers
|
||||
{
|
||||
get { return (int)GetValue(CountOnlinePlayersProperty); }
|
||||
set { SetValue(CountOnlinePlayersProperty, value); }
|
||||
}
|
||||
|
||||
private void LogEvent(LogEventType eventType, string message)
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case LogEventType.All:
|
||||
_allLogger?.Info(message);
|
||||
return;
|
||||
|
||||
case LogEventType.Event:
|
||||
_eventLogger?.Info(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnPlayerCollectionUpdated()
|
||||
{
|
||||
PlayersCollectionUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task UpdatePlayersAsync()
|
||||
{
|
||||
if (this._disposed)
|
||||
return;
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
var token = _cancellationTokenSource.Token;
|
||||
|
||||
await UpdatePlayerDetailsAsync(_cancellationTokenSource.Token);
|
||||
var cancelled = _cancellationTokenSource.IsCancellationRequested;
|
||||
|
||||
if (!cancelled)
|
||||
{
|
||||
await TaskUtils.RunOnUIThreadAsync(() =>
|
||||
{
|
||||
UpdatePlayerCollection();
|
||||
});
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
if (!cancelled)
|
||||
{
|
||||
await Task.Delay(PLAYER_LIST_INTERVAL).ContinueWith(t => UpdatePlayersAsync());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdatePlayerDetailsAsync(CancellationToken token)
|
||||
{
|
||||
if (this._disposed)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_playerListParameters.GameFile) || !File.Exists(_playerListParameters.GameFile))
|
||||
return;
|
||||
|
||||
var savedPath = ServerProfile.GetProfileSavePath(_playerListParameters.InstallDirectory);
|
||||
DataContainer dataContainer = null;
|
||||
|
||||
try
|
||||
{
|
||||
// load the player data from the files.
|
||||
dataContainer = await DataContainer.CreateAsync(_playerListParameters.GameFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_errorLogger?.Error($"{nameof(UpdatePlayerDetailsAsync)} - Error: CreateAsync. {ex.Message}\r\n{ex.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// update the player data with the latest update value from the players collection
|
||||
foreach (var playerData in dataContainer.Players)
|
||||
{
|
||||
var id = playerData.PlayerId;
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
continue;
|
||||
|
||||
this._players.TryGetValue(id, out PlayerInfo player);
|
||||
player?.UpdatePlatformData(playerData);
|
||||
}
|
||||
}, token);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
var totalPlayers = dataContainer.Players.Count;
|
||||
|
||||
foreach (var playerData in dataContainer.Players)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
var id = playerData.PlayerId;
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
var validPlayer = new PlayerInfo()
|
||||
{
|
||||
PlayerId = id,
|
||||
PlayerName = playerData.PlayerName,
|
||||
IsValid = true,
|
||||
};
|
||||
|
||||
this._players.AddOrUpdate(id, validPlayer, (k, v) => { v.PlayerName = playerData.PlayerName; v.IsValid = true; return v; });
|
||||
}
|
||||
else
|
||||
{
|
||||
_debugLogger?.Debug($"{nameof(UpdatePlayerDetailsAsync)} - Error: corrupted profile.\r\n{playerData.CharacterId}.");
|
||||
}
|
||||
|
||||
if (this._players.TryGetValue(id, out PlayerInfo player) && player != null)
|
||||
{
|
||||
player.UpdateData(playerData);
|
||||
|
||||
await TaskUtils.RunOnUIThreadAsync(() =>
|
||||
{
|
||||
player.IsWhitelisted = _playerListParameters?.Server?.Profile?.ServerFilesWhitelisted?.Any(u => u.PlayerId.Equals(player.PlayerId, StringComparison.OrdinalIgnoreCase)) ?? false;
|
||||
});
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// remove any players that do not have a player record.
|
||||
var droppedPlayers = this._players.Values.Where(p => dataContainer.Players.FirstOrDefault(pd => pd.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null).ToArray();
|
||||
foreach (var droppedPlayer in droppedPlayers)
|
||||
{
|
||||
_players.TryRemove(droppedPlayer.PlayerId, out PlayerInfo player);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlayerCollection()
|
||||
{
|
||||
lock (_updatePlayerCollectionLock)
|
||||
{
|
||||
this.Players = new SortableObservableCollection<PlayerInfo>(_players.Values);
|
||||
this.CountPlayers = this.Players.Count;
|
||||
this.CountInvalidPlayers = this.Players.Count(p => !p.IsValid);
|
||||
this.CountOnlinePlayers = this.Players.Count(p => p.IsOnline);
|
||||
|
||||
OnPlayerCollectionUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1633
src/ConanServerManager/Lib/ServerProfile.cs
Normal file
1633
src/ConanServerManager/Lib/ServerProfile.cs
Normal file
File diff suppressed because it is too large
Load diff
100
src/ConanServerManager/Lib/ServerProfileSnapshot.cs
Normal file
100
src/ConanServerManager/Lib/ServerProfileSnapshot.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using ServerManagerTool.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ServerProfileSnapshot
|
||||
{
|
||||
public string ProfileId;
|
||||
public string ProfileName;
|
||||
public string ServerName;
|
||||
public string InstallDirectory;
|
||||
public string GameFile;
|
||||
public string AdminPassword;
|
||||
public string ServerIP;
|
||||
public int ServerPort;
|
||||
public int ServerPeerPort;
|
||||
public int QueryPort;
|
||||
public string ServerMap;
|
||||
public List<string> ServerModIds;
|
||||
public bool RconEnabled;
|
||||
public int RconPort;
|
||||
public string RconPassword;
|
||||
public int MaxPlayerCount;
|
||||
|
||||
public string MOTD;
|
||||
public bool MOTDIntervalEnabled;
|
||||
public int MOTDInterval;
|
||||
|
||||
public string BranchName;
|
||||
public string BranchPassword;
|
||||
|
||||
public string SchedulerKey;
|
||||
public bool EnableAutoBackup;
|
||||
public bool EnableAutoUpdate;
|
||||
public bool EnableAutoShutdown1;
|
||||
public bool RestartAfterShutdown1;
|
||||
public bool UpdateAfterShutdown1;
|
||||
public bool EnableAutoShutdown2;
|
||||
public bool RestartAfterShutdown2;
|
||||
public bool UpdateAfterShutdown2;
|
||||
public bool AutoRestartIfShutdown;
|
||||
|
||||
public bool ServerUpdated;
|
||||
public string LastInstalledVersion;
|
||||
public DateTime LastStarted;
|
||||
|
||||
public static ServerProfileSnapshot Create(ServerProfile profile)
|
||||
{
|
||||
return new ServerProfileSnapshot
|
||||
{
|
||||
ProfileId = profile.ProfileID,
|
||||
ProfileName = profile.ProfileName,
|
||||
ServerName = profile.ServerName,
|
||||
InstallDirectory = profile.InstallDirectory,
|
||||
GameFile = profile.GetServerWorldFile(),
|
||||
AdminPassword = profile.AdminPassword,
|
||||
ServerIP = string.IsNullOrWhiteSpace(profile.ServerIP) ? IPAddress.Loopback.ToString() : profile.ServerIP.Trim(),
|
||||
ServerPort = profile.ServerPort,
|
||||
ServerPeerPort = profile.ServerPeerPort,
|
||||
QueryPort = profile.QueryPort,
|
||||
ServerMap = ServerProfile.GetProfileMapName(profile),
|
||||
ServerModIds = ModUtils.GetModIdList(profile.ServerModIds),
|
||||
RconEnabled = profile.RconEnabled,
|
||||
RconPort = profile.RconPort,
|
||||
RconPassword = profile.RconPassword,
|
||||
MaxPlayerCount = profile.MaxPlayers,
|
||||
|
||||
MOTD = profile.MOTD,
|
||||
MOTDIntervalEnabled = profile.MOTDIntervalEnabled && !string.IsNullOrWhiteSpace(profile.MOTD),
|
||||
MOTDInterval = Math.Max(1, Math.Min(int.MaxValue, profile.MOTDInterval)),
|
||||
|
||||
BranchName = profile.BranchName,
|
||||
BranchPassword = profile.BranchPassword,
|
||||
|
||||
SchedulerKey = profile.GetProfileKey(),
|
||||
EnableAutoBackup = profile.EnableAutoBackup,
|
||||
EnableAutoUpdate = profile.EnableAutoUpdate,
|
||||
EnableAutoShutdown1 = profile.EnableAutoShutdown1,
|
||||
RestartAfterShutdown1 = profile.RestartAfterShutdown1,
|
||||
UpdateAfterShutdown1 = profile.UpdateAfterShutdown1,
|
||||
EnableAutoShutdown2 = profile.EnableAutoShutdown2,
|
||||
RestartAfterShutdown2 = profile.RestartAfterShutdown2,
|
||||
UpdateAfterShutdown2 = profile.UpdateAfterShutdown2,
|
||||
AutoRestartIfShutdown = profile.AutoRestartIfShutdown,
|
||||
|
||||
ServerUpdated = false,
|
||||
LastInstalledVersion = profile.LastInstalledVersion ?? new Version(0, 0).ToString(),
|
||||
LastStarted = profile.LastStarted,
|
||||
};
|
||||
}
|
||||
|
||||
public void Update(ServerProfile profile)
|
||||
{
|
||||
profile.LastInstalledVersion = LastInstalledVersion;
|
||||
profile.LastStarted = LastStarted;
|
||||
}
|
||||
}
|
||||
}
|
||||
475
src/ConanServerManager/Lib/ServerRcon.cs
Normal file
475
src/ConanServerManager/Lib/ServerRcon.cs
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
using ConanData;
|
||||
using NLog;
|
||||
using ServerManagerTool.Common.Enums;
|
||||
using ServerManagerTool.Common.Interfaces;
|
||||
using ServerManagerTool.Common.Lib;
|
||||
using ServerManagerTool.Common.Model;
|
||||
using ServerManagerTool.Common.Utils;
|
||||
using ServerManagerTool.Enums;
|
||||
using ServerManagerTool.Lib.ViewModel;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using WPFSharp.Globalizer;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ServerRcon : DependencyObject, IAsyncDisposable
|
||||
{
|
||||
private const int PLAYER_LIST_INTERVAL = 5000;
|
||||
private const string NoResponseMatch = "Server received, But no response!!";
|
||||
public const string NoResponseOutput = "NO_RESPONSE";
|
||||
|
||||
public const string RCON_COMMAND_BROADCAST = "broadcast";
|
||||
public const string RCON_COMMAND_LISTPLAYERS = "#managerplayerlist#";
|
||||
|
||||
public event EventHandler PlayersCollectionUpdated;
|
||||
|
||||
public static readonly DependencyProperty PlayersProperty = DependencyProperty.Register(nameof(Players), typeof(SortableObservableCollection<PlayerInfo>), typeof(ServerRcon), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty CountPlayersProperty = DependencyProperty.Register(nameof(CountPlayers), typeof(int), typeof(ServerRcon), new PropertyMetadata(0));
|
||||
public static readonly DependencyProperty CountInvalidPlayersProperty = DependencyProperty.Register(nameof(CountInvalidPlayers), typeof(int), typeof(ServerRcon), new PropertyMetadata(0));
|
||||
public static readonly DependencyProperty CountOnlinePlayersProperty = DependencyProperty.Register(nameof(CountOnlinePlayers), typeof(int), typeof(ServerRcon), new PropertyMetadata(0));
|
||||
|
||||
private static readonly ConcurrentDictionary<string, bool> locks = new ConcurrentDictionary<string, bool>();
|
||||
private static readonly char[] lineSplitChars = new char[] { '\n' };
|
||||
private static readonly char[] argsSplitChars = new char[] { ' ' };
|
||||
|
||||
private readonly ActionQueue _commandProcessor = new ActionQueue(TaskScheduler.Default);
|
||||
private readonly ActionQueue _outputProcessor = new ActionQueue(TaskScheduler.FromCurrentSynchronizationContext());
|
||||
private readonly List<CommandListener> _commandListeners = new List<CommandListener>();
|
||||
|
||||
private RconParameters _rconParameters;
|
||||
private QueryMaster.Rcon _console;
|
||||
private int maxCommandRetries = 3;
|
||||
|
||||
private readonly ConcurrentDictionary<string, PlayerInfo> _players = new ConcurrentDictionary<string, PlayerInfo>();
|
||||
private readonly object _updatePlayerCollectionLock = new object();
|
||||
private CancellationTokenSource _cancellationTokenSource = null;
|
||||
|
||||
private Logger _chatLogger;
|
||||
private Logger _allLogger;
|
||||
private Logger _eventLogger;
|
||||
private Logger _debugLogger;
|
||||
private Logger _errorLogger;
|
||||
private bool _disposed = false;
|
||||
|
||||
public ServerRcon(RconParameters parameters)
|
||||
{
|
||||
this._rconParameters = parameters;
|
||||
this.Players = new SortableObservableCollection<PlayerInfo>();
|
||||
|
||||
_allLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_All", LogLevel.Info, LogLevel.Info);
|
||||
_chatLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_Chat", LogLevel.Info, LogLevel.Info);
|
||||
_eventLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_Event", LogLevel.Info, LogLevel.Info);
|
||||
_debugLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_Debug", LogLevel.Trace, LogLevel.Debug);
|
||||
_errorLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_Error", LogLevel.Error, LogLevel.Fatal);
|
||||
|
||||
UpdatePlayersAsync().DoNotWait();
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
await this._commandProcessor.DisposeAsync();
|
||||
await this._outputProcessor.DisposeAsync();
|
||||
|
||||
for (int index = this._commandListeners.Count - 1; index >= 0; index--)
|
||||
{
|
||||
this._commandListeners[index].Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
#region Properties
|
||||
public SortableObservableCollection<PlayerInfo> Players
|
||||
{
|
||||
get { return (SortableObservableCollection<PlayerInfo>)GetValue(PlayersProperty); }
|
||||
set { SetValue(PlayersProperty, value); }
|
||||
}
|
||||
|
||||
public int CountPlayers
|
||||
{
|
||||
get { return (int)GetValue(CountPlayersProperty); }
|
||||
set { SetValue(CountPlayersProperty, value); }
|
||||
}
|
||||
|
||||
public int CountInvalidPlayers
|
||||
{
|
||||
get { return (int)GetValue(CountInvalidPlayersProperty); }
|
||||
set { SetValue(CountInvalidPlayersProperty, value); }
|
||||
}
|
||||
|
||||
public int CountOnlinePlayers
|
||||
{
|
||||
get { return (int)GetValue(CountOnlinePlayersProperty); }
|
||||
set { SetValue(CountOnlinePlayersProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
private void LogEvent(LogEventType eventType, string message)
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case LogEventType.All:
|
||||
_allLogger?.Info(message);
|
||||
return;
|
||||
|
||||
case LogEventType.Chat:
|
||||
_chatLogger?.Info(message);
|
||||
return;
|
||||
|
||||
case LogEventType.Event:
|
||||
_eventLogger?.Info(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnPlayerCollectionUpdated()
|
||||
{
|
||||
PlayersCollectionUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private bool Reconnect()
|
||||
{
|
||||
if (this._console != null)
|
||||
{
|
||||
this._console.Dispose();
|
||||
this._console = null;
|
||||
}
|
||||
|
||||
var endpoint = new IPEndPoint(this._rconParameters.RconHostIP, this._rconParameters.RconPort);
|
||||
var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endpoint, sendTimeOut: 10000, receiveTimeOut: 10000);
|
||||
this._console = server.GetControl(this._rconParameters.RconPassword);
|
||||
return this._console != null;
|
||||
}
|
||||
|
||||
public IDisposable RegisterCommandListener(Action<ConsoleCommand> callback)
|
||||
{
|
||||
var listener = new CommandListener { Callback = callback, DisposeAction = UnregisterCommandListener };
|
||||
this._commandListeners.Add(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
private void UnregisterCommandListener(CommandListener listener)
|
||||
{
|
||||
this._commandListeners.Remove(listener);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Process Methods
|
||||
private bool ProcessInput(ConsoleCommand command)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!command.suppressCommand)
|
||||
{
|
||||
LogEvent(LogEventType.All, command.rawCommand);
|
||||
}
|
||||
|
||||
var args = command.rawCommand.Split(argsSplitChars, 2);
|
||||
command.command = args[0];
|
||||
if (args.Length > 1)
|
||||
{
|
||||
command.args = args[1];
|
||||
}
|
||||
|
||||
var result = SendCommand(command.rawCommand);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
_debugLogger?.Debug($"SendCommand '{command.rawCommand}' do not return any results.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var lines = result.Split(lineSplitChars, StringSplitOptions.RemoveEmptyEntries).Select(l => l.Trim()).ToArray();
|
||||
|
||||
if (!command.suppressOutput)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
LogEvent(LogEventType.All, line);
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.Length == 1 && lines[0].StartsWith(NoResponseMatch))
|
||||
{
|
||||
lines[0] = NoResponseOutput;
|
||||
}
|
||||
|
||||
command.lines = lines;
|
||||
}
|
||||
|
||||
command.status = ConsoleStatus.Connected;
|
||||
|
||||
this._outputProcessor.PostAction(() => ProcessOutput(command));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_errorLogger?.Error($"Failed to send command '{command.rawCommand}'. {ex.Message}");
|
||||
command.status = ConsoleStatus.Disconnected;
|
||||
this._outputProcessor.PostAction(() => ProcessOutput(command));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// This is bound to the UI thread
|
||||
private void ProcessOutput(ConsoleCommand command)
|
||||
{
|
||||
//
|
||||
// Handle results
|
||||
//
|
||||
HandleCommand(command);
|
||||
NotifyCommand(command);
|
||||
}
|
||||
|
||||
public Task<bool> IssueCommand(string userCommand)
|
||||
{
|
||||
return this._commandProcessor.PostAction(() => ProcessInput(new ConsoleCommand() { rawCommand = userCommand }));
|
||||
}
|
||||
|
||||
// This is bound to the UI thread
|
||||
private void HandleCommand(ConsoleCommand command)
|
||||
{
|
||||
//
|
||||
// Perform per-command special processing to extract data
|
||||
//
|
||||
if (command?.command?.Equals(RCON_COMMAND_BROADCAST, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
{
|
||||
LogEvent(LogEventType.Chat, command.rawCommand);
|
||||
command.suppressOutput = true;
|
||||
}
|
||||
|
||||
if (command?.command?.Equals(RCON_COMMAND_LISTPLAYERS, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
{
|
||||
command.suppressCommand = true;
|
||||
command.suppressOutput = false;
|
||||
}
|
||||
}
|
||||
|
||||
// This is bound to the UI thread
|
||||
private void NotifyCommand(ConsoleCommand command)
|
||||
{
|
||||
foreach (var listener in _commandListeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.Callback(command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_errorLogger?.Error("Exception in command listener: {0}\n{1}", ex.Message, ex.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string SendCommand(string command)
|
||||
{
|
||||
const int RETRY_DELAY = 100;
|
||||
|
||||
Exception lastException = null;
|
||||
int retries = 0;
|
||||
|
||||
while (retries < maxCommandRetries)
|
||||
{
|
||||
if (this._console != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this._console.SendCommand(command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// we will simply retry
|
||||
lastException = ex;
|
||||
}
|
||||
|
||||
Task.Delay(RETRY_DELAY).Wait();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Reconnect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastException = ex;
|
||||
}
|
||||
|
||||
retries++;
|
||||
}
|
||||
|
||||
this.maxCommandRetries = 10;
|
||||
_errorLogger?.Error($"Failed to connect to Rcon at {this._rconParameters.RconHostIP}:{this._rconParameters.RconPort} with {this._rconParameters.RconPassword}. {lastException.Message}");
|
||||
throw new Exception($"Command failed to send after {maxCommandRetries} attempts. Last exception: {lastException.Message}", lastException);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private async Task UpdatePlayersAsync()
|
||||
{
|
||||
if (this._disposed)
|
||||
return;
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
var token = _cancellationTokenSource.Token;
|
||||
|
||||
var output = await UpdatePlayerDetailsAsync(_cancellationTokenSource.Token);
|
||||
var cancelled = _cancellationTokenSource.IsCancellationRequested;
|
||||
|
||||
if (!cancelled)
|
||||
{
|
||||
await TaskUtils.RunOnUIThreadAsync(() =>
|
||||
{
|
||||
UpdatePlayerCollection();
|
||||
|
||||
var command = new ConsoleCommand() { rawCommand = RCON_COMMAND_LISTPLAYERS, command = RCON_COMMAND_LISTPLAYERS, lines = output };
|
||||
ProcessOutput(command);
|
||||
});
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
if (!cancelled)
|
||||
{
|
||||
await Task.Delay(PLAYER_LIST_INTERVAL).ContinueWith(t => UpdatePlayersAsync());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<string>> UpdatePlayerDetailsAsync(CancellationToken token)
|
||||
{
|
||||
if (this._disposed)
|
||||
return new List<string>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_rconParameters.GameFile) || !File.Exists(_rconParameters.GameFile))
|
||||
return new List<string>();
|
||||
|
||||
var savedPath = ServerProfile.GetProfileSavePath(_rconParameters.InstallDirectory);
|
||||
DataContainer dataContainer = null;
|
||||
|
||||
try
|
||||
{
|
||||
// load the player data from the files.
|
||||
dataContainer = await DataContainer.CreateAsync(_rconParameters.GameFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_errorLogger?.Error($"{nameof(UpdatePlayerDetailsAsync)} - Error: CreateAsync. {ex.Message}\r\n{ex.StackTrace}");
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return new List<string>();
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// update the player data with the latest update value from the players collection
|
||||
foreach (var playerData in dataContainer.Players)
|
||||
{
|
||||
var id = playerData.PlayerId;
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
continue;
|
||||
|
||||
this._players.TryGetValue(id, out PlayerInfo player);
|
||||
player?.UpdatePlatformData(playerData);
|
||||
}
|
||||
}, token);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return new List<string>();
|
||||
|
||||
var totalPlayers = dataContainer.Players.Count;
|
||||
var output = new List<string>();
|
||||
|
||||
foreach (var playerData in dataContainer.Players)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
var id = playerData.PlayerId;
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
var validPlayer = new PlayerInfo()
|
||||
{
|
||||
PlayerId = id,
|
||||
PlayerName = playerData.PlayerName,
|
||||
IsValid = true,
|
||||
};
|
||||
|
||||
this._players.AddOrUpdate(id, validPlayer, (k, v) => { v.PlayerName = playerData.PlayerName; v.IsValid = true; return v; });
|
||||
}
|
||||
else
|
||||
{
|
||||
_debugLogger?.Debug($"{nameof(UpdatePlayerDetailsAsync)} - Error: corrupted profile.\r\n{playerData.CharacterId}.");
|
||||
}
|
||||
|
||||
if (this._players.TryGetValue(id, out PlayerInfo player) && player != null)
|
||||
{
|
||||
if (player.IsOnline != playerData.Online)
|
||||
{
|
||||
if (playerData.Online)
|
||||
{
|
||||
var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Join") ?? "Player '{0}' joined the game.";
|
||||
var message = string.Format(messageFormat, playerData.CharacterName);
|
||||
output.Add(message);
|
||||
LogEvent(LogEventType.Event, message);
|
||||
LogEvent(LogEventType.All, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Leave") ?? "Player '{0}' left the game.";
|
||||
var message = string.Format(messageFormat, playerData.CharacterName);
|
||||
output.Add(message);
|
||||
LogEvent(LogEventType.Event, message);
|
||||
LogEvent(LogEventType.All, message);
|
||||
}
|
||||
}
|
||||
|
||||
player.UpdateData(playerData);
|
||||
|
||||
await TaskUtils.RunOnUIThreadAsync(() =>
|
||||
{
|
||||
player.IsWhitelisted = _rconParameters?.Server?.Profile?.ServerFilesWhitelisted?.Any(u => u.PlayerId.Equals(player.PlayerId, StringComparison.OrdinalIgnoreCase)) ?? false;
|
||||
});
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return new List<string>();
|
||||
|
||||
// remove any players that do not have a player record.
|
||||
var droppedPlayers = this._players.Values.Where(p => dataContainer.Players.FirstOrDefault(pd => pd.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null).ToArray();
|
||||
foreach (var droppedPlayer in droppedPlayers)
|
||||
{
|
||||
_players.TryRemove(droppedPlayer.PlayerId, out PlayerInfo player);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private void UpdatePlayerCollection()
|
||||
{
|
||||
lock (_updatePlayerCollectionLock)
|
||||
{
|
||||
this.Players = new SortableObservableCollection<PlayerInfo>(_players.Values);
|
||||
this.CountPlayers = this.Players.Count;
|
||||
this.CountInvalidPlayers = this.Players.Count(p => !p.IsValid);
|
||||
this.CountOnlinePlayers = this.Players.Count(p => p.IsOnline);
|
||||
|
||||
OnPlayerCollectionUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1138
src/ConanServerManager/Lib/ServerRuntime.cs
Normal file
1138
src/ConanServerManager/Lib/ServerRuntime.cs
Normal file
File diff suppressed because it is too large
Load diff
14
src/ConanServerManager/Lib/ServerStatusUpdate.cs
Normal file
14
src/ConanServerManager/Lib/ServerStatusUpdate.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using ServerManagerTool.Enums;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public struct ServerStatusUpdate
|
||||
{
|
||||
public Process Process;
|
||||
public WatcherServerStatus Status;
|
||||
public QueryMaster.ServerInfo ServerInfo;
|
||||
public int OnlinePlayerCount;
|
||||
}
|
||||
}
|
||||
24
src/ConanServerManager/Lib/ServerStatusUpdateRegistration.cs
Normal file
24
src/ConanServerManager/Lib/ServerStatusUpdateRegistration.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using ServerManagerTool.Common.Interfaces;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ServerStatusUpdateRegistration : IAsyncDisposable
|
||||
{
|
||||
public string InstallDirectory;
|
||||
public IPEndPoint LocalEndpoint;
|
||||
public IPEndPoint SteamEndpoint;
|
||||
public Action<IAsyncDisposable, ServerStatusUpdate> UpdateCallback;
|
||||
public Func<Task> UnregisterAction;
|
||||
|
||||
public string ProfileId;
|
||||
public string GameFile;
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await UnregisterAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
353
src/ConanServerManager/Lib/ServerStatusWatcher.cs
Normal file
353
src/ConanServerManager/Lib/ServerStatusWatcher.cs
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
using ConanData;
|
||||
using NLog;
|
||||
using ServerManagerTool.Common.Interfaces;
|
||||
using ServerManagerTool.Common.Utils;
|
||||
using ServerManagerTool.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
|
||||
namespace ServerManagerTool.Lib
|
||||
{
|
||||
public class ServerStatusWatcher
|
||||
{
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly List<ServerStatusUpdateRegistration> _serverRegistrations = new List<ServerStatusUpdateRegistration>();
|
||||
private readonly ActionBlock<Func<Task>> _eventQueue;
|
||||
private readonly Dictionary<string, DateTime> _nextExternalStatusQuery = new Dictionary<string, DateTime>();
|
||||
|
||||
static ServerStatusWatcher()
|
||||
{
|
||||
Instance = new ServerStatusWatcher();
|
||||
}
|
||||
|
||||
private ServerStatusWatcher()
|
||||
{
|
||||
_eventQueue = new ActionBlock<Func<Task>>(async f => await f.Invoke(), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1 });
|
||||
_eventQueue.Post(DoLocalUpdate);
|
||||
}
|
||||
|
||||
public static ServerStatusWatcher Instance
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public IAsyncDisposable RegisterForUpdates(string installDirectory, string profileId, string gameFile, IPEndPoint localEndpoint, IPEndPoint steamEndpoint, Action<IAsyncDisposable, ServerStatusUpdate> updateCallback)
|
||||
{
|
||||
var registration = new ServerStatusUpdateRegistration
|
||||
{
|
||||
InstallDirectory = installDirectory,
|
||||
ProfileId = profileId,
|
||||
GameFile = gameFile,
|
||||
LocalEndpoint = localEndpoint,
|
||||
SteamEndpoint = steamEndpoint,
|
||||
UpdateCallback = updateCallback,
|
||||
};
|
||||
|
||||
registration.UnregisterAction = async () =>
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
_eventQueue.Post(() =>
|
||||
{
|
||||
if(_serverRegistrations.Contains(registration))
|
||||
{
|
||||
Logger.Debug($"{nameof(RegisterForUpdates)} Removing registration for L:{registration.LocalEndpoint} S:{registration.SteamEndpoint}");
|
||||
_serverRegistrations.Remove(registration);
|
||||
}
|
||||
tcs.TrySetResult(true);
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
|
||||
await tcs.Task;
|
||||
};
|
||||
|
||||
_eventQueue.Post(() =>
|
||||
{
|
||||
if (!_serverRegistrations.Contains(registration))
|
||||
{
|
||||
Logger.Debug($"{nameof(RegisterForUpdates)} Adding registration for L:{registration.LocalEndpoint} S:{registration.SteamEndpoint}");
|
||||
_serverRegistrations.Add(registration);
|
||||
|
||||
var registrationKey = registration.SteamEndpoint.ToString();
|
||||
_nextExternalStatusQuery[registrationKey] = DateTime.MinValue;
|
||||
}
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
);
|
||||
|
||||
return registration;
|
||||
}
|
||||
|
||||
private static ServerProcessStatus GetServerProcessStatus(ServerStatusUpdateRegistration updateContext, out Process serverProcess)
|
||||
{
|
||||
serverProcess = null;
|
||||
if (String.IsNullOrWhiteSpace(updateContext.InstallDirectory))
|
||||
{
|
||||
return ServerProcessStatus.NotInstalled;
|
||||
}
|
||||
|
||||
var serverExePath = Path.Combine(updateContext.InstallDirectory, Config.Default.ServerBinaryRelativePath, Config.Default.ServerExeFile);
|
||||
if(!File.Exists(serverExePath))
|
||||
{
|
||||
return ServerProcessStatus.NotInstalled;
|
||||
}
|
||||
|
||||
//
|
||||
// The server appears to be installed, now determine if it is running or stopped.
|
||||
//
|
||||
try
|
||||
{
|
||||
foreach (var process in Process.GetProcessesByName(Config.Default.ServerProcessName))
|
||||
{
|
||||
var commandLine = ProcessUtils.GetCommandLineForProcess(process.Id)?.ToLower();
|
||||
|
||||
if (commandLine != null && commandLine.Contains(updateContext.InstallDirectory.ToLower()) && commandLine.Contains(Config.Default.ServerExeFile.ToLower()))
|
||||
{
|
||||
// Does this match our server exe and port?
|
||||
var serverArgMatch = String.Format(Config.Default.ServerCommandLineArgsPortMatchFormat, updateContext.LocalEndpoint.Port).ToLower();
|
||||
if (commandLine.Contains(serverArgMatch))
|
||||
{
|
||||
// Was an IP set on it?
|
||||
var anyIpArgMatch = String.Format(Config.Default.ServerCommandLineArgsIPMatchFormat, String.Empty).ToLower();
|
||||
if (commandLine.Contains(anyIpArgMatch))
|
||||
{
|
||||
// If we have a specific IP, check for it.
|
||||
var ipArgMatch = String.Format(Config.Default.ServerCommandLineArgsIPMatchFormat, updateContext.LocalEndpoint.Address.ToString()).ToLower();
|
||||
if (!commandLine.Contains(ipArgMatch))
|
||||
{
|
||||
// Specific IP set didn't match
|
||||
continue;
|
||||
}
|
||||
|
||||
// Specific IP matched
|
||||
}
|
||||
|
||||
// Either specific IP matched or no specific IP was set and we will claim this is ours.
|
||||
|
||||
process.EnableRaisingEvents = true;
|
||||
if (process.HasExited)
|
||||
{
|
||||
return ServerProcessStatus.Stopped;
|
||||
}
|
||||
|
||||
serverProcess = process;
|
||||
return ServerProcessStatus.Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logger.Error($"{nameof(GetServerProcessStatus)}. {ex.Message}\r\n{ex.StackTrace}");
|
||||
}
|
||||
|
||||
return ServerProcessStatus.Stopped;
|
||||
}
|
||||
|
||||
private async Task DoLocalUpdate()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var registration in this._serverRegistrations)
|
||||
{
|
||||
ServerStatusUpdate statusUpdate = new ServerStatusUpdate();
|
||||
try
|
||||
{
|
||||
Logger.Info($"{nameof(DoLocalUpdate)} Start: {registration.LocalEndpoint}");
|
||||
statusUpdate = await GenerateServerStatusUpdateAsync(registration);
|
||||
|
||||
PostServerStatusUpdate(registration, registration.UpdateCallback, statusUpdate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// We don't want to stop other registration queries or break the ActionBlock
|
||||
Logger.Error($"{nameof(DoLocalUpdate)} - Exception in local update. {ex.Message}\r\n{ex.StackTrace}");
|
||||
Debugger.Break();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.Info($"{nameof(DoLocalUpdate)} End: {registration.LocalEndpoint}: {statusUpdate.Status}");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Task.Delay(Config.Default.ServerStatusWatcher_LocalStatusQueryDelay).ContinueWith(_ => _eventQueue.Post(DoLocalUpdate)).DoNotWait();
|
||||
}
|
||||
}
|
||||
|
||||
private void PostServerStatusUpdate(ServerStatusUpdateRegistration registration, Action<IAsyncDisposable, ServerStatusUpdate> callback, ServerStatusUpdate statusUpdate)
|
||||
{
|
||||
_eventQueue.Post(() =>
|
||||
{
|
||||
if (this._serverRegistrations.Contains(registration))
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(registration, statusUpdate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugUtils.WriteFormatThreadSafeAsync("Exception during local status update callback: {0}\n{1}", ex.Message, ex.StackTrace).DoNotWait();
|
||||
}
|
||||
}
|
||||
return TaskUtils.FinishedTask;
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<ServerStatusUpdate> GenerateServerStatusUpdateAsync(ServerStatusUpdateRegistration registration)
|
||||
{
|
||||
var registrationKey = registration.SteamEndpoint.ToString();
|
||||
|
||||
//
|
||||
// First check the process status
|
||||
//
|
||||
var processStatus = GetServerProcessStatus(registration, out Process process);
|
||||
switch (processStatus)
|
||||
{
|
||||
case ServerProcessStatus.NotInstalled:
|
||||
return new ServerStatusUpdate { Status = WatcherServerStatus.NotInstalled };
|
||||
|
||||
case ServerProcessStatus.Stopped:
|
||||
return new ServerStatusUpdate { Status = WatcherServerStatus.Stopped };
|
||||
|
||||
case ServerProcessStatus.Unknown:
|
||||
return new ServerStatusUpdate { Status = WatcherServerStatus.Unknown };
|
||||
|
||||
case ServerProcessStatus.Running:
|
||||
break;
|
||||
|
||||
default:
|
||||
Debugger.Break();
|
||||
break;
|
||||
}
|
||||
|
||||
var currentStatus = WatcherServerStatus.Initializing;
|
||||
|
||||
//
|
||||
// If the process was running do we then perform network checks.
|
||||
//
|
||||
Logger.Info($"{nameof(GenerateServerStatusUpdateAsync)} Checking server local network status at {registration.LocalEndpoint}");
|
||||
|
||||
// get the server information direct from the server using local connection.
|
||||
GetLocalNetworkStatus(registration.GameFile, registration.LocalEndpoint, out QueryMaster.ServerInfo localInfo, out int onlinePlayerCount);
|
||||
|
||||
if (localInfo != null)
|
||||
{
|
||||
currentStatus = WatcherServerStatus.RunningLocalCheck;
|
||||
|
||||
//
|
||||
// Now that it's running, we can check the publication status.
|
||||
//
|
||||
Logger.Info($"{nameof(GenerateServerStatusUpdateAsync)} Checking server public status direct at {registration.SteamEndpoint}");
|
||||
|
||||
// get the server information direct from the server using public connection.
|
||||
var serverStatus = CheckServerStatusDirect(registration.SteamEndpoint);
|
||||
// check if the server returned the information.
|
||||
if (!serverStatus)
|
||||
{
|
||||
// server did not return any information
|
||||
var nextExternalStatusQuery = _nextExternalStatusQuery.ContainsKey(registrationKey) ? _nextExternalStatusQuery[registrationKey] : DateTime.MinValue;
|
||||
if (DateTime.Now >= nextExternalStatusQuery)
|
||||
{
|
||||
currentStatus = WatcherServerStatus.RunningExternalCheck;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Config.Default.ServerStatusUrlFormat))
|
||||
{
|
||||
Logger.Info($"{nameof(GenerateServerStatusUpdateAsync)} Checking server public status via api at {registration.SteamEndpoint}");
|
||||
|
||||
// get the server information direct from the server using external connection.
|
||||
var uri = new Uri(string.Format(Config.Default.ServerStatusUrlFormat, Config.Default.ServerManagerCode, App.Instance.Version, registration.SteamEndpoint.Address, registration.SteamEndpoint.Port));
|
||||
serverStatus = await NetworkUtils.CheckServerStatusViaAPI(uri, registration.SteamEndpoint);
|
||||
}
|
||||
|
||||
_nextExternalStatusQuery[registrationKey] = DateTime.Now.AddMilliseconds(Config.Default.ServerStatusWatcher_RemoteStatusQueryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
// check if the server returned the information.
|
||||
if (serverStatus)
|
||||
{
|
||||
currentStatus = WatcherServerStatus.Published;
|
||||
}
|
||||
}
|
||||
|
||||
var statusUpdate = new ServerStatusUpdate
|
||||
{
|
||||
Process = process,
|
||||
Status = currentStatus,
|
||||
ServerInfo = localInfo,
|
||||
OnlinePlayerCount = onlinePlayerCount,
|
||||
};
|
||||
|
||||
return await Task.FromResult(statusUpdate);
|
||||
}
|
||||
|
||||
private static bool GetLocalNetworkStatus(string gameFile, IPEndPoint endpoint, out QueryMaster.ServerInfo serverInfo, out int onlinePlayerCount)
|
||||
{
|
||||
serverInfo = null;
|
||||
onlinePlayerCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
using (var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endpoint))
|
||||
{
|
||||
try
|
||||
{
|
||||
serverInfo = server.GetInfo();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
serverInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// load the player data from the files.
|
||||
onlinePlayerCount = DataContainer.GetOnlinePlayerCount(gameFile);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
onlinePlayerCount = 0;
|
||||
}
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
// Common when the server is unreachable. Log and Ignore it.
|
||||
Logger.Debug($"{nameof(GetLocalNetworkStatus)} failed: {endpoint.Address}:{endpoint.Port}. {ex.Message}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CheckServerStatusDirect(IPEndPoint endpoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
QueryMaster.ServerInfo serverInfo;
|
||||
|
||||
using (var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endpoint))
|
||||
{
|
||||
serverInfo = server.GetInfo();
|
||||
}
|
||||
|
||||
return serverInfo != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug($"{nameof(CheckServerStatusDirect)} - Failed checking status direct for: {endpoint.Address}:{endpoint.Port}. {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using WPFSharp.Globalizer;
|
||||
|
||||
namespace ServerManagerTool.Lib.ViewModel
|
||||
{
|
||||
public class EnumDescriptionTypeConverter : EnumConverter
|
||||
{
|
||||
public EnumDescriptionTypeConverter(Type type)
|
||||
: base(type)
|
||||
{
|
||||
}
|
||||
|
||||
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var strType = value.GetType().Name;
|
||||
var strVal = value.ToString();
|
||||
|
||||
return GlobalizedApplication.Instance.GetResourceString($"{strType}_{strVal}") ?? strVal;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using ServerManagerTool.Utils;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using WPFSharp.Globalizer;
|
||||
|
||||
namespace ServerManagerTool.Lib.ViewModel
|
||||
{
|
||||
public class MapNameValueConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
var valueString = value as string;
|
||||
if (valueString == null)
|
||||
return string.Empty;
|
||||
|
||||
var name = GlobalizedApplication.Instance.GetResourceString(valueString);
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
return name;
|
||||
|
||||
name = GameData.FriendlyMapNameForClass(valueString, true);
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
return name;
|
||||
|
||||
var mapName = ModUtils.GetMapName(valueString);
|
||||
|
||||
// check if the name is stored in the globalization file
|
||||
name = GlobalizedApplication.Instance.GetResourceString(mapName);
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
return name;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mapName))
|
||||
return mapName;
|
||||
|
||||
return valueString;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
160
src/ConanServerManager/Lib/ViewModel/PlayerInfo.cs
Normal file
160
src/ConanServerManager/Lib/ViewModel/PlayerInfo.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
using ConanData;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ServerManagerTool.Lib.ViewModel
|
||||
{
|
||||
public class PlayerInfo : INotifyPropertyChanged
|
||||
{
|
||||
public PlayerInfo()
|
||||
{
|
||||
PlayerId = string.Empty;
|
||||
PlayerName = string.Empty;
|
||||
CharacterName = string.Empty;
|
||||
IsOnline = false;
|
||||
IsAdmin = false;
|
||||
IsWhitelisted = false;
|
||||
GuildName = string.Empty;
|
||||
IsValid = true;
|
||||
LastOnline = null;
|
||||
PlayerData = null;
|
||||
}
|
||||
|
||||
public string PlayerId
|
||||
{
|
||||
get { return Get<string>(); }
|
||||
set { Set(value); }
|
||||
}
|
||||
public string PlayerName
|
||||
{
|
||||
get { return Get<string>(); }
|
||||
set
|
||||
{
|
||||
Set(value);
|
||||
|
||||
PlatformNameFilterString = value?.ToLower();
|
||||
}
|
||||
}
|
||||
public string PlatformNameFilterString
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public long CharacterId
|
||||
{
|
||||
get { return Get<long>(); }
|
||||
set { Set(value); }
|
||||
}
|
||||
public string CharacterName
|
||||
{
|
||||
get { return Get<string>(); }
|
||||
set
|
||||
{
|
||||
Set(value);
|
||||
|
||||
CharacterNameFilterString = value?.ToLower();
|
||||
}
|
||||
}
|
||||
public string CharacterNameFilterString
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public bool IsOnline
|
||||
{
|
||||
get { return Get<bool>(); }
|
||||
set { Set(value); }
|
||||
}
|
||||
public bool IsAdmin
|
||||
{
|
||||
get { return Get<bool>(); }
|
||||
set { Set(value); }
|
||||
}
|
||||
public bool IsWhitelisted
|
||||
{
|
||||
get { return Get<bool>(); }
|
||||
set { Set(value); }
|
||||
}
|
||||
public string GuildName
|
||||
{
|
||||
get { return Get<string>(); }
|
||||
set
|
||||
{
|
||||
Set(value);
|
||||
|
||||
GuildNameFilterString = value?.ToLower();
|
||||
}
|
||||
}
|
||||
public string GuildNameFilterString
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public bool IsValid
|
||||
{
|
||||
get { return Get<bool>(); }
|
||||
set { Set(value); }
|
||||
}
|
||||
public int? LastOnline
|
||||
{
|
||||
get { return Get<int?>(); }
|
||||
set { Set(value); }
|
||||
}
|
||||
public PlayerData PlayerData
|
||||
{
|
||||
get { return Get<PlayerData>(); }
|
||||
set { Set(value); }
|
||||
}
|
||||
|
||||
public void UpdateData(PlayerData playerData)
|
||||
{
|
||||
this.PlayerData = playerData;
|
||||
this.PlayerId = playerData?.PlayerId;
|
||||
this.CharacterId = playerData?.CharacterId ?? 0L;
|
||||
this.CharacterName = playerData?.CharacterName;
|
||||
this.GuildName = playerData?.Guild?.GuildName;
|
||||
this.IsOnline = playerData?.Online ?? false;
|
||||
this.LastOnline = playerData?.LastOnline;
|
||||
}
|
||||
|
||||
public void UpdatePlatformData(PlayerData playerData)
|
||||
{
|
||||
if (playerData == null)
|
||||
return;
|
||||
|
||||
playerData.PlayerName = PlayerData?.PlayerName;
|
||||
playerData.LastPlatformUpdateUtc = PlayerData?.LastPlatformUpdateUtc ?? DateTime.MinValue;
|
||||
}
|
||||
|
||||
#region INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private Dictionary<string, object> _properties = new Dictionary<string, object>();
|
||||
|
||||
protected T Get<T>([CallerMemberName] string name = null)
|
||||
{
|
||||
object value = null;
|
||||
if (_properties?.TryGetValue(name, out value) ?? false)
|
||||
return value == null ? default : (T)value;
|
||||
return default;
|
||||
}
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
protected void Set<T>(T value, [CallerMemberName] string name = null)
|
||||
{
|
||||
if (Equals(value, Get<T>(name)))
|
||||
return;
|
||||
if (_properties == null)
|
||||
_properties = new Dictionary<string, object>();
|
||||
_properties[name] = value;
|
||||
OnPropertyChanged(name);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue