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 } }
public void 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, ParentAppID = appID, 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 there is no public manifest for this depot, it still could have some sort of open beta 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.GetConnection()) { db.Execute("INSERT INTO `Depots` (`DepotID`, `Name`) VALUES (@DepotID, @DepotName) ON DUPLICATE KEY UPDATE `Name` = VALUES(`Name`)", new { request.DepotID, request.DepotName }); } continue; } request.BuildID = branch["build"].AsInteger(); } else { request.BuildID = depots["branches"]["public"]["buildid"].AsInteger(); } requests.Add(request); } if (!requests.Any()) { return; } using (var db = Database.GetConnection()) { var dbDepots = db.Query<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 < 2) { // Update depot name if changed if (!request.DepotName.Equals(dbDepot.Name)) { db.Execute("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)) { db.Execute(@"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) { MakeHistory(db, request, string.Empty, "manifest_change", dbDepot.ManifestID, request.ManifestID); } if (LicenseList.OwnedApps.ContainsKey(request.DepotID) || Settings.Current.FullRun > 1) { DepotLocks.TryAdd(request.DepotID, 1); JobManager.AddJob(() => Steam.Instance.Apps.GetDepotDecryptionKey(request.DepotID, request.ParentAppID), request); } #if DEBUG else { Log.WriteDebug("Depot Processor", "Skipping depot {0} from app {1} because we don't own it", request.DepotID, request.ParentAppID); } #endif } } }
public void 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 there is no public manifest for this depot, it still could have some sort of open beta 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.GetConnection()) { db.Execute("INSERT INTO `Depots` (`DepotID`, `Name`) VALUES (@DepotID, @DepotName) ON DUPLICATE KEY UPDATE `Name` = VALUES(`Name`)", new { request.DepotID, request.DepotName }); } continue; } request.BuildID = branch["build"].AsInteger(); } else { request.BuildID = depots["branches"]["public"]["buildid"].AsInteger(); } requests.Add(request); } if (!requests.Any()) { return; } var depotsToDownload = new List <ManifestJob>(); using (var db = Database.GetConnection()) { var dbDepots = db.Query <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.Normal) { // Update depot name if changed if (!request.DepotName.Equals(dbDepot.Name)) { db.Execute("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)) { db.Execute(@"INSERT INTO `Depots` (`DepotID`, `Name`, `BuildID`, `ManifestID`) VALUES (@DepotID, @DepotName, @BuildID, @ManifestID) ON DUPLICATE KEY UPDATE `LastUpdated` = unix_timestamp(), `Name` = VALUES(`Name`), `BuildID` = VALUES(`BuildID`), `ManifestID` = VALUES(`ManifestID`)", new { request.DepotID, request.DepotName, request.BuildID, request.ManifestID }); } if (dbDepot.ManifestID != request.ManifestID) { MakeHistory(db, 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 || Settings.Current.FullRun == FullRunState.WithForcedDepots || Settings.Current.FullRun == FullRunState.ImportantOnly) { 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 DEBUG else { Log.WriteDebug("Depot Processor", "Skipping depot {0} from app {1} because we don't own it", request.DepotID, appID); } #endif } } if (depotsToDownload.Any()) { PICSProductInfo.ProcessorThreadPool.QueueWorkItem(async() => { try { await DownloadDepots(appID, depotsToDownload); } catch (Exception e) { ErrorReporter.Notify(e); } foreach (var depot in depotsToDownload) { RemoveLock(depot.DepotID); } }); } }
public void 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, ParentAppID = appID, 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 there is no public manifest for this depot, it still could have some sort of open beta 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.GetConnection()) { db.Execute("INSERT INTO `Depots` (`DepotID`, `Name`) VALUES (@DepotID, @DepotName) ON DUPLICATE KEY UPDATE `Name` = @DepotName", new { request.DepotID, request.DepotName }); } continue; } request.BuildID = branch["build"].AsInteger(); } else { request.BuildID = depots["branches"]["public"]["buildid"].AsInteger(); } requests.Add(request); } if (!requests.Any()) { return; } using (var db = Database.GetConnection()) { var dbDepots = db.Query <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 < 2) { // Update depot name if changed if (!request.DepotName.Equals(dbDepot.Name)) { db.Execute("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)) { db.Execute(@"INSERT INTO `Depots` (`DepotID`, `Name`, `BuildID`, `ManifestID`) VALUES (@DepotID, @DepotName, @BuildID, @ManifestID) ON DUPLICATE KEY UPDATE `LastUpdated` = CURRENT_TIMESTAMP(), `Name` = @DepotName, `BuildID` = @BuildID, `ManifestID` = @ManifestID", new { request.DepotID, request.DepotName, request.BuildID, request.ManifestID }); } if (dbDepot.ManifestID != request.ManifestID) { MakeHistory(db, request, string.Empty, "manifest_change", dbDepot.ManifestID, request.ManifestID); } if (LicenseList.OwnedApps.ContainsKey(request.DepotID) || Settings.Current.FullRun > 1) { DepotLocks.TryAdd(request.DepotID, 1); JobManager.AddJob(() => Steam.Instance.Apps.GetDepotDecryptionKey(request.DepotID, request.ParentAppID), request); } #if DEBUG else { Log.WriteDebug("Depot Processor", "Skipping depot {0} from app {1} because we don't own it", request.DepotID, request.ParentAppID); } #endif } } }
public async Task Process(IDbConnection db, uint appID, uint changeNumber, KeyValue depots) { var requests = new List <ManifestJob>(); var dlcNames = new Dictionary <uint, string>(); // Get data in format we want first foreach (var depot in depots.Children) { var request = new ManifestJob { ChangeNumber = changeNumber, }; // Ignore keys that aren't integers, for example "branches" if (!uint.TryParse(depot.Name, out request.DepotID)) { continue; } // Ignore these for now, parent app should be updated too anyway if (depot["depotfromapp"].Value != null) { continue; } request.DepotName = depot["name"].AsString(); if (string.IsNullOrEmpty(request.DepotName)) { request.DepotName = $"SteamDB Unnamed Depot {request.DepotID}"; } else if (depot["dlcappid"].Value != null) { if (uint.TryParse(depot["dlcappid"].Value, out var dlcAppId)) { dlcNames[dlcAppId] = request.DepotName; } } // TODO: instead of locking we could wait for current process to finish if (DepotLocks.ContainsKey(request.DepotID)) { continue; } // SteamVR trickery if (appID == 250820 && depot["manifests"]["beta"].Value != null && depots["branches"]["beta"]["buildid"].AsInteger() > depots["branches"]["public"]["buildid"].AsInteger()) { request.BuildID = depots["branches"]["beta"]["buildid"].AsInteger(); request.ManifestID = ulong.Parse(depot["manifests"]["beta"].Value); } else if (depot["manifests"]["public"].Value == null || !ulong.TryParse(depot["manifests"]["public"].Value, out request.ManifestID)) { var branch = depot["manifests"].Children.Find(x => x.Name != "local"); if (branch == null || !ulong.TryParse(branch.Value, out request.ManifestID)) { await db.ExecuteAsync("INSERT INTO `Depots` (`DepotID`, `Name`) VALUES (@DepotID, @DepotName) ON DUPLICATE KEY UPDATE `DepotID` = VALUES(`DepotID`)", new { request.DepotID, request.DepotName }); continue; } Log.WriteDebug(nameof(DepotProcessor), $"Depot {request.DepotID} (from {appID}) has no public branch, but there is another one"); request.BuildID = depots["branches"][branch.Name]["buildid"].AsInteger(); } else { request.BuildID = depots["branches"]["public"]["buildid"].AsInteger(); } requests.Add(request); } if (dlcNames.Any()) { await ProcessDlcNames(appID, changeNumber, dlcNames); } foreach (var branch in depots["branches"].Children) { var buildId = branch["buildid"].AsInteger(); if (buildId < 1) { continue; } var isPublic = branch.Name != null && branch.Name.Equals("public", StringComparison.OrdinalIgnoreCase); await db.ExecuteAsync(isPublic? "INSERT INTO `Builds` (`BuildID`, `ChangeID`, `AppID`, `Public`) VALUES (@BuildID, @ChangeNumber, @AppID, 1) ON DUPLICATE KEY UPDATE `ChangeID` = IF(`Public` = 0, VALUES(`ChangeID`), `ChangeID`), `Public` = 1" : "INSERT INTO `Builds` (`BuildID`, `ChangeID`, `AppID`) VALUES (@BuildID, @ChangeNumber, @AppID) ON DUPLICATE KEY UPDATE `AppID` = `AppID`", new { BuildID = buildId, ChangeNumber = changeNumber, AppID = appID, }); } if (requests.Count == 0) { return; } var depotsToDownload = new List <ManifestJob>(); var depotIds = requests.Select(x => x.DepotID).ToList(); var dbDepots = (await db.QueryAsync <Depot>("SELECT `DepotID`, `Name`, `BuildID`, `ManifestID`, `LastManifestID`, `FilenamesEncrypted` FROM `Depots` WHERE `DepotID` IN @depotIds", new { depotIds })) .ToDictionary(x => x.DepotID, x => x); var decryptionKeys = (await db.QueryAsync <DepotKey>("SELECT `DepotID`, `Key` FROM `DepotsKeys` WHERE `DepotID` IN @depotIds", new { depotIds })) .ToDictionary(x => x.DepotID, x => Utils.StringToByteArray(x.Key)); foreach (var request in requests) { Depot dbDepot; decryptionKeys.TryGetValue(request.DepotID, out request.DepotKey); 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(nameof(DepotProcessor), $"Skipping depot {request.DepotID} due to old buildid: {dbDepot.BuildID} > {request.BuildID}"); continue; } if (dbDepot.LastManifestID == request.ManifestID && dbDepot.ManifestID == request.ManifestID && Settings.FullRun != FullRunState.WithForcedDepots && !dbDepot.FilenamesEncrypted && request.DepotKey != null) { // Update depot name if changed if (request.DepotName != dbDepot.Name) { await db.ExecuteAsync("UPDATE `Depots` SET `Name` = @DepotName WHERE `DepotID` = @DepotID", new { request.DepotID, request.DepotName }); } continue; } request.StoredFilenamesEncrypted = dbDepot.FilenamesEncrypted; request.LastManifestID = dbDepot.LastManifestID; } else { dbDepot = new Depot(); } if (dbDepot.BuildID != request.BuildID || dbDepot.ManifestID != request.ManifestID || request.DepotName != 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); } if (Settings.Current.OnlyOwnedDepots && !LicenseList.OwnedDepots.ContainsKey(request.DepotID)) { continue; } lock (DepotLocks) { DepotLocks.Add(request.DepotID, 1); } depotsToDownload.Add(request); } if (depotsToDownload.Count > 0) { _ = TaskManager.Run(async() => await DownloadDepots(appID, depotsToDownload)).ContinueWith(task => { Log.WriteError(nameof(DepotProcessor), $"An exception occured when processing depots from app {appID}, removing locks"); foreach (var depot in depotsToDownload) { RemoveLock(depot.DepotID); } }, TaskContinuationOptions.OnlyOnFaulted); } }
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, }; // Ignore keys that aren't integers, for example "branches" if (!uint.TryParse(depot.Name, out request.DepotID)) { continue; } request.DepotName = depot["name"].AsString(); if (string.IsNullOrEmpty(request.DepotName)) { request.DepotName = $"SteamDB Unnamed Depot {request.DepotID}"; } // TODO: instead of locking we could wait for current process to finish if (DepotLocks.ContainsKey(request.DepotID)) { continue; } // SteamVR trickery if (appID == 250820 && depot["manifests"]["beta"].Value != null && depots["branches"]["beta"]["buildid"].AsInteger() > depots["branches"]["public"]["buildid"].AsInteger()) { request.BuildID = depots["branches"]["beta"]["buildid"].AsInteger(); request.ManifestID = ulong.Parse(depot["manifests"]["beta"].Value); } else if (depot["manifests"]["public"].Value == null || !ulong.TryParse(depot["manifests"]["public"].Value, out request.ManifestID)) { var branch = depot["manifests"].Children.Find(x => x.Name != "local"); if (branch == null || !ulong.TryParse(branch.Value, out request.ManifestID)) { await using var db = await Database.GetConnectionAsync(); await db.ExecuteAsync("INSERT INTO `Depots` (`DepotID`, `Name`) VALUES (@DepotID, @DepotName) ON DUPLICATE KEY UPDATE `DepotID` = VALUES(`DepotID`)", new { request.DepotID, request.DepotName }); continue; } Log.WriteDebug(nameof(DepotProcessor), $"Depot {request.DepotID} (from {appID}) has no public branch, but there is another one"); request.BuildID = depots["branches"][branch.Name]["buildid"].AsInteger(); } else { request.BuildID = depots["branches"]["public"]["buildid"].AsInteger(); } requests.Add(request); } if (requests.Count == 0) { return; } var depotsToDownload = new List <ManifestJob>(); await using (var db = await Database.GetConnectionAsync()) { await db.ExecuteAsync("INSERT INTO `Builds` (`BuildID`, `ChangeID`, `AppID`) VALUES (@BuildID, @ChangeNumber, @AppID) ON DUPLICATE KEY UPDATE `AppID` = VALUES(`AppID`)", new { requests[0].BuildID, requests[0].ChangeNumber, appID }); var depotIds = requests.Select(x => x.DepotID).ToList(); var dbDepots = (await db.QueryAsync <Depot>("SELECT `DepotID`, `Name`, `BuildID`, `ManifestID`, `LastManifestID`, `FilenamesEncrypted` FROM `Depots` WHERE `DepotID` IN @depotIds", new { depotIds })) .ToDictionary(x => x.DepotID, x => x); var decryptionKeys = (await db.QueryAsync <DepotKey>("SELECT `DepotID`, `Key` FROM `DepotsKeys` WHERE `DepotID` IN @depotIds", new { depotIds })) .ToDictionary(x => x.DepotID, x => Utils.StringToByteArray(x.Key)); foreach (var request in requests) { Depot dbDepot; decryptionKeys.TryGetValue(request.DepotID, out request.DepotKey); 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(nameof(DepotProcessor), $"Skipping depot {request.DepotID} due to old buildid: {dbDepot.BuildID} > {request.BuildID}"); continue; } if (dbDepot.LastManifestID == request.ManifestID && dbDepot.ManifestID == request.ManifestID && Settings.Current.FullRun != FullRunState.WithForcedDepots && !dbDepot.FilenamesEncrypted && request.DepotKey != null) { // Update depot name if changed if (request.DepotName != dbDepot.Name) { await db.ExecuteAsync("UPDATE `Depots` SET `Name` = @DepotName WHERE `DepotID` = @DepotID", new { request.DepotID, request.DepotName }); } continue; } request.StoredFilenamesEncrypted = dbDepot.FilenamesEncrypted; request.LastManifestID = dbDepot.LastManifestID; } else { dbDepot = new Depot(); } if (dbDepot.BuildID != request.BuildID || dbDepot.ManifestID != request.ManifestID || request.DepotName != 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); } lock (DepotLocks) { // This doesn't really save us from concurrency issues if (DepotLocks.ContainsKey(request.DepotID)) { Log.WriteWarn(nameof(DepotProcessor), $"Depot {request.DepotID} was locked in another thread"); continue; } DepotLocks.Add(request.DepotID, 1); } depotsToDownload.Add(request); } } if (depotsToDownload.Count > 0) { _ = TaskManager.Run(async() => { try { await DownloadDepots(appID, depotsToDownload); } catch (Exception e) { ErrorReporter.Notify(nameof(DepotProcessor), e); } foreach (var depot in depotsToDownload) { RemoveLock(depot.DepotID); } }); } }