FACT++  1.0
Fits.cc
Go to the documentation of this file.
1 // **************************************************************************
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 
28 using namespace std;
29 using namespace CCfits;
30 
31 // --------------------------------------------------------------------------
32 //
39 //
40 void 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 //
61 //
62 void Fits::InitDataColumns(const vector<Description> &desc, const vector<string>& dataFormat, MessageImp* out)
63 {
64  fDataFormats = dataFormat;
65 
66  if ((desc.size() == 0) && (dataFormat.size() == 0))
67  {
68  fDataColDesc.clear();
69  return;
70  }
71 
72  //we will copy this information here. It duplicates the data, which is not great,
73  // but it is the easiest way of doing it right now
74  if (
75  (desc.size() == dataFormat.size()+1) || // regular service
76  (desc.size() == dataFormat.size()+2) // service with ending string. skipped in fits
77  )
78  {
79  //services have one (or two) more description than columns. skip the first entry while copying as it describes the table itself.
80 
81  fDataColDesc.clear();
82 
83  fTableDesc = desc[0].comment;
84  if (fTableDesc.size() > 68)
85  {
86  out->Warn("Table description '" + fTableDesc + "' exceeds 68 chars... truncated.");
87  fTableDesc = fTableDesc.substr(0,68);
88  }
89 
90  for (unsigned int i=0; i<dataFormat.size(); i++)
91  {
92  string name = desc[i+1].name;
93  if (name.length() > 68)
94  {
95  out->Warn("Column name '" + name + "' exceeds 68 chars... truncated.");
96  name = name.substr(0, 68);
97  }
98 
99  string comment = desc[i+1].comment;
100  if (comment.length() + name.length() > 71)
101  {
102  out->Warn("Column '" + name + " / " + comment + "' exceeds 68 chars... truncated.");
103  comment = comment.substr(0,68);
104  }
105 
106  string unit = desc[i+1].unit;
107  if (unit.length() > 68)
108  {
109  out->Warn("Unit '" + name + "' exceeds 68 chars... truncated.");
110  unit = comment.substr(0,68);
111  }
112 
113  const size_t p = fDataFormats[i].find_last_of('B');
114  if ((boost::iequals(unit, "text") || boost::iequals(unit, "string")) && p!=string::npos)
115  {
116  out->Info("Column '" + name + "' detected to be an ascii string (FITS format 'A').");
117  fDataFormats[i].replace(p, 1, "A");
118  }
119 
120  fDataColDesc.push_back(Description(name, comment, unit));
121  }
122  return;
123  }
124 
125  {//if we arrived here, this means that the columns descriptions could not be parsed
126  ostringstream str;
127  str << "Expected " << dataFormat.size() << " descriptions of columns, got " << (int)(desc.size())-1 << " for service: ";
128  if (desc.size() > 0)
129  str << desc[0].name;
130  else
131  str << "<unknown>";
132 
133  out->Warn(str.str());
134  }
135 
136  fDataColDesc.clear();
137  // fDataColDesc.push_back(Description("service", "comment", "unit"));
138  for (unsigned int i=0;i<dataFormat.size();i++)
139  {
140  ostringstream stt;
141  stt << "Data" << i;
142  fDataColDesc.push_back(Description(stt.str(), "", ""));
143  }
144 }
145 
146 // --------------------------------------------------------------------------
147 //
155 //
156 bool Fits::Open(const string& fileName, const string& tableName, uint32_t* fitsCounter, MessageImp* out, int runNumber, FITS* file)
157 {
158  fRunNumber = runNumber;
159  fMess = out;
160  fFileName = fileName;
161 
162  if (fFile)
163  {
164  fMess->Error("File already open...");
165  return false;
166  }
167 
168  fFile = new FitsFile(*fMess);
169 
170  if (file == NULL)
171  {
172  if (!fFile->OpenFile(fileName, true))
173  return false;
174 
175  fNumOpenFitsFiles = fitsCounter;
176  (*fNumOpenFitsFiles)++;
177  }
178  else
179  {
180  if (!fFile->SetFile(file))
181  return false;
182  }
183 
184  //concatenate the standard and data columns
185  //do it the inneficient way first: its easier and faster to code.
186  for (unsigned int i=0;i<fStandardColDesc.size();i++)
187  {
188  fFile->AddColumn(fStandardColDesc[i].name, fStandardFormats[i],
189  fStandardColDesc[i].unit);
190  }
191 
192  for (unsigned int i=0; i<fDataColDesc.size(); i++)
193  {
194  string name = fDataColDesc[i].name;
195  if (name.empty())
196  {
197  ostringstream stt;
198  stt << "Data" << i;
199  name = stt.str();
200  }
201 //cout << endl << "#####adding column: " << name << " " << fDataFormats[i] << " " << fDataColDesc[i].unit << endl << endl;
202  fFile->AddColumn(name, fDataFormats[i], fDataColDesc[i].unit);
203  }
204 
205  try
206  {
207  if (!fFile->OpenNewTable(tableName, 100))
208  {
209  Close();
210  //if the file already exist, then the column names must have changed
211  //let's move the file and try to open it again.
212  string fileNameWithoutFits = fFileName.substr(0, fileName.size()-4);
213  int counter = 0;
214  while (counter < 100)
215  {
216  ostringstream newFileName;
217  newFileName << fileNameWithoutFits << counter << ".fits";
218  ifstream testStream(newFileName.str().c_str());
219  if (!testStream)
220  {
221  if (rename(fFileName.c_str(), newFileName.str().c_str()))
222  return false;
223  break;
224  }
225  counter++;
226  }
227  if (counter == 100)
228  return false;
229  //now we open it again.
230  fFile = new FitsFile(*fMess);
231  if (file == NULL)
232  {
233  if (!fFile->OpenFile(fileName, true))
234  return false;
235  fNumOpenFitsFiles = fitsCounter;
236  (*fNumOpenFitsFiles)++;
237  }
238  else
239  {
240  if (!fFile->SetFile(file))
241  return false;
242  }
243  //YES, we must also redo that thing here...
244  //concatenate the standard and data columns
245  //do it the inneficient way first: its easier and faster to code.
246  for (unsigned int i=0;i<fStandardColDesc.size();i++)
247  {
248  fFile->AddColumn(fStandardColDesc[i].name, fStandardFormats[i],
249  fStandardColDesc[i].unit);
250  }
251 
252  for (unsigned int i=0; i<fDataColDesc.size(); i++)
253  {
254  string name = fDataColDesc[i].name;
255  if (name.empty())
256  {
257  ostringstream stt;
258  stt << "Data" << i;
259  name = stt.str();
260  }
261  //cout << endl << "#####adding column: " << name << " " << fDataFormats[i] << " " << fDataColDesc[i].unit << endl << endl;
262  fFile->AddColumn(name, fDataFormats[i], fDataColDesc[i].unit);
263  }
264  if (!fFile->OpenNewTable(tableName, 100))
265  {
266  Close();
267  return false;
268  }
269  }
270 
271  fCopyBuffer.resize(fFile->GetDataSize());
272 //write header comments
273 
274  ostringstream str;
275  for (unsigned int i=0;i<fStandardColDesc.size();i++)
276  {
277  str.str("");
278  str << "TTYPE" << i+1;
279  fFile->WriteKeyNT(str.str(), fStandardColDesc[i].name, fStandardColDesc[i].comment);
280  str.str("");
281  str << "TCOMM" << i+1;
282  fFile->WriteKeyNT(str.str(), fStandardColDesc[i].comment, "");
283  }
284 
285  for (unsigned int i=0; i<fDataColDesc.size(); i++)
286  {
287  string name = fDataColDesc[i].name;
288  if (name.empty())
289  {
290  ostringstream stt;
291  stt << "Data" << i;
292  name = stt.str();
293  }
294  str.str("");
295  str << "TTYPE" << i+fStandardColDesc.size()+1;
296  fFile->WriteKeyNT(str.str(), name, fDataColDesc[i].comment);
297  str.str("");
298  str << "TCOMM" << i+fStandardColDesc.size()+1;
299  fFile->WriteKeyNT(str.str(), fDataColDesc[i].comment, "");
300  }
301 
302  fFile->WriteKeyNT("COMMENT", fTableDesc, "");
303 
304  if (fFile->GetNumRows() == 0)
305  {//if new file, then write header keys -> reset fEndMjD used as flag
306  fEndMjD = 0;
307  }
308  else
309  {//file is beingn updated. Prevent from overriding header keys
310  fEndMjD = Time().Mjd();
311  }
312 
313  return fFile->GetNumRows()==0 ? WriteHeaderKeys() : true;
314  }
315  catch (const CCfits::FitsException &e)
316  {
317  cout << "Exception !" << endl;
318  fMess->Error("Opening or creating table '"+tableName+"' in '"+fileName+"': "+e.message());
319 
320  fFile->fTable = NULL;
321  Close();
322  return false;
323  }
324 }
325 
326 // --------------------------------------------------------------------------
327 //
329 //
331 {
332  if (!fFile->fTable)
333  return false;
334 
335  if (!fFile->WriteDefaultKeys("datalogger"))
336  return false;
337 
338  if (!fFile->WriteKeyNT("TSTARTI", 0, "Time when first event received (integral part)") ||
339  !fFile->WriteKeyNT("TSTARTF", 0, "Time when first event received (fractional part)") ||
340  !fFile->WriteKeyNT("TSTOPI", 0, "Time when last event received (integral part)") ||
341  !fFile->WriteKeyNT("TSTOPF", 0, "Time when last event received (fractional part)") ||
342  !fFile->WriteKeyNT("DATE-OBS", 0, "Time when first event received") ||
343  !fFile->WriteKeyNT("DATE-END", 0, "Time when last event received") ||
344  !fFile->WriteKeyNT("RUNID", fRunNumber, "Run number. 0 means not run file"))
345  return false;
346 
347  return true;
348 }
350 {
351  ostringstream corruptName;
352  struct stat st;
353  int append = 0;
354  corruptName << fFileName << "corrupt" << append;
355  while (!stat(corruptName.str().c_str(), &st))
356  {
357  append++;
358  corruptName.str("");
359  corruptName << fFileName << "corrupt" << append;
360  }
361  if (rename(fFileName.c_str(), corruptName.str().c_str()) != 0)
362  {
363  ostringstream str;
364  str << "rename() failed for '" << fFileName << "': " << strerror(errno) << " [errno=" << errno << "]";
365  fMess->Error(str);
366  return;
367  }
368 
369  fMess->Message("Renamed file " + fFileName + " to " + corruptName.str());
370 
371 }
372 // --------------------------------------------------------------------------
373 //
376 //
377 bool Fits::Write(const Converter &conv, const void* data)
378 {
379  //first copy the standard variables to the copy buffer
380  int shift = 0;
381  for (unsigned int i=0;i<fStandardNumBytes.size();i++)
382  {
383  const char *charSrc = reinterpret_cast<char*>(fStandardPointers[i]);
384  reverse_copy(charSrc, charSrc+fStandardNumBytes[i], fCopyBuffer.data()+shift);
385  shift += fStandardNumBytes[i];
386  }
387  try
388  {
389  //now take care of the DIM data. The Converter is here for that purpose
390  conv.ToFits(fCopyBuffer.data()+shift, data, fCopyBuffer.size()-shift);
391  }
392  catch (const runtime_error &e)
393  {
394  ostringstream str;
395  str << fFile->GetName() << ": " << e.what();
396  fMess->Error(str);
397  return false;
398  }
399 
400  // This is not necessary, is it?
401  // fFile->fTable->makeThisCurrent();
402  if (!fFile->AddRow())
403  {
404  Close();
405  MoveFileToCorruptedFile();
406  return false;
407  }
408  if (!fFile->WriteData(fCopyBuffer))
409  {
410  Close();
411  return false;
412  }
413  const double tm = *reinterpret_cast<double*>(fStandardPointers[0]);
414 
415  //the first standard variable is the current MjD
416  if (fEndMjD==0)
417  {
418  // FIXME: Check error?
419  fFile->WriteKeyNT("TSTARTI", uint32_t(floor(tm)), "Time when first event received (integral part)");
420  fFile->WriteKeyNT("TSTARTF", fmod(tm, 1), "Time when first event received (fractional part)");
421  fFile->WriteKeyNT("TSTOPI", uint32_t(floor(fEndMjD)), "Time when last event received (integral part)");
422  fFile->WriteKeyNT("TSTOPF", fmod(fEndMjD, 1), "Time when last event received (fractional part)");
423 
424  fFile->WriteKeyNT("DATE-OBS", Time(tm+40587).Iso(),
425  "Time when first event received");
426 
427  fFile->WriteKeyNT("DATE-END", Time(fEndMjD+40587).Iso(),
428  "Time when last event received");
429  }
430 
431  fEndMjD = tm;
432 
433  return true;
434 }
435 
436 // --------------------------------------------------------------------------
437 //
440 //
441 void Fits::Close()
442 {
443  if (!fFile)
444  return;
445  if (fFile->IsOpen() && fFile->IsOwner())
446  {
447  // FIMXE: Check for error? (It is allowed that fFile is NULL)
448  fFile->WriteKeyNT("TSTOPI", uint32_t(floor(fEndMjD)), "Time when last event received (integral part)");
449  fFile->WriteKeyNT("TSTOPF", fmod(fEndMjD, 1), "Time when last event received (fractional part)");
450 
451  fFile->WriteKeyNT("DATE-END", Time(fEndMjD+40587).Iso(),
452  "Time when last event received");
453  }
454  if (fFile->IsOwner())
455  {
456  if (fNumOpenFitsFiles != NULL)
457  (*fNumOpenFitsFiles)--;
458  }
459  const string name = fFile->GetName();
460  delete fFile;
461  fFile = NULL;
462  fMess->Info("Closed: "+name);
463 // fMess = NULL;
464 }
465 
467 {
468  if (!fFile)
469  return;
470 
471  fFile->Flush();
472 }
473 // --------------------------------------------------------------------------
476 {
477  if (!IsOpen())
478  return 0;
479 
480  struct stat st;
481  if (stat(fFile->GetName().c_str(), &st))
482  return 0;
483 
484  return st.st_size;
485 }
486 
487 /*
488  To be done:
489  - Check the check for column names in opennewtable
490  - If Open return false we end in an infinite loop (at least if
491  the dynamic cats to Bintable fails.
492 
493 */
uint32_t fRunNumber
Definition: HeadersFAD.h:190
int i
Definition: db_dim_client.c:21
The base implementation of a distributed messaging system.
Definition: MessageImp.h:10
void InitDataColumns(const vector< Description > &desc, const vector< string > &dataFormat, MessageImp *out)
Adds columns specific to the service being logged.
Definition: Fits.cc:62
Adds some functionality to boost::posix_time::ptime for our needs.
Definition: Time.h:30
char str[80]
Definition: test_client.c:7
void AddStandardColumn(const Description &desc, const string &dataFormat, void *dataPointer, long unsigned int numDataBytes)
Adds a column that exists in all FITS files.
Definition: Fits.cc:40
bool WriteHeaderKeys()
Write the FITS header keys.
Definition: Fits.cc:330
STL namespace.
bool Write(const Converter &conv, const void *data)
Write one line of data. Use the given converter.
Definition: Fits.cc:377
Definition: FITS.h:24
A struct which stores a name, a unit and a comment.
Definition: Description.h:7
void MoveFileToCorruptedFile()
Definition: Fits.cc:349
void Mjd(double mjd)
Definition: Time.cc:145
int Error(const std::string &str)
Definition: MessageImp.h:49
int Warn(const std::string &str)
Definition: MessageImp.h:48
std::string name
Definition: Description.h:9
FITS writter for the FACT project.
Definition: FitsFile.h:9
int GetWrittenSize() const
Get the size currently written on the disk.
Definition: Fits.cc:475
void Flush()
Flush the currently opened file to disk.
Definition: Fits.cc:466
float data[4 *1440]
int counter
Definition: db_dim_client.c:19
int Info(const std::string &str)
Definition: MessageImp.h:47
bool Open(const string &fileName, const string &tableName, uint32_t *fitsCounter, MessageImp *out, int runNumber, CCfits::FITS *file=0)
Opens a FITS file.
Definition: Fits.cc:156
A compiler for the DIM data format string.
Definition: Converter.h:16
void Close()
Close the currently opened file.
Definition: Fits.cc:441
void ToFits(void *dest, const void *src, size_t size) const
Definition: Converter.cc:891