예제 #1
0
        private static void OnSillyCrashHandler(object sender, UnhandledExceptionEventArgs args)
        {
            var parentException = args.ExceptionObject as Exception;

            if (parentException is AggregateException aggregateException)
            {
                aggregateException.Flatten().Handle(e =>
                {
                    ErrorReporter.Notify("Bootstrapper", e);

                    return(false);
                });
            }
            else
            {
                ErrorReporter.Notify("Bootstrapper", parentException);
            }

            if (args.IsTerminating)
            {
                AppDomain.CurrentDomain.UnhandledException -= OnSillyCrashHandler;

                IRC.Instance.SendOps("🥀 Backend process has crashed.");

                Application.Cleanup();
            }
        }
        public async Task Process()
        {
            Log.WriteInfo(ToString(), "Process");

            try
            {
                DbConnection = await Database.GetConnectionAsync().ConfigureAwait(false);

                await LoadData().ConfigureAwait(false);

                if (ProductInfo == null)
                {
                    await ProcessUnknown().ConfigureAwait(false);
                }
                else
                {
                    await ProcessData().ConfigureAwait(false);
                }
            }
            catch (MySqlException e)
            {
                ErrorReporter.Notify(ToString(), e);

                JobManager.AddJob(() => RefreshSteam());
            }

#if DEBUG
            Log.WriteDebug(ToString(), "Processed");
#endif
        }
예제 #3
0
        public void Connect()
        {
            try
            {
                IrcClientConnectionOptions options = null;

                if (Settings.Current.IRC.Ssl)
                {
                    options = new IrcClientConnectionOptions
                    {
                        Ssl         = true,
                        SslHostname = Settings.Current.IRC.Server
                    };

                    if (Settings.Current.IRC.SslAcceptInvalid)
                    {
                        options.SslCertificateValidationCallback = (sender, certificate, chain, policyErrors) => true;
                    }
                }

                Client.Connect(Settings.Current.IRC.Server, Settings.Current.IRC.Port, options);
            }
            catch (Exception e)
            {
                ErrorReporter.Notify("IRC", e);
            }
        }
        private async void OnServerList(SteamClient.ServerListCallback callback)
        {
            IList <CDNClient.Server> serverList;

            try
            {
                serverList = await CDNClient.FetchServerListAsync(maxServers : 60);
            }
            catch (Exception e)
            {
                ErrorReporter.Notify("Depot Downloader", e);

                return;
            }

            CDNServers = new List <string>();

            foreach (var server in serverList)
            {
                if (server.Type == "CDN" && (server.Host.StartsWith("valve") || server.Host.StartsWith("cache")))
                {
                    Log.WriteDebug("Depot Processor", "Adding {0} to CDN server list", server.Host);
                    CDNServers.Add(server.Host);
                }
                else
                {
                    Log.WriteDebug("Depot Processor", "Skipping {0}, not added to CDN server list", server.Host);
                }
            }

            Log.WriteDebug("Depot Processor", "Finished adding CDN servers, added {0} out of {1}", CDNServers.Count, serverList.Count);
        }
        public static void Cleanup()
        {
            Log.WriteInfo(nameof(Application), "Exiting...");

            try
            {
                Steam.Instance.IsRunning = false;
                Steam.Instance.Client.Disconnect();
                Steam.Instance.Dispose();
            }
            catch (Exception e)
            {
                ErrorReporter.Notify(nameof(Application), e);
            }

            if (Settings.Current.IRC.Enabled)
            {
                Log.WriteInfo(nameof(Application), "Closing IRC connection...");

                RssReader.Timer.Stop();
                RssReader.Dispose();

                IRC.Instance.Close();

                IrcThread.Join(TimeSpan.FromSeconds(5));
            }

            TaskManager.CancelAllTasks();

            if (!Settings.IsFullRun)
            {
                LocalConfig.Update("backend.changenumber", Steam.Instance.PICSChanges.PreviousChangeNumber.ToString())
                .GetAwaiter().GetResult();
            }
        }
예제 #6
0
        private async void OnServerList(SteamClient.ServerListCallback callback)
        {
            IList <CDNClient.Server> serverList;

            try
            {
                serverList = await CDNClient.FetchServerListAsync(maxServers : 30);
            }
            catch (Exception e)
            {
                if (CDNServers.Count == 0)
                {
                    ErrorReporter.Notify("Depot Downloader", e);
                }

                return;
            }

            CDNServers = new List <string>();

            foreach (var server in serverList)
            {
                // akamai.cdn.steampipe.steamcontent.com returns 404 Not Found unnecessarily
                if (server.Type == "CDN" && !server.Host.Contains("cdn."))
                {
                    CDNServers.Add(server.Host);
                }
            }
        }
