using System.Runtime.InteropServices; using API.MangaDownloadClients; using API.Schema; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace API; public static class TrangaSettings { public static string downloadLocation { get; private set; } = (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Downloads")); public static string workingDirectory { get; private set; } = Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api"); [JsonIgnore] internal static readonly string DefaultUserAgent = $"Tranga ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")}) / 1.0"; public static string userAgent { get; private set; } = DefaultUserAgent; public static int compression{ get; private set; } = 40; public static bool bwImages { get; private set; } = false; /// /// Placeholders: /// %M Manga Name /// %V Volume /// %C Chapter /// %T Title /// %A Author (first in list) /// %I Chapter Internal ID /// %i Manga Internal ID /// %Y Year (Manga) /// /// ?_(...) replace _ with a value from above: /// Everything inside the braces will only be added if the value of %_ is not null /// public static string chapterNamingScheme { get; private set; } = "%M - ?V(Vol.%V )Ch.%C?T( - %T)"; [JsonIgnore] public static string settingsFilePath => Path.Join(workingDirectory, "settings.json"); [JsonIgnore] public static string coverImageCache => Path.Join(workingDirectory, "imageCache"); public static bool aprilFoolsMode { get; private set; } = true; public static int startNewJobTimeoutMs { get; private set; } = 1000; [JsonIgnore] internal static readonly Dictionary DefaultRequestLimits = new () { {RequestType.MangaInfo, 250}, {RequestType.MangaDexFeed, 250}, {RequestType.MangaDexImage, 40}, {RequestType.MangaImage, 60}, {RequestType.MangaCover, 250}, {RequestType.Default, 60} }; public static Dictionary requestLimits { get; private set; } = DefaultRequestLimits; public static TimeSpan NotificationUrgencyDelay(NotificationUrgency urgency) => urgency switch { NotificationUrgency.High => TimeSpan.Zero, NotificationUrgency.Normal => TimeSpan.FromMinutes(5), NotificationUrgency.Low => TimeSpan.FromMinutes(10), _ => TimeSpan.FromHours(1) }; //TODO make this a setting? public static void Load() { if(File.Exists(settingsFilePath)) Deserialize(File.ReadAllText(settingsFilePath)); else return; Directory.CreateDirectory(downloadLocation); ExportSettings(); } public static void UpdateAprilFoolsMode(bool enabled) { aprilFoolsMode = enabled; ExportSettings(); } public static void UpdateCompressImages(int value) { compression = int.Clamp(value, 1, 100); ExportSettings(); } public static void UpdateBwImages(bool enabled) { bwImages = enabled; ExportSettings(); } public static void UpdateUserAgent(string? customUserAgent) { userAgent = customUserAgent ?? DefaultUserAgent; ExportSettings(); } public static void UpdateRequestLimit(RequestType requestType, int newLimit) { requestLimits[requestType] = newLimit; ExportSettings(); } public static void UpdateChapterNamingScheme(string namingScheme) { chapterNamingScheme = namingScheme; ExportSettings(); } public static void ResetRequestLimits() { requestLimits = DefaultRequestLimits; ExportSettings(); } public static void ExportSettings() { if (File.Exists(settingsFilePath)) { while(IsFileInUse(settingsFilePath)) Thread.Sleep(100); } else Directory.CreateDirectory(new FileInfo(settingsFilePath).DirectoryName!); File.WriteAllText(settingsFilePath, Serialize()); } internal static bool IsFileInUse(string filePath) { if (!File.Exists(filePath)) return false; try { using FileStream stream = new (filePath, FileMode.Open, FileAccess.Read, FileShare.None); stream.Close(); return false; } catch (IOException) { return true; } } public static JObject AsJObject() { JObject jobj = new (); jobj.Add("downloadLocation", JToken.FromObject(downloadLocation)); jobj.Add("workingDirectory", JToken.FromObject(workingDirectory)); jobj.Add("userAgent", JToken.FromObject(userAgent)); jobj.Add("aprilFoolsMode", JToken.FromObject(aprilFoolsMode)); jobj.Add("requestLimits", JToken.FromObject(requestLimits)); jobj.Add("compression", JToken.FromObject(compression)); jobj.Add("bwImages", JToken.FromObject(bwImages)); jobj.Add("startNewJobTimeoutMs", JToken.FromObject(startNewJobTimeoutMs)); jobj.Add("chapterNamingScheme", JToken.FromObject(chapterNamingScheme)); return jobj; } public static string Serialize() => AsJObject().ToString(); public static void Deserialize(string serialized) { JObject jobj = JObject.Parse(serialized); if (jobj.TryGetValue("downloadLocation", out JToken? dl)) downloadLocation = dl.Value()!; if (jobj.TryGetValue("workingDirectory", out JToken? wd)) workingDirectory = wd.Value()!; if (jobj.TryGetValue("userAgent", out JToken? ua)) userAgent = ua.Value()!; if (jobj.TryGetValue("aprilFoolsMode", out JToken? afm)) aprilFoolsMode = afm.Value()!; if (jobj.TryGetValue("requestLimits", out JToken? rl)) requestLimits = rl.ToObject>()!; if (jobj.TryGetValue("compression", out JToken? ci)) compression = ci.Value()!; if (jobj.TryGetValue("bwImages", out JToken? bwi)) bwImages = bwi.Value()!; if (jobj.TryGetValue("startNewJobTimeoutMs", out JToken? snjt)) startNewJobTimeoutMs = snjt.Value()!; if (jobj.TryGetValue("chapterNamingScheme", out JToken? cns)) chapterNamingScheme = cns.Value()!; } }