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

#include <file_storage.h>

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

Public Member Functions

 file_storage (const file_storage_config &config)
 Construct file storage with configuration.
 
 ~file_storage () override=default
 Destructor.
 
 file_storage (const file_storage &)=delete
 Non-copyable (contains mutex)
 
file_storageoperator= (const file_storage &)=delete
 
 file_storage (file_storage &&)=delete
 Non-movable (contains mutex)
 
file_storageoperator= (file_storage &&)=delete
 
auto store (const core::dicom_dataset &dataset) -> VoidResult override
 Store a DICOM dataset to filesystem.
 
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 file by SOP Instance UID.
 
auto exists (std::string_view sop_instance_uid) const -> bool override
 Check if a DICOM instance exists.
 
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 get_file_path (std::string_view sop_instance_uid) const -> std::filesystem::path
 Get the filesystem path for a SOP Instance UID.
 
auto import_directory (const std::filesystem::path &source) -> VoidResult
 Import DICOM files from a directory.
 
auto root_path () const -> const std::filesystem::path &
 Get the root storage path.
 
auto rebuild_index () -> VoidResult
 Rebuild the internal index from filesystem.
 
- 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_path (std::string_view study_uid, std::string_view series_uid, std::string_view sop_uid) const -> std::filesystem::path
 Build filesystem path for a dataset.
 
auto build_date_path (std::string_view study_date, std::string_view study_uid, std::string_view sop_uid) const -> std::filesystem::path
 Build filesystem path using date-based hierarchy.
 
void update_index (const std::string &sop_uid, const std::filesystem::path &path)
 Update internal index with new mapping.
 
void remove_from_index (const std::string &sop_uid)
 Remove entry from internal index.
 

Static Private Member Functions

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

Private Attributes

file_storage_config config_
 Storage configuration.
 
std::unordered_map< std::string, std::filesystem::path > index_
 Mapping from SOP Instance UID to file path.
 
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

Constructor & Destructor Documentation

◆ file_storage() [1/3]

kcenon::pacs::storage::file_storage::file_storage ( const file_storage_config & config)
explicit

Construct file storage with configuration.

Parameters
configStorage configuration
Exceptions
std::runtime_errorif root_path is invalid and create_directories is false
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/file_storage.h.

Definition at line 56 of file file_storage.cpp.

57 : config_(config) {
58 // Create root directory if configured
60 std::error_code ec;
61 std::filesystem::create_directories(config_.root_path, ec);
62 // Ignore error - will be caught during store operations
63 }
64
65 // Rebuild index from existing files
66 if (std::filesystem::exists(config_.root_path)) {
67 (void)rebuild_index();
68 }
69}
auto rebuild_index() -> VoidResult
Rebuild the internal index from filesystem.
file_storage_config config_
Storage configuration.
bool create_directories
Create directories automatically if they don't exist.
std::filesystem::path root_path
Root directory for storage.

References config_, kcenon::pacs::storage::file_storage_config::create_directories, if(), and kcenon::pacs::storage::file_storage_config::root_path.

Here is the call graph for this function:

◆ ~file_storage()

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

◆ file_storage() [2/3]

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

Non-copyable (contains mutex)

◆ file_storage() [3/3]

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

Non-movable (contains mutex)

Member Function Documentation

◆ build_date_path()

auto kcenon::pacs::storage::file_storage::build_date_path ( std::string_view study_date,
std::string_view study_uid,
std::string_view sop_uid ) const -> std::filesystem::path
nodiscardprivate

Build filesystem path using date-based hierarchy.

Parameters
study_dateStudy date (YYYYMMDD format)
study_uidStudy Instance UID
sop_uidSOP Instance UID
Returns
The constructed path
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/file_storage.h.

Definition at line 472 of file file_storage.cpp.

475 {
476 // Parse date YYYYMMDD
477 std::string year = "unknown";
478 std::string month = "01";
479 std::string day = "01";
480
481 if (study_date.length() >= 8) {
482 year = std::string{study_date.substr(0, 4)};
483 month = std::string{study_date.substr(4, 2)};
484 day = std::string{study_date.substr(6, 2)};
485 }
486
487 return config_.root_path / year / month / day / sanitize_uid(study_uid) /
489}
static auto sanitize_uid(std::string_view uid) -> std::string
Sanitize UID for use in filesystem path.
constexpr dicom_tag study_date
Study Date.
std::string file_extension
File extension for DICOM files.