예제 #7
0
        public static void RegisterErrorHandler(Task t)
        {
            Tasks.TryAdd(t, 1);

#if DEBUG
            Log.WriteDebug("Task Manager", "New task: {0}", t);
#endif

            t.ContinueWith(task =>
            {
                if (Tasks.TryRemove(task, out _))
                {
#if DEBUG
                    Log.WriteDebug("Task Manager", "Removed task: {0} ({1} jobs left)", t, TasksCount);
#endif
                }
            });

            t.ContinueWith(task =>
            {
                task.Exception.Flatten().Handle(e =>
                {
                    ErrorReporter.Notify("Task Manager", e);

                    return(false);
                });
            }, TaskContinuationOptions.OnlyOnFaulted);
        }
        private static void RemoveProcessorLock(Task <BaseProcessor> task)
        {
            var processor = task.Result;

            lock (CurrentlyProcessing)
            {
#if false
                if (CurrentlyProcessing[processor.Id]?.Id == task.Id)
                {
                    CurrentlyProcessing.Remove(processor.Id);
                }
#endif

                if (CurrentlyProcessing.TryGetValue(processor.Id, out var mostRecentItem))
                {
                    if (mostRecentItem.IsCompleted)
                    {
                        CurrentlyProcessing.Remove(processor.Id);

                        Log.WriteDebug(processor.ToString(), $"Removed completed lock ({CurrentlyProcessing.Count})");
                    }
                    else if (mostRecentItem.Id == task.Id)
                    {
                        ErrorReporter.Notify(processor.ToString(), new System.Exception($"Matching worker task ids, but task is not completed? @xPaw"));
                    }
                }
            }
        }
예제 #9
0
        private async void OnServerList(SteamClient.ServerListCallback callback)
        {
            IList <CDNClient.Server> serverList;

            try
            {
                serverList = await CDNClient.FetchServerListAsync(maxServers : 60);
            }
            catch (Exception e)
            {
                if (CDNServers.Count == 0)
                {
                    ErrorReporter.Notify("Depot Downloader", e);
                }

                return;
            }

            CDNServers = new List <string>();

            foreach (var server in serverList)
            {
                if (server.Type == "CDN" && server.Host.StartsWith("valve"))
                {
                    CDNServers.Add(server.Host);
                }
            }
        }
예제 #10
0
        private static void TryCommand(Command command, CommandArguments commandData)
        {
#if false
            if (commandData.CommandType == ECommandType.IRC && IRC.IsRecipientChannel(commandData.Recipient))
            {
                if (DateTime.Now.Subtract(LastCommandUseTime).TotalSeconds < 60)
                {
                    commandData.ReplyAsNotice = ++LastCommandUseCount > 3;
                }
                else
                {
                    LastCommandUseCount = 1;
                }

                LastCommandUseTime = DateTime.Now;
            }
#endif

            try
            {
                command.OnCommand(commandData);
            }
            catch (Exception e)
            {
                Log.WriteError("CommandHandler", "Exception while executing a command: {0}\n{1}", e.Message, e.StackTrace);

                ReplyToCommand(commandData, "Exception: {0}", e.Message);

                ErrorReporter.Notify(e);
            }
        }
예제 #11
0
        public static void Cleanup()
        {
            Log.WriteInfo(nameof(Application), "Exiting...");

            try
            {
                Steam.Instance.IsRunning = false;
                Steam.Instance.Client.Disconnect();
                Steam.Instance.Dispose();
            }
            catch (Exception e)
            {
                ErrorReporter.Notify(nameof(Application), e);
            }

            if (Settings.Current.IRC.Enabled)
            {
                Log.WriteInfo(nameof(Application), "Closing IRC connection...");

                RssReader.Timer.Stop();
                RssReader.Dispose();

                IRC.Instance.Close();

                IrcThread.Join(TimeSpan.FromSeconds(5));
            }

            TaskManager.CancelAllTasks();

            LocalConfig.Save();
        }
