PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::storage::azure_blob_storage Class Reference

#include <azure_blob_storage.h>

Inheritance diagram for kcenon::pacs::storage::azure_blob_storage:
Inheritance graph
Collaboration diagram for kcenon::pacs::storage::azure_blob_storage:
Collaboration graph

Classes

class  azure_client_interface
 Abstract interface for Azure Blob client operations. More...
 

Public Member Functions

 azure_blob_storage (const azure_storage_config &config)
 Construct Azure Blob storage with configuration.
 
 ~azure_blob_storage () override
 Destructor.
 
 azure_blob_storage (const azure_blob_storage &)=delete
 Non-copyable (contains mutex)
 
azure_blob_storageoperator= (const azure_blob_storage &)=delete
 
 azure_blob_storage (azure_blob_storage &&)=delete
 Non-movable (contains mutex)
 
azure_blob_storageoperator= (azure_blob_storage &&)=delete
 
auto store (const core::dicom_dataset &dataset) -> VoidResult override
 Store a DICOM dataset to Azure Blob Storage.
 
auto retrieve (std::string_view sop_instance_uid) -> Result< core::dicom_dataset > override
 Retrieve a DICOM dataset by SOP Instance UID.
 
auto remove (std::string_view sop_instance_uid) -> VoidResult override
 Remove a DICOM blob from Azure Storage.
 
auto exists (std::string_view sop_instance_uid) const -> bool override
 Check if a DICOM instance exists in Azure Storage.
 
auto find (const core::dicom_dataset &query) -> Result< std::vector< core::dicom_dataset > > override
 Find DICOM datasets matching query criteria.
 
auto get_statistics () const -> storage_statistics override
 Get storage statistics.
 
auto verify_integrity () -> VoidResult override
 Verify storage integrity.
 
auto store_with_progress (const core::dicom_dataset &dataset, azure_progress_callback callback) -> VoidResult
 Store with progress tracking.
 
auto retrieve_with_progress (std::string_view sop_instance_uid, azure_progress_callback callback) -> Result< core::dicom_dataset >
 Retrieve with progress tracking.
 
auto get_blob_name (std::string_view sop_instance_uid) const -> std::string
 Get the blob name for a SOP Instance UID.
 
auto container_name () const -> const std::string &
 Get the container name.
 
auto rebuild_index () -> VoidResult
 Rebuild the local index from Azure.
 
auto is_connected () const -> bool
 Check Azure connectivity.
 
auto set_access_tier (std::string_view sop_instance_uid, std::string_view tier) -> VoidResult
 Set blob access tier.
 
- Public Member Functions inherited from kcenon::pacs::storage::storage_interface
virtual ~storage_interface ()=default
 Virtual destructor for proper polymorphic destruction.
 
virtual auto store_batch (const std::vector< core::dicom_dataset > &datasets) -> VoidResult
 Store multiple DICOM datasets in a single operation.
 
virtual auto retrieve_batch (const std::vector< std::string > &sop_instance_uids) -> Result< std::vector< core::dicom_dataset > >
 Retrieve multiple DICOM datasets by their SOP Instance UIDs.
 

Private Member Functions

auto build_blob_name (std::string_view study_uid, std::string_view series_uid, std::string_view sop_uid) const -> std::string
 Build blob name for a dataset.
 
auto upload_block_blob (const std::string &blob_name, const std::vector< std::uint8_t > &data, azure_progress_callback callback) -> VoidResult
 Execute block blob upload for large files.
 

Static Private Member Functions

static auto sanitize_uid (std::string_view uid) -> std::string
 Sanitize UID for use in blob name.
 
static auto matches_query (const core::dicom_dataset &dataset, const core::dicom_dataset &query) -> bool
 Check if dataset matches query criteria.
 

Private Attributes

azure_storage_config config_
 Storage configuration.
 
std::unique_ptr< azure_client_interfaceclient_
 Azure client (mock for testing, Azure SDK for production)
 
std::unordered_map< std::string, azure_blob_infoindex_
 Mapping from SOP Instance UID to Azure blob info.
 
std::shared_mutex mutex_
 Mutex for thread-safe access.
 

Additional Inherited Members

- Protected Member Functions inherited from kcenon::pacs::storage::storage_interface
 storage_interface ()=default
 Protected default constructor for derived classes.
 
 storage_interface (const storage_interface &)=delete
 Non-copyable.
 
