using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LSFE.MobApp.Services.Coverage { public interface IFileDownloadService { Task DownloadFileAsync(string fileUrl, string fileName, CancellationToken cancellationToken); Task IsFileDownloadedAsync(string fileName); string GetLocalFilePath(string fileName); } public class FileDownloadService : IFileDownloadService { private readonly HttpClient _httpClient; private readonly string _downloadFolder; private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3); // Limit concurrent downloads public FileDownloadService(IHttpClientFactory httpClientFactory) { _httpClient = httpClientFactory.CreateClient("ApiClient"); _httpClient.Timeout = TimeSpan.FromMinutes(20); _downloadFolder = Path.Combine(FileSystem.AppDataDirectory, "ProductMaterials"); Directory.CreateDirectory(_downloadFolder); } public async Task DownloadFileAsync(string fileUrl, string fileName, CancellationToken cancellationToken = default) { await _semaphore.WaitAsync(cancellationToken); try { var localPath = Path.Combine(_downloadFolder, fileName); if (File.Exists(localPath)) { var fileInfo = new FileInfo(localPath); if (fileInfo.Length > 0) return localPath; File.Delete(localPath); } var tempPath = localPath + ".tmp"; using var response = await _httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); await using var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None); await stream.CopyToAsync(fileStream, cancellationToken); await fileStream.FlushAsync(cancellationToken); fileStream.Close(); if (File.Exists(localPath)) File.Delete(localPath); File.Move(tempPath, localPath); return localPath; } catch (HttpRequestException ex) when (ex.InnerException is System.Security.Authentication.AuthenticationException) { Debug.WriteLine($"SSL/TLS error for {fileName}: {ex.Message}"); throw new InvalidOperationException("SSL certificate validation failed. Please check server certificate configuration.", ex); } catch (Exception ex) { Debug.WriteLine($"Download failed for {fileName}: {ex.Message}"); throw; } finally { _semaphore.Release(); } } public Task IsFileDownloadedAsync(string fileName) { try { if (string.IsNullOrWhiteSpace(fileName)) return Task.FromResult(false); var localPath = Path.Combine(_downloadFolder, fileName); if (!File.Exists(localPath)) return Task.FromResult(false); // Verify file is not empty/corrupted var fileInfo = new FileInfo(localPath); return Task.FromResult(fileInfo.Length > 0); } catch (Exception ex) { Debug.WriteLine($"Error checking file existence for {fileName}: {ex.Message}"); return Task.FromResult(false); } } public string GetLocalFilePath(string fileName) { return Path.Combine(_downloadFolder, fileName); } } }