forked from sim0n00ps/OF-DL
255 lines
8.5 KiB
C#
255 lines
8.5 KiB
C#
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using Newtonsoft.Json;
|
|
using OF_DL.Helpers;
|
|
using OF_DL.Models;
|
|
using Serilog;
|
|
using WidevineConstants = OF_DL.Widevine.Constants;
|
|
|
|
namespace OF_DL.Services;
|
|
|
|
public class StartupService(IConfigService configService, IAuthService authService) : IStartupService
|
|
{
|
|
public async Task<StartupResult> ValidateEnvironmentAsync()
|
|
{
|
|
StartupResult result = new();
|
|
|
|
// OS validation
|
|
OperatingSystem os = Environment.OSVersion;
|
|
result.OsVersionString = os.VersionString;
|
|
Log.Debug($"Operating system information: {os.VersionString}");
|
|
|
|
if (os.Platform == PlatformID.Win32NT && os.Version.Major < 10)
|
|
{
|
|
result.IsWindowsVersionValid = false;
|
|
Log.Error("Windows version prior to 10.x: {0}", os.VersionString);
|
|
}
|
|
|
|
// FFmpeg detection
|
|
DetectFfmpeg(result);
|
|
|
|
if (result.FfmpegFound)
|
|
{
|
|
// Escape backslashes for Windows
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
|
result.FfmpegPath!.Contains(@":\") &&
|
|
!result.FfmpegPath.Contains(@":\\"))
|
|
{
|
|
result.FfmpegPath = result.FfmpegPath.Replace(@"\", @"\\");
|
|
configService.CurrentConfig!.FFmpegPath = result.FfmpegPath;
|
|
}
|
|
|
|
// Get FFmpeg version
|
|
result.FfmpegVersion = await GetFfmpegVersionAsync(result.FfmpegPath!);
|
|
}
|
|
|
|
// Widevine device checks
|
|
result.ClientIdBlobMissing = !File.Exists(Path.Join(WidevineConstants.DEVICES_FOLDER,
|
|
WidevineConstants.DEVICE_NAME, "device_client_id_blob"));
|
|
result.DevicePrivateKeyMissing = !File.Exists(Path.Join(WidevineConstants.DEVICES_FOLDER,
|
|
WidevineConstants.DEVICE_NAME, "device_private_key"));
|
|
|
|
if (!result.ClientIdBlobMissing)
|
|
{
|
|
Log.Debug("device_client_id_blob found");
|
|
}
|
|
else
|
|
{
|
|
Log.Debug("device_client_id_blob missing");
|
|
}
|
|
|
|
if (!result.DevicePrivateKeyMissing)
|
|
{
|
|
Log.Debug("device_private_key found");
|
|
}
|
|
else
|
|
{
|
|
Log.Debug("device_private_key missing");
|
|
}
|
|
|
|
// rules.json validation
|
|
if (File.Exists("rules.json"))
|
|
{
|
|
result.RulesJsonExists = true;
|
|
try
|
|
{
|
|
JsonConvert.DeserializeObject<DynamicRules>(File.ReadAllText("rules.json"));
|
|
Log.Debug("Rules.json: ");
|
|
Log.Debug(JsonConvert.SerializeObject(File.ReadAllText("rules.json"), Formatting.Indented));
|
|
result.RulesJsonValid = true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
result.RulesJsonError = e.Message;
|
|
Log.Error("rules.json processing failed.", e.Message);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<VersionCheckResult> CheckVersionAsync()
|
|
{
|
|
VersionCheckResult result = new();
|
|
|
|
#if !DEBUG
|
|
try
|
|
{
|
|
result.LocalVersion = Assembly.GetEntryAssembly()?.GetName().Version;
|
|
|
|
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30));
|
|
string? latestReleaseTag = null;
|
|
|
|
try
|
|
{
|
|
latestReleaseTag = await VersionHelper.GetLatestReleaseTag(cts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
result.TimedOut = true;
|
|
Log.Warning("Version check timed out after 30 seconds");
|
|
return result;
|
|
}
|
|
|
|
if (latestReleaseTag == null)
|
|
{
|
|
result.CheckFailed = true;
|
|
Log.Error("Failed to get the latest release tag.");
|
|
return result;
|
|
}
|
|
|
|
result.LatestVersion = new Version(latestReleaseTag.Replace("OFDLV", ""));
|
|
int versionComparison = result.LocalVersion!.CompareTo(result.LatestVersion);
|
|
result.IsUpToDate = versionComparison >= 0;
|
|
|
|
Log.Debug("Detected client running version " +
|
|
$"{result.LocalVersion.Major}.{result.LocalVersion.Minor}.{result.LocalVersion.Build}");
|
|
Log.Debug("Latest release version " +
|
|
$"{result.LatestVersion.Major}.{result.LatestVersion.Minor}.{result.LatestVersion.Build}");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
result.CheckFailed = true;
|
|
Log.Error("Error checking latest release on GitHub.", e.Message);
|
|
}
|
|
#else
|
|
Log.Debug("Running in Debug/Local mode. Version check skipped.");
|
|
result.IsUpToDate = true;
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
private void DetectFfmpeg(StartupResult result)
|
|
{
|
|
if (!string.IsNullOrEmpty(configService.CurrentConfig!.FFmpegPath) &&
|
|
ValidateFilePath(configService.CurrentConfig.FFmpegPath))
|
|
{
|
|
result.FfmpegFound = true;
|
|
result.FfmpegPath = configService.CurrentConfig.FFmpegPath;
|
|
Log.Debug($"FFMPEG found: {result.FfmpegPath}");
|
|
Log.Debug("FFMPEG path set in config.conf");
|
|
}
|
|
else if (!string.IsNullOrEmpty(authService.CurrentAuth?.FfmpegPath) &&
|
|
ValidateFilePath(authService.CurrentAuth.FfmpegPath))
|
|
{
|
|
result.FfmpegFound = true;
|
|
result.FfmpegPath = authService.CurrentAuth.FfmpegPath;
|
|
configService.CurrentConfig.FFmpegPath = result.FfmpegPath;
|
|
Log.Debug($"FFMPEG found: {result.FfmpegPath}");
|
|
Log.Debug("FFMPEG path set in auth.json");
|
|
}
|
|
else if (string.IsNullOrEmpty(configService.CurrentConfig.FFmpegPath))
|
|
{
|
|
string? ffmpegPath = GetFullPath("ffmpeg") ?? GetFullPath("ffmpeg.exe");
|
|
if (ffmpegPath != null)
|
|
{
|
|
result.FfmpegFound = true;
|
|
result.FfmpegPathAutoDetected = true;
|
|
result.FfmpegPath = ffmpegPath;
|
|
configService.CurrentConfig.FFmpegPath = ffmpegPath;
|
|
Log.Debug($"FFMPEG found: {ffmpegPath}");
|
|
Log.Debug("FFMPEG path found via PATH or current directory");
|
|
}
|
|
}
|
|
|
|
if (!result.FfmpegFound)
|
|
{
|
|
Log.Error($"Cannot locate FFmpeg with path: {configService.CurrentConfig.FFmpegPath}");
|
|
}
|
|
}
|
|
|
|
private static async Task<string?> GetFfmpegVersionAsync(string ffmpegPath)
|
|
{
|
|
try
|
|
{
|
|
ProcessStartInfo processStartInfo = new()
|
|
{
|
|
FileName = ffmpegPath,
|
|
Arguments = "-version",
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
using Process? process = Process.Start(processStartInfo);
|
|
if (process != null)
|
|
{
|
|
string output = await process.StandardOutput.ReadToEndAsync();
|
|
await process.WaitForExitAsync();
|
|
|
|
Log.Information("FFmpeg version output:\n{Output}", output);
|
|
|
|
string firstLine = output.Split('\n')[0].Trim();
|
|
if (firstLine.StartsWith("ffmpeg version"))
|
|
{
|
|
int versionStart = "ffmpeg version ".Length;
|
|
int copyrightIndex = firstLine.IndexOf(" Copyright");
|
|
return copyrightIndex > versionStart
|
|
? firstLine.Substring(versionStart, copyrightIndex - versionStart)
|
|
: firstLine.Substring(versionStart);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warning(ex, "Failed to get FFmpeg version");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static bool ValidateFilePath(string path)
|
|
{
|
|
char[] invalidChars = Path.GetInvalidPathChars();
|
|
if (path.Any(c => invalidChars.Contains(c)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return File.Exists(path);
|
|
}
|
|
|
|
public static string? GetFullPath(string filename)
|
|
{
|
|
if (File.Exists(filename))
|
|
{
|
|
return Path.GetFullPath(filename);
|
|
}
|
|
|
|
string pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
|
|
foreach (string path in pathEnv.Split(Path.PathSeparator))
|
|
{
|
|
string fullPath = Path.Combine(path, filename);
|
|
if (File.Exists(fullPath))
|
|
{
|
|
return fullPath;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|