I'll make this modular some day

This commit is contained in:
yosh 2024-10-31 13:42:25 -04:00
parent f3860d0ffe
commit a92e50dc99
3 changed files with 187 additions and 27 deletions

11
Config.cs Normal file
View file

@ -0,0 +1,11 @@
namespace ResoniteCacheCleaner
{
public class Config
{
// Delete older than this access time
public int AccessTimeDays = 30;
// DataPath
public string DataPath;
}
}

View file

@ -2,46 +2,71 @@ 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)
{
/* set data/cache paths */
// CHECK LINUX, SET Config.DataPath
string UserPath;
string DataPath;
string CachePath;
bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
CachePathResolver CachePathHandler = CachePathResolverWindows;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
if (IsLinux) {
Console.Error.WriteLine("On Linux, assuming Proton path.");
UserPath = Environment.GetEnvironmentVariable("HOME")
+ "/.steam/steam/steamapps/compatdata/2519830/pfx/drive_c/users/steamuser";
CachePath = Path.Combine(UserPath, "Temp");
ProtonRealRoot = Environment.GetEnvironmentVariable("HOME")
+ "/.steam/steam/steamapps/compatdata/2519830/pfx/drive_c";
UserPath = ProtonRealRoot + "/users/steamuser";
CachePathHandler = CachePathResolverLinux;
} else {
UserPath = Environment.GetEnvironmentVariable("USERPROFILE");
CachePath = Environment.GetEnvironmentVariable("TMP") ?? Path.Combine(UserPath, "Temp");
}
string[] _s = { UserPath, "AppData", "LocalLow", "Yellow Dog Man Studios", "Resonite" };
DataPath = Path.Combine(_s);
CachePath = Path.Combine(CachePath, "Yellow Dog Man Studios", "Resonite", "Cache");
if (!Directory.Exists(DataPath)) {
Console.Error.WriteLine("DataPath not found. Try manually specifying it with -DataPath");
Environment.Exit(1);
}
if (!Directory.Exists(CachePath)) {
Console.Error.WriteLine("CachePath not found. Try manually specifying it with -CachePath");
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: " + DataPath);
Console.Error.WriteLine("CachePath: " + CachePath);
/* end data/cache paths */
Console.Error.WriteLine("DataPath: " + Config.DataPath);
/* get machine id */
string LocalKeyPath = Path.Combine(DataPath, "LocalKey.bin");
// 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);
@ -49,18 +74,94 @@ namespace ResoniteCacheCleaner
string MachineID = FrooxHelper.GetMachineID(LocalKeyPath);
Console.Error.WriteLine("Machine ID: " + MachineID);
/* get database */
string DatabasePath = Path.Combine(DataPath, "Data.litedb");
// 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);
}
/* establish connection to db */
// CONNECT TO DATABASE
string ConnStr = FrooxHelper.ProcessConnection("filename=" + DatabasePath + ";", MachineID);
Console.Error.WriteLine(ConnStr);
LiteDatabase DB = new LiteDatabase(ConnStr);
var RecordCollection = DB.GetCollection("Records");
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);

48
Util.cs Normal file
View file

@ -0,0 +1,48 @@
using System;
using System.Globalization;
namespace ResoniteCacheCleaner
{
internal class Util
{
// surprised this isn't built in to c# given how much stuff c# has
// https://stackoverflow.com/a/11124118
public static string BytesToString(long value)
{
string suffix;
double readable;
switch (Math.Abs(value))
{
case >= 0x1000000000000000:
suffix = "EiB";
readable = value >> 50;
break;
case >= 0x4000000000000:
suffix = "PiB";
readable = value >> 40;
break;
case >= 0x10000000000:
suffix = "TiB";
readable = value >> 30;
break;
case >= 0x40000000:
suffix = "GiB";
readable = value >> 20;
break;
case >= 0x100000:
suffix = "MiB";
readable = value >> 10;
break;
case >= 0x400:
suffix = "KiB";
readable = value;
break;
default:
return value.ToString("0 B");
}
return (readable / 1024).ToString("0.## ", CultureInfo.InvariantCulture) + suffix;
}
}
}