storage_interfaceoperator= (const storage_interface &)=delete
 
 storage_interface (storage_interface &&)=default
 Movable.
 
storage_interfaceoperator= (storage_interface &&)=default
 

Detailed Description

Definition at line 170 of file azure_blob_storage.h.

Constructor & Destructor Documentation

◆ azure_blob_storage() [1/3]

kcenon::pacs::storage::azure_blob_storage::azure_blob_storage ( const azure_storage_config & config)
explicit

Construct Azure Blob storage with configuration.

Parameters
configAzure storage configuration
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 552 of file azure_blob_storage.cpp.

553 : config_(config),
554#if defined(PACS_WITH_AZURE_SDK) && !defined(PACS_USE_MOCK_AZURE)
555 client_(std::make_unique<azure_sdk_client>(config))
556#else
557 client_(std::make_unique<mock_azure_client>(config))
558#endif
559{
560}
azure_storage_config config_
Storage configuration.
std::unique_ptr< azure_client_interface > client_
Azure client (mock for testing, Azure SDK for production)

◆ ~azure_blob_storage()

kcenon::pacs::storage::azure_blob_storage::~azure_blob_storage ( )
overridedefault

◆ azure_blob_storage() [2/3]

kcenon::pacs::storage::azure_blob_storage::azure_blob_storage ( const azure_blob_storage & )
delete

Non-copyable (contains mutex)

◆ azure_blob_storage() [3/3]

kcenon::pacs::storage::azure_blob_storage::azure_blob_storage ( azure_blob_storage && )
delete

Non-movable (contains mutex)

Member Function Documentation

◆ build_blob_name()

auto kcenon::pacs::storage::azure_blob_storage::build_blob_name ( std::string_view study_uid,
std::string_view series_uid,
std::string_view sop_uid ) const -> std::string
nodiscardprivate

Build blob name for a dataset.

Parameters
study_uidStudy Instance UID
series_uidSeries Instance UID
sop_uidSOP Instance UID
Returns
The constructed blob name
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 902 of file azure_blob_storage.cpp.

905 {
906 std::ostringstream oss;
907 oss << sanitize_uid(study_uid) << "/" << sanitize_uid(series_uid) << "/"
908 << sanitize_uid(sop_uid) << ".dcm";
909 return oss.str();
910}
static auto sanitize_uid(std::string_view uid) -> std::string
Sanitize UID for use in blob name.

◆ container_name()

auto kcenon::pacs::storage::azure_blob_storage::container_name ( ) const -> const std::string &
nodiscard

Get the container name.

Returns
The configured container name
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 823 of file azure_blob_storage.cpp.

823 {
824 return config_.container_name;
825}
std::string container_name
Azure Blob container name for storing DICOM files.

References config_, and kcenon::pacs::storage::azure_storage_config::container_name.

◆ exists()

auto kcenon::pacs::storage::azure_blob_storage::exists ( std::string_view sop_instance_uid) const -> bool
nodiscardoverridevirtual

Check if a DICOM instance exists in Azure Storage.

Uses Azure GetBlobProperties to check existence without downloading.

Parameters
sop_instance_uidThe unique identifier to check
Returns
true if the blob exists, false otherwise

Implements kcenon::pacs::storage::storage_interface.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 713 of file azure_blob_storage.cpp.

714 {
715 std::shared_lock lock(mutex_);
716 return index_.contains(std::string{sop_instance_uid});
717}
std::unordered_map< std::string, azure_blob_info > index_
Mapping from SOP Instance UID to Azure blob info.
std::shared_mutex mutex_
Mutex for thread-safe access.
constexpr dicom_tag sop_instance_uid
SOP Instance UID.

◆ find()

auto kcenon::pacs::storage::azure_blob_storage::find ( const core::dicom_dataset & query) -> Result<std::vector<core::dicom_dataset>>
nodiscardoverridevirtual

Find DICOM datasets matching query criteria.

This operation requires scanning the local index. For large containers, consider using the index_database instead.

Parameters
queryThe query dataset containing search criteria
Returns
Result containing matching datasets or error information
Note
This operation can be slow for large storage

Implements kcenon::pacs::storage::storage_interface.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 719 of file azure_blob_storage.cpp.