예제 #12
0
        private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback)
        {
            Log.WriteInfo("Steam", "Updating sentry file...");

            byte[] sentryHash = CryptoHelper.SHAHash(callback.Data);

            if (callback.Data.Length != callback.BytesToWrite)
            {
                ErrorReporter.Notify(new InvalidDataException(string.Format("Data.Length ({0}) != BytesToWrite ({1}) in OnMachineAuth", callback.Data.Length, callback.BytesToWrite)));
            }

            using (var file = File.OpenWrite(SentryFile))
            {
                file.Seek(callback.Offset, SeekOrigin.Begin);
                file.Write(callback.Data, 0, callback.BytesToWrite);
            }

            Steam.Instance.User.SendMachineAuthResponse(new SteamUser.MachineAuthDetails
            {
                JobID = callback.JobID,

                FileName = callback.FileName,

                BytesWritten = callback.BytesToWrite,
                FileSize     = callback.Data.Length,
                Offset       = callback.Offset,

                Result    = EResult.OK,
                LastError = 0,

                OneTimePassword = callback.OneTimePassword,

                SentryFileHash = sentryHash
            });
        }
        public static void Cleanup()
        {
            // If threads is null, app was not yet initialized and there is nothing to cleanup
            if (Threads == null)
            {
                return;
            }

            Log.WriteInfo("Bootstrapper", "Exiting...");

            ChangelistTimer.Stop();

            Log.WriteInfo("Bootstrapper", "Disconnecting from Steam...");

            try
            {
                Steam.Instance.IsRunning = false;
                Steam.Instance.Client.Disconnect();
            }
            catch (Exception e)
            {
                ErrorReporter.Notify("Bootstrapper", e);
            }

            if (Settings.Current.IRC.Enabled)
            {
                Log.WriteInfo("Bootstrapper", "Closing IRC connection...");

                RssReader.Timer.Stop();

                IRC.Instance.Close();
            }

            Log.WriteInfo("Bootstrapper", "Cancelling {0} tasks...", TaskManager.TasksCount);

            TaskManager.CancelAllTasks();

            foreach (var thread in Threads.Where(thread => thread.ThreadState == ThreadState.Running))
            {
                Log.WriteInfo("Bootstrapper", "Joining thread {0}...", thread.Name);

                thread.Join(TimeSpan.FromSeconds(5));
            }

            Log.WriteInfo("Bootstrapper", "Saving local config...");

            LocalConfig.Save();

            try
            {
                Steam.Anonymous.IsRunning = false;
                Steam.Anonymous.Client.Disconnect();
            }
            catch (Exception e)
            {
                ErrorReporter.Notify("Bootstrapper", e);
            }
        }
예제 #14
0
        private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
        {
            args.Exception?.Flatten().Handle(e =>
            {
                ErrorReporter.Notify(nameof(Bootstrapper), e);

                return(true);
            });
        }
예제 #15
0
        public static void RegisterErrorHandler(Task t)
        {
            t.ContinueWith(task =>
            {
                task.Exception.Flatten().Handle(e =>
                {
                    ErrorReporter.Notify("Task Manager", e);

                    return(false);
                });
            }, TaskContinuationOptions.OnlyOnFaulted);
        }
예제 #16
0
        private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback)
        {
            Log.WriteInfo("Steam", "Updating sentry file... {0}", callback.FileName);

            if (callback.Data.Length != callback.BytesToWrite)
            {
                ErrorReporter.Notify(new InvalidDataException(string.Format("Data.Length ({0}) != BytesToWrite ({1}) in OnMachineAuth", callback.Data.Length, callback.BytesToWrite)));
            }

            int fileSize;

            byte[] sentryHash;


            using (var stream = new MemoryStream(LocalConfig.Sentry ?? new byte[callback.BytesToWrite]))
            {
                stream.Seek(callback.Offset, SeekOrigin.Begin);
                stream.Write(callback.Data, 0, callback.BytesToWrite);
                stream.Seek(0, SeekOrigin.Begin);

                fileSize = (int)stream.Length;

                using (var sha = new SHA1CryptoServiceProvider())
                {
                    sentryHash = sha.ComputeHash(stream);
                }

                LocalConfig.Sentry = stream.ToArray();
            }

            LocalConfig.SentryFileName = callback.FileName;
            LocalConfig.Save();

            Steam.Instance.User.SendMachineAuthResponse(new SteamUser.MachineAuthDetails
            {
                JobID = callback.JobID,

                FileName = callback.FileName,

                BytesWritten = callback.BytesToWrite,
                FileSize     = fileSize,
                Offset       = callback.Offset,

                Result    = EResult.OK,
                LastError = 0,

                OneTimePassword = callback.OneTimePassword,

                SentryFileHash = sentryHash
            });
        }
예제 #17
0
        private static void RegisterErrorHandler(Task t)
        {
            t.ContinueWith(task =>
            {
                task.Exception.Flatten().Handle(e =>
                {
                    Log.WriteError("Task Manager", "Exception: {0}\n{1}", e.Message, e.StackTrace);

                    ErrorReporter.Notify(e);

                    return(false);
                });
            }, TaskContinuationOptions.OnlyOnFaulted);
        }
예제 #18
0
 private void Tick(object sender, ElapsedEventArgs e)
 {
     Parallel.ForEach(Settings.Current.RssFeeds, feed =>
     {
         try
         {
             ProcessFeed(feed);
         }
         catch (Exception ex)
         {
             ErrorReporter.Notify("RSS", ex);
         }
     });
 }
