From ccb2a00a7113e3105242e88d9aade62610a1596a Mon Sep 17 00:00:00 2001 From: ZakFahey Date: Mon, 27 Jul 2020 10:05:28 -0700 Subject: [PATCH 1/3] Fix config file access errors when starting up multiple TShock servers at once Use case would be multi-server networks. This commit ensures that sscconfig.json is read with FileShare.Read enabled and stops writing to config.json and sscconfig.json on server load immediately after reading it if the file already exists, which is a no-op since neither of the config files have changed at that point. --- TShockAPI/FileTools.cs | 7 +++++-- TShockAPI/ServerSideCharacters/ServerSideConfig.cs | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/TShockAPI/FileTools.cs b/TShockAPI/FileTools.cs index ba6a9272..314c7c5a 100644 --- a/TShockAPI/FileTools.cs +++ b/TShockAPI/FileTools.cs @@ -108,7 +108,10 @@ namespace TShockAPI TShock.Config = ConfigFile.Read(ConfigPath); // Add all the missing config properties in the json file } - TShock.Config.Write(ConfigPath); + else + { + TShock.Config.Write(ConfigPath); + } if (File.Exists(ServerSideCharacterConfigPath)) { @@ -127,8 +130,8 @@ namespace TShockAPI new NetItem(-16, 1, 0) } }; + TShock.ServerSideCharacterConfig.Write(ServerSideCharacterConfigPath); } - TShock.ServerSideCharacterConfig.Write(ServerSideCharacterConfigPath); } /// diff --git a/TShockAPI/ServerSideCharacters/ServerSideConfig.cs b/TShockAPI/ServerSideCharacters/ServerSideConfig.cs index 31d5caac..b1c6f6da 100644 --- a/TShockAPI/ServerSideCharacters/ServerSideConfig.cs +++ b/TShockAPI/ServerSideCharacters/ServerSideConfig.cs @@ -48,7 +48,8 @@ namespace TShockAPI.ServerSideCharacters public static ServerSideConfig Read(string path) { - using (var reader = new StreamReader(path)) + using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new StreamReader(fileStream)) { string txt = reader.ReadToEnd(); var config = JsonConvert.DeserializeObject(txt); From be2040a7402e927966e0009ebc3a2035d39c8d62 Mon Sep 17 00:00:00 2001 From: ZakFahey Date: Mon, 27 Jul 2020 10:10:14 -0700 Subject: [PATCH 2/3] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bcf7f49..1697775c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin ## Upcoming changes * Added Gravedigger's Shovel support. (@Zennos) +* You can now start up multiple TShock servers at once without getting a startup error. (@ZakFahey) ## TShock 4.4.0 (Pre-release 12) * Fixed various bugs related to Snake Charmer's Flute. (@rustly) From 90f519a1c64880dbd69b0a5d31d89eab1c4ec36b Mon Sep 17 00:00:00 2001 From: ZakFahey Date: Sat, 1 Aug 2020 10:13:04 -0700 Subject: [PATCH 3/3] Overwrite the config if any new fields are missing --- TShockAPI/ConfigFile.cs | 24 +++++++--- TShockAPI/DB/GroupManager.cs | 7 ++- TShockAPI/FileTools.cs | 46 ++++++++++++++++--- .../ServerSideCharacters/ServerSideConfig.cs | 13 +++++- 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs index acfeb61a..b65c5402 100644 --- a/TShockAPI/ConfigFile.cs +++ b/TShockAPI/ConfigFile.cs @@ -602,14 +602,21 @@ namespace TShockAPI /// Reads a configuration file from a given path /// /// string path - /// ConfigFile object - public static ConfigFile Read(string path) + /// The path to the config file + /// + /// Whether the config object has any new files in it, meaning that the config file has to be + /// overwritten. + /// + public static ConfigFile Read(string path, out bool anyMissingFields) { if (!File.Exists(path)) + { + anyMissingFields = true; return new ConfigFile(); + } using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - return Read(fs); + return Read(fs, out anyMissingFields); } } @@ -617,14 +624,17 @@ namespace TShockAPI /// Reads the configuration file from a stream /// /// stream + /// + /// Whether the config object has any new fields in it, meaning that the config file has to be + /// overwritten. + /// /// ConfigFile object - public static ConfigFile Read(Stream stream) + public static ConfigFile Read(Stream stream, out bool anyMissingFields) { using (var sr = new StreamReader(stream)) { - var cf = JsonConvert.DeserializeObject(sr.ReadToEnd()); - if (ConfigRead != null) - ConfigRead(cf); + var cf = FileTools.LoadConfigAndCheckForMissingFields(sr.ReadToEnd(), out anyMissingFields); + ConfigRead?.Invoke(cf); return cf; } } diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs index ceb8e433..eae7df43 100644 --- a/TShockAPI/DB/GroupManager.cs +++ b/TShockAPI/DB/GroupManager.cs @@ -389,7 +389,7 @@ namespace TShockAPI.DB } // Read the config file to prevent the possible loss of any unsaved changes - TShock.Config = ConfigFile.Read(FileTools.ConfigPath); + TShock.Config = ConfigFile.Read(FileTools.ConfigPath, out bool writeConfig); if (TShock.Config.DefaultGuestGroupName == oldGroup.Name) { TShock.Config.DefaultGuestGroupName = newGroup.Name; @@ -399,7 +399,10 @@ namespace TShockAPI.DB { TShock.Config.DefaultRegistrationGroupName = newGroup.Name; } - TShock.Config.Write(FileTools.ConfigPath); + if (writeConfig) + { + TShock.Config.Write(FileTools.ConfigPath); + } // We also need to check if any users belong to the old group and automatically apply changes using (var command = db.CreateCommand()) diff --git a/TShockAPI/FileTools.cs b/TShockAPI/FileTools.cs index 314c7c5a..7b421213 100644 --- a/TShockAPI/FileTools.cs +++ b/TShockAPI/FileTools.cs @@ -16,9 +16,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; using TShockAPI.ServerSideCharacters; namespace TShockAPI @@ -103,23 +107,26 @@ namespace TShockAPI CreateIfNot(MotdPath, MotdFormat); CreateIfNot(WhitelistPath); + bool writeConfig = true; // Default to true if the file doesn't exist if (File.Exists(ConfigPath)) { - TShock.Config = ConfigFile.Read(ConfigPath); - // Add all the missing config properties in the json file + TShock.Config = ConfigFile.Read(ConfigPath, out writeConfig); } - else + if (writeConfig) { + // Add all the missing config properties in the json file TShock.Config.Write(ConfigPath); } + bool writeSSCConfig = true; // Default to true if the file doesn't exist if (File.Exists(ServerSideCharacterConfigPath)) { - TShock.ServerSideCharacterConfig = ServerSideConfig.Read(ServerSideCharacterConfigPath); - // Add all the missing config properties in the json file + TShock.ServerSideCharacterConfig = + ServerSideConfig.Read(ServerSideCharacterConfigPath, out writeSSCConfig); } - else + if (writeSSCConfig) { + // Add all the missing config properties in the json file TShock.ServerSideCharacterConfig = new ServerSideConfig { StartingInventory = @@ -166,5 +173,30 @@ namespace TShockAPI return true; } } + + /// + /// Parse some json text and also return whether any fields are missing from the json + /// + /// The type of the config file object + /// The json text to parse + /// Whether any fields are missing from the config + /// The config object + internal static T LoadConfigAndCheckForMissingFields(string json, out bool anyMissingFields) + { + JObject jObject = JObject.Parse(json); + + anyMissingFields = false; + var configFields = new HashSet(typeof(T).GetFields() + .Where(field => !field.IsStatic) + .Select(field => field.Name)); + var jsonFields = new HashSet(jObject + .Children() + .Select(field => field as JProperty) + .Where(field => field != null) + .Select(field => field.Name)); + anyMissingFields = !configFields.SetEquals(jsonFields); + + return jObject.ToObject(); + } } -} \ No newline at end of file +} diff --git a/TShockAPI/ServerSideCharacters/ServerSideConfig.cs b/TShockAPI/ServerSideCharacters/ServerSideConfig.cs index b1c6f6da..701dce11 100644 --- a/TShockAPI/ServerSideCharacters/ServerSideConfig.cs +++ b/TShockAPI/ServerSideCharacters/ServerSideConfig.cs @@ -46,13 +46,22 @@ namespace TShockAPI.ServerSideCharacters [Description("The starting default inventory for new SSC.")] public List StartingInventory = new List(); - public static ServerSideConfig Read(string path) + /// + /// Reads a server-side configuration file from a given path + /// + /// The path to the config file + /// + /// Whether the config object has any new fields in it, meaning that the config file has to be + /// overwritten. + /// + /// ConfigFile object + public static ServerSideConfig Read(string path, out bool anyMissingFields) { using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var reader = new StreamReader(fileStream)) { string txt = reader.ReadToEnd(); - var config = JsonConvert.DeserializeObject(txt); + var config = FileTools.LoadConfigAndCheckForMissingFields(txt, out anyMissingFields); return config; } }