ifw-daq  3.0.0-pre2
IFW Data Acquisition modules
workspace.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_libdaq
4  * @copyright (c) Copyright ESO 2022
5  * All Rights Reserved
6  * ESO (eso.org) is an Intergovernmental Organisation, and therefore special legal conditions apply.
7  *
8  * @brief daq::Workspace interface and implementation declaration
9  */
10 #include <daq/workspace.hpp>
11 
12 #include <fstream>
13 #include <stdexcept>
14 #include <system_error>
15 
16 #include <fmt/format.h>
17 #include <nlohmann/json.hpp>
18 
19 #include <daq/json.hpp>
20 
21 namespace fs = std::filesystem;
22 
23 namespace daq {
24 namespace {
25 
26 constexpr const std::string_view PATH_IN_PROGRESS = "in-progress";
27 constexpr const std::string_view PATH_ARCHIVE = "archive";
28 constexpr const std::string_view FILE_DAQ_LIST = "list.json";
29 constexpr const std::string_view SUFFIX_DAQ_CONTEXT = "-context.json";
30 constexpr const std::string_view SUFFIX_DAQ_STATUS = "-status.json";
31 constexpr const std::string_view FILE_TEMP_EXT = ".swap";
32 
33 template <class T>
34 void SafeWrite(T const& content, fs::path const& destination) {
35  try {
36  auto temp = destination;
37  auto temp_file = destination.filename().native();
38  temp_file += FILE_TEMP_EXT;
39  temp.replace_filename(temp_file);
40  // Write to temporary file
41  {
42  std::fstream temp_file(temp, std::ios::out | std::ios::trunc);
43  temp_file << std::setw(4) << content;
44  if (!temp_file) {
45  throw std::runtime_error(
46  fmt::format("File contains error after write: '{}'", temp.native()));
47  }
48  temp_file.close();
49  }
50  // Move (atomically) into location
51  fs::rename(temp, destination);
52  } catch (...) {
53  std::throw_with_nested(
54  std::runtime_error(fmt::format("Failed to write file '{}'", destination.native())));
55  }
56 }
57 
58 /**
59  * Initialize workspace
60  *
61  * @note Is safe to run for existing and valid workspace.
62  */
63 void InitRootWorkspace(fs::path const& root) {
64  // If there are non-managed files refuse to initialize new worksace as we are misconfigured
65  auto status = fs::status(root);
66  if (fs::exists(status)) {
67  if (!fs::is_directory(status)) {
68  throw std::invalid_argument(
69  fmt::format("Workspace root exist but is not a directory: {}", root.native()));
70  }
71 
72  for (auto const& entry : fs::directory_iterator(root)) {
73  if (entry.path().extension() == FILE_TEMP_EXT) {
74  // Old temporary files -> delete
75  fs::remove(entry);
76  continue;
77  }
78  if (entry.path().filename() == FILE_DAQ_LIST) {
79  continue;
80  }
81  if (entry.path().filename() == PATH_IN_PROGRESS) {
82  continue;
83  }
84  if (entry.path().filename() == PATH_ARCHIVE) {
85  continue;
86  }
87  throw std::invalid_argument(
88  fmt::format("Workspace {} has unexpected contents - aborting to prevent filesystem "
89  "modifications due to misconfiguration",
90  root.native()));
91  }
92  }
93 
94  fs::create_directories(root);
95  fs::create_directory(root / PATH_IN_PROGRESS);
96  fs::create_directory(root / PATH_ARCHIVE);
97 }
98 
99 } // namespace
100 
101 WorkspaceImpl::WorkspaceImpl(fs::path root) : m_root(std::move(root)) {
102  auto status = fs::status(m_root);
103  if (status.type() != fs::file_type::directory && status.type() != fs::file_type::not_found) {
104  throw std::invalid_argument(
105  fmt::format("Workspace root directory '{}' is not a directory", m_root.string())
106  .c_str());
107  }
108 
109  InitRootWorkspace(m_root);
110  // Initialize empty DAQ list if necessary
111  if (!fs::exists(m_root / FILE_DAQ_LIST)) {
112  WorkspaceImpl::StoreList({});
113  }
114 }
115 
116 
117 void WorkspaceImpl::RemoveDaq(std::string const& id) {
118  fs::remove(MakePath(FileType::Context, id));
119  fs::remove(MakePath(FileType::Status, id));
120 }
121 
122 void WorkspaceImpl::ArchiveDaq(std::string const& id) {
123  auto new_context = m_root / PATH_ARCHIVE / (id + std::string(SUFFIX_DAQ_CONTEXT));
124  auto new_status = m_root / PATH_ARCHIVE / (id + std::string(SUFFIX_DAQ_STATUS));
125 
126  if (fs::exists(new_context)) {
127  new_context += ".";
128  for (auto idx = 1u; idx < 0xffff; ++idx) {
129  auto tmp = new_context;
130  tmp += std::to_string(idx);
131  if (!fs::exists(tmp)) {
132  new_context.swap(tmp);
133  break;
134  }
135  }
136  }
137  if (fs::exists(new_status)) {
138  new_context += ".";
139  for (auto idx = 1u; idx < 0xffff; ++idx) {
140  auto tmp = new_status;
141  tmp += std::to_string(idx);
142  if (!fs::exists(tmp)) {
143  new_context.swap(tmp);
144  break;
145  }
146  }
147  }
148  fs::rename(MakePath(FileType::Context, id), new_context);
149  fs::rename(MakePath(FileType::Status, id), new_status);
150 }
151 
152 auto WorkspaceImpl::LoadList() const -> std::vector<std::string> {
153  auto file = m_root / FILE_DAQ_LIST;
154  try {
155  std::fstream fs(file, std::ios::in);
156  auto json = nlohmann::json::parse(fs);
157  return json.get<std::vector<std::string>>();
158  } catch (...) {
159  std::throw_with_nested(
160  std::runtime_error(fmt::format("Failed to load DAQ list '{}'", file.native())));
161  }
162 }
163 
164 void WorkspaceImpl::StoreList(std::vector<std::string> const& list) const {
165  try {
166  nlohmann::json json;
167  json = list;
168  SafeWrite(json, m_root / FILE_DAQ_LIST);
169  } catch (...) {
170  std::throw_with_nested(std::runtime_error("Failed to store DAQ list"));
171  }
172 }
173 
174 auto WorkspaceImpl::LoadStatus(std::string const& id) const -> Status {
175  try {
176  std::fstream fs(MakePath(FileType::Status, id));
177  auto json = nlohmann::json::parse(fs);
178  return json.get<Status>();
179  } catch (...) {
180  std::throw_with_nested(std::runtime_error("Failed to load DAQ status"));
181  }
182 }
183 
184 void WorkspaceImpl::StoreStatus(Status const& status) const {
185  try {
186  nlohmann::json json;
187  json = status;
188  SafeWrite(json, MakePath(FileType::Status, status.id));
189  } catch (...) {
190  std::throw_with_nested(std::runtime_error("Failed to store DAQ status"));
191  }
192 }
193 
194 auto WorkspaceImpl::LoadContext(std::string const& id) const -> DaqContext {
195  auto file = MakePath(FileType::Context, id);
196  try {
197  std::fstream fs(file, std::ios::in);
198  auto json = nlohmann::json::parse(fs);
199  DaqContext context;
200  json.get_to(context);
201  return context;
202  } catch (...) {
203  std::throw_with_nested(std::runtime_error(
204  fmt::format("Failed to load DAQ Context '{}'", file.native())));
205  }
206 }
207 
208 void WorkspaceImpl::StoreContext(DaqContext const& context) const {
209  try {
210  nlohmann::json json;
211  to_json(json, context);
212  SafeWrite(json, MakePath(FileType::Context, context.id));
213  } catch (...) {
214  std::throw_with_nested(std::runtime_error("Failed to store DAQ status"));
215  }
216 }
217 
218 std::filesystem::path WorkspaceImpl::MakePath(FileType type, std::string const& id) const {
219  switch (type) {
220  case FileType::Context:
221  return m_root / PATH_IN_PROGRESS / (id + std::string(SUFFIX_DAQ_CONTEXT));
222  case FileType::Status:
223  return m_root / PATH_IN_PROGRESS / (id + std::string(SUFFIX_DAQ_STATUS));
224  default:
225  assert(false);
226  };
227 }
228 
229 } // namespace daq
WorkspaceImpl(std::filesystem::path root)
Opens or creates workspace in the specified location, using that as a root.
Declares JSON support for serialization.
daq::Workspace interface and implementation declaration
NLOHMANN_JSON_SERIALIZE_ENUM(State, { {State::NotStarted, "NotStarted"}, {State::Starting, "Starting"}, {State::Acquiring, "Acquiring"}, {State::Stopping, "Stopping"}, {State::Stopped, "Stopped"}, {State::NotScheduled, "NotScheduled"}, {State::Scheduled, "Scheduled"}, {State::Transferring, "Transferring"}, {State::Merging, "Merging"}, {State::Releasing, "Releasing"}, {State::AbortingAcquiring, "AbortingAcquiring"}, {State::AbortingMerging, "AbortingMerging"}, {State::Aborted, "Aborted"}, {State::Completed, "Completed"}, }) void to_json(nlohmann void to_json(nlohmann::json &j, Alert const &p)
Definition: json.cpp:48
Structure carrying context needed to start a Data Acquisition and construct a Data Product Specificat...
Definition: daqContext.hpp:44
std::string id
DAQ identfier, possibly provided by user.
Definition: daqContext.hpp:60
Non observable status object that keeps stores status of data acquisition.
Definition: status.hpp:124
std::string id
Definition: status.hpp:140