예제 #19
0
        public override void HandleMsg(IPacketMsg packetMsg)
        {
            if (packetMsg.MsgType == EMsg.ClientServiceCall ||
                packetMsg.MsgType == EMsg.ClientServiceModule ||
                packetMsg.MsgType == EMsg.ClientReadMachineAuth ||
                packetMsg.MsgType == EMsg.ClientRequestMachineAuth)
            {
                IRC.Instance.SendOps("Yo xPaw and Netshroud, got {0}", packetMsg.MsgType);

                ErrorReporter.Notify(new NotImplementedException(string.Format("Got {0}", packetMsg.MsgType)));

                string file = Path.Combine(Application.Path, "files", ".support", string.Format("dump_{0}_{1}.bin", packetMsg.MsgType, DateTime.UtcNow.ToString("yyyyMMddHHmmssfff")));
                Console.WriteLine(file);
                File.WriteAllBytes(file, packetMsg.GetData());
            }
        }
예제 #20
0
        private static void AddTask(Task t)
        {
            Tasks.TryAdd(t, 1);

            t.ContinueWith(task =>
            {
                task.Exception?.Flatten().Handle(e =>
                {
                    ErrorReporter.Notify(nameof(TaskManager), e);

                    return(false);
                });
            }, TaskContinuationOptions.OnlyOnFaulted);

            t.ContinueWith(task => Tasks.TryRemove(task, out _));
        }
예제 #21
0
        public async void UpdateContentServerList()
        {
            LastServerRefreshTime = DateTime.Now;

            KeyValue response;

            using (var steamDirectory = Steam.Configuration.GetAsyncWebAPIInterface("ISteamDirectory"))
            {
                steamDirectory.Timeout = TimeSpan.FromSeconds(30);

                try
                {
                    response = await steamDirectory.CallAsync(HttpMethod.Get, "GetCSList", 1,
                                                              new Dictionary <string, object>
                    {
                        { "cellid", LocalConfig.Current.CellID },
                        { "maxcount", "20" }     // Use the first 20 servers as they're sorted by cellid and we want low latency
                    });

                    if ((EResult)response["result"].AsInteger() != EResult.OK)
                    {
                        throw new Exception($"GetCSList result is EResult.${response["result"]}");
                    }
                }
                catch (Exception e)
                {
                    ErrorReporter.Notify("Depot Downloader", e);

                    return;
                }
            }

            // Note: There are servers like `origin5-sea1.steamcontent.com`
            // which are hosted in Seattle, and most likely are the source of truth for
            // game content, perhaps we should filter to these. But the latency is poor from Europe.

            var newServers = response["serverlist"].Children.Select(x => (CDNClient.Server) new DnsEndPoint(x.Value.ToString(), 80)).ToList();

            if (newServers.Count > 0)
            {
                CDNServers = newServers;
            }
        }
예제 #22
0
        private static void RegisterErrorHandler(Task t)
        {
            Tasks.TryAdd(t, 1);

            t.ContinueWith(task =>
            {
                Tasks.TryRemove(task, out _);
            });

            t.ContinueWith(task =>
            {
                task.Exception.Flatten().Handle(e =>
                {
                    ErrorReporter.Notify("Task Manager", e);

                    return(false);
                });
            }, TaskContinuationOptions.OnlyOnFaulted);
        }
예제 #23
0
        private static async void TryCommand(Command command, CommandArguments commandData)
        {
            try
            {
                await command.OnCommand(commandData);
            }
            catch (TaskCanceledException)
            {
                commandData.Reply("Your command timed out.");
            }
            catch (AsyncJobFailedException)
            {
                commandData.Reply("Steam says this job failed, unable to execute your command.");
            }
            catch (Exception e)
            {
                commandData.Reply("Exception: {0}", e.Message);

                ErrorReporter.Notify(e);
            }
        }
        private static async void TryCommand(Command command, CommandArguments commandData)
        {
            try
            {
                await command.OnCommand(commandData);
            }
            catch (TaskCanceledException)
            {
                commandData.Reply("Your {0}{1}{2} command timed out.", Colors.OLIVE, command.Trigger, Colors.NORMAL);
            }
            catch (AsyncJobFailedException)
            {
                commandData.Reply("Steam says this job failed. Unable to execute your {0}{1}{2} command.", Colors.OLIVE, command.Trigger, Colors.NORMAL);
            }
            catch (Exception e)
            {
                ErrorReporter.Notify("IRC", e);

                commandData.Reply("Exception: {0}", e.Message);
            }
        }
예제 #25
0
        public void Connect()
        {
            try
            {
                IrcClientConnectionOptions options = null;

                if (Settings.Current.IRC.Ssl)
                {
                    options = new IrcClientConnectionOptions
                    {
                        Ssl         = true,
                        SslHostname = Settings.Current.IRC.Server
                    };
                }

                Client.Connect(Settings.Current.IRC.Server, Settings.Current.IRC.Port, options);
            }
            catch (Exception e)
            {
                ErrorReporter.Notify("IRC", e);
            }
        }
