Refactor code structure for improved readability and maintainability

This commit is contained in:
MaddoScientisto 2026-02-21 10:40:12 +01:00
commit 4f488bae45
78 changed files with 3309 additions and 1570 deletions

View file

@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
namespace TwitchArchive.Core.Config
{
public class AppSettings
{
[JsonPropertyName("streamlink_path")]
public string? StreamlinkPath { get; set; }
[JsonPropertyName("ffmpeg_path")]
public string? FfmpegPath { get; set; }
[JsonPropertyName("twitchdownloader_path")]
public string? TwitchDownloaderPath { get; set; }
[JsonPropertyName("rclone_path")]
public string? RclonePath { get; set; }
[JsonPropertyName("password_hash")]
public string? PasswordHash { get; set; }
}
}

View file

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace TwitchArchive.Core.Config
{
public class ConfigurationService : IConfigurationService
{
private readonly string _basePath;
private readonly string _streamersPath;
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { WriteIndented = true };
public ConfigurationService(string? basePath = null)
{
_basePath = basePath ?? Path.Combine(AppContext.BaseDirectory, "config");
_streamersPath = Path.Combine(_basePath, "streamers");
Directory.CreateDirectory(_basePath);
Directory.CreateDirectory(_streamersPath);
}
public GlobalConfig LoadGlobal()
{
var file = Path.Combine(_basePath, "global.json");
if (!File.Exists(file)) return new GlobalConfig();
var txt = File.ReadAllText(file);
return JsonSerializer.Deserialize<GlobalConfig>(txt, _jsonOptions) ?? new GlobalConfig();
}
public void SaveGlobal(GlobalConfig cfg)
{
var file = Path.Combine(_basePath, "global.json");
var txt = JsonSerializer.Serialize(cfg, _jsonOptions);
File.WriteAllText(file, txt);
}
public StreamerConfig? LoadStreamer(string username)
{
if (string.IsNullOrWhiteSpace(username)) return null;
var file = Path.Combine(_streamersPath, username + ".json");
if (!File.Exists(file)) return null;
var txt = File.ReadAllText(file);
return JsonSerializer.Deserialize<StreamerConfig>(txt, _jsonOptions);
}
public void SaveStreamer(StreamerConfig cfg)
{
if (cfg == null) throw new ArgumentNullException(nameof(cfg));
if (string.IsNullOrWhiteSpace(cfg.Username)) throw new ArgumentException("Username required");
var file = Path.Combine(_streamersPath, cfg.Username + ".json");
var txt = JsonSerializer.Serialize(cfg, _jsonOptions);
File.WriteAllText(file, txt);
}
public IEnumerable<StreamerConfig> GetAllStreamers()
{
if (!Directory.Exists(_streamersPath)) return Enumerable.Empty<StreamerConfig>();
var files = Directory.EnumerateFiles(_streamersPath, "*.json");
var list = new List<StreamerConfig>();
foreach (var f in files)
{
try
{
var txt = File.ReadAllText(f);
var s = JsonSerializer.Deserialize<StreamerConfig>(txt, _jsonOptions);
if (s != null) list.Add(s);
}
catch { }
}
return list;
}
public EffectiveConfig GetEffectiveConfig(string username)
{
var global = LoadGlobal();
var streamer = LoadStreamer(username);
return EffectiveConfig.Merge(global, streamer);
}
public void DeleteStreamer(string username)
{
if (string.IsNullOrWhiteSpace(username)) return;
var file = Path.Combine(_streamersPath, username + ".json");
try { if (File.Exists(file)) File.Delete(file); } catch { }
}
}
}

View file