720 {
721 std::vector<core::dicom_dataset> results;
722
723 std::vector<std::string> blobs_to_retrieve;
724 {
725 std::shared_lock lock(mutex_);
726 blobs_to_retrieve.reserve(index_.size());
727 for (const auto &[uid, info] : index_) {
728 blobs_to_retrieve.push_back(info.blob_name);
729 }
730 }
731
732 for (const auto &blob_name : blobs_to_retrieve) {
733 auto download_result = client_->get_blob(blob_name);
734 if (download_result.is_err()) {
735 continue; // Skip blobs that can't be downloaded
736 }
737
738 auto parse_result = core::dicom_file::from_bytes(download_result.value());
739 if (parse_result.is_err()) {
740 continue; // Skip invalid DICOM files
741 }
742
743 const auto &dataset = parse_result.value().dataset();
744 if (matches_query(dataset, query)) {
745 results.push_back(dataset);
746 }
747 }
748
749 return results;
750}
static auto from_bytes(std::span< const uint8_t > data) -> kcenon::pacs::Result< dicom_file >
Parse a DICOM file from raw bytes.
static auto matches_query(const core::dicom_dataset &dataset, const core::dicom_dataset &query) -> bool
Check if dataset matches query criteria.
std::string_view uid

References kcenon::pacs::core::dicom_file::from_bytes(), and uid.

Here is the call graph for this function:

◆ get_blob_name()

auto kcenon::pacs::storage::azure_blob_storage::get_blob_name ( std::string_view sop_instance_uid) const -> std::string
nodiscard

Get the blob name for a SOP Instance UID.

Parameters
sop_instance_uidThe SOP Instance UID
Returns
The blob name (may be empty if not found)
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 813 of file azure_blob_storage.cpp.

814 {
815 std::shared_lock lock(mutex_);
816 auto it = index_.find(std::string{sop_instance_uid});
817 if (it != index_.end()) {
818 return it->second.blob_name;
819 }
820 return {};
821}

◆ get_statistics()

auto kcenon::pacs::storage::azure_blob_storage::get_statistics ( ) const -> storage_statistics
nodiscardoverridevirtual

Get storage statistics.

Returns
Storage statistics structure

Implements kcenon::pacs::storage::storage_interface.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 752 of file azure_blob_storage.cpp.

752 {
753 storage_statistics stats;
754
755 std::set<std::string> studies;
756 std::set<std::string> series;
757
758 {
759 std::shared_lock lock(mutex_);
760 stats.total_instances = index_.size();
761
762 for (const auto &[uid, info] : index_) {
763 stats.total_bytes += info.size_bytes;
764
765 if (!info.study_instance_uid.empty()) {
766 studies.insert(info.study_instance_uid);
767 }
768 if (!info.series_instance_uid.empty()) {
769 series.insert(info.series_instance_uid);
770 }
771 }
772 }
773
774 stats.studies_count = studies.size();
775 stats.series_count = series.size();
776 // Note: patient_count requires downloading datasets to extract PatientID
777
778 return stats;
779}

References index_, mutex_, and uid.

◆ is_connected()

auto kcenon::pacs::storage::azure_blob_storage::is_connected ( ) const -> bool
nodiscard

Check Azure connectivity.

Returns
true if Azure is reachable, false otherwise
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 867 of file azure_blob_storage.cpp.

867 {
868 return client_ && client_->is_connected();
869}

References client_.

◆ matches_query()

auto kcenon::pacs::storage::azure_blob_storage::matches_query ( const core::dicom_dataset & dataset,
const core::dicom_dataset & query ) -> bool
staticnodiscardprivate

Check if dataset matches query criteria.

Parameters
datasetThe dataset to check
queryThe query criteria
Returns
true if dataset matches query
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 976 of file azure_blob_storage.cpp.

