feat: exceptions
Some checks failed
build-packages / meilisearch-dotnet-packages (push) Has been cancelled

This commit is contained in:
Damien 2025-03-01 14:09:25 -05:00
parent ab8c4398f8
commit 430e1d8617
11 changed files with 100 additions and 87 deletions

View File

@ -1,12 +0,0 @@
namespace meilisearch.NET.Exceptions;
public class DocumentBatchException : DocumentManagementException
{
public int BatchSize { get; }
public DocumentBatchException(int batchSize, Exception innerException)
: base($"Failed to process batch of {batchSize} documents", innerException)
{
BatchSize = batchSize;
}
}

View File

@ -1,10 +0,0 @@
namespace meilisearch.NET.Exceptions;
/// <summary>
/// Exception thrown when there are issues with document management
/// </summary>
public class DocumentManagementException : MeiliSearchException
{
public DocumentManagementException(string message) : base(message) { }
public DocumentManagementException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@ -1,7 +0,0 @@
namespace meilisearch.NET.Exceptions;
public class DocumentSyncException : DocumentManagementException
{
public DocumentSyncException(string message, Exception innerException)
: base($"Failed to sync documents: {message}", innerException) { }
}

View File

@ -1,7 +0,0 @@
namespace meilisearch.NET.Exceptions;
public class DocumentValidationException : DocumentManagementException
{
public DocumentValidationException(string message)
: base($"Document validation failed: {message}") { }
}

View File

@ -0,0 +1,12 @@
namespace meilisearch.NET.Exceptions;
public class IndexAlreadyExistsException : IndexManagementException
{
public string IndexName { get; }
public IndexAlreadyExistsException(string indexName)
: base($"Index '{indexName}' already exists")
{
IndexName = indexName;
}
}

View File

@ -1,7 +1,7 @@
namespace meilisearch.NET.Exceptions; namespace meilisearch.NET.Exceptions;
public class IndexLimitExceededException : IndexManagementException public class IndexLimitReachedException : IndexManagementException
{ {
public IndexLimitExceededException() public IndexLimitReachedException()
: base("Maximum number of indexes (1000) has been reached") { } : base("Maximum number of indexes (1000) has been reached") { }
} }

View File

@ -2,5 +2,5 @@
public class ProcessStartException : ProcessManagementException public class ProcessStartException : ProcessManagementException
{ {
public ProcessStartException(string message) : base($"Failed to start Meilisearch process: {message}") { } public ProcessStartException(Exception innerException, string message) : base($"Failed to start Meilisearch process: {message}", innerException) { }
} }

View File

@ -2,5 +2,5 @@
public class ProcessStopException : ProcessManagementException public class ProcessStopException : ProcessManagementException
{ {
public ProcessStopException(string message) : base($"Failed to stop Meilisearch process: {message}") { } public ProcessStopException(Exception innerException, string message) : base($"Failed to stop Meilisearch process: {message}", innerException) { }
} }

View File

@ -1,7 +1,9 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using Meilisearch; using Meilisearch;
using meilisearch.NET.Exceptions;
using meilisearch.NET.Interfaces; using meilisearch.NET.Interfaces;
using meilisearch.NET.Services.ProcessManagement;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace meilisearch.NET.Services.DocumentManagement; namespace meilisearch.NET.Services.DocumentManagement;
@ -9,13 +11,15 @@ namespace meilisearch.NET.Services.DocumentManagement;
public class DocumentManager:IDocumentManager public class DocumentManager:IDocumentManager
{ {
private readonly ILogger<DocumentManager> _logger; private readonly ILogger<DocumentManager> _logger;
private readonly MeiliSearchProcessManager _meiliSearchProcessManager;
private readonly MeilisearchClient _client; private readonly MeilisearchClient _client;
private const int THRESHOLD = 100; private const int THRESHOLD = 100;
private ObservableCollection<KeyValuePair<string,IDocument>> _documentCollection; private ObservableCollection<KeyValuePair<string,IDocument>> _documentCollection;
public DocumentManager(MeilisearchClient client, ILogger<DocumentManager> logger) public DocumentManager(MeilisearchClient client, ILogger<DocumentManager> logger, MeiliSearchProcessManager meiliSearchProcessManager)
{ {
_meiliSearchProcessManager = meiliSearchProcessManager;
_logger = logger; _logger = logger;
_client = client; _client = client;
_documentCollection = new ObservableCollection<KeyValuePair<string,IDocument>>(); _documentCollection = new ObservableCollection<KeyValuePair<string,IDocument>>();
@ -24,6 +28,9 @@ public class DocumentManager:IDocumentManager
public async Task AddDocumentAsync(string repositoryId, IDocument document, bool autoCommit = false) public async Task AddDocumentAsync(string repositoryId, IDocument document, bool autoCommit = false)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
_logger.LogTrace($"Adding document '{document.Id}' to repository '{repositoryId}'..."); _logger.LogTrace($"Adding document '{document.Id}' to repository '{repositoryId}'...");
_documentCollection.Add(new KeyValuePair<string, IDocument>(repositoryId, document)); _documentCollection.Add(new KeyValuePair<string, IDocument>(repositoryId, document));
_logger.LogInformation($"Document {document.Id} added to collection."); _logger.LogInformation($"Document {document.Id} added to collection.");
@ -34,6 +41,9 @@ public class DocumentManager:IDocumentManager
} }
public void AddDocument(string repositoryId, IDocument document, bool autoCommit = false) public void AddDocument(string repositoryId, IDocument document, bool autoCommit = false)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
_logger.LogTrace($"Adding document '{document.Id}' to repository '{repositoryId}'..."); _logger.LogTrace($"Adding document '{document.Id}' to repository '{repositoryId}'...");
_documentCollection.Add(new KeyValuePair<string, IDocument>(repositoryId, document)); _documentCollection.Add(new KeyValuePair<string, IDocument>(repositoryId, document));
_logger.LogInformation($"Document {document.Id} added to collection."); _logger.LogInformation($"Document {document.Id} added to collection.");
@ -44,6 +54,9 @@ public class DocumentManager:IDocumentManager
} }
public void SyncDocumentsToServer() public void SyncDocumentsToServer()
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var grouped = _documentCollection.GroupBy(pair => pair.Key) var grouped = _documentCollection.GroupBy(pair => pair.Key)
.ToDictionary(group => group.Key, group => group.Select(pair => pair.Value).ToList()); .ToDictionary(group => group.Key, group => group.Select(pair => pair.Value).ToList());
foreach (var repository in grouped) foreach (var repository in grouped)
@ -53,9 +66,12 @@ public class DocumentManager:IDocumentManager
_documentCollection.Clear(); _documentCollection.Clear();
var result = RetryAsync(() => repositoryIndex.AddDocumentsAsync(repository.Value, "id")).Result; var result = RetryAsync(() => repositoryIndex.AddDocumentsAsync(repository.Value, "id")).Result;
} }
} }
public async Task SyncDocumentsToServerAsync() public async Task SyncDocumentsToServerAsync()
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var grouped = _documentCollection.GroupBy(pair => pair.Key) var grouped = _documentCollection.GroupBy(pair => pair.Key)
.ToDictionary(group => group.Key, group => group.Select(pair => pair.Value).ToList()); .ToDictionary(group => group.Key, group => group.Select(pair => pair.Value).ToList());
foreach (var repository in grouped) foreach (var repository in grouped)

