Initial commit

This commit is contained in:
2026-07-13 09:25:40 +07:00
parent c08ff54676
commit bccfb156d7
1938 changed files with 641646 additions and 0 deletions

View File

@@ -0,0 +1,375 @@
using RobotNet10.CustomConfiguration.Events;
using RobotNet10.CustomConfiguration.Helpers;
using RobotNet10.CustomConfiguration.Models;
using RobotNet10.CustomConfiguration.Validators;
using System.Text;
using System.Text.Json;
namespace RobotNet10.CustomConfiguration.Services;
/// <summary>
/// Implementation của các methods trong ConfigService
/// </summary>
public partial class ConfigService
{
public async Task<ConfigFile> CreateConfigAsync(string configType, List<ConfigVariable> variables, string? description = null)
{
// Validate ConfigType
if (string.IsNullOrWhiteSpace(configType))
{
throw new ArgumentException("ConfigType cannot be empty", nameof(configType));
}
// Check if ConfigType already exists
var existing = await GetMetadataByTypeAsync(configType);
if (existing != null)
{
throw new InvalidOperationException($"Config with type '{configType}' already exists");
}
// Create config file
var config = new ConfigFile
{
Id = Guid.NewGuid(),
ConfigType = configType,
Variables = variables,
Description = description,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
FilePath = $"{ConfigPath}/{configType}{ConfigFileExtension}"
};
// Validate config
var validationResult = ConfigValidator.ValidateConfig(config);
if (!validationResult.IsValid)
{
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
}
// Save config file (includes metadata)
await SaveConfigFileAsync(config);
// Trigger event
OnConfigChanged(config, ConfigChangeType.Created);
return config;
}
public async Task<ConfigFile?> GetConfigByIdAsync(Guid id)
{
// Load all configs and find by ID
var allMetadata = await LoadAllMetadataAsync();
var metadata = allMetadata.FirstOrDefault(m => m.Id == id);
if (metadata == null)
{
return null;
}
return await LoadConfigFileAsync(metadata.ConfigType);
}
public async Task<ConfigFile?> GetConfigByTypeAsync(string configType)
{
return await LoadConfigFileAsync(configType);
}
public async Task<List<ConfigFileMetadata>> GetAllConfigsAsync()
{
var metadata = await LoadAllMetadataAsync();
return [.. metadata.OrderBy(m => m.ConfigType)];
}
public async Task<List<ConfigFileMetadata>> SearchConfigsAsync(string? searchText)
{
var allMetadata = await GetAllConfigsAsync();
if (string.IsNullOrWhiteSpace(searchText))
{
return allMetadata;
}
var searchLower = searchText.ToLower();
return [.. allMetadata.Where(m =>
m.ConfigType.ToLower().Contains(searchLower) ||
(m.Description != null && m.Description.ToLower().Contains(searchLower))
)];
}
public async Task<ConfigFile> UpdateConfigAsync(Guid id, List<ConfigVariable>? variables = null, string? description = null)
{
var config = await GetConfigByIdAsync(id) ?? throw new KeyNotFoundException($"Config with ID '{id}' not found");
// Update variables if provided
if (variables != null)
{
config.Variables = variables;
}
// Update description if provided
if (description != null)
{
config.Description = description;
}
config.UpdatedAt = DateTime.UtcNow;
// Validate config
var validationResult = ConfigValidator.ValidateConfig(config);
if (!validationResult.IsValid)
{
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
}
// Save config file (includes metadata)
await SaveConfigFileAsync(config);
// Trigger event
OnConfigChanged(config, ConfigChangeType.Updated);
return config;
}
public async Task<bool> DeleteConfigAsync(Guid id)
{
// Find config by ID
var allMetadata = await LoadAllMetadataAsync();
var metadata = allMetadata.FirstOrDefault(m => m.Id == id);
if (metadata == null)
{
return false;
}
// Delete config file
// objectName should be {configType}.config so StorageManager adds .json to make {configType}.config.json
var objectName = $"{metadata.ConfigType}.config";
var exists = await _storageManager.ExistsAsync(ConfigPath, objectName, CancellationToken.None);
if (exists)
{
await _storageManager.DeleteAsync(ConfigPath, objectName, CancellationToken.None);
// Trigger event
var deletedConfig = new ConfigFile
{
Id = metadata.Id,
ConfigType = metadata.ConfigType,
CreatedAt = metadata.CreatedAt,
UpdatedAt = metadata.UpdatedAt,
Description = metadata.Description
};
OnConfigChanged(deletedConfig, ConfigChangeType.Deleted);
}
return true;
}
public async Task<bool> ConfigTypeExistsAsync(string configType)
{
var metadata = await GetMetadataByTypeAsync(configType);
return metadata != null;
}
public async Task<ConfigFile> ImportConfigFromJsonAsync(Stream jsonStream, string configType)
{
return await ImportConfigFromJsonAsync(jsonStream, configType, null);
}
public async Task<ConfigFile> ImportConfigFromJsonAsync(Stream jsonStream, string configType, string? description)
{
// Validate ConfigType
if (string.IsNullOrWhiteSpace(configType))
{
throw new ArgumentException("ConfigType cannot be empty", nameof(configType));
}
// Check if ConfigType already exists
var existing = await GetMetadataByTypeAsync(configType);
if (existing != null)
{
throw new InvalidOperationException($"Config with type '{configType}' already exists");
}
// Try to parse as full config file (new format with metadata)
// Reset stream position first
jsonStream.Position = 0;
string? fileDescription = null;
List<ConfigVariable> variables;
try
{
// Try to parse as ConfigFile (new format)
var configFile = JsonConfigParser.ParseConfigFile(jsonStream);
// If successful, use description from file
fileDescription = configFile.Description;
variables = configFile.Variables;
}
catch (Exception ex) when (ex is JsonException or ArgumentException)
{
// If parsing as ConfigFile fails, try old format (array of variables)
jsonStream.Position = 0;
variables = JsonConfigParser.ParseVariables(jsonStream);
}
// Use description from parameter if provided, otherwise use description from file
var finalDescription = !string.IsNullOrWhiteSpace(description) ? description : fileDescription;
// Create config with description (from parameter or file)
var config = await CreateConfigAsync(configType, variables, finalDescription);
// Note: CreateConfigAsync already triggers Created event, so no need to trigger again
return config;
}
public async Task<Stream> ExportConfigToJsonAsync(Guid id)
{
var config = await GetConfigByIdAsync(id);
return config == null ? throw new KeyNotFoundException($"Config with ID '{id}' not found") : await ExportConfigToJsonAsync(config);
}
public Task<Stream> ExportConfigToJsonAsync(ConfigFile config)
{
// Export với format mới (metadata + variables)
var json = JsonConfigParser.SerializeConfigFile(config);
var bytes = Encoding.UTF8.GetBytes(json);
Stream stream = new MemoryStream(bytes)
{
Position = 0 // Ensure stream is at the beginning
};
return Task.FromResult(stream);
}
public async Task<ConfigFile> UpdateVariableAsync(Guid configId, string variableName, object? value)
{
var config = await GetConfigByIdAsync(configId) ??
throw new KeyNotFoundException($"Config with ID '{configId}' not found");
var variable = config.Variables.FirstOrDefault(v => v.Name.Equals(variableName, StringComparison.OrdinalIgnoreCase)) ??
throw new KeyNotFoundException($"Variable '{variableName}' not found in config");
variable.Value = value;
config.UpdatedAt = DateTime.UtcNow;
// Validate
var validationResult = ConfigValidator.ValidateConfig(config);
if (!validationResult.IsValid)
{
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
}
// Save config file (includes metadata)
await SaveConfigFileAsync(config);
// Trigger event
OnConfigChanged(config, ConfigChangeType.VariableUpdated, variableName);
return config;
}
public async Task<ConfigFile> AddVariableAsync(Guid configId, ConfigVariable variable)
{
var config = await GetConfigByIdAsync(configId) ?? throw new KeyNotFoundException($"Config with ID '{configId}' not found");
// Check if variable name already exists
if (config.Variables.Any(v => v.Name.Equals(variable.Name, StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException($"Variable '{variable.Name}' already exists in config");
}
config.Variables.Add(variable);
config.UpdatedAt = DateTime.UtcNow;
// Validate
var validationResult = ConfigValidator.ValidateConfig(config);
if (!validationResult.IsValid)
{
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
}
// Save config file (includes metadata)
await SaveConfigFileAsync(config);
// Trigger event
OnConfigChanged(config, ConfigChangeType.VariableAdded, variable.Name);
return config;
}
public async Task<ConfigFile> RemoveVariableAsync(Guid configId, string variableName)
{
var config = await GetConfigByIdAsync(configId) ??
throw new KeyNotFoundException($"Config with ID '{configId}' not found");
var variable = config.Variables.FirstOrDefault(v => v.Name.Equals(variableName, StringComparison.OrdinalIgnoreCase)) ??
throw new KeyNotFoundException($"Variable '{variableName}' not found in config");
config.Variables.Remove(variable);
config.UpdatedAt = DateTime.UtcNow;
// Validate
var validationResult = ConfigValidator.ValidateConfig(config);
if (!validationResult.IsValid)
{
throw new ArgumentException($"Invalid config: {string.Join("; ", validationResult.Errors)}");
}
// Save config file (includes metadata)
await SaveConfigFileAsync(config);
// Trigger event
OnConfigChanged(config, ConfigChangeType.VariableRemoved, variableName);
return config;
}
// ==========================================
// PRIVATE HELPER METHODS
// ==========================================
/// <summary>
/// Load config file từ StorageManager (format mới với metadata)
/// </summary>
private async Task<ConfigFile?> LoadConfigFileAsync(string configType)
{
try
{
// objectName should be {configType}.config so StorageManager finds {configType}.config.json
var objectName = $"{configType}.config";
var exists = await _storageManager.ExistsAsync(ConfigPath, objectName, CancellationToken.None);
if (!exists)
{
return null;
}
// Get file stream from StorageManager (works with both local and remote storage)
using var stream = await _storageManager.GetFileAsync(ConfigPath, objectName, CancellationToken.None);
// Parse config file directly from stream (format mới với metadata)
return JsonConfigParser.ParseConfigFile(stream);
}
catch (JsonException)
{
// Config file corrupted/invalid format - treat as not found
return null;
}
}
/// <summary>
/// Save config file vào StorageManager (format mới với metadata)
/// </summary>
private async Task SaveConfigFileAsync(ConfigFile config)
{
// Serialize với format mới (metadata + variables)
var json = JsonConfigParser.SerializeConfigFile(config);
var bytes = Encoding.UTF8.GetBytes(json);
// objectName should be {configType}.config so StorageManager adds .json to make {configType}.config.json
var objectName = $"{config.ConfigType}.config";
using var stream = new MemoryStream(bytes);
await _storageManager.UploadAsync(
ConfigPath,
objectName,
stream,
stream.Length,
"application/json",
CancellationToken.None
);
}
}