978 {
979 // If query is empty, match all
980 if (query.empty()) {
981 return true;
982 }
983
984 // Check each query element
985 for (const auto &[tag, element] : query) {
986 auto query_value = element.as_string().unwrap_or("");
987 if (query_value.empty()) {
988 continue; // Empty value acts as wildcard
989 }
990
991 auto dataset_value = dataset.get_string(tag);
992
993 // Support basic wildcard matching (* and ?)
994 if (query_value.find('*') != std::string::npos ||
995 query_value.find('?') != std::string::npos) {
996 // Simple pattern matching
997 if (query_value.front() == '*' && query_value.back() == '*') {
998 // Contains
999 auto inner = query_value.substr(1, query_value.length() - 2);
1000 if (dataset_value.find(inner) == std::string::npos) {
1001 return false;
1002 }
1003 } else if (query_value.front() == '*') {
1004 // Ends with
1005 auto suffix = query_value.substr(1);
1006 if (dataset_value.length() < suffix.length() ||
1007 dataset_value.substr(dataset_value.length() - suffix.length()) !=
1008 suffix) {
1009 return false;
1010 }
1011 } else if (query_value.back() == '*') {
1012 // Starts with
1013 auto prefix = query_value.substr(0, query_value.length() - 1);
1014 if (dataset_value.substr(0, prefix.length()) != prefix) {
1015 return false;
1016 }
1017 }
1018 } else {
1019 // Exact match
1020 if (dataset_value != query_value) {
1021 return false;
1022 }
1023 }
1024 }
1025
1026 return true;
1027}
const atna_coded_value query
Query (110112)

◆ operator=() [1/2]

azure_blob_storage & kcenon::pacs::storage::azure_blob_storage::operator= ( azure_blob_storage && )
delete

◆ operator=() [2/2]

azure_blob_storage & kcenon::pacs::storage::azure_blob_storage::operator= ( const azure_blob_storage & )
delete

◆ rebuild_index()

auto kcenon::pacs::storage::azure_blob_storage::rebuild_index ( ) -> VoidResult
nodiscard

Rebuild the local index from Azure.

Lists all blobs in the container and rebuilds the SOP UID mapping.

Returns
VoidResult Success or error information
Note
This operation can be slow for containers with many blobs
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 827 of file azure_blob_storage.cpp.

827 {
828 std::unique_lock lock(mutex_);
829 index_.clear();
830
831 // List all blobs from Azure
832 auto blob_names = client_->list_blobs();
833
834 for (const auto &blob_name : blob_names) {
835 // Download and parse each blob to rebuild index
836 auto download_result = client_->get_blob(blob_name);
837 if (download_result.is_err()) {
838 continue;
839 }
840
841 auto parse_result = core::dicom_file::from_bytes(download_result.value());
842 if (parse_result.is_err()) {
843 continue;
844 }
845
846 const auto &dataset = parse_result.value().dataset();
847 auto sop_uid = dataset.get_string(core::tags::sop_instance_uid);
848 auto study_uid = dataset.get_string(core::tags::study_instance_uid);
849 auto series_uid = dataset.get_string(core::tags::series_instance_uid);
850
851 if (!sop_uid.empty()) {
852 azure_blob_info info;
853 info.blob_name = blob_name;
854 info.sop_instance_uid = sop_uid;
855 info.study_instance_uid = study_uid;
856 info.series_instance_uid = series_uid;
857 info.size_bytes = client_->get_blob_size(blob_name);
858 info.etag = client_->get_blob_etag(blob_name);
859 info.content_md5 = client_->get_blob_md5(blob_name);
860 index_[sop_uid] = std::move(info);
861 }
862 }
863
864 return ok();
865}
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag series_instance_uid
Series Instance UID.

References kcenon::pacs::core::dicom_file::from_bytes(), kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::sop_instance_uid, and kcenon::pacs::core::tags::study_instance_uid.

Here is the call graph for this function:

◆ remove()

auto kcenon::pacs::storage::azure_blob_storage::remove ( std::string_view sop_instance_uid) -> VoidResult
nodiscardoverridevirtual

Remove a DICOM blob from Azure Storage.

Parameters
sop_instance_uidThe unique identifier for the instance to remove
Returns
VoidResult Success or error information

Implements kcenon::pacs::storage::storage_interface.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 691 of file azure_blob_storage.cpp.

692 {
693 std::string blob_name;
694
695 {
696 std::unique_lock lock(mutex_);
697 auto it = index_.find(std::string{sop_instance_uid});
698 if (it == index_.end()) {
699 // Not found is not an error for remove
700 return ok();
701 }
702 blob_name = it->second.blob_name;
703 index_.erase(it);
704 }
705
706 // Delete from Azure
707 auto delete_result = client_->delete_blob(blob_name);
708 // Ignore delete errors - blob might have been deleted externally
709
710 return ok();
711}

◆ retrieve()

auto kcenon::pacs::storage::azure_blob_storage::retrieve ( std::string_view sop_instance_uid) -> Result<core::dicom_dataset>
nodiscardoverridevirtual

