ifw-daq 3.1.0
IFW Data Acquisition modules
Loading...
Searching...
No Matches
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
18namespace fs = std::filesystem;
19namespace {
20template <class T>
21constexpr bool AlwaysFalse() {
22 return false;
23}
24} // namespace
25
26namespace daq::dpm::merge {
27
28std::optional<KeywordRuleProcessor::DefaultRule>
29Convert(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 */
51public:
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
59private:
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 */
73public:
74 virtual void PostAlert(std::string const& id, std::string const& message) override {
75 std::cout << "[event] alert: " << message << '\n';
76 }
77};
78
80public:
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
87class Sorter : public KeywordSorter {
88public:
89 virtual void SortKeywords(std::vector<fits::LiteralKeyword>& keywords) override {
90 fits::v1::StandardSort(keywords);
91 }
92};
93
94fs::path MakeSourcePath(fs::path source, fs::path const& root) {
95 if (!source.is_absolute()) {
96 return root / source;
97 }
98 return source;
99}
100
101std::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
145int 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 LOG4CPLUS_INFO("daq.dpmmerge", "Dry run: " << std::boolalpha << dry_run);
152 LOG4CPLUS_INFO("daq.dpmmerge", "Workspace root: " << root);
153
154 fits::MemoryFitsFile dry_run_file =
155 dry_run ? fits::MemoryFitsFile(4096u) : fits::MemoryFitsFile();
156
157 auto out_path = opt_out_path.value_or(
158 root / fs::path(fmt::format("{}{}.fits", spec.target.file_prefix, spec.target.file_id)));
159 LOG4CPLUS_INFO("daq.dpmmerge", "Output: " << out_path);
160 TargetSource target_source = [&] {
161 if (spec.target.source) {
162 /* in-place merge */
163 LOG4CPLUS_INFO("daq.dpmmerge",
164 fmt::format("*in-place* target specified act as merge target: '{}'",
165 spec.target.source->source_name));
166
167 auto kw_rules = MakeKeywordRuleProcessor(spec.target.source->keyword_rules);
168 auto path = MakeSourcePath(
169 resolver.Resolve({spec.target.source->source_name, spec.target.source->location}),
170 root);
171 if (dry_run) {
172 // Open existing in read-only mode
173 return TargetSource(spec.target.source->source_name,
174 path,
175 Convert(spec.target.source->initial_keywords),
176 std::move(kw_rules),
177 fits::Open(path.c_str(), fits::OpenMode::ReadOnly));
178 } else {
179 return TargetSource(spec.target.source->source_name,
180 path,
181 Convert(spec.target.source->initial_keywords),
182 std::move(kw_rules));
183 }
184 } else {
185 /* new empty, but valid, file to act as the merge target */
186 LOG4CPLUS_INFO("daq.dpmmerge",
187 "no *in-place* target specified -> creating minimal FITS file to "
188 "act as merge target");
189 // Create new target file, initialized without primary data array
190 auto path =
191 MakeSourcePath(fmt::format("{}-in-progress.fits", spec.target.file_id), root);
192 std::optional<KeywordRuleProcessor::DefaultRule> initial_keywords = std::nullopt;
193 if (dry_run) {
194 // Use in-memory file
195 return TargetSource("" /* no name */,
196 path,
197 initial_keywords,
198 std::make_unique<StandardKeywordRuleProcessor>(),
199 std::move(dry_run_file).GetOwnedFile());
200 } else {
201 auto ptr = fits::CreateEmpty(path.c_str());
203 return TargetSource("" /* no name */,
204 path,
205 initial_keywords,
206 std::make_unique<StandardKeywordRuleProcessor>());
207 }
208 }
209 }();
210
211 std::vector<SourceTypes> sources;
212 for (json::DpSpec::SourceTypes const& dp_source : spec.sources) {
213 std::visit(
214 [&](auto const& source) {
215 using T = std::decay_t<decltype(source)>;
216
217 auto kw_rules = MakeKeywordRuleProcessor(source.keyword_rules);
218 if constexpr (std::is_same_v<T, json::FitsKeywordsSource>) {
219 sources.emplace_back(std::in_place_type<FitsKeywordsSource>,
220 source.source_name,
221 source.keywords,
222 Convert(source.initial_keywords),
223 std::move(kw_rules));
224 } else if constexpr (std::is_same_v<T, json::FitsFileSource>) {
225 auto path = MakeSourcePath(
226 resolver.Resolve({source.source_name, source.location}), root);
227 sources.emplace_back(std::in_place_type<FitsFileSource>,
228 source.source_name,
229 std::move(path),
230 source.location,
231 Convert(source.initial_keywords),
232 std::move(kw_rules),
233 source.alert_unmergeable);
234 } else {
235 static_assert(AlwaysFalse<T>(), "non exhaustive visitor");
236 }
237 },
238 dp_source);
239 }
240
241 // Execute merge
242 auto logger = log4cplus::Logger::getInstance("daq.dpmmerge");
243 JsonReporter json_reporter(logger);
244 SimpleReporter simple_reporter;
245 Formatter formatter;
246 Sorter sorter;
247 Operations ops{use_json ? static_cast<StatusReporter&>(json_reporter)
248 : static_cast<StatusReporter&>(simple_reporter),
249 formatter,
250 sorter,
251 logger};
252
254 params.arcfile = spec.target.file_id + ".fits";
255 params.origfile = out_path.filename().native();
256
257 try {
258 Merge(ops, params, target_source, sources, dry_run);
259 } catch (...) {
260 std::throw_with_nested(std::runtime_error("Merge failed"));
261 }
262
263 // Finally move file into output and change permission
264 target_source.Close();
265
266 if (!dry_run) {
267 fs::permissions(target_source.GetFilePath(),
268 fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read,
269 fs::perm_options::replace);
270 fs::rename(target_source.GetFilePath(), out_path);
271 }
272 return 0;
273}
274
275} // 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:129
In-memory FITS file.
Definition: cfitsio.hpp:44
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
std::unique_ptr< KeywordRuleProcessor > MakeKeywordRuleProcessor(json::KeywordRules const &rules)
Definition: entrypoint.cpp:101
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:351
fs::path MakeSourcePath(fs::path source, fs::path const &root)
Definition: entrypoint.cpp:94
std::optional< KeywordRuleProcessor::DefaultRule > Convert(std::optional<::daq::json::InitialKeywords > const &rhs)
Definition: entrypoint.cpp:29
void StandardSort(std::vector< LiteralKeyword > &keywords)
Sorts keywords according to ESO DICD standards.
Definition: keyword.cpp:909
UniqueFitsFile Open(char const *filename, OpenMode mode)
Open file.
Definition: cfitsio.cpp:187
LiteralKeyword Format(KeywordVariant const &keyword)
Definition: keyword.cpp:782
void InitPrimaryHduNoImage(fitsfile *ptr)
Initializes an empty FITS file with a primary HDU.
Definition: cfitsio.cpp:144
std::vector< KeywordVariant > KeywordVector
Vector of keywords.
Definition: keyword.hpp:423
std::variant< ValueKeyword, EsoKeyword, LiteralKeyword > KeywordVariant
The different variants of keywords that are supported.
Definition: keyword.hpp:409
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:177
@ None
Keeps nothing (useful for disabling keyword copying)
@ User
Keep only user-keywords.
@ All
Keep all keywords (useful for in-place merge)
Target target
Describes target which will become the data produtc.
Definition: dpSpec.hpp:49
std::optional< FitsFileSource > source
Definition: dpSpec.hpp:37
std::vector< KeywordRuleTypes > KeywordRules
std::vector< SourceTypes > sources
List of sources to create data product from.
Definition: dpSpec.hpp:54
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