diff --git a/DevBase.Api/AGENT.md b/DevBase.Api/AGENT.md index fea681f..9c4dbfd 100644 --- a/DevBase.Api/AGENT.md +++ b/DevBase.Api/AGENT.md @@ -1,107 +1,485 @@ -# DevBase.Api Agent Guide +# DevBase.Api - AI Agent Guide + +This guide helps AI agents effectively use the DevBase.Api library for integrating with music streaming services, lyrics providers, and AI platforms. ## Overview -DevBase.Api provides ready-to-use API clients for music streaming services, AI platforms, and lyrics providers. -## Key Classes +DevBase.Api provides ready-to-use API clients for: +- **Music Services**: Deezer, Tidal, Apple Music, NetEase +- **Lyrics Providers**: BeautifulLyrics, Musixmatch, OpenLyricsClient +- **AI Services**: OpenAI, Replicate + +All clients extend `ApiClient` and use DevBase.Net for networking. -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `ApiClient` | `DevBase.Api.Apis` | Base class for all API clients | -| `Deezer` | `DevBase.Api.Apis.Deezer` | Deezer music API | -| `Tidal` | `DevBase.Api.Apis.Tidal` | Tidal music API | -| `AppleMusic` | `DevBase.Api.Apis.AppleMusic` | Apple Music API | -| `NetEase` | `DevBase.Api.Apis.NetEase` | NetEase music API | -| `BeautifulLyrics` | `DevBase.Api.Apis.BeautifulLyrics` | Lyrics provider | +**Target Framework:** .NET 9.0 -## Error Handling Pattern +## Core Concept: ApiClient Base Class -All API clients extend `ApiClient` which provides error handling: +All API clients inherit from `ApiClient`: ```csharp -public class MyApiClient : ApiClient +public class ApiClient +{ + public bool StrictErrorHandling { get; set; } + + protected dynamic Throw(Exception exception); + protected (string, bool) ThrowTuple(Exception exception); +} +``` + +### Error Handling Pattern + +**Key Rule:** Always use `Throw()` or `ThrowTuple()` for error handling in API client methods. + +```csharp +// For single return values +if (response.StatusCode != HttpStatusCode.OK) + return Throw(new MyException(ExceptionType.NotFound)); + +// For tuple return values +if (condition_failed) + return ThrowTuple(new MyException(ExceptionType.Error)); +``` + +**Behavior:** +- `StrictErrorHandling = false` (default): Returns `null` or default value +- `StrictErrorHandling = true`: Throws the exception + +## Usage Patterns for AI Agents + +### Pattern 1: Basic API Call with Error Handling + +```csharp +using DevBase.Api.Apis.Deezer; + +var deezer = new Deezer(); + +// Lenient mode (default) +var track = await deezer.GetSong("123456"); +if (track == null) +{ + Console.WriteLine("Track not found or error occurred"); + return; +} + +// Strict mode +deezer.StrictErrorHandling = true; +try +{ + var track = await deezer.GetSong("123456"); +} +catch (DeezerException ex) +{ + Console.WriteLine($"Error: {ex.ExceptionType}"); +} +``` + +### Pattern 2: Search and Retrieve + +```csharp +// Search for tracks +var results = await deezer.Search("Rick Astley Never Gonna Give You Up"); + +if (results?.data == null || results.data.Length == 0) { - public async Task GetData() + Console.WriteLine("No results found"); + return; +} + +// Get first result +var firstTrack = results.data[0]; +var fullTrack = await deezer.GetSong(firstTrack.id.ToString()); +``` + +### Pattern 3: Authenticated Access (Deezer) + +```csharp +// Initialize with ARL token +var deezer = new Deezer("your-arl-token-here"); + +// Get JWT token +var jwt = await deezer.GetJwtToken(); +if (jwt == null) +{ + Console.WriteLine("Invalid ARL token"); + return; +} + +// Use authenticated endpoints +// Cookies are automatically managed +``` + +### Pattern 4: Device Authentication Flow (Tidal) + +```csharp +using DevBase.Api.Apis.Tidal; + +var tidal = new Tidal(); + +// Step 1: Register device +var deviceAuth = await tidal.RegisterDevice(); +Console.WriteLine($"Visit: {deviceAuth.verificationUriComplete}"); +Console.WriteLine($"Or enter code: {deviceAuth.userCode}"); + +// Step 2: Poll for authorization +JsonTidalAccountAccess access = null; +int maxAttempts = 60; +int attempts = 0; + +while (access == null && attempts < maxAttempts) +{ + await Task.Delay(deviceAuth.interval * 1000); + + tidal.StrictErrorHandling = false; + access = await tidal.GetTokenFrom(deviceAuth.deviceCode); + attempts++; +} + +if (access == null) +{ + Console.WriteLine("Authorization timeout"); + return; +} + +// Step 3: Login with access token +var session = await tidal.Login(access.access_token); +``` + +### Pattern 5: Multi-Provider Lyrics Search + +```csharp +async Task FindBestLyrics(string isrc, string title, string artist, string trackId) +{ + // Priority 1: BeautifulLyrics (best quality, rich-synced) + var bl = new BeautifulLyrics(); + var (blLyrics, isRichSync) = await bl.GetRawLyrics(isrc); + if (blLyrics != null) { - if (errorCondition) - return Throw(new MyException(ExceptionType.Reason)); - - // Normal return - return result; + Console.WriteLine($"Found lyrics on BeautifulLyrics (Rich: {isRichSync})"); + return blLyrics; } - // For tuple return types - public async Task<(string Data, bool Flag)> GetTuple() + // Priority 2: Musixmatch + var mm = new MusixMatch(); + var mmResults = await mm.SearchTrack(title, artist); + if (mmResults != null) { - if (errorCondition) - return ThrowTuple(new MyException(ExceptionType.Reason)); - - return (data, true); + Console.WriteLine("Found lyrics on Musixmatch"); + return mmResults; + } + + // Priority 3: Deezer + var deezer = new Deezer(); + var deezerLyrics = await deezer.GetLyrics(trackId); + if (deezerLyrics != null) + { + Console.WriteLine("Found lyrics on Deezer"); + return deezerLyrics; + } + + // Priority 4: NetEase (for Chinese songs) + var netease = new NetEase(); + var neSearch = await netease.Search($"{artist} {title}"); + if (neSearch?.result?.songs != null && neSearch.result.songs.Length > 0) + { + var neLyrics = await netease.Lyrics(neSearch.result.songs[0].id.ToString()); + if (neLyrics?.lrc?.lyric != null) + { + Console.WriteLine("Found lyrics on NetEase"); + return neLyrics.lrc.lyric; + } } + + return null; } ``` -### Error Handling Modes -- `StrictErrorHandling = true` → Exceptions are thrown -- `StrictErrorHandling = false` → Default values returned (null, empty, false) +### Pattern 6: OpenAI Integration -## Quick Reference +```csharp +using DevBase.Api.Apis.OpenAi; + +var openai = new OpenAi("sk-your-api-key"); + +var messages = new[] +{ + new { role = "system", content = "You are a music expert." }, + new { role = "user", content = "What genre is this song?" } +}; + +var response = await openai.ChatCompletion("gpt-4", messages); +string answer = response.choices[0].message.content; +``` + +### Pattern 7: Batch Processing with Rate Limiting -### Deezer ```csharp -var deezer = new Deezer(arlToken: "optional"); -var results = await deezer.Search("artist name"); -var track = await deezer.GetSong("trackId"); +using DevBase.Async.Task; + +var deezer = new Deezer(); +var trackIds = new[] { "123", "456", "789", /* ... */ }; + +// Limit to 5 concurrent requests +Multitasking tasks = new Multitasking(capacity: 5); + +foreach (var id in trackIds) +{ + tasks.Register(async () => + { + var track = await deezer.GetSong(id); + if (track != null) + { + await ProcessTrackAsync(track); + } + }); +} + +await tasks.WaitAll(); ``` +## API-Specific Guidelines + +### Deezer + +**Key Methods:** +- `Search(query)` - Search tracks, albums, artists +- `GetSong(id)` - Get track details +- `GetLyrics(id)` - Get synchronized lyrics +- `GetJwtToken()` - Requires ARL token +- `GetAccessToken()` - Get unlogged access token + +**Important:** +- ARL token required for authenticated endpoints +- Cookies are automatically managed +- Supports Blowfish decryption for downloads + +### Tidal + +**Key Methods:** +- `RegisterDevice()` - Start device auth flow +- `GetTokenFrom(deviceCode)` - Poll for authorization +- `Login(accessToken)` - Get session +- `Search(query, sessionId)` - Search catalog +- `GetLyrics(trackId, sessionId)` - Get lyrics + +**Important:** +- Device authentication flow required +- Session ID needed for most operations +- Poll interval specified in device auth response + +### Apple Music + +**Key Methods:** +- `SearchCatalog(storefront, query)` - Search catalog +- `GetSong(storefront, id)` - Get song details +- `GetLyrics(storefront, id)` - Get lyrics + +**Important:** +- Requires developer token +- Storefront code required (e.g., "us", "gb") +- Token must be valid JWT + ### NetEase + +**Key Methods:** +- `Search(query)` - Search Chinese music +- `SongDetail(id)` - Get song details +- `Lyrics(id)` - Get LRC and karaoke lyrics + +**Important:** +- Best for Chinese music +- Returns both standard LRC and karaoke lyrics +- No authentication required + +### BeautifulLyrics + +**Key Methods:** +- `GetRawLyrics(isrc)` - Get lyrics by ISRC + +**Important:** +- Returns tuple: `(lyrics, isRichSync)` +- Rich-synced lyrics include word-level timing +- ISRC is International Standard Recording Code + +## Common Mistakes to Avoid + +### ❌ Mistake 1: Not Checking for Null + ```csharp -var netease = new NetEase(); -var results = await netease.Search("keyword"); -var lyrics = await netease.Lyrics("trackId"); +// Wrong +var track = await deezer.GetSong("123"); +Console.WriteLine(track.title); // NullReferenceException! + +// Correct +var track = await deezer.GetSong("123"); +if (track != null) +{ + Console.WriteLine(track.title); +} ``` -### BeautifulLyrics +### ❌ Mistake 2: Using Wrong Error Handling Method + ```csharp -var lyrics = new BeautifulLyrics(); -var (rawLyrics, isRichSync) = await lyrics.GetRawLyrics("isrc"); -var parsedLyrics = await lyrics.GetLyrics("isrc"); +// Wrong - in API client implementation +if (error) + throw new MyException(); // Don't throw directly! + +// Correct - in API client implementation +if (error) + return Throw(new MyException(ExceptionType.Error)); ``` -### AppleMusic +### ❌ Mistake 3: Not Using ThrowTuple for Tuple Returns + ```csharp -var apple = await AppleMusic.WithAccessToken(); -var results = await apple.Search("query"); +// Wrong - in API client returning tuple +if (error) + return Throw<(string, bool)>(new MyException()); // Won't work! + +// Correct +if (error) + return ThrowTuple(new MyException()); ``` -## File Structure +### ❌ Mistake 4: Creating New Client for Each Request + +```csharp +// Wrong - inefficient +foreach (var id in ids) +{ + var deezer = new Deezer(); // Don't recreate! + var track = await deezer.GetSong(id); +} + +// Correct +var deezer = new Deezer(); +foreach (var id in ids) +{ + var track = await deezer.GetSong(id); +} ``` -DevBase.Api/ -├── Apis/ -│ ├── ApiClient.cs # Base class with Throw methods -│ ├── Deezer/ -│ │ ├── Deezer.cs -│ │ └── Structure/ # JSON response types -│ ├── Tidal/ -│ ├── AppleMusic/ -│ ├── NetEase/ -│ ├── BeautifulLyrics/ -│ └── ... -├── Enums/ # Exception type enums -├── Exceptions/ # Custom exceptions -└── Serializer/ # JSON deserializer + +### ❌ Mistake 5: Not Handling Tidal Authorization Pending + +```csharp +// Wrong +var access = await tidal.GetTokenFrom(deviceCode); // Might throw! + +// Correct +tidal.StrictErrorHandling = false; +var access = await tidal.GetTokenFrom(deviceCode); +if (access == null) +{ + // Still pending, try again later +} ``` -## Important Notes +## Creating Custom API Clients + +When extending `ApiClient`: + +```csharp +using DevBase.Api.Apis; +using DevBase.Api.Exceptions; +using DevBase.Net.Core; -1. **Always extend `ApiClient`** for new API clients -2. **Use `Throw()`** for reference type returns -3. **Use `ThrowTuple()`** for `ValueTuple` returns -4. **JSON types are in `Structure/Json/` folders** -5. **Use `JsonDeserializer`** for JSON parsing -6. **External APIs may be unavailable** - handle gracefully in tests +public class MyApiClient : ApiClient +{ + public async Task GetData(string id) + { + var response = await new Request($"https://api.example.com/data/{id}") + .WithTimeout(TimeSpan.FromSeconds(30)) + .SendAsync(); + + // Always check status + if (!response.IsSuccessStatusCode) + return Throw(new MyApiException("Request failed")); + + // Parse response + var data = await response.ParseJsonAsync(); + + // Validate + if (data == null) + return Throw(new MyApiException("Invalid response")); + + return data; + } + + // For tuple returns + public async Task<(string, bool)> GetStatus(string id) + { + var response = await new Request($"https://api.example.com/status/{id}") + .SendAsync(); + + if (!response.IsSuccessStatusCode) + return ThrowTuple(new MyApiException("Request failed")); + + var status = await response.GetStringAsync(); + return (status, true); + } +} +``` + +## Integration with DevBase.Format + +Many API responses include lyrics that can be parsed: + +```csharp +using DevBase.Format; +using DevBase.Format.Formats.LrcFormat; + +var deezer = new Deezer(); +var lyrics = await deezer.GetLyrics("123456"); + +// Parse LRC format +var parser = new LrcParser(); +var parsed = parser.Parse(lyrics); + +foreach (var line in parsed) +{ + Console.WriteLine($"[{line.Timestamp}] {line.Text}"); +} +``` + +## Performance Tips + +1. **Reuse client instances** across multiple requests +2. **Use lenient mode** for better performance (no exception overhead) +3. **Implement caching** for frequently accessed data +4. **Batch requests** when possible +5. **Use Multitasking** for concurrent API calls with rate limiting + +## Quick Reference + +| API | Primary Use Case | Authentication | +|-----|-----------------|----------------| +| Deezer | Track search, lyrics, downloads | ARL token (optional) | +| Tidal | High-quality audio, lyrics | Device auth flow | +| Apple Music | Catalog browsing, lyrics | Developer token | +| NetEase | Chinese music, karaoke lyrics | None | +| BeautifulLyrics | Rich-synced lyrics | None | +| Musixmatch | Lyrics search | None | +| OpenAI | Chat completions | API key | +| Replicate | Model inference | API token | + +## Testing Considerations + +- Use lenient mode (`StrictErrorHandling = false`) for tests +- Mock API responses by extending client classes +- Test both success and failure scenarios +- Consider rate limits in integration tests +- Use test credentials for authenticated APIs + +## Version + +Current version: **1.5.0** +Target framework: **.NET 9.0** ## Dependencies -- **DevBase.Net** for HTTP requests -- **DevBase.Format** for lyrics parsing -- **Newtonsoft.Json** for JSON serialization + +- DevBase.Net +- DevBase +- DevBase.Format +- DevBase.Cryptography +- Newtonsoft.Json diff --git a/DevBase.Api/README.md b/DevBase.Api/README.md index e734695..edc2ec6 100644 --- a/DevBase.Api/README.md +++ b/DevBase.Api/README.md @@ -1,62 +1,63 @@ # DevBase.Api -A comprehensive API client library for .NET providing ready-to-use integrations with popular music and AI services. +**DevBase.Api** provides a comprehensive collection of API clients for popular music streaming services, AI platforms, and lyrics providers. All clients are built on top of DevBase.Net for high-performance networking. -## Features +## Supported APIs -- **Music Services** - - Apple Music API - - Deezer API - - Tidal API - - Musixmatch API - - NetEase Music API -- **AI Services** - - OpenAI API - - Replicate API -- **Lyrics Services** - - BeautifulLyrics - - OpenLyricsClient +### Music Streaming Services +- **Deezer** - Search, track info, lyrics, song downloads with Blowfish decryption +- **Tidal** - Device authentication, search, lyrics, high-quality downloads +- **Apple Music** - Catalog search, track details, lyrics (requires auth token) +- **NetEase Cloud Music** - Chinese music service with LRC/karaoke lyrics -## Installation +### Lyrics Providers +- **BeautifulLyrics** - Rich-synced lyrics via ISRC lookup +- **Musixmatch** - Lyrics search and retrieval +- **OpenLyricsClient** - AI-powered lyrics synchronization -```xml - -``` +### AI Services +- **OpenAI** - Chat completions and GPT models +- **Replicate** - Model inference and predictions -Or via NuGet CLI: +## Installation ```bash dotnet add package DevBase.Api ``` -## Supported APIs +## Features -| Service | Description | -|---------|-------------| -| Apple Music | Apple Music streaming service API | -| Deezer | Deezer music streaming API | -| Tidal | Tidal HiFi streaming API | -| Musixmatch | Lyrics and music metadata API | -| NetEase | NetEase Cloud Music API | -| OpenAI | ChatGPT and AI models API | -| Replicate | AI model hosting and inference | -| BeautifulLyrics | Lyrics service | -| OpenLyricsClient | Open lyrics database | +- **Unified Error Handling** - Consistent error handling across all clients +- **Strict/Lenient Modes** - Choose between exceptions or null returns +- **Built on DevBase.Net** - High-performance HTTP with retry policies +- **Type-Safe Responses** - Strongly-typed JSON deserialization +- **Cookie Management** - Automatic cookie handling for authenticated APIs -## Usage Examples +## Quick Start ### Deezer API ```csharp using DevBase.Api.Apis.Deezer; -DeezerApi deezer = new DeezerApi(); +// Initialize client +var deezer = new Deezer(); // Search for tracks -var results = await deezer.SearchAsync("artist name song title"); +var searchResults = await deezer.Search("Never Gonna Give You Up"); +var firstTrack = searchResults.data[0]; // Get track details -var track = await deezer.GetTrackAsync(trackId); +var track = await deezer.GetSong(firstTrack.id.ToString()); +Console.WriteLine($"Title: {track.title}"); +Console.WriteLine($"Artist: {track.artist.name}"); + +// Get lyrics +var lyrics = await deezer.GetLyrics(track.id.ToString()); + +// With ARL token for authenticated access +var authenticatedDeezer = new Deezer("your-arl-token"); +var jwtToken = await authenticatedDeezer.GetJwtToken(); ``` ### Tidal API @@ -64,13 +65,85 @@ var track = await deezer.GetTrackAsync(trackId); ```csharp using DevBase.Api.Apis.Tidal; -TidalApi tidal = new TidalApi(accessToken); +var tidal = new Tidal(); + +// Device authentication flow +var deviceAuth = await tidal.RegisterDevice(); +Console.WriteLine($"Visit: {deviceAuth.verificationUriComplete}"); + +// Poll for token +JsonTidalAccountAccess access = null; +while (access == null) +{ + await Task.Delay(deviceAuth.interval * 1000); + try + { + access = await tidal.GetTokenFrom(deviceAuth.deviceCode); + } + catch { /* Still pending */ } +} -// Search for albums -var albums = await tidal.SearchAlbumsAsync("album name"); +// Login with access token +var session = await tidal.Login(access.access_token); -// Get track stream URL -var streamUrl = await tidal.GetStreamUrlAsync(trackId); +// Search +var searchResults = await tidal.Search("Rick Astley", session.sessionId); + +// Get lyrics +var lyrics = await tidal.GetLyrics(trackId, session.sessionId); +``` + +### Apple Music API + +```csharp +using DevBase.Api.Apis.AppleMusic; + +var appleMusic = new AppleMusic("your-developer-token"); + +// Search catalog +var results = await appleMusic.SearchCatalog("us", "Never Gonna Give You Up"); + +// Get song details +var song = await appleMusic.GetSong("us", songId); + +// Get lyrics +var lyrics = await appleMusic.GetLyrics("us", songId); +``` + +### NetEase Cloud Music API + +```csharp +using DevBase.Api.Apis.NetEase; + +var netease = new NetEase(); + +// Search +var searchResults = await netease.Search("周杰伦"); +var firstSong = searchResults.result.songs[0]; + +// Get lyrics (supports LRC and karaoke formats) +var lyrics = await netease.Lyrics(firstSong.id.ToString()); +Console.WriteLine(lyrics.lrc.lyric); // Standard LRC +Console.WriteLine(lyrics.klyric.lyric); // Karaoke lyrics + +// Get song details +var songDetail = await netease.SongDetail(firstSong.id.ToString()); +``` + +### BeautifulLyrics API + +```csharp +using DevBase.Api.Apis.BeautifulLyrics; + +var beautifulLyrics = new BeautifulLyrics(); + +// Get lyrics by ISRC +var (rawLyrics, isRichSync) = await beautifulLyrics.GetRawLyrics("GBAYE0601330"); + +if (isRichSync) +{ + Console.WriteLine("Rich-synced lyrics available!"); +} ``` ### Musixmatch API @@ -78,13 +151,13 @@ var streamUrl = await tidal.GetStreamUrlAsync(trackId); ```csharp using DevBase.Api.Apis.Musixmatch; -MusixmatchApi musixmatch = new MusixmatchApi(apiKey); +var musixmatch = new MusixMatch(); // Search for lyrics -var lyrics = await musixmatch.GetLyricsAsync("artist", "song title"); +var results = await musixmatch.SearchTrack("Never Gonna Give You Up", "Rick Astley"); -// Get synced lyrics -var syncedLyrics = await musixmatch.GetSyncedLyricsAsync(trackId); +// Get lyrics by track ID +var lyrics = await musixmatch.GetLyrics(trackId); ``` ### OpenAI API @@ -92,16 +165,17 @@ var syncedLyrics = await musixmatch.GetSyncedLyricsAsync(trackId); ```csharp using DevBase.Api.Apis.OpenAi; -OpenAiApi openai = new OpenAiApi(apiKey); +var openai = new OpenAi("your-api-key"); // Chat completion -var response = await openai.ChatAsync("Hello, how are you?"); - -// With system prompt -var response = await openai.ChatAsync( - "Translate to German", - "Hello World" -); +var messages = new[] +{ + new { role = "system", content = "You are a helpful assistant." }, + new { role = "user", content = "What is the capital of France?" } +}; + +var response = await openai.ChatCompletion("gpt-4", messages); +Console.WriteLine(response.choices[0].message.content); ``` ### Replicate API @@ -109,45 +183,240 @@ var response = await openai.ChatAsync( ```csharp using DevBase.Api.Apis.Replicate; -ReplicateApi replicate = new ReplicateApi(apiToken); +var replicate = new Replicate("your-api-token"); // Run a model -var result = await replicate.RunAsync( - "owner/model:version", - new { prompt = "input text" } +var prediction = await replicate.CreatePrediction( + "stability-ai/sdxl", + new { prompt = "A beautiful sunset over mountains" } ); -// Get prediction status -var status = await replicate.GetPredictionAsync(predictionId); +// Check prediction status +var result = await replicate.GetPrediction(prediction.id); ``` -## Architecture +## Error Handling + +All API clients extend `ApiClient`, providing two error handling modes: + +### Lenient Mode (Default) + +Returns `null` or default values on errors: +```csharp +var deezer = new Deezer(); +var track = await deezer.GetSong("invalid-id"); // Returns null + +if (track == null) +{ + Console.WriteLine("Track not found"); +} ``` -DevBase.Api/ -├── Apis/ -│ ├── AppleMusic/ # Apple Music integration -│ ├── Deezer/ # Deezer API client -│ ├── Tidal/ # Tidal API client -│ ├── Musixmatch/ # Musixmatch lyrics API -│ ├── NetEase/ # NetEase Cloud Music -│ ├── OpenAi/ # OpenAI ChatGPT -│ ├── Replicate/ # Replicate AI models -│ ├── BeautifulLyrics/ # Lyrics service -│ └── OpenLyricsClient/ # Open lyrics database -├── Enums/ # API enumerations -├── Exceptions/ # API-specific exceptions -└── Serializer/ # JSON serialization helpers + +### Strict Mode + +Throws typed exceptions on errors: + +```csharp +var deezer = new Deezer { StrictErrorHandling = true }; + +try +{ + var track = await deezer.GetSong("invalid-id"); +} +catch (DeezerException ex) +{ + Console.WriteLine($"Deezer error: {ex.ExceptionType}"); +} +catch (Exception ex) +{ + Console.WriteLine($"General error: {ex.Message}"); +} ``` +## Exception Types + +Each API has its own exception type: + +| API | Exception Type | Enum | +|-----|---------------|------| +| Deezer | `DeezerException` | `EnumDeezerExceptionType` | +| Tidal | `TidalException` | `EnumTidalExceptionType` | +| Apple Music | `AppleMusicException` | `EnumAppleMusicExceptionType` | +| NetEase | `NetEaseException` | `EnumNetEaseExceptionType` | +| BeautifulLyrics | `BeautifulLyricsException` | `EnumBeautifulLyricsExceptionType` | +| OpenLyricsClient | `OpenLyricsClientException` | `EnumOpenLyricsClientExceptionType` | +| Replicate | `ReplicateException` | `EnumReplicateExceptionType` | + +## Advanced Usage + +### Custom Timeout + +```csharp +// Most API methods use Request internally +// You can extend the client to customize behavior +``` + +### Cookie Persistence (Deezer) + +```csharp +var deezer = new Deezer("arl-token"); + +// Cookies are automatically managed +var token = await deezer.GetJwtToken(); +// Subsequent requests use updated cookies +``` + +### Retry Policies + +Since all clients use DevBase.Net, you can implement retry logic: + +```csharp +// Clients internally use Request which supports retry policies +// For custom implementations, extend the client class +``` + +## API Client Base Class + +All clients extend `ApiClient`: + +```csharp +public class ApiClient +{ + public bool StrictErrorHandling { get; set; } + + protected dynamic Throw(Exception exception); + protected (string, bool) ThrowTuple(Exception exception); +} +``` + +### Creating Custom API Clients + +```csharp +using DevBase.Api.Apis; +using DevBase.Net.Core; + +public class MyApiClient : ApiClient +{ + public async Task GetData(string id) + { + var response = await new Request($"https://api.example.com/data/{id}") + .SendAsync(); + + if (!response.IsSuccessStatusCode) + return Throw(new MyApiException("Failed to fetch data")); + + return await response.ParseJsonAsync(); + } +} +``` + +## Response Types + +All API responses are strongly typed using JSON structure classes: + +- `JsonDeezer*` - Deezer response types +- `JsonTidal*` - Tidal response types +- `JsonAppleMusic*` - Apple Music response types +- `JsonNetEase*` - NetEase response types + ## Dependencies -- DevBase.Net (HTTP client) -- DevBase.Format (lyrics parsing) -- DevBase.Cryptography.BouncyCastle (authentication) -- Newtonsoft.Json -- HtmlAgilityPack +- **DevBase.Net** - HTTP client library +- **DevBase** - Core utilities +- **DevBase.Format** - Lyrics format parsing +- **DevBase.Cryptography** - Deezer Blowfish decryption +- **Newtonsoft.Json** - JSON serialization + +## Common Patterns + +### Pattern 1: Search and Download + +```csharp +var deezer = new Deezer("arl-token"); + +// Search +var results = await deezer.Search("artist - song"); +var track = results.data.FirstOrDefault(); + +// Get full details +var song = await deezer.GetSong(track.id.ToString()); + +// Download (requires authentication) +var downloadUrl = await deezer.GetDownloadUrl(song); +``` + +### Pattern 2: Multi-Provider Lyrics Search + +```csharp +async Task FindLyrics(string isrc, string title, string artist) +{ + // Try BeautifulLyrics first (best quality) + var bl = new BeautifulLyrics(); + var (lyrics, isRich) = await bl.GetRawLyrics(isrc); + if (lyrics != null) return lyrics; + + // Try Musixmatch + var mm = new MusixMatch(); + var mmResults = await mm.SearchTrack(title, artist); + if (mmResults != null) return mmResults; + + // Try Deezer + var deezer = new Deezer(); + var deezerLyrics = await deezer.GetLyrics(trackId); + + return deezerLyrics; +} +``` + +### Pattern 3: Authenticated API Access + +```csharp +// Store tokens securely +string arlToken = LoadFromSecureStorage("deezer_arl"); +var deezer = new Deezer(arlToken); + +// Use authenticated endpoints +var jwt = await deezer.GetJwtToken(); +var userPlaylists = await deezer.GetUserPlaylists(jwt.token); +``` + +## Performance Tips + +1. **Reuse client instances** - Don't create new clients for each request +2. **Enable caching** - Cache search results and track metadata +3. **Use batch operations** - Some APIs support batch requests +4. **Handle rate limits** - Implement exponential backoff for rate-limited APIs +5. **Dispose resources** - Response objects should be disposed + +## Testing + +```csharp +// Use lenient mode for tests +var client = new Deezer { StrictErrorHandling = false }; + +// Mock responses by extending the client +public class MockDeezer : Deezer +{ + public override async Task Search(string query) + { + return new JsonDeezerSearch { /* mock data */ }; + } +} +``` + +## Target Framework + +- **.NET 9.0** ## License -MIT License - see LICENSE file for details. +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/DevBase.Avalonia.Extension/AGENT.md b/DevBase.Avalonia.Extension/AGENT.md new file mode 100644 index 0000000..2b7194c --- /dev/null +++ b/DevBase.Avalonia.Extension/AGENT.md @@ -0,0 +1,381 @@ +# DevBase.Avalonia.Extension - AI Agent Guide + +This guide helps AI agents effectively use DevBase.Avalonia.Extension for advanced color analysis in Avalonia UI applications. + +## Overview + +DevBase.Avalonia.Extension provides advanced color analysis using LAB color space and sophisticated preprocessing capabilities. + +**Target Framework:** .NET 9.0, Avalonia UI 11.x + +## Core Components + +### Color Calculators + +```csharp +// RGB-based clustering +ClusterColorCalculator +List Calculate(Bitmap bitmap, int clusterCount) + +// LAB-based clustering (more accurate) +LabClusterColorCalculator +List Calculate(Bitmap bitmap, int clusterCount) +``` + +### Converters + +```csharp +RGBToLabConverter +(double L, double A, double B) Convert(Color rgb) +``` + +## Usage Patterns for AI Agents + +### Pattern 1: LAB Color Clustering + +```csharp +using DevBase.Avalonia.Extension.Color.Image; +using Avalonia.Media.Imaging; + +var bitmap = new Bitmap("album-art.png"); +var calculator = new LabClusterColorCalculator(); + +// Extract 5 dominant colors +var colors = calculator.Calculate(bitmap, clusterCount: 5); + +// Use for UI theming +var primary = colors[0]; +var secondary = colors[1]; +var accent = colors[2]; +``` + +### Pattern 2: Image Preprocessing + +```csharp +using DevBase.Avalonia.Extension.Processing; +using DevBase.Avalonia.Extension.Configuration; + +var config = new PreProcessingConfiguration +{ + Brightness = new BrightnessConfiguration + { + Enabled = true, + MinBrightness = 0.3, + MaxBrightness = 0.9 + }, + Chroma = new ChromaConfiguration + { + Enabled = true, + MinChroma = 0.2 + }, + Filter = new FilterConfiguration + { + Enabled = true, + ExcludeGrayscale = true + } +}; + +var preprocessor = new ImagePreProcessor(config); +var processed = preprocessor.Process(bitmap); + +// Now extract colors from processed image +var calculator = new LabClusterColorCalculator(); +var colors = calculator.Calculate(processed, 5); +``` + +### Pattern 3: Music Player Theme + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Avalonia.Extension.Color.Image; +using DevBase.Avalonia.Extension.Configuration; +using DevBase.Avalonia.Extension.Processing; + +public async Task> GetAlbumTheme(string trackId) +{ + // Get album art + var deezer = new Deezer(); + var track = await deezer.GetSong(trackId); + var bitmap = await DownloadBitmap(track.album.cover_xl); + + // Preprocess to remove dark/grayscale colors + var config = new PreProcessingConfiguration + { + Brightness = new BrightnessConfiguration + { + Enabled = true, + MinBrightness = 0.4 + }, + Chroma = new ChromaConfiguration + { + Enabled = true, + MinChroma = 0.3 + }, + Filter = new FilterConfiguration + { + Enabled = true, + ExcludeGrayscale = true + } + }; + + var preprocessor = new ImagePreProcessor(config); + var processed = preprocessor.Process(bitmap); + + // Extract colors using LAB + var calculator = new LabClusterColorCalculator(); + return calculator.Calculate(processed, 5).ToList(); +} +``` + +### Pattern 4: RGB to LAB Conversion + +```csharp +using DevBase.Avalonia.Extension.Converter; +using Avalonia.Media; + +var converter = new RGBToLabConverter(); +var color = Colors.Red; + +var (l, a, b) = converter.Convert(color); +Console.WriteLine($"L: {l}, A: {a}, B: {b}"); + +// L = Lightness (0-100) +// A = Green-Red axis +// B = Blue-Yellow axis +``` + +### Pattern 5: Filter Specific Colors + +```csharp +var config = new PreProcessingConfiguration +{ + Filter = new FilterConfiguration + { + Enabled = true, + ExcludeGrayscale = true, + ExcludeColors = new List + { + Colors.White, + Colors.Black + } + } +}; + +var preprocessor = new ImagePreProcessor(config); +var filtered = preprocessor.Process(bitmap); +``` + +## Important Concepts + +### 1. LAB vs RGB Clustering + +**Use LAB when:** +- Accuracy is important +- Perceptual color distance matters +- Creating color palettes for UI + +**Use RGB when:** +- Speed is critical +- Simple color extraction +- Quick prototyping + +```csharp +// RGB - faster but less accurate +var rgbCalc = new ClusterColorCalculator(); +var rgbColors = rgbCalc.Calculate(bitmap, 5); + +// LAB - slower but more accurate +var labCalc = new LabClusterColorCalculator(); +var labColors = labCalc.Calculate(bitmap, 5); +``` + +### 2. Preprocessing Configuration + +```csharp +// Minimal preprocessing +var minConfig = new PreProcessingConfiguration +{ + Brightness = new BrightnessConfiguration { Enabled = true, MinBrightness = 0.2 } +}; + +// Aggressive preprocessing +var maxConfig = new PreProcessingConfiguration +{ + Brightness = new BrightnessConfiguration + { + Enabled = true, + MinBrightness = 0.4, + MaxBrightness = 0.85 + }, + Chroma = new ChromaConfiguration + { + Enabled = true, + MinChroma = 0.3 + }, + Filter = new FilterConfiguration + { + Enabled = true, + ExcludeGrayscale = true + } +}; +``` + +### 3. Cluster Count Selection + +```csharp +// Simple palette (2-3 colors) +var simple = calculator.Calculate(bitmap, 3); + +// Balanced palette (5-7 colors) +var balanced = calculator.Calculate(bitmap, 5); + +// Detailed palette (10+ colors) +var detailed = calculator.Calculate(bitmap, 10); +``` + +## Common Mistakes to Avoid + +### ❌ Mistake 1: Too Many Clusters + +```csharp +// Wrong - too many clusters for small image +var colors = calculator.Calculate(smallBitmap, 50); // Overkill! + +// Correct +var colors = calculator.Calculate(smallBitmap, 5); +``` + +### ❌ Mistake 2: No Preprocessing + +```csharp +// Wrong - extracting from raw image with dark/grayscale colors +var colors = calculator.Calculate(bitmap, 5); + +// Correct - preprocess first +var config = new PreProcessingConfiguration +{ + Brightness = new BrightnessConfiguration { Enabled = true, MinBrightness = 0.3 }, + Filter = new FilterConfiguration { Enabled = true, ExcludeGrayscale = true } +}; +var preprocessor = new ImagePreProcessor(config); +var processed = preprocessor.Process(bitmap); +var colors = calculator.Calculate(processed, 5); +``` + +### ❌ Mistake 3: Wrong Calculator for Use Case + +```csharp +// Wrong - using RGB for accurate color palette +var calculator = new ClusterColorCalculator(); +var colors = calculator.Calculate(bitmap, 5); // Less accurate + +// Correct - use LAB for accurate palettes +var calculator = new LabClusterColorCalculator(); +var colors = calculator.Calculate(bitmap, 5); +``` + +### ❌ Mistake 4: Not Scaling Large Images + +```csharp +// Wrong - processing 4K image directly +var bitmap = new Bitmap("4k-image.png"); +var colors = calculator.Calculate(bitmap, 5); // Slow! + +// Correct - scale down first +var original = new Bitmap("4k-image.png"); +var scaled = original.CreateScaledBitmap(new PixelSize(400, 400)); +var colors = calculator.Calculate(scaled, 5); +``` + +## Configuration Reference + +### BrightnessConfiguration + +| Property | Type | Range | Description | +|----------|------|-------|-------------| +| Enabled | bool | - | Enable brightness filtering | +| MinBrightness | double | 0.0-1.0 | Minimum brightness threshold | +| MaxBrightness | double | 0.0-1.0 | Maximum brightness threshold | + +### ChromaConfiguration + +| Property | Type | Range | Description | +|----------|------|-------|-------------| +| Enabled | bool | - | Enable chroma filtering | +| MinChroma | double | 0.0-1.0 | Minimum chroma/saturation | + +### FilterConfiguration + +| Property | Type | Description | +|----------|------|-------------| +| Enabled | bool | Enable color filtering | +| ExcludeGrayscale | bool | Remove grayscale colors | +| ExcludeColors | List | Specific colors to exclude | + +## Performance Tips + +1. **Scale images** before processing (300-500px is usually sufficient) +2. **Use RGB** for quick analysis, **LAB** for accuracy +3. **Limit clusters** to 5-10 for most use cases +4. **Cache results** for frequently analyzed images +5. **Preprocess once** then extract multiple palettes + +## Integration Examples + +### With DevBase.Api + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Avalonia.Extension.Color.Image; + +var deezer = new Deezer(); +var track = await deezer.GetSong("123456"); +var bitmap = await DownloadBitmap(track.album.cover_xl); + +var calculator = new LabClusterColorCalculator(); +var colors = calculator.Calculate(bitmap, 5); +``` + +### With DevBase.Avalonia + +```csharp +using DevBase.Avalonia.Color.Image; +using DevBase.Avalonia.Extension.Color.Image; + +// Basic analysis +var basic = new BrightestColorCalculator().Calculate(bitmap); + +// Advanced analysis +var advanced = new LabClusterColorCalculator().Calculate(bitmap, 5); +``` + +## Quick Reference + +| Task | Code | +|------|------| +| LAB clustering | `new LabClusterColorCalculator().Calculate(bitmap, count)` | +| RGB clustering | `new ClusterColorCalculator().Calculate(bitmap, count)` | +| RGB to LAB | `new RGBToLabConverter().Convert(color)` | +| Preprocess | `new ImagePreProcessor(config).Process(bitmap)` | +| Filter brightness | `BrightnessConfiguration { MinBrightness = 0.3 }` | +| Filter chroma | `ChromaConfiguration { MinChroma = 0.2 }` | +| Exclude grayscale | `FilterConfiguration { ExcludeGrayscale = true }` | + +## Testing Considerations + +- Test with various image types (photos, graphics, logos) +- Test with different cluster counts +- Test preprocessing configurations +- Verify LAB conversion accuracy +- Test performance with large images + +## Version + +Current version: **1.0.0** +Target framework: **.NET 9.0** + +## Dependencies + +- Avalonia +- DevBase +- DevBase.Avalonia diff --git a/DevBase.Avalonia.Extension/README.md b/DevBase.Avalonia.Extension/README.md new file mode 100644 index 0000000..7e65bcd --- /dev/null +++ b/DevBase.Avalonia.Extension/README.md @@ -0,0 +1,407 @@ +# DevBase.Avalonia.Extension + +**DevBase.Avalonia.Extension** provides advanced color analysis and image processing capabilities for Avalonia UI applications, including LAB color space conversion, clustering algorithms, and sophisticated image preprocessing. + +## Features + +- **LAB Color Space** - Perceptually uniform color analysis +- **K-Means Clustering** - Advanced color clustering algorithms +- **Image Preprocessing** - Brightness, chroma, and filter adjustments +- **Post-Processing** - Color refinement and normalization +- **Bitmap Extensions** - Enhanced bitmap manipulation methods + +## Installation + +```bash +dotnet add package DevBase.Avalonia.Extension +``` + +## Core Components + +### Color Calculators + +- **`ClusterColorCalculator`** - RGB-based K-means clustering +- **`LabClusterColorCalculator`** - LAB color space clustering (more accurate) + +### Converters + +- **`RGBToLabConverter`** - Convert RGB to LAB color space + +### Configuration + +- **`BrightnessConfiguration`** - Brightness adjustment settings +- **`ChromaConfiguration`** - Chroma/saturation settings +- **`FilterConfiguration`** - Color filtering options +- **`PreProcessingConfiguration`** - Combined preprocessing settings +- **`PostProcessingConfiguration`** - Post-processing options + +### Processing + +- **`ImagePreProcessor`** - Apply preprocessing to images + +## Quick Start + +### LAB Color Space Clustering + +```csharp +using Avalonia.Media.Imaging; +using DevBase.Avalonia.Extension.Color.Image; + +var bitmap = new Bitmap("image.png"); +var calculator = new LabClusterColorCalculator(); + +// Get 5 dominant colors using LAB color space +var dominantColors = calculator.Calculate(bitmap, clusterCount: 5); + +foreach (var color in dominantColors) +{ + Console.WriteLine($"Color: {color}"); +} +``` + +### RGB Clustering + +```csharp +using DevBase.Avalonia.Extension.Color.Image; + +var calculator = new ClusterColorCalculator(); +var colors = calculator.Calculate(bitmap, clusterCount: 3); +``` + +### Image Preprocessing + +```csharp +using DevBase.Avalonia.Extension.Processing; +using DevBase.Avalonia.Extension.Configuration; + +var config = new PreProcessingConfiguration +{ + Brightness = new BrightnessConfiguration + { + Enabled = true, + MinBrightness = 0.3, + MaxBrightness = 0.9 + }, + Chroma = new ChromaConfiguration + { + Enabled = true, + MinChroma = 0.2 + }, + Filter = new FilterConfiguration + { + Enabled = true, + ExcludeGrayscale = true + } +}; + +var preprocessor = new ImagePreProcessor(config); +var processedBitmap = preprocessor.Process(bitmap); +``` + +## Usage Examples + +### Extract Color Palette with LAB + +```csharp +using DevBase.Avalonia.Extension.Color.Image; +using Avalonia.Media; + +public class ColorPaletteExtractor +{ + public List ExtractPalette(Bitmap image, int colorCount) + { + var calculator = new LabClusterColorCalculator(); + var colors = calculator.Calculate(image, colorCount); + + return colors.ToList(); + } +} +``` + +### Adaptive Theme Generation + +```csharp +using DevBase.Avalonia.Extension.Color.Image; +using DevBase.Avalonia.Extension.Configuration; +using DevBase.Avalonia.Extension.Processing; + +public class ThemeGenerator +{ + public (Color Primary, Color Secondary, Color Accent) GenerateTheme(Bitmap image) + { + // Preprocess image + var config = new PreProcessingConfiguration + { + Brightness = new BrightnessConfiguration { Enabled = true, MinBrightness = 0.4 }, + Chroma = new ChromaConfiguration { Enabled = true, MinChroma = 0.3 } + }; + + var preprocessor = new ImagePreProcessor(config); + var processed = preprocessor.Process(image); + + // Extract colors using LAB clustering + var calculator = new LabClusterColorCalculator(); + var colors = calculator.Calculate(processed, clusterCount: 3); + + return (colors[0], colors[1], colors[2]); + } +} +``` + +### Music Player with Advanced Color Analysis + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Avalonia.Extension.Color.Image; +using DevBase.Avalonia.Extension.Configuration; +using DevBase.Avalonia.Extension.Processing; + +public class MusicPlayerTheme +{ + public async Task> GetAlbumColors(string trackId) + { + // Get album art + var deezer = new Deezer(); + var track = await deezer.GetSong(trackId); + var bitmap = await DownloadBitmap(track.album.cover_xl); + + // Preprocess + var config = new PreProcessingConfiguration + { + Brightness = new BrightnessConfiguration + { + Enabled = true, + MinBrightness = 0.3, + MaxBrightness = 0.9 + }, + Chroma = new ChromaConfiguration + { + Enabled = true, + MinChroma = 0.25 + }, + Filter = new FilterConfiguration + { + Enabled = true, + ExcludeGrayscale = true + } + }; + + var preprocessor = new ImagePreProcessor(config); + var processed = preprocessor.Process(bitmap); + + // Extract colors + var calculator = new LabClusterColorCalculator(); + return calculator.Calculate(processed, clusterCount: 5).ToList(); + } +} +``` + +### Color Filtering + +```csharp +public class ColorFilter +{ + public Bitmap FilterDarkColors(Bitmap image) + { + var config = new PreProcessingConfiguration + { + Brightness = new BrightnessConfiguration + { + Enabled = true, + MinBrightness = 0.5 // Remove dark colors + } + }; + + var preprocessor = new ImagePreProcessor(config); + return preprocessor.Process(image); + } + + public Bitmap FilterGrayscale(Bitmap image) + { + var config = new PreProcessingConfiguration + { + Filter = new FilterConfiguration + { + Enabled = true, + ExcludeGrayscale = true // Remove grayscale colors + } + }; + + var preprocessor = new ImagePreProcessor(config); + return preprocessor.Process(image); + } +} +``` + +## Configuration Options + +### BrightnessConfiguration + +```csharp +public class BrightnessConfiguration +{ + public bool Enabled { get; set; } + public double MinBrightness { get; set; } // 0.0 - 1.0 + public double MaxBrightness { get; set; } // 0.0 - 1.0 +} +``` + +### ChromaConfiguration + +```csharp +public class ChromaConfiguration +{ + public bool Enabled { get; set; } + public double MinChroma { get; set; } // 0.0 - 1.0 +} +``` + +### FilterConfiguration + +```csharp +public class FilterConfiguration +{ + public bool Enabled { get; set; } + public bool ExcludeGrayscale { get; set; } + public List ExcludeColors { get; set; } +} +``` + +## LAB Color Space + +LAB color space is perceptually uniform, meaning the distance between colors corresponds to human perception: + +- **L** - Lightness (0-100) +- **A** - Green-Red axis +- **B** - Blue-Yellow axis + +### Why Use LAB? + +- More accurate color clustering +- Better perceptual color distance +- Superior to RGB for color analysis + +### RGB to LAB Conversion + +```csharp +using DevBase.Avalonia.Extension.Converter; +using Avalonia.Media; + +var converter = new RGBToLabConverter(); +var rgbColor = Colors.Red; + +var (l, a, b) = converter.Convert(rgbColor); +Console.WriteLine($"L: {l}, A: {a}, B: {b}"); +``` + +## Advanced Usage + +### Custom Clustering Parameters + +```csharp +var calculator = new LabClusterColorCalculator(); + +// More clusters for detailed palette +var detailedPalette = calculator.Calculate(bitmap, clusterCount: 10); + +// Fewer clusters for simplified palette +var simplePalette = calculator.Calculate(bitmap, clusterCount: 3); +``` + +### Combined Preprocessing + +```csharp +var config = new PreProcessingConfiguration +{ + Brightness = new BrightnessConfiguration + { + Enabled = true, + MinBrightness = 0.3, + MaxBrightness = 0.85 + }, + Chroma = new ChromaConfiguration + { + Enabled = true, + MinChroma = 0.2 + }, + Filter = new FilterConfiguration + { + Enabled = true, + ExcludeGrayscale = true, + ExcludeColors = new List { Colors.White, Colors.Black } + } +}; + +var preprocessor = new ImagePreProcessor(config); +var result = preprocessor.Process(bitmap); +``` + +### Post-Processing + +```csharp +var postConfig = new PostProcessingConfiguration +{ + NormalizeColors = true, + RemoveDuplicates = true, + SortByBrightness = true +}; + +// Apply post-processing to extracted colors +var processedColors = ApplyPostProcessing(colors, postConfig); +``` + +## Performance Considerations + +- **LAB conversion** - Slightly slower than RGB but more accurate +- **Cluster count** - More clusters = longer processing time +- **Image size** - Scale down large images before processing +- **Preprocessing** - Each filter adds processing time + +## Comparison: RGB vs LAB Clustering + +| Aspect | RGB Clustering | LAB Clustering | +|--------|---------------|----------------| +| Speed | Faster | Slightly slower | +| Accuracy | Good | Excellent | +| Perceptual | No | Yes | +| Use Case | Quick analysis | Accurate color extraction | + +## Integration with DevBase.Avalonia + +Works seamlessly with DevBase.Avalonia: + +```csharp +using DevBase.Avalonia.Color.Image; +using DevBase.Avalonia.Extension.Color.Image; + +// Basic analysis (DevBase.Avalonia) +var basicCalculator = new BrightestColorCalculator(); +var brightestColor = basicCalculator.Calculate(bitmap); + +// Advanced analysis (DevBase.Avalonia.Extension) +var advancedCalculator = new LabClusterColorCalculator(); +var palette = advancedCalculator.Calculate(bitmap, 5); +``` + +## Target Framework + +- **.NET 9.0** +- **Avalonia UI 11.x** + +## Dependencies + +- **Avalonia** - UI framework +- **DevBase** - Core utilities +- **DevBase.Avalonia** - Base color analysis + +## License + +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/DevBase.Avalonia/AGENT.md b/DevBase.Avalonia/AGENT.md index 70abf2b..c4c4c1e 100644 --- a/DevBase.Avalonia/AGENT.md +++ b/DevBase.Avalonia/AGENT.md @@ -1,50 +1,290 @@ -# DevBase.Avalonia Agent Guide +# DevBase.Avalonia - AI Agent Guide + +This guide helps AI agents effectively use DevBase.Avalonia for color analysis in Avalonia UI applications. ## Overview -DevBase.Avalonia provides utilities for the Avalonia UI framework, specifically image color analysis. -## Key Classes +DevBase.Avalonia provides color extraction and analysis tools for Avalonia UI bitmaps. -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `ColorCalculator` | `DevBase.Avalonia.Color` | Dominant/accent color extraction | -| `ColorConverter` | `DevBase.Avalonia.Color` | Color space conversions | +**Target Framework:** .NET 9.0, Avalonia UI 11.x -## Quick Reference +## Core Components + +### Color Calculators + +```csharp +// Find brightest color +BrightestColorCalculator +Color Calculate(Bitmap bitmap) + +// Group similar colors +GroupColorCalculator +Dictionary Calculate(Bitmap bitmap) + +// Find nearest color +NearestColorCalculator +Color FindNearest(Bitmap bitmap, Color target) +``` + +## Usage Patterns for AI Agents + +### Pattern 1: Extract Dominant Color -### Extract Dominant Color ```csharp -using DevBase.Avalonia.Color; using Avalonia.Media.Imaging; +using DevBase.Avalonia.Color.Image; + +var bitmap = new Bitmap("album-art.png"); +var calculator = new BrightestColorCalculator(); +var dominantColor = calculator.Calculate(bitmap); + +// Use for UI theming +window.Background = new SolidColorBrush(dominantColor); +``` + +### Pattern 2: Color Palette Generation + +```csharp +var calculator = new GroupColorCalculator(); +var colorGroups = calculator.Calculate(bitmap); + +// Get top 5 colors +var palette = colorGroups + .OrderByDescending(g => g.Value) + .Take(5) + .Select(g => g.Key) + .ToList(); +``` -Bitmap image = new Bitmap("path/to/image.png"); -ColorCalculator calculator = new ColorCalculator(image); +### Pattern 3: Adaptive UI Theming -Color dominantColor = calculator.GetDominantColor(); -Color accentColor = calculator.GetAccentColor(); +```csharp +public void ApplyImageTheme(Bitmap image) +{ + var brightest = new BrightestColorCalculator().Calculate(image); + var groups = new GroupColorCalculator().Calculate(image); + + // Primary color + var primary = brightest; + + // Secondary color (most common after primary) + var secondary = groups + .Where(g => g.Key != primary) + .OrderByDescending(g => g.Value) + .First() + .Key; + + // Apply to UI + ApplyColors(primary, secondary); +} ``` -### Color Conversion +### Pattern 4: Music Player Integration + ```csharp -// RGB to HSL -var hsl = ColorConverter.RgbToHsl(255, 128, 64); +using DevBase.Api.Apis.Deezer; +using DevBase.Avalonia.Color.Image; + +public async Task UpdatePlayerTheme(string trackId) +{ + // Get album art + var deezer = new Deezer(); + var track = await deezer.GetSong(trackId); + var albumArtUrl = track.album.cover_xl; + + // Download and analyze + var bitmap = await DownloadBitmap(albumArtUrl); + var calculator = new BrightestColorCalculator(); + var color = calculator.Calculate(bitmap); + + // Update player UI + playerBackground.Background = new SolidColorBrush(color); +} +``` + +## Important Concepts + +### 1. Bitmap Requirements + +```csharp +// ✅ Correct - Avalonia bitmap +using Avalonia.Media.Imaging; +var bitmap = new Bitmap("image.png"); -// HSL to RGB -var rgb = ColorConverter.HslToRgb(hsl.H, hsl.S, hsl.L); +// ❌ Wrong - System.Drawing.Bitmap +using System.Drawing; +var bitmap = new Bitmap("image.png"); // Wrong type! ``` -## File Structure +### 2. Color Analysis Performance + +```csharp +// For large images, consider downscaling first +var originalBitmap = new Bitmap("large-image.png"); +var scaledBitmap = originalBitmap.CreateScaledBitmap( + new PixelSize(200, 200) +); + +var calculator = new BrightestColorCalculator(); +var color = calculator.Calculate(scaledBitmap); ``` -DevBase.Avalonia/ -└── Color/ - ├── ColorCalculator.cs - └── ColorConverter.cs + +### 3. Caching Results + +```csharp +private Dictionary _colorCache = new(); + +public Color GetDominantColor(string imagePath) +{ + if (_colorCache.ContainsKey(imagePath)) + return _colorCache[imagePath]; + + var bitmap = new Bitmap(imagePath); + var calculator = new BrightestColorCalculator(); + var color = calculator.Calculate(bitmap); + + _colorCache[imagePath] = color; + return color; +} ``` -## Important Notes +## Common Mistakes to Avoid + +### ❌ Mistake 1: Wrong Bitmap Type + +```csharp +// Wrong +using System.Drawing; +var bitmap = new Bitmap("image.png"); +calculator.Calculate(bitmap); // Type mismatch! + +// Correct +using Avalonia.Media.Imaging; +var bitmap = new Bitmap("image.png"); +calculator.Calculate(bitmap); +``` + +### ❌ Mistake 2: Not Disposing Bitmaps + +```csharp +// Wrong - memory leak +for (int i = 0; i < 1000; i++) +{ + var bitmap = new Bitmap($"image{i}.png"); + var color = calculator.Calculate(bitmap); +} + +// Correct +for (int i = 0; i < 1000; i++) +{ + using var bitmap = new Bitmap($"image{i}.png"); + var color = calculator.Calculate(bitmap); +} +``` + +### ❌ Mistake 3: Processing Too Large Images + +```csharp +// Wrong - slow for large images +var bitmap = new Bitmap("4k-image.png"); +var color = calculator.Calculate(bitmap); + +// Correct - scale down first +var original = new Bitmap("4k-image.png"); +var scaled = original.CreateScaledBitmap(new PixelSize(300, 300)); +var color = calculator.Calculate(scaled); +``` + +## Integration Examples + +### With Music Player + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Avalonia.Color.Image; +using Avalonia.Media.Imaging; + +public class MusicPlayerViewModel +{ + private readonly BrightestColorCalculator _colorCalculator; + + public MusicPlayerViewModel() + { + _colorCalculator = new BrightestColorCalculator(); + } + + public async Task LoadTrack(string trackId) + { + var deezer = new Deezer(); + var track = await deezer.GetSong(trackId); + + // Load album art + var bitmap = await LoadBitmapFromUrl(track.album.cover_xl); + + // Extract color + var dominantColor = _colorCalculator.Calculate(bitmap); + + // Update UI + BackgroundColor = dominantColor; + } +} +``` + +### With Image Gallery + +```csharp +public class ImageGalleryViewModel +{ + public async Task> AnalyzeImages(List paths) + { + var calculator = new BrightestColorCalculator(); + var results = new List<(string, Color)>(); + + foreach (var path in paths) + { + using var bitmap = new Bitmap(path); + var color = calculator.Calculate(bitmap); + results.Add((path, color)); + } + + return results; + } +} +``` + +## Performance Tips + +1. **Scale down large images** before analysis +2. **Cache results** for frequently accessed images +3. **Use async** for multiple image processing +4. **Dispose bitmaps** to free memory +5. **Limit color groups** when using GroupColorCalculator + +## Quick Reference + +| Task | Code | +|------|------| +| Load bitmap | `new Bitmap("path.png")` | +| Brightest color | `new BrightestColorCalculator().Calculate(bitmap)` | +| Group colors | `new GroupColorCalculator().Calculate(bitmap)` | +| Find nearest | `new NearestColorCalculator().FindNearest(bitmap, target)` | +| Apply to UI | `control.Background = new SolidColorBrush(color)` | + +## Testing Considerations + +- Test with various image sizes +- Test with different color depths +- Test with grayscale images +- Test with transparent images +- Verify memory disposal + +## Version + +Current version: **1.0.0** +Target framework: **.NET 9.0** + +## Dependencies -1. **Requires Avalonia** UI framework -2. **ColorCalculator** analyzes bitmap images -3. **Dominant color** = most prevalent color -4. **Accent color** = complementary/vibrant color -5. **Color space conversions** between RGB, HSL, HSV +- Avalonia +- Avalonia.Media +- DevBase diff --git a/DevBase.Avalonia/README.md b/DevBase.Avalonia/README.md index bae8964..ddb5092 100644 --- a/DevBase.Avalonia/README.md +++ b/DevBase.Avalonia/README.md @@ -1,72 +1,296 @@ # DevBase.Avalonia -A utility library for Avalonia UI applications providing color manipulation, helper functions, and common UI utilities. +**DevBase.Avalonia** provides color analysis and image processing utilities for Avalonia UI applications. It specializes in extracting dominant colors, calculating color clusters, and analyzing image properties. ## Features -- **Color Utilities** - Color manipulation and conversion -- **Avalonia Helpers** - Common Avalonia UI helper functions -- **Cross-Platform** - Works on Windows, macOS, and Linux +- **Color Extraction** - Extract dominant colors from images +- **Cluster Analysis** - Group similar colors using clustering algorithms +- **Brightness Calculation** - Find brightest colors in images +- **Color Utilities** - Color manipulation and conversion helpers +- **Avalonia Integration** - Works seamlessly with Avalonia UI bitmaps ## Installation -```xml - +```bash +dotnet add package DevBase.Avalonia ``` -Or via NuGet CLI: +## Core Components -```bash -dotnet add package DevBase.Avalonia +### Color Calculators + +- **`BrightestColorCalculator`** - Finds the brightest color in an image +- **`GroupColorCalculator`** - Groups similar colors together +- **`NearestColorCalculator`** - Finds nearest color to a target + +### Extensions + +- **`ColorExtension`** - Color manipulation methods +- **`ColorNormalizerExtension`** - Color normalization utilities +- **`LockedFramebufferExtensions`** - Direct pixel access helpers + +### Utilities + +- **`ColorUtils`** - General color utility methods + +## Quick Start + +### Extract Brightest Color + +```csharp +using Avalonia.Media.Imaging; +using DevBase.Avalonia.Color.Image; + +var bitmap = new Bitmap("image.png"); +var calculator = new BrightestColorCalculator(); + +var brightestColor = calculator.Calculate(bitmap); +Console.WriteLine($"Brightest color: {brightestColor}"); +``` + +### Group Similar Colors + +```csharp +using DevBase.Avalonia.Color.Image; + +var calculator = new GroupColorCalculator(); +var colorGroups = calculator.Calculate(bitmap); + +foreach (var group in colorGroups) +{ + Console.WriteLine($"Group: {group.Key}, Count: {group.Value}"); +} +``` + +### Find Nearest Color + +```csharp +using DevBase.Avalonia.Color.Image; +using Avalonia.Media; + +var calculator = new NearestColorCalculator(); +var targetColor = Colors.Blue; + +var nearestColor = calculator.FindNearest(bitmap, targetColor); +Console.WriteLine($"Nearest to blue: {nearestColor}"); ``` ## Usage Examples -### Color Manipulation +### Album Art Color Analysis + +```csharp +using Avalonia.Media.Imaging; +using DevBase.Avalonia.Color.Image; + +public class AlbumArtAnalyzer +{ + public Color GetDominantColor(string imagePath) + { + var bitmap = new Bitmap(imagePath); + var calculator = new BrightestColorCalculator(); + + return calculator.Calculate(bitmap); + } + + public List GetColorPalette(string imagePath, int colorCount) + { + var bitmap = new Bitmap(imagePath); + var calculator = new GroupColorCalculator(); + + var groups = calculator.Calculate(bitmap); + return groups.Keys.Take(colorCount).ToList(); + } +} +``` + +### UI Theme Generation ```csharp -using DevBase.Avalonia.Color; +public class ThemeGenerator +{ + public (Color Primary, Color Accent) GenerateTheme(Bitmap image) + { + var brightest = new BrightestColorCalculator().Calculate(image); + var groups = new GroupColorCalculator().Calculate(image); + + var primary = brightest; + var accent = groups.Keys.FirstOrDefault(c => c != primary); + + return (primary, accent); + } +} +``` + +### Color Extensions + +```csharp +using DevBase.Avalonia.Color.Extensions; +using Avalonia.Media; + +var color = Colors.Red; + +// Normalize color +var normalized = color.Normalize(); + +// Convert to different format +var converted = color.ToHsl(); + +// Adjust brightness +var brighter = color.AdjustBrightness(1.2); +``` + +## Integration with Avalonia UI + +### Window Background Color + +```csharp +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using DevBase.Avalonia.Color.Image; -// Convert between color formats -var avaloniaColor = ColorUtils.FromHex("#FF5733"); -var rgbColor = ColorUtils.ToRgb(avaloniaColor); +public class DynamicWindow : Window +{ + public void SetBackgroundFromImage(string imagePath) + { + var bitmap = new Bitmap(imagePath); + var calculator = new BrightestColorCalculator(); + var dominantColor = calculator.Calculate(bitmap); + + this.Background = new SolidColorBrush(dominantColor); + } +} +``` -// Color blending -var blended = ColorUtils.Blend(color1, color2, 0.5); +### Control Theming -// Brightness adjustment -var lighter = ColorUtils.Lighten(color, 0.2); -var darker = ColorUtils.Darken(color, 0.2); +```csharp +public class ThemedControl : UserControl +{ + public void ApplyImageTheme(Bitmap image) + { + var calculator = new GroupColorCalculator(); + var colors = calculator.Calculate(image); + + var primary = colors.Keys.First(); + var secondary = colors.Keys.Skip(1).First(); + + // Apply to controls + this.Foreground = new SolidColorBrush(primary); + this.BorderBrush = new SolidColorBrush(secondary); + } +} ``` -### Color Schemes +## Data Structures + +### ClusterData ```csharp -// Generate complementary colors -var complementary = ColorUtils.GetComplementary(baseColor); +public class ClusterData +{ + public Color CenterColor { get; set; } + public List Colors { get; set; } + public int Count { get; set; } +} +``` + +## Performance Considerations + +- **Image Size** - Larger images take longer to process +- **Color Depth** - More unique colors increase processing time +- **Caching** - Cache results for frequently analyzed images +- **Async Processing** - Consider async for large images + +## Common Use Cases -// Generate color palette -var palette = ColorUtils.GeneratePalette(baseColor, 5); +### Use Case 1: Music Player UI + +```csharp +public class MusicPlayer +{ + public void UpdateUIFromAlbumArt(Bitmap albumArt) + { + var calculator = new BrightestColorCalculator(); + var dominantColor = calculator.Calculate(albumArt); + + // Update player UI colors + playerBackground.Background = new SolidColorBrush(dominantColor); + progressBar.Foreground = new SolidColorBrush(dominantColor); + } +} ``` -## Architecture +### Use Case 2: Image Gallery +```csharp +public class ImageGallery +{ + public void GenerateThumbnailColors(List imagePaths) + { + var calculator = new BrightestColorCalculator(); + + foreach (var path in imagePaths) + { + var bitmap = new Bitmap(path); + var color = calculator.Calculate(bitmap); + + // Store color for thumbnail border + thumbnailColors[path] = color; + } + } +} ``` -DevBase.Avalonia/ -├── Color/ # Color utilities -│ ├── ColorUtils.cs # Color manipulation -│ ├── ColorConverter.cs # Format conversion -│ └── ... -└── Data/ # Data helpers + +### Use Case 3: Adaptive UI + +```csharp +public class AdaptiveUI +{ + public void AdaptToImage(Bitmap image) + { + var groups = new GroupColorCalculator().Calculate(image); + var brightest = new BrightestColorCalculator().Calculate(image); + + // Determine if image is light or dark + bool isLight = CalculateBrightness(brightest) > 0.5; + + // Adjust UI accordingly + if (isLight) + { + textColor = Colors.Black; + backgroundColor = Colors.White; + } + else + { + textColor = Colors.White; + backgroundColor = Colors.Black; + } + } +} ``` +## Target Framework + +- **.NET 9.0** +- **Avalonia UI 11.x** + ## Dependencies -- Avalonia (11.x) -- System.Drawing.Common -- DevBase (core library) -- DevBase.Cryptography +- **Avalonia** - UI framework +- **Avalonia.Media** - Color and imaging +- **DevBase** - Core utilities ## License -MIT License - see LICENSE file for details. +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/DevBase.Cryptography.BouncyCastle/AGENT.md b/DevBase.Cryptography.BouncyCastle/AGENT.md index 3289955..ba6982f 100644 --- a/DevBase.Cryptography.BouncyCastle/AGENT.md +++ b/DevBase.Cryptography.BouncyCastle/AGENT.md @@ -1,42 +1,426 @@ -# DevBase.Cryptography.BouncyCastle Agent Guide +# DevBase.Cryptography.BouncyCastle - AI Agent Guide + +This guide helps AI agents effectively use DevBase.Cryptography.BouncyCastle for advanced cryptographic operations. ## Overview -DevBase.Cryptography.BouncyCastle provides modern cryptographic implementations using the BouncyCastle library. -## Key Classes +DevBase.Cryptography.BouncyCastle provides enterprise-grade cryptography using BouncyCastle: +- **AES-GCM** - Authenticated encryption +- **ECDH** - Key exchange +- **Token Verification** - JWT and signature verification +- **Secure Random** - Cryptographically secure RNG -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `AesGcm` | `DevBase.Cryptography.BouncyCastle.AesGcm` | AES-GCM authenticated encryption | -| `Blowfish` | `DevBase.Cryptography.BouncyCastle.Blowfish` | Enhanced Blowfish | +**Target Framework:** .NET 9.0 -## Quick Reference +## Core Components + +### AESBuilderEngine + +```csharp +public class AESBuilderEngine +{ + public AESBuilderEngine(); + public byte[] Encrypt(byte[] buffer); + public byte[] Decrypt(byte[] buffer); + public void SetKey(byte[] key); +} +``` + +### EcdhEngineBuilder + +```csharp +public class EcdhEngineBuilder +{ + public (AsymmetricKeyParameter Public, AsymmetricKeyParameter Private) GenerateKeyPair(); + public byte[] DeriveSharedSecret(AsymmetricKeyParameter privateKey, AsymmetricKeyParameter publicKey); + public byte[] ExportPublicKey(AsymmetricKeyParameter publicKey); + public AsymmetricKeyParameter ImportPublicKey(byte[] publicKeyBytes); +} +``` + +### Token Verifiers + +```csharp +RsTokenVerifier // RSA signatures (RS256, RS384, RS512) +EsTokenVerifier // ECDSA signatures (ES256, ES384, ES512) +PsTokenVerifier // RSA-PSS signatures (PS256, PS384, PS512) +ShaTokenVerifier // HMAC signatures (HS256, HS384, HS512) +``` + +## Usage Patterns for AI Agents + +### Pattern 1: AES-GCM Encryption + +```csharp +using DevBase.Cryptography.BouncyCastle.AES; + +// Create engine (auto-generates key) +var aes = new AESBuilderEngine(); + +// Encrypt +byte[] plaintext = Encoding.UTF8.GetBytes("Secret message"); +byte[] encrypted = aes.Encrypt(plaintext); + +// Decrypt +byte[] decrypted = aes.Decrypt(encrypted); +string message = Encoding.UTF8.GetString(decrypted); +``` + +### Pattern 2: ECDH Key Exchange + +```csharp +using DevBase.Cryptography.BouncyCastle.ECDH; + +// Alice generates key pair +var alice = new EcdhEngineBuilder(); +var (alicePublic, alicePrivate) = alice.GenerateKeyPair(); + +// Bob generates key pair +var bob = new EcdhEngineBuilder(); +var (bobPublic, bobPrivate) = bob.GenerateKeyPair(); + +// Alice derives shared secret +byte[] aliceShared = alice.DeriveSharedSecret(alicePrivate, bobPublic); + +// Bob derives shared secret +byte[] bobShared = bob.DeriveSharedSecret(bobPrivate, alicePublic); + +// Both secrets are identical +// Use shared secret as encryption key +var aes = new AESBuilderEngine(); +aes.SetKey(aliceShared); +``` + +### Pattern 3: JWT Verification + +```csharp +using DevBase.Cryptography.BouncyCastle.Hashing.Verification; + +string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."; +string publicKey = "-----BEGIN PUBLIC KEY-----\n..."; + +// Verify RSA signature +var rsVerifier = new RsTokenVerifier(); +bool isValid = rsVerifier.Verify(token, publicKey); + +// Verify ECDSA signature +var esVerifier = new EsTokenVerifier(); +bool isValid = esVerifier.Verify(token, publicKey); + +// Verify HMAC signature +var shaVerifier = new ShaTokenVerifier(); +bool isValid = shaVerifier.Verify(token, secret); +``` + +### Pattern 4: Secure Random Generation + +```csharp +using DevBase.Cryptography.BouncyCastle.Random; + +var random = new Random(); + +// Generate random bytes +byte[] randomBytes = random.GenerateRandom(32); + +// Generate random string +string randomString = random.GenerateRandomString(16); + +// Generate random number +int randomNumber = random.GenerateRandomInt(1, 100); +``` + +### Pattern 5: Secure Communication + +```csharp +public class SecureMessaging +{ + public byte[] EncryptMessage(string message, byte[] recipientPublicKey) + { + // Generate ephemeral key pair + var ecdh = new EcdhEngineBuilder(); + var (ephemeralPublic, ephemeralPrivate) = ecdh.GenerateKeyPair(); + + // Derive shared secret + byte[] sharedSecret = ecdh.DeriveSharedSecret(ephemeralPrivate, recipientPublicKey); + + // Encrypt with AES-GCM + var aes = new AESBuilderEngine(); + aes.SetKey(sharedSecret); + byte[] encrypted = aes.Encrypt(Encoding.UTF8.GetBytes(message)); + + // Package: [ephemeral public key length][ephemeral public key][encrypted data] + byte[] publicKeyBytes = ecdh.ExportPublicKey(ephemeralPublic); + + using var ms = new MemoryStream(); + ms.Write(BitConverter.GetBytes(publicKeyBytes.Length), 0, 4); + ms.Write(publicKeyBytes, 0, publicKeyBytes.Length); + ms.Write(encrypted, 0, encrypted.Length); + + return ms.ToArray(); + } + + public string DecryptMessage(byte[] package, byte[] privateKey) + { + using var ms = new MemoryStream(package); + + // Extract ephemeral public key + byte[] lengthBytes = new byte[4]; + ms.Read(lengthBytes, 0, 4); + int keyLength = BitConverter.ToInt32(lengthBytes, 0); + + byte[] ephemeralPublicKey = new byte[keyLength]; + ms.Read(ephemeralPublicKey, 0, keyLength); + + // Extract encrypted data + byte[] encrypted = new byte[ms.Length - ms.Position]; + ms.Read(encrypted, 0, encrypted.Length); + + // Derive shared secret + var ecdh = new EcdhEngineBuilder(); + var publicKey = ecdh.ImportPublicKey(ephemeralPublicKey); + byte[] sharedSecret = ecdh.DeriveSharedSecret(privateKey, publicKey); + + // Decrypt + var aes = new AESBuilderEngine(); + aes.SetKey(sharedSecret); + byte[] decrypted = aes.Decrypt(encrypted); + + return Encoding.UTF8.GetString(decrypted); + } +} +``` + +## Important Concepts + +### 1. AES-GCM Features + +- **Authenticated Encryption** - Provides both confidentiality and authenticity +- **Nonce** - Automatically generated and prepended to ciphertext +- **Tag** - Authentication tag included in output +- **No Padding** - GCM mode doesn't require padding + +```csharp +// AES-GCM automatically handles: +// - Nonce generation +// - Tag computation +// - Nonce prepending to ciphertext + +var aes = new AESBuilderEngine(); +byte[] encrypted = aes.Encrypt(data); // Nonce + encrypted + tag +byte[] decrypted = aes.Decrypt(encrypted); // Verifies tag, extracts nonce +``` + +### 2. ECDH Key Exchange + +```csharp +// Generate key pairs +var alice = new EcdhEngineBuilder(); +var (alicePublic, alicePrivate) = alice.GenerateKeyPair(); + +var bob = new EcdhEngineBuilder(); +var (bobPublic, bobPrivate) = bob.GenerateKeyPair(); + +// Exchange public keys (can be sent over insecure channel) +byte[] alicePublicBytes = alice.ExportPublicKey(alicePublic); +byte[] bobPublicBytes = bob.ExportPublicKey(bobPublic); + +// Derive shared secrets (identical on both sides) +byte[] aliceShared = alice.DeriveSharedSecret(alicePrivate, bobPublic); +byte[] bobShared = bob.DeriveSharedSecret(bobPrivate, alicePublic); + +// Use shared secret as encryption key +var aes = new AESBuilderEngine(); +aes.SetKey(aliceShared); +``` + +### 3. Token Verification Algorithms + +| Verifier | Algorithms | Key Type | +|----------|-----------|----------| +| RsTokenVerifier | RS256, RS384, RS512 | RSA Public Key | +| EsTokenVerifier | ES256, ES384, ES512 | ECDSA Public Key | +| PsTokenVerifier | PS256, PS384, PS512 | RSA Public Key (PSS) | +| ShaTokenVerifier | HS256, HS384, HS512 | Shared Secret | -### AES-GCM Encryption ```csharp -using DevBase.Cryptography.BouncyCastle.AesGcm; +// Select verifier based on algorithm +string algorithm = ExtractAlgorithmFromToken(token); + +bool isValid = algorithm switch +{ + "RS256" or "RS384" or "RS512" => new RsTokenVerifier().Verify(token, publicKey), + "ES256" or "ES384" or "ES512" => new EsTokenVerifier().Verify(token, publicKey), + "PS256" or "PS384" or "PS512" => new PsTokenVerifier().Verify(token, publicKey), + "HS256" or "HS384" or "HS512" => new ShaTokenVerifier().Verify(token, secret), + _ => false +}; +``` + +## Common Mistakes to Avoid -byte[] key = new byte[32]; // 256-bit key -byte[] nonce = new byte[12]; // 96-bit nonce -byte[] plaintext = Encoding.UTF8.GetBytes("secret data"); +### ❌ Mistake 1: Reusing Nonces + +```csharp +// Wrong - AES-GCM handles nonces automatically +// Don't try to manage nonces manually -AesGcm aes = new AesGcm(key); -byte[] ciphertext = aes.Encrypt(nonce, plaintext); -byte[] decrypted = aes.Decrypt(nonce, ciphertext); +// Correct - let AESBuilderEngine handle it +var aes = new AESBuilderEngine(); +byte[] encrypted1 = aes.Encrypt(data1); // New nonce +byte[] encrypted2 = aes.Encrypt(data2); // New nonce ``` -## File Structure +### ❌ Mistake 2: Wrong Verifier for Algorithm + +```csharp +// Wrong - using RS verifier for ES token +var verifier = new RsTokenVerifier(); +bool isValid = verifier.Verify(es256Token, publicKey); // Will fail! + +// Correct - match verifier to algorithm +var verifier = new EsTokenVerifier(); +bool isValid = verifier.Verify(es256Token, publicKey); ``` -DevBase.Cryptography.BouncyCastle/ -├── AesGcm/ -│ └── AesGcm.cs -└── Blowfish/ - └── Blowfish.cs + +### ❌ Mistake 3: Not Storing Private Keys Securely + +```csharp +// Wrong - hardcoding private key +byte[] privateKey = new byte[] { 1, 2, 3, ... }; + +// Correct - load from secure storage +byte[] privateKey = LoadFromSecureStorage("private-key"); ``` -## Important Notes +### ❌ Mistake 4: Using Same Key for Everything + +```csharp +// Wrong - using same key for multiple purposes +var aes = new AESBuilderEngine(); +aes.SetKey(masterKey); +byte[] encrypted1 = aes.Encrypt(userData); +byte[] encrypted2 = aes.Encrypt(configData); + +// Correct - derive different keys for different purposes +byte[] userDataKey = DeriveKey(masterKey, "user-data"); +byte[] configDataKey = DeriveKey(masterKey, "config-data"); +``` + +## Security Best Practices + +1. **Key Management** + - Never hardcode keys + - Use secure key storage (Azure Key Vault, AWS KMS, etc.) + - Rotate keys regularly + +2. **Algorithm Selection** + - Use AES-GCM for encryption (not CBC) + - Use ECDH for key exchange (not RSA) + - Use ES256 for signatures when possible + +3. **Random Generation** + - Always use `DevBase.Cryptography.BouncyCastle.Random` + - Never use `System.Random` for cryptographic purposes + +4. **Token Verification** + - Always verify token signatures + - Check token expiration + - Validate token claims + +## Performance Tips + +1. **Reuse instances** - Create AESBuilderEngine once, use multiple times +2. **Key caching** - Cache derived ECDH shared secrets +3. **Async operations** - Use async for large data encryption +4. **Buffer pooling** - Reuse byte arrays when possible + +## Integration Examples + +### With DevBase.Api + +```csharp +using DevBase.Api.Apis; +using DevBase.Cryptography.BouncyCastle.Hashing.Verification; + +public class SecureApiClient : ApiClient +{ + private readonly RsTokenVerifier _verifier; + + public SecureApiClient() + { + _verifier = new RsTokenVerifier(); + } + + public async Task GetSecureAsync(string url, string token, string publicKey) + { + // Verify token first + if (!_verifier.Verify(token, publicKey)) + return Throw(new SecurityException("Invalid token")); + + // Make request + var response = await new Request(url) + .WithHeader("Authorization", $"Bearer {token}") + .SendAsync(); + + return await response.ParseJsonAsync(); + } +} +``` + +### With DevBase.Net + +```csharp +using DevBase.Net.Core; +using DevBase.Cryptography.BouncyCastle.AES; + +public class EncryptedHttpClient +{ + private readonly AESBuilderEngine _aes; + + public EncryptedHttpClient(byte[] encryptionKey) + { + _aes = new AESBuilderEngine(); + _aes.SetKey(encryptionKey); + } + + public async Task PostEncryptedAsync(string url, byte[] data) + { + byte[] encrypted = _aes.Encrypt(data); + + return await new Request(url) + .AsPost() + .WithRawBody(encrypted, "application/octet-stream") + .SendAsync(); + } +} +``` + +## Quick Reference + +| Task | Code | +|------|------| +| AES encrypt | `new AESBuilderEngine().Encrypt(data)` | +| AES decrypt | `aes.Decrypt(encrypted)` | +| Generate key pair | `new EcdhEngineBuilder().GenerateKeyPair()` | +| Derive shared secret | `ecdh.DeriveSharedSecret(privateKey, publicKey)` | +| Verify RS token | `new RsTokenVerifier().Verify(token, publicKey)` | +| Verify ES token | `new EsTokenVerifier().Verify(token, publicKey)` | +| Verify HMAC token | `new ShaTokenVerifier().Verify(token, secret)` | +| Generate random | `new Random().GenerateRandom(32)` | + +## Testing Considerations + +- Test encryption/decryption round-trip +- Test key exchange with multiple parties +- Test token verification with valid/invalid tokens +- Test with different key sizes +- Verify random generation quality + +## Version + +Current version: **1.0.0** +Target framework: **.NET 9.0** + +## Dependencies -1. **Requires BouncyCastle.NetCore** NuGet package -2. **Use unique nonces** for each encryption -3. **AES-GCM provides authenticated encryption** (integrity + confidentiality) -4. **Prefer this over DevBase.Cryptography** for security +- BouncyCastle.Cryptography diff --git a/DevBase.Cryptography.BouncyCastle/README.md b/DevBase.Cryptography.BouncyCastle/README.md index 361d1a7..e8e2a98 100644 --- a/DevBase.Cryptography.BouncyCastle/README.md +++ b/DevBase.Cryptography.BouncyCastle/README.md @@ -1,148 +1,402 @@ # DevBase.Cryptography.BouncyCastle -A comprehensive cryptography wrapper library built on BouncyCastle, providing advanced encryption, key exchange, and token verification capabilities. +**DevBase.Cryptography.BouncyCastle** provides advanced cryptographic operations using the BouncyCastle library, including AES-GCM encryption, ECDH key exchange, token verification, and secure random generation. ## Features -- **AES Encryption** - Advanced Encryption Standard with builder pattern -- **ECDH Key Exchange** - Elliptic Curve Diffie-Hellman key agreement -- **Token Verification** - JWT and cryptographic token validation - - HMAC (HS256, HS384, HS512) - - RSA (RS256, RS384, RS512) - - ECDSA (ES256, ES384, ES512) - - RSA-PSS (PS256, PS384, PS512) -- **Secure Random** - Cryptographically secure random number generation -- **Data Sealing** - Secure data packaging +- **AES-GCM Encryption** - Authenticated encryption with GCM mode +- **ECDH Key Exchange** - Elliptic Curve Diffie-Hellman +- **Token Verification** - Multiple signature algorithms (RSA, ECDSA, HMAC) +- **Secure Random** - Cryptographically secure random generation +- **Key Management** - Asymmetric key pair generation and storage +- **Data Sealing** - Encrypt and seal data with authentication ## Installation -```xml - -``` - -Or via NuGet CLI: - ```bash dotnet add package DevBase.Cryptography.BouncyCastle ``` -## Usage Examples +## AES-GCM Encryption + +AES-GCM provides authenticated encryption with associated data (AEAD). -### AES Encryption +### Basic Usage ```csharp using DevBase.Cryptography.BouncyCastle.AES; -// Create AES engine with builder -AESBuilderEngine aes = new AESBuilderEngine() - .WithKey(keyBytes) - .WithIV(ivBytes) - .Build(); +// Create engine (generates random key) +var aes = new AESBuilderEngine(); // Encrypt +byte[] plaintext = Encoding.UTF8.GetBytes("Secret message"); byte[] encrypted = aes.Encrypt(plaintext); // Decrypt byte[] decrypted = aes.Decrypt(encrypted); +string message = Encoding.UTF8.GetString(decrypted); ``` -### ECDH Key Exchange +### With Custom Key + +```csharp +var aes = new AESBuilderEngine(); + +// Set custom key (32 bytes for AES-256) +byte[] customKey = new byte[32]; +RandomNumberGenerator.Fill(customKey); +aes.SetKey(customKey); + +byte[] encrypted = aes.Encrypt(data); +``` + +## ECDH Key Exchange + +Elliptic Curve Diffie-Hellman for secure key exchange. + +### Generate Key Pair ```csharp using DevBase.Cryptography.BouncyCastle.ECDH; -// Create key exchange builder -EcdhEngineBuilder ecdh = new EcdhEngineBuilder(); +var ecdh = new EcdhEngineBuilder(); // Generate key pair -var keyPair = ecdh.GenerateKeyPair(); +var (publicKey, privateKey) = ecdh.GenerateKeyPair(); -// Derive shared secret -byte[] sharedSecret = ecdh.DeriveSharedSecret( - privateKey, - otherPartyPublicKey -); +// Export public key for sharing +byte[] publicKeyBytes = ecdh.ExportPublicKey(publicKey); ``` -### Token Verification (JWT) +### Derive Shared Secret ```csharp -using DevBase.Cryptography.BouncyCastle.Hashing; +// Alice generates key pair +var alice = new EcdhEngineBuilder(); +var (alicePublic, alicePrivate) = alice.GenerateKeyPair(); + +// Bob generates key pair +var bob = new EcdhEngineBuilder(); +var (bobPublic, bobPrivate) = bob.GenerateKeyPair(); -// Symmetric verification (HMAC) -SymmetricTokenVerifier verifier = new SymmetricTokenVerifier(); -bool isValid = verifier.Verify(tokenData, signature, secretKey, "HS256"); +// Alice derives shared secret using Bob's public key +byte[] aliceShared = alice.DeriveSharedSecret(alicePrivate, bobPublic); -// Asymmetric verification (RSA/ECDSA) -AsymmetricTokenVerifier asymVerifier = new AsymmetricTokenVerifier(); -bool isValid = asymVerifier.Verify(tokenData, signature, publicKey, "RS256"); +// Bob derives shared secret using Alice's public key +byte[] bobShared = bob.DeriveSharedSecret(bobPrivate, alicePublic); + +// Both shared secrets are identical +Assert.Equal(aliceShared, bobShared); ``` -### Secure Random +## Token Verification + +Verify JWT and other signed tokens. + +### RSA Token Verification + +```csharp +using DevBase.Cryptography.BouncyCastle.Hashing.Verification; + +var verifier = new RsTokenVerifier(); + +string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."; +string publicKey = "-----BEGIN PUBLIC KEY-----\n..."; + +bool isValid = verifier.Verify(token, publicKey); +``` + +### ECDSA Token Verification + +```csharp +var verifier = new EsTokenVerifier(); +bool isValid = verifier.Verify(token, publicKey); +``` + +### HMAC Token Verification + +```csharp +var verifier = new ShaTokenVerifier(); +string secret = "your-secret-key"; +bool isValid = verifier.Verify(token, secret); +``` + +### PSS Token Verification + +```csharp +var verifier = new PsTokenVerifier(); +bool isValid = verifier.Verify(token, publicKey); +``` + +## Secure Random Generation + +Cryptographically secure random number generation. + +### Generate Random Bytes ```csharp using DevBase.Cryptography.BouncyCastle.Random; +var random = new Random(); + // Generate random bytes -byte[] randomBytes = Random.GenerateBytes(32); +byte[] randomBytes = random.GenerateRandom(32); + +// Generate random string +string randomString = random.GenerateRandomString(16); // Generate random number -int randomNumber = Random.GenerateInt(1, 100); +int randomNumber = random.GenerateRandomInt(1, 100); ``` -### Data Sealing +## Data Sealing + +Encrypt and seal data with authentication. + +### Seal Data ```csharp using DevBase.Cryptography.BouncyCastle.Sealing; -// Seal data -Sealing sealer = new Sealing(); -byte[] sealed = sealer.Seal(data, key); +var sealing = new Sealing(); + +byte[] data = Encoding.UTF8.GetBytes("Sensitive data"); +byte[] key = new byte[32]; +RandomNumberGenerator.Fill(key); + +// Seal data (encrypt + authenticate) +byte[] sealed = sealing.Seal(data, key); -// Unseal data -byte[] unsealed = sealer.Unseal(sealed, key); +// Unseal data (verify + decrypt) +byte[] unsealed = sealing.Unseal(sealed, key); ``` -## Architecture +## Usage Examples + +### Secure Communication +```csharp +using DevBase.Cryptography.BouncyCastle.ECDH; +using DevBase.Cryptography.BouncyCastle.AES; + +public class SecureChannel +{ + public async Task SendSecureMessage(string message, byte[] recipientPublicKey) + { + // Generate ephemeral key pair + var ecdh = new EcdhEngineBuilder(); + var (ephemeralPublic, ephemeralPrivate) = ecdh.GenerateKeyPair(); + + // Derive shared secret + byte[] sharedSecret = ecdh.DeriveSharedSecret(ephemeralPrivate, recipientPublicKey); + + // Encrypt message with shared secret + var aes = new AESBuilderEngine(); + aes.SetKey(sharedSecret); + byte[] encrypted = aes.Encrypt(Encoding.UTF8.GetBytes(message)); + + // Include ephemeral public key with encrypted message + byte[] publicKeyBytes = ecdh.ExportPublicKey(ephemeralPublic); + + using var ms = new MemoryStream(); + ms.Write(BitConverter.GetBytes(publicKeyBytes.Length), 0, 4); + ms.Write(publicKeyBytes, 0, publicKeyBytes.Length); + ms.Write(encrypted, 0, encrypted.Length); + + return ms.ToArray(); + } + + public string ReceiveSecureMessage(byte[] encryptedPackage, byte[] privateKey) + { + using var ms = new MemoryStream(encryptedPackage); + + // Read ephemeral public key + byte[] lengthBytes = new byte[4]; + ms.Read(lengthBytes, 0, 4); + int publicKeyLength = BitConverter.ToInt32(lengthBytes, 0); + + byte[] ephemeralPublicKey = new byte[publicKeyLength]; + ms.Read(ephemeralPublicKey, 0, publicKeyLength); + + // Read encrypted message + byte[] encrypted = new byte[ms.Length - ms.Position]; + ms.Read(encrypted, 0, encrypted.Length); + + // Derive shared secret + var ecdh = new EcdhEngineBuilder(); + var publicKey = ecdh.ImportPublicKey(ephemeralPublicKey); + byte[] sharedSecret = ecdh.DeriveSharedSecret(privateKey, publicKey); + + // Decrypt message + var aes = new AESBuilderEngine(); + aes.SetKey(sharedSecret); + byte[] decrypted = aes.Decrypt(encrypted); + + return Encoding.UTF8.GetString(decrypted); + } +} ``` -DevBase.Cryptography.BouncyCastle/ -├── AES/ -│ └── AESBuilderEngine.cs # AES encryption builder -├── ECDH/ -│ └── EcdhEngineBuilder.cs # ECDH key exchange -├── Hashing/ -│ ├── SymmetricTokenVerifier.cs # HMAC verification -│ ├── AsymmetricTokenVerifier.cs # RSA/ECDSA verification -│ └── Verification/ -│ ├── ShaTokenVerifier.cs # SHA-based HMAC -│ ├── RsTokenVerifier.cs # RSA signature -│ ├── EsTokenVerifier.cs # ECDSA signature -│ └── PsTokenVerifier.cs # RSA-PSS signature -├── Random/ -│ └── Random.cs # Secure random generator -├── Sealing/ -│ └── Sealing.cs # Data sealing -├── Identifier/ -│ └── Identification.cs # Key identification -└── Extensions/ - └── AsymmetricKeyParameterExtension.cs + +### JWT Verification + +```csharp +using DevBase.Cryptography.BouncyCastle.Hashing.Verification; + +public class JwtValidator +{ + public bool ValidateToken(string token, string publicKey, string algorithm) + { + return algorithm switch + { + "RS256" or "RS384" or "RS512" => new RsTokenVerifier().Verify(token, publicKey), + "ES256" or "ES384" or "ES512" => new EsTokenVerifier().Verify(token, publicKey), + "PS256" or "PS384" or "PS512" => new PsTokenVerifier().Verify(token, publicKey), + "HS256" or "HS384" or "HS512" => new ShaTokenVerifier().Verify(token, publicKey), + _ => false + }; + } +} +``` + +### Secure File Storage + +```csharp +using DevBase.Cryptography.BouncyCastle.AES; +using DevBase.Cryptography.BouncyCastle.Random; + +public class SecureFileStorage +{ + private readonly AESBuilderEngine _aes; + private readonly byte[] _masterKey; + + public SecureFileStorage(byte[] masterKey) + { + _masterKey = masterKey; + _aes = new AESBuilderEngine(); + _aes.SetKey(masterKey); + } + + public void SaveSecure(string filePath, byte[] data) + { + byte[] encrypted = _aes.Encrypt(data); + File.WriteAllBytes(filePath, encrypted); + } + + public byte[] LoadSecure(string filePath) + { + byte[] encrypted = File.ReadAllBytes(filePath); + return _aes.Decrypt(encrypted); + } +} ``` -## Supported Algorithms +### API Authentication -### Symmetric -- AES (128, 192, 256 bit) -- HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 +```csharp +using DevBase.Cryptography.BouncyCastle.Hashing.Verification; + +public class ApiAuthenticator +{ + private readonly Dictionary _publicKeys; + + public bool AuthenticateRequest(string authHeader) + { + // Extract token from header + string token = authHeader.Replace("Bearer ", ""); + + // Extract key ID from token + string keyId = ExtractKeyId(token); + + if (!_publicKeys.ContainsKey(keyId)) + return false; + + // Verify token + var verifier = new RsTokenVerifier(); + return verifier.Verify(token, _publicKeys[keyId]); + } +} +``` -### Asymmetric -- RSA (RS256, RS384, RS512) -- RSA-PSS (PS256, PS384, PS512) -- ECDSA (ES256, ES384, ES512) +## Key Management + +### Generate and Store Keys + +```csharp +using DevBase.Cryptography.BouncyCastle.ECDH; +using DevBase.Cryptography.BouncyCastle.Extensions; + +public class KeyManager +{ + public void GenerateAndSaveKeyPair(string publicKeyPath, string privateKeyPath) + { + var ecdh = new EcdhEngineBuilder(); + var (publicKey, privateKey) = ecdh.GenerateKeyPair(); + + // Export keys + byte[] publicKeyBytes = publicKey.ExportPublicKey(); + byte[] privateKeyBytes = privateKey.ExportPrivateKey(); + + // Save to files + File.WriteAllBytes(publicKeyPath, publicKeyBytes); + File.WriteAllBytes(privateKeyPath, privateKeyBytes); + } + + public (byte[] PublicKey, byte[] PrivateKey) LoadKeyPair(string publicKeyPath, string privateKeyPath) + { + byte[] publicKey = File.ReadAllBytes(publicKeyPath); + byte[] privateKey = File.ReadAllBytes(privateKeyPath); + + return (publicKey, privateKey); + } +} +``` + +## Security Best Practices + +1. **Key Storage** - Never hardcode keys, use secure key storage +2. **Key Rotation** - Regularly rotate encryption keys +3. **Random Generation** - Always use cryptographically secure random +4. **Token Expiration** - Implement token expiration checks +5. **Algorithm Selection** - Use modern algorithms (AES-GCM, ECDH, ES256) + +## Algorithm Support + +### Encryption +- **AES-GCM** - 128, 192, 256-bit keys ### Key Exchange -- ECDH (various curves) +- **ECDH** - P-256, P-384, P-521 curves + +### Signatures +- **RSA** - RS256, RS384, RS512 +- **ECDSA** - ES256, ES384, ES512 +- **RSA-PSS** - PS256, PS384, PS512 +- **HMAC** - HS256, HS384, HS512 + +## Performance Considerations + +- **AES-GCM** - Fast and secure for bulk encryption +- **ECDH** - Faster than RSA for key exchange +- **Key Caching** - Cache derived keys when possible +- **Async Operations** - Consider async for large data + +## Target Framework + +- **.NET 9.0** + +## Dependencies + +- **BouncyCastle.Cryptography** - Cryptographic primitives ## License -MIT License - see LICENSE file for details. +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/DevBase.Cryptography/AGENT.md b/DevBase.Cryptography/AGENT.md index 67e9b10..c63f6c0 100644 --- a/DevBase.Cryptography/AGENT.md +++ b/DevBase.Cryptography/AGENT.md @@ -1,47 +1,435 @@ -# DevBase.Cryptography Agent Guide +# DevBase.Cryptography - AI Agent Guide + +This guide helps AI agents effectively use the DevBase.Cryptography library for encryption and hashing operations. ## Overview -DevBase.Cryptography provides basic cryptographic implementations including Blowfish and MD5. -## Key Classes +DevBase.Cryptography provides: +- **Blowfish** - Symmetric block cipher (CBC mode) +- **MD5** - Fast hashing for checksums -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `Blowfish` | `DevBase.Cryptography.Blowfish` | Blowfish encryption/decryption | -| `MD5` | `DevBase.Cryptography.Hash` | MD5 hashing | +**Target Framework:** .NET 9.0 -## Quick Reference +## Core Components + +### Blowfish Class + +```csharp +public sealed class Blowfish +{ + public Blowfish(byte[] key); + public bool Encrypt(Span data, ReadOnlySpan initVector); + public bool Decrypt(Span data, ReadOnlySpan initVector); +} +``` + +### Codec Class + +```csharp +public sealed class Codec +{ + public Codec(byte[] key); + public void Encrypt(Span block); + public void Decrypt(Span block); +} +``` + +### MD5 Class + +```csharp +public static class MD5 +{ + public static string Hash(string input); + public static string Hash(byte[] data); +} +``` + +## Usage Patterns for AI Agents + +### Pattern 1: Basic Blowfish Encryption -### Blowfish Encryption ```csharp using DevBase.Cryptography.Blowfish; +using System.Security.Cryptography; + +// Prepare key +byte[] key = Encoding.UTF8.GetBytes("my-secret-key"); + +// Prepare data (MUST be multiple of 8 bytes) +string plaintext = "Hello World!"; +byte[] data = Encoding.UTF8.GetBytes(plaintext); + +// Pad to multiple of 8 +int paddedLength = (data.Length + 7) / 8 * 8; +byte[] paddedData = new byte[paddedLength]; +Array.Copy(data, paddedData, data.Length); -byte[] key = Encoding.UTF8.GetBytes("secret-key"); -byte[] data = Encoding.UTF8.GetBytes("data to encrypt"); +// Generate IV (MUST be 8 bytes) +byte[] iv = new byte[8]; +RandomNumberGenerator.Fill(iv); -Blowfish blowfish = new Blowfish(key); -byte[] encrypted = blowfish.Encrypt(data); -byte[] decrypted = blowfish.Decrypt(encrypted); +// Encrypt +var blowfish = new Blowfish(key); +bool success = blowfish.Encrypt(paddedData, iv); + +if (success) +{ + Console.WriteLine("Encrypted successfully"); +} ``` -### MD5 Hashing +### Pattern 2: Basic Blowfish Decryption + ```csharp -using DevBase.Cryptography.Hash; +// Decrypt (using same key and IV) +var blowfish = new Blowfish(key); +bool success = blowfish.Decrypt(paddedData, iv); -string hash = MD5.ComputeHash("input string"); +if (success) +{ + // Remove padding + string decrypted = Encoding.UTF8.GetString(paddedData).TrimEnd('\0'); + Console.WriteLine($"Decrypted: {decrypted}"); +} ``` -## File Structure +### Pattern 3: Complete Encryption/Decryption Flow + +```csharp +public byte[] EncryptData(string plaintext, string password) +{ + // Generate key + byte[] key = Encoding.UTF8.GetBytes(password); + + // Pad data + byte[] data = Encoding.UTF8.GetBytes(plaintext); + int paddedLength = (data.Length + 7) / 8 * 8; + byte[] paddedData = new byte[paddedLength]; + Array.Copy(data, paddedData, data.Length); + + // Generate IV + byte[] iv = new byte[8]; + RandomNumberGenerator.Fill(iv); + + // Encrypt + var blowfish = new Blowfish(key); + blowfish.Encrypt(paddedData, iv); + + // Combine IV + encrypted data + byte[] result = new byte[8 + paddedData.Length]; + Array.Copy(iv, 0, result, 0, 8); + Array.Copy(paddedData, 0, result, 8, paddedData.Length); + + return result; +} + +public string DecryptData(byte[] encrypted, string password) +{ + // Extract IV + byte[] iv = new byte[8]; + Array.Copy(encrypted, 0, iv, 0, 8); + + // Extract data + byte[] data = new byte[encrypted.Length - 8]; + Array.Copy(encrypted, 8, data, 0, data.Length); + + // Decrypt + byte[] key = Encoding.UTF8.GetBytes(password); + var blowfish = new Blowfish(key); + blowfish.Decrypt(data, iv); + + // Remove padding + return Encoding.UTF8.GetString(data).TrimEnd('\0'); +} ``` -DevBase.Cryptography/ -├── Blowfish/ -│ └── Blowfish.cs -└── Hash/ - └── MD5.cs + +### Pattern 4: MD5 Hashing + +```csharp +using DevBase.Cryptography.MD5; + +// Hash string +string input = "Hello World"; +string hash = MD5.Hash(input); +Console.WriteLine(hash); // "b10a8db164e0754105b7a99be72e3fe5" + +// Hash bytes +byte[] data = Encoding.UTF8.GetBytes("Hello World"); +string hash = MD5.Hash(data); + +// Verify integrity +bool isValid = MD5.Hash(data) == expectedHash; +``` + +### Pattern 5: Deezer Audio Decryption + +```csharp +using DevBase.Cryptography.Blowfish; +using DevBase.Cryptography.MD5; + +public byte[] DecryptDeezerAudio(byte[] encryptedData, string trackId) +{ + // Generate Deezer key + string secret = "g4el58wc0zvf9na1"; + string idMd5 = MD5.Hash(trackId); + + byte[] key = new byte[16]; + for (int i = 0; i < 16; i++) + { + key[i] = (byte)(idMd5[i] ^ idMd5[i + 16] ^ secret[i]); + } + + // Decrypt in 2048-byte chunks + var blowfish = new Blowfish(key); + byte[] iv = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }; + + for (int i = 0; i < encryptedData.Length; i += 2048) + { + int chunkSize = Math.Min(2048, encryptedData.Length - i); + + // Only decrypt if chunk is multiple of 8 + if (chunkSize % 8 == 0) + { + Span chunk = encryptedData.AsSpan(i, chunkSize); + blowfish.Decrypt(chunk, iv); + } + } + + return encryptedData; +} +``` + +### Pattern 6: File Encryption + +```csharp +public void EncryptFile(string inputPath, string outputPath, string password) +{ + // Read file + byte[] fileData = File.ReadAllBytes(inputPath); + + // Encrypt + byte[] encrypted = EncryptData( + Encoding.UTF8.GetString(fileData), + password + ); + + // Write encrypted file + File.WriteAllBytes(outputPath, encrypted); +} + +public void DecryptFile(string inputPath, string outputPath, string password) +{ + // Read encrypted file + byte[] encrypted = File.ReadAllBytes(inputPath); + + // Decrypt + string decrypted = DecryptData(encrypted, password); + + // Write decrypted file + File.WriteAllText(outputPath, decrypted); +} +``` + +## Important Concepts + +### 1. Blowfish Requirements + +**Critical Rules:** +- Data MUST be multiple of 8 bytes +- IV MUST be exactly 8 bytes +- Key can be any length (1-56 bytes recommended) + +```csharp +// ✅ Correct - 16 bytes (multiple of 8) +byte[] data = new byte[16]; + +// ❌ Wrong - 15 bytes (not multiple of 8) +byte[] data = new byte[15]; +blowfish.Encrypt(data, iv); // Returns false! + +// ✅ Correct - pad to 16 bytes +byte[] data = new byte[15]; +byte[] padded = new byte[16]; +Array.Copy(data, padded, data.Length); +blowfish.Encrypt(padded, iv); +``` + +### 2. Initialization Vector (IV) + +```csharp +// ✅ Correct - random IV +byte[] iv = new byte[8]; +RandomNumberGenerator.Fill(iv); + +// ❌ Wrong - zero IV (predictable) +byte[] iv = new byte[8]; // All zeros + +// ✅ Correct - store IV with encrypted data +byte[] result = new byte[8 + encryptedData.Length]; +Array.Copy(iv, result, 8); +Array.Copy(encryptedData, 0, result, 8, encryptedData.Length); ``` -## Important Notes +### 3. Padding Strategies + +```csharp +// Zero padding (simplest) +int paddedLength = (data.Length + 7) / 8 * 8; +byte[] padded = new byte[paddedLength]; +Array.Copy(data, padded, data.Length); +// Remaining bytes are zero + +// PKCS7 padding (standard) +int paddingLength = 8 - (data.Length % 8); +byte[] padded = new byte[data.Length + paddingLength]; +Array.Copy(data, padded, data.Length); +for (int i = data.Length; i < padded.Length; i++) +{ + padded[i] = (byte)paddingLength; +} +``` + +### 4. MD5 Usage + +```csharp +// ✅ Good - checksums, integrity verification +string fileHash = MD5.Hash(fileData); +bool isValid = fileHash == expectedHash; + +// ❌ Bad - password hashing (use bcrypt/Argon2 instead) +string passwordHash = MD5.Hash(password); // NOT SECURE! +``` + +## Common Mistakes to Avoid + +### ❌ Mistake 1: Data Not Multiple of 8 + +```csharp +// Wrong +byte[] data = Encoding.UTF8.GetBytes("Hello"); // 5 bytes +blowfish.Encrypt(data, iv); // Returns false! + +// Correct +byte[] data = Encoding.UTF8.GetBytes("Hello"); +byte[] padded = new byte[8]; +Array.Copy(data, padded, data.Length); +blowfish.Encrypt(padded, iv); +``` + +### ❌ Mistake 2: Wrong IV Size + +```csharp +// Wrong +byte[] iv = new byte[16]; // Too large! +blowfish.Encrypt(data, iv); // Returns false! + +// Correct +byte[] iv = new byte[8]; +RandomNumberGenerator.Fill(iv); +``` + +### ❌ Mistake 3: Not Storing IV + +```csharp +// Wrong - IV lost, can't decrypt! +byte[] encrypted = EncryptData(plaintext, password); +// IV not included in result + +// Correct - IV prepended to encrypted data +byte[] result = new byte[8 + encrypted.Length]; +Array.Copy(iv, result, 8); +Array.Copy(encrypted, 0, result, 8, encrypted.Length); +``` + +### ❌ Mistake 4: Reusing Same IV + +```csharp +// Wrong - same IV for multiple encryptions +byte[] iv = new byte[8]; +RandomNumberGenerator.Fill(iv); +blowfish.Encrypt(data1, iv); +blowfish.Encrypt(data2, iv); // Security risk! + +// Correct - new IV for each encryption +byte[] iv1 = new byte[8]; +RandomNumberGenerator.Fill(iv1); +blowfish.Encrypt(data1, iv1); + +byte[] iv2 = new byte[8]; +RandomNumberGenerator.Fill(iv2); +blowfish.Encrypt(data2, iv2); +``` + +### ❌ Mistake 5: Using MD5 for Passwords + +```csharp +// Wrong - MD5 is not secure for passwords +string passwordHash = MD5.Hash(password); + +// Correct - use proper password hashing +// (not in this library, use BCrypt.Net or similar) +``` + +## Security Best Practices + +1. **Never hardcode keys** - Load from secure storage +2. **Use random IVs** - Generate new IV for each encryption +3. **Store IV with data** - Prepend IV to encrypted data +4. **Secure key derivation** - Use PBKDF2 for password-based keys +5. **MD5 for checksums only** - Not for passwords or security + +## Performance Tips + +1. **Reuse Blowfish instances** for multiple operations with same key +2. **Use Span** for zero-allocation operations +3. **Batch operations** when encrypting multiple blocks +4. **Pre-allocate buffers** for padding operations + +## Integration with DevBase.Api + +Used by Deezer API for audio decryption: + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Cryptography.Blowfish; + +var deezer = new Deezer("arl-token"); +byte[] encryptedAudio = await deezer.DownloadTrack("123456"); + +// Decrypt using Blowfish +byte[] decrypted = DecryptDeezerAudio(encryptedAudio, "123456"); +``` + +## Quick Reference + +| Task | Code | +|------|------| +| Create Blowfish | `new Blowfish(keyBytes)` | +| Encrypt | `blowfish.Encrypt(data, iv)` | +| Decrypt | `blowfish.Decrypt(data, iv)` | +| Hash string | `MD5.Hash(text)` | +| Hash bytes | `MD5.Hash(bytes)` | +| Generate IV | `RandomNumberGenerator.Fill(iv)` | +| Pad data | `(length + 7) / 8 * 8` | + +## Testing Considerations + +- Test with various data sizes +- Test with edge cases (empty, single byte, exact multiples of 8) +- Test IV generation randomness +- Test encryption/decryption round-trip +- Test with different key sizes +- Verify MD5 hash consistency + +## Credits + +Blowfish implementation based on [jdvor/encryption-blowfish](https://github.com/jdvor/encryption-blowfish) (MIT License) + +## Version + +Current version: **1.0.0** +Target framework: **.NET 9.0** + +## Dependencies -1. **MD5 is not cryptographically secure** - use only for checksums -2. **Blowfish requires key setup** before encryption -3. **For modern crypto, use DevBase.Cryptography.BouncyCastle** +None (pure .NET) diff --git a/DevBase.Cryptography/README.md b/DevBase.Cryptography/README.md index c38b517..ec4a974 100644 --- a/DevBase.Cryptography/README.md +++ b/DevBase.Cryptography/README.md @@ -1,75 +1,357 @@ # DevBase.Cryptography -A lightweight cryptography library for .NET providing common encryption algorithms and hashing utilities. +**DevBase.Cryptography** provides cryptographic implementations for .NET 9.0, including Blowfish encryption and MD5 hashing. The library focuses on performance and ease of use. ## Features -- **Blowfish Encryption** - Symmetric block cipher implementation -- **MD5 Hashing** - MD5 hash generation utilities -- **Simple API** - Easy-to-use cryptographic operations +- **Blowfish Encryption** - CBC mode implementation +- **MD5 Hashing** - Fast MD5 hash generation +- **Span Support** - Memory-efficient operations +- **No External Dependencies** - Pure .NET implementation ## Installation -```xml - +```bash +dotnet add package DevBase.Cryptography ``` -Or via NuGet CLI: +## Blowfish Encryption -```bash -dotnet add package DevBase.Cryptography +Blowfish is a symmetric block cipher that encrypts data in 8-byte blocks using CBC (Cipher Block Chaining) mode. + +### Basic Usage + +```csharp +using DevBase.Cryptography.Blowfish; + +// Create cipher with key +byte[] key = Encoding.UTF8.GetBytes("my-secret-key"); +var blowfish = new Blowfish(key); + +// Prepare data (must be multiple of 8 bytes) +byte[] data = Encoding.UTF8.GetBytes("Hello World!"); // 12 bytes +byte[] paddedData = PadData(data); // Pad to 16 bytes + +// Initialization vector (must be 8 bytes) +byte[] iv = new byte[8]; +RandomNumberGenerator.Fill(iv); + +// Encrypt +bool success = blowfish.Encrypt(paddedData, iv); + +// Decrypt +bool decrypted = blowfish.Decrypt(paddedData, iv); +string result = Encoding.UTF8.GetString(paddedData).TrimEnd('\0'); ``` -## Usage Examples +### Encryption Example + +```csharp +using DevBase.Cryptography.Blowfish; +using System.Security.Cryptography; + +public byte[] EncryptData(string plaintext, string password) +{ + // Generate key from password + byte[] key = Encoding.UTF8.GetBytes(password); + + // Pad data to multiple of 8 + byte[] data = Encoding.UTF8.GetBytes(plaintext); + int paddedLength = (data.Length + 7) / 8 * 8; + byte[] paddedData = new byte[paddedLength]; + Array.Copy(data, paddedData, data.Length); + + // Generate random IV + byte[] iv = new byte[8]; + RandomNumberGenerator.Fill(iv); + + // Encrypt + var blowfish = new Blowfish(key); + blowfish.Encrypt(paddedData, iv); + + // Combine IV + encrypted data + byte[] result = new byte[8 + paddedData.Length]; + Array.Copy(iv, 0, result, 0, 8); + Array.Copy(paddedData, 0, result, 8, paddedData.Length); + + return result; +} +``` -### Blowfish Encryption +### Decryption Example + +```csharp +public string DecryptData(byte[] encrypted, string password) +{ + // Extract IV and data + byte[] iv = new byte[8]; + Array.Copy(encrypted, 0, iv, 0, 8); + + byte[] data = new byte[encrypted.Length - 8]; + Array.Copy(encrypted, 8, data, 0, data.Length); + + // Decrypt + byte[] key = Encoding.UTF8.GetBytes(password); + var blowfish = new Blowfish(key); + blowfish.Decrypt(data, iv); + + // Remove padding and convert to string + string result = Encoding.UTF8.GetString(data).TrimEnd('\0'); + return result; +} +``` + +### Using Codec Directly ```csharp using DevBase.Cryptography.Blowfish; -// Create Blowfish instance with key -BlowfishEngine blowfish = new BlowfishEngine("your-secret-key"); +// Create codec +byte[] key = Encoding.UTF8.GetBytes("secret"); +var codec = new Codec(key); -// Encrypt data -byte[] plaintext = Encoding.UTF8.GetBytes("Hello World"); -byte[] encrypted = blowfish.Encrypt(plaintext); +// Encrypt single block (8 bytes) +Span block = stackalloc byte[8]; +// ... fill block with data +codec.Encrypt(block); -// Decrypt data -byte[] decrypted = blowfish.Decrypt(encrypted); -string result = Encoding.UTF8.GetString(decrypted); +// Decrypt +codec.Decrypt(block); ``` -### MD5 Hashing +## MD5 Hashing + +Fast MD5 hash generation for data integrity checks. + +### Basic Usage ```csharp using DevBase.Cryptography.MD5; -// Generate MD5 hash -string input = "data to hash"; -string hash = MD5Utils.ComputeHash(input); +// Hash string +string input = "Hello World"; +string hash = MD5.Hash(input); +Console.WriteLine(hash); // "b10a8db164e0754105b7a99be72e3fe5" + +// Hash bytes +byte[] data = Encoding.UTF8.GetBytes("Hello World"); +string hash = MD5.Hash(data); +``` + +### File Hashing + +```csharp +using DevBase.Cryptography.MD5; +using DevBase.IO; + +// Hash file +byte[] fileData = AFile.ReadFile("document.pdf").ToArray(); +string fileHash = MD5.Hash(fileData); +Console.WriteLine($"File hash: {fileHash}"); +``` + +### Verify Data Integrity + +```csharp +public bool VerifyIntegrity(byte[] data, string expectedHash) +{ + string actualHash = MD5.Hash(data); + return actualHash.Equals(expectedHash, StringComparison.OrdinalIgnoreCase); +} +``` + +## Advanced Usage + +### Blowfish with Custom Padding + +```csharp +public static byte[] PadPKCS7(byte[] data) +{ + int blockSize = 8; + int paddingLength = blockSize - (data.Length % blockSize); + byte[] padded = new byte[data.Length + paddingLength]; + + Array.Copy(data, padded, data.Length); + + // Fill padding bytes with padding length value + for (int i = data.Length; i < padded.Length; i++) + { + padded[i] = (byte)paddingLength; + } + + return padded; +} + +public static byte[] UnpadPKCS7(byte[] data) +{ + int paddingLength = data[data.Length - 1]; + byte[] unpadded = new byte[data.Length - paddingLength]; + Array.Copy(data, unpadded, unpadded.Length); + return unpadded; +} +``` + +### Stream Encryption + +```csharp +public void EncryptStream(Stream input, Stream output, byte[] key, byte[] iv) +{ + var blowfish = new Blowfish(key); + byte[] buffer = new byte[8]; // Blowfish block size + + int bytesRead; + while ((bytesRead = input.Read(buffer, 0, 8)) > 0) + { + if (bytesRead < 8) + { + // Pad last block + Array.Clear(buffer, bytesRead, 8 - bytesRead); + } + + blowfish.Encrypt(buffer, iv); + output.Write(buffer, 0, 8); + + // Update IV for CBC mode + Array.Copy(buffer, iv, 8); + } +} +``` + +### Deezer Audio Decryption + +Blowfish is used by Deezer for audio file encryption: + +```csharp +using DevBase.Cryptography.Blowfish; -// Verify hash -bool isValid = MD5Utils.VerifyHash(input, hash); +public byte[] DecryptDeezerAudio(byte[] encryptedData, string trackId) +{ + // Generate Deezer-specific key + string secret = "g4el58wc0zvf9na1"; + string idMd5 = MD5.Hash(trackId); + + byte[] key = new byte[16]; + for (int i = 0; i < 16; i++) + { + key[i] = (byte)(idMd5[i] ^ idMd5[i + 16] ^ secret[i]); + } + + // Decrypt in 2048-byte chunks + var blowfish = new Blowfish(key); + byte[] iv = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }; + + for (int i = 0; i < encryptedData.Length; i += 2048) + { + int chunkSize = Math.Min(2048, encryptedData.Length - i); + if (chunkSize % 8 == 0) + { + Span chunk = encryptedData.AsSpan(i, chunkSize); + blowfish.Decrypt(chunk, iv); + } + } + + return encryptedData; +} ``` -## API Reference +## Performance Considerations ### Blowfish -| Class | Description | -|-------|-------------| -| `BlowfishEngine` | Blowfish encryption/decryption engine | +- **Block Size**: 8 bytes - data must be padded to multiples of 8 +- **Key Size**: Variable (1-56 bytes recommended) +- **CBC Mode**: Requires 8-byte initialization vector +- **Span**: Uses `Span` for zero-allocation operations ### MD5 -| Class | Description | -|-------|-------------| -| `MD5Utils` | MD5 hashing utilities | +- **Fast**: Optimized for speed +- **Not Cryptographically Secure**: Use only for checksums, not passwords +- **Output**: 32-character hexadecimal string + +## Security Notes + +⚠️ **Important Security Considerations:** + +1. **MD5 is not secure** for password hashing - use bcrypt, Argon2, or PBKDF2 instead +2. **Blowfish key management** - Store keys securely, never hardcode +3. **IV generation** - Always use cryptographically secure random IVs +4. **Padding** - Implement proper padding to avoid padding oracle attacks +5. **Modern alternatives** - Consider AES for new projects -## Security Note +## Common Use Cases -This library provides basic cryptographic primitives. For production security-critical applications, consider using well-audited libraries and consult security experts. +### Use Case 1: File Encryption + +```csharp +public void EncryptFile(string inputPath, string outputPath, string password) +{ + byte[] data = File.ReadAllBytes(inputPath); + byte[] encrypted = EncryptData(Encoding.UTF8.GetString(data), password); + File.WriteAllBytes(outputPath, encrypted); +} +``` + +### Use Case 2: Data Integrity Verification + +```csharp +public bool VerifyDownload(string filePath, string expectedMd5) +{ + byte[] fileData = File.ReadAllBytes(filePath); + string actualMd5 = MD5.Hash(fileData); + return actualMd5.Equals(expectedMd5, StringComparison.OrdinalIgnoreCase); +} +``` + +### Use Case 3: Secure Communication + +```csharp +public byte[] EncryptMessage(string message, byte[] sharedKey) +{ + byte[] data = Encoding.UTF8.GetBytes(message); + byte[] paddedData = PadPKCS7(data); + + byte[] iv = new byte[8]; + RandomNumberGenerator.Fill(iv); + + var blowfish = new Blowfish(sharedKey); + blowfish.Encrypt(paddedData, iv); + + // Prepend IV + byte[] result = new byte[8 + paddedData.Length]; + Array.Copy(iv, result, 8); + Array.Copy(paddedData, 0, result, 8, paddedData.Length); + + return result; +} +``` + +## Credits + +### Blowfish Implementation + +Based on [jdvor/encryption-blowfish](https://github.com/jdvor/encryption-blowfish) + +**License:** MIT License + +The implementation has been integrated into DevBase for ecosystem consistency. + +## Target Framework + +- **.NET 9.0** + +## Dependencies + +- No external dependencies (pure .NET) ## License -MIT License - see LICENSE file for details. +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/DevBase.Extensions/AGENT.md b/DevBase.Extensions/AGENT.md index b07a729..86fc8b4 100644 --- a/DevBase.Extensions/AGENT.md +++ b/DevBase.Extensions/AGENT.md @@ -1,36 +1,448 @@ -# DevBase.Extensions Agent Guide +# DevBase.Extensions - AI Agent Guide + +This guide helps AI agents effectively use the DevBase.Extensions library for enhanced Stopwatch functionality. ## Overview -DevBase.Extensions provides extension methods for standard .NET types. -## Key Classes +DevBase.Extensions provides enhanced timing capabilities with detailed time breakdowns and formatted output. -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `StopwatchExtensions` | `DevBase.Extensions` | Stopwatch formatting | +**Target Framework:** .NET 9.0 -## Quick Reference +## Core Components + +### StopwatchExtension + +```csharp +public static class StopwatchExtension +{ + public static void PrintTimeTable(this Stopwatch stopwatch); + public static string GetTimeTable(this Stopwatch stopwatch); +} +``` + +### TimeUtils + +```csharp +public static class TimeUtils +{ + public static (int Hours, string Unit) GetHours(Stopwatch stopwatch); + public static (int Minutes, string Unit) GetMinutes(Stopwatch stopwatch); + public static (int Seconds, string Unit) GetSeconds(Stopwatch stopwatch); + public static (int Milliseconds, string Unit) GetMilliseconds(Stopwatch stopwatch); + public static (long Microseconds, string Unit) GetMicroseconds(Stopwatch stopwatch); + public static (long Nanoseconds, string Unit) GetNanoseconds(Stopwatch stopwatch); +} +``` + +## Usage Patterns for AI Agents + +### Pattern 1: Basic Timing -### Stopwatch Formatting ```csharp -using DevBase.Extensions; +using DevBase.Extensions.Stopwatch; using System.Diagnostics; -Stopwatch sw = Stopwatch.StartNew(); -// ... work ... -sw.Stop(); +var stopwatch = Stopwatch.StartNew(); + +// Perform operation +PerformOperation(); + +stopwatch.Stop(); + +// Print detailed breakdown +stopwatch.PrintTimeTable(); +``` + +### Pattern 2: Get Time Table as String + +```csharp +var stopwatch = Stopwatch.StartNew(); +await PerformAsyncOperation(); +stopwatch.Stop(); + +string timeTable = stopwatch.GetTimeTable(); +Console.WriteLine(timeTable); + +// Save to file +File.WriteAllText("timing.md", timeTable); +``` + +### Pattern 3: Benchmarking + +```csharp +public void BenchmarkOperation() +{ + var stopwatch = Stopwatch.StartNew(); + + for (int i = 0; i < 1000000; i++) + { + _ = i * 2; + } + + stopwatch.Stop(); + + Console.WriteLine("Benchmark results:"); + stopwatch.PrintTimeTable(); +} +``` + +### Pattern 4: API Request Timing + +```csharp +using DevBase.Net.Core; +using DevBase.Extensions.Stopwatch; + +var stopwatch = Stopwatch.StartNew(); +var response = await new Request("https://api.example.com").SendAsync(); +stopwatch.Stop(); + +Console.WriteLine($"Request completed with status {response.StatusCode}:"); +stopwatch.PrintTimeTable(); +``` + +### Pattern 5: Individual Time Components + +```csharp +using DevBase.Extensions.Utils; + +var stopwatch = Stopwatch.StartNew(); +// ... operation ... +stopwatch.Stop(); + +var (hours, unit1) = TimeUtils.GetHours(stopwatch); +var (minutes, unit2) = TimeUtils.GetMinutes(stopwatch); +var (seconds, unit3) = TimeUtils.GetSeconds(stopwatch); +var (milliseconds, unit4) = TimeUtils.GetMilliseconds(stopwatch); + +Console.WriteLine($"{hours} {unit1}, {minutes} {unit2}, {seconds} {unit3}, {milliseconds} {unit4}"); +``` + +### Pattern 6: Performance Monitoring + +```csharp +public async Task MonitorPerformance(Func> operation, string name) +{ + var stopwatch = Stopwatch.StartNew(); + + try + { + var result = await operation(); + stopwatch.Stop(); + + Console.WriteLine($"{name} completed:"); + stopwatch.PrintTimeTable(); + + return result; + } + catch (Exception ex) + { + stopwatch.Stop(); + Console.WriteLine($"{name} failed after {stopwatch.Elapsed}"); + throw; + } +} +``` + +## Important Concepts + +### 1. Stopwatch Must Be Stopped + +**Critical Rule:** Always stop stopwatch before calling `GetTimeTable()` or `PrintTimeTable()`. + +```csharp +// ✅ Correct +var stopwatch = Stopwatch.StartNew(); +PerformOperation(); +stopwatch.Stop(); +stopwatch.PrintTimeTable(); + +// ❌ Wrong - throws StopwatchException +var stopwatch = Stopwatch.StartNew(); +PerformOperation(); +stopwatch.PrintTimeTable(); // Exception! +``` + +### 2. Time Table Format + +Output is Markdown-formatted table: + +``` +| Data | Unit | +|------|--------------| +| 2 | Hours | +| 15 | Minutes | +| 30 | Seconds | +| 500 | Milliseconds | +``` + +Only non-zero values are included. + +### 3. Time Components + +```csharp +// All return tuples: (value, unit) +var (hours, unit) = TimeUtils.GetHours(stopwatch); // (2, "Hours") +var (minutes, unit) = TimeUtils.GetMinutes(stopwatch); // (15, "Minutes") +var (seconds, unit) = TimeUtils.GetSeconds(stopwatch); // (30, "Seconds") +var (ms, unit) = TimeUtils.GetMilliseconds(stopwatch); // (500, "Milliseconds") +var (us, unit) = TimeUtils.GetMicroseconds(stopwatch); // (1234, "Microseconds") +var (ns, unit) = TimeUtils.GetNanoseconds(stopwatch); // (5678, "Nanoseconds") +``` + +## Common Mistakes to Avoid + +### ❌ Mistake 1: Not Stopping Stopwatch -string formatted = sw.ToFormattedString(); -// Output: "1.234s" or "123ms" depending on duration +```csharp +// Wrong +var stopwatch = Stopwatch.StartNew(); +PerformOperation(); +stopwatch.PrintTimeTable(); // Throws StopwatchException! + +// Correct +var stopwatch = Stopwatch.StartNew(); +PerformOperation(); +stopwatch.Stop(); +stopwatch.PrintTimeTable(); +``` + +### ❌ Mistake 2: Forgetting to Start Stopwatch + +```csharp +// Wrong +var stopwatch = new Stopwatch(); // Not started! +PerformOperation(); +stopwatch.Stop(); +stopwatch.PrintTimeTable(); // Shows 0 for everything + +// Correct +var stopwatch = Stopwatch.StartNew(); +PerformOperation(); +stopwatch.Stop(); +stopwatch.PrintTimeTable(); +``` + +### ❌ Mistake 3: Multiple Stops + +```csharp +// Wrong - stopping multiple times +var stopwatch = Stopwatch.StartNew(); +PerformOperation1(); +stopwatch.Stop(); +PerformOperation2(); +stopwatch.Stop(); // Doesn't restart timing! + +// Correct - use Restart() +var stopwatch = Stopwatch.StartNew(); +PerformOperation1(); +stopwatch.Stop(); +stopwatch.PrintTimeTable(); + +stopwatch.Restart(); // Restart for next operation +PerformOperation2(); +stopwatch.Stop(); +stopwatch.PrintTimeTable(); +``` + +### ❌ Mistake 4: Not Handling Exceptions + +```csharp +// Wrong - stopwatch never stopped if exception occurs +var stopwatch = Stopwatch.StartNew(); +PerformOperation(); // Might throw +stopwatch.Stop(); + +// Correct - use try-finally +var stopwatch = Stopwatch.StartNew(); +try +{ + PerformOperation(); +} +finally +{ + stopwatch.Stop(); + stopwatch.PrintTimeTable(); +} +``` + +## Advanced Usage + +### Comparing Multiple Operations + +```csharp +public void CompareOperations() +{ + var sw1 = Stopwatch.StartNew(); + Operation1(); + sw1.Stop(); + + var sw2 = Stopwatch.StartNew(); + Operation2(); + sw2.Stop(); + + Console.WriteLine("Operation 1:"); + Console.WriteLine(sw1.GetTimeTable()); + + Console.WriteLine("\nOperation 2:"); + Console.WriteLine(sw2.GetTimeTable()); + + // Compare + if (sw1.Elapsed < sw2.Elapsed) + Console.WriteLine("Operation 1 was faster"); + else + Console.WriteLine("Operation 2 was faster"); +} +``` + +### Aggregate Statistics + +```csharp +public class PerformanceTracker +{ + private List timings = new(); + + public void Track(Action operation) + { + var stopwatch = Stopwatch.StartNew(); + operation(); + stopwatch.Stop(); + + timings.Add(stopwatch.Elapsed); + stopwatch.PrintTimeTable(); + } + + public void PrintStats() + { + var avg = TimeSpan.FromTicks((long)timings.Average(t => t.Ticks)); + var min = timings.Min(); + var max = timings.Max(); + + Console.WriteLine($"Average: {avg}"); + Console.WriteLine($"Min: {min}"); + Console.WriteLine($"Max: {max}"); + } +} +``` + +### Conditional Timing + +```csharp +public void ConditionalTiming(bool enableTiming) +{ + Stopwatch stopwatch = enableTiming ? Stopwatch.StartNew() : null; + + PerformOperation(); + + if (stopwatch != null) + { + stopwatch.Stop(); + stopwatch.PrintTimeTable(); + } +} ``` -## File Structure +## Integration Examples + +### With DevBase.Net + +```csharp +using DevBase.Net.Core; +using DevBase.Extensions.Stopwatch; + +var stopwatch = Stopwatch.StartNew(); +var response = await new Request("https://api.example.com").SendAsync(); +stopwatch.Stop(); + +Console.WriteLine($"Request: {response.StatusCode}"); +stopwatch.PrintTimeTable(); +``` + +### With DevBase.Api + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Extensions.Stopwatch; + +var stopwatch = Stopwatch.StartNew(); +var deezer = new Deezer(); +var results = await deezer.Search("artist"); +stopwatch.Stop(); + +Console.WriteLine($"Found {results.data.Length} results:"); +stopwatch.PrintTimeTable(); ``` -DevBase.Extensions/ -└── StopwatchExtensions.cs + +### With DevBase.Format + +```csharp +using DevBase.Format; +using DevBase.Format.Formats.LrcFormat; +using DevBase.Extensions.Stopwatch; + +var stopwatch = Stopwatch.StartNew(); +var parser = new FileParser>(); +var lyrics = parser.ParseFromDisk("song.lrc"); +stopwatch.Stop(); + +Console.WriteLine($"Parsed {lyrics.Length} lines:"); +stopwatch.PrintTimeTable(); ``` -## Important Notes +## Exception Handling + +### StopwatchException + +```csharp +using DevBase.Extensions.Exceptions; + +try +{ + var stopwatch = Stopwatch.StartNew(); + stopwatch.PrintTimeTable(); // Throws! +} +catch (StopwatchException ex) +{ + Console.WriteLine("Must stop stopwatch first"); +} +``` + +## Performance Tips + +1. **Minimal overhead** - Extension methods add negligible overhead +2. **High precision** - Uses Stopwatch.Frequency for accuracy +3. **Reuse instances** - Use Restart() instead of creating new stopwatches +4. **Stop before printing** - Always stop before calling GetTimeTable() + +## Quick Reference + +| Task | Code | +|------|------| +| Start timing | `Stopwatch.StartNew()` | +| Stop timing | `stopwatch.Stop()` | +| Print table | `stopwatch.PrintTimeTable()` | +| Get table string | `stopwatch.GetTimeTable()` | +| Get hours | `TimeUtils.GetHours(stopwatch)` | +| Get minutes | `TimeUtils.GetMinutes(stopwatch)` | +| Get seconds | `TimeUtils.GetSeconds(stopwatch)` | +| Get milliseconds | `TimeUtils.GetMilliseconds(stopwatch)` | +| Get microseconds | `TimeUtils.GetMicroseconds(stopwatch)` | +| Get nanoseconds | `TimeUtils.GetNanoseconds(stopwatch)` | +| Restart | `stopwatch.Restart()` | + +## Testing Considerations + +- Test with very short operations (< 1ms) +- Test with long operations (> 1 hour) +- Test exception handling during timing +- Test with async operations +- Verify time table formatting + +## Version + +Current version: **1.0.0** +Target framework: **.NET 9.0** + +## Dependencies -1. **Import `DevBase.Extensions`** namespace to use extensions -2. **Stopwatch formatter** auto-selects appropriate unit (ms, s, min) +- ConsoleTables (for formatted output) +- System.Diagnostics diff --git a/DevBase.Extensions/README.md b/DevBase.Extensions/README.md index 3e7baf4..37b3ddc 100644 --- a/DevBase.Extensions/README.md +++ b/DevBase.Extensions/README.md @@ -1,60 +1,447 @@ # DevBase.Extensions -A collection of useful extension methods and utilities for .NET development. +**DevBase.Extensions** provides extension methods and utilities for .NET 9.0, focusing on enhanced Stopwatch functionality with detailed time breakdowns and formatted output. ## Features -- **Stopwatch Extensions** - Enhanced stopwatch functionality -- **Console Tables** - Formatted console output -- **Utility Methods** - Common helper functions +- **Stopwatch Extensions** - Enhanced timing with detailed breakdowns +- **Time Utilities** - Time conversion and formatting helpers +- **Console Table Output** - Pretty-printed time tables in Markdown format ## Installation -```xml - +```bash +dotnet add package DevBase.Extensions ``` -Or via NuGet CLI: +## Stopwatch Extensions -```bash -dotnet add package DevBase.Extensions +Enhanced stopwatch functionality with detailed time breakdowns. + +### Basic Usage + +```csharp +using DevBase.Extensions.Stopwatch; +using System.Diagnostics; + +var stopwatch = Stopwatch.StartNew(); + +// ... perform operations ... + +stopwatch.Stop(); + +// Print detailed time table +stopwatch.PrintTimeTable(); +``` + +### Output Example + +``` +| Data | Unit | +|------|--------------| +| 2 | Hours | +| 15 | Minutes | +| 30 | Seconds | +| 500 | Milliseconds | +| 1234 | Microseconds | +| 5678 | Nanoseconds | +``` + +### Get Time Table as String + +```csharp +var stopwatch = Stopwatch.StartNew(); +// ... operations ... +stopwatch.Stop(); + +string timeTable = stopwatch.GetTimeTable(); +Console.WriteLine(timeTable); + +// Or save to file +File.WriteAllText("timing.md", timeTable); +``` + +## Time Utilities + +Helper methods for time conversions and formatting. + +### Get Individual Time Components + +```csharp +using DevBase.Extensions.Utils; +using System.Diagnostics; + +var stopwatch = Stopwatch.StartNew(); +// ... operations ... +stopwatch.Stop(); + +// Get hours +(int hours, string unit) = TimeUtils.GetHours(stopwatch); +Console.WriteLine($"{hours} {unit}"); // "2 Hours" + +// Get minutes +(int minutes, string unit) = TimeUtils.GetMinutes(stopwatch); +Console.WriteLine($"{minutes} {unit}"); // "15 Minutes" + +// Get seconds +(int seconds, string unit) = TimeUtils.GetSeconds(stopwatch); +Console.WriteLine($"{seconds} {unit}"); // "30 Seconds" + +// Get milliseconds +(int milliseconds, string unit) = TimeUtils.GetMilliseconds(stopwatch); +Console.WriteLine($"{milliseconds} {unit}"); // "500 Milliseconds" + +// Get microseconds +(long microseconds, string unit) = TimeUtils.GetMicroseconds(stopwatch); +Console.WriteLine($"{microseconds} {unit}"); // "1234 Microseconds" + +// Get nanoseconds +(long nanoseconds, string unit) = TimeUtils.GetNanoseconds(stopwatch); +Console.WriteLine($"{nanoseconds} {unit}"); // "5678 Nanoseconds" ``` ## Usage Examples -### Stopwatch Extensions +### Benchmarking Operations ```csharp using DevBase.Extensions.Stopwatch; +using System.Diagnostics; + +public void BenchmarkOperation() +{ + var stopwatch = Stopwatch.StartNew(); + + // Perform operation + for (int i = 0; i < 1000000; i++) + { + _ = i * 2; + } + + stopwatch.Stop(); + + Console.WriteLine("Operation completed:"); + stopwatch.PrintTimeTable(); +} +``` -Stopwatch sw = Stopwatch.StartNew(); -// ... some work ... -sw.Stop(); +### Comparing Multiple Operations -// Get formatted elapsed time -string elapsed = sw.GetFormattedElapsed(); -Console.WriteLine($"Elapsed: {elapsed}"); +```csharp +public void CompareOperations() +{ + // Operation 1 + var sw1 = Stopwatch.StartNew(); + PerformOperation1(); + sw1.Stop(); + + // Operation 2 + var sw2 = Stopwatch.StartNew(); + PerformOperation2(); + sw2.Stop(); + + Console.WriteLine("Operation 1:"); + Console.WriteLine(sw1.GetTimeTable()); + + Console.WriteLine("\nOperation 2:"); + Console.WriteLine(sw2.GetTimeTable()); +} ``` -### Console Table Output +### Logging Performance Metrics ```csharp -using DevBase.Extensions.Utils; +using DevBase.Extensions.Stopwatch; +using DevBase.Logging.Logger; + +public async Task MeasureAsync(Func> operation, string operationName) +{ + var stopwatch = Stopwatch.StartNew(); + + try + { + var result = await operation(); + stopwatch.Stop(); + + var logger = new Logger("Performance"); + logger.Write($"{operationName} completed:\n{stopwatch.GetTimeTable()}", LogType.INFO); + + return result; + } + catch (Exception ex) + { + stopwatch.Stop(); + Console.WriteLine($"{operationName} failed after {stopwatch.Elapsed}"); + throw; + } +} +``` + +### API Response Time Tracking + +```csharp +using DevBase.Net.Core; +using DevBase.Extensions.Stopwatch; + +public async Task TimedRequest(string url) +{ + var stopwatch = Stopwatch.StartNew(); + + var response = await new Request(url).SendAsync(); + + stopwatch.Stop(); + + Console.WriteLine($"Request to {url}:"); + stopwatch.PrintTimeTable(); + + return response; +} +``` + +### Database Query Performance -// Create formatted table output -var data = new[] +```csharp +public async Task> GetUsersWithTiming() { - new { Name = "John", Age = 30 }, - new { Name = "Jane", Age = 25 } -}; + var stopwatch = Stopwatch.StartNew(); + + var users = await database.Users.ToListAsync(); + + stopwatch.Stop(); + + Console.WriteLine($"Retrieved {users.Count} users:"); + stopwatch.PrintTimeTable(); + + return users; +} +``` + +### File Processing Timing + +```csharp +using DevBase.IO; +using DevBase.Extensions.Stopwatch; -ConsoleTableUtils.PrintTable(data); +public void ProcessFilesWithTiming(string directory) +{ + var stopwatch = Stopwatch.StartNew(); + + var files = AFile.GetFiles(directory, readContent: true); + + foreach (var file in files) + { + ProcessFile(file); + } + + stopwatch.Stop(); + + Console.WriteLine($"Processed {files.Length} files:"); + stopwatch.PrintTimeTable(); +} +``` + +## Advanced Usage + +### Custom Time Formatting + +```csharp +public string FormatElapsedTime(Stopwatch stopwatch) +{ + var (hours, _) = TimeUtils.GetHours(stopwatch); + var (minutes, _) = TimeUtils.GetMinutes(stopwatch); + var (seconds, _) = TimeUtils.GetSeconds(stopwatch); + var (milliseconds, _) = TimeUtils.GetMilliseconds(stopwatch); + + if (hours > 0) + return $"{hours}h {minutes}m {seconds}s"; + else if (minutes > 0) + return $"{minutes}m {seconds}s"; + else if (seconds > 0) + return $"{seconds}s {milliseconds}ms"; + else + return $"{milliseconds}ms"; +} ``` +### Performance Threshold Alerts + +```csharp +public void CheckPerformance(Stopwatch stopwatch, TimeSpan threshold) +{ + stopwatch.Stop(); + + if (stopwatch.Elapsed > threshold) + { + Console.WriteLine("⚠️ Performance threshold exceeded!"); + stopwatch.PrintTimeTable(); + } +} +``` + +### Aggregate Timing Statistics + +```csharp +public class TimingStats +{ + private List timings = new(); + + public void AddTiming(Stopwatch stopwatch) + { + timings.Add(stopwatch.Elapsed); + } + + public void PrintStatistics() + { + var avg = TimeSpan.FromTicks((long)timings.Average(t => t.Ticks)); + var min = timings.Min(); + var max = timings.Max(); + + Console.WriteLine($"Average: {avg}"); + Console.WriteLine($"Min: {min}"); + Console.WriteLine($"Max: {max}"); + } +} +``` + +## Exception Handling + +### StopwatchException + +Thrown when trying to get time table from a running stopwatch: + +```csharp +using DevBase.Extensions.Exceptions; + +try +{ + var stopwatch = Stopwatch.StartNew(); + // Don't stop it + stopwatch.PrintTimeTable(); // Throws StopwatchException +} +catch (StopwatchException ex) +{ + Console.WriteLine("Stopwatch must be stopped first"); + stopwatch.Stop(); + stopwatch.PrintTimeTable(); +} +``` + +## Integration with Other DevBase Libraries + +### With DevBase.Net + +```csharp +using DevBase.Net.Core; +using DevBase.Extensions.Stopwatch; + +var stopwatch = Stopwatch.StartNew(); +var response = await new Request("https://api.example.com").SendAsync(); +stopwatch.Stop(); + +Console.WriteLine("API Request:"); +stopwatch.PrintTimeTable(); +Console.WriteLine($"Response: {response.StatusCode}"); +``` + +### With DevBase.Api + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Extensions.Stopwatch; + +var stopwatch = Stopwatch.StartNew(); +var deezer = new Deezer(); +var results = await deezer.Search("Rick Astley"); +stopwatch.Stop(); + +Console.WriteLine("Deezer Search:"); +stopwatch.PrintTimeTable(); +``` + +### With DevBase.Format + +```csharp +using DevBase.Format; +using DevBase.Format.Formats.LrcFormat; +using DevBase.Extensions.Stopwatch; + +var stopwatch = Stopwatch.StartNew(); +var parser = new FileParser>(); +var lyrics = parser.ParseFromDisk("song.lrc"); +stopwatch.Stop(); + +Console.WriteLine("LRC Parsing:"); +stopwatch.PrintTimeTable(); +``` + +## Performance Tips + +1. **Stop before printing** - Always stop stopwatch before calling `GetTimeTable()` or `PrintTimeTable()` +2. **Reuse stopwatch instances** - Use `Restart()` instead of creating new instances +3. **Minimal overhead** - Extension methods have negligible performance impact +4. **High precision** - Uses `Stopwatch.Frequency` for accurate measurements + +## Common Patterns + +### Pattern 1: Try-Finally Timing + +```csharp +var stopwatch = Stopwatch.StartNew(); +try +{ + PerformOperation(); +} +finally +{ + stopwatch.Stop(); + stopwatch.PrintTimeTable(); +} +``` + +### Pattern 2: Async Operation Timing + +```csharp +var stopwatch = Stopwatch.StartNew(); +await PerformAsyncOperation(); +stopwatch.Stop(); +stopwatch.PrintTimeTable(); +``` + +### Pattern 3: Conditional Timing + +```csharp +Stopwatch stopwatch = null; +if (enableTiming) +{ + stopwatch = Stopwatch.StartNew(); +} + +PerformOperation(); + +if (stopwatch != null) +{ + stopwatch.Stop(); + stopwatch.PrintTimeTable(); +} +``` + +## Target Framework + +- **.NET 9.0** + ## Dependencies -- ConsoleTables +- **ConsoleTables** - For formatted table output +- **System.Diagnostics** - Stopwatch functionality ## License -MIT License - see LICENSE file for details. +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/DevBase.Format/AGENT.md b/DevBase.Format/AGENT.md index 45c3e95..70c3b91 100644 --- a/DevBase.Format/AGENT.md +++ b/DevBase.Format/AGENT.md @@ -1,104 +1,522 @@ -# DevBase.Format Agent Guide +# DevBase.Format - AI Agent Guide + +This guide helps AI agents effectively use the DevBase.Format library for parsing lyrics and configuration files. ## Overview -DevBase.Format provides parsers for lyrics (LRC), subtitles (SRT), and environment files (ENV). -## Key Classes +DevBase.Format provides parsers for various text-based formats, primarily focused on lyrics formats (LRC, SRT, ELRC, etc.) and configuration files (ENV). -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `LrcParser` | `DevBase.Format.Formats.LrcFormat` | LRC lyrics parser | -| `SrtParser` | `DevBase.Format.Formats.SrtFormat` | SRT subtitle parser | -| `EnvParser` | `DevBase.Format.Formats.EnvFormat` | ENV file parser | -| `TimeStampedLyric` | `DevBase.Format.Structure` | Simple timestamped lyric | -| `RichTimeStampedLyric` | `DevBase.Format.Structure` | Word-level timestamped lyric | +**Target Framework:** .NET 9.0 -## Quick Reference +## Core Architecture + +### Generic Parser Pattern + +All parsers follow this pattern: + +```csharp +FileParser +``` + +Where: +- `FormatType` - The format parser class (e.g., `LrcFormat`, `SrtFormat`) +- `ResultType` - The parsed result type (e.g., `AList`, `Dictionary`) + +### Base Classes + +1. **`FileFormat`** - Base class for all format parsers +2. **`RevertableFileFormat`** - For formats that can convert back to string +3. **`FileParser`** - Generic parser wrapper + +## Usage Patterns for AI Agents + +### Pattern 1: Parse LRC Lyrics -### Parse LRC Lyrics ```csharp +using DevBase.Format; using DevBase.Format.Formats.LrcFormat; -using DevBase.Format.Structure; -LrcParser parser = new LrcParser(); -AList lyrics = parser.Parse(lrcContent); +var parser = new FileParser>(); -foreach (var lyric in lyrics.GetAsList()) +// From string +var lyrics = parser.ParseFromString(lrcContent); + +// From file +var lyrics = parser.ParseFromDisk("song.lrc"); + +// Safe parsing +if (parser.TryParseFromString(lrcContent, out var lyrics)) { - Console.WriteLine($"{lyric.StartTime}: {lyric.Text}"); + foreach (var line in lyrics) + { + Console.WriteLine($"[{line.StartTime}] {line.Text}"); + } } ``` -### Parse SRT Subtitles +### Pattern 2: Parse SRT Subtitles + ```csharp using DevBase.Format.Formats.SrtFormat; -SrtParser parser = new SrtParser(); -AList subtitles = parser.Parse(srtContent); +var parser = new FileParser>(); +var subtitles = parser.ParseFromDisk("movie.srt"); + +foreach (var subtitle in subtitles) +{ + Console.WriteLine($"{subtitle.StartTime}: {subtitle.Text}"); +} +``` + +### Pattern 3: Parse ENV Configuration + +```csharp +using DevBase.Format.Formats.EnvFormat; + +var parser = new FileParser>(); +var config = parser.ParseFromDisk(".env"); + +string dbHost = config["DB_HOST"]; +string apiKey = config["API_KEY"]; ``` -### Parse from File +### Pattern 4: Multi-Format Detection + +```csharp +AList ParseLyricsAnyFormat(string content) +{ + // Try LRC + var lrcParser = new FileParser>(); + if (lrcParser.TryParseFromString(content, out var lrcLyrics)) + return lrcLyrics; + + // Try SRT + var srtParser = new FileParser>(); + if (srtParser.TryParseFromString(content, out var srtLyrics)) + return srtLyrics; + + // Try ELRC + var elrcParser = new FileParser>(); + if (elrcParser.TryParseFromString(content, out var elrcLyrics)) + { + // Convert to TimeStampedLyric if needed + return ConvertToTimeStampedLyrics(elrcLyrics); + } + + return null; +} +``` + +### Pattern 5: Integration with API Clients + ```csharp +using DevBase.Api.Apis.Deezer; using DevBase.Format; +using DevBase.Format.Formats.LrcFormat; + +var deezer = new Deezer(); +string lyricsString = await deezer.GetLyrics("123456"); + +if (lyricsString != null) +{ + var parser = new FileParser>(); + if (parser.TryParseFromString(lyricsString, out var lyrics)) + { + // Process parsed lyrics + foreach (var line in lyrics) + { + Console.WriteLine($"[{line.StartTime}] {line.Text}"); + } + } +} +``` + +### Pattern 6: Working with Timestamps + +```csharp +using DevBase.Format.Structure; + +var lyric = new TimeStampedLyric +{ + Text = "Hello world", + StartTime = TimeSpan.FromSeconds(12.5) +}; + +// Get milliseconds +long ms = lyric.StartTimestamp; // 12500 + +// Format for display +string formatted = $"[{lyric.StartTime:mm\\:ss\\.ff}] {lyric.Text}"; +``` + +### Pattern 7: Rich-Synced Lyrics (Word-Level) + +```csharp +using DevBase.Format.Structure; +using DevBase.Format.Formats.ElrcFormat; + +var parser = new FileParser>(); +var richLyrics = parser.ParseFromString(elrcContent); + +foreach (var line in richLyrics) +{ + Console.WriteLine($"Line starts at: {line.StartTime}"); + foreach (var word in line.Words) + { + Console.WriteLine($" [{word.StartTime}] {word.Text}"); + } +} +``` + +## Important Concepts + +### 1. Parser Instantiation + +**Always use `FileParser` wrapper:** + +```csharp +// ✅ Correct +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(content); + +// ❌ Wrong - don't instantiate format directly +var format = new LrcFormat(); +var lyrics = format.Parse(content); // Works but not recommended +``` + +### 2. Try-Parse Pattern + +**Use Try-Parse for safe parsing:** + +```csharp +// ✅ Correct - no exceptions +if (parser.TryParseFromString(content, out var result)) +{ + // Success +} +else +{ + // Failed +} + +// ❌ Avoid - can throw exceptions +var result = parser.ParseFromString(content); +``` + +### 3. File vs String Parsing + +```csharp +// From file (reads and parses) +var lyrics = parser.ParseFromDisk("song.lrc"); + +// From string (already in memory) +var lyrics = parser.ParseFromString(lrcContent); + +// From FileInfo +var lyrics = parser.ParseFromDisk(new FileInfo("song.lrc")); +``` + +### 4. Result Types + +Different formats return different types: + +| Format | Result Type | +|--------|-------------| +| LRC, SRT, Apple XML | `AList` | +| ELRC, RLRC | `AList` | +| ENV | `Dictionary` | + +### 5. Timestamp Handling + +```csharp +var lyric = new TimeStampedLyric +{ + Text = "Line text", + StartTime = TimeSpan.FromMilliseconds(12500) +}; -var fileParser = new FileParser>(); -var lyrics = fileParser.ParseFromDisk(fileInfo); +// Access as TimeSpan +TimeSpan time = lyric.StartTime; + +// Access as milliseconds +long ms = lyric.StartTimestamp; + +// Create from seconds +lyric.StartTime = TimeSpan.FromSeconds(12.5); +``` + +## Format-Specific Guidelines + +### LRC Format + +**Structure:** +``` +[mm:ss.xx]Lyric text +``` + +**Metadata:** +``` +[ar:Artist] +[ti:Title] +[al:Album] +[by:Creator] +[offset:+/-ms] +``` + +**Parsing:** +```csharp +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(lrcContent); +``` + +### SRT Format + +**Structure:** +``` +1 +00:00:12,000 --> 00:00:15,000 +Subtitle text +``` + +**Parsing:** +```csharp +var parser = new FileParser>(); +var subtitles = parser.ParseFromDisk("movie.srt"); +``` + +### ENV Format + +**Structure:** +``` +KEY=value +DATABASE_URL=postgresql://localhost/mydb +# Comments +``` + +**Parsing:** +```csharp +var parser = new FileParser>(); +var config = parser.ParseFromString(envContent); +string value = config["KEY"]; +``` + +### Apple Formats + +**Apple XML, Apple LRC XML, Apple Rich XML:** + +```csharp +using DevBase.Format.Formats.AppleXmlFormat; +using DevBase.Format.Formats.AppleLrcXmlFormat; +using DevBase.Format.Formats.AppleRichXmlFormat; + +// Standard Apple XML +var parser1 = new FileParser>(); + +// Apple LRC XML +var parser2 = new FileParser>(); + +// Apple Rich XML (word-level timing) +var parser3 = new FileParser>(); ``` -## Data Structures +## Common Mistakes to Avoid + +### ❌ Mistake 1: Wrong Result Type -### TimeStampedLyric ```csharp -public class TimeStampedLyric +// Wrong - LrcFormat returns AList, not string +var parser = new FileParser(); + +// Correct +var parser = new FileParser>(); +``` + +### ❌ Mistake 2: Not Checking Parse Result + +```csharp +// Wrong - might be null +var lyrics = parser.ParseFromString(content); +foreach (var line in lyrics) // NullReferenceException! + +// Correct +if (parser.TryParseFromString(content, out var lyrics)) { - public TimeSpan StartTime { get; set; } - public string Text { get; set; } + foreach (var line in lyrics) + { + // Safe + } } ``` -### RichTimeStampedLyric +### ❌ Mistake 3: Using Wrong Format Parser + ```csharp -public class RichTimeStampedLyric +// Wrong - trying to parse SRT with LRC parser +var parser = new FileParser>(); +var subtitles = parser.ParseFromString(srtContent); // Will fail! + +// Correct +var parser = new FileParser>(); +var subtitles = parser.ParseFromString(srtContent); +``` + +### ❌ Mistake 4: Not Handling Empty Results + +```csharp +// Wrong +var lyrics = parser.ParseFromString(content); +var firstLine = lyrics[0]; // Might be empty! + +// Correct +var lyrics = parser.ParseFromString(content); +if (lyrics != null && !lyrics.IsEmpty()) { - public TimeSpan StartTime { get; set; } - public TimeSpan EndTime { get; set; } - public string Text { get; set; } - public AList Words { get; set; } + var firstLine = lyrics[0]; } ``` -### RichTimeStampedWord +### ❌ Mistake 5: Incorrect Timestamp Format + +```csharp +// Wrong - using wrong time format +var lyric = new TimeStampedLyric +{ + StartTime = TimeSpan.Parse("12:50") // Hours:Minutes, not Minutes:Seconds! +}; + +// Correct +var lyric = new TimeStampedLyric +{ + StartTime = TimeSpan.FromSeconds(12.5) // 12.5 seconds +}; +``` + +## Advanced Usage + +### Creating Custom Format Parser + ```csharp -public class RichTimeStampedWord +using DevBase.Format; + +public class CustomFormat : FileFormat> { - public TimeSpan StartTime { get; set; } - public TimeSpan EndTime { get; set; } - public string Word { get; set; } + public override AList Parse(string input) + { + var lines = new AList(); + foreach (var line in input.Split('\n')) + { + if (!string.IsNullOrWhiteSpace(line)) + lines.Add(line.Trim()); + } + return lines; + } + + public override bool TryParse(string input, out AList parsed) + { + try + { + parsed = Parse(input); + return true; + } + catch + { + parsed = null; + return false; + } + } } + +// Use it +var parser = new FileParser>(); +var result = parser.ParseFromString("line1\nline2"); ``` -## File Structure +### Revertable Formats + +Some formats can convert back to string: + +```csharp +// Parse +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(lrcContent); + +// Modify +lyrics.Add(new TimeStampedLyric +{ + Text = "New line", + StartTime = TimeSpan.FromSeconds(30) +}); + +// Convert back +var format = new LrcFormat(); +string newLrcContent = format.Revert(lyrics); ``` -DevBase.Format/ -├── Formats/ -│ ├── LrcFormat/ -│ │ └── LrcParser.cs -│ ├── SrtFormat/ -│ │ └── SrtParser.cs -│ ├── EnvFormat/ -│ │ └── EnvParser.cs -│ └── KLyricsFormat/ # Karaoke lyrics -├── Structure/ -│ ├── TimeStampedLyric.cs -│ ├── RichTimeStampedLyric.cs -│ └── RichTimeStampedWord.cs -└── FileParser.cs + +## Performance Tips + +1. **Reuse parser instances** for multiple files +2. **Use Try-Parse** to avoid exception overhead +3. **Parse from string** when data is already in memory +4. **Cache parsed results** for frequently accessed files +5. **Use appropriate format** - don't try all formats if you know the type + +## Integration Examples + +### With DevBase.Api + +```csharp +// Get lyrics from API +var deezer = new Deezer(); +string lyricsString = await deezer.GetLyrics("123456"); + +// Parse +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(lyricsString); ``` -## Important Notes +### With DevBase.IO + +```csharp +using DevBase.IO; + +// Read file +AFileObject file = AFile.ReadFileToObject("song.lrc"); +string content = file.ToStringData(); + +// Parse +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(content); +``` + +## Quick Reference + +| Task | Code | +|------|------| +| Parse LRC | `new FileParser>()` | +| Parse SRT | `new FileParser>()` | +| Parse ENV | `new FileParser>()` | +| Parse ELRC | `new FileParser>()` | +| From string | `parser.ParseFromString(content)` | +| From file | `parser.ParseFromDisk("file.lrc")` | +| Safe parse | `parser.TryParseFromString(content, out var result)` | +| Get timestamp (ms) | `lyric.StartTimestamp` | +| Get timestamp (TimeSpan) | `lyric.StartTime` | + +## Testing Considerations + +- Test with valid and invalid formats +- Test with empty files +- Test with malformed timestamps +- Test with special characters in lyrics +- Test with different encodings +- Test with large files + +## Version + +Current version: **1.0.0** +Target framework: **.NET 9.0** + +## Dependencies -1. **Use `AList`** for parsed results (not `List`) -2. **TimeSpan for timestamps** (not long/int) -3. **RichTimeStampedLyric** has word-level timing -4. **TimeStampedLyric** has line-level timing only -5. **FileParser** for file-based parsing +- DevBase (core utilities) +- System.Text.RegularExpressions diff --git a/DevBase.Format/README.md b/DevBase.Format/README.md index 52adcd0..d870026 100644 --- a/DevBase.Format/README.md +++ b/DevBase.Format/README.md @@ -1,150 +1,520 @@ # DevBase.Format -A versatile file format parser library for .NET supporting various text-based formats including lyrics, subtitles, and configuration files. +**DevBase.Format** is a comprehensive file format parsing library for .NET 9.0, specializing in lyrics formats (LRC, SRT, ELRC, etc.) and configuration files (ENV). It provides a unified API for parsing various text-based formats with strong typing and error handling. -## Features +## Supported Formats -- **LRC Parser** - Standard lyrics format with timestamps -- **ELRC Parser** - Enhanced LRC with word-level timing -- **SRT Parser** - SubRip subtitle format -- **ENV Parser** - Environment variable files -- **Apple Lyrics XML** - Apple Music lyrics formats -- **MML/RMML** - Musixmatch lyrics formats -- **Extensible Architecture** - Easy to add new format parsers +### Lyrics Formats +- **LRC** - Standard LRC lyrics with timestamps `[mm:ss.xx]` +- **ELRC** - Enhanced LRC with word-level timing +- **RLRC** - Rich LRC format +- **SRT** - SubRip subtitle format +- **Apple XML** - Apple Music lyrics XML format +- **Apple LRC XML** - Apple's LRC-based XML format +- **Apple Rich XML** - Apple's rich-synced lyrics format +- **MML** - Music Markup Language +- **RMML** - Rich Music Markup Language +- **KLyrics** - Karaoke lyrics format + +### Configuration Formats +- **ENV** - Environment variable files (`.env`) ## Installation -```xml - -``` - -Or via NuGet CLI: - ```bash dotnet add package DevBase.Format ``` -## Supported Formats +## Features -| Format | Extension | Description | -|--------|-----------|-------------| -| LRC | `.lrc` | Standard lyrics with line timestamps | -| ELRC | `.elrc` | Enhanced LRC with word timing | -| RLRC | `.rlrc` | Rich LRC format | -| SRT | `.srt` | SubRip subtitle format | -| ENV | `.env` | Environment configuration | -| Apple XML | `.xml` | Apple Music lyrics XML | -| Apple Rich XML | `.xml` | Apple Music rich sync lyrics | -| MML | `.json` | Musixmatch lyrics format | -| RMML | `.json` | Rich Musixmatch format | -| KLyrics | - | Korean lyrics format | +- **Type-Safe Parsing** - Strongly-typed result objects +- **Error Handling** - Try-parse pattern for safe parsing +- **File & String Input** - Parse from disk or in-memory strings +- **Timestamp Conversion** - Automatic timestamp parsing and conversion +- **Rich Metadata** - Extract metadata tags from lyrics files +- **Revertable Formats** - Convert parsed data back to original format -## Usage Examples +## Quick Start -### LRC Parsing +### Parsing LRC Lyrics ```csharp +using DevBase.Format; using DevBase.Format.Formats.LrcFormat; +// Parse from string string lrcContent = @" -[00:12.00]Line one -[00:17.20]Line two -[00:21.10]Line three +[00:12.00]First line +[00:15.50]Second line +[00:20.00]Third line "; -LrcParser parser = new LrcParser(); -var lyrics = parser.Parse(lrcContent); +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(lrcContent); -foreach (var line in lyrics.Lines) +foreach (var line in lyrics) { - Console.WriteLine($"[{line.Timestamp}] {line.Text}"); + Console.WriteLine($"[{line.StartTime}] {line.Text}"); } + +// Parse from file +var lyricsFromFile = parser.ParseFromDisk("song.lrc"); ``` -### SRT Parsing +### Parsing SRT Subtitles ```csharp using DevBase.Format.Formats.SrtFormat; -string srtContent = @" -1 -00:00:12,000 --> 00:00:17,200 -First subtitle line +var parser = new FileParser>(); +var subtitles = parser.ParseFromDisk("movie.srt"); -2 -00:00:17,200 --> 00:00:21,100 -Second subtitle line +foreach (var subtitle in subtitles) +{ + Console.WriteLine($"{subtitle.StartTime}: {subtitle.Text}"); +} +``` + +### Parsing ENV Files + +```csharp +using DevBase.Format.Formats.EnvFormat; + +var parser = new FileParser>(); +var config = parser.ParseFromDisk(".env"); + +string dbHost = config["DB_HOST"]; +string apiKey = config["API_KEY"]; +``` + +### Safe Parsing with Try-Parse + +```csharp +var parser = new FileParser>(); + +if (parser.TryParseFromString(lrcContent, out var lyrics)) +{ + Console.WriteLine($"Parsed {lyrics.Length} lines"); +} +else +{ + Console.WriteLine("Failed to parse lyrics"); +} +``` + +## Usage Examples + +### Working with Timestamps + +```csharp +using DevBase.Format.Structure; + +var lyric = new TimeStampedLyric +{ + Text = "Hello world", + StartTime = TimeSpan.FromSeconds(12.5) +}; + +// Get timestamp in milliseconds +long ms = lyric.StartTimestamp; // 12500 + +// Format for display +Console.WriteLine($"[{lyric.StartTime:mm\\:ss\\.ff}] {lyric.Text}"); +``` + +### Rich-Synced Lyrics (Word-Level Timing) + +```csharp +using DevBase.Format.Structure; + +var richLyric = new RichTimeStampedLyric +{ + StartTime = TimeSpan.FromSeconds(10), + Words = new AList + { + new RichTimeStampedWord { Text = "Hello", StartTime = TimeSpan.FromSeconds(10.0) }, + new RichTimeStampedWord { Text = "world", StartTime = TimeSpan.FromSeconds(10.5) } + } +}; + +foreach (var word in richLyric.Words) +{ + Console.WriteLine($"[{word.StartTime}] {word.Text}"); +} +``` + +### Parsing Apple Music Lyrics + +```csharp +using DevBase.Format.Formats.AppleXmlFormat; + +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(appleMusicXml); + +// Apple XML includes metadata +foreach (var line in lyrics) +{ + Console.WriteLine($"{line.StartTime}: {line.Text}"); +} +``` + +### Creating Custom Format Parser + +```csharp +using DevBase.Format; + +public class MyFormat : FileFormat> +{ + public override AList Parse(string input) + { + var lines = new AList(); + foreach (var line in input.Split('\n')) + { + if (!string.IsNullOrWhiteSpace(line)) + lines.Add(line.Trim()); + } + return lines; + } + + public override bool TryParse(string input, out AList parsed) + { + try + { + parsed = Parse(input); + return true; + } + catch + { + parsed = null; + return false; + } + } +} + +// Use custom format +var parser = new FileParser>(); +var result = parser.ParseFromString("line1\nline2\nline3"); +``` + +### Revertable Formats (Convert Back to String) + +```csharp +using DevBase.Format; + +// Some formats implement RevertableFileFormat +public class LrcFormat : RevertableFileFormat> +{ + public override string Revert(AList parsed) + { + StringBuilder sb = new StringBuilder(); + foreach (var lyric in parsed) + { + int minutes = (int)lyric.StartTime.TotalMinutes; + double seconds = lyric.StartTime.TotalSeconds % 60; + sb.AppendLine($"[{minutes:D2}:{seconds:00.00}]{lyric.Text}"); + } + return sb.ToString(); + } +} + +// Parse and revert +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(lrcContent); + +var format = new LrcFormat(); +string lrcString = format.Revert(lyrics); +``` + +## Format-Specific Details + +### LRC Format + +**Structure:** +``` +[mm:ss.xx]Lyric text +[mm:ss.xx]Another line +``` + +**Metadata Tags:** +``` +[ar:Artist Name] +[ti:Song Title] +[al:Album Name] +[by:Creator] +[offset:+/-milliseconds] +``` + +**Example:** +```csharp +string lrc = @" +[ar:Rick Astley] +[ti:Never Gonna Give You Up] +[al:Whenever You Need Somebody] +[00:12.00]We're no strangers to love +[00:16.00]You know the rules and so do I "; -SrtParser parser = new SrtParser(); -var subtitles = parser.Parse(srtContent); +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(lrc); ``` -### ENV File Parsing +### SRT Format +**Structure:** +``` +1 +00:00:12,000 --> 00:00:15,000 +First subtitle + +2 +00:00:16,000 --> 00:00:20,000 +Second subtitle +``` + +**Example:** ```csharp -using DevBase.Format.Formats.EnvFormat; +var parser = new FileParser>(); +var subtitles = parser.ParseFromDisk("movie.srt"); +``` + +### ENV Format -string envContent = @" -DATABASE_URL=postgres://localhost:5432/db +**Structure:** +``` +KEY=value +DATABASE_URL=postgresql://localhost/mydb API_KEY=secret123 -DEBUG=true -"; +# Comments are supported +``` -EnvParser parser = new EnvParser(); -var variables = parser.Parse(envContent); +**Example:** +```csharp +var parser = new FileParser>(); +var config = parser.ParseFromString("DB_HOST=localhost\nDB_PORT=5432"); -string dbUrl = variables["DATABASE_URL"]; +Console.WriteLine(config["DB_HOST"]); // "localhost" +Console.WriteLine(config["DB_PORT"]); // "5432" ``` -### Generic File Parsing +## Core Classes + +### FileParser + +Generic parser that works with any format: ```csharp -using DevBase.Format; +public class FileParser where P : FileFormat +{ + T ParseFromString(string content); + bool TryParseFromString(string content, out T parsed); + T ParseFromDisk(string filePath); + bool TryParseFromDisk(string filePath, out T parsed); +} +``` -// Auto-detect format by extension -FileParser fileParser = new FileParser(); -var result = fileParser.Parse("lyrics.lrc"); +### FileFormat -// Or specify format explicitly -var lrcResult = fileParser.Parse("content"); +Base class for all format parsers: + +```csharp +public abstract class FileFormat +{ + abstract TOutput Parse(TInput input); + abstract bool TryParse(TInput input, out TOutput parsed); +} ``` -### Converting Between Formats +### RevertableFileFormat + +For formats that can be converted back: ```csharp -// Parse LRC -LrcParser lrcParser = new LrcParser(); -var lyrics = lrcParser.Parse(lrcContent); +public abstract class RevertableFileFormat : FileFormat +{ + abstract TInput Revert(TOutput parsed); +} +``` + +### Data Structures + +| Class | Description | +|-------|-------------| +| `TimeStampedLyric` | Single lyric line with timestamp | +| `RichTimeStampedLyric` | Lyric line with word-level timing | +| `RichTimeStampedWord` | Individual word with timestamp | +| `RawLyric` | Unparsed lyric data | + +## Utilities + +### LyricsUtils -// Convert to SRT -SrtParser srtParser = new SrtParser(); -string srtOutput = srtParser.ToString(lyrics); +```csharp +using DevBase.Format.Utilities; + +// Utility methods for lyrics manipulation +string cleaned = LyricsUtils.CleanLyrics(rawLyrics); +bool hasTimestamps = LyricsUtils.HasTimestamps(lrcContent); ``` -## Architecture +### TimeUtils + +```csharp +using DevBase.Format.Utilities; +// Time conversion utilities +TimeSpan time = TimeUtils.ParseLrcTimestamp("[00:12.50]"); +string formatted = TimeUtils.FormatLrcTimestamp(TimeSpan.FromSeconds(12.5)); ``` -DevBase.Format/ -├── FileFormat.cs # Base format class -├── FileParser.cs # Generic file parser -├── RevertableFileFormat.cs # Format with undo support -├── Formats/ -│ ├── LrcFormat/ # LRC parser -│ ├── ElrcFormat/ # Enhanced LRC parser -│ ├── RlrcFormat/ # Rich LRC parser -│ ├── SrtFormat/ # SRT subtitle parser -│ ├── EnvFormat/ # Environment file parser -│ ├── AppleXmlFormat/ # Apple lyrics XML -│ ├── AppleLrcXmlFormat/ # Apple LRC XML -│ ├── AppleRichXmlFormat/ # Apple rich sync XML -│ ├── MmlFormat/ # Musixmatch format -│ ├── RmmlFormat/ # Rich Musixmatch format -│ └── KLyricsFormat/ # Korean lyrics format -├── Structure/ # Common data structures -└── Utilities/ # Helper utilities + +## Extensions + +### LyricsExtensions + +```csharp +using DevBase.Format.Extensions; + +var lyrics = new AList(); +// Extension methods for lyrics manipulation +var sorted = lyrics.SortByTimestamp(); +var filtered = lyrics.FilterByTimeRange(TimeSpan.Zero, TimeSpan.FromMinutes(2)); ``` +## Error Handling + +### Using Try-Parse Pattern + +```csharp +var parser = new FileParser>(); + +if (parser.TryParseFromDisk("song.lrc", out var lyrics)) +{ + // Success + ProcessLyrics(lyrics); +} +else +{ + // Handle parsing failure + Console.WriteLine("Invalid LRC format"); +} +``` + +### Catching Exceptions + +```csharp +try +{ + var lyrics = parser.ParseFromString(content); +} +catch (ParsingException ex) +{ + Console.WriteLine($"Parsing error: {ex.Message}"); +} +catch (FileNotFoundException ex) +{ + Console.WriteLine($"File not found: {ex.Message}"); +} +``` + +## Integration with DevBase.Api + +Many API clients return lyrics that can be parsed: + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Format; +using DevBase.Format.Formats.LrcFormat; + +var deezer = new Deezer(); +var lyricsString = await deezer.GetLyrics("123456"); + +var parser = new FileParser>(); +var lyrics = parser.ParseFromString(lyricsString); + +foreach (var line in lyrics) +{ + Console.WriteLine($"[{line.StartTime}] {line.Text}"); +} +``` + +## Performance Tips + +1. **Reuse parser instances** for multiple files +2. **Use Try-Parse** for better performance (no exceptions) +3. **Parse from string** when data is already in memory +4. **Use appropriate format** - don't parse LRC as SRT +5. **Cache parsed results** for frequently accessed files + +## Common Patterns + +### Pattern 1: Multi-Format Lyrics Parser + +```csharp +AList ParseLyrics(string content) +{ + // Try LRC first + var lrcParser = new FileParser>(); + if (lrcParser.TryParseFromString(content, out var lrcLyrics)) + return lrcLyrics; + + // Try SRT + var srtParser = new FileParser>(); + if (srtParser.TryParseFromString(content, out var srtLyrics)) + return srtLyrics; + + // Try Apple XML + var appleParser = new FileParser>(); + if (appleParser.TryParseFromString(content, out var appleLyrics)) + return appleLyrics; + + return null; +} +``` + +### Pattern 2: Lyrics Synchronization + +```csharp +void SyncLyricsToAudio(AList lyrics, TimeSpan currentTime) +{ + var currentLyric = lyrics.Find(l => + l.StartTime <= currentTime && + (lyrics.IndexOf(l) == lyrics.Length - 1 || + lyrics[lyrics.IndexOf(l) + 1].StartTime > currentTime) + ); + + if (currentLyric != null) + DisplayLyric(currentLyric.Text); +} +``` + +### Pattern 3: Format Conversion + +```csharp +// Convert LRC to SRT +var lrcParser = new FileParser>(); +var lyrics = lrcParser.ParseFromDisk("song.lrc"); + +var srtFormat = new SrtFormat(); +string srtContent = srtFormat.Revert(lyrics); +File.WriteAllText("song.srt", srtContent); +``` + +## Target Framework + +- **.NET 9.0** + +## Dependencies + +- **DevBase** - Core utilities and collections +- **System.Text.RegularExpressions** - Pattern matching + ## License -MIT License - see LICENSE file for details. +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/DevBase.Logging/AGENT.md b/DevBase.Logging/AGENT.md index 85420ee..b791c37 100644 --- a/DevBase.Logging/AGENT.md +++ b/DevBase.Logging/AGENT.md @@ -1,40 +1,450 @@ -# DevBase.Logging Agent Guide +# DevBase.Logging - AI Agent Guide + +This guide helps AI agents effectively use the DevBase.Logging library for simple logging operations. ## Overview -DevBase.Logging is a lightweight, context-aware logging utility for debug output. -## Key Classes +DevBase.Logging is a lightweight logging library that writes to the Debug console. It's designed for simplicity and ease of use. -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `Logger` | `DevBase.Logging` | Main logging class | +**Target Framework:** .NET 9.0 -## Quick Reference +## Core Components + +### Logger + +```csharp +public class Logger +{ + public Logger(T type); + public void Write(string message, LogType logType); + public void Write(Exception exception); +} +``` + +### LogType Enum + +```csharp +public enum LogType +{ + INFO, + WARNING, + ERROR, + DEBUG +} +``` + +## Usage Patterns for AI Agents + +### Pattern 1: Basic Logging + +```csharp +using DevBase.Logging.Logger; +using DevBase.Logging.Enums; + +public class MyService +{ + private readonly Logger _logger; + + public MyService() + { + _logger = new Logger(this); + } + + public void DoWork() + { + _logger.Write("Starting work", LogType.INFO); + // ... work ... + _logger.Write("Work completed", LogType.INFO); + } +} +``` + +### Pattern 2: Exception Logging + +```csharp +try +{ + PerformOperation(); +} +catch (Exception ex) +{ + _logger.Write(ex); // Logs as ERROR +} +``` + +### Pattern 3: Different Log Levels + +```csharp +_logger.Write("Application started", LogType.INFO); +_logger.Write("Debug information", LogType.DEBUG); +_logger.Write("Potential issue", LogType.WARNING); +_logger.Write("Critical error", LogType.ERROR); +``` + +### Pattern 4: API Request Logging + +```csharp +using DevBase.Net.Core; +using DevBase.Logging.Logger; +using DevBase.Logging.Enums; + +public class ApiClient +{ + private readonly Logger _logger; + + public ApiClient() + { + _logger = new Logger(this); + } + + public async Task GetAsync(string url) + { + _logger.Write($"GET {url}", LogType.INFO); + + try + { + var response = await new Request(url).SendAsync(); + _logger.Write($"Response: {response.StatusCode}", LogType.DEBUG); + return response; + } + catch (Exception ex) + { + _logger.Write(ex); + throw; + } + } +} +``` + +### Pattern 5: Performance Logging + +```csharp +using DevBase.Extensions.Stopwatch; + +public void MonitorOperation() +{ + _logger.Write("Starting operation", LogType.INFO); + + var stopwatch = Stopwatch.StartNew(); + + try + { + PerformOperation(); + stopwatch.Stop(); + + _logger.Write($"Completed in {stopwatch.Elapsed}", LogType.INFO); + } + catch (Exception ex) + { + stopwatch.Stop(); + _logger.Write($"Failed after {stopwatch.Elapsed}", LogType.ERROR); + _logger.Write(ex); + throw; + } +} +``` + +## Important Concepts + +### 1. Logger Instantiation + +**Create one logger per class:** + +```csharp +// ✅ Correct - one logger per class +public class MyService +{ + private readonly Logger _logger; + + public MyService() + { + _logger = new Logger(this); + } +} + +// ❌ Wrong - creating logger each time +public void DoWork() +{ + var logger = new Logger(this); // Don't do this +} +``` + +### 2. Log Output Format + +``` +HH:mm:ss.fffffff : ClassName : LogType : Message +``` + +Example: +``` +14:23:45.1234567 : MyService : INFO : Application started +``` + +### 3. Exception Logging -### Basic Logging ```csharp -using DevBase.Logging; +// Automatically logs as ERROR with exception message +_logger.Write(exception); -Logger logger = new Logger(); -logger.Debug("Debug message"); -logger.Info("Info message"); -logger.Warn("Warning message"); -logger.Error("Error message"); +// Equivalent to: +_logger.Write(exception.Message, LogType.ERROR); ``` -### With Context +### 4. Log Levels + +| Level | Use Case | +|-------|----------| +| INFO | Important events, milestones | +| DEBUG | Detailed diagnostic information | +| WARNING | Potential issues, non-critical errors | +| ERROR | Errors, exceptions | + +## Common Mistakes to Avoid + +### ❌ Mistake 1: Creating Logger in Method + ```csharp -logger.Info("Processing item", context: "ItemProcessor"); +// Wrong - creates new logger each call +public void DoWork() +{ + var logger = new Logger(this); + logger.Write("Working", LogType.INFO); +} + +// Correct - reuse logger instance +private readonly Logger _logger; + +public MyService() +{ + _logger = new Logger(this); +} + +public void DoWork() +{ + _logger.Write("Working", LogType.INFO); +} ``` -## File Structure +### ❌ Mistake 2: Not Logging Exceptions + +```csharp +// Wrong - swallowing exception +try +{ + PerformOperation(); +} +catch (Exception ex) +{ + // No logging! +} + +// Correct +try +{ + PerformOperation(); +} +catch (Exception ex) +{ + _logger.Write(ex); + throw; // Or handle appropriately +} ``` -DevBase.Logging/ -└── Logger.cs + +### ❌ Mistake 3: Wrong Log Level + +```csharp +// Wrong - using ERROR for non-errors +_logger.Write("User logged in", LogType.ERROR); + +// Correct +_logger.Write("User logged in", LogType.INFO); +``` + +### ❌ Mistake 4: Logging Sensitive Data + +```csharp +// Wrong - logging password +_logger.Write($"User: {username}, Password: {password}", LogType.DEBUG); + +// Correct +_logger.Write($"User logged in: {username}", LogType.INFO); +``` + +## Integration Examples + +### With DevBase.Api + +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Logging.Logger; +using DevBase.Logging.Enums; + +public class MusicService +{ + private readonly Logger _logger; + private readonly Deezer _deezer; + + public MusicService() + { + _logger = new Logger(this); + _deezer = new Deezer(); + } + + public async Task SearchAsync(string query) + { + _logger.Write($"Searching: {query}", LogType.INFO); + + try + { + var results = await _deezer.Search(query); + _logger.Write($"Found {results.data.Length} results", LogType.DEBUG); + return results; + } + catch (Exception ex) + { + _logger.Write(ex); + return null; + } + } +} ``` -## Important Notes +### With DevBase.Format + +```csharp +using DevBase.Format; +using DevBase.Format.Formats.LrcFormat; +using DevBase.Logging.Logger; +using DevBase.Logging.Enums; + +public class LyricsService +{ + private readonly Logger _logger; + + public LyricsService() + { + _logger = new Logger(this); + } + + public AList ParseLyrics(string lrcContent) + { + _logger.Write("Parsing LRC lyrics", LogType.DEBUG); + + try + { + var parser = new FileParser>(); + var lyrics = parser.ParseFromString(lrcContent); + + _logger.Write($"Parsed {lyrics.Length} lines", LogType.INFO); + return lyrics; + } + catch (Exception ex) + { + _logger.Write(ex); + return null; + } + } +} +``` + +### With DevBase.Net + +```csharp +using DevBase.Net.Core; +using DevBase.Logging.Logger; +using DevBase.Logging.Enums; + +public class HttpService +{ + private readonly Logger _logger; + + public HttpService() + { + _logger = new Logger(this); + } + + public async Task FetchDataAsync(string url) + { + _logger.Write($"Fetching: {url}", LogType.INFO); + + try + { + var response = await new Request(url).SendAsync(); + + if (response.IsSuccessStatusCode) + { + _logger.Write("Request successful", LogType.DEBUG); + return await response.GetStringAsync(); + } + else + { + _logger.Write($"Request failed: {response.StatusCode}", LogType.WARNING); + return null; + } + } + catch (Exception ex) + { + _logger.Write(ex); + return null; + } + } +} +``` + +## Best Practices + +1. **One logger per class** - Create in constructor, store as field +2. **Use appropriate levels** - INFO for events, DEBUG for details, ERROR for exceptions +3. **Log exceptions** - Always log exceptions with `Write(exception)` +4. **Be concise** - Keep messages short and informative +5. **Avoid sensitive data** - Never log passwords, tokens, or PII +6. **Log at boundaries** - Log at method entry/exit for important operations +7. **Include context** - Add relevant information (IDs, names, counts) + +## Limitations + +- **Debug output only** - No file or other sinks +- **No configuration** - Fixed format +- **No filtering** - All levels output +- **Synchronous** - No async logging + +## When to Use + +**Good for:** +- Development and debugging +- Simple applications +- Internal tools +- Quick prototyping + +**Not for:** +- Production applications +- High-volume logging +- Structured logging needs +- Log aggregation systems + +## Quick Reference + +| Task | Code | +|------|------| +| Create logger | `new Logger(this)` | +| Log info | `_logger.Write(message, LogType.INFO)` | +| Log debug | `_logger.Write(message, LogType.DEBUG)` | +| Log warning | `_logger.Write(message, LogType.WARNING)` | +| Log error | `_logger.Write(message, LogType.ERROR)` | +| Log exception | `_logger.Write(exception)` | + +## Testing Considerations + +- Logger writes to Debug output +- Use Debug listeners in tests to capture output +- Consider mocking logger for unit tests +- Test exception logging paths + +## Version + +Current version: **1.0.0** +Target framework: **.NET 9.0** + +## Dependencies -1. **Lightweight** - minimal overhead -2. **Context-aware** - pass context for better log organization -3. **Debug output** - primarily for development/debugging +- System.Diagnostics diff --git a/DevBase.Logging/README.md b/DevBase.Logging/README.md index 264e8c3..209f6a9 100644 --- a/DevBase.Logging/README.md +++ b/DevBase.Logging/README.md @@ -1,24 +1,57 @@ # DevBase.Logging -A simple, lightweight logging library for .NET applications. +**DevBase.Logging** is a lightweight logging library for .NET 9.0 that provides simple, type-safe logging with minimal configuration. ## Features -- **Simple API** - Easy-to-use logging interface -- **Multiple Log Levels** - Debug, Info, Warning, Error, Fatal -- **Lightweight** - Minimal dependencies -- **Flexible Output** - Console and custom outputs +- **Generic Type Logger** - Type-safe logging with generic type parameter +- **Multiple Log Levels** - INFO, WARNING, ERROR, DEBUG +- **Debug Output** - Writes to Debug console +- **Timestamp Support** - Automatic timestamp inclusion +- **Exception Logging** - Built-in exception handling ## Installation -```xml - +```bash +dotnet add package DevBase.Logging ``` -Or via NuGet CLI: +## Quick Start -```bash -dotnet add package DevBase.Logging +```csharp +using DevBase.Logging.Logger; +using DevBase.Logging.Enums; + +// Create logger with type +var logger = new Logger(this); + +// Log messages +logger.Write("Application started", LogType.INFO); +logger.Write("Configuration loaded", LogType.DEBUG); +logger.Write("Connection timeout", LogType.WARNING); +logger.Write("Database error", LogType.ERROR); + +// Log exceptions +try +{ + // ... operation ... +} +catch (Exception ex) +{ + logger.Write(ex); +} +``` + +## Log Types + +```csharp +public enum LogType +{ + INFO, + WARNING, + ERROR, + DEBUG +} ``` ## Usage Examples @@ -27,55 +60,378 @@ dotnet add package DevBase.Logging ```csharp using DevBase.Logging.Logger; +using DevBase.Logging.Enums; + +public class MyService +{ + private readonly Logger _logger; + + public MyService() + { + _logger = new Logger(this); + } + + public void DoWork() + { + _logger.Write("Starting work", LogType.INFO); + + try + { + // Perform work + _logger.Write("Work in progress", LogType.DEBUG); + } + catch (Exception ex) + { + _logger.Write(ex); + } + + _logger.Write("Work completed", LogType.INFO); + } +} +``` + +### Logging in API Clients -Logger logger = new Logger(); +```csharp +using DevBase.Api.Apis.Deezer; +using DevBase.Logging.Logger; +using DevBase.Logging.Enums; -// Different log levels -logger.Debug("Debug message"); -logger.Info("Information message"); -logger.Warning("Warning message"); -logger.Error("Error message"); -logger.Fatal("Fatal error message"); +public class MusicService +{ + private readonly Logger _logger; + private readonly Deezer _deezer; + + public MusicService() + { + _logger = new Logger(this); + _deezer = new Deezer(); + } + + public async Task SearchAsync(string query) + { + _logger.Write($"Searching for: {query}", LogType.INFO); + + try + { + var results = await _deezer.Search(query); + _logger.Write($"Found {results.data.Length} results", LogType.DEBUG); + return results; + } + catch (Exception ex) + { + _logger.Write(ex); + return null; + } + } +} ``` -### With Context +### Logging HTTP Requests ```csharp -logger.Info("User logged in", new { UserId = 123, Username = "john" }); -logger.Error("Operation failed", exception); +using DevBase.Net.Core; +using DevBase.Logging.Logger; +using DevBase.Logging.Enums; + +public class ApiClient +{ + private readonly Logger _logger; + + public ApiClient() + { + _logger = new Logger(this); + } + + public async Task GetAsync(string url) + { + _logger.Write($"GET {url}", LogType.INFO); + + try + { + var response = await new Request(url).SendAsync(); + + if (response.IsSuccessStatusCode) + { + _logger.Write($"Success: {response.StatusCode}", LogType.DEBUG); + } + else + { + _logger.Write($"Failed: {response.StatusCode}", LogType.WARNING); + } + + return response; + } + catch (Exception ex) + { + _logger.Write(ex); + throw; + } + } +} +``` + +### Application Lifecycle Logging + +```csharp +public class Application +{ + private readonly Logger _logger; + + public Application() + { + _logger = new Logger(this); + } + + public void Start() + { + _logger.Write("Application starting", LogType.INFO); + + try + { + InitializeComponents(); + _logger.Write("Components initialized", LogType.DEBUG); + + LoadConfiguration(); + _logger.Write("Configuration loaded", LogType.DEBUG); + + _logger.Write("Application started successfully", LogType.INFO); + } + catch (Exception ex) + { + _logger.Write(ex); + _logger.Write("Application failed to start", LogType.ERROR); + throw; + } + } + + public void Stop() + { + _logger.Write("Application stopping", LogType.INFO); + + try + { + CleanupResources(); + _logger.Write("Application stopped", LogType.INFO); + } + catch (Exception ex) + { + _logger.Write(ex); + } + } +} ``` -### Configure Log Level +### Performance Monitoring ```csharp +using DevBase.Extensions.Stopwatch; +using DevBase.Logging.Logger; using DevBase.Logging.Enums; -Logger logger = new Logger(EnumLogLevel.Warning); -// Only Warning, Error, and Fatal will be logged +public class PerformanceMonitor +{ + private readonly Logger _logger; + + public PerformanceMonitor() + { + _logger = new Logger(this); + } + + public void MonitorOperation(Action operation, string name) + { + _logger.Write($"Starting: {name}", LogType.DEBUG); + + var stopwatch = Stopwatch.StartNew(); + + try + { + operation(); + stopwatch.Stop(); + + _logger.Write($"Completed: {name} in {stopwatch.Elapsed}", LogType.INFO); + } + catch (Exception ex) + { + stopwatch.Stop(); + _logger.Write($"Failed: {name} after {stopwatch.Elapsed}", LogType.ERROR); + _logger.Write(ex); + throw; + } + } +} +``` + +### Database Operations + +```csharp +public class DatabaseService +{ + private readonly Logger _logger; + + public DatabaseService() + { + _logger = new Logger(this); + } + + public async Task GetUserAsync(int id) + { + _logger.Write($"Fetching user {id}", LogType.DEBUG); + + try + { + var user = await database.Users.FindAsync(id); + + if (user == null) + { + _logger.Write($"User {id} not found", LogType.WARNING); + } + else + { + _logger.Write($"User {id} retrieved", LogType.DEBUG); + } + + return user; + } + catch (Exception ex) + { + _logger.Write(ex); + throw; + } + } +} +``` + +## Log Output Format + +``` +HH:mm:ss.fffffff : ClassName : LogType : Message ``` -## Log Levels +Example: +``` +14:23:45.1234567 : MyService : INFO : Application started +14:23:45.2345678 : MyService : DEBUG : Configuration loaded +14:23:46.3456789 : MyService : ERROR : Connection failed +``` -| Level | Description | -|-------|-------------| -| `Debug` | Detailed debugging information | -| `Info` | General informational messages | -| `Warning` | Warning conditions | -| `Error` | Error conditions | -| `Fatal` | Critical failures | +## Best Practices -## API Reference +1. **Create logger per class** - One logger instance per class +2. **Use appropriate log levels** - INFO for important events, DEBUG for details +3. **Log exceptions** - Always log exceptions with context +4. **Avoid sensitive data** - Don't log passwords, tokens, or PII +5. **Be concise** - Keep log messages short and informative -### Logger Class +## Common Patterns -| Method | Description | -|--------|-------------| -| `Debug(string)` | Log debug message | -| `Info(string)` | Log info message | -| `Warning(string)` | Log warning message | -| `Error(string)` | Log error message | -| `Fatal(string)` | Log fatal message | +### Pattern 1: Try-Catch Logging + +```csharp +try +{ + PerformOperation(); +} +catch (Exception ex) +{ + _logger.Write(ex); + throw; // Re-throw if needed +} +``` + +### Pattern 2: Conditional Logging + +```csharp +if (debugMode) +{ + _logger.Write("Debug information", LogType.DEBUG); +} +``` + +### Pattern 3: Operation Tracking + +```csharp +_logger.Write("Starting operation", LogType.INFO); +// ... operation ... +_logger.Write("Operation completed", LogType.INFO); +``` + +## Integration with DevBase Libraries + +### With DevBase.Net + +```csharp +var logger = new Logger(this); + +logger.Write("Sending request", LogType.DEBUG); +var response = await new Request(url).SendAsync(); +logger.Write($"Response: {response.StatusCode}", LogType.INFO); +``` + +### With DevBase.Api + +```csharp +var logger = new Logger(this); + +logger.Write("Searching Deezer", LogType.INFO); +var results = await deezer.Search(query); +logger.Write($"Found {results.data.Length} results", LogType.DEBUG); +``` + +### With DevBase.Format + +```csharp +var logger = new Logger(this); + +logger.Write("Parsing lyrics", LogType.DEBUG); +var lyrics = parser.ParseFromDisk("song.lrc"); +logger.Write($"Parsed {lyrics.Length} lines", LogType.INFO); +``` + +## Limitations + +- **Debug output only** - Logs to Debug console, not file or other sinks +- **No configuration** - Fixed format and output +- **No filtering** - All log levels are output +- **No async logging** - Synchronous writes only + +## When to Use + +**Good for:** +- Simple applications +- Debug logging during development +- Quick prototyping +- Internal tools + +**Not recommended for:** +- Production applications requiring file logging +- Applications needing log aggregation +- Systems requiring structured logging +- High-performance scenarios + +## Alternatives + +For production applications, consider: +- **Serilog** - Structured logging with sinks +- **NLog** - Flexible logging framework +- **Microsoft.Extensions.Logging** - Built-in .NET logging + +## Target Framework + +- **.NET 9.0** + +## Dependencies + +- **System.Diagnostics** - Debug output ## License -MIT License - see LICENSE file for details. +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/DevBase.Net/AGENT.md b/DevBase.Net/AGENT.md index dbb2f5c..ad34260 100644 --- a/DevBase.Net/AGENT.md +++ b/DevBase.Net/AGENT.md @@ -1,105 +1,463 @@ -# DevBase.Net Agent Guide +# DevBase.Net - AI Agent Guide + +This guide helps AI agents effectively use the DevBase.Net HTTP client library. ## Overview -DevBase.Net is a high-performance HTTP client library with fluent API, proxy support, and advanced features. -## Key Classes +DevBase.Net is the networking backbone of the DevBase solution. It provides a high-performance `Request` class that wraps `HttpClient` with advanced features like SOCKS5 proxying, retry policies, interceptors, and detailed metrics. -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `Request` | `DevBase.Net.Core` | Main HTTP request builder | -| `Response` | `DevBase.Net.Core` | HTTP response wrapper with parsing | -| `ProxyInfo` | `DevBase.Net.Proxy` | Proxy configuration | -| `RetryPolicy` | `DevBase.Net.Core` | Retry strategy configuration | +**Target Framework:** .NET 9.0 -## Quick Reference +## Core Architecture + +### Request Lifecycle + +1. **Build** → Create `Request` with fluent API +2. **Configure** → Add headers, body, timeout, retries, proxy +3. **Execute** → Call `SendAsync()` +4. **Intercept** → Request/Response interceptors modify flow +5. **Parse** → Extract data from `Response` +6. **Dispose** → Release resources + +### Key Components + +| Component | Purpose | +|-----------|---------| +| `Request` | Main entry point, fluent builder, manages execution | +| `Response` | Wraps HttpResponseMessage + MemoryStream, provides parsing | +| `RequestBuilder` | Internal builder for URI, headers, body | +| `HttpToSocks5Proxy` | Local HTTP proxy tunneling to SOCKS5 | +| `RetryPolicy` | Handles transient failures with backoff | +| `RequestMetrics` | Captures precise timing data | +| `IRequestInterceptor` | Middleware for request modification | +| `IResponseInterceptor` | Middleware for response transformation | + +## Usage Patterns for AI Agents + +### Pattern 1: Standard API Call + +**Always use this pattern for external API interactions:** -### Basic Request ```csharp using DevBase.Net.Core; -var response = await new Request("https://api.example.com") +// 1. Create request +var request = new Request("https://api.example.com/resource") + .WithTimeout(TimeSpan.FromSeconds(30)) + .WithHeader("Authorization", "Bearer token"); + +// 2. Send +using Response response = await request.SendAsync(); + +// 3. Validate +if (!response.IsSuccessStatusCode) +{ + Console.WriteLine($"Error: {response.StatusCode}"); + return null; +} + +// 4. Parse +var data = await response.ParseJsonAsync(); +``` + +### Pattern 2: POST with JSON Body + +```csharp +var payload = new { name = "John", age = 30 }; + +var response = await new Request("https://api.example.com/users") + .AsPost() + .WithJsonBody(payload) + .WithHeader("Content-Type", "application/json") .SendAsync(); ``` -### With Headers & Body +### Pattern 3: Handling Proxies + +**When user mentions proxies, use `HttpToSocks5Proxy`:** + ```csharp +using DevBase.Net.Proxy; +using DevBase.Net.Proxy.HttpToSocks5; + +// Create proxy +var proxyInfo = new Socks5ProxyInfo("proxy.example.com", 1080, "user", "pass"); +var proxy = new HttpToSocks5Proxy(new[] { proxyInfo }); + +// Use with request var response = await new Request("https://api.example.com") - .AsPost() - .WithHeader("Authorization", "Bearer token") - .WithJsonBody(new { key = "value" }) - .WithTimeout(TimeSpan.FromSeconds(30)) + .WithProxy(new TrackedProxyInfo(proxyInfo)) .SendAsync(); + +// Cleanup +proxy.Dispose(); ``` -### Response Parsing +**Important:** The proxy starts a local HTTP listener on a random port. Always dispose when done. + +### Pattern 4: Streaming Large Responses + +**For large datasets (NDJSON, logs, CSV), use streaming:** + ```csharp -// JSON to type -var data = await response.ParseJsonAsync(); +await foreach (string line in response.StreamLinesAsync()) +{ + var item = JsonSerializer.Deserialize(line); + await ProcessAsync(item); +} +``` + +### Pattern 5: JSON Path Queries + +**When you only need specific fields from large JSON:** + +```csharp +// Extract single value +string userId = await response.ParseJsonPathAsync("$.user.id"); -// String content -string text = await response.GetStringAsync(); +// Extract array +List names = await response.ParseJsonPathAsync>("$.users[*].name"); -// Bytes -byte[] bytes = await response.GetBytesAsync(); +// Extract nested value +decimal price = await response.ParseJsonPathAsync("$.product.pricing.amount"); ``` -### Proxy Support +### Pattern 6: Retry with Exponential Backoff + +**For unreliable APIs or transient errors:** + ```csharp -using DevBase.Net.Proxy; +var response = await new Request("https://unreliable-api.com/data") + .WithRetryPolicy(RetryPolicy.Exponential( + maxRetries: 3, + baseDelay: TimeSpan.FromSeconds(1) + )) + .SendAsync(); +``` + +### Pattern 7: HTML Scraping -var response = await new Request(url) - .WithProxy(new ProxyInfo("host", 1080, EnumProxyType.Socks5)) +```csharp +var response = await new Request("https://example.com").SendAsync(); +IDocument doc = await response.ParseHtmlAsync(); + +// Query elements +string title = doc.Title; +var links = doc.QuerySelectorAll("a"); +var images = doc.QuerySelectorAll("img[src]"); + +foreach (var link in links) +{ + string href = link.GetAttribute("href"); + string text = link.TextContent; +} +``` + +### Pattern 8: Form Data Submission + +```csharp +var formData = new[] +{ + new FormKeypair("username", "john"), + new FormKeypair("password", "secret"), + new FormKeypair("remember", "true") +}; + +var response = await new Request("https://example.com/login") + .AsPost() + .WithFormBody(formData) .SendAsync(); ``` -## File Structure +### Pattern 9: File Upload + +```csharp +byte[] fileBytes = await File.ReadAllBytesAsync("document.pdf"); + +var response = await new Request("https://api.example.com/upload") + .AsPost() + .WithRawBody(fileBytes, "application/pdf") + .WithHeader("X-Filename", "document.pdf") + .SendAsync(); ``` -DevBase.Net/ -├── Core/ -│ ├── Request.cs # Main request class (partial) -│ ├── RequestConfiguration.cs # With* methods -│ ├── RequestHttp.cs # HTTP execution -│ └── Response.cs # Response wrapper -├── Data/ -│ ├── Body/ # Request body builders -│ └── Header/ # Header builders -├── Proxy/ # Proxy implementations -├── Security/ # JWT/Token handling -└── Utils/ # Helper utilities + +### Pattern 10: Concurrent Requests with Rate Limiting + +```csharp +using DevBase.Async.Task; + +Multitasking taskManager = new Multitasking(capacity: 5); // Max 5 concurrent + +foreach (var url in urls) +{ + taskManager.Register(async () => + { + var response = await new Request(url).SendAsync(); + await ProcessResponseAsync(response); + }); +} + +await taskManager.WaitAll(); ``` -## Important Notes +## Important Concepts -1. **Request is a partial class** split across multiple files -2. **Use `SendAsync()`** to execute requests -3. **Response has `IsSuccessStatusCode`** property for status checks -4. **Use `WithTimeout()`** to set request timeouts -5. **Cookies are handled via `Response.GetCookies()`** +### 1. Response Disposal -## Common Patterns +**Always dispose Response objects:** -### Check Response Status ```csharp -if (!response.IsSuccessStatusCode) +// ✅ Correct - using statement +using Response response = await request.SendAsync(); + +// ✅ Correct - explicit disposal +Response response = await request.SendAsync(); +try { - // Handle error + // Use response } +finally +{ + response.Dispose(); +} + +// ❌ Wrong - memory leak +Response response = await request.SendAsync(); +// No disposal ``` -### With Retry Policy +### 2. Timeout Configuration + +**Default timeout is 30 seconds. Adjust based on use case:** + ```csharp -var response = await new Request(url) - .WithRetryPolicy(new RetryPolicy(maxRetries: 3)) - .SendAsync(); +// Quick API calls +request.WithTimeout(TimeSpan.FromSeconds(10)); + +// File downloads +request.WithTimeout(TimeSpan.FromMinutes(5)); + +// Long-polling +request.WithTimeout(TimeSpan.FromMinutes(30)); + +// Streaming (no timeout) +request.WithTimeout(Timeout.InfiniteTimeSpan); ``` -### Form Data +### 3. Error Handling + ```csharp -var response = await new Request(url) - .AsPost() - .WithFormBody(new Dictionary { - { "key", "value" } - }) +try +{ + using Response response = await request.SendAsync(); + + if (!response.IsSuccessStatusCode) + { + string error = await response.GetStringAsync(); + throw new HttpRequestException($"API error: {response.StatusCode} - {error}"); + } + + return await response.ParseJsonAsync(); +} +catch (TaskCanceledException) +{ + // Timeout occurred + throw new TimeoutException("Request timed out"); +} +catch (HttpRequestException ex) +{ + // Network error + throw new Exception($"Network error: {ex.Message}", ex); +} +``` + +### 4. Proxy Chaining + +**Chain multiple proxies for enhanced anonymity:** + +```csharp +var proxies = new[] +{ + new Socks5ProxyInfo("first.proxy.com", 1080, "user1", "pass1"), + new Socks5ProxyInfo("second.proxy.com", 1080, "user2", "pass2"), + new Socks5ProxyInfo("third.proxy.com", 1080) +}; + +var proxy = new HttpToSocks5Proxy(proxies); +``` + +### 5. Remote vs Local DNS Resolution + +```csharp +var proxy = new HttpToSocks5Proxy(proxyInfo); + +// Remote DNS (SOCKS5h) - default, more private +proxy.ResolveHostnamesLocally = false; + +// Local DNS - faster but less private +proxy.ResolveHostnamesLocally = true; +``` + +## Common Mistakes to Avoid + +### ❌ Mistake 1: Not Disposing Responses + +```csharp +// Wrong +Response response = await request.SendAsync(); +string data = await response.GetStringAsync(); +// Memory leak! + +// Correct +using Response response = await request.SendAsync(); +string data = await response.GetStringAsync(); +``` + +### ❌ Mistake 2: Using Wrong Timeout for Operation + +```csharp +// Wrong - default 30s timeout for large file download +var response = await new Request("https://cdn.com/large-file.zip").SendAsync(); + +// Correct +var response = await new Request("https://cdn.com/large-file.zip") + .WithTimeout(TimeSpan.FromMinutes(10)) .SendAsync(); ``` + +### ❌ Mistake 3: Not Checking Status Code + +```csharp +// Wrong - assumes success +var data = await response.ParseJsonAsync(); + +// Correct +if (response.IsSuccessStatusCode) +{ + var data = await response.ParseJsonAsync(); +} +else +{ + // Handle error +} +``` + +### ❌ Mistake 4: Creating New HttpClient for Each Request + +```csharp +// Wrong - don't do this +using var client = new HttpClient(); +var response = await client.GetAsync(url); + +// Correct - use Request class +using var response = await new Request(url).SendAsync(); +``` + +### ❌ Mistake 5: Not Using Streaming for Large Data + +```csharp +// Wrong - loads entire response into memory +string allData = await response.GetStringAsync(); +foreach (string line in allData.Split('\n')) +{ + Process(line); +} + +// Correct - streams line by line +await foreach (string line in response.StreamLinesAsync()) +{ + Process(line); +} +``` + +### ❌ Mistake 6: Forgetting to Set Content-Type + +```csharp +// Wrong - might fail +request.AsPost().WithRawBody(jsonString); + +// Correct +request.AsPost() + .WithRawBody(jsonString, "application/json") + .WithHeader("Content-Type", "application/json"); + +// Better - use WithJsonBody +request.AsPost().WithJsonBody(obj); +``` + +## Performance Optimization Tips + +1. **Reuse Request configuration** for multiple calls to same endpoint +2. **Use connection pooling** (enabled by default) +3. **Stream large responses** instead of loading into memory +4. **Use JSON Path** for extracting specific fields from large JSON +5. **Configure appropriate timeouts** to avoid hanging +6. **Use retry policies** for transient failures +7. **Dispose responses** to release resources quickly +8. **Use ArrayPool** for buffer management (done internally) + +## Metrics and Monitoring + +```csharp +Response response = await request.SendAsync(); +RequestMetrics m = response.Metrics; + +// Log performance +Console.WriteLine($"DNS: {m.DnsLookupTime.TotalMilliseconds}ms"); +Console.WriteLine($"Connect: {m.ConnectionTime.TotalMilliseconds}ms"); +Console.WriteLine($"TLS: {m.TlsHandshakeTime.TotalMilliseconds}ms"); +Console.WriteLine($"TTFB: {m.TimeToFirstByte.TotalMilliseconds}ms"); +Console.WriteLine($"Total: {m.TotalTime.TotalMilliseconds}ms"); +Console.WriteLine($"Downloaded: {m.BytesReceived} bytes"); + +// Detect slow requests +if (m.TotalTime.TotalSeconds > 5) +{ + Console.WriteLine("Warning: Slow request detected"); +} +``` + +## Integration with Other DevBase Libraries + +- **DevBase.Api** - All API clients use `Request` for HTTP calls +- **DevBase** - Uses `AList` for collections +- **DevBase.Format** - Can parse responses with format parsers + +## Quick Reference + +| Task | Method | +|------|--------| +| GET request | `new Request(url).SendAsync()` | +| POST JSON | `.AsPost().WithJsonBody(obj)` | +| Add header | `.WithHeader(name, value)` | +| Set timeout | `.WithTimeout(TimeSpan)` | +| Use proxy | `.WithProxy(TrackedProxyInfo)` | +| Retry policy | `.WithRetryPolicy(RetryPolicy.Exponential(3))` | +| Parse JSON | `await response.ParseJsonAsync()` | +| JSON Path | `await response.ParseJsonPathAsync(path)` | +| Parse HTML | `await response.ParseHtmlAsync()` | +| Stream lines | `await foreach (var line in response.StreamLinesAsync())` | +| Get string | `await response.GetStringAsync()` | +| Get bytes | `await response.GetBytesAsync()` | +| Check status | `response.IsSuccessStatusCode` | + +## Testing Considerations + +- **Mock responses** using `IResponseInterceptor` +- **Simulate failures** with retry policies +- **Test timeouts** with appropriate delays +- **Verify metrics** are captured correctly +- **Test proxy connections** require actual SOCKS5 server + +## Version + +Current version: **1.1.0** +Target framework: **.NET 9.0** + +## Dependencies + +- Newtonsoft.Json +- AngleSharp +- Serilog +- System.IO.Pipelines +- ZiggyCreatures.FusionCache diff --git a/DevBase.Net/README.md b/DevBase.Net/README.md index 0811262..41e6a75 100644 --- a/DevBase.Net/README.md +++ b/DevBase.Net/README.md @@ -1,31 +1,23 @@ -# DevBase.Request +# DevBase.Net -A modern, high-performance HTTP client library for .NET with fluent API, proxy support, retry policies, and advanced parsing features. +A modern, high-performance HTTP client library for .NET 9.0 with fluent API, SOCKS5 proxy support, retry policies, and advanced response parsing. ## Features -- **Fluent API** - Intuitive, chainable method calls -- **Async/Await** - Fully asynchronous implementation -- **Connection Pooling** - Efficient HTTP client reuse -- **Proxy Support** - HTTP, HTTPS, SOCKS4 and SOCKS5 (including proxy chaining) -- **Retry Policies** - Configurable retry strategies with backoff -- **Response Caching** - Built-in caching with SHA256 keys -- **JsonPath Parsing** - Streaming-capable JSON parsing -- **Browser Spoofing** - Realistic user-agent generation -- **Header Validation** - Automatic header validation -- **Request/Response Interceptors** - Middleware pattern -- **Metrics** - Detailed request performance metrics +- Fluent request builder API +- SOCKS5 proxy support with HttpToSocks5Proxy +- Configurable retry policies (linear/exponential backoff) +- JSON, HTML, XML parsing +- JSON Path queries +- Response streaming +- Request/Response interceptors +- Detailed request metrics +- Connection pooling ## Installation -```xml - -``` - -Or via NuGet CLI: - ```bash -dotnet add package DevBase.Request +dotnet add package DevBase.Net ``` ## Quick Start @@ -34,137 +26,82 @@ dotnet add package DevBase.Request using DevBase.Net.Core; // Simple GET request -Request request = new Request("https://api.example.com/data"); -Response response = await request.SendAsync(); +var response = await new Request("https://api.example.com/data").SendAsync(); string content = await response.GetStringAsync(); -// With Fluent API -Response response = await new Request("https://api.example.com/users") - .AsGet() - .WithAcceptJson() - .WithTimeout(TimeSpan.FromSeconds(10)) +// POST with JSON +var response = await new Request("https://api.example.com/users") + .AsPost() + .WithJsonBody(new { name = "John" }) .SendAsync(); - -MyUser user = await response.ParseJsonAsync(); ``` -## Basic Usage +## Usage Examples -### GET Requests +### HTTP Methods ```csharp -Request request = new Request("https://api.example.com/data"); -Response response = await request.SendAsync(); -``` +// GET (default) +var response = await new Request(url).SendAsync(); -### POST Requests with JSON +// POST +var response = await new Request(url).AsPost().WithJsonBody(data).SendAsync(); -```csharp -MyData data = new MyData { Name = "Test", Value = 42 }; +// PUT +var response = await new Request(url).AsPut().WithJsonBody(data).SendAsync(); -Response response = await new Request("https://api.example.com/create") - .AsPost() - .WithJsonBody(data) - .SendAsync(); +// DELETE +var response = await new Request(url).AsDelete().SendAsync(); ``` -### Headers and Parameters +### Headers and Authentication ```csharp -Response response = await new Request("https://api.example.com/search") - .WithHeader("X-Custom-Header", "CustomValue") - .WithParameter("query", "test") - .WithParameters(("page", "1"), ("limit", "50")) +var response = await new Request(url) + .WithHeader("Authorization", "Bearer token") + .WithHeader("X-Custom", "value") .SendAsync(); ``` -## Response Processing +### Response Parsing ```csharp -Response response = await request.SendAsync(); - -// As string -string content = await response.GetStringAsync(); - -// As bytes -byte[] bytes = await response.GetBytesAsync(); +// JSON +var data = await response.ParseJsonAsync(); -// JSON deserialization -MyClass result = await response.ParseJsonAsync(); +// JSON Path +var value = await response.ParseJsonPathAsync("$.user.name"); -// HTML parsing with AngleSharp -IDocument htmlDoc = await response.ParseHtmlAsync(); +// HTML +var doc = await response.ParseHtmlAsync(); -// JsonPath queries -string name = await response.ParseJsonPathAsync("$.user.name"); +// String +var text = await response.GetStringAsync(); ``` -## Retry Policies +### SOCKS5 Proxy ```csharp -Response response = await new Request("https://api.example.com/data") - .WithRetryPolicy(RetryPolicy.Default) // 3 retries, Linear Backoff - .SendAsync(); - -// Custom policy -RetryPolicy customPolicy = new RetryPolicy -{ - MaxRetries = 5, - BaseDelay = TimeSpan.FromSeconds(1), - BackoffStrategy = EnumBackoffStrategy.Exponential -}; -``` +using DevBase.Net.Proxy.HttpToSocks5; -## Proxy Support - -```csharp -// HTTP Proxy -ProxyInfo proxyInfo = new ProxyInfo("proxy.example.com", 8080); - -// SOCKS5 Proxy -ProxyInfo socks5Proxy = new ProxyInfo("socks.example.com", 1080, EnumProxyType.Socks5); - -Response response = await new Request("https://api.example.com/data") - .WithProxy(proxyInfo) +var proxy = new HttpToSocks5Proxy("127.0.0.1", 9050); +var response = await new Request(url) + .WithProxy(proxy) .SendAsync(); ``` -## Authentication +### Retry Policies ```csharp -// Basic Authentication -Response response = await new Request("https://api.example.com/protected") - .UseBasicAuthentication("username", "password") +var response = await new Request(url) + .WithRetryPolicy(RetryPolicy.Exponential(maxRetries: 3)) .SendAsync(); - -// Bearer Token -Response response = await new Request("https://api.example.com/protected") - .UseBearerAuthentication("your-jwt-token-here") - .SendAsync(); -``` - -## Batch Requests - -```csharp -Requests batchRequests = new Requests() - .WithRateLimit(10, TimeSpan.FromSeconds(1)) - .WithParallelism(5) - .Add("https://api.example.com/item/1") - .Add("https://api.example.com/item/2"); - -List responses = await batchRequests.SendAllAsync(); ``` -## Metrics +## Credits -```csharp -Response response = await request.SendAsync(); -RequestMetrics metrics = response.Metrics; - -Console.WriteLine($"Total Duration: {metrics.TotalDuration.TotalMilliseconds}ms"); -Console.WriteLine($"Time to First Byte: {metrics.TimeToFirstByte.TotalMilliseconds}ms"); -``` +HttpToSocks5Proxy based on [MihaZupan/HttpToSocks5Proxy](https://github.com/MihaZupan/HttpToSocks5Proxy) (MIT License). ## License -MIT License - see LICENSE file for details. +MIT License diff --git a/DevBase/AGENT.md b/DevBase/AGENT.md index 62b2d21..028a15c 100644 --- a/DevBase/AGENT.md +++ b/DevBase/AGENT.md @@ -1,94 +1,327 @@ -# DevBase (Core) Agent Guide +# DevBase - AI Agent Guide + +This guide helps AI agents effectively use the DevBase core library. ## Overview -DevBase is the core library providing generic collections, IO utilities, and async task management. -## Key Classes +DevBase is the foundational library providing core utilities for the entire DevBase solution. It targets **.NET 9.0** and has no external dependencies. -| Class | Namespace | Purpose | -|-------|-----------|---------| -| `AList` | `DevBase.Generics` | Enhanced generic list | -| `AFile` | `DevBase.IO` | File operations utility | -| `AFileObject` | `DevBase.IO` | File wrapper object | -| `AString` | `DevBase.Typography` | String utilities | +## Key Concepts -## Quick Reference +### 1. AList - The Core Collection Type + +**When to use:** Prefer `AList` over `List` throughout the DevBase ecosystem for consistency. -### AList - Enhanced List ```csharp using DevBase.Generics; -AList list = new AList(); -list.Add("item"); -string item = list.Get(0); -bool empty = list.IsEmpty(); -int length = list.Length; -string[] array = list.GetAsArray(); -List asList = list.GetAsList(); -string random = list.GetRandom(); +// Creation +AList items = new AList(); +AList numbers = new AList(1, 2, 3, 4, 5); +AList users = new AList(existingList); + +// Common operations +items.Add("value"); +items.AddRange("a", "b", "c"); +items.Remove(0); +items.RemoveRange(0, 2); +string item = items[0]; +bool exists = items.Contains("value"); + +// Advanced operations +string random = items.GetRandom(); +AList> chunks = items.Slice(10); // Split into chunks of 10 +items.ForEach(x => Console.WriteLine(x)); +items.Sort(Comparer.Default); ``` -### AFile - File Operations +**Important:** +- `Contains()` and `Remove()` use size-based optimization (faster but requires valid objects) +- Use `SafeContains()` and `SafeRemove()` when size comparison might fail +- `FindEntry()` throws `AListEntryException` if not found; use `Find()` for safe searches + +### 2. File I/O Pattern + +**Always use `AFile` static methods for file operations:** + ```csharp using DevBase.IO; -// Get files recursively -AList files = AFile.GetFiles("path", recursive: true, "*.txt"); +// Read file with automatic encoding detection +Memory content = AFile.ReadFile(filePath, out Encoding encoding); + +// Read file as object (includes metadata) +AFileObject fileObj = AFile.ReadFileToObject(filePath); +byte[] data = fileObj.Content.ToArray(); +Encoding enc = fileObj.Encoding; +FileInfo info = fileObj.FileInfo; -// Read file -AFileObject file = files.Get(0); -string content = file.ToStringData(); -byte[] bytes = file.ToByteData(); +// Get all files in directory +AList files = AFile.GetFiles( + directory: "path/to/dir", + readContent: true, // Load file contents + filter: "*.txt" // File pattern +); + +// Check file access before operations +if (AFile.CanFileBeAccessed(fileInfo, FileAccess.ReadWrite)) +{ + // Perform operations +} ``` -### AString - String Utilities +**Key points:** +- Encoding is automatically detected via `StreamReader` +- Returns `Memory` for efficient memory usage +- Use `AFileObject` when you need both content and metadata + +### 3. Multitasking - Concurrent Task Management + +**Use for managing many concurrent operations with capacity limits:** + ```csharp -using DevBase.Typography; +using DevBase.Async.Task; + +// Create with capacity limit +Multitasking taskManager = new Multitasking( + capacity: 10, // Max 10 concurrent tasks + scheduleDelay: 100 // Check interval in ms +); + +// Register tasks (they don't start immediately) +for (int i = 0; i < 100; i++) +{ + taskManager.Register(async () => + { + await ProcessItemAsync(i); + }); +} + +// Wait for all to complete +await taskManager.WaitAll(); -AString str = new AString("line1\nline2\nline3"); -AList lines = str.AsList(); +// Or cancel all remaining tasks +await taskManager.KillAll(); ``` -## File Structure +**Important:** +- Tasks are queued and executed based on capacity +- Completed tasks are automatically removed +- Use `WaitAll()` to ensure all tasks finish +- Use `KillAll()` to cancel pending tasks + +### 4. Caching Pattern + +**Use `DataCache` for time-based caching:** + +```csharp +using DevBase.Cache; + +// Create cache with expiration time +DataCache cache = new DataCache( + expirationMS: 60000 // 60 seconds +); + +// Write to cache +cache.WriteToCache("key", response); + +// Read from cache (returns default if expired or not found) +ApiResponse cached = cache.DataFromCache("key"); +if (cached != null) +{ + // Use cached data +} + +// Check existence +if (cache.IsInCache("key")) +{ + // Key exists and not expired +} + +// Multiple entries with same key +cache.WriteToCache("tag", response1); +cache.WriteToCache("tag", response2); +AList all = cache.DataFromCacheAsList("tag"); ``` -DevBase/ -├── Cache/ # Caching utilities -├── Generics/ -│ └── AList.cs # Enhanced generic list -├── IO/ -│ ├── AFile.cs # File operations -│ └── AFileObject.cs # File wrapper -├── Typography/ -│ └── AString.cs # String utilities -└── Utilities/ # Helper utilities + +**Key points:** +- Expired entries are automatically cleaned on read operations +- Returns `default(V)` if key not found or expired +- Supports multiple values per key with `DataFromCacheAsList()` + +### 5. String Utilities + +```csharp +using DevBase.Utilities; +using DevBase.Typography; + +// Generate random strings +string id = StringUtils.RandomString(16); +string hex = StringUtils.RandomString(8, "0123456789ABCDEF"); + +// Join and split +string joined = StringUtils.Separate(new[] { "a", "b", "c" }, ", "); +string[] parts = StringUtils.DeSeparate(joined, ", "); + +// AString operations +AString text = new AString("multi\nline\ntext"); +AList lines = text.AsList(); // Split by newlines +string capitalized = text.CapitalizeFirst(); ``` -## Important Notes +### 6. Base64 Encoding -1. **Prefer `AList` over `List`** for DevBase projects -2. **Use `Get(index)` instead of indexer `[index]`** for AList -3. **`IsEmpty()` checks for empty list** -4. **`Length` property for count** (not `Count`) -5. **`GetAsArray()` converts to array** +```csharp +using DevBase.Typography.Encoded; + +// Encode +Base64EncodedAString encoded = new Base64EncodedAString("plain text"); +string base64 = encoded.ToString(); + +// Decode +Base64EncodedAString decoded = new Base64EncodedAString(base64, isEncoded: true); +string plain = decoded.GetDecoded(); +``` ## Common Patterns -### Iterate AList +### Pattern 1: Processing Files in Directory + ```csharp -for (int i = 0; i < list.Length; i++) +AList files = AFile.GetFiles("path", readContent: true, filter: "*.json"); + +files.ForEach(file => +{ + string content = file.Encoding.GetString(file.Content.ToArray()); + ProcessJson(content); +}); +``` + +### Pattern 2: Batch Processing with Concurrency Control + +```csharp +Multitasking tasks = new Multitasking(capacity: 5); + +items.ForEach(item => +{ + tasks.Register(async () => await ProcessAsync(item)); +}); + +await tasks.WaitAll(); +``` + +### Pattern 3: Cached API Calls + +```csharp +DataCache cache = new DataCache(30000); + +async Task GetDataAsync(string key) { - var item = list.Get(i); + if (cache.IsInCache(key)) + return cache.DataFromCache(key); + + ApiResponse response = await FetchFromApiAsync(key); + cache.WriteToCache(key, response); + return response; } +``` + +### Pattern 4: Chunked Processing + +```csharp +AList items = GetLargeItemList(); +AList> chunks = items.Slice(100); // Process 100 at a time -// Or use GetAsList() for foreach -foreach (var item in list.GetAsList()) +chunks.ForEach(chunk => { - // process + ProcessBatch(chunk.GetAsList()); +}); +``` + +## Exception Handling + +```csharp +try +{ + T item = list.FindEntry(searchItem); } +catch (AListEntryException ex) +{ + // Handle: EntryNotFound, OutOfBounds, InvalidRange + if (ex.ExceptionType == AListEntryException.Type.EntryNotFound) + { + // Item not found + } +} +``` + +## Memory Efficiency Tips + +1. **Use `Memory` and `Span`** - DevBase uses these for file operations +2. **Avoid unnecessary conversions** - `AList` can return arrays directly with `GetAsArray()` +3. **Slice large collections** - Use `Slice()` to process data in chunks +4. **Reuse `Multitasking` instances** - Don't create new instances for each batch + +## Integration with Other DevBase Libraries + +- **DevBase.Net** uses `AList` for collections +- **DevBase.Api** uses `AList` for response data +- **DevBase.Format** uses `AList` for parsed data +- All libraries follow the same patterns for consistency + +## Common Mistakes to Avoid + +❌ **Don't use `List` when `AList` is available** +```csharp +List items = new List(); // Avoid +AList items = new AList(); // Prefer ``` -### Check Empty +❌ **Don't forget encoding when reading files** ```csharp -if (list.IsEmpty()) - return; +byte[] bytes = File.ReadAllBytes(path); // No encoding info +Memory bytes = AFile.ReadFile(path, out Encoding enc); // Better ``` + +❌ **Don't create unbounded concurrent tasks** +```csharp +foreach (var item in items) + Task.Run(() => Process(item)); // Can overwhelm system + +Multitasking tasks = new Multitasking(10); +items.ForEach(item => tasks.Register(() => Process(item))); // Controlled +``` + +❌ **Don't use `FindEntry()` without try-catch** +```csharp +var item = list.FindEntry(search); // Throws if not found +var item = list.Find(x => x == search); // Returns default if not found +``` + +## Quick Reference + +| Task | Method | +|------|--------| +| Create list | `new AList()` or `new AList(items)` | +| Add items | `Add()`, `AddRange()` | +| Remove items | `Remove()`, `RemoveRange()`, `SafeRemove()` | +| Search | `Find()`, `FindEntry()`, `Contains()` | +| Iterate | `ForEach()`, `foreach` loop | +| Convert | `GetAsList()`, `GetAsArray()` | +| Read file | `AFile.ReadFile()`, `AFile.ReadFileToObject()` | +| List files | `AFile.GetFiles()` | +| Manage tasks | `Multitasking.Register()`, `WaitAll()` | +| Cache data | `DataCache.WriteToCache()`, `DataFromCache()` | +| Random string | `StringUtils.RandomString()` | + +## Testing Considerations + +- **`AList`** operations are synchronous and fast +- **`Multitasking`** requires `await` for completion +- **`DataCache`** expiration is time-based, consider in tests +- **File operations** require actual file system access + +## Version + +Current version: **1.3.4** +Target framework: **.NET 9.0** diff --git a/DevBase/README.md b/DevBase/README.md index 7379ff1..15a219e 100644 --- a/DevBase/README.md +++ b/DevBase/README.md @@ -1,146 +1,261 @@ # DevBase -A comprehensive .NET development base library providing essential utilities, data structures, and helper classes for everyday development needs. +**DevBase** is a foundational .NET library providing core utilities, generic collections, I/O helpers, async task management, caching, and string manipulation tools. It serves as the base layer for the entire DevBase solution. ## Features -- **Generic Collections** - `AList`, `ATupleList` with enhanced functionality -- **Async Utilities** - Task management, multithreading helpers, and suspension tokens -- **Caching** - Simple in-memory caching with `DataCache` -- **IO Operations** - File and directory abstractions (`AFile`, `ADirectory`) -- **String Handling** - `AString` with encoding support and Base64 utilities -- **Web Utilities** - HTTP request helpers with cookie management -- **Type Utilities** - Generic type conversion and encoding helpers +### 🔹 Generic Collections +- **`AList`** - Enhanced array-backed list with optimized operations +- **`ATupleList`** - Tuple-based key-value collection +- Memory-efficient operations with size-based comparisons + +### 🔹 I/O Operations +- **`AFile`** - File reading with encoding detection +- **`AFileObject`** - File representation with metadata +- **`ADirectory`** - Directory operations and management +- Buffered stream reading with `Memory` support + +### 🔹 Async Task Management +- **`Multitasking`** - Concurrent task scheduler with capacity limits +- **`TaskRegister`** - Task registration and tracking +- Automatic task lifecycle management + +### 🔹 Caching +- **`DataCache`** - Time-based expiration cache +- Automatic cleanup of expired entries +- Thread-safe operations + +### 🔹 Typography & String Utilities +- **`AString`** - String wrapper with enhanced operations +- **`Base64EncodedAString`** - Base64 encoding/decoding +- **`StringUtils`** - Random string generation, separation, and formatting + +### 🔹 Utilities +- **`MemoryUtils`** - Memory size calculations +- **`EncodingUtils`** - Encoding detection and conversion +- **`CollectionUtils`** - Collection manipulation helpers ## Installation -```xml - -``` - -Or via NuGet CLI: - ```bash dotnet add package DevBase ``` ## Usage Examples -### Generic Collections +### AList - Enhanced Generic List ```csharp using DevBase.Generics; -// Enhanced list with additional methods -AList list = new AList(); -list.Add("item1"); -list.Add("item2"); +// Create and populate +AList items = new AList(); +items.Add("apple"); +items.AddRange("banana", "cherry"); + +// Access elements +string first = items[0]; +string random = items.GetRandom(); + +// Search and filter +string found = items.Find(x => x.StartsWith("b")); +bool contains = items.Contains("apple"); + +// Slice into chunks +AList> chunks = items.Slice(2); -// Tuple list for key-value pairs -ATupleList tupleList = new ATupleList(); -tupleList.Add("key", 42); +// Range operations +string[] range = items.GetRangeAsArray(0, 1); +items.RemoveRange(0, 1); + +// Iteration +items.ForEach(item => Console.WriteLine(item)); + +// Sorting +items.Sort(Comparer.Default); ``` -### Async Task Management +### File Operations ```csharp -using DevBase.Async.Task; +using DevBase.IO; + +// Read file with encoding detection +Memory content = AFile.ReadFile("path/to/file.txt", out Encoding encoding); -// Task registration and management -TaskRegister register = new TaskRegister(); -register.RegisterTask(async () => await DoWorkAsync()); +// Read file to object +AFileObject fileObj = AFile.ReadFileToObject("path/to/file.txt"); +Console.WriteLine($"Size: {fileObj.FileInfo.Length} bytes"); +Console.WriteLine($"Encoding: {fileObj.Encoding}"); -// Multitasking with suspension support -Multitasking tasks = new Multitasking(); -tasks.AddTask(myTask); -await tasks.RunAllAsync(); +// Get all files in directory +AList files = AFile.GetFiles("path/to/directory", readContent: true, filter: "*.txt"); + +// Check file accessibility +FileInfo file = new FileInfo("path/to/file.txt"); +bool canRead = AFile.CanFileBeAccessed(file, FileAccess.Read); ``` -### Caching +### Multitasking - Concurrent Task Management ```csharp -using DevBase.Cache; +using DevBase.Async.Task; -DataCache cache = new DataCache(); -cache.Set("key", myData); -MyData? cached = cache.Get("key"); +// Create task manager with capacity of 5 concurrent tasks +Multitasking taskManager = new Multitasking(capacity: 5, scheduleDelay: 100); + +// Register tasks +for (int i = 0; i < 20; i++) +{ + int taskId = i; + taskManager.Register(() => + { + Console.WriteLine($"Task {taskId} executing"); + Thread.Sleep(1000); + }); +} + +// Wait for all tasks to complete +await taskManager.WaitAll(); + +// Or cancel all tasks +await taskManager.KillAll(); ``` -### File Operations +### DataCache - Time-Based Caching ```csharp -using DevBase.IO; +using DevBase.Cache; -// File abstraction -AFile file = new AFile("path/to/file.txt"); -string content = file.ReadAllText(); +// Create cache with 5-second expiration +DataCache userCache = new DataCache(expirationMS: 5000); -// Directory operations -ADirectory dir = new ADirectory("path/to/dir"); -var files = dir.GetFiles(); +// Write to cache +userCache.WriteToCache("user123", new User { Name = "John" }); + +// Read from cache +User user = userCache.DataFromCache("user123"); + +// Check if in cache +bool exists = userCache.IsInCache("user123"); + +// Multiple entries with same key +userCache.WriteToCache("tag", new User { Name = "Alice" }); +userCache.WriteToCache("tag", new User { Name = "Bob" }); +AList users = userCache.DataFromCacheAsList("tag"); ``` ### String Utilities ```csharp +using DevBase.Utilities; using DevBase.Typography; -AString str = new AString("Hello World"); -string result = str.ToLowerCase(); +// Generate random string +string random = StringUtils.RandomString(10); +string alphanumeric = StringUtils.RandomString(8, "0123456789ABCDEF"); -// Base64 encoding -using DevBase.Typography.Encoded; -Base64EncodedAString encoded = new Base64EncodedAString("data"); -string base64 = encoded.Encode(); +// Separate and de-separate +string[] items = new[] { "apple", "banana", "cherry" }; +string joined = StringUtils.Separate(items, ", "); // "apple, banana, cherry" +string[] split = StringUtils.DeSeparate(joined, ", "); + +// AString operations +AString text = new AString("hello world\nline two"); +AList lines = text.AsList(); +string capitalized = text.CapitalizeFirst(); // "Hello world\nline two" ``` -### Web Requests +### Base64 Encoding ```csharp -using DevBase.Web; +using DevBase.Typography.Encoded; + +// Encode string to Base64 +Base64EncodedAString encoded = new Base64EncodedAString("Hello World"); +string base64 = encoded.ToString(); -Request request = new Request("https://api.example.com/data"); -ResponseData response = await request.GetAsync(); -string content = response.Content; +// Decode from Base64 +Base64EncodedAString decoded = new Base64EncodedAString(base64, isEncoded: true); +string original = decoded.GetDecoded(); ``` -## API Reference +## Key Classes Reference ### Collections - | Class | Description | |-------|-------------| -| `AList` | Enhanced generic list | -| `ATupleList` | List of tuples | +| `AList` | Array-backed generic list with enhanced operations | +| `ATupleList` | Tuple-based key-value collection | | `GenericTypeConversion` | Type conversion utilities | -### Async +### I/O +| Class | Description | +|-------|-------------| +| `AFile` | Static file operations with encoding detection | +| `AFileObject` | File wrapper with content and metadata | +| `ADirectory` | Directory operations | +| `ADirectoryObject` | Directory wrapper with metadata | +### Async | Class | Description | |-------|-------------| -| `TaskRegister` | Task registration and management | -| `Multitasking` | Parallel task execution | -| `TaskSuspensionToken` | Task suspension control | -| `AThread` | Thread abstraction | +| `Multitasking` | Concurrent task scheduler with capacity control | +| `TaskRegister` | Task registration and tracking | +| `Multithreading` | Thread management utilities | -### IO +### Cache +| Class | Description | +|-------|-------------| +| `DataCache` | Time-based expiration cache | +| `CacheElement` | Cache entry with expiration timestamp | +### Typography | Class | Description | |-------|-------------| -| `AFile` | File operations wrapper | -| `ADirectory` | Directory operations wrapper | -| `AFileObject` | File metadata | -| `ADirectoryObject` | Directory metadata | +| `AString` | String wrapper with enhanced operations | +| `Base64EncodedAString` | Base64 encoding/decoding | +| `EncodedAString` | Base class for encoded strings | ### Utilities - | Class | Description | |-------|-------------| -| `StringUtils` | String manipulation helpers | -| `EncodingUtils` | Encoding utilities | -| `MemoryUtils` | Memory management helpers | -| `CollectionUtils` | Collection utilities | +| `StringUtils` | String generation and manipulation | +| `MemoryUtils` | Memory size calculations | +| `EncodingUtils` | Encoding detection and conversion | +| `CollectionUtils` | Collection manipulation helpers | + +## Exceptions + +| Exception | Description | +|-----------|-------------| +| `AListEntryException` | Thrown for invalid list operations (out of bounds, invalid range, entry not found) | +| `EncodingException` | Thrown for encoding-related errors | +| `ErrorStatementException` | Thrown for general error conditions | + +## Performance Considerations + +- **`AList`** uses size-based comparison before equality checks for faster lookups +- **`SafeContains()`** and **`SafeRemove()`** skip size checks when needed +- File operations use `BufferedStream` and `Memory` for efficiency +- **`Multitasking`** limits concurrent tasks to prevent resource exhaustion + +## Target Framework + +- **.NET 9.0** + +## Dependencies + +- No external dependencies (pure .NET) ## License -MIT License - see LICENSE file for details. +MIT License - See LICENSE file for details + +## Author + +AlexanderDotH + +## Repository + +https://github.com/AlexanderDotH/DevBase diff --git a/docs/DevBase.Api.md b/docs/DevBase.Api.md deleted file mode 100644 index d8517f9..0000000 --- a/docs/DevBase.Api.md +++ /dev/null @@ -1,229 +0,0 @@ -# DevBase.Api - -DevBase.Api is a comprehensive client library for integrating with various Music, AI, and Lyrics services. It provides a unified and strongly-typed way to interact with these platforms. - -## Table of Contents -- [Music Services](#music-services) - - [Deezer](#deezer) - - [Tidal](#tidal) - - [Apple Music](#apple-music) - - [NetEase Cloud Music](#netease-cloud-music) - - [Musixmatch](#musixmatch) -- [AI Services](#ai-services) - - [OpenAI](#openai) - - [Replicate](#replicate) -- [Lyrics Services](#lyrics-services) - - [BeautifulLyrics](#beautifullyrics) - - [OpenLyricsClient](#openlyricsclient) - ---- - -## Music Services - -### Deezer -Interact with the Deezer API, including authentication, search, and track details. - -**Key Features:** -- **Search**: Tracks, Albums, Artists. -- **Details**: detailed song metadata. -- **Lyrics**: Fetch synchronized lyrics (LRC) via internal APIs (Ajax/Pipe). -- **Download**: (Experimental) Decryption of tracks (Blowfish). - -**Initialization:** -```csharp -using DevBase.Api.Apis.Deezer; - -// Initialize (ARL token optional, needed for some user-specific features) -var deezer = new Deezer("your_arl_token"); -``` - -**Examples:** -```csharp -// Search for a track -var searchResult = await deezer.Search("Eminem Lose Yourself"); - -// Get Track Details -var track = await deezer.GetSong("123456789"); -Console.WriteLine($"{track.Title} by {track.Artists[0]}"); - -// Get Lyrics (Synced) -var lyrics = await deezer.GetLyrics("123456789"); -foreach(var line in lyrics.TimeStampedLyrics) -{ - Console.WriteLine($"[{line.StartTime}] {line.Text}"); -} -``` - -### Tidal -Interact with the Tidal API. Requires valid Client ID/Secret or Access Tokens. - -**Key Features:** -- **Authentication**: OAuth2 Device Flow, Login with credentials (if supported). -- **Search**: High-fidelity track search. -- **Download**: Get stream URLs. - -**Initialization:** -```csharp -using DevBase.Api.Apis.Tidal; - -var tidal = new Tidal(); -``` - -**Examples:** -```csharp -// Login (using a known access token for session) -var session = await tidal.Login("your_access_token"); - -// Search -var results = await tidal.Search("The Weeknd", countryCode: "US"); - -// Get Lyrics -var lyrics = await tidal.GetLyrics("your_access_token", "track_id"); -``` - -### Apple Music -Wrapper for the Apple Music API (Kit). - -**Key Features:** -- **Search**: Catalog search using `amp-api`. -- **Lyrics**: Fetch syllable-synced lyrics (requires User Media Token). - -**Initialization:** -```csharp -using DevBase.Api.Apis.AppleMusic; - -// Initialize with Developer Token -var appleMusic = new AppleMusic("your_developer_token"); - -// (Optional) Add User Media Token for personalized/restricted endpoints -appleMusic.WithMediaUserToken(myUserToken); -``` - -**Examples:** -```csharp -// Search -var songs = await appleMusic.Search("Taylor Swift", limit: 5); - -// Get Lyrics (Requires User Media Token) -var lyricsResponse = await appleMusic.GetLyrics("song_id"); -``` - -### NetEase Cloud Music -Integration with NetEase Cloud Music (via `music.xianqiao.wang` or similar proxy). - -**Key Features:** -- **Search**: Standard keyword search. -- **Lyrics**: Fetch standard and "Karaoke" (K-Lyrics) format. -- **Download**: Get direct download URLs. - -**Initialization:** -```csharp -using DevBase.Api.Apis.NetEase; - -var netease = new NetEase(); -``` - -**Examples:** -```csharp -// Search -var searchResult = await netease.Search("Anime OST"); - -// Get Download URL -var urlResponse = await netease.Url("track_id"); - -// Get Lyrics -var lyrics = await netease.Lyrics("track_id"); -``` - -### Musixmatch -Client for the Musixmatch API. - -**Initialization:** -```csharp -using DevBase.Api.Apis.Musixmatch; - -var mxm = new MusixMatch(); -``` - -**Examples:** -```csharp -// Login -var auth = await mxm.Login("email", "password"); -``` - ---- - -## AI Services - -### OpenAI -Simple wrapper for OpenAI's API, currently focused on Audio Transcription (Whisper). - -**Initialization:** -```csharp -using DevBase.Api.Apis.OpenAi; - -var openai = new OpenAi("your_api_key"); -``` - -**Examples:** -```csharp -// Transcribe Audio -byte[] audioData = File.ReadAllBytes("audio.mp3"); -var transcription = await openai.Transcribe(audioData); -Console.WriteLine(transcription.text); -``` - -### Replicate -Client for Replicate.com to run AI models. - -**Initialization:** -```csharp -using DevBase.Api.Apis.Replicate; - -// Pass a list of tokens to rotate or just one -var replicate = new Replicate(new AList("token1", "token2")); -``` - -**Examples:** -```csharp -// Run a Prediction -var response = await replicate.Predict( - modelID: "version_id", - linkToAudio: "https://...", - model: "whisper", - webhook: "https://callback.url" -); -``` - ---- - -## Lyrics Services - -### BeautifulLyrics -Fetches lyrics from the BeautifulLyrics service (often provides rich sync). - -**Examples:** -```csharp -using DevBase.Api.Apis.BeautifulLyrics; - -var client = new BeautifulLyrics(); - -// Get Lyrics by ISRC -var lyrics = await client.GetLyrics("US123456789"); -``` - -### OpenLyricsClient -Client for OpenLyricsClient API, supporting AI synchronization and subscriptions. - -**Initialization:** -```csharp -using DevBase.Api.Apis.OpenLyricsClient; - -var client = new OpenLyricsClient("server_public_key"); -``` - -**Examples:** -```csharp -// AI Sync -var result = await client.AiSync(subscription, "Title", "Album", durationMs, "model", "Artist"); -``` diff --git a/docs/DevBase.Avalonia.md b/docs/DevBase.Avalonia.md deleted file mode 100644 index 932c809..0000000 --- a/docs/DevBase.Avalonia.md +++ /dev/null @@ -1,98 +0,0 @@ -# DevBase.Avalonia & DevBase.Avalonia.Extension - -These libraries provide powerful tools for working with colors and images within the Avalonia UI framework. They are particularly useful for extracting color palettes from images, performing color space conversions (RGB <-> Lab), and manipulating bitmaps. - -## Table of Contents -- [DevBase.Avalonia](#devbaseavalonia) - - [Image Color Calculators](#image-color-calculators) -- [DevBase.Avalonia.Extension](#devbaseavaloniaextension) - - [Bitmap Extensions](#bitmap-extensions) - - [LabColor Extensions](#labcolor-extensions) - ---- - -## DevBase.Avalonia - -Focuses on analyzing images to extract meaningful color data. - -### Image Color Calculators -Located in `DevBase.Avalonia.Color.Image`, these classes help you extract specific types of colors from a `Bitmap`. - -#### Common Parameters -Most calculators share these configuration properties: -- **PixelSteps**: Determines how many pixels to skip when sampling (higher = faster but less accurate). Default: 10. -- **ColorRange**: The tolerance for grouping similar colors. -- **BigShift / SmallShift**: Weights used during color averaging/correction. - -#### BrightestColorCalculator -Finds the brightest color in an image. It identifies the pixel with the highest calculated brightness and then averages similar surrounding pixels to return a representative color. - -```csharp -using DevBase.Avalonia.Color.Image; - -var calculator = new BrightestColorCalculator(); -var brightestColor = calculator.GetColorFromBitmap(myBitmap); -``` - -#### GroupColorCalculator -Groups similar colors together to find the "dominant" color group in an image. It handles noise by filtering out small groups and averaging the largest color cluster. - -```csharp -var calculator = new GroupColorCalculator(); -calculator.Brightness = 20; // Ignore very dark colors -var dominantColor = calculator.GetColorFromBitmap(myBitmap); -``` - -#### NearestColorCalculator -Finds colors that are distinct or closest to a baseline, useful for finding accent colors that stand out or blend in. - -```csharp -var calculator = new NearestColorCalculator(); -var accentColor = calculator.GetColorFromBitmap(myBitmap); -``` - ---- - -## DevBase.Avalonia.Extension - -Provides extension methods for seamless interoperability and advanced color math. - -### Bitmap Extensions -Facilitates conversion between different image libraries. -- **Avalonia** (`Avalonia.Media.Imaging.Bitmap`) -- **System.Drawing** (`System.Drawing.Bitmap`) -- **ImageSharp** (`SixLabors.ImageSharp.Image`) - -**Example:** -```csharp -using DevBase.Avalonia.Extension.Extension; - -// Avalonia -> System.Drawing -System.Drawing.Bitmap sysBitmap = avaloniaBitmap.ToBitmap(); - -// System.Drawing -> Avalonia -Avalonia.Media.Imaging.Bitmap avBitmap = sysBitmap.ToBitmap(); - -// ImageSharp -> Avalonia -Avalonia.Media.Imaging.Bitmap avBitmapFromSharp = imageSharpImage.ToBitmap(); -``` - -### LabColor Extensions -Extends the `Colourful.LabColor` type for advanced filtering and manipulation. - -**Features:** -- **FilterBrightness**: Returns colors within a specific lightness (L) range. -- **FilterChroma**: Returns colors within a specific chromatic intensity. -- **ToPastel**: Converts a color to a pastel version by adjusting lightness and saturation. -- **Converters**: Batch convert `AList` to `AList` and vice versa. - -**Example:** -```csharp -using DevBase.Avalonia.Extension.Extension; - -// Filter for bright colors -var brightColors = allLabColors.FilterBrightness(min: 70, max: 100); - -// Convert to pastel -var pastelColor = myLabColor.ToPastel(); -``` diff --git a/docs/DevBase.Cryptography.md b/docs/DevBase.Cryptography.md deleted file mode 100644 index 4ae63b8..0000000 --- a/docs/DevBase.Cryptography.md +++ /dev/null @@ -1,100 +0,0 @@ -# DevBase.Cryptography - -DevBase offers two cryptography libraries: -1. **DevBase.Cryptography**: Contains legacy or specific implementations (Blowfish, MD5). -2. **DevBase.Cryptography.BouncyCastle**: A modern wrapper around the BouncyCastle library, providing high-level abstractions for AES-GCM, Token Verification, and more. - -## Table of Contents -- [DevBase.Cryptography](#devbasecryptography) - - [Blowfish](#blowfish) - - [MD5](#md5) -- [DevBase.Cryptography.BouncyCastle](#devbasecryptographybouncycastle) - - [AES (GCM)](#aes-gcm) - - [Token Verification](#token-verification) - ---- - -## DevBase.Cryptography - -### Blowfish -An implementation of the Blowfish algorithm in CBC (Cipher Block Chaining) mode. - -**Features:** -- Encrypts and decrypts byte spans. -- Requires an 8-byte initialization vector (IV). - -**Example:** -```csharp -using DevBase.Cryptography.Blowfish; - -byte[] key = ...; // Your key -var blowfish = new Blowfish(key); - -byte[] iv = ...; // 8 bytes IV -Span data = ...; // Data to encrypt (multiple of 8 bytes) - -// Encrypt -blowfish.Encrypt(data, iv); - -// Decrypt -blowfish.Decrypt(data, iv); -``` - -### MD5 -Simple helper class for MD5 hashing. - -**Example:** -```csharp -using DevBase.Cryptography.MD5; - -// To Hex String -string hash = MD5.ToMD5String("Hello World"); - -// To Binary -byte[] hashBytes = MD5.ToMD5Binary("Hello World"); -``` - ---- - -## DevBase.Cryptography.BouncyCastle - -### AES (GCM) -`AESBuilderEngine` provides a secure, easy-to-use wrapper for AES encryption in GCM (Galois/Counter Mode). It handles nonce generation and management automatically. - -**Key Features:** -- **Automatic Nonce Handling**: Generates a 12-byte nonce for every encryption and prepends it to the output. -- **Secure Random**: Uses BouncyCastle's `SecureRandom` for key and nonce generation. -- **Base64 Helpers**: Methods to encrypt/decrypt strings directly to/from Base64. - -**Example:** -```csharp -using DevBase.Cryptography.BouncyCastle.AES; - -// Initialize -var aes = new AESBuilderEngine(); -aes.SetRandomKey(); // Or .SetKey(yourKey) - -// Encrypt String -string secret = "My Secret Data"; -string encrypted = aes.EncryptString(secret); - -// Decrypt String -string decrypted = aes.DecryptString(encrypted); -``` - -### Token Verification -Abstract base classes and implementations for verifying cryptographic signatures/tokens, similar to JWT verification logic. - -**Classes:** -- `SymmetricTokenVerifier`: Base class for verifying signatures where the secret is shared. - -**Example (Conceptual):** -```csharp -// Assuming an implementation exists or you extend SymmetricTokenVerifier -verifier.VerifySignature( - header: "...", - payload: "...", - signature: "...", - secret: "my-secret-key" -); -``` diff --git a/docs/DevBase.Extensions.md b/docs/DevBase.Extensions.md deleted file mode 100644 index d35d000..0000000 --- a/docs/DevBase.Extensions.md +++ /dev/null @@ -1,54 +0,0 @@ -# DevBase.Extensions - -DevBase.Extensions provides extension methods to enhance standard .NET types. Currently, it focuses on extensions for `System.Diagnostics.Stopwatch` to provide detailed and formatted execution time reporting. - -## Table of Contents -- [Stopwatch Extensions](#stopwatch-extensions) - -## Stopwatch Extensions - -### StopwatchExtension - -Provides extension methods for `System.Diagnostics.Stopwatch` to generate or print a markdown-formatted table of elapsed time, broken down into hours, minutes, seconds, milliseconds, microseconds, and nanoseconds. - -**Dependencies:** -- `ConsoleTables` - -**Key Methods:** -- `PrintTimeTable()`: Prints the formatted table directly to the Console. -- `GetTimeTable()`: Returns the formatted table as a string. - -**Note:** The stopwatch must be stopped before calling these methods, otherwise a `StopwatchException` is thrown. - -**Example:** -```csharp -using System.Diagnostics; -using DevBase.Extensions.Stopwatch; - -var stopwatch = new Stopwatch(); -stopwatch.Start(); - -// Perform some heavy work -Thread.Sleep(1234); - -stopwatch.Stop(); - -// Print table to console -stopwatch.PrintTimeTable(); - -// Or get the string for logging -string table = stopwatch.GetTimeTable(); -Console.WriteLine(table); -``` - -**Output Format:** -The output is a markdown table showing only the non-zero time units. - -```text - -------------------------- - | Data | Unit | - -------------------------- - | 1 | Second | - | 234 | Milliseconds | - -------------------------- -``` diff --git a/docs/DevBase.Format.md b/docs/DevBase.Format.md deleted file mode 100644 index a12d7e1..0000000 --- a/docs/DevBase.Format.md +++ /dev/null @@ -1,86 +0,0 @@ -# DevBase.Format - -DevBase.Format provides a robust parsing framework for various file formats. It utilizes a generic `FileFormat` base class to standardize parsing logic, supporting both one-way parsing and revertable (two-way) parsing. - -## Table of Contents -- [Core Architecture](#core-architecture) -- [Supported Formats](#supported-formats) - - [LRC (Lyrics)](#lrc-lyrics) - - [ENV (Environment Variables)](#env-environment-variables) - - [SRT (Subtitles)](#srt-subtitles) - -## Core Architecture - -### FileFormat -The base abstract class for all parsers. -- **F**: The input format type (usually `string` for text-based formats). -- **T**: The output type (e.g., `AList`). -- **StrictErrorHandling**: A boolean property to toggle between throwing exceptions or returning default values on error. - -### RevertableFileFormat -Extends `FileFormat` to support converting the parsed object back to its original format (e.g., saving modified lyrics back to an .lrc string). - -## Supported Formats - -### LRC (Lyrics) -Parses standard `.lrc` files into a list of timestamped lyrics. - -**Class:** `LrcParser` -**Input:** `string` (file content) -**Output:** `AList` - -**Example:** -```csharp -using DevBase.Format.Formats.LrcFormat; -using DevBase.Format.Structure; - -string lrcContent = "[00:12.00]Line 1\n[00:15.30]Line 2"; -var parser = new LrcParser(); - -// Parse -if (parser.TryParse(lrcContent, out var lyrics)) -{ - foreach(var line in lyrics) - { - Console.WriteLine($"{line.StartTime}: {line.Text}"); - } -} - -// Revert (Convert back to string) -string newContent = parser.Revert(lyrics); -``` - -### ENV (Environment Variables) -Parses `.env` files into key-value pairs. - -**Class:** `EnvParser` -**Input:** `string` -**Output:** `ATupleList` - -**Example:** -```csharp -using DevBase.Format.Formats.EnvFormat; - -string envContent = "KEY=Value\nDEBUG=true"; -var parser = new EnvParser(); - -var envVars = parser.Parse(envContent); -string debugValue = envVars.FindEntry("DEBUG"); // Returns "true" -``` - -### SRT (Subtitles) -Parses `.srt` subtitle files. - -**Class:** `SrtParser` -**Input:** `string` -**Output:** `AList` (Contains StartTime, EndTime, and Text) - -**Example:** -```csharp -using DevBase.Format.Formats.SrtFormat; - -string srtContent = "..."; // Load SRT content -var parser = new SrtParser(); - -var subtitles = parser.Parse(srtContent); -``` diff --git a/docs/DevBase.Logging.md b/docs/DevBase.Logging.md deleted file mode 100644 index f30022f..0000000 --- a/docs/DevBase.Logging.md +++ /dev/null @@ -1,64 +0,0 @@ -# DevBase.Logging - -DevBase.Logging is a lightweight logging utility designed for simple debug output to the Visual Studio Output window (or any listener attached to `System.Diagnostics.Debug`). - -## Table of Contents -- [Logger](#loggert) -- [LogType](#logtype) - -## Logger -The generic `Logger` class allows you to instantiate a logger bound to a specific context (usually the class where it is used). - -**Features:** -- Writes to `System.Diagnostics.Debug`. -- formats output with Timestamp, Class Name, Log Type, and Message. - -**Constructor:** -- `Logger(T type)`: Initializes the logger. passing `this` is common practice. - -**Methods:** -- `Write(string message, LogType debugType)`: Logs a formatted message. -- `Write(Exception exception)`: Logs an exception message with `LogType.ERROR`. - -**Example:** -```csharp -using DevBase.Logging.Logger; -using DevBase.Logging.Enums; - -public class MyService -{ - private readonly Logger _logger; - - public MyService() - { - _logger = new Logger(this); - } - - public void DoWork() - { - _logger.Write("Starting work...", LogType.INFO); - - try - { - // ... work - } - catch (Exception ex) - { - _logger.Write(ex); - } - } -} -``` - -**Output Format:** -```text -14:23:45.1234567 : MyService : INFO : Starting work... -``` - -## LogType -Enum defining the severity levels of logs. - -- `INFO` -- `DEBUG` -- `ERROR` -- `FATAL` diff --git a/docs/DevBase.Net.md b/docs/DevBase.Net.md deleted file mode 100644 index b08fed1..0000000 --- a/docs/DevBase.Net.md +++ /dev/null @@ -1,115 +0,0 @@ -# DevBase.Net - -DevBase.Net is a modern, high-performance HTTP client library for .NET. It offers a fluent API, robust proxy support (including SOCKS5 tunneling), retry policies, and advanced response processing capabilities. - -## Table of Contents -- [Core Features](#core-features) -- [Basic Usage](#basic-usage) -- [Advanced Usage](#advanced-usage) - - [Proxy Support](#proxy-support) - - [Authentication](#authentication) - - [Batch Requests](#batch-requests) - - [Metrics](#metrics) - -## Core Features -- **Fluent API**: Chainable methods for building requests intuitively. -- **Async/Await**: Built from the ground up for asynchronous operations. -- **Connection Pooling**: Efficient reuse of `HttpClient` instances. -- **Proxy Support**: Native support for HTTP, HTTPS, SOCKS4, and SOCKS5 proxies, including chaining. -- **Retry Policies**: configurable retry strategies with exponential or linear backoff. -- **Response Caching**: Built-in caching mechanism to reduce redundant network calls. - -## Basic Usage - -### Simple GET -```csharp -using DevBase.Net.Core; - -// Simplest form -Request request = new Request("https://api.example.com/data"); -Response response = await request.SendAsync(); -string content = await response.GetStringAsync(); -``` - -### Fluent POST with JSON -```csharp -var payload = new { Name = "Test", Value = 42 }; - -Response response = await new Request("https://api.example.com/create") - .AsPost() - .WithJsonBody(payload) - .WithHeader("X-API-Key", "secret") - .SendAsync(); - -if (response.IsSuccessStatusCode) -{ - Console.WriteLine("Success!"); -} -``` - -### Response Parsing -The `Response` object provides several helpers to parse content: -```csharp -// Parse JSON directly to type -MyModel model = await response.ParseJsonAsync(); - -// Parse specific field via JsonPath -string token = await response.ParseJsonPathAsync("$.auth.token"); - -// Get raw bytes -byte[] data = await response.GetBytesAsync(); -``` - -## Advanced Usage - -### Proxy Support -DevBase.Net includes `HttpToSocks5Proxy`, allowing you to tunnel HTTP traffic through SOCKS5 proxies. - -```csharp -using DevBase.Net.Proxy; - -// Standard HTTP Proxy -var httpProxy = new ProxyInfo("1.2.3.4", 8080); - -// SOCKS5 Proxy -var socksProxy = new ProxyInfo("5.6.7.8", 1080, EnumProxyType.Socks5); - -// Apply to request -var response = await new Request("https://api.ipify.org") - .WithProxy(socksProxy) - .SendAsync(); -``` - -### Authentication -Helper methods make adding standard authentication headers easy. - -```csharp -// Basic Auth -.UseBasicAuthentication("user", "pass") - -// Bearer Token -.UseBearerAuthentication("eyJh...") -``` - -### Batch Requests -Send multiple requests in parallel with rate limiting. - -```csharp -var batch = new Requests() - .WithRateLimit(requestsPerSecond: 5) - .WithParallelism(degreeOfParallelism: 3); - -batch.Add("https://site.com/1"); -batch.Add("https://site.com/2"); - -List results = await batch.SendAllAsync(); -``` - -### Metrics -Inspect request performance details. - -```csharp -RequestMetrics metrics = response.Metrics; -Console.WriteLine($"Total: {metrics.TotalDuration.TotalMilliseconds}ms"); -Console.WriteLine($"TTFB: {metrics.TimeToFirstByte.TotalMilliseconds}ms"); -``` diff --git a/docs/DevBase.md b/docs/DevBase.md deleted file mode 100644 index 7577c2e..0000000 --- a/docs/DevBase.md +++ /dev/null @@ -1,119 +0,0 @@ -# DevBase (Core) - -DevBase is the core library providing essential utilities, custom generic collections, IO helpers, and asynchronous task management. - -## Table of Contents -- [Generics](#generics) -- [IO Utilities](#io-utilities) -- [Asynchronous Operations](#asynchronous-operations) -- [Typography](#typography) -- [Utilities](#utilities) - -## Generics - -### AList -`AList` is a custom implementation of a list that wraps a standard array but provides additional utility methods, including performance optimizations for searching based on memory size. - -**Key Features:** -- **Memory-Optimized Search**: Uses `MemoryUtils.GetSize()` to quickly filter objects before checking equality. -- **Fluent API**: Methods for slicing, random selection, and safe removal. - -**Example:** -```csharp -using DevBase.Generics; - -// Create a new list -var list = new AList("Alpha", "Beta", "Gamma"); - -// Add items -list.Add("Delta"); - -// Find entry (optimized) -string entry = list.FindEntry("Beta"); - -// Safe Remove (checks existence first) -list.SafeRemove("Gamma"); - -// Get Random item -string random = list.GetRandom(); -``` - -**Tip:** -`AList` relies on `MemoryUtils.GetSize()` for some operations. Ensure `Globals.ALLOW_SERIALIZATION` is true (default) for this to work effectively with complex objects. - -### ATupleList -Extends `AList` to handle `Tuple`, allowing you to search by either the first or second item of the tuple. - -**Example:** -```csharp -var tupleList = new ATupleList(); -tupleList.Add(1, "One"); -tupleList.Add(2, "Two"); - -// Find by Item1 -string value = tupleList.FindEntry(1); // Returns "One" - -// Find by Item2 -int key = tupleList.FindEntry("Two"); // Returns 2 -``` - -## IO Utilities - -### AFile -Static helper class for file operations, reading files into memory or `AFileObject`. - -**Example:** -```csharp -using DevBase.IO; - -// Read file content into memory -Memory data = AFile.ReadFile("path/to/file.txt"); - -// Get all files in directory as objects -var files = AFile.GetFiles("path/to/dir", readContent: true, filter: "*.json"); -``` - -### ADirectory -Helper for retrieving directory structures. - -## Asynchronous Operations - -### Multitasking -Manages a queue of tasks with a constrained capacity, useful for throttling concurrent operations. - -**Example:** -```csharp -using DevBase.Async.Task; - -// Create a manager that allows 5 concurrent tasks -var taskManager = new Multitasking(capacity: 5); - -// Register tasks -for(int i = 0; i < 20; i++) { - taskManager.Register(() => { - Console.WriteLine($"Working {i}"); - Thread.Sleep(1000); - }); -} - -// Wait for all to finish -await taskManager.WaitAll(); -``` - -## Typography - -### AString -A wrapper around `string` providing additional manipulation methods. - -**Example:** -```csharp -var aStr = new AString("hello world"); -Console.WriteLine(aStr.CapitalizeFirst()); // "Hello world" -``` - -## Utilities - -### MemoryUtils -Provides methods to get the size of objects via serialization. - -**Note:** This relies on `BinaryFormatter` which is obsolete in newer .NET versions. Use with caution or ensure `Globals.ALLOW_SERIALIZATION` is managed if security is a concern. diff --git a/docs/DevBaseLive.md b/docs/DevBaseLive.md deleted file mode 100644 index 1cddd2b..0000000 --- a/docs/DevBaseLive.md +++ /dev/null @@ -1,58 +0,0 @@ -# DevBaseLive - -DevBaseLive is a console application designed as a comprehensive test suite and performance benchmark for the `DevBase.Requests` library. It verifies functional correctness, measures performance, and compares results against the standard .NET `HttpClient`. - -## Overview - -The application performs three main categories of tests: - -1. **Functional Tests**: Verifies that individual features work as expected (GET/POST, Headers, Auth, Parsing). -2. **Performance Tests**: Measures the execution time of different parsing strategies (JsonPath vs. Full Deserialization). -3. **Comparison Tests**: Benchmarks `DevBase.Requests` against `HttpClient` to ensure competitive performance and data integrity. - -## Test Suite Details - -### Functional Tests -Ensures the core reliability of the library. -- **GET/POST Requests**: Basic connectivity and payload transmission. -- **JSON Parsing**: Verifies generic deserialization. -- **JsonPath**: Tests the streaming JsonPath extractor. -- **Header Validation**: Checks valid Accept/Content-Type headers. -- **JWT Parsing**: Validates token structure and expiration logic. -- **Timeouts**: Confirms that request timeouts throw appropriate exceptions. - -### Performance Tests -Runs multiple iterations to calculate average response times for: -1. **DevBase + JsonPath**: Extracting specific fields without full object deserialization. -2. **HttpClient + JsonSerializer**: Standard approach. -3. **DevBase + Full Deserialization**: Full object mapping using DevBase. - -### Comparison Tests -- **Response Time**: Checks if DevBase is within an acceptable margin of HttpClient (aims for <= 1.5x of HttpClient's raw speed, usually competitive). -- **Data Integrity**: Ensures all methods return identical data sets. - -## Usage - -To run the test suite: - -```bash -cd DevBaseLive -dotnet run -``` - -**Sample Output:** -```text -╔══════════════════════════════════════════════════════════╗ -║ DevBase.Requests Test Suite ║ -╚══════════════════════════════════════════════════════════╝ - - Warming up HTTP connections... - -╔══════════════════════════════════════════════════════════╗ -║ Functional Tests ║ -╚══════════════════════════════════════════════════════════╝ - - GET Request - Basic PASS (150ms) - GET Request - JSON Parsing PASS (200ms) - ... -``` diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 891568b..0000000 --- a/docs/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# DevBase Solution Documentation - -Welcome to the comprehensive documentation for the DevBase solution. This solution contains a set of libraries designed to provide robust utilities, API clients, and helpers for .NET development. - -## Documentation Index - -### Core Libraries -- **[DevBase (Core)](DevBase.md)** - The foundation of the solution. Contains custom generic collections (`AList`), IO utilities, asynchronous task management, and basic helpers. - -- **[DevBase.Extensions](DevBase.Extensions.md)** - Extensions for standard .NET types, featuring a robust `Stopwatch` formatter for benchmarking and logging. - -- **[DevBase.Logging](DevBase.Logging.md)** - A lightweight, context-aware logging utility for debug output. - -### Web & APIs -- **[DevBase.Net](DevBase.Net.md)** - A high-performance, fluent HTTP client library with support for SOCKS5 proxies, retry policies, and advanced response parsing. - -- **[DevBase.Api](DevBase.Api.md)** - A collection of ready-to-use API clients for major services including: - - **Music**: Deezer, Tidal, Apple Music, NetEase, Musixmatch. - - **AI**: OpenAI, Replicate. - - **Lyrics**: BeautifulLyrics, OpenLyricsClient. - -### Data Formats & Cryptography -- **[DevBase.Format](DevBase.Format.md)** - Parsers for various file formats including LRC (Lyrics), SRT (Subtitles), and ENV files. - -- **[DevBase.Cryptography](DevBase.Cryptography.md)** - Cryptographic implementations including Blowfish, MD5, and modern AES-GCM wrappers via BouncyCastle. - -### UI & Graphics -- **[DevBase.Avalonia](DevBase.Avalonia.md)** - Utilities for the Avalonia UI framework, specifically focused on image color analysis (Dominant/Accent color extraction) and color space conversions. - -### Testing & Tools -- **[DevBaseLive](DevBaseLive.md)** - A console application used for functional testing and performance benchmarking of the `DevBase.Net` library.