◆ build_path()

auto kcenon::pacs::storage::file_storage::build_path ( std::string_view study_uid,
std::string_view series_uid,
std::string_view sop_uid ) const -> std::filesystem::path
nodiscardprivate

Build filesystem path for a dataset.

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

Definition at line 463 of file file_storage.cpp.

466 {
467 return config_.root_path / sanitize_uid(study_uid) /
468 sanitize_uid(series_uid) /
470}

◆ exists()

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

Check if a DICOM instance exists.

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

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

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

Definition at line 247 of file file_storage.cpp.

247 {
248 std::shared_lock lock(mutex_);
249 return index_.contains(std::string{sop_instance_uid});
250}
std::unordered_map< std::string, std::filesystem::path > index_
Mapping from SOP Instance UID to file path.
std::shared_mutex mutex_
Mutex for thread-safe access.
constexpr dicom_tag sop_instance_uid
SOP Instance UID.

◆ find()

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

Find DICOM datasets matching query criteria.

Scans the storage directory and matches datasets against query.

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

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

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

Definition at line 252 of file file_storage.cpp.

253 {
254 std::vector<core::dicom_dataset> results;
255
256 std::vector<std::filesystem::path> paths_to_check;
257 {
258 std::shared_lock lock(mutex_);
259 paths_to_check.reserve(index_.size());
260 for (const auto& [uid, path] : index_) {
261 paths_to_check.push_back(path);
262 }
263 }
264
265 for (const auto& path : paths_to_check) {
266 auto open_result = core::dicom_file::open(path);
267 if (open_result.is_err()) {
268 continue; // Skip files that can't be read
269 }
270
271 const auto& dataset = open_result.value().dataset();
272 if (matches_query(dataset, query)) {
273 results.push_back(dataset);
274 }
275 }
276
277 return results;
278}
static auto open(const std::filesystem::path &path) -> kcenon::pacs::Result< dicom_file >
Open and read a DICOM file from disk.
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::open(), and uid.

Here is the call graph for this function:

◆ get_file_path()

auto kcenon::pacs::storage::file_storage::get_file_path ( std::string_view sop_instance_uid) const -> std::filesystem::path
nodiscard

Get the filesystem path for a SOP Instance UID.

Parameters
sop_instance_uidThe SOP Instance UID
Returns
The filesystem path (may not exist)
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/file_storage.h.

Definition at line 375 of file file_storage.cpp.

376 {
377 std::shared_lock lock(mutex_);
378 auto it = index_.find(std::string{sop_instance_uid});
379 if (it != index_.end()) {
380 return it->second;
381 }
382 return {};
383}

◆ get_statistics()

auto kcenon::pacs::storage::file_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/file_storage.h.

Definition at line 280 of file file_storage.cpp.

280 {
281 storage_statistics stats;
282
283 std::set<std::string> studies;
284 std::set<std::string> series;
285 std::set<std::string> patients;
286
287 std::vector<std::filesystem::path> paths;
288 {
289 std::shared_lock lock(mutex_);
290 stats.total_instances = index_.size();
291 paths.reserve(index_.size());
292 for (const auto& [uid, path] : index_) {
293 paths.push_back(path);
294 }
295 }
296
297 for (const auto& path : paths) {
298 std::error_code ec;
299 stats.total_bytes += std::filesystem::file_size(path, ec);
300
301 // Read file to get study/series/patient info
302 auto open_result = core::dicom_file::open(path);
303 if (open_result.is_ok()) {
304 const auto& ds = open_result.value().dataset();
305 auto study_uid = ds.get_string(core::tags::study_instance_uid);
306 auto series_uid = ds.get_string(core::tags::series_instance_uid);
307 auto patient_id = ds.get_string(core::tags::patient_id);
308
309 if (!study_uid.empty()) {
310 studies.insert(study_uid);
311 }
312 if (!series_uid.empty()) {
313 series.insert(series_uid);
314 }
315 if (!patient_id.empty()) {
316 patients.insert(patient_id);
317 }
318 }
319 }
320
321 stats.studies_count = studies.size();
322 stats.series_count = series.size();
323 stats.patients_count = patients.size();
324
325 return stats;
326}
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag series_instance_uid
Series Instance UID.

