ifw-daq  2.1.0-pre1
IFW Data Acquisition modules
entrypoint.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_dpm_merge
4  * @copyright ESO - European Southern Observatory
5  */
6 #include "entrypoint.hpp"
7 
8 #include <iostream>
9 
10 #include <fmt/format.h>
11 #include <log4cplus/loggingmacros.h>
12 
13 #include <daq/dpm/keywordEx.hpp>
14 #include <daq/fits/cfitsio.hpp>
15 
16 #include "merge.hpp"
17 
18 namespace fs = std::filesystem;
19 namespace {
20 template <class T>
21 constexpr bool AlwaysFalse() {
22  return false;
23 }
24 } // namespace
25 
26 namespace daq::dpm::merge {
27 
28 /**
29  * Reports in JSON format
30  */
31 class JsonReporter : public StatusReporter {
32 public:
33  JsonReporter(log4cplus::Logger logger) : m_logger(std::move(logger)) {
34  }
35  virtual void PostAlert(std::string const& id, std::string const& message) override {
36  LOG4CPLUS_WARN(m_logger, "Alert: " << message);
37  Report("alert", nlohmann::json({{"id", id}, {"message", message}}));
38  }
39 
40 private:
41  void Report(char const* type, nlohmann::json const& content) {
42  auto ts =
43  std::chrono::nanoseconds(std::chrono::system_clock::now().time_since_epoch()).count();
44  nlohmann::json j{{"type", type}, {"timestamp", ts}, {"content", content}};
45  std::cout << j;
46  }
47  log4cplus::Logger m_logger;
48 };
49 
50 /**
51  * Reports in unspecified human-readable format
52  */
54 public:
55  virtual void PostAlert(std::string const& id, std::string const& message) override {
56  std::cout << "[event] alert: " << message << '\n';
57  }
58 };
59 
60 class Formatter : public KeywordFormatter {
61 public:
62  virtual fits::LiteralKeyword FormatKeyword(fits::KeywordVariant const& keyword) override {
63  // @todo: Use dictionary to validate and format
64  return fits::Format(keyword);
65  }
66 };
67 
68 class Sorter : public KeywordSorter {
69 public:
70  virtual void SortKeywords(std::vector<fits::LiteralKeyword>& keywords) override {
71  fits::v1::StandardSort(keywords);
72  }
73 };
74 
75 fs::path MakeSourcePath(fs::path source, fs::path const& root) {
76  if (!source.is_absolute()) {
77  return root / source;
78  }
79  return source;
80 }
81 
82 std::unique_ptr<KeywordRuleProcessor> MakeKeywordRuleProcessor(DpSpec::KeywordRules const& rules) {
83  auto processor = std::make_unique<StandardKeywordRuleProcessor>();
84  for (DpSpec::KeywordRule const& rule : rules) {
85  std::visit(
86  [&](auto const& rule) {
87  using T = std::decay_t<decltype(rule)>;
88  if constexpr (std::is_same_v<T, DpSpec::Filter>) {
89  LOG4CPLUS_DEBUG("dpm.merge", fmt::format("Adding KeywordRule 'filter'"));
90  processor->AddRule([filter = KeywordEx(std::begin(rule.selection_patterns),
91  std::end(rule.selection_patterns))](
93  fits::KeywordVector result;
94  result.reserve(kws.size());
95  Filter(std::begin(kws), std::end(kws), std::back_inserter(result), filter);
96  return result;
97  });
98  } else if constexpr (std::is_same_v<T, DpSpec::Transform>) {
99  LOG4CPLUS_DEBUG("dpm.merge", fmt::format("Adding KeywordRule 'transform'"));
100  processor->AddRule([filter = KeywordEx(std::begin(rule.selection_patterns),
101  std::end(rule.selection_patterns)),
102  regex = std::regex(rule.regex),
103  format = rule.format](
105  fits::KeywordVector result;
106  result.reserve(kws.size());
107  Transform(std::begin(kws),
108  std::end(kws),
109  std::back_inserter(result),
110  filter,
111  regex,
112  format.c_str());
113  return result;
114  });
115  } else {
116  static_assert(AlwaysFalse<T>(), "non exhaustive visitor");
117  }
118  },
119  rule);
120  }
121  return processor;
122 }
123 
124 int Entrypoint(fs::path const& root,
125  std::optional<fs::path> const& opt_out_path,
126  DpSpec const& spec,
127  SourceResolver const& resolver,
128  bool dry_run,
129  bool use_json) {
130  fits::MemoryFitsFile dry_run_file =
131  dry_run ? fits::MemoryFitsFile(4096u) : fits::MemoryFitsFile();
132 
133  auto out_path = opt_out_path.value_or(
134  root / fs::path(fmt::format("{}{}.fits", spec.target.file_prefix, spec.target.file_id)));
135  TargetSource target_source = [&] {
136  if (spec.target.source) {
137  /* in-place merge */
138  LOG4CPLUS_INFO("dpm.merge",
139  fmt::format("*in-place* target specified act as merge target: '{}'",
140  spec.target.source->source_name));
141 
142  auto kw_rules = MakeKeywordRuleProcessor(spec.target.source->keyword_rules);
143  auto path = MakeSourcePath(
144  resolver.Resolve({spec.target.source->source_name, spec.target.source->origin}),
145  root);
146  if (dry_run) {
147  // Open existing in read-only mode
148  return TargetSource(spec.target.source->source_name,
149  path,
150  std::move(kw_rules),
151  fits::Open(path.c_str(), fits::OpenMode::ReadOnly));
152  } else {
153  return TargetSource(spec.target.source->source_name, path, std::move(kw_rules));
154  }
155  } else {
156  /* new empty, but valid, file to act as the merge target */
157  LOG4CPLUS_INFO("dpm.merge",
158  "no *in-place* target specified -> creating minimal FITS file to "
159  "act as merge target");
160  // Create new target file, initialized without primary data array
161  auto path =
162  MakeSourcePath(fmt::format("{}-in-progress.fits", spec.target.file_id), root);
163  if (dry_run) {
164  // Use in-memory file
165  return TargetSource("" /* no name */,
166  path,
167  std::make_unique<StandardKeywordRuleProcessor>(),
168  std::move(dry_run_file).GetOwnedFile());
169  } else {
170  auto ptr = fits::CreateEmpty(path.c_str());
171  fits::InitPrimaryHduNoImage(ptr.get());
172  return TargetSource(
173  "" /* no name */, path, std::make_unique<StandardKeywordRuleProcessor>());
174  }
175  }
176  }();
177 
178  std::vector<SourceTypes> sources;
179  for (DpSpec::SourceTypes const& dp_source : spec.sources) {
180  std::visit(
181  [&](auto const& source) {
182  using T = std::decay_t<decltype(source)>;
183 
184  auto kw_rules = MakeKeywordRuleProcessor(source.keyword_rules);
185  if constexpr (std::is_same_v<T, DpSpec::SourceFitsKeywords>) {
186  sources.emplace_back(std::in_place_type<FitsKeywordsSource>,
187  source.source_name,
188  source.keywords,
189  std::move(kw_rules));
190  } else if constexpr (std::is_same_v<T, DpSpec::SourceFitsFile>) {
191  auto path =
192  MakeSourcePath(resolver.Resolve({source.source_name, source.origin}), root);
193  sources.emplace_back(std::in_place_type<FitsFileSource>,
194  source.source_name,
195  std::move(path),
196  source.origin,
197  std::move(kw_rules));
198  } else {
199  static_assert(AlwaysFalse<T>(), "non exhaustive visitor");
200  }
201  },
202  dp_source);
203  }
204 
205  // Execute merge
206  auto logger = log4cplus::Logger::getInstance("dpm.merge");
207  JsonReporter json_reporter(logger);
208  SimpleReporter simple_reporter;
209  Formatter formatter;
210  Sorter sorter;
211  Operations ops{use_json ? static_cast<StatusReporter&>(json_reporter)
212  : static_cast<StatusReporter&>(simple_reporter),
213  formatter,
214  sorter,
215  logger};
216 
218  params.arcfile = spec.target.file_id + ".fits";
219  params.origfile = out_path.filename().native();
220 
221  try {
222  Merge(ops, params, target_source, sources, dry_run);
223  } catch (...) {
224  std::throw_with_nested(std::runtime_error("Merge failed"));
225  }
226 
227  // Finally move file into output and change permission
228  target_source.Close();
229 
230  if (!dry_run) {
231  fs::permissions(target_source.GetFilePath(),
232  fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read,
233  fs::perm_options::replace);
234  fs::rename(target_source.GetFilePath(), out_path);
235  }
236  return 0;
237 }
238 
239 } // namespace daq::dpm::merge
daq::dpm::merge::Operations
Definition: merge.hpp:88
keywordEx.hpp
daq::fits::MemoryFitsFile
In-memory FITS file.
Definition: cfitsio.hpp:37
daq::fits::KeywordVariant
std::variant< ValueKeyword, EsoKeyword, LiteralKeyword > KeywordVariant
The different variants of keywords that are supported.
Definition: keyword.hpp:400
daq::dpm::merge::Params::origfile
std::string origfile
Definition: merge.hpp:102
daq::dpm::merge::SimpleReporter::PostAlert
virtual void PostAlert(std::string const &id, std::string const &message) override
Post event.
Definition: entrypoint.cpp:55
daq::fits::v1::StandardSort
void StandardSort(std::vector< LiteralKeyword > &keywords)
Sorts keywords according to ESO DICD standards.
Definition: keyword.cpp:716
daq::dpm::merge::SimpleReporter
Reports in unspecified human-readable format.
Definition: entrypoint.cpp:53
daq::DpSpec
Close representation of the JSON structure but with stronger types.
Definition: dpSpec.hpp:28
daq::dpm::merge::JsonReporter::JsonReporter
JsonReporter(log4cplus::Logger logger)
Definition: entrypoint.cpp:33
daq::DpSpec::Target::file_id
std::string file_id
Definition: dpSpec.hpp:51
daq::fits::CreateEmpty
UniqueFitsFile CreateEmpty(char const *filename)
Creates empty FITS file using fits_create_file and returns a pointer with a deleter that will close t...
Definition: cfitsio.cpp:158
entrypoint.hpp
daq::dpm::merge::Formatter::FormatKeyword
virtual fits::LiteralKeyword FormatKeyword(fits::KeywordVariant const &keyword) override
Format keyword.
Definition: entrypoint.cpp:62
daq::dpm::merge::Sorter::SortKeywords
virtual void SortKeywords(std::vector< fits::LiteralKeyword > &keywords) override
Sort keywords.
Definition: entrypoint.cpp:70
daq::DpSpec::sources
std::vector< SourceTypes > sources
Definition: dpSpec.hpp:63
daq::dpm::merge::StatusReporter
Interface to reporter (implementations exist for JSON or human readable)
Definition: merge.hpp:26
daq::fits::Format
LiteralKeyword Format(KeywordVariant const &keyword)
Definition: keyword.cpp:622
daq::fits::Open
UniqueFitsFile Open(char const *filename, OpenMode mode)
Open file.
Definition: cfitsio.cpp:168
daq::DpSpec::Target::source
std::optional< SourceFitsFile > source
Definition: dpSpec.hpp:56
daq::dpm::merge::Merge
void Merge(Operations ops, Params const &params, TargetSource &target, std::vector< SourceTypes > const &sources, bool dry_run)
Merge sources into the target target.
Definition: merge.cpp:286
daq::dpm::KeywordEx
Create keyword expression that memoize the provided string pattern.
Definition: keywordEx.hpp:59
daq::dpm::SourceResolver::Resolve
auto Resolve(SourceFile const &source) const -> std::filesystem::path
Resolves local file that was previously added with Add().
Definition: sourceResolver.cpp:30
daq::DpSpec::KeywordRule
std::variant< Filter, Transform > KeywordRule
Definition: dpSpec.hpp:37
daq::dpm::merge::MakeSourcePath
fs::path MakeSourcePath(fs::path source, fs::path const &root)
Definition: entrypoint.cpp:75
daq::dpm::merge::Params
Definition: merge.hpp:100
daq::DpSpec::KeywordRules
std::vector< KeywordRule > KeywordRules
Definition: dpSpec.hpp:38
daq::dpm::merge::KeywordSorter
Definition: merge.hpp:44
daq::dpm::merge::Sorter
Definition: entrypoint.cpp:68
daq::dpm::SourceResolver
Provides location of fits source file.
Definition: sourceResolver.hpp:27
daq::dpm::merge::TargetSource
Target FITS file.
Definition: sources.hpp:111
daq::DpSpec::SourceTypes
std::variant< SourceFitsKeywords, SourceFitsFile > SourceTypes
Definition: dpSpec.hpp:59
daq::dpm::merge
Definition: entrypoint.cpp:26
cfitsio.hpp
Contains functions and data structures related to cfitsio.
daq::DpSpec::target
Target target
Definition: dpSpec.hpp:62
daq::fits::KeywordVector
std::vector< KeywordVariant > KeywordVector
Vector of keywords.
Definition: keyword.hpp:414
daq::dpm::merge::JsonReporter::PostAlert
virtual void PostAlert(std::string const &id, std::string const &message) override
Post event.
Definition: entrypoint.cpp:35
daq::dpm::merge::Formatter
Definition: entrypoint.cpp:60
daq::dpm::merge::Entrypoint
int Entrypoint(fs::path const &root, std::optional< fs::path > const &opt_out_path, DpSpec const &spec, SourceResolver const &resolver, bool dry_run, bool use_json)
Definition: entrypoint.cpp:124
daq::fits::InitPrimaryHduNoImage
void InitPrimaryHduNoImage(fitsfile *ptr)
Initializes an empty FITS file with a primary HDU.
Definition: cfitsio.cpp:125
daq::dpm::merge::MakeKeywordRuleProcessor
std::unique_ptr< KeywordRuleProcessor > MakeKeywordRuleProcessor(DpSpec::KeywordRules const &rules)
Definition: entrypoint.cpp:82
daq::dpm::merge::JsonReporter
Reports in JSON format.
Definition: entrypoint.cpp:31
daq::DpSpec::Target::file_prefix
std::string file_prefix
Optioal user chosen file prefix to make it easier to identify the produced file.
Definition: dpSpec.hpp:55
merge.hpp
daq::dpm::merge::KeywordFormatter
Definition: merge.hpp:34
daq::dpm::merge::Params::arcfile
std::string arcfile
Definition: merge.hpp:101
daq::fits::LiteralKeyword
Represents the literal 80-character FITS keyword record.
Definition: keyword.hpp:125
daq::json
nlohmann::json json
Definition: json.cpp:20