private static void CalculateMoE(DateTime?moeLastDateXbox, DateTime?moeLastDatePs, DbProvider provider, DbRecorder recorder, MailSender mailSender, string resultDirectoryXbox, string resultDirectoryPs, FtpPutter ftpPutterXbox, FtpPutter ftpPutterPs, int utcShiftToCalculate) { var csw = Stopwatch.StartNew(); recorder.CalculateMoE(utcShiftToCalculate); csw.Stop(); Log.Debug($"Cálculo das MoE feito em {csw.Elapsed.TotalSeconds:N0}."); if (csw.Elapsed.TotalMinutes > 1) { mailSender.Send("Cálculo das MoE", $"Cálculo das MoE feito em {csw.Elapsed.TotalSeconds:N0}."); } if (moeLastDateXbox.HasValue) { PutMoEOnPlataform(Platform.XBOX, moeLastDateXbox, provider, mailSender, resultDirectoryXbox, ftpPutterXbox); } if (moeLastDatePs.HasValue) { PutMoEOnPlataform(Platform.PS, moeLastDatePs, provider, mailSender, resultDirectoryPs, ftpPutterPs); } }
public ImportWn8WotcStat(Fetcher fetcher, Putter putter, DbProvider provider, DbRecorder recorder) { _fetcher = fetcher; _provider = provider; _recorder = recorder; _putter = putter; }
public GetPlayers(Fetcher fetcher, Putter putter, DbProvider provider, DbRecorder recorder) { _fetcher = fetcher; _provider = provider; _recorder = recorder; _putter = putter; }
public async Task ForgetWhoIAm(CommandContext ctx) { try { await ctx.TriggerTypingAsync(); if (!await CanExecute(ctx, Features.Players)) { return; } long userId = (long)(ctx?.User?.Id ?? 0UL); if (userId == 0) { await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. I don't now your Discord User Id! WTF!??!"); return; } var recorder = new DbRecorder(_connectionString); recorder.AssociateDiscordUserToPlayer(userId, 0); await ctx.RespondAsync($"Done, {ctx.User.Mention}, I no longer know who you are on the game."); return; } catch (Exception ex) { Log.Error($"{nameof(WhoIAm)}()", ex); await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. There was an error... the *Coder* will be notified of `{ex.Message}`."); return; } }
public CalculateStats(Fetcher fetcher, Putter putter, DbProvider provider, DbRecorder recorder) { _fetcher = fetcher; _provider = provider; _recorder = recorder; _putter = putter; }
private static Clan CalculateClan(ClanPlataform clan, DbProvider provider, DbRecorder recorder) { Log.DebugFormat("Calculando cla {0}@{1}...", clan.ClanTag, clan.Plataform); var cc = provider.GetClan(clan.Plataform, clan.ClanId); if (cc == null) { Log.Warn("O cla ainda não teve nenhum membro atualizado."); return(null); } if (cc.Count == 0) { Log.Warn("O cla ainda não teve nenhum membro atualizado."); return(null); } Log.InfoFormat("------------------------------------------------------------------"); Log.InfoFormat("cla: {0}@{1}", cc.ClanTag, cc.Plataform); Log.InfoFormat("# Membros: {0};{1};{2} - Patched: {3}", cc.Count, cc.Active, 0, cc.NumberOfPatchedPlayers); Log.InfoFormat("Batalhas: T:{0:N0};A:{1:N0};W:{2:N0}", cc.TotalBattles, cc.ActiveBattles, 0); recorder.SetClanCalculation(cc); return(cc); }
private static void CalculateTanksReferences(DateTime?lastReferencesXbox, DateTime?lastReferencesPs, DbProvider provider, DbRecorder recorder, MailSender mailSender, string resultDirectoryXbox, string resultDirectoryPs, FtpPutter ftpPutterXbox, FtpPutter ftpPutterPs, int utcShiftToCalculate) { var csw = Stopwatch.StartNew(); recorder.CalculateReference(utcShiftToCalculate); csw.Stop(); Log.Debug($"Cálculo das Referências de Tanques feito em {csw.Elapsed.TotalSeconds:N0}."); if (csw.Elapsed.TotalMinutes > 1) { mailSender.Send("Cálculo das Referências de Tanques", $"Cálculo das Referências de Tanques feito em {csw.Elapsed.TotalSeconds:N0}."); } if (lastReferencesXbox.HasValue) { PutTanksReferencesOnPlataform(Platform.XBOX, lastReferencesXbox, provider, mailSender, resultDirectoryXbox, ftpPutterXbox, utcShiftToCalculate); } if (lastReferencesPs.HasValue) { PutTanksReferencesOnPlataform(Platform.PS, lastReferencesPs, provider, mailSender, resultDirectoryPs, ftpPutterPs, utcShiftToCalculate); } }
public async Task SetWhoIAm(CommandContext ctx, [Description("The *gamer tag* or *PSN Name*")][RemainingText] string gamerTag = "") { try { await ctx.TriggerTypingAsync(); if (!await CanExecute(ctx, Features.Players)) { return; } long userId = (long)(ctx?.User?.Id ?? 0UL); if (userId == 0) { await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. I don't now your Discord User Id! WTF!??!"); return; } var cfg = GuildConfiguration.FromGuild(ctx.Guild); var plataform = GetPlataform(gamerTag, cfg.Plataform, out gamerTag); if (string.IsNullOrWhiteSpace(gamerTag)) { gamerTag = ctx.User.Username; } var provider = new DbProvider(_connectionString); var playerId = provider.GetPlayerIdByName(plataform, gamerTag); if (playerId == null) { await ctx.RespondAsync($"Sorry, {ctx.User.Mention}, I do not track `{gamerTag}` yet. Are you a member of a tracked clan? Are you sure about the gamer tag?"); return; } var recorder = new DbRecorder(_connectionString); recorder.AssociateDiscordUserToPlayer(userId, playerId.Value); await ctx.RespondAsync($"Ok, {ctx.User.Mention}, for now on you can use `me` instead of your full {plataform.TagName()} on " + $"commands that take it as a parameters. I promisse to never abuse this association, and protected it from use outside of this system. " + $"If you want me to remove this piece of information use the `ForgetWhoIAm` command."); return; } catch (Exception ex) { Log.Error($"{nameof(WhoIAm)}()", ex); await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. There was an error... the *Coder* will be notified of `{ex.Message}`."); return; } }
private static void RetrievePlayer(Player player, Fetcher fetcher, DbProvider provider, DbRecorder recorder, IReadOnlyDictionary <long, Tank> allTanks, Wn8ExpectedValues wn8Expected, Putter putter) { var tanks = fetcher.GetTanksForPlayer(player.Plataform, player.Id); var validTanks = tanks.Where(t => allTanks.ContainsKey(t.TankId)).ToArray(); recorder.Set(validTanks); var played = provider.GetWn8RawStatsForPlayer(player.Plataform, player.Id); player.Performance = played; player.Calculate(wn8Expected); player.Moment = DateTime.UtcNow; player.Origin = PlayerDataOrigin.Self; var previous = provider.GetPlayer(player.Id, player.Date, true); if (previous != null) { if (player.Check(previous, true)) { Log.Warn($"Player {player.Name}.{player.Id}@{player.Plataform} was patched."); } } if (player.CanSave()) { recorder.Set(player); if (!player.IsPatched) { _ = Task.Run(() => { try { putter?.Put(player); } catch (Exception ex) { Log.Error($"Error putting player {player.Id} on the remote site.", ex); } }); } } else { Log.Warn($"Player {player.Name}.{player.Id}@{player.Plataform} has to much zero data."); } }
private static void RetrievePlayer(Player player, Fetcher fetcher, Fetcher externalFetcher, DbProvider provider, DbRecorder recorder, IReadOnlyDictionary <long, Tank> allTanks, Wn8ExpectedValues wn8Expected, Putter putter) { Task <Player> externalTask = null; if (externalFetcher != null) { externalTask = externalFetcher.GetPlayerWn8Async((Player)player.Clone()); } var tanks = fetcher.GetTanksForPlayer(player.Plataform, player.Id); var validTanks = tanks.Where(t => allTanks.ContainsKey(t.TankId)).ToArray(); recorder.Set(validTanks); var played = provider.GetWn8RawStatsForPlayer(player.Plataform, player.Id); player.Performance = played; player.Calculate(wn8Expected); player.Moment = DateTime.UtcNow; player.Origin = PlayerDataOrigin.Self; var previous = provider.GetPlayer(player.Id, player.Date, true); if (previous != null) { if (player.Check(previous, true)) { Log.Warn($"Player {player.Name}.{player.Id}@{player.Plataform} was patched."); } } if (player.CanSave()) { recorder.Set(player); if (!player.IsPatched) { putter?.Put(player); } } else { Log.Warn($"Player {player.Name}.{player.Id}@{player.Plataform} has to much zero data."); } // Se foi feita a chamada na API externa, espero o retorno, que será ignorado; mas serviu para popular o BD do parceiro. externalTask?.Wait(); }
private static void GetXvmWn8() { var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var provider = new DbProvider(connectionString); var recorder = new DbRecorder(connectionString); var webCacheAge = TimeSpan.FromMinutes(10); var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1), ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; }
public async Task PurgePlayer(CommandContext ctx, [Description("Player Id")] long playerId) { await ctx.TriggerTypingAsync(); var userId = ctx.User?.Id ?? 0; Log.Info($"Requesting {nameof(GetPlayerById)}({playerId}) by {userId}..."); if (userId != _coder) { var emoji = DiscordEmoji.FromName(ctx.Client, ":no_entry:"); var embed = new DiscordEmbedBuilder { Title = "Access denied", Description = $"{emoji} You may be a *coder*, but you are not **The Coder**!", Color = DiscordColor.Red }; await ctx.RespondAsync("", embed : embed); return; } try { var provider = new DbProvider(_connectionString); var player = provider.GetPlayer(playerId); if (player == null) { await ctx.RespondAsync($"There is no player on the database with the id `{playerId}`."); return; } await ctx.RespondAsync($"The id `{playerId}` corresponds to the tanker `{player.Name}` on the `{player.Platform}`..."); var recorder = new DbRecorder(_connectionString); recorder.PurgePlayer(playerId); await ctx.RespondAsync($"The tanker `{player.Name}` on `{player.Platform}` was purged from the database. Rip."); } catch (Exception ex) { Log.Error($"{nameof(GetDbStatus)}", ex); await ctx.RespondAsync( $"Sorry, {ctx.User?.Mention}. There was an error... the *Coder* will be notified of `{ex.Message}`."); } }
private static void GetAllTanks(Plataform plataform) { var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = TimeSpan.FromMinutes(15), WebFetchInterval = TimeSpan.FromSeconds(1), ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; var tanks = fetcher.GetTanks(Plataform.PC).ToArray(); var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var recorder = new DbRecorder(connectionString); recorder.Set(tanks); }
private static void GetXvmWn8() { string connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; DbProvider provider = new DbProvider(connectionString); DbRecorder recorder = new DbRecorder(connectionString); TimeSpan webCacheAge = TimeSpan.FromMinutes(10); string cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; Fetcher fetcher = new Fetcher(cacheDirectory) { WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1), ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; Negri.Wot.Tanks.Tank[] data = provider.EnumTanks(Platform.XBOX).ToArray(); }
/// <summary> /// Retrieve and populate all medals in the game /// </summary> private static void PopulateAllMedals() { var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = TimeSpan.FromMinutes(15), WebFetchInterval = TimeSpan.FromSeconds(1), ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; var gameMedals = fetcher.GetMedals(Platform.XBOX).ToDictionary(m => m.Code); var maxCode = gameMedals.Values.Max(m => m.Code.Length); var maxName = gameMedals.Values.Max(m => m.Name.Length); var maxDescription = gameMedals.Values.Max(m => m.Description.Length); var maxHeroInformation = gameMedals.Values.Max(m => m.HeroInformation?.Length ?? 0); var maxCondition = gameMedals.Values.Max(m => m.Condition?.Length ?? 0); var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var recorder = new DbRecorder(connectionString); recorder.Set(gameMedals.Values); }
private static void CalculateAndPutReferences() { var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var provider = new DbProvider(connectionString); var recorder = new DbRecorder(connectionString); var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; var wargamingApplicationId = ConfigurationManager.AppSettings["WgAppId"]; var wotClansAdminApiKey = ConfigurationManager.AppSettings["ApiAdminKey"]; var webCacheAge = TimeSpan.FromMinutes(1); var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1), WargamingApplicationId = wargamingApplicationId, WotClansAdminApiKey = wotClansAdminApiKey, WotClansBaseUrl = "http://localhost/ClanStatsWeb" }; var putter = new Putter(wotClansAdminApiKey) { BaseUrl = "http://localhost/ClanStatsWeb" }; const int utcShiftToCalculate = -7; const int topLeaders = 50; const int maxParallel = 1; recorder.CalculateReference(utcShiftToCalculate); var siteDiagnostic = fetcher.GetSiteDiagnostic(); var lastLeaderboard = siteDiagnostic.TankLeadersLastDate; Log.Info($"Last leaderboard on site: {lastLeaderboard:yyyy-MM-dd}"); var cd = DateTime.UtcNow.AddHours(utcShiftToCalculate); var previousMonday = cd.PreviousDayOfWeek(DayOfWeek.Monday); Log.Info($"Previous Monday: {previousMonday:yyyy-MM-dd}"); if (previousMonday <= lastLeaderboard) { Log.Info("No need to upload."); return; } Log.Info($"Getting tanks stats for {previousMonday:yyyy-MM-dd}..."); var references = provider.GetTanksReferences(previousMonday, null, true, false, true, topLeaders).ToArray(); Log.Debug($"Data for {references.Length} tanks retrieved."); var leaders = new ConcurrentBag <Leader>(); Parallel.For(0, references.Length, new ParallelOptions { MaxDegreeOfParallelism = maxParallel }, i => { var r = references[i]; Log.Debug($"Putting references for tank {r.Name}..."); if (!putter.Put(r)) { Log.Error($"Error putting tank reference files for tank {r.Name}."); } foreach (var leader in r.Leaders) { leaders.Add(leader); } }); var orderedLeaders = leaders.OrderByDescending(l => l.Tier).ThenBy(l => l.Type).ThenBy(l => l.Nation).ThenBy(l => l.Name).ThenBy(l => l.Order) .ToArray(); Log.Info($"Uploading leaderboard with {orderedLeaders.Length} players..."); if (!putter.Put(previousMonday, orderedLeaders)) { Log.Error("Error putting leaders to the server."); } }
/// <summary> /// Calcula todos os clas habilitados e os salva /// </summary> private static void CalculateAllClans() { var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var provider = new DbProvider(connectionString); var clans = provider.GetClans().ToArray(); Log.InfoFormat("{0} clas devem ser calculados.", clans.Length); var recorder = new DbRecorder(connectionString); var putterXbox = new FtpPutter(ConfigurationManager.AppSettings["FtpFolder"], ConfigurationManager.AppSettings["FtpUser"], ConfigurationManager.AppSettings["FtpPassworld"], Plataform.XBOX); var putterPs = new FtpPutter(ConfigurationManager.AppSettings["PsFtpFolder"], ConfigurationManager.AppSettings["PsFtpUser"], ConfigurationManager.AppSettings["PsFtpPassworld"], Plataform.PS); var resultDirectory = ConfigurationManager.AppSettings["ResultDirectory"]; var resultDirectoryPs = ConfigurationManager.AppSettings["PsResultDirectory"]; var already = new HashSet <string>(File.ReadAllLines(Path.Combine(resultDirectory, "CalcTask.txt"))); var alreadyPs = new HashSet <string>(File.ReadAllLines(Path.Combine(resultDirectoryPs, "CalcTask.txt"))); var o = new object(); // Calcula cada cla var doneCount = 0; var sw = Stopwatch.StartNew(); Parallel.For(0, clans.Length, new ParallelOptions { MaxDegreeOfParallelism = 2 }, i => { var clan = clans[i]; bool done = false; if (clan.Plataform == Plataform.XBOX) { done = already.Contains(clan.ClanTag); } else if (clan.Plataform == Plataform.PS) { done = alreadyPs.Contains(clan.ClanTag); } if (done) { Log.InfoFormat("cla {0} de {1}: {2}@{3} feito anteriormente.", i + 1, clans.Length, clan.ClanTag, clan.Plataform); Interlocked.Increment(ref doneCount); return; } Log.InfoFormat("Processando cla {0} de {1}: {2}@{3}...", i + 1, clans.Length, clan.ClanTag, clan.Plataform); var csw = Stopwatch.StartNew(); var cc = CalculateClan(clan, provider, recorder); Log.InfoFormat("Calculado cla {0} de {1}: {2}@{3} em {4:N1}s...", i + 1, clans.Length, clan.ClanTag, clan.Plataform, csw.Elapsed.TotalSeconds); if (cc != null) { var fsw = Stopwatch.StartNew(); switch (cc.Plataform) { case Plataform.XBOX: { var fileName = cc.ToFile(resultDirectory); Log.InfoFormat("Arquivo de resultado escrito em '{0}'", fileName); putterXbox.PutClan(fileName); lock (o) { File.AppendAllText(Path.Combine(resultDirectory, "CalcTask.txt"), $"{cc.ClanTag}\r\n", Encoding.UTF8); } } break; case Plataform.PS: { var fileName = cc.ToFile(resultDirectoryPs); Log.InfoFormat("Arquivo de resultado escrito em '{0}'", fileName); putterPs.PutClan(fileName); lock (o) { File.AppendAllText(Path.Combine(resultDirectoryPs, "CalcTask.txt"), $"{cc.ClanTag}\r\n", Encoding.UTF8); } } break; case Plataform.Virtual: break; default: throw new ArgumentOutOfRangeException(); } Log.InfoFormat("Upload do cla {0} de {1}: {2}@{3} em {4:N1}s...", i + 1, clans.Length, clan.ClanTag, clan.Plataform, fsw.Elapsed.TotalSeconds); } Interlocked.Increment(ref doneCount); Log.InfoFormat("Processado cla {0} de {1}: {2}@{3} em {4:N1}s. {5} totais.", i + 1, clans.Length, clan.ClanTag, clan.Plataform, csw.Elapsed.TotalSeconds, doneCount); }); var calculationTime = sw.Elapsed; }
public CalculateClans(Putter putter, DbProvider provider, DbRecorder recorder) { _provider = provider; _recorder = recorder; _putter = putter; }
private static int Main(string[] args) { try { var sw = Stopwatch.StartNew(); ParseParans(args, out int ageHours, out int maxClans, out int maxRunMinutes, out bool noSaveOnDatabase, out TimeSpan webFetchInterval, out int maxToAutoAdd, out int hourToAutoAdd, out int autoMinNumberOfMembers); var saveOnDatabase = !noSaveOnDatabase; var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; Log.Info("------------------------------------------------------------------------------------"); Log.Info("FetchClanMembership iniciando..."); Log.InfoFormat( "ageHours: {0}; maxClans: {1}; maxRunMinutes: {2}; cacheDirectory: {3}; noSaveOnDatabase:{4}; webFetchInterval:{5}", ageHours, maxClans, maxRunMinutes, cacheDirectory, noSaveOnDatabase, webFetchInterval); var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var provider = new DbProvider(connectionString); var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = TimeSpan.FromHours(ageHours - 1), WebFetchInterval = webFetchInterval, ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; var recorder = new DbRecorder(connectionString); var mailSender = new MailSender(ConfigurationManager.AppSettings["SmtpHost"], int.Parse(ConfigurationManager.AppSettings["SmtpPort"]), true, ConfigurationManager.AppSettings["SmtpUsername"], ConfigurationManager.AppSettings["SmtpPassword"]) { From = new MailAddress(ConfigurationManager.AppSettings["SmtpUsername"], "Membership Service") }; // Verifica a necessidade de adicionar novos clãs ao sistema var clansToAdd = provider.GetClansToAdd().ToArray(); foreach (var clanToAdd in clansToAdd) { var clanWithId = fetcher.FindClan(clanToAdd.Plataform, clanToAdd.ClanTag); if (clanWithId != null) { var existingClan = provider.GetClan(clanWithId.Plataform, clanWithId.ClanId); if (existingClan != null) { if (existingClan.Enabled) { Log.WarnFormat( "Foi requerido adicionar o clã {0}@{1}, mas já existe no sistema o clã {2}.{3}@{4}.", clanToAdd.ClanTag, clanToAdd.Plataform, existingClan.ClanId, existingClan.ClanTag, existingClan.Plataform); mailSender.Send($"Clã {clanWithId.ClanTag} já existe e está habilitado", $"Id: {clanWithId.ClanId}; Plataforma: {clanWithId.Plataform}; URL: {GetClanUrl(clanWithId.Plataform, clanWithId.ClanTag)}"); } else { // Reabilitar o cla recorder.EnableClan(clanWithId.Plataform, clanWithId.ClanId); mailSender.Send($"Habilitado manualmente o clã {clanWithId.ClanTag}", $"Id: {clanWithId.ClanId}; Plataforma: {clanWithId.Plataform}; URL: {GetClanUrl(clanWithId.Plataform, clanWithId.ClanTag)}"); } } else { clanWithId.Country = clanToAdd.Country; recorder.Add(clanWithId); mailSender.Send($"Adicionado manualmente o clã {clanWithId.ClanTag}", $"Id: {clanWithId.ClanId}; Plataforma: {clanWithId.Plataform}; URL: {GetClanUrl(clanWithId.Plataform, clanWithId.ClanTag)}"); } } else { Log.WarnFormat( "Foi requerido adicionar o clã {0}@{1}, mas ele não foi encontrado, ou é muito pequeno.", clanToAdd.ClanTag, clanToAdd.Plataform); mailSender.Send($"Clã {clanToAdd.ClanTag}@{clanToAdd.Plataform} não adicionado", $"Foi requerido adicionar o clã {clanToAdd.ClanTag}@{clanToAdd.Plataform}, mas ele não foi encontrado, ou é muito pequeno."); } } recorder.ClearClansToAddQueue(); // Autocadastro de clãs if (maxToAutoAdd > 0 && DateTime.UtcNow.Hour == hourToAutoAdd) { var toAdd = fetcher.GetClans(Platform.XBOX, autoMinNumberOfMembers) .Where(c => !provider.ClanExists(c.Plataform, c.ClanId)) .Concat(fetcher.GetClans(Platform.PS, autoMinNumberOfMembers) .Where(c => !provider.ClanExists(c.Plataform, c.ClanId))) .OrderByDescending(c => c.AllMembersCount).ThenBy(c => c.ClanId).ToArray(); foreach (var c in toAdd.Take(maxToAutoAdd)) { recorder.Add(c); mailSender.Send($"Adicionado automaticamente o clã {c.ClanTag}@{c.Plataform}", $"Id: {c.ClanId}, Plataforma: {c.Plataform}; Membros: {c.AllMembersCount}; URL: {GetClanUrl(c.Plataform, c.ClanTag)}"); } // Readição de clãs pequenos ou que ficaram inativos recorder.ReAddClans(maxToAutoAdd); } var clans = provider.GetClanMembershipUpdateOrder(maxClans, ageHours).ToArray(); Log.InfoFormat("{0} clãs devem ser atualizados.", clans.Length); if (clans.Length == 0) { Log.Info("Nenhum clã para atualizar."); return(0); } var clansToRename = new List <Clan>(); var clansToUpdate = fetcher.GetClans(clans).ToArray(); // Clãs debandados foreach (var clan in clansToUpdate.Where(c => c.IsDisbanded)) { var disbandedClan = provider.GetClan(clan.Plataform, clan.ClanId); if (saveOnDatabase) { recorder.DisableClan(disbandedClan.Plataform, disbandedClan.ClanId, DisabledReason.Disbanded); var putter = new Putter(disbandedClan.Plataform, ConfigurationManager.AppSettings["ApiAdminKey"]); putter.DeleteClan(disbandedClan.ClanTag); } var sb = new StringBuilder(); sb.AppendLine( $"Clã {disbandedClan.ClanTag}, id {disbandedClan.ClanId}, no {disbandedClan.Plataform}, com {disbandedClan.Count} jogadores, foi debandado."); sb.AppendLine($"WN8t15: {disbandedClan.Top15Wn8:N0} em {disbandedClan.Moment:yyyy-MM-dd HH:mm:ss}"); sb.AppendLine($"URL: {GetClanUrl(disbandedClan.Plataform, disbandedClan.ClanTag)}"); sb.AppendLine(); sb.AppendLine("Jogadores:"); foreach (var player in disbandedClan.Players) { sb.AppendLine( $"{player.Name}: WN8 recente de {player.MonthWn8:N0} em {player.MonthBattles:N0}"); } mailSender.Send($"Clã {disbandedClan.ClanTag}@{disbandedClan.Plataform} foi debandado", sb.ToString()); } // Clãs que ficaram pequenos demais foreach (var clan in clansToUpdate.Where(c => !c.IsDisbanded && c.Count < 4)) { if (saveOnDatabase) { recorder.DisableClan(clan.Plataform, clan.ClanId, DisabledReason.TooSmall); } mailSender.Send($"Clã {clan.ClanTag}@{clan.Plataform} foi suspenso", $"Clã {clan.ClanTag}@{clan.Plataform} com {clan.Count} jogadores foi suspenso de atualizações futuras. " + $"URL: {GetClanUrl(clan.Plataform, clan.ClanTag)}"); } foreach (var clan in clansToUpdate.Where(c => !c.IsDisbanded && c.Count >= 4)) { Log.DebugFormat("Clã {0}.{1}@{2}...", clan.ClanId, clan.ClanTag, clan.Plataform); if (saveOnDatabase) { recorder.Set(clan, true); if (clan.HasChangedTag) { clansToRename.Add(clan); } } } if (clansToRename.Any(c => c.Plataform == Platform.XBOX)) { var putter = new FtpPutter(ConfigurationManager.AppSettings["FtpFolder"], ConfigurationManager.AppSettings["FtpUser"], ConfigurationManager.AppSettings["FtpPassworld"]); var resultDirectory = Path.Combine(ConfigurationManager.AppSettings["ResultDirectory"], "Clans"); foreach (var clan in clansToRename.Where(c => c.Plataform == Platform.XBOX)) { Log.InfoFormat("O clã {0}.{1}@{2} teve o tag trocado a partir de {3}.", clan.ClanId, clan.ClanTag, clan.Plataform, clan.OldTag); // Faz copia do arquivo local, e o upload com o novo nome string oldFile = Path.Combine(resultDirectory, $"clan.{clan.OldTag}.json"); if (File.Exists(oldFile)) { string newFile = Path.Combine(resultDirectory, $"clan.{clan.ClanTag}.json"); File.Copy(oldFile, newFile, true); _ = Task.Run(() => { try { putter.PutClan(newFile); putter.DeleteFile($"Clans/clan.{clan.OldTag}.json"); putter.SetRenameFile(clan.OldTag, clan.ClanTag); } catch (Exception ex) { Log.Error($"Error on XBOX renaming clan {clan.OldTag} to {clan.ClanTag}", ex); } }); } mailSender.Send($"Clã Renomeado: {clan.OldTag} -> {clan.ClanTag} em {clan.Plataform}", $"URL: {GetClanUrl(clan.Plataform, clan.ClanTag)}"); } } if (clansToRename.Any(c => c.Plataform == Platform.PS)) { var putter = new FtpPutter(ConfigurationManager.AppSettings["PsFtpFolder"], ConfigurationManager.AppSettings["PsFtpUser"], ConfigurationManager.AppSettings["PsFtpPassworld"]); var resultDirectory = ConfigurationManager.AppSettings["PsResultDirectory"]; foreach (var clan in clansToRename.Where(c => c.Plataform == Platform.PS)) { Log.InfoFormat("O clã {0}.{1}@{2} teve o tag trocado a partir de {3}.", clan.ClanId, clan.ClanTag, clan.Plataform, clan.OldTag); // Faz copia do arquivo local, e o upload com o novo nome string oldFile = Path.Combine(resultDirectory, $"clan.{clan.OldTag}.json"); if (File.Exists(oldFile)) { string newFile = Path.Combine(resultDirectory, $"clan.{clan.ClanTag}.json"); File.Copy(oldFile, newFile, true); _ = Task.Run(() => { try { putter.PutClan(newFile); putter.DeleteFile($"Clans/clan.{clan.OldTag}.json"); putter.SetRenameFile(clan.OldTag, clan.ClanTag); } catch (Exception ex) { Log.Error($"Error on PS renaming clan {clan.OldTag} to {clan.ClanTag}", ex); } }); } mailSender.Send($"Clã Renomeado: {clan.OldTag} -> {clan.ClanTag} em {clan.Plataform}", $"URL: {GetClanUrl(clan.Plataform, clan.ClanTag)}"); } } // Tempo extra para os e-mails terminarem de serem enviados Log.Info("Esperando 2 minutos antes de encerrar para terminar de enviar os e-mails..."); Thread.Sleep(TimeSpan.FromMinutes(2)); Log.InfoFormat("FetchClanMembership terminando normalmente em {0}.", sw.Elapsed); return(0); } catch (Exception ex) { Log.Fatal(ex); Console.WriteLine(ex); return(1); } }
private static int Main(string[] args) { try { ParseParams(args, out var ageHours, out var maxRunMinutes, out var hourToDeleteOldFiles, out var daysToKeepOnDelete, out var calculateMoe, out var calculateReference, out var playersPerMinute, out var utcShiftToCalculate, out var waitForRemote); var resultDirectoryXbox = ConfigurationManager.AppSettings["ResultDirectory"]; var resultDirectoryPs = ConfigurationManager.AppSettings["PsResultDirectory"]; Log.Info("------------------------------------------------------------------------------------"); Log.Info("CalculateClanStats iniciando..."); Log.InfoFormat( "ageHours: {0}; maxRunMinutes: {1}; resultDirectory: {2}; resultDirectoryPs: {3}; utcShiftToCalculate: {4}; waitForRemote: {5}", ageHours, maxRunMinutes, resultDirectoryXbox, resultDirectoryPs, utcShiftToCalculate, waitForRemote); var sw = Stopwatch.StartNew(); var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var provider = new DbProvider(connectionString); var clans = provider.GetClanCalculateOrder(ageHours).ToArray(); Log.InfoFormat("{0} clãs devem ser calculados.", clans.Length); const int averageOutTimeMinutes = 30; const int averageClanSize = 25; var maxPerDay = playersPerMinute * 60 * 24 - averageOutTimeMinutes * playersPerMinute; var minPerDay = maxPerDay - averageClanSize; Log.Info($"playersPerMinute: {playersPerMinute}; maxPerDay: {maxPerDay}; minPerDay: {minPerDay}"); var recorder = new DbRecorder(connectionString); var putterXbox = new FtpPutter(ConfigurationManager.AppSettings["FtpFolder"], ConfigurationManager.AppSettings["FtpUser"], ConfigurationManager.AppSettings["FtpPassworld"]); var putterPs = new FtpPutter(ConfigurationManager.AppSettings["PsFtpFolder"], ConfigurationManager.AppSettings["PsFtpUser"], ConfigurationManager.AppSettings["PsFtpPassworld"]); var mailSender = new MailSender(ConfigurationManager.AppSettings["SmtpHost"], int.Parse(ConfigurationManager.AppSettings["SmtpPort"]), true, ConfigurationManager.AppSettings["SmtpUsername"], ConfigurationManager.AppSettings["SmtpPassword"]) { From = new MailAddress(ConfigurationManager.AppSettings["SmtpUsername"], "Calculate Service") }; var webCacheAge = TimeSpan.FromMinutes(1); var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1), ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; // Obtém o status, por causa da data dos MoE, para disparar o cálculo assíncrono DateTime lastMoeXbox, lastMoePs, lastReferencesXbox, lastReferencesPs; SiteDiagnostic siteStatusXbox, siteStatusPs; try { siteStatusXbox = fetcher.GetSiteDiagnostic( ConfigurationManager.AppSettings["RemoteSiteStatusApi"], ConfigurationManager.AppSettings["ApiAdminKey"]); siteStatusPs = fetcher.GetSiteDiagnostic( ConfigurationManager.AppSettings["PsRemoteSiteStatusApi"], ConfigurationManager.AppSettings["ApiAdminKey"]); lastMoeXbox = siteStatusXbox.TanksMoELastDate; lastMoePs = siteStatusPs.TanksMoELastDate; lastReferencesXbox = siteStatusXbox.TankLeadersLastDate; lastReferencesPs = siteStatusPs.TankLeadersLastDate; } catch (Exception ex) { Log.Error("Error getting initial site diagnostics", ex); // Since the site id offline... siteStatusXbox = siteStatusPs = new SiteDiagnostic(null); GetLastDatesFromFiles(resultDirectoryXbox, out lastMoeXbox, out lastReferencesXbox); GetLastDatesFromFiles(resultDirectoryPs, out lastMoePs, out lastReferencesPs); } Log.Info($"lastMoeXbox: {lastMoeXbox:yyyy-MM-dd}"); Log.Info($"lastMoePs: {lastMoePs:yyyy-MM-dd}"); Log.Info($"lastReferencesXbox: {lastReferencesXbox:yyyy-MM-dd}"); Log.Info($"lastReferencesPs: {lastReferencesPs:yyyy-MM-dd}"); // Dispara as tarefas de calculo gerais var calculationTask = Task.Run(() => RunCalculations(calculateReference, calculateMoe, lastMoeXbox, lastMoePs, lastReferencesXbox, lastReferencesPs, provider, recorder, mailSender, resultDirectoryXbox, resultDirectoryPs, putterXbox, putterPs, fetcher, utcShiftToCalculate)); // Dispara a tarefa de apagar arquivos antigos dos servidores var deleteTask = Task.Run(() => { if (DateTime.UtcNow.Hour != hourToDeleteOldFiles || daysToKeepOnDelete < 28) { return; } // Apaga arquivos com a API administrativa var cleanXBox = Task.Run(() => { try { var cleaner = new Putter(Platform.XBOX, ConfigurationManager.AppSettings["ApiAdminKey"]); cleaner.CleanFiles(); } catch (Exception ex) { Log.Error("Erro cleaning XBOX", ex); } }); var cleanPs = Task.Run(() => { try { var cleaner = new Putter(Platform.PS, ConfigurationManager.AppSettings["ApiAdminKey"]); cleaner.CleanFiles(); } catch (Exception ex) { Log.Error("Erro cleaning XBOX", ex); } }); if (waitForRemote) { Task.WhenAll(cleanXBox, cleanPs); } }); // Calcula cada clã var doneCount = 0; var timedOut = false; Parallel.For(0, clans.Length, new ParallelOptions { MaxDegreeOfParallelism = 2 }, i => { if (sw.Elapsed.TotalMinutes > maxRunMinutes) { timedOut = true; return; } var clan = clans[i]; Log.InfoFormat("Processando clã {0} de {1}: {2}@{3}...", i + 1, clans.Length, clan.ClanTag, clan.Plataform); var csw = Stopwatch.StartNew(); var cc = CalculateClan(clan, provider, recorder); Log.InfoFormat("Calculado clã {0} de {1}: {2}@{3} em {4:N1}s...", i + 1, clans.Length, clan.ClanTag, clan.Plataform, csw.Elapsed.TotalSeconds); if (cc != null) { var fsw = Stopwatch.StartNew(); switch (cc.Plataform) { case Platform.XBOX: { var fileName = cc.ToFile(resultDirectoryXbox); Log.InfoFormat("Arquivo de resultado escrito em '{0}'", fileName); var putTask = Task.Run(() => { try { putterXbox.PutClan(fileName); } catch (Exception ex) { Log.Error($"Error putting XBOX clan file for {cc.ClanTag}", ex); } }); if (waitForRemote) { putTask.Wait(); } } break; case Platform.PS: { var fileName = cc.ToFile(resultDirectoryPs); Log.InfoFormat("Arquivo de resultado escrito em '{0}'", fileName); var putTask = Task.Run(() => { try { putterPs.PutClan(fileName); } catch (Exception ex) { Log.Error($"Error putting PS clan file for {cc.ClanTag}", ex); } }); if (waitForRemote) { putTask.Wait(); } } break; case Platform.Virtual: break; default: throw new ArgumentOutOfRangeException(); } Log.InfoFormat("Upload do clã {0} de {1}: {2}@{3} em {4:N1}s...", i + 1, clans.Length, clan.ClanTag, clan.Plataform, fsw.Elapsed.TotalSeconds); } Interlocked.Increment(ref doneCount); Log.InfoFormat("Processado clã {0} de {1}: {2}@{3} em {4:N1}s. {5} totais.", i + 1, clans.Length, clan.ClanTag, clan.Plataform, csw.Elapsed.TotalSeconds, doneCount); }); var calculationTime = sw.Elapsed; // Envia o e-mail de status try { siteStatusXbox = fetcher.GetSiteDiagnostic(ConfigurationManager.AppSettings["RemoteSiteStatusApi"], ConfigurationManager.AppSettings["ApiAdminKey"]); siteStatusPs = fetcher.GetSiteDiagnostic(ConfigurationManager.AppSettings["PsRemoteSiteStatusApi"], ConfigurationManager.AppSettings["ApiAdminKey"]); } catch (Exception ex) { Log.Error("Error getting final site diagnostics", ex); // The site is offline... siteStatusXbox = siteStatusPs = new SiteDiagnostic(null); } Log.Debug("Obtendo informações do BD..."); var dd = provider.GetDataDiagnostic(); Log.InfoFormat("Filas: {0} jogadores; {1} clans; {2} cálculos.", dd.PlayersQueueLenght, dd.MembershipQueueLenght, dd.CalculateQueueLenght); Log.InfoFormat( "{0} jogadores; {1:N0} por dia; {2:N0} por hora; reais: {3:N0}; {4:N0}; {5:N0}; {6:N0}", dd.TotalPlayers, dd.ScheduledPlayersPerDay, dd.ScheduledPlayersPerHour, dd.AvgPlayersPerHourLastDay, dd.AvgPlayersPerHourLast6Hours, dd.AvgPlayersPerHourLast2Hours, dd.AvgPlayersPerHourLastHour); Log.Debug("Enviando e-mail..."); mailSender.SendStatusMessage(siteStatusXbox, siteStatusPs, dd, minPerDay, maxPerDay, calculationTime, doneCount, timedOut); if (dd.ScheduledPlayersPerDay < minPerDay || dd.ScheduledPlayersPerDay > maxPerDay) { recorder.BalanceClanSchedule(minPerDay, maxPerDay); } Log.DebugFormat( "DateTime.UtcNow: {0:o}; hourToDeleteOldFiles: {1}; daysToKeepOnDelete: {2}", DateTime.UtcNow, hourToDeleteOldFiles, daysToKeepOnDelete); // Espera o cálculo e a deleção terminarem Task.WaitAll(calculationTask, deleteTask); // Tempo extra para os e-mails terminarem de serem enviados Thread.Sleep(TimeSpan.FromMinutes(2)); Log.InfoFormat("CalculateClanStats terminando normalmente para {1} clãs em {0}.", sw.Elapsed, clans.Length); return(0); } catch (Exception ex) { Log.Fatal(ex); Console.WriteLine(ex); return(1); } }
public async Task SetClan(CommandContext ctx, [Description("The clan tag")] string clanTag, [Description("The clan flag")] string flagCode = null, [Description("Enable or Disable the Clan")] bool enable = true, [Description("To ban or not a clan from the site")] bool isBan = false) { await ctx.TriggerTypingAsync(); var userId = ctx?.User?.Id ?? 0; Log.Info($"Requesting {nameof(SetClan)} by {userId}..."); if (userId != _coder) { var emoji = DiscordEmoji.FromName(ctx.Client, ":no_entry:"); var embed = new DiscordEmbedBuilder { Title = "Access denied", Description = $"{emoji} You may be a *coder*, but you are not **The Coder**!", Color = DiscordColor.Red }; await ctx.RespondAsync("", embed : embed); return; } var cfg = GuildConfiguration.FromGuild(ctx.Guild); var plataform = GetPlataform(clanTag, cfg.Plataform, out clanTag); clanTag = clanTag.Trim('[', ']'); clanTag = clanTag.ToUpperInvariant(); if (!ClanTagRegex.IsMatch(clanTag)) { await ctx.RespondAsync($"You must send a **valid** clan **tag** as parameter, {ctx.User.Mention}."); return; } if (!string.IsNullOrWhiteSpace(flagCode)) { flagCode = flagCode.RemoveDiacritics().ToUpperInvariant(); if (flagCode.Length != 2) { await ctx.RespondAsync($"The flag code must be 2 letters only, {ctx.User.Mention}."); return; } } Log.Warn($"{nameof(SetClan)}({clanTag}, {plataform}, {flagCode}, {enable}, {isBan})..."); try { await ctx.TriggerTypingAsync(); var provider = new DbProvider(_connectionString); var recorder = new DbRecorder(_connectionString); var cacheDirectory = ConfigurationManager.AppSettings["CacheDir"] ?? Path.GetTempPath(); var webCacheAge = TimeSpan.FromHours(4); var appId = ConfigurationManager.AppSettings["WgAppId"] ?? "demo"; var fetcher = new Fetcher(cacheDirectory) { ApplicationId = appId, WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1) }; var clan = provider.GetClan(plataform, clanTag); if (clan == null && enable) { // Check to add... await ctx.RespondAsync($"Not found `{clanTag}` on the database. Searching the WG API..."); await ctx.TriggerTypingAsync(); var clanOnSite = fetcher.FindClan(plataform, clanTag, true); if (clanOnSite == null) { await ctx.RespondAsync($"Not found `{clanTag}` on the WG API for `{plataform}`. Check the clan tag."); return; } if (clanOnSite.AllMembersCount < 7) { await ctx.RespondAsync($"The clan `{clanTag}` on `{plataform}` has only {clanOnSite.AllMembersCount}, and will not be added to the system."); return; } clanOnSite.Country = flagCode; recorder.Add(clanOnSite); await ctx.RespondAsync($"The clan `{clanTag}` on `{plataform}` with {clanOnSite.AllMembersCount} members was added to the system and " + $"should appear on the site in ~12 hours. Keep playing to achieve at least 7 members with 21 recent battles and appear on the default view."); Log.Info($"Added {plataform}.{clanTag}"); return; } if (!clan.Enabled && enable) { // Can be enabled? var clanOnSite = fetcher.GetClans(new[] { clan }).FirstOrDefault(); if (clanOnSite == null) { await ctx.RespondAsync($"Not found `{clanTag}` on the WG API for `{plataform}`. Check the clan tag."); return; } if (clanOnSite.IsDisbanded) { await ctx.RespondAsync($"The clan `{clanTag}` on `{plataform}` was disbanded."); return; } if (clanOnSite.Count < 7) { await ctx.RespondAsync($"The clan `{clanTag}` on `{plataform}` has only {clanOnSite.Count} members and will not be enabled."); return; } if (clan.DisabledReason == DisabledReason.Banned) { await ctx.RespondAsync($"The clan `{clanTag}` ({clan.ClanId}) on `{plataform}` was **banned** from the site."); return; } recorder.EnableClan(clanOnSite.Plataform, clanOnSite.ClanId); await ctx.RespondAsync($"The clan `{clanTag}` on `{plataform}` disabled for `{clan.DisabledReason}` is enabled again."); Log.Info($"Enabled {plataform}.{clanTag}"); } else if (clan.Enabled && !enable) { if (isBan) { recorder.DisableClan(clan.Plataform, clan.ClanId, DisabledReason.Banned); await ctx.RespondAsync($"The clan `{clanTag}` ({clan.ClanId}) on `{plataform}` was **BANNED** from the site."); Log.Warn($"BANNED {plataform}.{clanTag}"); } else { recorder.DisableClan(clan.Plataform, clan.ClanId, DisabledReason.Unknow); await ctx.RespondAsync($"The clan `{clanTag}` ({clan.ClanId}) on `{plataform}` was **disabled** from the site."); Log.Warn($"Disabled {plataform}.{clanTag}"); } } // change flag? flagCode = flagCode ?? string.Empty; if (flagCode.ToUpperInvariant() != (clan.Country ?? string.Empty).ToUpperInvariant()) { recorder.SetClanFlag(clan.Plataform, clan.ClanId, flagCode); await ctx.RespondAsync($"The flag of the clan `{clanTag}` on `{plataform}` was changed to `{flagCode}`."); Log.Info($"Flag changed on {plataform}.{clanTag} to {flagCode}."); } await ctx.RespondAsync($"all done for `{clan.ClanTag}` on `{plataform}`."); } catch (Exception ex) { Log.Error($"{nameof(SetClan)}", ex); await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. There was an error... the *Coder* will be notified of `{ex.Message}`."); return; } }
private static void RunCalculations(bool calculateReference, bool calculateMoe, DateTime?moeLastDateXbox, DateTime?moeLastDatePs, DateTime?lastReferencesXbox, DateTime?lastReferencesPs, DbProvider provider, DbRecorder recorder, MailSender mailSender, string resultDirectory, string resultDirectoryPs, FtpPutter ftpPutterXbox, FtpPutter ftpPutterPs, Fetcher fetcher, int utcShiftToCalculate) { Debug.Assert(mailSender != null); // Obtém os valores esperados de WN8 if ((DateTime.UtcNow.Hour % 4) == 1) { HandleWn8ExpectedValues(Platform.XBOX, provider, resultDirectory, ftpPutterXbox, recorder, fetcher); HandleWn8ExpectedValues(Platform.PS, provider, resultDirectoryPs, ftpPutterPs, recorder, fetcher); } // 2nd hour of every day retrieve PC Tanks if (DateTime.UtcNow.Hour == 2) { Log.Info("Retrieving PC Tanks..."); recorder.Set(fetcher.GetTanks(Platform.PC)); Log.Debug("PC Tanks saved on Database"); } if (calculateMoe) { CalculateMoE(moeLastDateXbox, moeLastDatePs, provider, recorder, mailSender, resultDirectory, resultDirectoryPs, ftpPutterXbox, ftpPutterPs, utcShiftToCalculate); } if (calculateReference) { CalculateTanksReferences(lastReferencesXbox, lastReferencesPs, provider, recorder, mailSender, resultDirectory, resultDirectoryPs, ftpPutterXbox, ftpPutterPs, utcShiftToCalculate); } }
private static void GetFromWoTStatConsole() { var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var provider = new DbProvider(connectionString); var recorder = new DbRecorder(connectionString); var webCacheAge = TimeSpan.FromMinutes(10); var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1), ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; Log.Debug("Obtendo jogadores a atualizar."); const int ageHours = 24; var players = provider.GetPlayersUpdateOrder(1000, ageHours).Where(p => p.AdjustedAgeHours < 1000).Take(1000).ToArray(); if (players.Length <= 0) { Log.Warn("Fila vazia!"); return; } Log.Info($"{players.Length} na fila."); var sw = Stopwatch.StartNew(); var cts = new CancellationTokenSource(); var po = new ParallelOptions { CancellationToken = cts.Token, MaxDegreeOfParallelism = 2 }; bool isEnd = false; int count = 0; Parallel.For(0, players.Length, po, i => { if (isEnd) { return; } if (sw.Elapsed.TotalMinutes > 10) { isEnd = true; Log.Warn("Tempo Esgotado!"); Log.Info($"Completo! {count} em {sw.Elapsed.TotalSeconds}. {count / sw.Elapsed.TotalMinutes:N1} players/minute"); return; } var player = players[i]; var task = fetcher.GetPlayerWn8Async(player); task.Wait(po.CancellationToken); var completePlayer = task.Result; if (completePlayer != null) { if (completePlayer.CanSave()) { recorder.Set(completePlayer); Log.Info("Salvo!"); } else { Log.WarnFormat("Jogador {0}.{1}@{2} com muitos dados zerados não será salvo no BD.", completePlayer.Id, completePlayer.Name, completePlayer.Plataform); } } Interlocked.Increment(ref count); }); sw.Stop(); Log.Info($"Completo! {count} em {sw.Elapsed.TotalSeconds}. {count / sw.Elapsed.TotalMinutes:N1} players/minute"); }
public ImportMoe(Fetcher fetcher, DbRecorder recorder) { _fetcher = fetcher; _recorder = recorder; }
/// <summary> /// Retrieves a player from database, or only WG API /// </summary> /// <param name="gamerTag"></param> /// <returns></returns> public async Task <Player> GetPlayer(CommandContext ctx, string gamerTag) { try { var cfg = GuildConfiguration.FromGuild(ctx.Guild); var plataform = GetPlataform(gamerTag, cfg.Plataform, out gamerTag); var provider = new DbProvider(_connectionString); var recorder = new DbRecorder(_connectionString); DiscordMessage willTryApiMessage = null; long?playerId = null; if (gamerTag.EqualsCiAi("me")) { playerId = provider.GetPlayerIdByDiscordId((long)ctx.User.Id); } playerId = playerId ?? provider.GetPlayerIdByName(plataform, gamerTag); if (playerId == null) { Log.Debug($"Could not find player {plataform}.{gamerTag} on the database... trying the API..."); willTryApiMessage = await ctx.RespondAsync($"I could not find a player on `{plataform}` " + $"with the Gamer Tag `{gamerTag}` on the Database, {ctx.User.Mention}. I will try the Wargaming API... it may take some time..."); } var cacheDirectory = ConfigurationManager.AppSettings["CacheDir"] ?? Path.GetTempPath(); var webCacheAge = TimeSpan.FromHours(4); var appId = ConfigurationManager.AppSettings["WgAppId"] ?? "demo"; var fetcher = new Fetcher(cacheDirectory) { ApplicationId = appId, WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1) }; Player apiPlayer = null; if (playerId == null) { await ctx.TriggerTypingAsync(); apiPlayer = fetcher.GetPlayerByGamerTag(plataform, gamerTag); if (apiPlayer == null) { Log.Debug($"Could not find player {plataform}.{gamerTag} on the WG API."); if (willTryApiMessage != null) { await willTryApiMessage.DeleteAsync("Information updated."); } await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. I could not find a player on `{plataform}` " + $"with the Gamer Tag `{gamerTag}` on the Wargaming API. Are you sure about the **exact** gamer tag?."); return(null); } playerId = apiPlayer.Id; } var player = provider.GetPlayer(playerId.Value, true); var wn8Expected = provider.GetWn8ExpectedValues(player?.Plataform ?? apiPlayer.Plataform); if ((player == null) && (apiPlayer != null)) { // Not on my database, but on the API Let's work with overall data! IEnumerable <WgApi.TankPlayer> tanks = fetcher.GetTanksForPlayer(apiPlayer.Plataform, apiPlayer.Id); foreach (var t in tanks) { t.All.TreesCut = t.TreesCut; t.All.BattleLifeTimeSeconds = t.BattleLifeTimeSeconds; t.All.MarkOfMastery = t.MarkOfMastery; t.All.MaxFrags = t.MaxFrags; t.All.LastBattle = t.LastBattle; } apiPlayer.Performance = new Tanks.TankPlayerPeriods() { All = tanks.ToDictionary(t => t.TankId, t => t.All) }; apiPlayer.Calculate(wn8Expected); if (willTryApiMessage != null) { await willTryApiMessage.DeleteAsync("Information updated."); } return(apiPlayer); } if (player.Age.TotalHours > 4) { willTryApiMessage = await ctx.RespondAsync($"Data for `{player.Name}` on `{player.Plataform}` " + $"is more than {player.Age.TotalHours:N0}h old, {ctx.User.Mention}. Retrieving fresh data, please wait..."); var tanks = fetcher.GetTanksForPlayer(player.Plataform, player.Id); var allTanks = provider.GetTanks(player.Plataform).ToDictionary(t => t.TankId); var validTanks = tanks.Where(t => allTanks.ContainsKey(t.TankId)).ToArray(); recorder.Set(validTanks); var played = provider.GetWn8RawStatsForPlayer(player.Plataform, player.Id); player.Performance = played; player.Calculate(wn8Expected); player.Moment = DateTime.UtcNow; player.Origin = PlayerDataOrigin.Self; var previous = provider.GetPlayer(player.Id, player.Date, true); if (previous != null) { if (player.Check(previous, true)) { Log.Warn($"Player {player.Name}.{player.Id}@{player.Plataform} was patched."); } } if (player.CanSave()) { recorder.Set(player); if (!player.IsPatched) { var putter = new Putter(player.Plataform, ConfigurationManager.AppSettings["ApiAdminKey"]); putter.Put(player); } } else { Log.Warn($"Player {player.Name}.{player.Id}@{player.Plataform} has to much zero data."); } } else { player.Calculate(wn8Expected); } if (willTryApiMessage != null) { await willTryApiMessage.DeleteAsync("Information updated."); } return(player); } catch (Exception ex) { Log.Error($"{nameof(GetPlayer)}({gamerTag})", ex); await ctx.RespondAsync($"Sorry, {ctx.User.Mention}. There was an error... the *Coder* will be notified of `{ex.Message}`."); return(null); } }
public PurgeOldPlayers(DbRecorder recorder) { _recorder = recorder; }
private static int Main(string[] args) { try { var sw = Stopwatch.StartNew(); ParseParans(args, out var ageHours, out var maxPlayers, out var maxRunMinutes, out var webCacheAge, out var kp, out var ki, out var kd, out var maxParallel, out var queryWotStatConsoleApi, out var putPlayers); var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; Log.Info("------------------------------------------------------------------------------------"); Log.Info("FetchPlayers iniciando..."); Log.Info($"Compilado em {RetrieveLinkerTimestamp():yyyy-MM-dd HH:mm}; executando no diretorio {Environment.CurrentDirectory}"); Log.InfoFormat("ageHours: {0}; cacheDirectory: {1}, maxParallel: {2}, queryWotStatConsoleApi: {3}", ageHours, cacheDirectory, maxParallel, queryWotStatConsoleApi); Log.Info($"kp: {kp:R}; ki: {ki:R}; kd: {kd:R}"); var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var provider = new DbProvider(connectionString); var recorder = new DbRecorder(connectionString); if (maxRunMinutes == null) { maxRunMinutes = 60 - DateTime.Now.Minute; if (maxRunMinutes < 5) { maxRunMinutes = 5; } } var dbInfo = provider.GetDataDiagnostic(); int originalMaxPlayers = maxPlayers ?? 0; if (maxPlayers == null) { maxPlayers = (int?)(dbInfo.ScheduledPlayersPerHour / 60.0 * maxRunMinutes.Value); originalMaxPlayers = maxPlayers.Value; maxPlayers = (int?)(maxPlayers.Value * 1.20); } Log.Info($"maxRunMinutes: {maxRunMinutes}; maxPlayers: {maxPlayers}; originalMaxPlayers: {originalMaxPlayers}"); var players = provider.GetPlayersUpdateOrder(maxPlayers.Value, ageHours).ToArray(); if (players.Length <= 0) { Log.Warn("Fila vazia!"); return(3); } double playersPerMinute = players.Length * 1.0 / ((double)maxRunMinutes); Log.Debug("Avg playersPerMinute: {playersPerMinute:N0}"); double externalApiCallFactor = 0.0; if (queryWotStatConsoleApi > 0) { externalApiCallFactor = queryWotStatConsoleApi / playersPerMinute; if (externalApiCallFactor > 1.0) { externalApiCallFactor = 1.0; } Log.Debug("externalApiCallFactor: {externalApiCallFactor}"); } var externalCallRandom = new Random(69); var recentlyUpdatedPlayers = players.Select(p => p.AdjustedAgeHours).Where(a => a < 6000).ToArray(); if (recentlyUpdatedPlayers.Any()) { var averageAgeHours = recentlyUpdatedPlayers.Take(originalMaxPlayers).Average(); var modeAgeHours = recentlyUpdatedPlayers .Take(originalMaxPlayers).Select(a => (decimal)(Math.Floor(a * 100.0) / 100.0)).GroupBy(a => a) .OrderByDescending(g => g.Count()).ThenBy(g => g.Key).Select(g => g.Key).FirstOrDefault(); Log.Info($"Idade média dos dados: {averageAgeHours:N2}"); Log.Info($"Idade moda dos dados: {modeAgeHours:N2}"); } Log.Info($"Delay últimas 48h: {dbInfo.Last48HDelay:N2}"); if (dbInfo.Last48HDelay <= ageHours) { // Pontual, faz apenas o planejado Log.InfoFormat("Não está atrasado."); players = players.Take(originalMaxPlayers).ToArray(); } var fetchers = new ConcurrentQueue <Fetcher>(); for (int i = 0; i < maxParallel * 8; ++i) { var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1), ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; fetchers.Enqueue(fetcher); } Log.Debug("Obtendo todos os tanques em XBOX..."); var f = fetchers.Dequeue(); recorder.Set(f.GetTanks(Plataform.XBOX)); var allTanksXbox = provider.GetTanks(Plataform.XBOX).ToDictionary(t => t.TankId); fetchers.Enqueue(f); Log.InfoFormat("Obtidos {0} tanques para XBOX.", allTanksXbox.Count); Log.Debug("Obtendo todos os tanques em PS..."); f = fetchers.Dequeue(); recorder.Set(f.GetTanks(Plataform.PS)); var allTanksPs = provider.GetTanks(Plataform.PS).ToDictionary(t => t.TankId); fetchers.Enqueue(f); Log.InfoFormat("Obtidos {0} tanques para PS.", allTanksPs.Count); // Ambas as plataformas usam os mesmos valores de referência var wn8Expected = provider.GetWn8ExpectedValues(Plataform.XBOX); var idealInterval = (maxRunMinutes.Value * 60.0 - sw.Elapsed.TotalSeconds) / players.Length; double threadInterval = idealInterval * maxParallel * 0.80; var lockObject = new object(); // To save on players on the remote server (Same remote DB, so the plataform doesn't matter) var putter = putPlayers ? new Putter(Plataform.PS, ConfigurationManager.AppSettings["ApiAdminKey"]) : null; var cts = new CancellationTokenSource(); var po = new ParallelOptions { CancellationToken = cts.Token, MaxDegreeOfParallelism = maxParallel }; // A velocidade do loop é controlada por um sistema PID. Veja exemplo em https://www.codeproject.com/Articles/36459/PID-process-control-a-Cruise-Control-example double integral = 0.0; double previousError = 0.0, sumErrorSq = 0.0; DateTime previousTime = DateTime.UtcNow; int count = 0, controlledLoops = 0; try { Parallel.For(0, players.Length, po, i => { if (po.CancellationToken.IsCancellationRequested) { return; } if (sw.Elapsed.TotalMinutes > maxRunMinutes) { cts.Cancel(); } var wg = fetchers.Dequeue(); Fetcher ext = null; if (externalCallRandom.NextDouble() < externalApiCallFactor) { ext = fetchers.Dequeue(); } var player = players[i]; var swPlayer = Stopwatch.StartNew(); var allTanks = player.Plataform == Plataform.XBOX ? allTanksXbox : allTanksPs; RetrievePlayer(player, wg, ext, provider, recorder, allTanks, wn8Expected, putter); swPlayer.Stop(); if (ext != null) { fetchers.Enqueue(ext); } fetchers.Enqueue(wg); Thread.Sleep(TimeSpan.FromSeconds(threadInterval)); lock (lockObject) { Interlocked.Increment(ref count); var actualInterval = sw.Elapsed.TotalSeconds / count; var error = idealInterval - actualInterval; var time = DateTime.UtcNow; var dt = (time - previousTime).TotalSeconds; var remainingSeconds = maxRunMinutes.Value * 60.0 - sw.Elapsed.TotalSeconds; var remainingPlayers = players.Length - count; if ((count > 20) && (remainingSeconds > 60) && (remainingPlayers > 5) && (dt > 0.01)) { integral += error * dt; var derivative = (error - previousError) / dt; var output = (error * kp) + (integral * ki) + (derivative * kd); threadInterval += output; controlledLoops++; sumErrorSq += error * error; if (threadInterval < 0.1) { threadInterval = 0.1; } else if (threadInterval > 30) { threadInterval = 30; } Log.Info($"i: {i:0000}; {player.Id:00000000000}@{player.Plataform.ToString().PadRight(4)}; Count: {count:0000}; " + $"TI: {threadInterval:00.00}; Actual: {actualInterval:00.00}; Ideal: {idealInterval:00.00}; " + $"out: {output:+00.000;-00.000}; dt: {dt:000.000}; rt: {swPlayer.Elapsed.TotalSeconds:000.000}; Target: {maxRunMinutes*60.0/actualInterval:0000}; " + $"HadExt: {ext != null}"); idealInterval = remainingSeconds / remainingPlayers; } else { Log.Info($"i: {i:0000}; {player.Id:00000000000}@{player.Plataform.ToString().PadRight(4)}; Count: {count:0000}; " + $"TI: {threadInterval:00.00}; Actual: {actualInterval:00.00}; Ideal: {idealInterval:00.00}; " + $"out: {0.0:+00.000;-00.000}; dt: {dt:000.000}; rt: {swPlayer.Elapsed.TotalSeconds:000.000}; Target: {maxRunMinutes * 60.0 / actualInterval:0000}; " + $"HadExt: {ext != null}"); } previousError = error; previousTime = time; } }); } catch (OperationCanceledException) { Log.WarnFormat("Tempo esgotado antes de concluir a fila no {0} de {1}. Erro de Controle: {2:N4}", count, players.Length, sumErrorSq / controlledLoops); return(2); } Log.InfoFormat("FetchPlayers terminando normalmente em {0}. Feitos {1}. Erro de Controle: {2:N4}", sw.Elapsed, players.Length, sumErrorSq / controlledLoops); return(0); } catch (Exception ex) { Log.Fatal(ex); Console.WriteLine(ex); return(1); } }
private static void HandleWn8ExpectedValues(Platform platform, DbProvider provider, string resultDirectory, FtpPutter ftpPutter, DbRecorder recorder, Fetcher fetcher) { // Aproveito e pego e salvo os dados de WN8 if ((recorder != null) && (fetcher != null)) { Log.Info($"Pegando dados de WN8 para {platform}..."); recorder.Set(fetcher.GetXvmWn8ExpectedValuesAsync().Result); Log.Info("Dados de WN8 obtidos e salvos."); } var wn8 = provider.GetWn8ExpectedValues(platform); if (wn8 != null) { var json = JsonConvert.SerializeObject(wn8, Formatting.Indented); var file = Path.Combine(resultDirectory, "MoE", $"{wn8.Date:yyyy-MM-dd}.WN8.json"); File.WriteAllText(file, json, Encoding.UTF8); Log.DebugFormat("Salvo o WN8 Expected em '{0}'", file); _ = Task.Run(() => { try { ftpPutter.PutMoe(file); } catch (Exception ex) { Log.Error($"Error putting WN8 on {platform}", ex); } }); Log.Debug($"Feito upload do WN8 para {platform}"); } }
public BalanceClans(DbProvider provider, DbRecorder recorder) { _provider = provider; _recorder = recorder; }
private static void RunCalculations(bool calculateReference, bool calculateMoe, DateTime?moeLastDateXbox, DateTime?moeLastDatePs, DateTime?lastReferencesXbox, DateTime?lastReferencesPs, DbProvider provider, DbRecorder recorder, MailSender mailSender, string resultDirectory, string resultDirectoryPs, FtpPutter ftpPutterXbox, FtpPutter ftpPutterPs, Fetcher fetcher, int utcShiftToCalculate) { Debug.Assert(mailSender != null); // Obtem os valores esperados de WN8 if ((DateTime.UtcNow.Hour % 4) == 1) { HandleWn8ExpectedValues(Platform.XBOX, provider, resultDirectory, ftpPutterXbox, recorder, fetcher); HandleWn8ExpectedValues(Platform.PS, provider, resultDirectoryPs, ftpPutterPs, recorder, fetcher); } if (calculateMoe) { CalculateMoE(moeLastDateXbox, moeLastDatePs, provider, recorder, mailSender, resultDirectory, resultDirectoryPs, ftpPutterXbox, ftpPutterPs, utcShiftToCalculate); } if (calculateReference) { CalculateTanksReferences(lastReferencesXbox, lastReferencesPs, provider, recorder, mailSender, resultDirectory, resultDirectoryPs, ftpPutterXbox, ftpPutterPs, utcShiftToCalculate); } }