References index_, mutex_, kcenon::pacs::core::dicom_file::open(), kcenon::pacs::core::tags::patient_id, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::study_instance_uid, and uid.

Here is the call graph for this function:

◆ import_directory()

auto kcenon::pacs::storage::file_storage::import_directory ( const std::filesystem::path & source) -> VoidResult
nodiscard

Import DICOM files from a directory.

Recursively scans the source directory for DICOM files and imports them.

Parameters
sourceSource directory to import from
Returns
VoidResult Success or error information
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/file_storage.h.

Definition at line 385 of file file_storage.cpp.

386 {
387 if (!std::filesystem::exists(source)) {
388 return make_error<std::monostate>(
389 kFileNotFound,
390 "Source directory does not exist: " + source.string(),
391 "file_storage");
392 }
393
394 std::error_code ec;
395 for (const auto& entry :
396 std::filesystem::recursive_directory_iterator(source, ec)) {
397 if (!entry.is_regular_file()) {
398 continue;
399 }
400
401 // Try to open as DICOM file
402 auto open_result = core::dicom_file::open(entry.path());
403 if (open_result.is_err()) {
404 continue; // Not a DICOM file, skip
405 }
406
407 // Store the dataset
408 auto store_result = store(open_result.value().dataset());
409 if (store_result.is_err()) {
410 // Log error but continue with other files
411 // In production, this should be logged
412 }
413 }
414
415 return ok();
416}
auto store(const core::dicom_dataset &dataset) -> VoidResult override
Store a DICOM dataset to filesystem.
const atna_coded_value source
Source Role ID (110153)

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

Here is the call graph for this function:

◆ matches_query()

auto kcenon::pacs::storage::file_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/file_storage.h.

Definition at line 502 of file file_storage.cpp.

503 {
504 // If query is empty, match all
505 if (query.empty()) {
506 return true;
507 }
508
509 // Check each query element
510 for (const auto& [tag, element] : query) {
511 auto query_value = element.as_string().unwrap_or("");
512 if (query_value.empty()) {
513 continue; // Empty value acts as wildcard
514 }
515
516 auto dataset_value = dataset.get_string(tag);
517
518 // Support basic wildcard matching (* and ?)
519 if (query_value.find('*') != std::string::npos ||
520 query_value.find('?') != std::string::npos) {
521 // Convert to regex-like matching
522 std::string pattern = query_value;
523 // Escape regex special characters except * and ?
524 std::string escaped;
525 for (char c : pattern) {
526 if (c == '*') {
527 escaped += ".*";
528 } else if (c == '?') {
529 escaped += ".";
530 } else if (c == '.' || c == '[' || c == ']' || c == '(' ||
531 c == ')' || c == '+' || c == '^' || c == '$' ||
532 c == '|' || c == '\\') {
533 escaped += '\\';
534 escaped += c;
535 } else {
536 escaped += c;
537 }
538 }
539
540 // Simple pattern matching (without full regex for performance)
541 // For now, just check if pattern starts with or ends with value
542 if (query_value.front() == '*' && query_value.back() == '*') {
543 // Contains
544 auto inner =
545 query_value.substr(1, query_value.length() - 2);
546 if (dataset_value.find(inner) == std::string::npos) {
547 return false;
548 }
549 } else if (query_value.front() == '*') {
550 // Ends with
551 auto suffix = query_value.substr(1);
552 if (dataset_value.length() < suffix.length() ||
553 dataset_value.substr(dataset_value.length() -
554 suffix.length()) != suffix) {
555 return false;
556 }
557 } else if (query_value.back() == '*') {
558 // Starts with
559 auto prefix = query_value.substr(0, query_value.length() - 1);
560 if (dataset_value.substr(0, prefix.length()) != prefix) {
561 return false;
562 }
563 }
564 } else {
565 // Exact match
566 if (dataset_value != query_value) {
567 return false;
568 }
569 }
570 }
571
572 return true;
573}
const atna_coded_value query
Query (110112)

