- Improved SteamCMD cleanup process
- Improved SteamCMD appinfo caching, upping the minimum appinfo version to facilitate changes
- Implemented SteamCMD process limit of 20, resulting in less CPU usage with faster and more stable appinfo gathering
This commit is contained in:
pointfeev 2022-02-11 18:25:18 -05:00
parent 3d780e55d2
commit e1e9b1ccdc
4 changed files with 114 additions and 61 deletions

View file

@ -6,6 +6,7 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
@ -17,59 +18,60 @@ namespace CreamInstaller.Classes;
internal static class SteamCMD internal static class SteamCMD
{ {
internal static readonly int ProcessLimit = 20;
internal static readonly Version MinimumAppInfoVersion = Version.Parse("2.3.3.0");
internal static readonly string DirectoryPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\CreamInstaller"; internal static readonly string DirectoryPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\CreamInstaller";
internal static readonly string FilePath = DirectoryPath + @"\steamcmd.exe"; internal static readonly string FilePath = DirectoryPath + @"\steamcmd.exe";
internal static readonly string ArchivePath = DirectoryPath + @"\steamcmd.zip";
internal static readonly string DllPath = DirectoryPath + @"\steamclient.dll";
internal static readonly string AppCachePath = DirectoryPath + @"\appcache";
internal static readonly string AppCacheAppInfoPath = AppCachePath + @"\appinfo.vdf";
internal static readonly string AppInfoPath = DirectoryPath + @"\appinfo";
internal static readonly Version MinimumAppInfoVersion = Version.Parse("2.0.3.2"); private static readonly int[] locks = new int[ProcessLimit];
internal static readonly string AppInfoVersionPath = AppInfoPath + @"\version.txt";
//private static readonly int[] locks = new int[20]; // acts as an effective process limit
internal static async Task<string> Run(string command) => await Task.Run(() => internal static async Task<string> Run(string command) => await Task.Run(() =>
{ {
/*wait_for_lock: wait_for_lock:
if (Program.Canceled) return "";
for (int i = 0; i < locks.Length; i++)
{
if (Program.Canceled) return ""; if (Program.Canceled) return "";
for (int i = 0; i < locks.Length; i++) if (Interlocked.CompareExchange(ref locks[i], 1, 0) == 0)
{ {
if (Program.Canceled) return ""; if (Program.Canceled) return "";
if (Interlocked.CompareExchange(ref locks[i], 1, 0) == 0) List<string> logs = new();
{*/ ProcessStartInfo processStartInfo = new()
if (Program.Canceled) return ""; {
List<string> logs = new(); FileName = FilePath,
ProcessStartInfo processStartInfo = new() RedirectStandardOutput = true,
{ RedirectStandardInput = true,
FileName = FilePath, RedirectStandardError = true,
RedirectStandardOutput = true, UseShellExecute = false,
RedirectStandardInput = true, Arguments = command,
RedirectStandardError = true, CreateNoWindow = true,
UseShellExecute = false, StandardInputEncoding = Encoding.UTF8,
Arguments = command, StandardOutputEncoding = Encoding.UTF8,
CreateNoWindow = true, StandardErrorEncoding = Encoding.UTF8
StandardInputEncoding = Encoding.UTF8, };
StandardOutputEncoding = Encoding.UTF8, using Process process = Process.Start(processStartInfo);
StandardErrorEncoding = Encoding.UTF8 process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data);
}; process.BeginOutputReadLine();
using Process process = Process.Start(processStartInfo); process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data);
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data); process.BeginErrorReadLine();
process.BeginOutputReadLine(); process.WaitForExit();
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data); Interlocked.Decrement(ref locks[i]);
process.BeginErrorReadLine(); return string.Join("\r\n", logs);
process.WaitForExit(); }
//Interlocked.Decrement(ref locks[i]); Thread.Sleep(0);
return string.Join("\r\n", logs); }
/*}
Thread.Sleep(200); Thread.Sleep(200);
} goto wait_for_lock;
goto wait_for_lock;*/
}); });
internal static readonly string ArchivePath = DirectoryPath + @"\steamcmd.zip";
internal static readonly string DllPath = DirectoryPath + @"\steamclient.dll";
internal static readonly string AppInfoPath = DirectoryPath + @"\appinfo";
internal static readonly string AppInfoVersionPath = AppInfoPath + @"\version.txt";
internal static async Task Setup(IProgress<int> progress = null) internal static async Task Setup(IProgress<int> progress = null)
{ {
await Kill(); await Cleanup();
if (!Directory.Exists(DirectoryPath)) Directory.CreateDirectory(DirectoryPath); if (!Directory.Exists(DirectoryPath)) Directory.CreateDirectory(DirectoryPath);
if (!File.Exists(FilePath)) if (!File.Exists(FilePath))
{ {
@ -81,7 +83,6 @@ internal static class SteamCMD
ZipFile.ExtractToDirectory(ArchivePath, DirectoryPath); ZipFile.ExtractToDirectory(ArchivePath, DirectoryPath);
File.Delete(ArchivePath); File.Delete(ArchivePath);
} }
if (File.Exists(AppCacheAppInfoPath)) File.Delete(AppCacheAppInfoPath);
if (!File.Exists(AppInfoVersionPath) || !Version.TryParse(File.ReadAllText(AppInfoVersionPath, Encoding.UTF8), out Version version) || version < MinimumAppInfoVersion) if (!File.Exists(AppInfoVersionPath) || !Version.TryParse(File.ReadAllText(AppInfoVersionPath, Encoding.UTF8), out Version version) || version < MinimumAppInfoVersion)
{ {
if (Directory.Exists(AppInfoPath)) Directory.Delete(AppInfoPath, true); if (Directory.Exists(AppInfoPath)) Directory.Delete(AppInfoPath, true);
@ -100,38 +101,82 @@ internal static class SteamCMD
progress.Report(cur); progress.Report(cur);
watcher.Changed += (sender, e) => progress.Report(++cur); watcher.Changed += (sender, e) => progress.Report(++cur);
await Run($@"+quit"); await Run($@"+quit");
await Cleanup();
watcher.Dispose(); watcher.Dispose();
} }
} }
internal static async Task Cleanup() => await Task.Run(() => internal static readonly string AppCachePath = DirectoryPath + @"\appcache";
internal static readonly string ConfigPath = DirectoryPath + @"\config";
internal static readonly string DumpsPath = DirectoryPath + @"\dumps";
internal static readonly string LogsPath = DirectoryPath + @"\logs";
internal static readonly string SteamAppsPath = DirectoryPath + @"\steamapps";
internal static readonly string UserDataPath = DirectoryPath + @"\userdata";
internal static async Task Cleanup() => await Task.Run(async () =>
{ {
await Kill();
try try
{ {
string[] oldFiles = Directory.GetFiles(DirectoryPath, "*.old"); string[] oldFiles = Directory.GetFiles(DirectoryPath, "*.old");
foreach (string file in oldFiles) File.Delete(file); foreach (string file in oldFiles) File.Delete(file);
}
catch { }
try
{
string[] deleteFiles = Directory.GetFiles(DirectoryPath, "*.delete"); string[] deleteFiles = Directory.GetFiles(DirectoryPath, "*.delete");
foreach (string file in deleteFiles) File.Delete(file); foreach (string file in deleteFiles) File.Delete(file);
} }
catch { } catch { }
try
{
string[] crashFiles = Directory.GetFiles(DirectoryPath, "*.crash");
foreach (string file in crashFiles) File.Delete(file);
}
catch { }
try
{
if (Directory.Exists(AppCachePath)) Directory.Delete(AppCachePath, true);
}
catch { }
try
{
if (Directory.Exists(ConfigPath)) Directory.Delete(ConfigPath, true);
}
catch { }
try
{
if (Directory.Exists(DumpsPath)) Directory.Delete(DumpsPath, true);
}
catch { }
try
{
if (Directory.Exists(LogsPath)) Directory.Delete(LogsPath, true);
}
catch { }
try
{
if (Directory.Exists(SteamAppsPath)) Directory.Delete(SteamAppsPath, true);
}
catch { }
try
{
if (Directory.Exists(UserDataPath)) Directory.Delete(UserDataPath, true);
}
catch { }
}); });
internal static async Task<VProperty> GetAppInfo(int appId, string branch = "public", int buildId = 0) internal static async Task<VProperty> GetAppInfo(int appId, string branch = "public", int buildId = 0)
{ {
if (Program.Canceled) return null; if (Program.Canceled) return null;
string output; string output;
string appUpdatePath = $@"{AppInfoPath}\{appId}"; string appUpdateFile = $@"{AppInfoPath}\{appId}.vdf";
string appUpdateFile = $@"{appUpdatePath}\appinfo.txt";
restart: restart:
await Cleanup();
if (Program.Canceled) return null; if (Program.Canceled) return null;
if (!Directory.Exists(appUpdatePath)) Directory.CreateDirectory(appUpdatePath);
if (File.Exists(appUpdateFile)) output = File.ReadAllText(appUpdateFile, Encoding.UTF8); if (File.Exists(appUpdateFile)) output = File.ReadAllText(appUpdateFile, Encoding.UTF8);
else else
{ {
// we add app_update 4 to allow the app_info_print to finish // we add app_update 4 to allow the app_info_print to finish
output = await Run($@"@ShutdownOnFailedCommand 0 +force_install_dir {appUpdatePath} +login anonymous +app_info_print {appId} +app_update 4 +quit"); output = await Run($@"@ShutdownOnFailedCommand 0 +force_install_dir {DirectoryPath} +login anonymous +app_info_print {appId} +app_update 4 +quit");
int openBracket = output.IndexOf("{"); int openBracket = output.IndexOf("{");
int closeBracket = output.LastIndexOf("}"); int closeBracket = output.LastIndexOf("}");
if (openBracket != -1 && closeBracket != -1) if (openBracket != -1 && closeBracket != -1)
@ -143,13 +188,13 @@ internal static class SteamCMD
if (Program.Canceled || output is null) return null; if (Program.Canceled || output is null) return null;
if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo)) if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo))
{ {
Directory.Delete(appUpdatePath, true); File.Delete(appUpdateFile);
//new DialogForm(null).Show("AppInfoAttempts", SystemIcons.Information, "Deserialize exception:\n\n" + output, "OK"); //new DialogForm(null).Show("GetAppInfo", SystemIcons.Information, "Deserialize exception:\n\n" + output, "OK");
goto restart; goto restart;
} }
if (appInfo.Value is VValue) if (appInfo.Value is VValue)
{ {
//new DialogForm(null).Show("AppInfoAttempts", SystemIcons.Information, "VValue exception:\n\n" + output, "OK"); //new DialogForm(null).Show("GetAppInfo", SystemIcons.Information, "VValue exception:\n\n" + output, "OK");
goto restart; goto restart;
} }
if (appInfo is null || appInfo.Value?.Children()?.ToList()?.Count == 0) return appInfo; if (appInfo is null || appInfo.Value?.Children()?.ToList()?.Count == 0) return appInfo;
@ -163,10 +208,10 @@ internal static class SteamCMD
List<int> dlcAppIds = await ParseDlcAppIds(appInfo); List<int> dlcAppIds = await ParseDlcAppIds(appInfo);
foreach (int id in dlcAppIds) foreach (int id in dlcAppIds)
{ {
string dlcAppUpdatePath = $@"{AppInfoPath}\{id}"; string dlcAppUpdateFile = $@"{AppInfoPath}\{id}.vdf";
if (Directory.Exists(dlcAppUpdatePath)) Directory.Delete(dlcAppUpdatePath, true); if (File.Exists(dlcAppUpdateFile)) File.Delete(dlcAppUpdateFile);
} }
if (Directory.Exists(appUpdatePath)) Directory.Delete(appUpdatePath, true); if (File.Exists(appUpdateFile)) File.Delete(appUpdateFile);
goto restart; goto restart;
} }
} }
@ -199,12 +244,15 @@ internal static class SteamCMD
List<Task> tasks = new(); List<Task> tasks = new();
foreach (Process process in Process.GetProcessesByName("steamcmd")) foreach (Process process in Process.GetProcessesByName("steamcmd"))
{ {
try tasks.Add(Task.Run(() =>
{ {
process.Kill(); try
tasks.Add(Task.Run(() => process.WaitForExit())); {
} process.Kill(true);
catch { } process.WaitForExit();
}
catch { }
}));
} }
foreach (Task task in tasks) await task; foreach (Task task in tasks) await task;
} }