View File

@ -1,6 +1,7 @@
using System.IO.Compression; using System.IO.Compression;
using System.Reflection; using System.Reflection;
using Meilisearch; using Meilisearch;
using meilisearch.NET.Exceptions;
using meilisearch.NET.Interfaces; using meilisearch.NET.Interfaces;
using meilisearch.NET.Services.ProcessManagement; using meilisearch.NET.Services.ProcessManagement;
using Meilisearch.QueryParameters; using Meilisearch.QueryParameters;
@ -16,35 +17,36 @@ public class IndexManager:IIndexManager
private readonly string _indexBasePath = Path.Combine(AppContext.BaseDirectory, "db", "indexes" ); private readonly string _indexBasePath = Path.Combine(AppContext.BaseDirectory, "db", "indexes" );
private readonly ILogger<IndexManager> _logger; private readonly ILogger<IndexManager> _logger;
private readonly MeilisearchClient _client; private readonly MeilisearchClient _client;
private readonly MeiliSearchProcessManager _processManager; private readonly MeiliSearchProcessManager _meiliSearchProcessManager;
public IndexManager(ILogger<IndexManager> logger, MeilisearchClient client, MeiliSearchProcessManager processManager) public IndexManager(ILogger<IndexManager> logger, MeilisearchClient client, MeiliSearchProcessManager meiliSearchProcessManager)
{ {
_processManager = processManager; _meiliSearchProcessManager = meiliSearchProcessManager;
_client = client; _client = client;
_logger = logger; _logger = logger;
} }
public Task<List<string>> GetAllIndexes() public async Task<List<string>> GetAllIndexes()
{ {
throw new NotImplementedException(); _logger.LogTrace("Fetching all indexes from Meilisearch server created with the SDK...");
var result = _client.GetAllIndexesAsync().Result.Results.Select(x => x.Uid).Where(x=>x!="index_bindings").ToList();
_logger.LogInformation($"Fetched {result.Count} indexes from Meilisearch server.");
return result;
} }
public void CreateIndex<T>(string indexName) where T : IDocument public void CreateIndex<T>(string indexName) where T : IDocument
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var indexes = GetAllIndexes().Result; var indexes = GetAllIndexes().Result;
if(indexes.Count>=1000) if(indexes.Count>=1000)
{ throw new IndexLimitReachedException();
_logger.LogWarning("Maximum number of indexes reached, cannot create new index.");
return;
}
if (indexes.Any(x => x == indexName)) if (indexes.Any(x => x == indexName))
{ throw new IndexAlreadyExistsException(indexName);
_logger.LogWarning($"Index {indexName} already exists, skipping creation of index.");
return;
}
var foldersBefore = Directory.GetDirectories(_indexBasePath); var foldersBefore = Directory.GetDirectories(_indexBasePath);
_logger.LogTrace($"Creating index '{indexName}'..."); _logger.LogTrace($"Creating index '{indexName}'...");
@ -74,18 +76,16 @@ public class IndexManager:IIndexManager
} }
public async Task CreateIndexAsync<T>(string indexName) where T : IDocument public async Task CreateIndexAsync<T>(string indexName) where T : IDocument
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var indexes = await GetAllIndexes(); var indexes = await GetAllIndexes();
if(indexes.Count>=1000) if(indexes.Count>=1000)
{ throw new IndexLimitReachedException();
_logger.LogWarning("Maximum number of indexes reached, cannot create new index.");
return;
}
if (indexes.Any(x => x == indexName)) if (indexes.Any(x => x == indexName))
{ throw new IndexAlreadyExistsException(indexName);
_logger.LogWarning($"Index {indexName} already exists, skipping creation of index.");
return;
}
var foldersBefore = Directory.GetDirectories(_indexBasePath); var foldersBefore = Directory.GetDirectories(_indexBasePath);
_logger.LogTrace($"Creating index '{indexName}'..."); _logger.LogTrace($"Creating index '{indexName}'...");
@ -114,11 +114,13 @@ public class IndexManager:IIndexManager
} }
public void DeleteIndex(string indexName) public void DeleteIndex(string indexName)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var indexes = _client.GetAllIndexesAsync().Result; var indexes = _client.GetAllIndexesAsync().Result;
if (indexes.Results.Any(x => x.Uid == indexName)==false) if (indexes.Results.Any(x => x.Uid == indexName)==false)
{ {
_logger.LogWarning($"Index '{indexName}' does not exist, skipping deletion of index."); throw new IndexNotFoundException(indexName);
return;
} }
_logger.LogTrace($"Deleting index '{indexName}'..."); _logger.LogTrace($"Deleting index '{indexName}'...");
_client.DeleteIndexAsync(indexName).Wait(); _client.DeleteIndexAsync(indexName).Wait();
@ -127,11 +129,13 @@ public class IndexManager:IIndexManager
} }
public async Task DeleteIndexAsync(string indexName) public async Task DeleteIndexAsync(string indexName)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var indexes = _client.GetAllIndexesAsync().Result; var indexes = _client.GetAllIndexesAsync().Result;
if (indexes.Results.Any(x => x.Uid == indexName)==false) if (indexes.Results.Any(x => x.Uid == indexName)==false)
{ {
_logger.LogWarning($"Index '{indexName}' does not exist, skipping deletion of index."); throw new IndexNotFoundException(indexName);
return;
} }
_logger.LogTrace($"Deleting index '{indexName}'..."); _logger.LogTrace($"Deleting index '{indexName}'...");
await _client.DeleteIndexAsync(indexName); await _client.DeleteIndexAsync(indexName);
@ -140,6 +144,9 @@ public class IndexManager:IIndexManager
} }
public void SetIndexEnabled(string indexName, bool enabled) public void SetIndexEnabled(string indexName, bool enabled)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
_logger.LogTrace($"Updating index '{indexName}' status to {enabled}..."); _logger.LogTrace($"Updating index '{indexName}' status to {enabled}...");
if(enabled) if(enabled)
{ {
@ -153,6 +160,9 @@ public class IndexManager:IIndexManager
} }
public async Task SetIndexEnabledAsync(string indexName, bool enabled) public async Task SetIndexEnabledAsync(string indexName, bool enabled)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
_logger.LogTrace($"Updating index '{indexName}' status to {enabled}..."); _logger.LogTrace($"Updating index '{indexName}' status to {enabled}...");
if(enabled) if(enabled)
{ {
@ -166,6 +176,9 @@ public class IndexManager:IIndexManager
} }
public long GetIndexStorageUsage(string indexName, bool useCompressedSize = true) public long GetIndexStorageUsage(string indexName, bool useCompressedSize = true)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var doc = _client.GetIndexAsync("index_bindings").Result.GetDocumentAsync<Models.Index>(indexName).Result; var doc = _client.GetIndexAsync("index_bindings").Result.GetDocumentAsync<Models.Index>(indexName).Result;
if (doc.IsCompressed) if (doc.IsCompressed)
@ -176,21 +189,22 @@ public class IndexManager:IIndexManager
var indexPath = GetIndexFilePath(indexName).Result+".zip"; var indexPath = GetIndexFilePath(indexName).Result+".zip";
if (!File.Exists(indexPath)) if (!File.Exists(indexPath))
{ {
_logger.LogWarning($"Compressed index not found at: {indexPath}"); throw new FileNotFoundException($"Compressed index not found at: {indexPath}");
return 0;
} }
return new FileInfo(indexPath).Length; return new FileInfo(indexPath).Length;
} }
var path = Path.Combine(_indexBasePath, doc.FolderId); var path = Path.Combine(_indexBasePath, doc.FolderId);
if (!Directory.Exists(path)) if (!Directory.Exists(path))
{ {
_logger.LogWarning($"Index directory not found at: {path}"); throw new DirectoryNotFoundException($"Index directory not found at: {path}");
return 0;
} }
return new DirectoryInfo(path).GetFiles().Sum(f => f.Length); return new DirectoryInfo(path).GetFiles().Sum(f => f.Length);
} }
public long GetTotalStorageUsage(bool useCompressedSize = true) public long GetTotalStorageUsage(bool useCompressedSize = true)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var result = _client.GetIndexAsync("index_bindings").Result.GetDocumentsAsync<Models.Index>(new DocumentsQuery(){Limit = 1000}).Result; var result = _client.GetIndexAsync("index_bindings").Result.GetDocumentsAsync<Models.Index>(new DocumentsQuery(){Limit = 1000}).Result;
var total = 0L; var total = 0L;
foreach (var index in result.Results) foreach (var index in result.Results)
@ -208,6 +222,9 @@ public class IndexManager:IIndexManager
} }
public async Task<long> GetIndexStorageUsageAsync(string indexName, bool useCompressedSize = true) public async Task<long> GetIndexStorageUsageAsync(string indexName, bool useCompressedSize = true)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var doc = _client.GetIndexAsync("index_bindings").Result.GetDocumentAsync<Models.Index>(indexName).Result; var doc = _client.GetIndexAsync("index_bindings").Result.GetDocumentAsync<Models.Index>(indexName).Result;
if (doc.IsCompressed) if (doc.IsCompressed)
@ -218,21 +235,22 @@ public class IndexManager:IIndexManager
var indexPath = await GetIndexFilePath(indexName)+".zip"; var indexPath = await GetIndexFilePath(indexName)+".zip";
if (!File.Exists(indexPath)) if (!File.Exists(indexPath))
{ {
_logger.LogWarning($"Compressed index not found at: {indexPath}"); throw new FileNotFoundException($"Compressed index not found at: {indexPath}");
return 0;
} }
return new FileInfo(indexPath).Length; return new FileInfo(indexPath).Length;
} }
var path = Path.Combine(_indexBasePath, doc.FolderId); var path = Path.Combine(_indexBasePath, doc.FolderId);
if (!Directory.Exists(path)) if (!Directory.Exists(path))
{ {
_logger.LogWarning($"Index directory not found at: {path}"); throw new DirectoryNotFoundException($"Index directory not found at: {path}");
return 0;
} }
return new DirectoryInfo(path).GetFiles().Sum(f => f.Length); return new DirectoryInfo(path).GetFiles().Sum(f => f.Length);
} }
public async Task<long> GetTotalStorageUsageAsync(bool useCompressedSize = true) public async Task<long> GetTotalStorageUsageAsync(bool useCompressedSize = true)
{ {
if (!_meiliSearchProcessManager.IsProcessRunning())
throw new ProcessNotRunningException();
var result = _client.GetIndexAsync("index_bindings").Result.GetDocumentsAsync<Models.Index>(new DocumentsQuery(){Limit = 1000}).Result; var result = _client.GetIndexAsync("index_bindings").Result.GetDocumentsAsync<Models.Index>(new DocumentsQuery(){Limit = 1000}).Result;
var total = 0L; var total = 0L;
foreach (var index in result.Results) foreach (var index in result.Results)
@ -278,8 +296,7 @@ public class IndexManager:IIndexManager
var indexPath = await GetIndexFilePath(indexName); var indexPath = await GetIndexFilePath(indexName);
if (!Directory.Exists(indexPath)) if (!Directory.Exists(indexPath))
{ {
_logger.LogWarning($"Index directory not found at: {indexPath}"); throw new DirectoryNotFoundException($"Index directory not found at: {indexPath}");
return;
} }
var compressedPath = indexPath + ".zip"; var compressedPath = indexPath + ".zip";
@ -322,10 +339,9 @@ public class IndexManager:IIndexManager
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError($"Failed to compress index '{indexName}': {ex.Message}"); throw new IndexCompressionException(indexName, "compress", ex);
throw;
} }
_processManager.StopProcess(); _meiliSearchProcessManager.StopProcess();
} }
private async Task DecompressIndex(string indexName) private async Task DecompressIndex(string indexName)
@ -336,8 +352,7 @@ public class IndexManager:IIndexManager
if (!File.Exists(compressedPath)) if (!File.Exists(compressedPath))
{ {
_logger.LogWarning($"Compressed index not found at: {compressedPath}"); throw new FileNotFoundException($"Compressed index not found at: {compressedPath}");
return;
} }
_logger.LogTrace($"Decompressing index '{indexName}' to {extractPath}..."); _logger.LogTrace($"Decompressing index '{indexName}' to {extractPath}...");
@ -379,8 +394,7 @@ public class IndexManager:IIndexManager
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError($"Failed to decompress index '{indexName}': {ex.Message}"); throw new IndexCompressionException(indexName, "decompress", ex);
throw;
} }
} }
#endregion #endregion

View File

@ -1,4 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using meilisearch.NET.Exceptions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace meilisearch.NET.Services.ProcessManagement; namespace meilisearch.NET.Services.ProcessManagement;
@ -35,14 +36,20 @@ public abstract class BaseProcessManager : IProcessManager
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError($"Failed to start {GetProcessName()}: {ex.Message}"); throw new ProcessStartException(ex,ex.Message);
throw;
} }
} }
public virtual void StopProcess() public virtual void StopProcess()
{ {
Process?.Kill(); try
{
Process?.Kill();
}
catch (Exception ex)
{
Logger.LogError($"Error stopping {GetProcessName()} process: {ex.Message}", ex);
}
} }
public virtual bool IsProcessRunning() public virtual bool IsProcessRunning()