◆ operator=() [1/2]

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

◆ operator=() [2/2]

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

◆ rebuild_index()

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

Rebuild the internal index from filesystem.

Scans the storage directory and rebuilds the SOP UID to path mapping.

Returns
VoidResult Success or error information
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/file_storage.h.

Definition at line 422 of file file_storage.cpp.

422 {
423 std::unique_lock lock(mutex_);
424 index_.clear();
425
426 if (!std::filesystem::exists(config_.root_path)) {
427 return ok();
428 }
429
430 std::error_code ec;
431 for (const auto& entry :
432 std::filesystem::recursive_directory_iterator(config_.root_path, ec)) {
433 if (!entry.is_regular_file()) {
434 continue;
435 }
436
437 // Check file extension
438 if (!config_.file_extension.empty() &&
439 entry.path().extension() != config_.file_extension) {
440 continue;
441 }
442
443 // Try to read as DICOM file
444 auto open_result = core::dicom_file::open(entry.path());
445 if (open_result.is_err()) {
446 continue;
447 }
448
449 auto sop_uid =
450 open_result.value().dataset().get_string(core::tags::sop_instance_uid);
451 if (!sop_uid.empty()) {
452 index_[sop_uid] = entry.path();
453 }
454 }
455
456 return ok();
457}

References kcenon::pacs::core::dicom_file::open(), and kcenon::pacs::core::tags::sop_instance_uid.

Here is the call graph for this function:

◆ remove()

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

Remove a DICOM file by SOP Instance UID.

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/file_storage.h.

Definition at line 214 of file file_storage.cpp.

214 {
215 std::filesystem::path file_path;
216
217 {
218 std::unique_lock lock(mutex_);
219 auto it = index_.find(std::string{sop_instance_uid});
220 if (it == index_.end()) {
221 // Not found is not an error for remove
222 return ok();
223 }
224 file_path = it->second;
225 index_.erase(it);
226 }
227
228 // Delete the file
229 std::error_code ec;
230 std::filesystem::remove(file_path, ec);
231 // Ignore errors - file might have been deleted externally
232
233 // Try to clean up empty parent directories
234 auto parent = file_path.parent_path();
235 while (parent != config_.root_path) {
236 if (std::filesystem::is_empty(parent)) {
237 std::filesystem::remove(parent, ec);
238 parent = parent.parent_path();
239 } else {
240 break;
241 }
242 }
243
244 return ok();
245}

◆ remove_from_index()

void kcenon::pacs::storage::file_storage::remove_from_index ( const std::string & sop_uid)
private

Remove entry from internal index.

Parameters
sop_uidSOP Instance UID
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/file_storage.h.

Definition at line 497 of file file_storage.cpp.

497 {
498 std::unique_lock lock(mutex_);
499 index_.erase(sop_uid);
500}

References index_, and mutex_.

◆ retrieve()

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

Retrieve a DICOM dataset by SOP Instance UID.

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/file_storage.h.

Definition at line 186 of file file_storage.cpp.

187 {
188 std::filesystem::path file_path;
189
190 {
191 std::shared_lock lock(mutex_);
192 auto it = index_.find(std::string{sop_instance_uid});
193 if (it == index_.end()) {
194 return make_error<core::dicom_dataset>(
195 kFileNotFound,
196 "Instance not found: " + std::string{sop_instance_uid},
197 "file_storage");
198 }
199 file_path = it->second;
200 }
201
202 // Read DICOM file
203 auto open_result = core::dicom_file::open(file_path);
204 if (open_result.is_err()) {
205 return make_error<core::dicom_dataset>(
206 kFileReadError,
207 "Failed to read DICOM file: " + open_result.error().message,
208 "file_storage");
209 }
210
211 return open_result.value().dataset();
212}

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

Here is the call graph for this function:

◆ root_path()

auto kcenon::pacs::storage::file_storage::root_path ( ) const -> const std::filesystem::path&
nodiscard

