using System.Text.Json; using dbsync.Config; using Npgsql; using Serilog; namespace TvSyncApp.Services; public class TvSyncService { private readonly string _connectionString; public TvSyncService(string connectionString) { _connectionString = connectionString; } // ----------------------------------------- // 1. Download Updates // ----------------------------------------- public async Task> DownloadTvMazeUpdatesAsync( string updatesUrl, string tempDir) { using var http = new HttpClient(); var json = await http.GetStringAsync(updatesUrl); // Deserialize first to validate and normalize var parsed = JsonSerializer.Deserialize>(json) ?? new Dictionary(); // Convert to int-key dictionary (what your app uses) var result = parsed .Where(k => int.TryParse(k.Key, out _)) .ToDictionary(k => int.Parse(k.Key), v => v.Value); // Pretty-print JSON before saving var options = new JsonSerializerOptions { WriteIndented = true }; var formattedJson = JsonSerializer.Serialize(result, options); var filePath = Path.Combine(tempDir, "tvmaze_updates.json"); await File.WriteAllTextAsync(filePath, formattedJson); return result; } // ----------------------------------------- // 2. Update DB // ----------------------------------------- public void UpdateTvUpdatesTable(Dictionary updates) { using var conn = new NpgsqlConnection(_connectionString); conn.Open(); using var transaction = conn.BeginTransaction(); using var batch = new NpgsqlBatch(conn, transaction); foreach (var kvp in updates) { var cmd = new NpgsqlBatchCommand(@" INSERT INTO updates.tvupdates (seriesid, timestamp) VALUES (@id, @ts) ON CONFLICT (seriesid) DO UPDATE SET timestamp = EXCLUDED.timestamp;"); cmd.Parameters.AddWithValue("id", kvp.Key); cmd.Parameters.AddWithValue("ts", kvp.Value); batch.BatchCommands.Add(cmd); } batch.ExecuteNonQuery(); transaction.Commit(); Log.Information("Batch updated tvupdates table with {Count} entries.", updates.Count); } // ----------------------------------------- // 3. Determine Files Needed // ----------------------------------------- public List<(string Directory, string FileName)> DetermineFilesNeeded( Dictionary downloadedUpdates, Dictionary previousUpdates, Dictionary directories) { var filesNeeded = new List<(string, string)>(); var directoryList = directories.Values.ToList(); foreach (var kvp in downloadedUpdates) { var seriesId = kvp.Key; var newTs = kvp.Value; if (previousUpdates.TryGetValue(seriesId, out var oldTs)) { if (newTs > oldTs) foreach (var dir in directoryList) filesNeeded.Add((dir, $"{seriesId}.json")); } else { foreach (var dir in directoryList) filesNeeded.Add((dir, $"{seriesId}.json")); } } return filesNeeded; } // ----------------------------------------- // 4. Download Files // ----------------------------------------- public async Task DownloadSeriesFilesAsync( List<(string Directory, string FileName)> filesNeeded, Dictionary skipIds, Settings settings) { using var http = new HttpClient(); foreach (var item in filesNeeded) { var seriesId = item.FileName.Replace(".json", ""); var url = settings.Json!.SeriesApi! .Replace("", seriesId); if (skipIds.ContainsKey(seriesId)) { Log.Information("Skipping series {SeriesId}", seriesId); continue; } try { var json = await http.GetStringAsync(url); var savePath = Path.Combine(item.Directory, item.FileName); await File.WriteAllTextAsync(savePath, json); } catch (Exception ex) { Log.Error(ex, "Failed downloading {SeriesId}", seriesId); } } } }