1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2025-08-16 03:31:43 +02:00
Files
.config
.run
.workdir
Agent
Phantom.Agent
Phantom.Agent.Minecraft
Command
Instance
Java
JavaPropertiesFileEditor.cs
JavaRuntimeDiscovery.cs
JavaRuntimeExecutable.cs
JavaRuntimeRepository.cs
JvmArgumentBuilder.cs
JvmProperties.cs
Launcher
Properties
Server
Phantom.Agent.Minecraft.csproj
Phantom.Agent.Rpc
Phantom.Agent.Services
Common
Controller
Docker
Utils
Web
.dockerignore
.gitattributes
.gitignore
AddMigration.bat
AddMigration.sh
Directory.Build.props
Directory.Build.targets
Dockerfile
LICENSE
Packages.props
PhantomPanel.sln
README.md
global.json

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);
}
}
}
}