using System; using MySql.Data.MySqlClient; using FellowOakDicom; using FellowOakDicom.Network; using FellowOakDicom.Network.Client; using Microsoft.Extensions.Configuration; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using System.Threading; namespace DicomMigratorApp { class Program { private static IConfiguration Configuration; private static ILogger _logger; private static string connectionString; private static string targetIP; private static string senderAET; private static string targetAET; private static int targetPort; static async Task Main(string[] args) { var serviceProvider = new ServiceCollection() .AddLogging(config => { config.AddConsole(); config.SetMinimumLevel(LogLevel.Information); }) .BuildServiceProvider(); _logger = serviceProvider.GetService() .CreateLogger(); _logger.LogInformation("Application started"); connectionString = Environment.GetEnvironmentVariable("ConnectionStrings__DefaultConnection") ?? ""; targetIP = Environment.GetEnvironmentVariable("Dest__IP") ?? ""; senderAET = Environment.GetEnvironmentVariable("Sender__AET") ?? ""; targetAET = Environment.GetEnvironmentVariable("Dest__AET") ?? ""; var targetPortStr = Environment.GetEnvironmentVariable("Dest__Port") ?? ""; if (!int.TryParse(targetPortStr, out targetPort)) { _logger.LogError("TargetServer__Port environment variable is not a valid integer."); return; } while (true) { try { var migrationStudy = await GetMigrationStudy(connectionString); if (migrationStudy == null) { await Task.Delay(5000); continue; } _logger.LogInformation($"Retrieved study details for migration: {migrationStudy.StudyInstanceUID}"); try { var imagePaths = await GetImagePaths(connectionString, migrationStudy.StudyInstanceUID); foreach (var image in imagePaths) { await MigrateImage(image.Path); await UpdateImagePathStatus(connectionString, image.Id, "MIGRATION_COMPLETE"); } await UpdateSourceCFind(connectionString, migrationStudy.Id, "MIGRATION_COMPLETE"); } catch (DicomAssociationRejectedException ex) { _logger.LogError(ex, $"Association rejected for studyInstanceUID {migrationStudy.StudyInstanceUID}"); await UpdateSourceCFind(connectionString, migrationStudy.Id, "MIGRATION_ERRORED"); break; } catch (Exception ex) { _logger.LogError(ex, $"An error occurred while retrieving or saving studyInstanceUID {migrationStudy.StudyInstanceUID}"); await UpdateSourceCFind(connectionString, migrationStudy.Id, "MIGRATION_ERRORED"); break; } } catch (MySqlException ex) { _logger.LogError(ex, "An error occurred while connecting to the database"); break; } catch (Exception ex) { _logger.LogError(ex, "An error occurred in the main processing loop"); break; } } } private static async Task GetMigrationStudy(string connectionString) { var qrStudy = new SourceStudy(); try { using (var connection = new MySqlConnection(connectionString)) { _logger.LogInformation("Opening a connection to the database"); await connection.OpenAsync(); _logger.LogInformation("Database connection established"); var command = new MySqlCommand("SELECT id, study_instance_uid, patient_id, status FROM priority_migration_study WHERE status='QR_COMPLETE' ORDER BY schedule_date LIMIT 1", connection); var reader = await command.ExecuteReaderAsync(); while (await reader.ReadAsync()) { qrStudy = new SourceStudy { Id = reader.IsDBNull(0) ? 0 : reader.GetInt32(0), StudyInstanceUID = reader.IsDBNull(1) ? null : reader.GetString(1), PatientID = reader.IsDBNull(2) ? null : reader.GetString(2), Status = reader.IsDBNull(3) ? null : reader.GetString(3) }; } reader.Close(); } if (qrStudy.StudyInstanceUID != null) { _logger.LogInformation($"Picked study to be migrated from the database: {qrStudy.StudyInstanceUID}"); await UpdateSourceCFind(connectionString, qrStudy.Id, "MIGRATION_INPROGRESS"); } else { qrStudy = null; } } catch (Exception ex) { _logger.LogError(ex, "Error retrieving study to be migrated from the database"); throw; // Re-throw the exception to ensure it is caught by the outer try-catch block } return qrStudy; } private static async Task> GetImagePaths(string connectionString, string studyInstanceUID) { var imagePaths = new List(); try { using (var connection = new MySqlConnection(connectionString)) { await connection.OpenAsync(); var command = new MySqlCommand("SELECT id, image_path FROM priority_migration_image WHERE study_instance_uid = @StudyInstanceUID AND (status != 'MIGRATION_COMPLETE' or status is null)", connection); command.Parameters.AddWithValue("@StudyInstanceUID", studyInstanceUID); var reader = await command.ExecuteReaderAsync(); while (await reader.ReadAsync()) { imagePaths.Add(new ImageRecord { Id = reader.IsDBNull(0) ? 0 : reader.GetInt32(0), Path = reader.IsDBNull(1) ? null : reader.GetString(1) }); } reader.Close(); } _logger.LogInformation($"Retrieved {imagePaths.Count} image paths for studyInstanceUID {studyInstanceUID}"); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving image paths from the database"); throw; // Re-throw the exception to ensure it is caught by the outer try-catch block } return imagePaths; } private static async Task UpdateImagePathStatus(string connectionString, int imageId, string updateStatus) { try { using (var connection = new MySqlConnection(connectionString)) { await connection.OpenAsync(); var command = new MySqlCommand("UPDATE priority_migration_image SET status = @Status WHERE id = @ImageId", connection); command.Parameters.AddWithValue("@ImageId", imageId); command.Parameters.AddWithValue("@Status", updateStatus); await command.ExecuteNonQueryAsync(); _logger.LogInformation($"Updated image path record for image ID {imageId} with status {updateStatus}"); } } catch (Exception ex) { _logger.LogError(ex, $"Error updating image path record for image ID {imageId}"); throw; // Re-throw the exception to ensure it is caught by the outer try-catch block } } private static async Task MigrateImage(string imagePath) { try { var client = DicomClientFactory.Create(targetIP, targetPort, false, senderAET, targetAET); var dicomFile = await DicomFile.OpenAsync(imagePath); await client.AddRequestAsync(new DicomCStoreRequest(dicomFile)); await client.SendAsync(); _logger.LogInformation($"Migrated image {imagePath} to target AET {targetAET}"); } catch (Exception ex) { _logger.LogError(ex, $"Error migrating image {imagePath}"); throw; // Re-throw the exception to ensure it is caught by the outer try-catch block } } private static async Task UpdateSourceCFind(string connectionString, int studyId, string updateStatus) { try { using (var connection = new MySqlConnection(connectionString)) { await connection.OpenAsync(); var command = new MySqlCommand("UPDATE priority_migration_study SET status = @Status WHERE id = @StudyId", connection); command.Parameters.AddWithValue("@StudyId", studyId); command.Parameters.AddWithValue("@Status", updateStatus); await command.ExecuteNonQueryAsync(); _logger.LogInformation($"Updated source C-FIND record for study ID {studyId} with status {updateStatus}"); } } catch (Exception ex) { _logger.LogError(ex, $"Error updating source study record for study ID {studyId}"); throw; // Re-throw the exception to ensure it is caught by the outer try-catch block } } } class SourceStudy { public int Id { get; set; } public string PatientID { get; set; } public string StudyInstanceUID { get; set; } public string Status { get; set; } } class ImageRecord { public int Id { get; set; } public string Path { get; set; } } }