ifw-daq  3.0.0-pre2
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 std::optional<KeywordRuleProcessor::DefaultRule>
29 Convert(std::optional<::daq::json::InitialKeywords> const& rhs) {
30  if (!rhs.has_value()) {
31  return std::nullopt;
32  }
33  switch (*rhs) {
40  default:
41  throw std::invalid_argument(
42  fmt::format("Unknown value of daq::json::InitialKeywords: {}",
43  static_cast<std::underlying_type_t<daq::json::InitialKeywords>>(*rhs)));
44  }
45 }
46 
47 /**
48  * Reports in JSON format
49  */
50 class JsonReporter : public StatusReporter {
51 public:
52  JsonReporter(log4cplus::Logger logger) : m_logger(std::move(logger)) {
53  }
54  virtual void PostAlert(std::string const& id, std::string const& message) override {
55  LOG4CPLUS_WARN(m_logger, "Alert: " << message);
56  Report("alert", nlohmann::json({{"id", id}, {"message", message}}));
57  }
58 
59 private:
60  void Report(char const* type, nlohmann::json const& content) {
61  auto ts =
62  std::chrono::nanoseconds(std::chrono::system_clock::now().time_since_epoch()).count();
63  nlohmann::json j{{"type", type}, {"timestamp", ts}, {"content", content}};
64  std::cout << j;
65  }
66  log4cplus::Logger m_logger;
67 };
68 
69 /**
70  * Reports in unspecified human-readable format
71  */
73 public:
74  virtual void PostAlert(std::string const& id, std::string const& message) override {
75  std::cout << "[event] alert: " << message << '\n';
76  }
77 };
78 
79 class Formatter : public KeywordFormatter {
80 public:
81  virtual fits::LiteralKeyword FormatKeyword(fits::KeywordVariant const& keyword) override {
82  // @todo: Use dictionary to validate and format
83  return fits::Format(keyword);
84  }
85 };
86 
87 class Sorter : public KeywordSorter {
88 public:
89  virtual void SortKeywords(std::vector<fits::LiteralKeyword>& keywords) override {
90  fits::v1::StandardSort(keywords);
91  }
92 };
93 
94 fs::path MakeSourcePath(fs::path source, fs::path const& root) {
95  if (!source.is_absolute()) {
96  return root / source;
97  }
98  return source;
99 }
100 
101 std::unique_ptr<KeywordRuleProcessor> MakeKeywordRuleProcessor(json::KeywordRules const& rules) {
102  // If rules is an empty list it means "do not copy anything"
103  // whereas a completly undefined list is treated as "copy with provided rules"
104  auto processor = std::make_unique<StandardKeywordRuleProcessor>();
105  for (json::KeywordRuleTypes const& rule : rules) {
106  std::visit(
107  [&](auto const& rule) {
108  using T = std::decay_t<decltype(rule)>;
109  if constexpr (std::is_same_v<T, json::KeywordFilter>) {
110  LOG4CPLUS_DEBUG("daq.dpmmerge", fmt::format("Adding KeywordRule 'filter'"));
111  processor->AddRule([filter = KeywordEx(std::begin(rule.selection_patterns),
112  std::end(rule.selection_patterns))](
114  fits::KeywordVector result;
115  result.reserve(kws.size());
116  Filter(std::begin(kws), std::end(kws), std::back_inserter(result), filter);
117  return result;
118  });
119  } else if constexpr (std::is_same_v<T, json::KeywordTransform>) {
120  LOG4CPLUS_DEBUG("daq.dpmmerge", fmt::format("Adding KeywordRule 'transform'"));
121  processor->AddRule([filter = KeywordEx(std::begin(rule.selection_patterns),
122  std::end(rule.selection_patterns)),
123  regex = std::regex(rule.regex),
124  format = rule.format](
126  fits::KeywordVector result;
127  result.reserve(kws.size());
128  Transform(std::begin(kws),
129  std::end(kws),
130  std::back_inserter(result),
131  filter,
132  regex,
133  format.c_str());
134  return result;
135  });
136  } else {
137  static_assert(AlwaysFalse<T>(), "non exhaustive visitor");
138  }
139  },
140  rule);
141  }
142  return processor;
143 }
144 
145 int Entrypoint(fs::path const& root,
146  std::optional<fs::path> const& opt_out_path,
147  json::DpSpec const& spec,
148  SourceResolver const& resolver,
149  bool dry_run,
150  bool use_json) {
151  fits::MemoryFitsFile dry_run_file =
152  dry_run ? fits::MemoryFitsFile(4096u) : fits::MemoryFitsFile();
153 
154  auto out_path = opt_out_path.value_or(
155  root / fs::path(fmt::format("{}{}.fits", spec.target.file_prefix, spec.target.file_id)));
156  TargetSource target_source = [&] {
157  if (spec.target.source) {
158  /* in-place merge */
159  LOG4CPLUS_INFO("daq.dpmmerge",
160  fmt::format("*in-place* target specified act as merge target: '{}'",
161  spec.target.source->source_name));
162 
163  auto kw_rules = MakeKeywordRuleProcessor(spec.target.source->keyword_rules);
164  auto path = MakeSourcePath(
165  resolver.Resolve({spec.target.source->source_name, spec.target.source->location}),
166  root);
167  if (dry_run) {
168  // Open existing in read-only mode
169  return TargetSource(spec.target.source->source_name,
170  path,
171  Convert(spec.target.source->initial_keywords),
172  std::move(kw_rules),
173  fits::Open(path.c_str(), fits::OpenMode::ReadOnly));
174  } else {
175  return TargetSource(spec.target.source->source_name,
176  path,
177  Convert(spec.target.source->initial_keywords),
178  std::move(kw_rules));
179  }
180  } else {
181  /* new empty, but valid, file to act as the merge target */
182  LOG4CPLUS_INFO("daq.dpmmerge",
183  "no *in-place* target specified -> creating minimal FITS file to "
184  "act as merge target");
185  // Create new target file, initialized without primary data array
186  auto path =
187  MakeSourcePath(fmt::format("{}-in-progress.fits", spec.target.file_id), root);
188  std::optional<KeywordRuleProcessor::DefaultRule> initial_keywords = std::nullopt;
189  if (dry_run) {
190  // Use in-memory file
191  return TargetSource("" /* no name */,
192  path,
193  initial_keywords,
194  std::make_unique<StandardKeywordRuleProcessor>(),
195  std::move(dry_run_file).GetOwnedFile());
196  } else {
197  auto ptr = fits::CreateEmpty(path.c_str());
198  fits::InitPrimaryHduNoImage(ptr.get());
199  return TargetSource("" /* no name */,
200  path,
201  initial_keywords,
202  std::make_unique<StandardKeywordRuleProcessor>());
203  }
204  }
205  }();
206 
207  std::vector<SourceTypes> sources;
208  for (json::DpSpec::SourceTypes const& dp_source : spec.sources) {
209  std::visit(
210  [&](auto const& source) {
211  using T = std::decay_t<decltype(source)>;
212 
213  auto kw_rules = MakeKeywordRuleProcessor(source.keyword_rules);
214  if constexpr (std::is_same_v<T, json::FitsKeywordsSource>) {
215  sources.emplace_back(std::in_place_type<FitsKeywordsSource>,
216  source.source_name,
217  source.keywords,
218  Convert(source.initial_keywords),
219  std::move(kw_rules));
220  } else if constexpr (std::is_same_v<T, json::FitsFileSource>) {
221  auto path = MakeSourcePath(
222  resolver.Resolve({source.source_name, source.location}), root);
223  sources.emplace_back(std::in_place_type<FitsFileSource>,
224  source.source_name,
225  std::move(path),
226  source.location,
227  Convert(source.initial_keywords),
228  std::move(kw_rules));
229  } else {
230  static_assert(AlwaysFalse<T>(), "non exhaustive visitor");
231  }
232  },
233  dp_source);
234  }
235 
236  // Execute merge
237  auto logger = log4cplus::Logger::getInstance("daq.dpmmerge");
238  JsonReporter json_reporter(logger);
239  SimpleReporter simple_reporter;
240  Formatter formatter;
241  Sorter sorter;
242  Operations ops{use_json ? static_cast<StatusReporter&>(json_reporter)
243  : static_cast<StatusReporter&>(simple_reporter),
244  formatter,
245  sorter,
246  logger};
247 
249  params.arcfile = spec.target.file_id + ".fits";
250  params.origfile = out_path.filename().native();
251 
252  try {
253  Merge(ops, params, target_source, sources, dry_run);
254  } catch (...) {
255  std::throw_with_nested(std::runtime_error("Merge failed"));
256  }
257 
258  // Finally move file into output and change permission
259  target_source.Close();
260 
261  if (!dry_run) {
262  fs::permissions(target_source.GetFilePath(),
263  fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read,
264  fs::perm_options::replace);
265  fs::rename(target_source.GetFilePath(), out_path);
266  }
267  return 0;
268 }
269 
270 } // namespace daq::dpm::merge
Contains functions and data structures related to cfitsio.
Create keyword expression that memoize the provided string pattern.
Definition: keywordEx.hpp:59
@ None
None (to disable keyword copying)
@ User
Default is to keep only user-keywords.
@ All
Default rule is to keep all keywords (useful for in-place merge)
Provides location of fits source file.
auto Resolve(SourceFile const &source) const -> std::filesystem::path
Resolves local file that was previously added with Add().
virtual fits::LiteralKeyword FormatKeyword(fits::KeywordVariant const &keyword) override
Format keyword.
Definition: entrypoint.cpp:81
Reports in JSON format.
Definition: entrypoint.cpp:50
virtual void PostAlert(std::string const &id, std::string const &message) override
Post event.
Definition: entrypoint.cpp:54
JsonReporter(log4cplus::Logger logger)
Definition: entrypoint.cpp:52
Reports in unspecified human-readable format.
Definition: entrypoint.cpp:72
virtual void PostAlert(std::string const &id, std::string const &message) override
Post event.
Definition: entrypoint.cpp:74
virtual void SortKeywords(std::vector< fits::LiteralKeyword > &keywords) override
Sort keywords.
Definition: entrypoint.cpp:89
Interface to reporter (implementations exist for JSON or human readable)
Definition: merge.hpp:26
Represents the literal 80-character FITS keyword record.
Definition: keyword.hpp:125
In-memory FITS file.
Definition: cfitsio.hpp:38
std::optional< KeywordRuleProcessor::DefaultRule > Convert(std::optional<::daq::json::InitialKeywords > const &rhs)
Definition: entrypoint.cpp:29
std::string arcfile
Definition: merge.hpp:101
int Entrypoint(fs::path const &root, std::optional< fs::path > const &opt_out_path, json::DpSpec const &spec, SourceResolver const &resolver, bool dry_run, bool use_json)
Definition: entrypoint.cpp:145
std::string origfile
Definition: merge.hpp:102
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
fs::path MakeSourcePath(fs::path source, fs::path const &root)
Definition: entrypoint.cpp:94
std::unique_ptr< KeywordRuleProcessor > MakeKeywordRuleProcessor(json::KeywordRules const &rules)
Definition: entrypoint.cpp:101
void StandardSort(std::vector< LiteralKeyword > &keywords)
Sorts keywords according to ESO DICD standards.
Definition: keyword.cpp:720
UniqueFitsFile Open(char const *filename, OpenMode mode)
Open file.
Definition: cfitsio.cpp:168
LiteralKeyword Format(KeywordVariant const &keyword)
Definition: keyword.cpp:626
void InitPrimaryHduNoImage(fitsfile *ptr)
Initializes an empty FITS file with a primary HDU.
Definition: cfitsio.cpp:125
std::vector< KeywordVariant > KeywordVector
Vector of keywords.
Definition: keyword.hpp:414
std::variant< ValueKeyword, EsoKeyword, LiteralKeyword > KeywordVariant
The different variants of keywords that are supported.
Definition: keyword.hpp:400
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
@ None
Keeps nothing (useful for disabling keyword copying)
@ User
Keep only user-keywords.
@ All
Keep all keywords (useful for in-place merge)
std::optional< FitsFileSource > source
Definition: dpSpec.hpp:37
std::vector< KeywordRuleTypes > KeywordRules
std::vector< SourceTypes > sources
Definition: dpSpec.hpp:44
std::variant< FitsKeywordsSource, FitsFileSource > SourceTypes
Definition: dpSpec.hpp:40
std::string file_prefix
Optioal user chosen file prefix to make it easier to identify the produced file.
Definition: dpSpec.hpp:36
std::variant< KeywordFilter, KeywordTransform > KeywordRuleTypes
Close representation of the JSON structure but with stronger types.
Definition: dpSpec.hpp:30