Get the root storage path.

Returns
The configured root path
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/file_storage.h.

Definition at line 418 of file file_storage.cpp.

418 {
419 return config_.root_path;
420}

References config_, and kcenon::pacs::storage::file_storage_config::root_path.

◆ sanitize_uid()

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

Sanitize UID for use in filesystem path.

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

Definition at line 575 of file file_storage.cpp.

575 {
576 std::string result;
577 result.reserve(uid.length());
578
579 for (char c : uid) {
580 // UIDs contain digits and dots, which are safe for filesystems
581 // Replace any other characters with underscore
582 if (std::isalnum(static_cast<unsigned char>(c)) || c == '.') {
583 result += c;
584 } else {
585 result += '_';
586 }
587 }
588
589 return result;
590}

References uid.

◆ store()

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

Store a DICOM dataset to filesystem.

Extracts UIDs from the dataset and stores as a Part 10 file. Uses atomic write pattern: writes to temp file, then renames.

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/file_storage.h.

Definition at line 75 of file file_storage.cpp.

75 {
76 // Extract required UIDs
77 auto study_uid = dataset.get_string(core::tags::study_instance_uid);
78 auto series_uid = dataset.get_string(core::tags::series_instance_uid);
79 auto sop_uid = dataset.get_string(core::tags::sop_instance_uid);
80
81 if (study_uid.empty() || series_uid.empty() || sop_uid.empty()) {
82 return make_error<std::monostate>(
83 kMissingRequiredUid,
84 "Missing required UID (Study, Series, or SOP Instance UID)",
85 "file_storage");
86 }
87
88 // Build file path based on naming scheme
89 std::filesystem::path file_path;
90 switch (config_.naming) {
92 file_path = build_path(study_uid, series_uid, sop_uid);
93 break;
95 auto study_date = dataset.get_string(core::tags::study_date);
96 if (study_date.empty()) {
97 // Use current date if study date not available
98 auto now = std::chrono::system_clock::now();
99 auto time = std::chrono::system_clock::to_time_t(now);
100 std::tm tm_buf{};
101#ifdef _WIN32
102 localtime_s(&tm_buf, &time);
103#else
104 localtime_r(&time, &tm_buf);
105#endif
106 char date_str[9];
107 std::strftime(date_str, sizeof(date_str), "%Y%m%d", &tm_buf);
108 study_date = date_str;
109 }
110 file_path = build_date_path(study_date, study_uid, sop_uid);
111 break;
112 }
114 file_path = config_.root_path /
116 break;
117 }
118
119 // Handle duplicate checking
120 {
121 std::shared_lock lock(mutex_);
122 if (index_.contains(sop_uid)) {
123 switch (config_.duplicate) {
125 return make_error<std::monostate>(
126 kDuplicateInstance,
127 "Instance already exists: " + sop_uid,
128 "file_storage");
130 return ok();
132 // Continue to overwrite
133 break;
134 }
135 }
136 }
137
138 // Create directories if needed
140 std::error_code ec;
141 std::filesystem::create_directories(file_path.parent_path(), ec);
142 if (ec) {
143 return make_error<std::monostate>(
144 kDirectoryCreateError,
145 "Failed to create directory: " + ec.message(),
146 "file_storage");
147 }
148 }
149
150 // Create DICOM file and write atomically
151 auto dicom_file = core::dicom_file::create(
153
154 // Write to temporary file first
155 auto temp_path = generate_temp_filename(file_path);
156 auto save_result = dicom_file.save(temp_path);
157
158 if (save_result.is_err()) {
159 std::filesystem::remove(temp_path);
160 return make_error<std::monostate>(
161 kFileWriteError,
162 "Failed to write DICOM file: " + save_result.error().message,
163 "file_storage");
164 }
165
166 // Atomic rename
167 std::error_code ec;
168 std::filesystem::rename(temp_path, file_path, ec);
169 if (ec) {
170 std::filesystem::remove(temp_path);
171 return make_error<std::monostate>(
172 kFileWriteError,
173 "Failed to rename temp file: " + ec.message(),
174 "file_storage");
175 }
176
177 // Update index
178 {
179 std::unique_lock lock(mutex_);
180 index_[sop_uid] = file_path;
181 }
182
183 return ok();
184}
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_date_path(std::string_view study_date, std::string_view study_uid, std::string_view sop_uid) const -> std::filesystem::path
Build filesystem path using date-based hierarchy.
auto build_path(std::string_view study_uid, std::string_view series_uid, std::string_view sop_uid) const -> std::filesystem::path
Build filesystem path for a dataset.
@ flat
{SOPUID}.dcm (flat structure)
@ date_hierarchical
YYYY/MM/DD/{StudyUID}/{SOPUID}.dcm.
@ uid_hierarchical
{StudyUID}/{SeriesUID}/{SOPUID}.dcm
@ ignore
Skip silently if instance exists.
@ reject
Return error if instance already exists.
@ replace
Overwrite existing instance.
duplicate_policy duplicate
How to handle duplicate instances.
naming_scheme naming
File organization scheme.

