namespace MarketAlly.Replicate.Maui.Services { /// /// Default implementation of IFileSaveService. /// public class FileSaveService : IFileSaveService { private readonly HttpClient _httpClient; public FileSaveService(HttpClient httpClient) { _httpClient = httpClient; } public async Task SaveFromUrlAsync( string url, TransformationType type, string? savePath, string filenamePattern, string? predictionId = null, CancellationToken cancellationToken = default) { try { // Determine save path var basePath = savePath ?? FileSystem.CacheDirectory; // Ensure directory exists if (!Directory.Exists(basePath)) { Directory.CreateDirectory(basePath); } // Download the file var response = await _httpClient.GetAsync(url, cancellationToken); response.EnsureSuccessStatusCode(); // Determine file extension from content type or URL var extension = GetFileExtension(response.Content.Headers.ContentType?.MediaType, url, type); // Generate filename from pattern var filename = GenerateFilename(filenamePattern, predictionId, extension); // Full path var filePath = Path.Combine(basePath, filename); // Save the file var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); await File.WriteAllBytesAsync(filePath, bytes, cancellationToken); return FileSaveResult.Succeeded(filePath, bytes.Length); } catch (Exception ex) { return FileSaveResult.Failed(ex); } } public async Task SaveBytesAsync( byte[] bytes, TransformationType type, string? savePath, string filenamePattern, string extension, string? predictionId = null) { try { // Determine save path var basePath = savePath ?? FileSystem.CacheDirectory; // Ensure directory exists if (!Directory.Exists(basePath)) { Directory.CreateDirectory(basePath); } // Generate filename from pattern var filename = GenerateFilename(filenamePattern, predictionId, extension); // Full path var filePath = Path.Combine(basePath, filename); // Save the file await File.WriteAllBytesAsync(filePath, bytes); return FileSaveResult.Succeeded(filePath, bytes.Length); } catch (Exception ex) { return FileSaveResult.Failed(ex); } } public string GenerateFilename(string pattern, string? predictionId, string extension) { var now = DateTimeOffset.Now; var result = pattern .Replace("{timestamp}", now.ToUnixTimeSeconds().ToString()) .Replace("{datetime}", now.ToString("yyyyMMdd_HHmmss")) .Replace("{id}", predictionId ?? Guid.NewGuid().ToString("N")[..8]) .Replace("{guid}", Guid.NewGuid().ToString("N")); // Sanitize filename foreach (var c in Path.GetInvalidFileNameChars()) { result = result.Replace(c, '_'); } // Add extension if not already present if (!result.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) { result += extension; } return result; } private static string GetFileExtension(string? contentType, string url, TransformationType type) { // Try to get from content type if (!string.IsNullOrEmpty(contentType)) { var ext = contentType switch { "image/png" => ".png", "image/jpeg" or "image/jpg" => ".jpg", "image/webp" => ".webp", "image/gif" => ".gif", "video/mp4" => ".mp4", "video/webm" => ".webm", "video/quicktime" => ".mov", _ => null }; if (ext != null) return ext; } // Try to extract from URL try { var uri = new Uri(url); var path = uri.AbsolutePath; var ext = Path.GetExtension(path); if (!string.IsNullOrEmpty(ext)) return ext; } catch { } // Default extensions return type == TransformationType.Image ? ".png" : ".mp4"; } } }