예제 #26
0
        public static async Task SendWebhook(object payload)
        {
            if (Settings.Current.WebhookURL == null)
            {
                return;
            }

            var json    = JsonConvert.SerializeObject(payload);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            try
            {
                var result = await HttpClient.PostAsync(Settings.Current.WebhookURL, content);

                var output = await result.Content.ReadAsStringAsync();

                Log.WriteDebug("Webhook", $"Result: {output}");
            }
            catch (Exception e)
            {
                ErrorReporter.Notify("Webhook", e);
            }
        }
예제 #27
0
        public async Task Process(uint appID, uint changeNumber, KeyValue depots)
        {
            var requests = new List <ManifestJob>();

            // Get data in format we want first
            foreach (var depot in depots.Children)
            {
                // Ignore these for now, parent app should be updated too anyway
                if (depot["depotfromapp"].Value != null)
                {
                    continue;
                }

                var request = new ManifestJob
                {
                    ChangeNumber = changeNumber,
                    DepotName    = depot["name"].AsString()
                };

                // Ignore keys that aren't integers, for example "branches"
                if (!uint.TryParse(depot.Name, out request.DepotID))
                {
                    continue;
                }

                // TODO: instead of locking we could wait for current process to finish
                if (DepotLocks.ContainsKey(request.DepotID))
                {
                    continue;
                }

                if (depot["manifests"]["public"].Value == null || !ulong.TryParse(depot["manifests"]["public"].Value, out request.ManifestID))
                {
                    var branch = depot["manifests"].Children.FirstOrDefault(x => x.Name != "local");

                    if (branch == null || !ulong.TryParse(branch.Value, out request.ManifestID))
                    {
                        using (var db = Database.Get())
                        {
                            await db.ExecuteAsync("INSERT INTO `Depots` (`DepotID`, `Name`) VALUES (@DepotID, @DepotName) ON DUPLICATE KEY UPDATE `Name` = VALUES(`Name`)", new { request.DepotID, request.DepotName });
                        }

                        continue;
                    }

                    Log.WriteDebug("Depot Downloader", "Depot {0} (from {1}) has no public branch, but there is another one", request.DepotID, appID);

                    request.BuildID = depots["branches"][branch.Name]["buildid"].AsInteger();
                }
                else
                {
                    request.BuildID = depots["branches"]["public"]["buildid"].AsInteger();
                }

                requests.Add(request);
            }

            if (!requests.Any())
            {
                return;
            }

            var depotsToDownload = new List <ManifestJob>();

            using (var db = await Database.GetConnectionAsync())
            {
                var firstRequest = requests.First();
                await db.ExecuteAsync("INSERT INTO `Builds` (`BuildID`, `ChangeID`, `AppID`) VALUES (@BuildID, @ChangeNumber, @AppID) ON DUPLICATE KEY UPDATE `AppID` = VALUES(`AppID`)",
                                      new {
                    firstRequest.BuildID,
                    firstRequest.ChangeNumber,
                    appID
                });

                var dbDepots = (await db.QueryAsync <Depot>("SELECT `DepotID`, `Name`, `BuildID`, `ManifestID`, `LastManifestID` FROM `Depots` WHERE `DepotID` IN @Depots", new { Depots = requests.Select(x => x.DepotID) }))
                               .ToDictionary(x => x.DepotID, x => x);

                foreach (var request in requests)
                {
                    Depot dbDepot;

                    if (dbDepots.ContainsKey(request.DepotID))
                    {
                        dbDepot = dbDepots[request.DepotID];

                        if (dbDepot.BuildID > request.BuildID)
                        {
                            // buildid went back in time? this either means a rollback, or a shared depot that isn't synced properly

                            Log.WriteDebug("Depot Processor", "Skipping depot {0} due to old buildid: {1} > {2}", request.DepotID, dbDepot.BuildID, request.BuildID);

                            continue;
                        }

                        if (dbDepot.LastManifestID == request.ManifestID && dbDepot.ManifestID == request.ManifestID && Settings.Current.FullRun != FullRunState.WithForcedDepots)
                        {
                            // Update depot name if changed
                            if (!request.DepotName.Equals(dbDepot.Name))
                            {
                                await db.ExecuteAsync("UPDATE `Depots` SET `Name` = @DepotName WHERE `DepotID` = @DepotID", new { request.DepotID, request.DepotName });
                            }

                            continue;
                        }
                    }
                    else
                    {
                        dbDepot = new Depot();
                    }

                    if (dbDepot.BuildID != request.BuildID || dbDepot.ManifestID != request.ManifestID || !request.DepotName.Equals(dbDepot.Name))
                    {
                        await db.ExecuteAsync(@"INSERT INTO `Depots` (`DepotID`, `Name`, `BuildID`, `ManifestID`) VALUES (@DepotID, @DepotName, @BuildID, @ManifestID)
                                    ON DUPLICATE KEY UPDATE `LastUpdated` = CURRENT_TIMESTAMP(), `Name` = VALUES(`Name`), `BuildID` = VALUES(`BuildID`), `ManifestID` = VALUES(`ManifestID`)",
                                              new {
                            request.DepotID,
                            request.DepotName,
                            request.BuildID,
                            request.ManifestID
                        });
                    }

                    if (dbDepot.ManifestID != request.ManifestID)
                    {
                        await MakeHistory(db, null, request, string.Empty, "manifest_change", dbDepot.ManifestID, request.ManifestID);
                    }

                    var owned = LicenseList.OwnedApps.ContainsKey(request.DepotID);

                    if (!owned)
                    {
                        request.Anonymous = owned = LicenseList.AnonymousApps.ContainsKey(request.DepotID);

                        if (owned)
                        {
                            Log.WriteWarn("Depot Processor", "Will download depot {0} using anonymous account", request.DepotID);
                        }
                    }

                    if (owned)
                    {
                        lock (DepotLocks)
                        {
                            // This doesn't really save us from concurrency issues
                            if (DepotLocks.ContainsKey(request.DepotID))
                            {
                                Log.WriteWarn("Depot Processor", "Depot {0} was locked in another thread", request.DepotID);
                                continue;
                            }

                            DepotLocks.Add(request.DepotID, 1);
                        }

                        depotsToDownload.Add(request);
                    }
                }
            }

            if (depotsToDownload.Any())
            {
#pragma warning disable 4014
                if (FileDownloader.IsImportantDepot(appID) && !Settings.IsFullRun && !string.IsNullOrEmpty(Settings.Current.PatchnotesNotifyURL))
                {
                    TaskManager.Run(() => NotifyPatchnote(appID, depotsToDownload.First().BuildID));
                }

                TaskManager.Run(async() =>
                {
                    try
                    {
                        await DownloadDepots(appID, depotsToDownload);
                    }
                    catch (Exception e)
                    {
                        ErrorReporter.Notify("Depot Processor", e);
                    }

                    foreach (var depot in depotsToDownload)
                    {
                        RemoveLock(depot.DepotID);
                    }
                });
#pragma warning restore 4014
            }
        }