@ -0,0 +1,36 @@
using System;
namespace TwitchArchive.Core.Config
{
public class EffectiveConfig
{
public string? ArchiveRoot { get; init; }
public string? StreamlinkPath { get; init; }
public string? FfmpegPath { get; init; }
public string? TwitchDownloaderPath { get; init; }
public string? RclonePath { get; init; }
public bool UploadToCloud { get; init; }
public string? UploadDestination { get; init; }
public int RefreshIntervalSeconds { get; init; }
public int StreamSegmentThreads { get; init; }
public string? DefaultQuality { get; init; }
public static EffectiveConfig Merge(GlobalConfig global, StreamerConfig? streamer)
{
streamer ??= new StreamerConfig();
return new EffectiveConfig
{
ArchiveRoot = streamer.Username != null ? (global.ArchiveRoot ?? string.Empty) : (global.ArchiveRoot ?? string.Empty),
StreamlinkPath = streamer.StreamlinkPath ?? global.StreamlinkPath,
FfmpegPath = global.FfmpegPath,
TwitchDownloaderPath = global.TwitchDownloaderPath,
RclonePath = global.RclonePath,
UploadToCloud = streamer.UploadToCloud ?? global.UploadToCloud,
UploadDestination = streamer.UploadDestination ?? global.UploadDestination,
RefreshIntervalSeconds = global.RefreshIntervalSeconds,
StreamSegmentThreads = global.StreamSegmentThreads,
DefaultQuality = streamer.Quality ?? global.DefaultQuality
};
}
}
}

View file

@ -0,0 +1,37 @@
using System.Text.Json.Serialization;
namespace TwitchArchive.Core.Config
{
public class GlobalConfig
{
[JsonPropertyName("archive_root")]
public string? ArchiveRoot { get; set; }
[JsonPropertyName("streamlink_path")]
public string? StreamlinkPath { get; set; }
[JsonPropertyName("ffmpeg_path")]
public string? FfmpegPath { get; set; }
[JsonPropertyName("twitchdownloader_path")]
public string? TwitchDownloaderPath { get; set; }
[JsonPropertyName("rclone_path")]
public string? RclonePath { get; set; }
[JsonPropertyName("upload_to_cloud")]
public bool UploadToCloud { get; set; } = false;
[JsonPropertyName("upload_destination")]
public string? UploadDestination { get; set; }
[JsonPropertyName("refresh_interval_seconds")]
public int RefreshIntervalSeconds { get; set; } = 60;
[JsonPropertyName("stream_segment_threads")]
public int StreamSegmentThreads { get; set; } = 4;
[JsonPropertyName("default_quality")]
public string? DefaultQuality { get; set; } = "best";
}
}

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace TwitchArchive.Core.Config
{
public interface IConfigurationService
{
GlobalConfig LoadGlobal();
void SaveGlobal(GlobalConfig cfg);
StreamerConfig? LoadStreamer(string username);
void SaveStreamer(StreamerConfig cfg);
void DeleteStreamer(string username);
IEnumerable<StreamerConfig> GetAllStreamers();
EffectiveConfig GetEffectiveConfig(string username);
}
}

View file

@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
namespace TwitchArchive.Core.Config
{
public class StreamerConfig
{
[JsonPropertyName("username")]
public string Username { get; set; } = string.Empty;
[JsonPropertyName("enabled")]
public bool Enabled { get; set; } = true;
[JsonPropertyName("quality")]
public string? Quality { get; set; }
[JsonPropertyName("upload_to_cloud")]
public bool? UploadToCloud { get; set; }
[JsonPropertyName("upload_destination")]
public string? UploadDestination { get; set; }
[JsonPropertyName("streamlink_path")]
public string? StreamlinkPath { get; set; }
}
}

View file

@ -0,0 +1,35 @@
using System.Runtime.InteropServices;
namespace TwitchArchive.Core.Config
{
public static class ToolPathResolver
{
public static string DefaultStreamlinkPath()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return "streamlink.exe";
return "/usr/local/bin/streamlink";
}
public static string DefaultFfmpegPath()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return "ffmpeg.exe";
return "/usr/bin/ffmpeg";
}
public static string DefaultTwitchDownloaderPath()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return "TwitchDownloaderCLI.exe";
return "/app/bin/TwitchDownloaderCLI";
}
public static string DefaultRclonePath()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return "rclone.exe";
return "/usr/bin/rclone";
}
}
}