diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..468ca5c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# Use the official .NET SDK image to build the app +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env +WORKDIR /app + +# Copy the csproj and restore any dependencies +COPY *.csproj ./ +RUN dotnet restore + +# Copy the entire project and build the app +COPY . ./ +RUN dotnet publish -c Release -o out + +# Use the official .NET runtime image to run the app +FROM mcr.microsoft.com/dotnet/aspnet:6.0 +WORKDIR /app +COPY --from=build-env /app/out . + +# Expose port +EXPOSE 80 + +# Entry point to run the application +ENTRYPOINT ["dotnet", "DicomMigratorApp.dll"] diff --git a/Program.cs b/Program.cs index c625fa2..ece7128 100644 --- a/Program.cs +++ b/Program.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using System.Threading; namespace DicomMigratorApp { @@ -20,7 +21,6 @@ namespace DicomMigratorApp private static string targetIP; private static string senderAET; private static string targetAET; - private static string folderLocation; private static int targetPort; static async Task Main(string[] args) @@ -38,12 +38,11 @@ namespace DicomMigratorApp _logger.LogInformation("Application started"); - connectionString = "Server=127.0.0.1;Port=3306;Database=dms_db;User Id=root;Password=Rootmatrix23@;"; - targetIP = "127.0.0.1"; - senderAET = "MYAE"; - targetAET = "ORTHANC"; - folderLocation = "D:\\dicom_imags\\"; - var targetPortStr = "4242"; + 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)) { @@ -51,37 +50,55 @@ namespace DicomMigratorApp return; } - try + while (true) { - var migrationStudy = await GetMigrationStudy(connectionString); - if (migrationStudy == null) - { - _logger.LogInformation("No studies found for migration."); - return; - } - - _logger.LogInformation($"Retrieved studyDetails for Migration"); - try { - await MigrateStudy(migrationStudy.StudyInstanceUID, folderLocation + migrationStudy.StudyInstanceUID); - await UpdateSourceCFind(connectionString, migrationStudy.StudyInstanceUID, "MIGRATION_COMPLETE"); + 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 (DicomAssociationRejectedException ex) + catch (MySqlException ex) { - _logger.LogError(ex, $"Association rejected for studyInstanceUID {migrationStudy.StudyInstanceUID}"); - await UpdateSourceCFind(connectionString, migrationStudy.StudyInstanceUID, "MIGRATION_ERRORED"); + _logger.LogError(ex, "An error occurred while connecting to the database"); + break; } catch (Exception ex) { - _logger.LogError(ex, $"An error occurred while retrieving or saving studyInstanceUID {migrationStudy.StudyInstanceUID}"); - await UpdateSourceCFind(connectionString, migrationStudy.StudyInstanceUID, "MIGRATION_ERRORED"); + _logger.LogError(ex, "An error occurred in the main processing loop"); + break; } } - catch (Exception ex) - { - _logger.LogError(ex, "An error occurred in the main processing loop"); - } } private static async Task GetMigrationStudy(string connectionString) @@ -95,16 +112,17 @@ namespace DicomMigratorApp _logger.LogInformation("Opening a connection to the database"); await connection.OpenAsync(); _logger.LogInformation("Database connection established"); - var command = new MySqlCommand("SELECT study_instance_uid,patient_id,status FROM source_cfind WHERE status='QR_COMPLETE' LIMIT 1", connection); + 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 { - StudyInstanceUID = reader.IsDBNull(0) ? null : reader.GetString(0), - PatientID = reader.IsDBNull(1) ? null : reader.GetString(1), - Status = reader.IsDBNull(2) ? null : reader.GetString(2) + 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) }; } @@ -112,8 +130,8 @@ namespace DicomMigratorApp } if (qrStudy.StudyInstanceUID != null) { - _logger.LogInformation("Picked study to be migrated from the database"); - await UpdateSourceCFind(connectionString, qrStudy.StudyInstanceUID, "MIGRATION_INPROGRESS"); + _logger.LogInformation($"Picked study to be migrated from the database: {qrStudy.StudyInstanceUID}"); + await UpdateSourceCFind(connectionString, qrStudy.Id, "MIGRATION_INPROGRESS"); } else { @@ -123,54 +141,125 @@ namespace DicomMigratorApp 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 UpdateSourceCFind(string connectionString, string study_instance_uid, string updateStatus) + 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 source_cfind SET status = @Status WHERE study_instance_uid = @StudyInstanceUID", connection); + var command = new MySqlCommand("UPDATE priority_migration_image SET status = @Status WHERE id = @ImageId", connection); - command.Parameters.AddWithValue("@StudyInstanceUID", study_instance_uid); + command.Parameters.AddWithValue("@ImageId", imageId); command.Parameters.AddWithValue("@Status", updateStatus); await command.ExecuteNonQueryAsync(); - _logger.LogInformation($"Updated source C-FIND record for study instance uid {study_instance_uid}"); + _logger.LogInformation($"Updated image path record for image ID {imageId} with status {updateStatus}"); } } catch (Exception ex) { - _logger.LogError(ex, $"Error updating source study record for study {study_instance_uid}"); + _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 MigrateStudy(string studyInstanceUID, string folderPath) + private static async Task MigrateImage(string imagePath) { - var client = DicomClientFactory.Create(targetIP, targetPort, false, senderAET, targetAET); - - var files = System.IO.Directory.GetFiles(folderPath, "*"); - foreach (var file in files) + try { - var dicomFile = await DicomFile.OpenAsync(file); + 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 + } + } - await client.SendAsync(); + 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); - _logger.LogInformation($"Migrated studyInstanceUID {studyInstanceUID} to target AET {targetAET}"); + 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; } + } } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a65a010 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + dicom-migrator-app: + image: seyfertsoft/dicommigratorapp:1.0.0 + container_name: dicommigrator + environment: + - "ConnectionStrings__DefaultConnection=Server=mysqldb;Port=3306;Database=dms_db;User Id=root;Password=Rootmatrix23@;" + - Dest__IP=127.0.0.1 + - Sender__AET=MYAE + - Dest__AET=ORTHANC + - Dest__Port=4242 + volumes: + - /path/to/images1:/app/images1 + - /path/to/images2:/app/images2 + networks: + - dmsnetwork + +networks: + dmsnetwork: + external: true \ No newline at end of file