Merge pull request #2101 from ZakFahey/general-devel

Fix config file access errors when starting up multiple TShock servers at once
This commit is contained in:
Chris 2020-08-06 11:30:51 +09:30 committed by GitHub
commit 54ad8604aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 20 deletions

View file

@ -14,6 +14,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
## Upcoming changes ## Upcoming changes
* Added Gravedigger's Shovel support. (@Zennos) * 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) ## TShock 4.4.0 (Pre-release 12)
* Fixed various bugs related to Snake Charmer's Flute. (@rustly) * Fixed various bugs related to Snake Charmer's Flute. (@rustly)

View file

@ -602,14 +602,21 @@ namespace TShockAPI
/// Reads a configuration file from a given path /// Reads a configuration file from a given path
/// </summary> /// </summary>
/// <param name="path">string path</param> /// <param name="path">string path</param>
/// <returns>ConfigFile object</returns> /// <param name="path">The path to the config file</param>
public static ConfigFile Read(string path) /// <param name="anyMissingFields">
/// Whether the config object has any new files in it, meaning that the config file has to be
/// overwritten.
/// </param>
public static ConfigFile Read(string path, out bool anyMissingFields)
{ {
if (!File.Exists(path)) if (!File.Exists(path))
{
anyMissingFields = true;
return new ConfigFile(); return new ConfigFile();
}
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) 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 /// Reads the configuration file from a stream
/// </summary> /// </summary>
/// <param name="stream">stream</param> /// <param name="stream">stream</param>
/// <param name="anyMissingFields">
/// Whether the config object has any new fields in it, meaning that the config file has to be
/// overwritten.
/// </param>
/// <returns>ConfigFile object</returns> /// <returns>ConfigFile object</returns>
public static ConfigFile Read(Stream stream) public static ConfigFile Read(Stream stream, out bool anyMissingFields)
{ {
using (var sr = new StreamReader(stream)) using (var sr = new StreamReader(stream))
{ {
var cf = JsonConvert.DeserializeObject<ConfigFile>(sr.ReadToEnd()); var cf = FileTools.LoadConfigAndCheckForMissingFields<ConfigFile>(sr.ReadToEnd(), out anyMissingFields);
if (ConfigRead != null) ConfigRead?.Invoke(cf);
ConfigRead(cf);
return cf; return cf;
} }
} }

View file

@ -389,7 +389,7 @@ namespace TShockAPI.DB
} }
// Read the config file to prevent the possible loss of any unsaved changes // 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) if (TShock.Config.DefaultGuestGroupName == oldGroup.Name)
{ {
TShock.Config.DefaultGuestGroupName = newGroup.Name; TShock.Config.DefaultGuestGroupName = newGroup.Name;
@ -399,7 +399,10 @@ namespace TShockAPI.DB
{ {
TShock.Config.DefaultRegistrationGroupName = newGroup.Name; 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 // We also need to check if any users belong to the old group and automatically apply changes
using (var command = db.CreateCommand()) using (var command = db.CreateCommand())

View file

@ -16,9 +16,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using TShockAPI.ServerSideCharacters; using TShockAPI.ServerSideCharacters;
namespace TShockAPI namespace TShockAPI
@ -103,20 +107,26 @@ namespace TShockAPI
CreateIfNot(MotdPath, MotdFormat); CreateIfNot(MotdPath, MotdFormat);
CreateIfNot(WhitelistPath); CreateIfNot(WhitelistPath);
bool writeConfig = true; // Default to true if the file doesn't exist
if (File.Exists(ConfigPath)) if (File.Exists(ConfigPath))
{ {
TShock.Config = ConfigFile.Read(ConfigPath); TShock.Config = ConfigFile.Read(ConfigPath, out writeConfig);
// Add all the missing config properties in the json file }
if (writeConfig)
{
// Add all the missing config properties in the json file
TShock.Config.Write(ConfigPath);
} }
TShock.Config.Write(ConfigPath);
bool writeSSCConfig = true; // Default to true if the file doesn't exist
if (File.Exists(ServerSideCharacterConfigPath)) if (File.Exists(ServerSideCharacterConfigPath))
{ {
TShock.ServerSideCharacterConfig = ServerSideConfig.Read(ServerSideCharacterConfigPath); TShock.ServerSideCharacterConfig =
// Add all the missing config properties in the json file ServerSideConfig.Read(ServerSideCharacterConfigPath, out writeSSCConfig);
} }
else if (writeSSCConfig)
{ {
// Add all the missing config properties in the json file
TShock.ServerSideCharacterConfig = new ServerSideConfig TShock.ServerSideCharacterConfig = new ServerSideConfig
{ {
StartingInventory = StartingInventory =
@ -127,8 +137,8 @@ namespace TShockAPI
new NetItem(-16, 1, 0) new NetItem(-16, 1, 0)
} }
}; };
TShock.ServerSideCharacterConfig.Write(ServerSideCharacterConfigPath);
} }
TShock.ServerSideCharacterConfig.Write(ServerSideCharacterConfigPath);
} }
/// <summary> /// <summary>
@ -163,5 +173,30 @@ namespace TShockAPI
return true; return true;
} }
} }
/// <summary>
/// Parse some json text and also return whether any fields are missing from the json
/// </summary>
/// <typeparam name="T">The type of the config file object</typeparam>
/// <param name="json">The json text to parse</param>
/// <param name="anyMissingFields">Whether any fields are missing from the config</param>
/// <returns>The config object</returns>
internal static T LoadConfigAndCheckForMissingFields<T>(string json, out bool anyMissingFields)
{
JObject jObject = JObject.Parse(json);
anyMissingFields = false;
var configFields = new HashSet<string>(typeof(T).GetFields()
.Where(field => !field.IsStatic)
.Select(field => field.Name));
var jsonFields = new HashSet<string>(jObject
.Children()
.Select(field => field as JProperty)
.Where(field => field != null)
.Select(field => field.Name));
anyMissingFields = !configFields.SetEquals(jsonFields);
return jObject.ToObject<T>();
}
} }
} }

View file

@ -46,12 +46,22 @@ namespace TShockAPI.ServerSideCharacters
[Description("The starting default inventory for new SSC.")] [Description("The starting default inventory for new SSC.")]
public List<NetItem> StartingInventory = new List<NetItem>(); public List<NetItem> StartingInventory = new List<NetItem>();
public static ServerSideConfig Read(string path) /// <summary>
/// Reads a server-side configuration file from a given path
/// </summary>
/// <param name="path">The path to the config file</param>
/// <param name="anyMissingFields">
/// Whether the config object has any new fields in it, meaning that the config file has to be
/// overwritten.
/// </param>
/// <returns>ConfigFile object</returns>
public static ServerSideConfig Read(string path, out bool anyMissingFields)
{ {
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(); string txt = reader.ReadToEnd();
var config = JsonConvert.DeserializeObject<ServerSideConfig>(txt); var config = FileTools.LoadConfigAndCheckForMissingFields<ServerSideConfig>(txt, out anyMissingFields);
return config; return config;
} }
} }