- Overhauled SteamCMD appinfo gathering, with substantially increased speed
- Increased maximum SteamCMD processes from 20 to 30
- The gathering progress is now based on only games, so no more progress decreasing
This commit is contained in:
pointfeev 2022-03-05 20:36:43 -05:00
parent be28b0db3e
commit 7cae752787
3 changed files with 73 additions and 35 deletions

View file

@ -5,7 +5,7 @@
<UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Resources\ini.ico</ApplicationIcon>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<Version>3.0.2.1</Version>
<Version>3.1.0.0</Version>
<PackageIcon>Resources\ini.ico</PackageIcon>
<PackageIconUrl />
<Description />

View file

@ -81,12 +81,21 @@ internal partial class SelectForm : CustomForm
});
}
internal readonly List<Task> RunningTasks = new();
private async Task GetApplicablePrograms(IProgress<int> progress)
{
int TotalGameCount = 0;
int CompleteGameCount = 0;
void AddToRemainingGames(string gameName)
{
this.AddToRemainingGames(gameName);
progress.Report(-++TotalGameCount);
}
void RemoveFromRemainingGames(string gameName)
{
this.RemoveFromRemainingGames(gameName);
progress.Report(++CompleteGameCount);
}
if (Program.Canceled) return;
int CompleteTasks = 0;
RunningTasks.Clear(); // contains all running tasks including games AND their dlc
RemainingGames.Clear(); // for display purposes only, otherwise ignorable
RemainingDLCs.Clear(); // for display purposes only, otherwise ignorable
List<Task> appTasks = new();
@ -169,11 +178,8 @@ internal partial class SelectForm : CustomForm
if (!string.IsNullOrWhiteSpace(dlcName))
dlc[dlcAppId] = (DlcType.Default, dlcName, dlcIconStaticId);
RemoveFromRemainingDLCs(dlcAppId);
progress.Report(++CompleteTasks);
});
dlcTasks.Add(task);
RunningTasks.Add(task);
progress.Report(-RunningTasks.Count);
Thread.Sleep(10); // to reduce control & window freezing
}
}
@ -230,11 +236,8 @@ internal partial class SelectForm : CustomForm
});
if (Program.Canceled) return;
RemoveFromRemainingGames(name);
progress.Report(++CompleteTasks);
});
appTasks.Add(task);
RunningTasks.Add(task);
progress.Report(-RunningTasks.Count);
}
}
if (Directory.Exists(EpicLibrary.EpicAppDataPath))
@ -273,11 +276,8 @@ internal partial class SelectForm : CustomForm
if (Program.Canceled) return;
entitlements[id] = (name, product, icon, developer);
RemoveFromRemainingDLCs(id);
progress.Report(++CompleteTasks);
});
dlcTasks.Add(task);
RunningTasks.Add(task);
progress.Report(-RunningTasks.Count);
Thread.Sleep(10); // to reduce control & window freezing
}
}
@ -350,11 +350,8 @@ internal partial class SelectForm : CustomForm
});
if (Program.Canceled) return;
RemoveFromRemainingGames(name);
progress.Report(++CompleteTasks);
});
appTasks.Add(task);
RunningTasks.Add(task);
progress.Report(-RunningTasks.Count);
}
}
foreach (Task task in appTasks)
@ -362,7 +359,6 @@ internal partial class SelectForm : CustomForm
if (Program.Canceled) return;
await task;
}
progress.Report(RunningTasks.Count);
}
private async void OnLoad()

View file

