ResoniteCacheCleaner/ResoniteCacheCleaner.cs
2024-10-31 13:42:25 -04:00

170 lines
5.7 KiB
C#

using System;
using System.Runtime.InteropServices;
using System.IO;
using LiteDB;
using System.Linq;
using System.Collections.Generic;
namespace ResoniteCacheCleaner
{
public class ResoniteCacheCleaner
{
public static Config Config = new Config();
private static string? ProtonRealRoot; // only useful for linux
private delegate string CachePathResolver(string Path);
public static LiteDatabase? DB;
public static LiteDatabase? NewDB;
private static string CachePathResolverLinux(string _path)
{
return ProtonRealRoot + _path.Substring(2).Replace('\\', '/');
}
private static string CachePathResolverWindows(string _path)
{
return _path;
}
public static List<T> DatabaseExtractAllEntries<T>(LiteDatabase db, string collectionName, Func<T, string>? uniqueKeySelector = null)
{
ILiteCollection<T> coll = db.GetCollection<T>(collectionName);
HashSet<string> uniqueKeys = new HashSet<string>();
List<T> list = coll.FindAll().ToList();
if (uniqueKeySelector != null) {
list.RemoveAll((T e) => !uniqueKeys.Add(uniqueKeySelector(e)));
}
return list;
}
static void Main(string[] args)
{
// CHECK LINUX, SET Config.DataPath
string UserPath;
bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
CachePathResolver CachePathHandler = CachePathResolverWindows;
if (IsLinux) {
Console.Error.WriteLine("On Linux, assuming Proton path.");
ProtonRealRoot = Environment.GetEnvironmentVariable("HOME")
+ "/.steam/steam/steamapps/compatdata/2519830/pfx/drive_c";
UserPath = ProtonRealRoot + "/users/steamuser";
CachePathHandler = CachePathResolverLinux;
} else {
UserPath = Environment.GetEnvironmentVariable("USERPROFILE");
}
Config.DataPath = Path.Combine(new string[5] { UserPath, "AppData", "LocalLow", "Yellow Dog Man Studios", "Resonite" });
if (!Directory.Exists(Config.DataPath)) {
Console.Error.WriteLine("Config.DataPath not found. Try manually specifying it with -DataPath");
Environment.Exit(1);
}
Console.Error.WriteLine("DataPath: " + Config.DataPath);
// GET MACHINE ID
string LocalKeyPath = Path.Combine(Config.DataPath, "LocalKey.bin");
if (!File.Exists(LocalKeyPath)) {
Console.Error.WriteLine("No localkey.bin in DataPath. Is it the right DataPath?\nIf so, launch Resonite at least once.");
Environment.Exit(1);
}
string MachineID = FrooxHelper.GetMachineID(LocalKeyPath);
Console.Error.WriteLine("Machine ID: " + MachineID);
// GET DATABASE
string DatabasePath = Path.Combine(Config.DataPath, "Data2.litedb");
if (!File.Exists(DatabasePath)) {
Console.Error.WriteLine("No Data.litedb in DataPath. Is it the right DataPath?");
Environment.Exit(1);
}
// CONNECT TO DATABASE
string ConnStr = FrooxHelper.ProcessConnection("filename=" + DatabasePath + ";", MachineID);
Console.Error.WriteLine(ConnStr);
DB = new LiteDatabase(ConnStr);
// INITIALIZE DATE THRESHOLD
DateTime DateThreshold = DateTime.Now.AddDays(Config.AccessTimeDays * -1f);
// FETCH ASSETS
Console.WriteLine("Fetching Assets...");
var _assetReader = DB.Execute("SELECT $._id, $.path FROM Assets");
var FlaggedAssetsDel = new List<int>();
var FlaggedAssetsOld = new List<Tuple<int, string>>();
long FlaggedAssetsSizeBytes = 0;
while (_assetReader.Read()) {
// set ID and Path
var Asset = (Dictionary<string, BsonValue>)_assetReader.Current.RawValue;
int AssetID = Asset["_id"].AsInt32;
string AssetPath = Asset["Path"].AsString;
// There are 3 types of assets: Assets, Cache, and PreCache
// PreCache seems important enough to not touch at all. That can build up fine.
// Assets and Cache are the two we want to flag for checking
string[] _splitArr = AssetPath.Split('\\');
string FullPath;
if (_splitArr.Length == 1) { // Asset
FullPath = Path.Combine(Config.DataPath, "Assets", AssetPath);
} else if (_splitArr[_splitArr.Length - 2] == "Cache") { // Cache
FullPath = CachePathHandler(AssetPath);
} else { // PreCache
continue;
}
if (!File.Exists(FullPath)) {
FlaggedAssetsDel.Add(AssetID);
} else if (File.GetLastAccessTime(FullPath) < DateThreshold) {
var _fi = new FileInfo(FullPath);
FlaggedAssetsOld.Add(Tuple.Create(AssetID, FullPath));
FlaggedAssetsSizeBytes += _fi.Length;
}
}
// USER PROMPT
Console.WriteLine("Nonexistent files: " + FlaggedAssetsDel.Count);
Console.WriteLine("Files last accessed more than " + Config.AccessTimeDays + " days ago (will be removed from disk): " + FlaggedAssetsOld.Count);
Console.WriteLine("Total database entries to delete: " + (FlaggedAssetsDel.Count + FlaggedAssetsOld.Count));
Console.WriteLine("Estimated Space Saved: " + Util.BytesToString(FlaggedAssetsSizeBytes));
Console.WriteLine("Continue? [Y]es/[N]o");
if (Console.ReadLine().ToLower() != "y") {
Console.WriteLine("Input not y. Not continuing.");
Environment.Exit(0);
}
// COMMIT TO DATABASE CLEARING
DB.Execute("BEGIN");
try {
foreach (var _assetID in FlaggedAssetsDel) {
DB.Execute("DELETE Assets WHERE _id = " + _assetID);
}
foreach (var _asset in FlaggedAssetsOld) {
DB.Execute("DELETE Assets WHERE _id = " + _asset.Item1);
File.Delete(_asset.Item2);
}
} catch (Exception e) {
DB.Execute("ROLLBACK");
Console.Error.WriteLine("Exception occured, Database not updated:");
Console.Error.WriteLine(e.Message);
Environment.Exit(1);
}
DB.Execute("COMMIT");
// Console.WriteLine("Database Updated. Rebuilding (this will take a while)...");
//
// DatabaseBackup = DatabasePath + ".bak";
// File.Move(DatabasePath, DatabaseBackup);
//
// NewDB = new LiteDatabase(ConnStr);
//
// DB.Rebuild();
Environment.Exit(0);
}
}
}