예제 #28
0
        private async Task <EResult> ProcessDepotAfterDownload(IDbConnection db, IDbTransaction transaction, ManifestJob request, DepotManifest depotManifest)
        {
            var filesOld        = (await db.QueryAsync <DepotFile>("SELECT `ID`, `File`, `Hash`, `Size`, `Flags` FROM `DepotsFiles` WHERE `DepotID` = @DepotID", new { request.DepotID }, transaction: transaction)).ToDictionary(x => x.File, x => x);
            var filesAdded      = new List <DepotFile>();
            var shouldHistorize = filesOld.Any(); // Don't historize file additions if we didn't have any data before

            foreach (var file in depotManifest.Files)
            {
                var    name = file.FileName.Replace('\\', '/');
                byte[] hash = null;

                // Store empty hashes as NULL (e.g. an empty file)
                if (file.FileHash.Length > 0 && !file.Flags.HasFlag(EDepotFileFlag.Directory))
                {
                    for (int i = 0; i < file.FileHash.Length; ++i)
                    {
                        if (file.FileHash[i] != 0)
                        {
                            hash = file.FileHash;
                            break;
                        }
                    }
                }

                // safe guard
                if (name.Length > 255)
                {
                    ErrorReporter.Notify("Depot Processor", new OverflowException(string.Format("File \"{0}\" in depot {1} is too long", name, request.DepotID)));

                    continue;
                }

                if (filesOld.ContainsKey(name))
                {
                    var oldFile    = filesOld[name];
                    var updateFile = false;

                    if (oldFile.Size != file.TotalSize || !Utils.IsEqualSHA1(hash, oldFile.Hash))
                    {
                        await MakeHistory(db, transaction, request, name, "modified", oldFile.Size, file.TotalSize);

                        updateFile = true;
                    }

                    if (oldFile.Flags != file.Flags)
                    {
                        await MakeHistory(db, transaction, request, name, "modified_flags", (ulong)oldFile.Flags, (ulong)file.Flags);

                        updateFile = true;
                    }

                    if (updateFile)
                    {
                        await db.ExecuteAsync("UPDATE `DepotsFiles` SET `Hash` = @Hash, `Size` = @Size, `Flags` = @Flags WHERE `DepotID` = @DepotID AND `ID` = @ID", new DepotFile
                        {
                            ID      = oldFile.ID,
                            DepotID = request.DepotID,
                            Hash    = hash,
                            Size    = file.TotalSize,
                            Flags   = file.Flags
                        }, transaction : transaction);
                    }

                    filesOld.Remove(name);
                }
                else
                {
                    // We want to historize modifications first, and only then deletions and additions
                    filesAdded.Add(new DepotFile
                    {
                        DepotID = request.DepotID,
                        Hash    = hash,
                        File    = name,
                        Size    = file.TotalSize,
                        Flags   = file.Flags
                    });
                }
            }

            if (filesOld.Any())
            {
                await db.ExecuteAsync("DELETE FROM `DepotsFiles` WHERE `DepotID` = @DepotID AND `ID` IN @Files", new { request.DepotID, Files = filesOld.Select(x => x.Value.ID) }, transaction : transaction);

                await db.ExecuteAsync(HistoryQuery, filesOld.Select(x => new DepotHistory
                {
                    DepotID  = request.DepotID,
                    ChangeID = request.ChangeNumber,
                    Action   = "removed",
                    File     = x.Value.File
                }), transaction : transaction);
            }

            if (filesAdded.Any())
            {
                await db.ExecuteAsync("INSERT INTO `DepotsFiles` (`DepotID`, `File`, `Hash`, `Size`, `Flags`) VALUES (@DepotID, @File, @Hash, @Size, @Flags)", filesAdded, transaction : transaction);

                if (shouldHistorize)
                {
                    await db.ExecuteAsync(HistoryQuery, filesAdded.Select(x => new DepotHistory
                    {
                        DepotID  = request.DepotID,
                        ChangeID = request.ChangeNumber,
                        Action   = "added",
                        File     = x.File
                    }), transaction : transaction);
                }
            }

            await db.ExecuteAsync("UPDATE `Depots` SET `LastManifestID` = @ManifestID, `LastUpdated` = CURRENT_TIMESTAMP() WHERE `DepotID` = @DepotID", new { request.DepotID, request.ManifestID }, transaction : transaction);

            return(EResult.OK);
        }
