From 4b08765c49eaff521d24026c2db72c3547a2ed59 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 4 Jun 2017 18:35:26 +1000 Subject: [PATCH] Upgrade tempranks to new format. Allows temp ranking accurately to exact secons (addresses #277), also ranking deletes temprank, fixes #349 --- MCGalaxy/Commands/Moderation/CmdSetRank.cs | 7 +- MCGalaxy/Commands/Moderation/CmdTempRank.cs | 98 ++++++++++----------- MCGalaxy/CorePlugin/ModActionHandler.cs | 20 +++-- MCGalaxy/Player/List/PlayerExtList.cs | 3 +- MCGalaxy/Player/List/PlayerList.cs | 3 +- MCGalaxy/Server/Server.Fields.cs | 3 +- MCGalaxy/Server/Server.Init.cs | 5 +- MCGalaxy/Server/Server.cs | 16 +--- MCGalaxy/Server/Tasks/ServerTasks.cs | 72 +++++++++++---- MCGalaxy/Server/Tasks/UpgradeTasks.cs | 30 +++++++ MCGalaxy/util/Extensions/DateExts.cs | 10 +++ MCGalaxy/util/IO/Paths.cs | 17 ++-- MCGalaxy/util/Threading/Scheduler.cs | 8 ++ 13 files changed, 189 insertions(+), 103 deletions(-) diff --git a/MCGalaxy/Commands/Moderation/CmdSetRank.cs b/MCGalaxy/Commands/Moderation/CmdSetRank.cs index 45ce4bc31..7dac693bb 100644 --- a/MCGalaxy/Commands/Moderation/CmdSetRank.cs +++ b/MCGalaxy/Commands/Moderation/CmdSetRank.cs @@ -89,10 +89,11 @@ namespace MCGalaxy.Commands.Moderation { MessageTooHighRank(p, "change the rank of", false); return false; } if (p != null && newRank.Permission >= p.Rank) { - Player.Message(p, "Can only rank up to below {0}", newRank.ColoredName); return false; + Player.Message(p, "Cannot rank a player to a rank equal to or higher than yours."); return false; } - if (p != null && newRank.Permission >= p.Rank) { - Player.Message(p, "Cannot change the rank of a player to a rank equal or higher to yours."); return false; + if (newRank.Permission == curRank.Permission) { + Player.Message(p, "{0} %Sis already ranked {1}.", + PlayerInfo.GetColoredName(p, name), curRank.ColoredName); return false; } if (who == null) return true; diff --git a/MCGalaxy/Commands/Moderation/CmdTempRank.cs b/MCGalaxy/Commands/Moderation/CmdTempRank.cs index afcef5695..30b195d20 100644 --- a/MCGalaxy/Commands/Moderation/CmdTempRank.cs +++ b/MCGalaxy/Commands/Moderation/CmdTempRank.cs @@ -60,7 +60,7 @@ namespace MCGalaxy.Commands.Moderation { TimeSpan duration = TimeSpan.Zero; if (!CommandParser.GetTimespan(p, args[2], ref duration, "temp rank for", 'h')) return; - foreach (string line in Server.TempRanks.Find(target)) { + if (Server.tempRanks.Contains(target)) { Player.Message(p, "&cThe player already has a temporary rank assigned!"); return; } @@ -78,72 +78,70 @@ namespace MCGalaxy.Commands.Moderation { } static void Delete(Player p, string target) { - bool assigned = false; - StringBuilder all = new StringBuilder(); + string line = Server.tempRanks.FindData(target); + if (line == null) { + Player.Message(p, "{0}&c has not been assigned a temp rank.", + PlayerInfo.GetColoredName(p, target)); + return; + } + + string[] parts = line.SplitSpaces(); Player who = PlayerInfo.FindExact(target); Group curRank = who != null ? who.group : Group.findPlayerGroup(target); + + Group oldRank = Group.Find(parts[4 - 1]); // -1 because data, not whole line + // Old rank was deleted, can't revert rank + if (oldRank == null) { + Server.tempRanks.Remove(target); + return; + } + string reason = "temp rank unassigned"; + if (!CmdSetRank.CanChangeRank(target, curRank, oldRank, who, p, ref reason)) return; - foreach (string line in File.ReadAllLines(Paths.TempRanksFile)) { - if (!line.CaselessStarts(target)) { all.AppendLine(line); continue; } - - string[] parts = line.SplitSpaces(); - Group newRank = Group.Find(parts[2]); - if (!CmdSetRank.CanChangeRank(target, curRank, newRank, who, p, ref reason)) return; - - ModAction action = new ModAction(target, p, ModActionType.Rank, reason); - action.Metadata = newRank; - OnModActionEvent.Call(action); - assigned = true; - } - - if (!assigned) { - Player.Message(p, "&a{0}&c has not been assigned a temp rank.", target); return; - } - File.WriteAllText(Paths.TempRanksFile, all.ToString()); + ModAction action = new ModAction(target, p, ModActionType.Rank, reason); + action.Metadata = oldRank; + action.targetGroup = curRank; + OnModActionEvent.Call(action); } - static void Info(Player p, string name) { - List rankings = Server.TempRanks.FindMatches(p, name, "temp rank"); - if (rankings == null) return; - - foreach (string line in rankings) { - PrintTempRankInfo(p, line); return; + static void Info(Player p, string target) { + string line = Server.tempRanks.FindData(target); + if (line == null) { + Player.Message(p, "{0}&c has not been assigned a temp rank.", + PlayerInfo.GetColoredName(p, target)); + } else { + PrintTempRankInfo(p, line); } } static void List(Player p) { - int count = 0; - foreach (string line in File.ReadAllLines(Paths.TempRanksFile)) { - if (count == 0) - Player.Message(p, "&ePlayers with a temporary rank assigned:"); - PrintTempRankInfo(p, line); - count++; - } - if (count == 0) + List lines = Server.tempRanks.AllLines(); + if (lines.Count == 0) { Player.Message(p, "&cThere are no players with a temporary rank assigned."); + } else { + Player.Message(p, "&ePlayers with a temporary rank assigned:"); + foreach (string line in lines) { + PrintTempRankInfo(p, line); + } + } } static void PrintTempRankInfo(Player p, string line) { string[] args = line.SplitSpaces(); - string tempRanker = args[9]; - string tempRank = Group.GetColoredName(args[1]); - string oldRank = Group.GetColoredName(args[2]); + string assigner = args[1]; + DateTime assigned = long.Parse(args[2]).FromUnixTime(); + DateTime expiry = long.Parse(args[3]).FromUnixTime(); + string oldRank = Group.GetColoredName(args[4]); + string tempRank = Group.GetColoredName(args[5]); - int min = int.Parse(args[4]), hour = int.Parse(args[5]); - int day = int.Parse(args[6]), month = int.Parse(args[7]), year = int.Parse(args[8]); - int periodH = int.Parse(args[3]), periodM = 0; - if (args.Length > 10) periodM = int.Parse(args[10]); - - DateTime assigned = new DateTime(year, month, day, hour, min, 0); - DateTime expiry = assigned.AddHours(periodH).AddMinutes(periodM); - TimeSpan delta = DateTime.Now - assigned; - TimeSpan expireDelta = expiry - DateTime.Now; - - Player.Message(p, "Temp rank information for {0}:", PlayerInfo.GetColoredName(p, args[0])); + TimeSpan assignDelta = DateTime.UtcNow - assigned; + TimeSpan expireDelta = expiry - DateTime.UtcNow; + Player.Message(p, "Temp rank information for {0}:", + PlayerInfo.GetColoredName(p, args[0])); Player.Message(p, " From {0} %Sto {1}%S, by {2} &a{3} %Sago, expires in &a{4}", - oldRank, tempRank, tempRanker, - delta.Shorten(), expireDelta.Shorten()); + oldRank, tempRank, assigner, + assignDelta.Shorten(), expireDelta.Shorten()); } public override void Help(Player p) { diff --git a/MCGalaxy/CorePlugin/ModActionHandler.cs b/MCGalaxy/CorePlugin/ModActionHandler.cs index 2bd8392ca..519c354b5 100644 --- a/MCGalaxy/CorePlugin/ModActionHandler.cs +++ b/MCGalaxy/CorePlugin/ModActionHandler.cs @@ -20,6 +20,7 @@ using System; using MCGalaxy.Commands; using MCGalaxy.Commands.Moderation; using MCGalaxy.Events; +using MCGalaxy.Tasks; namespace MCGalaxy.Core { internal static class ModActionHandler { @@ -213,10 +214,14 @@ namespace MCGalaxy.Core { if (who != null) { who.SendMessage("You are now ranked " + newRank.ColoredName + "%S, type /help for your new set of commands."); } + if (Server.tempRanks.Remove(e.Target)) { + ServerTasks.TemprankCalcNextRun(); + Server.tempRanks.Save(); + } WriteRankInfo(e, newRank); if (e.Duration != TimeSpan.Zero) AddTempRank(e, newRank); - ModActionCmd.ChangeRank(e.Target, e.TargetGroup, newRank, who); + ModActionCmd.ChangeRank(e.Target, e.TargetGroup, newRank, who); } static void WriteRankInfo(ModAction e, Group newRank) { @@ -233,13 +238,14 @@ namespace MCGalaxy.Core { } static void AddTempRank(ModAction e, Group newRank) { - DateTime now = DateTime.Now; + long assign = DateTime.UtcNow.ToUnixTime(); + long expiry = DateTime.UtcNow.Add(e.Duration).ToUnixTime(); string assigner = e.Actor == null ? "(console)" : e.Actor.name; - int hours = (int)e.Duration.TotalHours; - - string data = e.Target + " " + newRank.name + " " + e.TargetGroup.name + " " + hours + " " + now.Minute + " " + - now.Hour + " " + now.Day + " " + now.Month + " " + now.Year + " " + assigner + " " + e.Duration.Minutes; - Server.TempRanks.Append(data); + + string data = assigner + " " + assign + " " + expiry + " " + e.TargetGroup.name + " " + newRank.name; + Server.tempRanks.AddOrReplace(e.Target, data); + ServerTasks.TemprankCalcNextRun(); + Server.tempRanks.Save(); } } } diff --git a/MCGalaxy/Player/List/PlayerExtList.cs b/MCGalaxy/Player/List/PlayerExtList.cs index f8434cd2a..dc92f9245 100644 --- a/MCGalaxy/Player/List/PlayerExtList.cs +++ b/MCGalaxy/Player/List/PlayerExtList.cs @@ -32,7 +32,8 @@ namespace MCGalaxy { public string Path; List names = new List(), lines = new List(); - readonly object locker = new object(), saveLocker = new object(); + internal readonly object locker = new object(); + readonly object saveLocker = new object(); /// Returns a copy of all names in the list. public List AllNames() { diff --git a/MCGalaxy/Player/List/PlayerList.cs b/MCGalaxy/Player/List/PlayerList.cs index 4897a4b58..7b53a3ede 100644 --- a/MCGalaxy/Player/List/PlayerList.cs +++ b/MCGalaxy/Player/List/PlayerList.cs @@ -29,7 +29,8 @@ namespace MCGalaxy { public string Path; List names = new List(); - readonly object locker = new object(), saveLocker = new object(); + internal readonly object locker = new object(); + readonly object saveLocker = new object(); public PlayerList() { } public PlayerList(string path) { Path = path; } diff --git a/MCGalaxy/Server/Server.Fields.cs b/MCGalaxy/Server/Server.Fields.cs index 1d74ceee3..9bbdeaa20 100644 --- a/MCGalaxy/Server/Server.Fields.cs +++ b/MCGalaxy/Server/Server.Fields.cs @@ -58,7 +58,6 @@ namespace MCGalaxy { public static PlayerExtList AutoloadMaps; public static PlayerMetaList RankInfo = new PlayerMetaList("text/rankinfo.txt"); - public static PlayerMetaList TempRanks = new PlayerMetaList("text/tempranks.txt"); public static PlayerMetaList Notes = new PlayerMetaList("text/notes.txt"); /// *** DO NOT USE THIS! *** Use VersionString, as this field is a constant and is inlined if used. @@ -81,7 +80,7 @@ namespace MCGalaxy { public static CTFGame ctf = null; public static PlayerList bannedIP, whiteList, ircControllers, muted, invalidIds; public static PlayerList ignored, frozen, hidden, agreed, vip, noEmotes, lockdown; - public static PlayerExtList jailed, models, skins, reach, tempBans, rotations; + public static PlayerExtList jailed, models, skins, reach, tempBans, tempRanks, rotations; public static readonly List Devs = new List(), Mods = new List(); diff --git a/MCGalaxy/Server/Server.Init.cs b/MCGalaxy/Server/Server.Init.cs index 224198a25..68f3e667b 100644 --- a/MCGalaxy/Server/Server.Init.cs +++ b/MCGalaxy/Server/Server.Init.cs @@ -72,9 +72,12 @@ namespace MCGalaxy { skins = PlayerExtList.Load("extra/skins.txt"); reach = PlayerExtList.Load("extra/reach.txt"); invalidIds = PlayerList.Load("extra/invalidids.txt"); - tempBans = PlayerExtList.Load("text/tempbans.txt"); + tempBans = PlayerExtList.Load(Paths.TempBansFile); rotations = PlayerExtList.Load("extra/rotations.txt"); + tempRanks = PlayerExtList.Load(Paths.TempRanksFile); + ServerTasks.TemprankCalcNextRun(); + if (useWhitelist) whiteList = PlayerList.Load("whitelist.txt"); } diff --git a/MCGalaxy/Server/Server.cs b/MCGalaxy/Server/Server.cs index 608c42515..c0d5db46d 100644 --- a/MCGalaxy/Server/Server.cs +++ b/MCGalaxy/Server/Server.cs @@ -116,11 +116,12 @@ namespace MCGalaxy { Background.QueueOnce(LoadMainLevel); Plugin.Load(); Background.QueueOnce(UpgradeTasks.UpgradeOldBlacklist); - Background.QueueOnce(LoadPlayerLists); Background.QueueOnce(LoadAutoloadMaps); Background.QueueOnce(UpgradeTasks.MovePreviousLevelFiles); Background.QueueOnce(UpgradeTasks.UpgradeOldLockdown); + Background.QueueOnce(UpgradeTasks.UpgradeOldTempranks); Background.QueueOnce(UpgradeTasks.UpgradeDBTimeSpent); + Background.QueueOnce(LoadPlayerLists); Background.QueueOnce(SetupSocket); Background.QueueOnce(InitTimers); @@ -130,16 +131,8 @@ namespace MCGalaxy { Devs.Clear(); Mods.Clear(); Background.QueueOnce(InitTasks.UpdateStaffList); - - MainScheduler.QueueRepeat(ServerTasks.TemprankExpiry, - null, TimeSpan.FromMinutes(1)); - MainScheduler.QueueRepeat(ServerTasks.CheckState, - null, TimeSpan.FromSeconds(3)); - - Background.QueueRepeat(ServerTasks.AutoSave, - 1, TimeSpan.FromSeconds(Server.backupInterval)); - Background.QueueRepeat(ServerTasks.BlockUpdates, - null, TimeSpan.FromSeconds(Server.blockInterval)); + + ServerTasks.QueueTasks(); Background.QueueRepeat(ThreadSafeCache.DBCache.CleanupTask, null, TimeSpan.FromMinutes(5)); } @@ -159,7 +152,6 @@ namespace MCGalaxy { if (!Directory.Exists("levels")) Directory.CreateDirectory("levels"); if (!Directory.Exists("bots")) Directory.CreateDirectory("bots"); if (!Directory.Exists("text")) Directory.CreateDirectory("text"); - TempRanks.EnsureExists(); RankInfo.EnsureExists(); Ban.EnsureExists(); diff --git a/MCGalaxy/Server/Tasks/ServerTasks.cs b/MCGalaxy/Server/Tasks/ServerTasks.cs index f6425ded7..c090c8463 100644 --- a/MCGalaxy/Server/Tasks/ServerTasks.cs +++ b/MCGalaxy/Server/Tasks/ServerTasks.cs @@ -17,6 +17,7 @@ permissions and limitations under the Licenses. */ using System; +using System.Collections.Generic; using System.IO; using System.Threading; using MCGalaxy.Commands.Chatting; @@ -26,6 +27,20 @@ using MCGalaxy.Maths; namespace MCGalaxy.Tasks { internal static class ServerTasks { + static SchedulerTask temprankTask; + internal static void QueueTasks() { + temprankTask = Server.MainScheduler.QueueRepeat(TemprankExpiry, + null, TimeSpan.FromHours(1)); + Server.MainScheduler.QueueRepeat(CheckState, + null, TimeSpan.FromSeconds(3)); + + Server.Background.QueueRepeat(AutoSave, + 1, TimeSpan.FromSeconds(Server.backupInterval)); + Server.Background.QueueRepeat(BlockUpdates, + null, TimeSpan.FromSeconds(Server.blockInterval)); + } + + internal static void LocationChecks(SchedulerTask task) { Player[] players = PlayerInfo.Online.Items; players = PlayerInfo.Online.Items; @@ -159,27 +174,48 @@ namespace MCGalaxy.Tasks { } internal static void TemprankExpiry(SchedulerTask task) { - Player[] players = PlayerInfo.Online.Items; - - foreach (string line in File.ReadAllLines(Paths.TempRanksFile)) - foreach (Player p in players) - { - if (!line.CaselessStarts(p.name)) continue; + List lines = Server.tempRanks.AllLines(); + foreach (string line in lines) { string[] args = line.SplitSpaces(); - - int min = int.Parse(args[4]), hour = int.Parse(args[5]); - int day = int.Parse(args[6]), month = int.Parse(args[7]), year = int.Parse(args[8]); - int periodH = int.Parse(args[3]), periodM = 0; - if (args.Length > 10) periodM = int.Parse(args[10]); + if (args.Length < 4) continue; - DateTime expire = new DateTime(year, month, day, hour, min, 0) - .AddHours(periodH).AddMinutes(periodM); - if (DateTime.Now >= expire) - Command.all.Find("temprank").Use(null, p.name + " delete"); + long expiry; + if (!long.TryParse(args[3], out expiry)) continue; + + if (DateTime.UtcNow >= expiry.FromUnixTime()) { + Command.all.Find("temprank").Use(null, args[0] + " delete"); + Server.tempRanks.Remove(args[0]); // handle case of temp rank being same as current rank + } } - - DateTime now = DateTime.UtcNow; - task.Delay = TimeSpan.FromSeconds(60 - now.Second); // TODO: down to seconds + task.Delay = TemprankNextRun(); + } + + internal static void TemprankCalcNextRun() { + temprankTask.Delay = TemprankNextRun(); + temprankTask.NextRun = DateTime.UtcNow.Add(temprankTask.Delay); + Server.MainScheduler.Recheck(); + } + + static TimeSpan TemprankNextRun() { + DateTime nextRun = DateTime.MaxValue; + // Lock because we want to ensure list not modified from under us + lock (Server.tempRanks.locker) { + List lines = Server.tempRanks.AllLines(); + // Line format: name assigner assigntime expiretime oldRank tempRank + + foreach (string line in lines) { + string[] args = line.SplitSpaces(); + if (args.Length < 4) continue; + + long expiry; + if (!long.TryParse(args[3], out expiry)) continue; + + DateTime expireTime = expiry.FromUnixTime(); + if (expireTime < nextRun) + nextRun = expireTime; + } + } + return nextRun - DateTime.UtcNow; } } } \ No newline at end of file diff --git a/MCGalaxy/Server/Tasks/UpgradeTasks.cs b/MCGalaxy/Server/Tasks/UpgradeTasks.cs index d45550a8c..1c391e1d5 100644 --- a/MCGalaxy/Server/Tasks/UpgradeTasks.cs +++ b/MCGalaxy/Server/Tasks/UpgradeTasks.cs @@ -151,6 +151,36 @@ namespace MCGalaxy.Tasks { Directory.Delete("text/lockdown/map"); } + internal static void UpgradeOldTempranks() { + if (!File.Exists(Paths.TempRanksFile)) return; + + // Check if empty, or not old form + using (StreamReader reader = new StreamReader(Paths.TempRanksFile)) { + string line = reader.ReadLine(); + if (line == null) return; + string[] parts = line.SplitSpaces(); + if (parts.Length < 9) return; + } + + string[] lines = File.ReadAllLines(Paths.TempRanksFile); + for (int i = 0; i < lines.Length; i++) { + string[] args = lines[i].SplitSpaces(); + + int min = int.Parse(args[4]), hour = int.Parse(args[5]); + int day = int.Parse(args[6]), month = int.Parse(args[7]), year = int.Parse(args[8]); + int periodH = int.Parse(args[3]), periodM = 0; + if (args.Length > 10) periodM = int.Parse(args[10]); + + DateTime assigned = new DateTime(year, month, day, hour, min, 0); + DateTime expiry = assigned.AddHours(periodH).AddMinutes(periodM); + + // Line format: name assigner assigntime expiretime oldRank tempRank + lines[i] = args[0] + " " + args[9] + " " + assigned.ToUnixTime() + + " " + expiry.ToUnixTime() + " " + args[2] + " " + args[1]; + } + File.WriteAllLines(Paths.TempRanksFile, lines); + } + internal static void UpgradeDBTimeSpent() { DataTable table = Database.Backend.GetRows(PlayerData.DBTable, "TimeSpent", "LIMIT 1"); if (table.Rows.Count == 0) return; // no players diff --git a/MCGalaxy/util/Extensions/DateExts.cs b/MCGalaxy/util/Extensions/DateExts.cs index c9f76d57d..f442082ee 100644 --- a/MCGalaxy/util/Extensions/DateExts.cs +++ b/MCGalaxy/util/Extensions/DateExts.cs @@ -27,6 +27,16 @@ namespace MCGalaxy { int.Parse(parts[2]), int.Parse(parts[3])); } + public static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static DateTime FromUnixTime(this long offset) { + return UnixEpoch.AddTicks(offset * TimeSpan.TicksPerSecond); + } + + public static long ToUnixTime(this DateTime time) { + return (long)(time.ToUniversalTime() - UnixEpoch).TotalSeconds; + } + public static bool AddSpamEntry(this List log, int maxEntries, int checkInterval) { DateTime now = DateTime.UtcNow; if (log.Count > 0 && log.Count >= maxEntries) diff --git a/MCGalaxy/util/IO/Paths.cs b/MCGalaxy/util/IO/Paths.cs index e370b44c6..bd0d0422e 100644 --- a/MCGalaxy/util/IO/Paths.cs +++ b/MCGalaxy/util/IO/Paths.cs @@ -21,14 +21,15 @@ namespace MCGalaxy { public static class Paths { - public const string CustomColorsFile = "text/customcolors.txt"; - public const string TempRanksFile = "text/tempranks.txt"; - public const string CustomTokensFile = "text/custom$s.txt"; - public const string BadWordsFile = "text/badwords.txt"; - public const string EatMessagesFile = "text/eatmessages.txt"; - public const string RulesFile = "text/rules.txt"; - public const string OprulesFile = "text/oprules.txt"; - public const string FaqFile = "text/faq.txt"; + public const string CustomColorsFile = "text/customcolors.txt"; + public const string TempRanksFile = "text/tempranks.txt"; + public const string TempBansFile = "text/tempbans.txt"; + public const string CustomTokensFile = "text/custom$s.txt"; + public const string BadWordsFile = "text/badwords.txt"; + public const string EatMessagesFile = "text/eatmessages.txt"; + public const string RulesFile = "text/rules.txt"; + public const string OprulesFile = "text/oprules.txt"; + public const string FaqFile = "text/faq.txt"; public const string AnnouncementsFile = "text/messages.txt"; public const string AliasesFile = "text/aliases.txt"; public const string NewsFile = "text/news.txt"; diff --git a/MCGalaxy/util/Threading/Scheduler.cs b/MCGalaxy/util/Threading/Scheduler.cs index c51dfcf1a..769210024 100644 --- a/MCGalaxy/util/Threading/Scheduler.cs +++ b/MCGalaxy/util/Threading/Scheduler.cs @@ -58,6 +58,14 @@ namespace MCGalaxy.Tasks { } } + /// Rechecks minimum delay for next task. + /// Useful for when external code changes the delay of a scheduled task. + public void Recheck() { + lock (taskLock) { + handle.Set(); + } + } + SchedulerTask EnqueueTask(SchedulerTask task) { lock (taskLock) {