282 lines
9.0 KiB
C#
282 lines
9.0 KiB
C#
using System.Diagnostics;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using Meilisearch;
|
|
using meilisearch.NET.Configurations;
|
|
using meilisearch.NET.Enums;
|
|
using meilisearch.NET.Extensions;
|
|
using meilisearch.NET.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using Index = Meilisearch.Index;
|
|
|
|
namespace meilisearch.NET;
|
|
|
|
|
|
public class MeiliSearchService:IMeiliSearchService
|
|
{
|
|
|
|
public MeilisearchClient Sdk { get; set; }
|
|
public MeiliSearchStatus Status { get; set; }
|
|
|
|
private readonly MeiliSearchConfiguration _meiliSearchConfiguration;
|
|
private readonly ProcessStartInfo _processStartInfo;
|
|
private readonly ILogger<MeiliSearchService> _logger;
|
|
private readonly HttpClient _sdkHttpClient;
|
|
|
|
private Timer _healthCheckTimer;
|
|
private Process? _process;
|
|
|
|
public MeiliSearchService(HttpClient httpClient, ILogger<MeiliSearchService> logger, MeiliSearchConfiguration meiliSearchConfiguration)
|
|
{
|
|
_logger = logger;
|
|
_meiliSearchConfiguration = meiliSearchConfiguration;
|
|
var binaryName = GetBinaryName();
|
|
MakeExecutable(Path.Combine(AppContext.BaseDirectory, binaryName));
|
|
string apiKey = "";
|
|
if (_meiliSearchConfiguration.EnableCustomApiKey)
|
|
apiKey = _meiliSearchConfiguration.ApiKey;
|
|
else
|
|
apiKey = ApiKeyGenerator.GenerateApiKey();
|
|
|
|
_sdkHttpClient = httpClient;
|
|
_sdkHttpClient.BaseAddress = new Uri("http://localhost:"+meiliSearchConfiguration.MeiliPort);
|
|
Sdk = new MeilisearchClient(httpClient, apiKey);
|
|
var path = Path.Combine(AppContext.BaseDirectory, binaryName);
|
|
var args = "--http-addr 127.0.0.1:" + _meiliSearchConfiguration.MeiliPort
|
|
+ " --master-key "+apiKey
|
|
+ " --env "+(_meiliSearchConfiguration.UiEnabled ? "development" : "production")
|
|
+" --db-path "+GetDataLocation();
|
|
_processStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = path,
|
|
Arguments = args,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
};
|
|
Status = MeiliSearchStatus.Stopped;
|
|
_logger?.LogInformation($"Meilisearch service initialized with the UI {(_meiliSearchConfiguration.UiEnabled ? "Enabled" : "Disabled)")} with database located at {GetDataLocation()}.");
|
|
}
|
|
|
|
|
|
|
|
private static void MakeExecutable(string filePath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(filePath))
|
|
{
|
|
throw new ArgumentException("File path cannot be null or empty.", nameof(filePath));
|
|
}
|
|
|
|
// Check if the file exists
|
|
if (!File.Exists(filePath))
|
|
{
|
|
throw new FileNotFoundException($"The file '{filePath}' does not exist.", filePath);
|
|
}
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
{
|
|
// For Linux and macOS, use chmod +x
|
|
ExecuteCommand($"chmod +x \"{filePath}\"");
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
throw new PlatformNotSupportedException("This platform is not supported.");
|
|
}
|
|
|
|
Console.WriteLine($"File '{filePath}' has been made executable.");
|
|
}
|
|
private static void ExecuteCommand(string command)
|
|
{
|
|
var processStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "/bin/bash", // Use bash for Linux and macOS
|
|
Arguments = $"-c \"{command}\"",
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
using (var process = Process.Start(processStartInfo))
|
|
{
|
|
process.WaitForExit();
|
|
|
|
// Optionally handle output and errors
|
|
string output = process.StandardOutput.ReadToEnd();
|
|
string error = process.StandardError.ReadToEnd();
|
|
|
|
if (process.ExitCode != 0)
|
|
{
|
|
throw new InvalidOperationException($"Error executing command: {error}");
|
|
}
|
|
}
|
|
}
|
|
private static string GetDataLocation()
|
|
{
|
|
return Path.Combine(AppContext.BaseDirectory, "data");
|
|
string appDataDirectory;
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
appDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MeiliSearchEmbedded");
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
{
|
|
appDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support", "MeiliSearchEmbedded");
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
{
|
|
appDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".config", "MeiliSearchEmbedded");
|
|
}
|
|
else
|
|
{
|
|
throw new PlatformNotSupportedException("Unsupported OS.");
|
|
}
|
|
return appDataDirectory;
|
|
}
|
|
private static string GetBinaryName()
|
|
{
|
|
string os = RuntimeInformation.OSDescription.ToLowerInvariant();
|
|
string architecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
|
|
string platform;
|
|
|
|
if (os.Contains("linux"))
|
|
{
|
|
platform = "meilisearch-linux-" + (architecture.Contains("arm") ? "arm" : "x64");
|
|
}
|
|
else if (os.Contains("macos") || os.Contains("darwin"))
|
|
{
|
|
platform = "meilisearch-macos-" + (architecture.Contains("arm") ? "arm" : "x64");
|
|
}
|
|
else if (os.Contains("windows"))
|
|
{
|
|
platform = "meilisearch-windows.exe";
|
|
}
|
|
else
|
|
{
|
|
throw new PlatformNotSupportedException("Operating system not supported.");
|
|
}
|
|
|
|
return platform;
|
|
}
|
|
private bool MeilisSearchReachable()
|
|
{
|
|
string host = "127.0.0.1"; // localhost
|
|
int port = _meiliSearchConfiguration.MeiliPort;
|
|
try
|
|
{
|
|
using (TcpClient client = new TcpClient())
|
|
{
|
|
client.Connect(host, port);
|
|
return true;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool HealthCheck(object? state)
|
|
{
|
|
if (_process == null)
|
|
throw new Exception("Meilisearch process is not running.");
|
|
var responding = _process.Responding;
|
|
var exited = _process.HasExited || _process.ExitCode == 0;
|
|
var reachable = MeilisSearchReachable();
|
|
return responding && exited && reachable;
|
|
}
|
|
|
|
public void RefreshApiKey()
|
|
{
|
|
_logger.LogInformation("Refreshing API key.");
|
|
string apiKey = "";
|
|
if (_meiliSearchConfiguration.EnableCustomApiKey)
|
|
apiKey = _meiliSearchConfiguration.ApiKey;
|
|
else
|
|
apiKey = ApiKeyGenerator.GenerateApiKey();
|
|
Sdk = new MeilisearchClient(_sdkHttpClient, apiKey);
|
|
_logger.LogInformation("New API key generated.");
|
|
Restart();
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
_logger.LogInformation("Starting MeiliSearch process.");
|
|
Status = MeiliSearchStatus.Starting;
|
|
_process = new Process { StartInfo = _processStartInfo };
|
|
|
|
try
|
|
{
|
|
_process.Start();
|
|
Status = MeiliSearchStatus.Running;
|
|
_logger.LogInformation("MeiliSearch process started successfully.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to start MeiliSearch process.");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public void Restart()
|
|
{
|
|
_logger.LogInformation("Restarting MeiliSearch process.");
|
|
Stop();
|
|
Start();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (_process == null)
|
|
{
|
|
_logger.LogWarning("Attempted to stop MeiliSearch process, but it is not running.");
|
|
throw new Exception("Meilisearch process is not running.");
|
|
}
|
|
|
|
_logger.LogInformation("Stopping MeiliSearch process.");
|
|
Status = MeiliSearchStatus.Stopping;
|
|
|
|
try
|
|
{
|
|
_process.Kill();
|
|
Status = MeiliSearchStatus.Stopped;
|
|
_logger.LogInformation("MeiliSearch process stopped successfully.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to stop MeiliSearch process.");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public List<string> ListIndexs()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public Index CreateIndex()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void LoadIndex(string indexId)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void UnloadIndex(string indexId)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void DeleteIndex(string indexId)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
} |