View file

@ -5,7 +5,7 @@
<UseWindowsForms>True</UseWindowsForms> <UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Resources\ini.ico</ApplicationIcon> <ApplicationIcon>Resources\ini.ico</ApplicationIcon>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract> <IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<Version>2.3.2.0</Version> <Version>2.3.3.0</Version>
<PackageIcon>Resources\ini.ico</PackageIcon> <PackageIcon>Resources\ini.ico</PackageIcon>
<PackageIconUrl /> <PackageIconUrl />
<Description>Automatically generates and installs CreamAPI files for Steam games on the user's computer. It can also generate and install CreamAPI for the Paradox Launcher should the user select a Paradox Interactive game.</Description> <Description>Automatically generates and installs CreamAPI files for Steam games on the user's computer. It can also generate and install CreamAPI for the Paradox Launcher should the user select a Paradox Interactive game.</Description>

View file

@ -379,7 +379,12 @@ internal partial class SelectForm : CustomForm
{ {
if (!int.TryParse(node.Name, out int appId) || node.Parent is null && ProgramSelection.FromAppId(appId) is null) node.Remove(); if (!int.TryParse(node.Name, out int appId) || node.Parent is null && ProgramSelection.FromAppId(appId) is null) node.Remove();
}); });
//DateTime start = DateTime.Now;
await GetCreamApiApplicablePrograms(iProgress); await GetCreamApiApplicablePrograms(iProgress);
//DateTime end = DateTime.Now;
//TimeSpan t = end - start;
//new DialogForm(null).Show("GetCreamApiApplicablePrograms", SystemIcons.Information, "Gathering took " + t.ToString(@"mm\:ss"), "OK");
await SteamCMD.Cleanup();
HideProgressBar(); HideProgressBar();
selectionTreeView.Enabled = ProgramSelection.All.Any(); selectionTreeView.Enabled = ProgramSelection.All.Any();

View file

@ -122,7 +122,7 @@ internal static class Program
internal static async void Cleanup(bool cancel = true) internal static async void Cleanup(bool cancel = true)
{ {
Canceled = cancel; Canceled = cancel;
await SteamCMD.Kill(); await SteamCMD.Cleanup();
} }
internal static void Invoke(this Control control, MethodInvoker methodInvoker) => control.Invoke(methodInvoker); internal static void Invoke(this Control control, MethodInvoker methodInvoker) => control.Invoke(methodInvoker);