예제 #29
0
        private async Task DownloadDepots(uint appID, List <ManifestJob> depots)
        {
            Log.WriteDebug("Depot Downloader", "Will process {0} depots ({1} depot locks left)", depots.Count(), DepotLocks.Count);

            var  processTasks       = new List <Task <EResult> >();
            bool anyFilesDownloaded = false;

            foreach (var depot in depots)
            {
                var instance = depot.Anonymous ? Steam.Anonymous.Apps : Steam.Instance.Apps;

                depot.DepotKey = await GetDepotDecryptionKey(instance, depot.DepotID, appID);

                if (depot.DepotKey == null)
                {
                    RemoveLock(depot.DepotID);

                    continue;
                }

                var cdnToken = await GetCDNAuthToken(instance, appID, depot.DepotID);

                if (cdnToken == null)
                {
                    RemoveLock(depot.DepotID);

                    Log.WriteDebug("Depot Downloader", "Got a depot key for depot {0} but no cdn auth token", depot.DepotID);

                    continue;
                }

                depot.CDNToken = cdnToken.Token;
                depot.Server   = GetContentServer();

                DepotManifest depotManifest = null;
                string        lastError     = string.Empty;

                for (var i = 0; i <= 5; i++)
                {
                    try
                    {
                        depotManifest = await CDNClient.DownloadManifestAsync(depot.DepotID, depot.ManifestID, depot.Server, depot.CDNToken, depot.DepotKey);

                        break;
                    }
                    catch (Exception e)
                    {
                        lastError = e.Message;

                        Log.WriteError("Depot Processor", "Failed to download depot manifest for app {0} depot {1} ({2}: {3}) (#{4})", appID, depot.DepotID, depot.Server, lastError, i);
                    }

                    // TODO: get new auth key if auth fails
                    depot.Server = GetContentServer();

                    if (depotManifest == null)
                    {
                        await Task.Delay(Utils.ExponentionalBackoff(i));
                    }
                }

                if (depotManifest == null)
                {
                    LocalConfig.CDNAuthTokens.TryRemove(depot.DepotID, out _);

                    RemoveLock(depot.DepotID);

                    if (FileDownloader.IsImportantDepot(depot.DepotID))
                    {
                        IRC.Instance.SendOps("{0}[{1}]{2} Failed to download manifest ({3})",
                                             Colors.OLIVE, depot.DepotName, Colors.NORMAL, lastError);
                    }

                    if (!Settings.IsFullRun)
                    {
                        JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appID, null));
                    }

                    continue;
                }

                var task = ProcessDepotAfterDownload(depot, depotManifest);

                processTasks.Add(task);

                if (!FileDownloader.IsImportantDepot(depot.DepotID))
                {
                    continue;
                }

                task = TaskManager.Run(async() =>
                {
                    var result = EResult.Fail;

                    try
                    {
                        result = await FileDownloader.DownloadFilesFromDepot(depot, depotManifest);

                        if (result == EResult.OK)
                        {
                            anyFilesDownloaded = true;
                        }
                    }
                    catch (Exception e)
                    {
                        ErrorReporter.Notify($"Depot Processor {depot.DepotID}", e);
                    }

                    return(result);
                }).Unwrap();

                processTasks.Add(task);
            }

            if (SaveLocalConfig)
            {
                SaveLocalConfig = false;

                LocalConfig.Save();
            }

            await Task.WhenAll(processTasks).ConfigureAwait(false);

            Log.WriteDebug("Depot Downloader", "{0} depot downloads finished", depots.Count());

            // TODO: use ContinueWith on tasks
            if (!anyFilesDownloaded)
            {
                foreach (var depot in depots)
                {
                    RemoveLock(depot.DepotID);
                }

                return;
            }

            if (!File.Exists(UpdateScript))
            {
                return;
            }

            lock (UpdateScriptLock)
            {
                foreach (var depot in depots)
                {
                    if (depot.Result == EResult.OK)
                    {
                        RunUpdateScript(string.Format("{0} no-git", depot.DepotID));
                    }
                    else if (depot.Result != EResult.Ignored)
                    {
                        Log.WriteWarn("Depot Processor", "Dropping stored token for {0} due to download failures", depot.DepotID);

                        LocalConfig.CDNAuthTokens.TryRemove(depot.DepotID, out _);

                        using (var db = Database.Get())
                        {
                            // Mark this depot for redownload
                            db.Execute("UPDATE `Depots` SET `LastManifestID` = 0 WHERE `DepotID` = @DepotID", new { depot.DepotID });
                        }
                    }

                    RemoveLock(depot.DepotID);
                }

                // Only commit changes if all depots downloaded
                if (processTasks.All(x => x.Result == EResult.OK || x.Result == EResult.Ignored))
                {
                    if (!RunUpdateScript(appID, depots.First().BuildID))
                    {
                        RunUpdateScript("0");
                    }
                }
                else
                {
                    Log.WriteDebug("Depot Processor", "Reprocessing the app {0} because some files failed to download", appID);

                    IRC.Instance.SendOps("{0}[{1}]{2} Reprocessing the app due to download failures",
                                         Colors.OLIVE, Steam.GetAppName(appID), Colors.NORMAL
                                         );

                    JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appID, null));
                }
            }
        }
