source: trunk/FACT++/src/Fits.cc@ 13686

Last change on this file since 13686 was 13205, checked in by lyard, 13 years ago
added fence from prevent column desc of messages services
File size: 14.2 KB
Line 
1// **************************************************************************
2/** @class FactFits
3
4@brief FITS writter for the FACT project.
5
6The FactFits class is able to open, manage and update FITS files.
7
8The file columns should be given to the class before the file is openned. Once
9a file has been created, the structure of its columns cannot be changed. Only
10row can be added.
11
12This class relies on the CCfits and CFitsIO packages.
13
14*/
15// **************************************************************************
16#include "Fits.h"
17
18#include "Time.h"
19#include "Converter.h"
20#include "MessageImp.h"
21
22#include <sys/stat.h> //for file stats
23#include <cstdio> // for file rename
24#include <cerrno>
25
26#include <boost/algorithm/string/predicate.hpp>
27
28using namespace std;
29using namespace CCfits;
30
31// --------------------------------------------------------------------------
32//
33//! This gives a standard variable to the file writter.
34//! This variable should not be related to the DIM service being logged.
35//! @param desc the description of the variable to add
36//! @param dataFormat the FITS data format corresponding to the variable to add.
37//! @param dataPointer the memory location where the variable is stored
38//! @param numDataBytes the number of bytes taken by the variable
39//
40void Fits::AddStandardColumn(const Description& desc, const string &dataFormat, void* dataPointer, long unsigned int numDataBytes)
41{
42 //check if entry already exist
43 for (vector<Description>::const_iterator it=fStandardColDesc.begin(); it != fStandardColDesc.end(); it++)
44 if (it->name == desc.name)
45 return;
46
47 fStandardColDesc.push_back(desc);
48 fStandardFormats.push_back(dataFormat);
49 fStandardPointers.push_back(dataPointer);
50 fStandardNumBytes.push_back(numDataBytes);
51}
52
53// --------------------------------------------------------------------------
54//
55//! This gives the file writter access to the DIM data
56//! @param desc a vector containing the description of all the columns to log
57//! @param dataFormat a vector containing the FITS data format of all the columsn to log
58//! @param dataPointer the memory location where the DIM data starts
59//! @param numDataBytes the number of bytes taken by the DIM data.
60//! @param out Message object to use for propagating messages
61//
62void Fits::InitDataColumns(const vector<Description> &desc, const vector<string>& dataFormat, void* dataPointer, MessageImp* out)
63{
64 fDataFormats = dataFormat;
65 fDataPointer = dataPointer;
66
67 if ((desc.size() == 0) && (dataFormat.size() == 0))
68 {
69 fDataColDesc.clear();
70 return;
71 }
72
73 //we will copy this information here. It duplicates the data, which is not great,
74 // but it is the easiest way of doing it right now
75 if (
76 (desc.size() == dataFormat.size()+1) || // regular service
77 (desc.size() == dataFormat.size()+2) // service with ending string. skipped in fits
78 )
79 {
80 //services have one (or two) more description than columns. skip the first entry while copying as it describes the table itself.
81
82 fDataColDesc.clear();
83
84 fTableDesc = desc[0].comment;
85 if (fTableDesc.size() > 68)
86 {
87 out->Warn("Table description '" + fTableDesc + "' exceeds 68 chars... truncated.");
88 fTableDesc = fTableDesc.substr(0,68);
89 }
90
91 for (unsigned int i=0; i<dataFormat.size(); i++)
92 {
93 string name = desc[i+1].name;
94 if (name.length() > 68)
95 {
96 out->Warn("Column name '" + name + "' exceeds 68 chars... truncated.");
97 name = name.substr(0, 68);
98 }
99
100 string comment = desc[i+1].comment;
101 if (comment.length() + name.length() > 71)
102 {
103 out->Warn("Column '" + name + " / " + comment + "' exceeds 68 chars... truncated.");
104 comment = comment.substr(0,68);
105 }
106
107 string unit = desc[i+1].unit;
108 if (unit.length() > 68)
109 {
110 out->Warn("Unit '" + name + "' exceeds 68 chars... truncated.");
111 unit = comment.substr(0,68);
112 }
113
114 const size_t p = fDataFormats[i].find_last_of('B');
115 if ((boost::iequals(unit, "text") || boost::iequals(unit, "string")) && p!=string::npos)
116 {
117 out->Info("Column '" + name + "' detected to be an ascii string (FITS format 'A').");
118 fDataFormats[i].replace(p, 1, "A");
119 }
120
121 fDataColDesc.push_back(Description(name, comment, unit));
122 }
123 return;
124 }
125
126 {//if we arrived here, this means that the columns descriptions could not be parsed
127 ostringstream str;
128 str << "Expected " << dataFormat.size() << " descriptions of columns, got " << desc.size()-1 << " for service: ";
129 if (desc.size() > 0)
130 str << desc[0].name;
131 else
132 str << "<unknown>";
133
134 out->Warn(str.str());
135 }
136
137 fDataColDesc.clear();
138 // fDataColDesc.push_back(Description("service", "comment", "unit"));
139 for (unsigned int i=0;i<dataFormat.size();i++)
140 {
141 ostringstream stt;
142 stt << "Data" << i;
143 fDataColDesc.push_back(Description(stt.str(), "", ""));
144 }
145}
146
147// --------------------------------------------------------------------------
148//
149//! This opens the FITS file (after the columns have been passed)
150//! @param fileName the filename with complete or relative path of the file to open
151//! @param tableName the name of the table that will receive the logged data.
152//! @param file a pointer to an existing FITS file. If NULL, file will be opened and managed internally
153//! @param fitsCounter a pointer to the integer keeping track of the opened FITS files
154//! @param out a pointer to the MessageImp that should be used to log errors
155//! @param runNumber the runNumber for which this file is opened. 0 means nightly file.
156//
157bool Fits::Open(const string& fileName, const string& tableName, uint32_t* fitsCounter, MessageImp* out, int runNumber, FITS* file)
158{
159 fRunNumber = runNumber;
160 fMess = out;
161 fFileName = fileName;
162
163 if (fFile)
164 {
165 fMess->Error("File already open...");
166 return false;
167 }
168
169 fFile = new FitsFile(*fMess);
170
171 if (file == NULL)
172 {
173 if (!fFile->OpenFile(fileName, true))
174 return false;
175
176 fNumOpenFitsFiles = fitsCounter;
177 (*fNumOpenFitsFiles)++;
178 }
179 else
180 {
181 if (!fFile->SetFile(file))
182 return false;
183 }
184
185 //concatenate the standard and data columns
186 //do it the inneficient way first: its easier and faster to code.
187 for (unsigned int i=0;i<fStandardColDesc.size();i++)
188 {
189 fFile->AddColumn(fStandardColDesc[i].name, fStandardFormats[i],
190 fStandardColDesc[i].unit);
191 }
192
193 for (unsigned int i=0; i<fDataColDesc.size(); i++)
194 {
195 string name = fDataColDesc[i].name;
196 if (name.empty())
197 {
198 ostringstream stt;
199 stt << "Data" << i;
200 name = stt.str();
201 }
202//cout << endl << "#####adding column: " << name << " " << fDataFormats[i] << " " << fDataColDesc[i].unit << endl << endl;
203 fFile->AddColumn(name, fDataFormats[i], fDataColDesc[i].unit);
204 }
205
206 try
207 {
208 if (!fFile->OpenNewTable(tableName, 100))
209 {
210 Close();
211 return false;
212 }
213
214 fCopyBuffer.resize(fFile->GetDataSize());
215//write header comments
216 ostringstream str;
217 for (unsigned int i=0;i<fStandardColDesc.size();i++)
218 {
219 str.str("");
220 str << "TTYPE" << i+1;
221 fFile->WriteKeyNT(str.str(), fStandardColDesc[i].name, fStandardColDesc[i].comment);
222 str.str("");
223 str << "TCOMM" << i+1;
224 fFile->WriteKeyNT(str.str(), fStandardColDesc[i].comment, "");
225 }
226
227 for (unsigned int i=0; i<fDataColDesc.size(); i++)
228 {
229 string name = fDataColDesc[i].name;
230 if (name.empty())
231 {
232 ostringstream stt;
233 stt << "Data" << i;
234 name = stt.str();
235 }
236 str.str("");
237 str << "TTYPE" << i+fStandardColDesc.size()+1;
238 fFile->WriteKeyNT(str.str(), name, fDataColDesc[i].comment);
239 str.str("");
240 str << "TCOMM" << i+fStandardColDesc.size()+1;
241 fFile->WriteKeyNT(str.str(), fDataColDesc[i].comment, "");
242 }
243 fFile->WriteKeyNT("COMMENT", fTableDesc, "");
244
245 if (fFile->GetNumRows() == 0)
246 {//if new file, then write header keys -> reset fEndMjD used as flag
247 fEndMjD = 0;
248 }
249 else
250 {//file is beingn updated. Prevent from overriding header keys
251 fEndMjD = Time().Mjd();
252 }
253 return fFile->GetNumRows()==0 ? WriteHeaderKeys() : true;
254 }
255 catch (const CCfits::FitsException &e)
256 {
257 fMess->Error("Opening or creating table '"+tableName+"' in '"+fileName+"': "+e.message());
258
259 fFile->fTable = NULL;
260 Close();
261 return false;
262 }
263}
264
265// --------------------------------------------------------------------------
266//
267//! This writes the standard header
268//
269bool Fits::WriteHeaderKeys()
270{
271 if (!fFile->fTable)
272 return false;
273
274 if (!fFile->WriteDefaultKeys("datalogger"))
275 return false;
276
277 if (!fFile->WriteKeyNT("TSTARTI", 0, "Time when first event received (integral part)") ||
278 !fFile->WriteKeyNT("TSTARTF", 0, "Time when first event received (fractional part)") ||
279 !fFile->WriteKeyNT("TSTOPI", 0, "Time when last event received (integral part)") ||
280 !fFile->WriteKeyNT("TSTOPF", 0, "Time when last event received (fractional part)") ||
281 !fFile->WriteKeyNT("DATE-OBS", 0, "Time when first event received") ||
282 !fFile->WriteKeyNT("DATE-END", 0, "Time when last event received") ||
283 !fFile->WriteKeyNT("RUNID", fRunNumber, "Run number. 0 means not run file"))
284 return false;
285
286 return true;
287}
288void Fits::MoveFileToCorruptedFile()
289{
290 ostringstream corruptName;
291 struct stat st;
292 int append = 0;
293 corruptName << fFileName << "corrupt" << append;
294 while (!stat(corruptName.str().c_str(), &st))
295 {
296 append++;
297 corruptName.str("");
298 corruptName << fFileName << "corrupt" << append;
299 }
300 if (rename(fFileName.c_str(), corruptName.str().c_str()) != 0)
301 {
302 ostringstream str;
303 str << "rename() failed for '" << fFileName << "': " << strerror(errno) << " [errno=" << errno << "]";
304 fMess->Error(str);
305 return;
306 }
307
308 fMess->Message("Renamed file " + fFileName + " to " + corruptName.str());
309
310}
311// --------------------------------------------------------------------------
312//
313//! This writes one line of data to the file.
314//! @param conv the converter corresponding to the service being logged
315//
316bool Fits::Write(const Converter &conv)
317{
318 //first copy the standard variables to the copy buffer
319 int shift = 0;
320 for (unsigned int i=0;i<fStandardNumBytes.size();i++)
321 {
322 const char *charSrc = reinterpret_cast<char*>(fStandardPointers[i]);
323 reverse_copy(charSrc, charSrc+fStandardNumBytes[i], fCopyBuffer.data()+shift);
324 shift += fStandardNumBytes[i];
325 }
326
327 try
328 {
329 //now take care of the DIM data. The Converter is here for that purpose
330 conv.ToFits(fCopyBuffer.data()+shift, fDataPointer, fCopyBuffer.size()-shift);
331 }
332 catch (const runtime_error &e)
333 {
334 ostringstream str;
335 str << fFile->GetName() << ": " << e.what();
336 fMess->Error(str);
337 return false;
338 }
339
340 // This is not necessary, is it?
341 // fFile->fTable->makeThisCurrent();
342
343 if (!fFile->AddRow())
344 {
345 Close();
346 MoveFileToCorruptedFile();
347 return false;
348 }
349
350 if (!fFile->WriteData(fCopyBuffer))
351 {
352 Close();
353 return false;
354 }
355
356 const double tm = *reinterpret_cast<double*>(fStandardPointers[0]);
357
358 //the first standard variable is the current MjD
359 if (fEndMjD==0)
360 {
361 // FIXME: Check error?
362 fFile->WriteKeyNT("TSTARTI", uint32_t(floor(tm)), "Time when first event received (integral part)");
363 fFile->WriteKeyNT("TSTARTF", fmod(tm, 1), "Time when first event received (fractional part)");
364 fFile->WriteKeyNT("TSTOPI", uint32_t(floor(fEndMjD)), "Time when last event received (integral part)");
365 fFile->WriteKeyNT("TSTOPF", fmod(fEndMjD, 1), "Time when last event received (fractional part)");
366
367 fFile->WriteKeyNT("DATE-OBS", Time(tm+40587).Iso(),
368 "Time when first event received");
369
370 fFile->WriteKeyNT("DATE-END", Time(fEndMjD+40587).Iso(),
371 "Time when last event received");
372 }
373
374 fEndMjD = tm;
375
376 return true;
377}
378
379// --------------------------------------------------------------------------
380//
381//! This closes the currently openned FITS file.
382//! it also updates the header to reflect the time of the last logged row
383//
384void Fits::Close()
385{
386 if (!fFile)
387 return;
388
389 if (fFile->IsOpen() && fFile->IsOwner())
390 {
391 // FIMXE: Check for error? (It is allowed that fFile is NULL)
392 fFile->WriteKeyNT("TSTOPI", uint32_t(floor(fEndMjD)), "Time when last event received (integral part)");
393 fFile->WriteKeyNT("TSTOPF", fmod(fEndMjD, 1), "Time when last event received (fractional part)");
394
395 fFile->WriteKeyNT("DATE-END", Time(fEndMjD+40587).Iso(),
396 "Time when last event received");
397 }
398
399 if (fFile->IsOwner())
400 {
401 if (fNumOpenFitsFiles != NULL)
402 (*fNumOpenFitsFiles)--;
403 }
404
405 const string name = fFile->GetName();
406
407 delete fFile;
408 fFile = NULL;
409
410 fMess->Info("Closed: "+name);
411
412// fMess = NULL;
413}
414
415void Fits::Flush()
416{
417 if (!fFile)
418 return;
419
420 fFile->Flush();
421}
422// --------------------------------------------------------------------------
423//! Returns the size on the disk of the Fits file being written.
424int Fits::GetWrittenSize() const
425{
426 if (!IsOpen())
427 return 0;
428
429 struct stat st;
430 if (stat(fFile->GetName().c_str(), &st))
431 return 0;
432
433 return st.st_size;
434}
435
436/*
437 To be done:
438 - Check the check for column names in opennewtable
439 - If Open return false we end in an infinite loop (at least if
440 the dynamic cats to Bintable fails.
441
442*/
Note: See TracBrowser for help on using the repository browser.