References kcenon::pacs::core::dicom_file::create(), kcenon::pacs::storage::date_hierarchical, kcenon::pacs::encoding::transfer_syntax::explicit_vr_little_endian, kcenon::pacs::storage::flat, kcenon::pacs::storage::ignore, kcenon::pacs::storage::reject, kcenon::pacs::storage::replace, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::sop_instance_uid, kcenon::pacs::core::tags::study_date, kcenon::pacs::core::tags::study_instance_uid, and kcenon::pacs::storage::uid_hierarchical.

Here is the call graph for this function:

◆ update_index()

void kcenon::pacs::storage::file_storage::update_index ( const std::string & sop_uid,
const std::filesystem::path & path )
private

Update internal index with new mapping.

Parameters
sop_uidSOP Instance UID
pathFile path
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/storage/file_storage.h.

Definition at line 491 of file file_storage.cpp.

492 {
493 std::unique_lock lock(mutex_);
494 index_[sop_uid] = path;
495}

References index_, and mutex_.

◆ verify_integrity()

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

Verify storage integrity.

Checks that all indexed files exist and are valid DICOM files.

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/file_storage.h.

Definition at line 328 of file file_storage.cpp.

328 {
329 std::vector<std::pair<std::string, std::filesystem::path>> entries;
330 {
331 std::shared_lock lock(mutex_);
332 entries.reserve(index_.size());
333 for (const auto& [uid, path] : index_) {
334 entries.emplace_back(uid, path);
335 }
336 }
337
338 std::vector<std::string> invalid_entries;
339
340 for (const auto& [uid, path] : entries) {
341 if (!std::filesystem::exists(path)) {
342 invalid_entries.push_back(uid + " (file missing)");
343 continue;
344 }
345
346 auto open_result = core::dicom_file::open(path);
347 if (open_result.is_err()) {
348 invalid_entries.push_back(uid + " (invalid DICOM)");
349 continue;
350 }
351
352 // Verify UID matches
353 auto file_uid =
354 open_result.value().dataset().get_string(core::tags::sop_instance_uid);
355 if (file_uid != uid) {
356 invalid_entries.push_back(uid + " (UID mismatch)");
357 }
358 }
359
360 if (!invalid_entries.empty()) {
361 std::string message = "Integrity check failed for " +
362 std::to_string(invalid_entries.size()) +
363 " entries";
364 return make_error<std::monostate>(kIntegrityError, message,
365 "file_storage");
366 }
367
368 return ok();
369}

References kcenon::pacs::core::dicom_file::open(), kcenon::pacs::core::tags::sop_instance_uid, and uid.

Here is the call graph for this function:

Member Data Documentation

◆ config_

file_storage_config kcenon::pacs::storage::file_storage::config_
private

Storage configuration.

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

Definition at line 327 of file file_storage.h.

Referenced by file_storage(), and root_path().

◆ index_

std::unordered_map<std::string, std::filesystem::path> kcenon::pacs::storage::file_storage::index_
private

Mapping from SOP Instance UID to file path.

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

Definition at line 330 of file file_storage.h.

Referenced by get_statistics(), remove_from_index(), and update_index().

◆ mutex_

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

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