Retrieve a DICOM dataset by SOP Instance UID.

Downloads the blob from Azure and deserializes to dicom_dataset.

Parameters
sop_instance_uidThe unique identifier for the instance
Returns
Result containing the dataset or error information

Implements kcenon::pacs::storage::storage_interface.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 642 of file azure_blob_storage.cpp.

643 {
644 return retrieve_with_progress(sop_instance_uid, nullptr);
645}
auto retrieve_with_progress(std::string_view sop_instance_uid, azure_progress_callback callback) -> Result< core::dicom_dataset >
Retrieve with progress tracking.

◆ retrieve_with_progress()

auto kcenon::pacs::storage::azure_blob_storage::retrieve_with_progress ( std::string_view sop_instance_uid,
azure_progress_callback callback ) -> Result<core::dicom_dataset>
nodiscard

Retrieve with progress tracking.

Parameters
sop_instance_uidThe SOP Instance UID
callbackProgress callback function
Returns
Result containing the dataset or error information
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 647 of file azure_blob_storage.cpp.

649 {
650 std::string blob_name;
651
652 {
653 std::shared_lock lock(mutex_);
654 auto it = index_.find(std::string{sop_instance_uid});
655 if (it == index_.end()) {
656 return make_error<core::dicom_dataset>(
657 kBlobNotFound,
658 "Instance not found: " + std::string{sop_instance_uid},
659 "azure_blob_storage");
660 }
661 blob_name = it->second.blob_name;
662 }
663
664 // Download from Azure
665 auto download_result = client_->get_blob(blob_name);
666 if (download_result.is_err()) {
667 return make_error<core::dicom_dataset>(kDownloadError,
668 "Failed to download from Azure",
669 "azure_blob_storage");
670 }
671
672 const auto &data = download_result.value();
673
674 // Report progress (download complete)
675 if (callback) {
676 callback(data.size(), data.size());
677 }
678
679 // Deserialize DICOM data
680 auto parse_result = core::dicom_file::from_bytes(data);
681 if (parse_result.is_err()) {
682 return make_error<core::dicom_dataset>(
683 kSerializationError,
684 "Failed to parse DICOM data: " + parse_result.error().message,
685 "azure_blob_storage");
686 }
687
688 return parse_result.value().dataset();
689}

References kcenon::pacs::core::dicom_file::from_bytes().

Here is the call graph for this function:

◆ sanitize_uid()

auto kcenon::pacs::storage::azure_blob_storage::sanitize_uid ( std::string_view uid) -> std::string
staticnodiscardprivate

Sanitize UID for use in blob name.

Parameters
uidThe UID to sanitize
Returns
Sanitized string safe for blob name use
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 912 of file azure_blob_storage.cpp.

912 {
913 std::string result;
914 result.reserve(uid.length());
915
916 for (char c : uid) {
917 // UIDs contain digits and dots, which are safe for blob names
918 // Replace any other characters with underscore
919 if (std::isalnum(static_cast<unsigned char>(c)) || c == '.') {
920 result += c;
921 } else {
922 result += '_';
923 }
924 }
925
926 return result;
927}

References uid.

◆ set_access_tier()

auto kcenon::pacs::storage::azure_blob_storage::set_access_tier ( std::string_view sop_instance_uid,
std::string_view tier ) -> VoidResult
nodiscard

Set blob access tier.

Parameters
sop_instance_uidThe SOP Instance UID
tierThe access tier (Hot, Cool, Archive)
Returns
VoidResult Success or error information
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 871 of file azure_blob_storage.cpp.

872 {
873 std::string blob_name;
874
875 {
876 std::shared_lock lock(mutex_);
877 auto it = index_.find(std::string{sop_instance_uid});
878 if (it == index_.end()) {
879 return make_error<std::monostate>(
880 kBlobNotFound,
881 "Instance not found: " + std::string{sop_instance_uid},
882 "azure_blob_storage");
883 }
884 blob_name = it->second.blob_name;
885 }
886
887 auto result = client_->set_tier(blob_name, std::string{tier});
888 if (result.is_err()) {
889 return make_error<std::monostate>(
890 kTierChangeError,
891 "Failed to change access tier: " + std::string{sop_instance_uid},
892 "azure_blob_storage");
893 }
894
895 return ok();
896}

