Add support for .keys file to FileSystemWatcher

This commit is contained in:
JustArchi
2018-02-17 19:29:33 +01:00
parent 08e64193fe
commit c968716940
4 changed files with 199 additions and 95 deletions

View File

@@ -188,7 +188,7 @@ namespace ArchiSteamFarm {
// Before attempting to connect, initialize our configuration // Before attempting to connect, initialize our configuration
await Bot.InitializeSteamConfiguration(Program.GlobalConfig.SteamProtocols, Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false); await Bot.InitializeSteamConfiguration(Program.GlobalConfig.SteamProtocols, Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false);
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).OrderBy(botName => botName)) { foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*" + SharedInfo.ConfigExtension).Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).OrderBy(botName => botName)) {
await Bot.RegisterBot(botName).ConfigureAwait(false); await Bot.RegisterBot(botName).ConfigureAwait(false);
} }
@@ -202,7 +202,7 @@ namespace ArchiSteamFarm {
return; return;
} }
FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") { NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite }; FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory) { NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite };
FileSystemWatcher.Changed += OnChanged; FileSystemWatcher.Changed += OnChanged;
FileSystemWatcher.Created += OnCreated; FileSystemWatcher.Created += OnCreated;
@@ -274,7 +274,16 @@ namespace ArchiSteamFarm {
return; return;
} }
string botName = Path.GetFileNameWithoutExtension(e.Name); await OnChangedFile(e.Name, e.FullPath).ConfigureAwait(false);
}
private static async Task OnChangedConfigFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
}
string botName = Path.GetFileNameWithoutExtension(name);
if (string.IsNullOrEmpty(botName) || (botName[0] == '.')) { if (string.IsNullOrEmpty(botName) || (botName[0] == '.')) {
return; return;
} }
@@ -319,7 +328,29 @@ namespace ArchiSteamFarm {
return; return;
} }
await bot.OnNewConfigLoaded(new BotConfigEventArgs(BotConfig.Load(e.FullPath))).ConfigureAwait(false); await bot.OnNewConfigLoaded(new BotConfigEventArgs(BotConfig.Load(fullPath))).ConfigureAwait(false);
}
private static async Task OnChangedFile(string name, string fullPath) {
string extension = Path.GetExtension(name);
switch (extension) {
case SharedInfo.ConfigExtension:
await OnChangedConfigFile(name, fullPath).ConfigureAwait(false);
break;
case SharedInfo.KeysExtension:
await OnChangedKeysFile(name, fullPath).ConfigureAwait(false);
break;
}
}
private static async Task OnChangedKeysFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
}
await OnCreatedKeysFile(name, fullPath).ConfigureAwait(false);
} }
private static async void OnCreated(object sender, FileSystemEventArgs e) { private static async void OnCreated(object sender, FileSystemEventArgs e) {
@@ -331,39 +362,39 @@ namespace ArchiSteamFarm {
await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false); await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false);
} }
private static async Task<bool> OnCreatedFile(string name, string fullPath) { private static async Task OnCreatedConfigFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) { if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath)); ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return false; return;
} }
string botName = Path.GetFileNameWithoutExtension(name); string botName = Path.GetFileNameWithoutExtension(name);
if (string.IsNullOrEmpty(botName) || (botName[0] == '.')) { if (string.IsNullOrEmpty(botName) || (botName[0] == '.')) {
return false; return;
} }
DateTime lastWriteTime = DateTime.UtcNow; DateTime lastWriteTime = DateTime.UtcNow;
if (LastWriteTimes.TryGetValue(botName, out DateTime savedLastWriteTime)) { if (LastWriteTimes.TryGetValue(name, out DateTime savedLastWriteTime)) {
if (savedLastWriteTime >= lastWriteTime) { if (savedLastWriteTime >= lastWriteTime) {
return false; return;
} }
} }
LastWriteTimes[botName] = lastWriteTime; LastWriteTimes[name] = lastWriteTime;
// It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it // It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it
await Task.Delay(1000).ConfigureAwait(false); await Task.Delay(1000).ConfigureAwait(false);
// It's also possible that we got some other event in the meantime // It's also possible that we got some other event in the meantime
if (LastWriteTimes.TryGetValue(botName, out savedLastWriteTime)) { if (LastWriteTimes.TryGetValue(name, out savedLastWriteTime)) {
if (lastWriteTime != savedLastWriteTime) { if (lastWriteTime != savedLastWriteTime) {
return false; return;
} }
if (LastWriteTimes.TryRemove(botName, out savedLastWriteTime)) { if (LastWriteTimes.TryRemove(name, out savedLastWriteTime)) {
if (lastWriteTime != savedLastWriteTime) { if (lastWriteTime != savedLastWriteTime) {
return false; return;
} }
} }
} }
@@ -371,15 +402,76 @@ namespace ArchiSteamFarm {
if (botName.Equals(SharedInfo.ASF)) { if (botName.Equals(SharedInfo.ASF)) {
ArchiLogger.LogGenericInfo(Strings.GlobalConfigChanged); ArchiLogger.LogGenericInfo(Strings.GlobalConfigChanged);
await RestartOrExit().ConfigureAwait(false); await RestartOrExit().ConfigureAwait(false);
return false; return;
} }
if (!IsValidBotName(botName)) { if (!IsValidBotName(botName)) {
return false; return;
} }
await CreateBot(botName).ConfigureAwait(false); await CreateBot(botName).ConfigureAwait(false);
return true; }
private static async Task OnCreatedFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
}
string extension = Path.GetExtension(name);
switch (extension) {
case SharedInfo.ConfigExtension:
await OnCreatedConfigFile(name, fullPath).ConfigureAwait(false);
break;
case SharedInfo.KeysExtension:
await OnCreatedKeysFile(name, fullPath).ConfigureAwait(false);
break;
}
}
private static async Task OnCreatedKeysFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
}
string botName = Path.GetFileNameWithoutExtension(name);
if (string.IsNullOrEmpty(botName) || (botName[0] == '.')) {
return;
}
DateTime lastWriteTime = DateTime.UtcNow;
if (LastWriteTimes.TryGetValue(name, out DateTime savedLastWriteTime)) {
if (savedLastWriteTime >= lastWriteTime) {
return;
}
}
LastWriteTimes[name] = lastWriteTime;
// It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it
await Task.Delay(1000).ConfigureAwait(false);
// It's also possible that we got some other event in the meantime
if (LastWriteTimes.TryGetValue(name, out savedLastWriteTime)) {
if (lastWriteTime != savedLastWriteTime) {
return;
}
if (LastWriteTimes.TryRemove(name, out savedLastWriteTime)) {
if (lastWriteTime != savedLastWriteTime) {
return;
}
}
}
if (!Bot.Bots.TryGetValue(botName, out Bot bot)) {
return;
}
await bot.ImportKeysToRedeem(fullPath).ConfigureAwait(false);
} }
private static async void OnDeleted(object sender, FileSystemEventArgs e) { private static async void OnDeleted(object sender, FileSystemEventArgs e) {
@@ -391,65 +483,78 @@ namespace ArchiSteamFarm {
await OnDeletedFile(e.Name, e.FullPath).ConfigureAwait(false); await OnDeletedFile(e.Name, e.FullPath).ConfigureAwait(false);
} }
private static async Task<bool> OnDeletedFile(string name, string fullPath) { private static async Task OnDeletedConfigFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) { if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath)); ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return false; return;
} }
string botName = Path.GetFileNameWithoutExtension(name); string botName = Path.GetFileNameWithoutExtension(name);
if (string.IsNullOrEmpty(botName)) { if (string.IsNullOrEmpty(botName)) {
return false; return;
} }
DateTime lastWriteTime = DateTime.UtcNow; DateTime lastWriteTime = DateTime.UtcNow;
if (LastWriteTimes.TryGetValue(botName, out DateTime savedLastWriteTime)) { if (LastWriteTimes.TryGetValue(name, out DateTime savedLastWriteTime)) {
if (savedLastWriteTime >= lastWriteTime) { if (savedLastWriteTime >= lastWriteTime) {
return false; return;
} }
} }
LastWriteTimes[botName] = lastWriteTime; LastWriteTimes[name] = lastWriteTime;
// It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it // It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it
await Task.Delay(1000).ConfigureAwait(false); await Task.Delay(1000).ConfigureAwait(false);
// It's also possible that we got some other event in the meantime // It's also possible that we got some other event in the meantime
if (LastWriteTimes.TryGetValue(botName, out savedLastWriteTime)) { if (LastWriteTimes.TryGetValue(name, out savedLastWriteTime)) {
if (lastWriteTime != savedLastWriteTime) { if (lastWriteTime != savedLastWriteTime) {
return false; return;
} }
if (LastWriteTimes.TryRemove(botName, out savedLastWriteTime)) { if (LastWriteTimes.TryRemove(name, out savedLastWriteTime)) {
if (lastWriteTime != savedLastWriteTime) { if (lastWriteTime != savedLastWriteTime) {
return false; return;
} }
} }
} }
if (botName.Equals(SharedInfo.ASF)) { if (botName.Equals(SharedInfo.ASF)) {
if (File.Exists(fullPath)) { if (File.Exists(fullPath)) {
return false; return;
} }
// Some editors might decide to delete file and re-create it in order to modify it // Some editors might decide to delete file and re-create it in order to modify it
// If that's the case, we wait for maximum of 5 seconds before shutting down // If that's the case, we wait for maximum of 5 seconds before shutting down
await Task.Delay(5000).ConfigureAwait(false); await Task.Delay(5000).ConfigureAwait(false);
if (File.Exists(fullPath)) { if (File.Exists(fullPath)) {
return false; return;
} }
ArchiLogger.LogGenericError(Strings.ErrorGlobalConfigRemoved); ArchiLogger.LogGenericError(Strings.ErrorGlobalConfigRemoved);
await Program.Exit(1).ConfigureAwait(false); await Program.Exit(1).ConfigureAwait(false);
return false; return;
} }
if (Bot.Bots.TryGetValue(botName, out Bot bot)) { if (Bot.Bots.TryGetValue(botName, out Bot bot)) {
await bot.OnNewConfigLoaded(new BotConfigEventArgs()).ConfigureAwait(false); await bot.OnNewConfigLoaded(new BotConfigEventArgs()).ConfigureAwait(false);
} }
}
return true; private static async Task OnDeletedFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
}
string extension = Path.GetExtension(name);
switch (extension) {
case SharedInfo.ConfigExtension:
await OnDeletedConfigFile(name, fullPath).ConfigureAwait(false);
break;
}
} }
private static async void OnRenamed(object sender, RenamedEventArgs e) { private static async void OnRenamed(object sender, RenamedEventArgs e) {
@@ -458,19 +563,8 @@ namespace ArchiSteamFarm {
return; return;
} }
// We must remember to handle all three cases here - *.any to *.json, *.json to *.any and *.json to *.json await OnDeletedFile(e.OldName, e.OldFullPath).ConfigureAwait(false);
await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false);
string oldFileExtension = Path.GetExtension(e.OldName);
if (!string.IsNullOrEmpty(oldFileExtension) && oldFileExtension.Equals(".json")) {
if (!await OnDeletedFile(e.OldName, e.OldFullPath).ConfigureAwait(false)) {
return;
}
}
string newFileExtension = Path.GetExtension(e.Name);
if (!string.IsNullOrEmpty(newFileExtension) && newFileExtension.Equals(".json")) {
await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false);
}
} }
private static async Task RestartOrExit() { private static async Task RestartOrExit() {

View File

@@ -94,13 +94,13 @@ namespace ArchiSteamFarm {
private readonly Trading Trading; private readonly Trading Trading;
private string BotPath => Path.Combine(SharedInfo.ConfigDirectory, BotName); private string BotPath => Path.Combine(SharedInfo.ConfigDirectory, BotName);
private string ConfigFilePath => BotPath + ".json"; private string ConfigFilePath => BotPath + SharedInfo.ConfigExtension;
private string DatabaseFilePath => BotPath + ".db"; private string DatabaseFilePath => BotPath + SharedInfo.DatabaseExtension;
private bool IsAccountLocked => AccountFlags.HasFlag(EAccountFlags.Lockdown); private bool IsAccountLocked => AccountFlags.HasFlag(EAccountFlags.Lockdown);
private string KeysToRedeemAlreadyOwnedFilePath => KeysToRedeemFilePath + ".owned"; private string KeysToRedeemAlreadyOwnedFilePath => KeysToRedeemFilePath + SharedInfo.KeysOwnedExtension;
private string KeysToRedeemFilePath => BotPath + ".keys"; private string KeysToRedeemFilePath => BotPath + SharedInfo.KeysExtension;
private string MobileAuthenticatorFilePath => BotPath + ".maFile"; private string MobileAuthenticatorFilePath => BotPath + SharedInfo.MobileAuthenticatorExtension;
private string SentryFilePath => BotPath + ".bin"; private string SentryFilePath => BotPath + SharedInfo.SentryHashExtension;
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamID))] [JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamID))]
private string SSteamID => SteamID.ToString(); private string SSteamID => SteamID.ToString();
@@ -668,6 +668,51 @@ namespace ArchiSteamFarm {
await ArchiHandler.PlayGames(games.Select(game => game.PlayableAppID), BotConfig.CustomGamePlayedWhileFarming).ConfigureAwait(false); await ArchiHandler.PlayGames(games.Select(game => game.PlayableAppID), BotConfig.CustomGamePlayedWhileFarming).ConfigureAwait(false);
} }
internal async Task ImportKeysToRedeem(string filePath) {
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) {
ArchiLogger.LogNullError(nameof(filePath));
return;
}
try {
Dictionary<string, string> gamesToRedeemInBackground = new Dictionary<string, string>();
using (StreamReader reader = new StreamReader(filePath)) {
string line;
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) {
if (line.Length == 0) {
continue;
}
string[] parsedArgs = line.Split('\t', StringSplitOptions.RemoveEmptyEntries);
if (parsedArgs.Length < 2) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsInvalid, line));
continue;
}
string game = string.Join(" ", parsedArgs.Take(parsedArgs.Length - 1));
string key = parsedArgs[parsedArgs.Length - 1];
if (!IsValidCdKey(key)) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsInvalid, key));
continue;
}
gamesToRedeemInBackground[key] = game;
}
}
if (gamesToRedeemInBackground.Count > 0) {
await AddGamesToRedeemInBackground(gamesToRedeemInBackground).ConfigureAwait(false);
}
File.Delete(filePath);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
}
}
internal static async Task InitializeSteamConfiguration(ProtocolTypes protocolTypes, uint cellID, IServerListProvider serverListProvider) { internal static async Task InitializeSteamConfiguration(ProtocolTypes protocolTypes, uint cellID, IServerListProvider serverListProvider) {
if (serverListProvider == null) { if (serverListProvider == null) {
ASF.ArchiLogger.LogNullError(nameof(serverListProvider)); ASF.ArchiLogger.LogNullError(nameof(serverListProvider));
@@ -1330,47 +1375,6 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorImportFinished); ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorImportFinished);
} }
private async Task ImportKeysToRedeem(string filePath) {
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) {
ArchiLogger.LogNullError(nameof(filePath));
return;
}
try {
using (StreamReader reader = new StreamReader(filePath)) {
Dictionary<string, string> gamesToRedeemInBackground = new Dictionary<string, string>();
string line;
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) {
if (line.Length == 0) {
continue;
}
string[] parsedArgs = line.Split('\t', StringSplitOptions.RemoveEmptyEntries);
if (parsedArgs.Length < 2) {
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, line));
continue;
}
string game = string.Join(" ", parsedArgs.Take(parsedArgs.Length - 1));
string key = parsedArgs[parsedArgs.Length - 1];
if (!IsValidCdKey(key)) {
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, key));
continue;
}
gamesToRedeemInBackground[key] = game;
}
await AddGamesToRedeemInBackground(gamesToRedeemInBackground).ConfigureAwait(false);
File.Delete(filePath);
}
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
}
}
private void InitConnectionFailureTimer() { private void InitConnectionFailureTimer() {
if (ConnectionFailureTimer != null) { if (ConnectionFailureTimer != null) {
return; return;

View File

@@ -336,7 +336,7 @@ namespace ArchiSteamFarm {
} }
} }
string filePath = Path.Combine(SharedInfo.ConfigDirectory, botName + ".json"); string filePath = Path.Combine(SharedInfo.ConfigDirectory, botName + SharedInfo.ConfigExtension);
if (!await BotConfig.Write(filePath, jsonRequest.BotConfig).ConfigureAwait(false)) { if (!await BotConfig.Write(filePath, jsonRequest.BotConfig).ConfigureAwait(false)) {
await ResponseJsonObject(request, response, new GenericResponse(false, "Writing bot config failed, check ASF log for details"), HttpStatusCode.BadRequest).ConfigureAwait(false); await ResponseJsonObject(request, response, new GenericResponse(false, "Writing bot config failed, check ASF log for details"), HttpStatusCode.BadRequest).ConfigureAwait(false);

View File

@@ -44,15 +44,21 @@ namespace ArchiSteamFarm {
internal const ulong ASFGroupSteamID = 103582791440160998; internal const ulong ASFGroupSteamID = 103582791440160998;
internal const string AssemblyName = nameof(ArchiSteamFarm); internal const string AssemblyName = nameof(ArchiSteamFarm);
internal const string ConfigDirectory = "config"; internal const string ConfigDirectory = "config";
internal const string ConfigExtension = ".json";
internal const string DatabaseExtension = ".db";
internal const string DebugDirectory = "debug"; internal const string DebugDirectory = "debug";
internal const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only internal const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
internal const string GithubRepo = "JustArchi/" + AssemblyName; internal const string GithubRepo = "JustArchi/" + AssemblyName;
internal const string GlobalConfigFileName = ASF + ".json"; internal const string GlobalConfigFileName = ASF + ConfigExtension;
internal const string GlobalDatabaseFileName = ASF + ".db"; internal const string GlobalDatabaseFileName = ASF + DatabaseExtension;
internal const string KeysExtension = ".keys";
internal const string KeysOwnedExtension = ".owned";
internal const string LogFile = "log.txt"; internal const string LogFile = "log.txt";
internal const string MobileAuthenticatorExtension = ".maFile";
internal const string SentryHashExtension = ".bin";
internal const string StatisticsServer = "asf.justarchi.net"; internal const string StatisticsServer = "asf.justarchi.net";
internal const string UpdateDirectory = "_old";
internal const string UlongCompatibilityStringPrefix = "s_"; internal const string UlongCompatibilityStringPrefix = "s_";
internal const string UpdateDirectory = "_old";
internal const string WebsiteDirectory = "www"; internal const string WebsiteDirectory = "www";
private const string SourceVariant = "source"; private const string SourceVariant = "source";