@ -18,15 +18,18 @@ namespace CreamInstaller.Steam;
internal static class SteamCMD
{
internal static readonly int ProcessLimit = 20;
internal static readonly int ProcessLimit = 30;
internal static string DirectoryPath => ProgramData.DirectoryPath;
internal static string AppInfoPath => ProgramData.AppInfoPath;
internal static readonly string FilePath = DirectoryPath + @"\steamcmd.exe";
private static readonly Dictionary<string, int> AttemptCount = new(); // the more app_updates, the longer SteamCMD should wait for app_info_print
private static string GetArguments(string appId) => $@"@ShutdownOnFailedCommand 0 +force_install_dir {DirectoryPath} +login anonymous +app_info_print {appId} " + string.Concat(Enumerable.Repeat("+app_update 4 ", AttemptCount[appId])) + "+quit";
private static readonly int[] locks = new int[ProcessLimit];
internal static async Task<string> Run(string command) => await Task.Run(() =>
internal static async Task<string> Run(string appId) => await Task.Run(() =>
{
wait_for_lock:
if (Program.Canceled) return "";
@ -35,6 +38,13 @@ internal static class SteamCMD
if (Program.Canceled) return "";
if (Interlocked.CompareExchange(ref locks[i], 1, 0) == 0)
{
if (appId is not null)
{
if (AttemptCount.ContainsKey(appId))
AttemptCount[appId]++;
else
AttemptCount[appId] = 0;
}
if (Program.Canceled) return "";
List<string> logs = new();
ProcessStartInfo processStartInfo = new()
@ -44,20 +54,55 @@ internal static class SteamCMD
RedirectStandardInput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = command,
Arguments = appId is null ? "+quit" : GetArguments(appId),
CreateNoWindow = true,
StandardInputEncoding = Encoding.UTF8,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};
using Process process = Process.Start(processStartInfo);
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data);
process.BeginOutputReadLine();
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data);
process.BeginErrorReadLine();
process.WaitForExit();
Process process = Process.Start(processStartInfo);
string output = "";
string appInfo = "";
bool appInfoStarted = false;
DateTime lastOutput = DateTime.UtcNow;
while (true)
{
if (Program.Canceled)
{
process.Kill(true);
process.Close();
break;
}
Thread.Sleep(0);
int c = process.StandardOutput.Read();
if (c != -1)
{
lastOutput = DateTime.UtcNow;
char ch = (char)c;
if (ch == '{') appInfoStarted = true;
if (appInfoStarted) appInfo += ch;
else output += ch;
}
DateTime now = DateTime.UtcNow;
TimeSpan timeDiff = now - lastOutput;
if (timeDiff.TotalSeconds > 0.1)
{
process.Kill(true);
process.Close();
if (output.Contains($"No app info for AppID {appId} found, requesting..."))
{
AttemptCount[appId]++;
processStartInfo.Arguments = GetArguments(appId);
process = Process.Start(processStartInfo);
appInfoStarted = false;
output = "";
appInfo = "";
}
else break;
}
}
Interlocked.Decrement(ref locks[i]);
return string.Join("\r\n", logs);
return appInfo;
}
Thread.Sleep(0);
}
@ -92,7 +137,7 @@ internal static class SteamCMD
int cur = 0;
progress.Report(cur);
watcher.Changed += (sender, e) => progress.Report(++cur);
await Run($@"+quit");
await Run(null);
watcher.Dispose();
}
}
@ -168,8 +213,7 @@ internal static class SteamCMD
if (File.Exists(appUpdateFile)) output = File.ReadAllText(appUpdateFile, Encoding.UTF8);
else
{
// we add app_update 4 to allow the app_info_print to finish
output = await Run($@"@ShutdownOnFailedCommand 0 +force_install_dir {DirectoryPath} +login anonymous +app_info_print {appId} +app_update 4 +quit");
output = await Run(appId);
int openBracket = output.IndexOf("{");
int closeBracket = output.LastIndexOf("}");
if (openBracket != -1 && closeBracket != -1)
@ -178,17 +222,14 @@ internal static class SteamCMD
output = output.Replace("ERROR! Failed to install app '4' (Invalid platform)", "");
File.WriteAllText(appUpdateFile, output, Encoding.UTF8);
}
else goto restart;
}
if (Program.Canceled || output is null) return null;
if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo))
if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo) || appInfo.Value is VValue)
{
File.Delete(appUpdateFile);
//new DialogForm(null).Show("GetAppInfo", SystemIcons.Information, "Deserialize exception:\n\n" + output, "OK");
goto restart;
}
if (appInfo.Value is VValue)
//new DialogForm(null).Show("GetAppInfo", SystemIcons.Information, "VValue exception:\n\n" + output, "OK");
goto restart;
if (appInfo is null || appInfo.Value?.Children()?.ToList()?.Count == 0) return appInfo;
VToken type = appInfo.Value?.GetChild("common")?.GetChild("type");
if (type is null || type.ToString() == "Game")
@ -243,6 +284,7 @@ internal static class SteamCMD
{
process.Kill(true);
process.WaitForExit();
process.Close();
}
catch { }
}));