◆ store()

auto kcenon::pacs::storage::azure_blob_storage::store ( const core::dicom_dataset & dataset) -> VoidResult
nodiscardoverridevirtual

Store a DICOM dataset to Azure Blob Storage.

Serializes the dataset to DICOM Part 10 format and uploads to Azure. Uses block blob upload for files exceeding block_upload_threshold.

Parameters
datasetThe DICOM dataset to store
Returns
VoidResult Success or error information
Note
Requires Study, Series, and SOP Instance UIDs in the dataset

Implements kcenon::pacs::storage::storage_interface.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 568 of file azure_blob_storage.cpp.

569 {
570 return store_with_progress(dataset, nullptr);
571}
auto store_with_progress(const core::dicom_dataset &dataset, azure_progress_callback callback) -> VoidResult
Store with progress tracking.

◆ store_with_progress()

auto kcenon::pacs::storage::azure_blob_storage::store_with_progress ( const core::dicom_dataset & dataset,
azure_progress_callback callback ) -> VoidResult
nodiscard

Store with progress tracking.

Parameters
datasetThe DICOM dataset to store
callbackProgress callback function
Returns
VoidResult Success or error information
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 573 of file azure_blob_storage.cpp.

575 {
576 // Extract required UIDs
577 auto study_uid = dataset.get_string(core::tags::study_instance_uid);
578 auto series_uid = dataset.get_string(core::tags::series_instance_uid);
579 auto sop_uid = dataset.get_string(core::tags::sop_instance_uid);
580
581 if (study_uid.empty() || series_uid.empty() || sop_uid.empty()) {
582 return make_error<std::monostate>(
583 kMissingRequiredUid,
584 "Missing required UID (Study, Series, or SOP Instance UID)",
585 "azure_blob_storage");
586 }
587
588 // Build blob name
589 auto blob_name = build_blob_name(study_uid, series_uid, sop_uid);
590
591 // Create DICOM file and serialize to bytes
592 auto dicom_file = core::dicom_file::create(
594
595 auto data = dicom_file.to_bytes();
596 if (data.empty()) {
597 return make_error<std::monostate>(kSerializationError,
598 "Failed to serialize DICOM dataset",
599 "azure_blob_storage");
600 }
601
602 // Report initial progress
603 if (callback && !callback(0, data.size())) {
604 return make_error<std::monostate>(kUploadError, "Upload cancelled by user",
605 "azure_blob_storage");
606 }
607
608 // Upload to Azure (use block blob for large files)
609 VoidResult upload_result = ok();
610 if (data.size() > config_.block_upload_threshold) {
611 upload_result = upload_block_blob(blob_name, data, callback);
612 } else {
613 upload_result = client_->put_blob(blob_name, data);
614
615 // Report completion progress
616 if (callback) {
617 callback(data.size(), data.size());
618 }
619 }
620
621 if (upload_result.is_err()) {
622 return upload_result;
623 }
624
625 // Update local index
626 {
627 std::unique_lock lock(mutex_);
628 azure_blob_info info;
629 info.blob_name = blob_name;
630 info.sop_instance_uid = sop_uid;
631 info.study_instance_uid = study_uid;
632 info.series_instance_uid = series_uid;
633 info.size_bytes = data.size();
634 info.etag = client_->get_blob_etag(blob_name);
635 info.content_md5 = client_->get_blob_md5(blob_name);
636 index_[sop_uid] = std::move(info);
637 }
638
639 return ok();
640}
static auto create(dicom_dataset dataset, const encoding::transfer_syntax &ts) -> dicom_file
Create a new DICOM file from a dataset.
static const transfer_syntax explicit_vr_little_endian
Explicit VR Little Endian (1.2.840.10008.1.2.1)
auto build_blob_name(std::string_view study_uid, std::string_view series_uid, std::string_view sop_uid) const -> std::string
Build blob name for a dataset.
auto upload_block_blob(const std::string &blob_name, const std::vector< std::uint8_t > &data, azure_progress_callback callback) -> VoidResult
Execute block blob upload for large files.
std::size_t block_upload_threshold
Threshold for block blob upload in bytes (default: 100MB) Files larger than this will use block blob ...

References kcenon::pacs::core::dicom_file::create(), kcenon::pacs::encoding::transfer_syntax::explicit_vr_little_endian, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::sop_instance_uid, and kcenon::pacs::core::tags::study_instance_uid.

Here is the call graph for this function:

◆ upload_block_blob()

auto kcenon::pacs::storage::azure_blob_storage::upload_block_blob ( const std::string & blob_name,
const std::vector< std::uint8_t > & data,
azure_progress_callback callback ) -> VoidResult
nodiscardprivate

Execute block blob upload for large files.

Parameters
blob_nameBlob name
dataData to upload
callbackOptional progress callback
Returns
VoidResult Success or error information
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 929 of file azure_blob_storage.cpp.

931 {
932 std::size_t total_bytes = data.size();
933 std::size_t bytes_uploaded = 0;
934 std::vector<std::string> block_ids;
935
936 // Stage blocks
937 std::size_t block_index = 0;
938 while (bytes_uploaded < total_bytes) {
939 std::size_t block_size =
940 (std::min)(config_.block_size, total_bytes - bytes_uploaded);
941
942 auto block_id = generate_block_id(block_index);
943 block_ids.push_back(block_id);
944
945 std::vector<std::uint8_t> block_data(data.begin() + bytes_uploaded,
946 data.begin() + bytes_uploaded +
947 block_size);
948
949 auto stage_result = client_->stage_block(blob_name, block_id, block_data);
950 if (stage_result.is_err()) {
951 return make_error<std::monostate>(kUploadError,
952 "Failed to stage block: " + block_id,
953 "azure_blob_storage");
954 }
955
956 bytes_uploaded += block_size;
957 block_index++;
958
959 if (callback && !callback(bytes_uploaded, total_bytes)) {
960 return make_error<std::monostate>(kUploadError,
961 "Upload cancelled by user",
962 "azure_blob_storage");
963 }
964 }
965
966 // Commit blocks
967 auto commit_result = client_->commit_blocks(blob_name, block_ids);
968 if (commit_result.is_err()) {
969 return make_error<std::monostate>(kUploadError, "Failed to commit blocks",
970 "azure_blob_storage");
971 }
972
973 return ok();
974}
std::size_t block_size
Block size for block blob upload in bytes (default: 4MB) Azure allows up to 4000MB per block (API ver...

◆ verify_integrity()

auto kcenon::pacs::storage::azure_blob_storage::verify_integrity ( ) -> VoidResult
nodiscardoverridevirtual

Verify storage integrity.

Checks that all indexed blobs exist in Azure and have valid ETags.

Returns
VoidResult Success if integrity is verified, error otherwise

Implements kcenon::pacs::storage::storage_interface.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 781 of file azure_blob_storage.cpp.

781 {
782 std::vector<std::pair<std::string, std::string>> entries;
783 {
784 std::shared_lock lock(mutex_);
785 entries.reserve(index_.size());
786 for (const auto &[uid, info] : index_) {
787 entries.emplace_back(uid, info.blob_name);
788 }
789 }
790
791 std::vector<std::string> invalid_entries;
792
793 for (const auto &[uid, blob_name] : entries) {
794 if (!client_->head_blob(blob_name)) {
795 invalid_entries.push_back(uid + " (blob missing)");
796 }
797 }
798
799 if (!invalid_entries.empty()) {
800 std::string message = "Integrity check failed for " +
801 std::to_string(invalid_entries.size()) + " entries";
802 return make_error<std::monostate>(kIntegrityError, message,
803 "azure_blob_storage");
804 }
805
806 return ok();
807}

References uid.

Member Data Documentation

◆ client_

std::unique_ptr<azure_client_interface> kcenon::pacs::storage::azure_blob_storage::client_
private

Azure client (mock for testing, Azure SDK for production)

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 410 of file azure_blob_storage.h.

Referenced by is_connected().

◆ config_

azure_storage_config kcenon::pacs::storage::azure_blob_storage::config_
private

◆ index_

std::unordered_map<std::string, azure_blob_info> kcenon::pacs::storage::azure_blob_storage::index_
private

Mapping from SOP Instance UID to Azure blob info.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 413 of file azure_blob_storage.h.

Referenced by get_statistics().

◆ mutex_

std::shared_mutex kcenon::pacs::storage::azure_blob_storage::mutex_
mutableprivate

Mutex for thread-safe access.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/azure_blob_storage.h.

Definition at line 416 of file azure_blob_storage.h.

Referenced by get_statistics().


The documentation for this class was generated from the following files: