public static async Task <IEnumerable <DailyLeaderboard> > GetDailyLeaderboardsAsync(ISteamClientApiClient steamClient)
        {
            var expectedDailyLeaderboards = new DailyLeaderboardsEnumerable(DateTime.UtcNow.Date);

            var missingDaily = expectedDailyLeaderboards.First(d => d.Date == new DateTime(2017, 10, 3, 0, 0, 0, DateTimeKind.Utc));

            // Add leaderboards found in local cache.
            // This does not perform updates since the local cache may contain bad data due to bugs from previous builds.
            var cachedDailyLeaderboards = JsonConvert.DeserializeObject <IEnumerable <DailyLeaderboard> >(Resources.DailyLeaderboards);
            var connectionString        = ConfigurationManager.ConnectionStrings[nameof(LeaderboardsContext)].ConnectionString;

            using (var storeClient = new LeaderboardsStoreClient(connectionString))
            {
                var options = new BulkUpsertOptions {
                    UpdateWhenMatched = false
                };
                await storeClient.BulkUpsertAsync(cachedDailyLeaderboards, options, default).ConfigureAwait(false);
            }

            using (var db = new LeaderboardsContext())
            {
                // Exclude existing daily leaderboards
                var dailyLeaderboards        = db.DailyLeaderboards.ToList();
                var missingDailyLeaderboards = expectedDailyLeaderboards.Except(dailyLeaderboards, new DailyLeaderboardEqualityComparer()).ToList();

                if (missingDailyLeaderboards.Any())
                {
                    // Get the leaderboard IDs for the missing leaderboards
                    await UpdateDailyLeaderboardsAsync(steamClient, missingDailyLeaderboards).ConfigureAwait(false);

                    // Store the new leaderboards
                    foreach (var dailyLeaderboard in missingDailyLeaderboards)
                    {
                        if (dailyLeaderboard.LeaderboardId == 0)
                        {
                            continue;
                        }

                        db.DailyLeaderboards.Add(dailyLeaderboard);
                    }

                    db.SaveChanges();
                }

                return((from l in db.DailyLeaderboards
                        orderby l.Date, l.ProductId, l.IsProduction
                        select l)
                       .ToList());
            }
        }
        public static async Task <IEnumerable <Leaderboard> > GetLeaderboardsAsync(ISteamClientApiClient steamClient)
        {
            using (var db = new LeaderboardsContext())
            {
                var products             = db.Products.ToList();
                var modes                = db.Modes.ToList();
                var runs                 = db.Runs.ToList();
                var characters           = db.Characters.ToList();
                var expectedLeaderboards = new LeaderboardsEnumerable(products, modes, runs, characters);

                // Exclude cached leaderboards
                var cachedLeaderboards  = JsonConvert.DeserializeObject <IEnumerable <Leaderboard> >(Resources.Leaderboards);
                var missingLeaderboards = expectedLeaderboards.Except(cachedLeaderboards, new LeaderboardEqualityComparer()).ToList();
                // Exclude existing leaderboards
                var leaderboards = db.Leaderboards.ToList();
                missingLeaderboards = expectedLeaderboards.Except(leaderboards, new LeaderboardEqualityComparer()).ToList();

                if (missingLeaderboards.Any())
                {
                    // Get the leaderboard IDs for the missing leaderboards
                    await UpdateLeaderboardsAsync(steamClient, missingLeaderboards).ConfigureAwait(false);

                    // Store the new leaderboards
                    foreach (var leaderboard in missingLeaderboards)
                    {
                        if (leaderboard.LeaderboardId == 0)
                        {
                            continue;
                        }

                        // null these out so Entity Framework doesn't try to add them as new entities.
                        leaderboard.Product   = null;
                        leaderboard.Mode      = null;
                        leaderboard.Run       = null;
                        leaderboard.Character = null;

                        db.Leaderboards.Add(leaderboard);
                    }

                    db.SaveChanges();
                }

                return((from l in db.Leaderboards
                        orderby l.ProductId, l.IsProduction, l.CharacterId, l.RunId, l.ModeId, l.IsCustomMusic, l.IsCoOp
                        select l)
                       .ToList());
            }
        }
        private static async Task MainAsync(string[] args)
        {
            if (args.Length != 2)
            {
                throw new ArgumentException("Your Steam user name and password must be passed in as arguments.");
            }

            var userName = args[0];
            var password = args[1];

            var areas = Areas.GetAreas();

            WriteJson(areas, "areas", DefaultValueHandling.Ignore);

            using (var db = new LeaderboardsContext())
            {
                var products = JsonConvert.DeserializeObject <Product[]>(Resources.Products);
                db.Products.AddOrUpdate(p => p.ProductId, products);

                var modes = JsonConvert.DeserializeObject <Mode[]>(Resources.Modes);
                db.Modes.AddOrUpdate(m => m.ModeId, modes);

                var runs = JsonConvert.DeserializeObject <Run[]>(Resources.Runs);
                db.Runs.AddOrUpdate(r => r.RunId, runs);

                var characters = JsonConvert.DeserializeObject <Character[]>(Resources.Characters);
                db.Characters.AddOrUpdate(c => c.CharacterId, characters);

                db.SaveChanges();
            }

            using (var steamClient = new SteamClientApiClient(userName, password, Policy.NoOpAsync(), TelemetryClient))
            {
                steamClient.Timeout = TimeSpan.FromSeconds(30);

                await steamClient.ConnectAndLogOnAsync().ConfigureAwait(false);

                var leaderboards = await Leaderboards.GetLeaderboardsAsync(steamClient).ConfigureAwait(false);

                WriteJson(leaderboards.Select(l => new
                {
                    l.LeaderboardId,
                    l.DisplayName,
                    l.Name,
                    l.IsProduction,
                    l.ProductId,
                    l.ModeId,
                    l.RunId,
                    l.CharacterId,
                    l.IsCustomMusic,
                    l.IsCoOp,
                }), "Leaderboards");

                var dailyLeaderboards = await DailyLeaderboards.GetDailyLeaderboardsAsync(steamClient).ConfigureAwait(false);

                WriteJson(dailyLeaderboards.Select(l => new
                {
                    l.LeaderboardId,
                    l.DisplayName,
                    l.Name,
                    l.IsProduction,
                    l.ProductId,
                    l.Date,
                }), "DailyLeaderboards");
            }
        }
        private static Area GetLeaderboardsArea()
        {
            var area = new Area
            {
                Name = "Leaderboards",
                Path = "/leaderboards",
            };

            using (var db = new LeaderboardsContext())
            {
                var areas = (from l in db.Leaderboards
                             group l by l.Product into byProduct
                             select new
                {
                    byProduct.Key,
                    Categories = from l in byProduct
                                 group l by l.Mode into byMode
                                 select new
                    {
                        byMode.Key,
                        Categories = from l in byMode
                                     group l by l.Run into byRun
                                     select new
                        {
                            byRun.Key,
                            Categories = from l in byRun
                                         group l by l.Character into byCharacter
                                         select new { byCharacter.Key },
                        },
                    },
                }).ToList();

                area.Categories = (from a in areas
                                   select new Area
                {
                    Name = a.Key.DisplayName,
                    Categories = (from b in a.Categories
                                  select new Area
                    {
                        Name = b.Key.DisplayName,
                        Categories = (from c in b.Categories
                                      select new Area
                        {
                            Name = c.Key.DisplayName,
                            Categories = (from d in c.Categories
                                          select new Area
                            {
                                Name = d.Key.DisplayName,
                                Path = ToPath(a.Key.Name, b.Key.Name, c.Key.Name, d.Key.Name),
                                Icon = $"/images/characters/{d.Key.Name}.png",
                            }).ToList(),
                        }).ToList(),
                    }).ToList(),
                }).ToList();
            }

            var classic = area.Categories.Single(c => c.Name == "Classic");

            classic.Categories.Add(new Area
            {
                Name = "Daily",
                Path = "/leaderboards/daily",
            });

            var amplified = area.Categories.Single(c => c.Name == "Amplified");

            amplified.Categories.Add(new Area
            {
                Name = "Daily",
                Path = "/leaderboards/amplified/daily",
            });

            return(area);
        }