예제 #30
0
        public static async Task <uint> TrySearchAppId(CommandArguments command)
        {
            uint appID = 0;
            var  name  = command.Message;
            Uri  uri;
            var  query = new Dictionary <string, string>
            {
                { "hitsPerPage", "1" },
                { "attributesToHighlight", "null" },
                { "attributesToSnippet", "null" },
                { "attributesToRetrieve", "[\"objectID\"]" },
                { "facetFilters", "[[\"appType:Game\",\"appType:Application\"]]" },
                { "advancedSyntax", "true" },
                { "query", name }
            };

            using (var content = new FormUrlEncodedContent(query))
            {
                uri = new UriBuilder("https://94he6yatei-dsn.algolia.net/1/indexes/steamdb/")
                {
                    Query = await content.ReadAsStringAsync()
                }.Uri;
            }

            using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri))
            {
                requestMessage.Headers.Add("Referer", "https://github.com/SteamDatabase/SteamDatabaseBackend");
                requestMessage.Headers.Add("X-Algolia-Application-Id", "94HE6YATEI");
                requestMessage.Headers.Add("X-Algolia-API-Key", "2414d3366df67739fe6e73dad3f51a43");

                try
                {
                    var response = await Utils.HttpClient.SendAsync(requestMessage);

                    var data = await response.Content.ReadAsStringAsync();

                    var json = JsonConvert.DeserializeObject <AlgoliaSearchAppHits>(data);

                    if (json.Hits.Length > 0)
                    {
                        appID = json.Hits[0].AppID;
                    }
                }
                catch (Exception e)
                {
                    ErrorReporter.Notify("Algolia search", e);
                }
            }

            if (appID > 0)
            {
                return(appID);
            }

            await using (var db = await Database.GetConnectionAsync())
            {
                appID = await db.ExecuteScalarAsync <uint>("SELECT `AppID` FROM `Apps` LEFT JOIN `AppsTypes` ON `Apps`.`AppType` = `AppsTypes`.`AppType` WHERE (`AppsTypes`.`Name` IN ('game', 'application', 'video', 'hardware') AND (`Apps`.`StoreName` LIKE @Name OR `Apps`.`Name` LIKE @Name)) OR (`AppsTypes`.`Name` = 'unknown' AND `Apps`.`LastKnownName` LIKE @Name) ORDER BY `LastUpdated` DESC LIMIT 1", new { Name = name });
            }

            if (appID == 0)
            {
                command.Reply("Nothing was found matching your request.");
            }

            return(appID);
        }