- 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.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
@ -17,27 +18,22 @@ namespace CreamInstaller.Classes;
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 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");
internal static readonly string AppInfoVersionPath = AppInfoPath + @"\version.txt";
//private static readonly int[] locks = new int[20]; // acts as an effective process limit
private static readonly int[] locks = new int[ProcessLimit];
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 (Interlocked.CompareExchange(ref locks[i], 1, 0) == 0)
{*/
{
if (Program.Canceled) return "";
List<string> logs = new();
ProcessStartInfo processStartInfo = new()
@ -59,17 +55,23 @@ internal static class SteamCMD
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data);
process.BeginErrorReadLine();
process.WaitForExit();
//Interlocked.Decrement(ref locks[i]);
Interlocked.Decrement(ref locks[i]);
return string.Join("\r\n", logs);
/*}
Thread.Sleep(200);
}
goto wait_for_lock;*/
Thread.Sleep(0);
}
Thread.Sleep(200);
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)
{
await Kill();
await Cleanup();
if (!Directory.Exists(DirectoryPath)) Directory.CreateDirectory(DirectoryPath);
if (!File.Exists(FilePath))
{
@ -81,7 +83,6 @@ internal static class SteamCMD
ZipFile.ExtractToDirectory(ArchivePath, DirectoryPath);
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 (Directory.Exists(AppInfoPath)) Directory.Delete(AppInfoPath, true);
@ -100,38 +101,82 @@ internal static class SteamCMD
progress.Report(cur);
watcher.Changed += (sender, e) => progress.Report(++cur);
await Run($@"+quit");
await Cleanup();
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
{
string[] oldFiles = Directory.GetFiles(DirectoryPath, "*.old");
foreach (string file in oldFiles) File.Delete(file);
}
catch { }
try
{
string[] deleteFiles = Directory.GetFiles(DirectoryPath, "*.delete");
foreach (string file in deleteFiles) File.Delete(file);
}
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)
{
if (Program.Canceled) return null;
string output;
string appUpdatePath = $@"{AppInfoPath}\{appId}";
string appUpdateFile = $@"{appUpdatePath}\appinfo.txt";
string appUpdateFile = $@"{AppInfoPath}\{appId}.vdf";
restart:
await Cleanup();
if (Program.Canceled) return null;
if (!Directory.Exists(appUpdatePath)) Directory.CreateDirectory(appUpdatePath);
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 {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 closeBracket = output.LastIndexOf("}");
if (openBracket != -1 && closeBracket != -1)
@ -143,13 +188,13 @@ internal static class SteamCMD
if (Program.Canceled || output is null) return null;
if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo))
{
Directory.Delete(appUpdatePath, true);
//new DialogForm(null).Show("AppInfoAttempts", SystemIcons.Information, "Deserialize exception:\n\n" + output, "OK");
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("AppInfoAttempts", SystemIcons.Information, "VValue exception:\n\n" + output, "OK");
//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;
@ -163,10 +208,10 @@ internal static class SteamCMD
List<int> dlcAppIds = await ParseDlcAppIds(appInfo);
foreach (int id in dlcAppIds)
{
string dlcAppUpdatePath = $@"{AppInfoPath}\{id}";
if (Directory.Exists(dlcAppUpdatePath)) Directory.Delete(dlcAppUpdatePath, true);
string dlcAppUpdateFile = $@"{AppInfoPath}\{id}.vdf";
if (File.Exists(dlcAppUpdateFile)) File.Delete(dlcAppUpdateFile);
}
if (Directory.Exists(appUpdatePath)) Directory.Delete(appUpdatePath, true);
if (File.Exists(appUpdateFile)) File.Delete(appUpdateFile);
goto restart;
}
}
@ -198,13 +243,16 @@ internal static class SteamCMD
{
List<Task> tasks = new();
foreach (Process process in Process.GetProcessesByName("steamcmd"))
{
tasks.Add(Task.Run(() =>
{
try
{
process.Kill();
tasks.Add(Task.Run(() => process.WaitForExit()));
process.Kill(true);
process.WaitForExit();
}
catch { }
}));
}
foreach (Task task in tasks) await task;
}

View file

@ -5,7 +5,7 @@
<UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Resources\ini.ico</ApplicationIcon>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<Version>2.3.2.0</Version>
<Version>2.3.3.0</Version>
<PackageIcon>Resources\ini.ico</PackageIcon>
<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>

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();
});
//DateTime start = DateTime.Now;
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();
selectionTreeView.Enabled = ProgramSelection.All.Any();

View file

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