mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-23 19:42:51 +01:00
162 lines
5.2 KiB
C#
162 lines
5.2 KiB
C#
using System.Diagnostics;
|
|
using Phantom.Common.Data.Java;
|
|
using Phantom.Utils.IO;
|
|
using Phantom.Utils.Logging;
|
|
using Serilog;
|
|
|
|
namespace Phantom.Agent.Minecraft.Java;
|
|
|
|
public sealed class JavaRuntimeDiscovery {
|
|
private static readonly ILogger Logger = PhantomLogger.Create(nameof(JavaRuntimeDiscovery));
|
|
|
|
public static string? GetSystemSearchPath() {
|
|
const string LinuxJavaPath = "/usr/lib/jvm";
|
|
|
|
if (OperatingSystem.IsLinux() && Directory.Exists(LinuxJavaPath)) {
|
|
return LinuxJavaPath;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static IAsyncEnumerable<JavaRuntimeExecutable> Scan(string folderPath) {
|
|
return new JavaRuntimeDiscovery().ScanInternal(folderPath);
|
|
}
|
|
|
|
private readonly Dictionary<string, int> duplicateDisplayNames = new ();
|
|
|
|
private async IAsyncEnumerable<JavaRuntimeExecutable> ScanInternal(string folderPath) {
|
|
Logger.Information("Starting Java runtime scan in: {FolderPath}", folderPath);
|
|
|
|
string javaExecutableName = OperatingSystem.IsWindows() ? "java.exe" : "java";
|
|
|
|
foreach (var binFolderPath in Directory.EnumerateDirectories(Paths.ExpandTilde(folderPath), "bin", new EnumerationOptions {
|
|
MatchType = MatchType.Simple,
|
|
RecurseSubdirectories = true,
|
|
ReturnSpecialDirectories = false,
|
|
IgnoreInaccessible = true,
|
|
AttributesToSkip = FileAttributes.Hidden | FileAttributes.ReparsePoint | FileAttributes.System
|
|
}).Order()) {
|
|
var javaExecutablePath = Paths.NormalizeSlashes(Path.Combine(binFolderPath, javaExecutableName));
|
|
|
|
FileAttributes javaExecutableAttributes;
|
|
try {
|
|
javaExecutableAttributes = File.GetAttributes(javaExecutablePath);
|
|
} catch (Exception) {
|
|
continue;
|
|
}
|
|
|
|
if (javaExecutableAttributes.HasFlag(FileAttributes.ReparsePoint)) {
|
|
continue;
|
|
}
|
|
|
|
Logger.Information("Found candidate Java executable: {JavaExecutablePath}", javaExecutablePath);
|
|
|
|
JavaRuntime? foundRuntime;
|
|
try {
|
|
foundRuntime = await TryReadJavaRuntimeInformationFromProcess(javaExecutablePath);
|
|
} catch (OperationCanceledException) {
|
|
Logger.Error("Java process did not exit in time.");
|
|
continue;
|
|
} catch (Exception e) {
|
|
Logger.Error(e, "Caught exception while reading Java version information.");
|
|
continue;
|
|
}
|
|
|
|
if (foundRuntime == null) {
|
|
Logger.Error("Java executable did not output version information.");
|
|
continue;
|
|
}
|
|
|
|
Logger.Information("Found Java {DisplayName} at: {Path}", foundRuntime.DisplayName, javaExecutablePath);
|
|
yield return new JavaRuntimeExecutable(javaExecutablePath, foundRuntime);
|
|
}
|
|
}
|
|
|
|
private async Task<JavaRuntime?> TryReadJavaRuntimeInformationFromProcess(string javaExecutablePath) {
|
|
var startInfo = new ProcessStartInfo {
|
|
FileName = javaExecutablePath,
|
|
WorkingDirectory = Path.GetDirectoryName(javaExecutablePath),
|
|
Arguments = "-XshowSettings:properties -version",
|
|
RedirectStandardInput = false,
|
|
RedirectStandardOutput = false,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false
|
|
};
|
|
|
|
var process = new Process { StartInfo = startInfo };
|
|
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
|
|
|
try {
|
|
process.Start();
|
|
|
|
JavaRuntimeBuilder runtimeBuilder = new ();
|
|
|
|
while (await process.StandardError.ReadLineAsync(cancellationTokenSource.Token) is {} line) {
|
|
ExtractJavaVersionPropertiesFromLine(line, runtimeBuilder);
|
|
|
|
JavaRuntime? runtime = runtimeBuilder.TryBuild(duplicateDisplayNames);
|
|
if (runtime != null) {
|
|
return runtime;
|
|
}
|
|
}
|
|
|
|
await process.WaitForExitAsync(cancellationTokenSource.Token);
|
|
return null;
|
|
} finally {
|
|
process.Dispose();
|
|
cancellationTokenSource.Dispose();
|
|
}
|
|
}
|
|
|
|
private static void ExtractJavaVersionPropertiesFromLine(ReadOnlySpan<char> line, JavaRuntimeBuilder runtimeBuilder) {
|
|
line = line.TrimStart();
|
|
|
|
int separatorIndex = line.IndexOf('=');
|
|
if (separatorIndex == -1) {
|
|
return;
|
|
}
|
|
|
|
var propertyName = line[..separatorIndex].TrimEnd();
|
|
if (propertyName.Equals("java.specification.version", StringComparison.Ordinal)) {
|
|
runtimeBuilder.MainVersion = ExtractValue(line, separatorIndex);
|
|
}
|
|
else if (propertyName.Equals("java.version", StringComparison.Ordinal)) {
|
|
runtimeBuilder.FullVersion = ExtractValue(line, separatorIndex);
|
|
}
|
|
else if (propertyName.Equals("java.vm.vendor", StringComparison.Ordinal)) {
|
|
runtimeBuilder.Vendor = ExtractValue(line, separatorIndex);
|
|
}
|
|
}
|
|
|
|
private static string ExtractValue(ReadOnlySpan<char> line, int separatorIndex) {
|
|
return line[(separatorIndex + 1)..].Trim().ToString();
|
|
}
|
|
|
|
private sealed class JavaRuntimeBuilder {
|
|
public string? MainVersion { get; set; } = null;
|
|
public string? FullVersion { get; set; } = null;
|
|
public string? Vendor { get; set; } = null;
|
|
|
|
public JavaRuntime? TryBuild(Dictionary<string, int> duplicateDisplayNames) {
|
|
if (MainVersion == null || FullVersion == null || Vendor == null) {
|
|
return null;
|
|
}
|
|
else {
|
|
string displayName = $"{FullVersion} ({Vendor})";
|
|
|
|
if (duplicateDisplayNames.TryGetValue(displayName, out int usedCount)) {
|
|
++usedCount;
|
|
displayName += " (" + usedCount + ")";
|
|
duplicateDisplayNames[displayName] = usedCount;
|
|
}
|
|
else {
|
|
duplicateDisplayNames[displayName] = 1;
|
|
}
|
|
|
|
return new JavaRuntime(MainVersion, FullVersion, displayName);
|
|
}
|
|
}
|
|
}
|
|
}
|