fvfile.cpp

00001
00002 /***************************************************************************
00003  *  fvfile.cpp - FireVision file
00004  *
00005  *  Created: Fri Mar 28 11:45:47 2008
00006  *  Copyright  2008  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version. A runtime exception applies to
00014  *  this software (see LICENSE.GPL_WRE file mentioned below for details).
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU Library General Public License for more details.
00020  *
00021  *  Read the full text in the LICENSE.GPL_WRE file in the doc directory.
00022  */
00023
00024 #include <fvutils/fileformat/fvfile.h>
00025
00026 #include <core/exceptions/system.h>
00027
00028 #include <cstring>
00029 #include <cstdio>
00030 #include <cerrno>
00031 #include <netinet/in.h>
00032 #include <sys/time.h>
00033 #ifdef __FreeBSD__
00034 #  include <strfunc.h>
00035 #endif
00036 
00037 using namespace fawkes;
00038 
00039 /** @class FireVisionDataFile <fvutils/fileformat/fvff.h>
00040  * FireVision File Format for data files.
00041  * The FireVision File Format (FVFF) defines a generic file layout that is used
00042  * to store large chunks of data on the the disk drive in a byte efficient way.
00043  *
00044  * It is meant to serve as a skeleton which is used by subclasses to implement
00045  * support for a concrete file format like colormaps or rectification information.
00046  * It allows for arbitrary meta data to be added that is relevant to the format and
00047  * it provides all the generic meta data that is needed to make the file format
00048  * work and that is common to all formats.
00049  *
00050  * Each format has a two byte magic token. In general it is of the form FFNN, where
00051  * FF stays literally (FireVision File) and NN is replaced with a number of the format.
00052  * Currently assigned format numbers include:
00053  * - FF01: colormaps
00054  * - FF02: generic lookup tables
00055  * - FF03: rectification information
00056  * - FF04: histograms
00057  *
00058  * We assume large chunks of data that is saved most efficiently in a proprietary
00059  * binary format that can be read and written quickly and mimics the layout of the
00060  * file in the memory.
00061  *
00062  * The general layout is:
00063  * @code
00064  *  1. General header (file type, version, endianess, number of blocks, etc.)
00065  *  2. Content type specific header (optional)
00066  *  3. Data blocks
00067  * @endcode
00068  * Each of the data blocks itself is of the following form:
00069  * @code
00070  *  1. General block header (type, size)
00071  *  2. Content type specific block header (optional)
00072  *  3. Data chunk (raw byte stream, content-specific)
00073  * @endcode
00074  *
00075  * @author Tim Niemueller
00076  */
00077 
00078 /** @var void * FireVisionDataFile::_spec_header
00079  * Content specific header.
00080  * Create this buffer and set the size in _spec_header_size to get it written to
00081  * the file.
00082  */
00083 /** @var size_t FireVisionDataFile::_spec_header_size
00084  * Size in bytes of _spec_header.
00085  */
00086
00087 
00088 /** Constructor.
00089  * @param magic_token magic token for the concrete file type
00090  * @param version file format version
00091  */
00092 FireVisionDataFile::FireVisionDataFile(unsigned short int magic_token,
00093                                        unsigned short int version)
00094 {
00095   __header       = (fvff_header_t *)calloc(1, sizeof(fvff_header_t));
00096
00097   __magic_token = magic_token;
00098   __version     = version;
00099   __comment     = strdup("");
00100
00101   _spec_header = NULL;
00102   _spec_header_size = 0;
00103
00104   __owns_blocks = true;
00105
00106   clear();
00107 }
00108
00109 
00110 /** Destructor. */
00111 FireVisionDataFile::~FireVisionDataFile()
00112 {
00113   clear();
00114
00115   free(__header);
00116   free(__comment);
00117   if ( _spec_header ) {
00118     free(_spec_header);
00119   }
00120 }
00121
00122 
00123 /** Clear internal storage.
00124  * All internal data is deleted.
00125  */
00126 void
00127 FireVisionDataFile::clear()
00128 {
00129   if (__owns_blocks) {
00130     for (__bi = __blocks.begin(); __bi != __blocks.end(); ++__bi) {
00131       delete *__bi;
00132     }
00133   }
00134
00135   __blocks.clear();
00136   memset(__header, 0, sizeof(fvff_header_t));
00137
00138   __header->magic_token = htons(__magic_token);
00139   __header->version     = __version;
00140   __header->num_blocks = 0;
00141 #if __BYTE_ORDER == __BIG_ENDIAN
00142   __header->endianess = 1;
00143 #else
00144   __header->endianess = 0;
00145 #endif
00146   free(__comment);
00147   __comment = strdup("");
00148 }
00149
00150 
00151 /** Get the magic token of the file.
00152  * @return Magic token
00153  */
00154 unsigned int
00155 FireVisionDataFile::magic_token()
00156 {
00157   return __header->magic_token;
00158 }
00159
00160 
00161 /** Get the version of the file.
00162  * @return version of the file (or the current supported version if no file was loaded)
00163  */
00164 unsigned int
00165 FireVisionDataFile::version()
00166 {
00167   return __header->version;
00168 }
00169
00170 
00171 /** Check if data is encoded as big endian.
00172  * @return true if data is encoded as big endian, false otherwise
00173  */
00174 bool
00175 FireVisionDataFile::is_big_endian()
00176 {
00177   return (__header->endianess == 1);
00178 }
00179
00180 
00181 /** Check if data is encoded as little endian.
00182  * @return true if data is encoded as little endian, false otherwise
00183  */
00184 bool
00185 FireVisionDataFile::is_little_endian()
00186 {
00187   return (__header->endianess == 0);
00188 }
00189
00190 
00191 /** Get comment.
00192  * @return comment of the file
00193  */
00194 const char *
00195 FireVisionDataFile::get_comment() const
00196 {
00197   return __comment;
00198 }
00199
00200 
00201 /** Set comment.
00202  * @param comment new comment to set
00203  */
00204 void
00205 FireVisionDataFile::set_comment(const char *comment)
00206 {
00207   free(__comment);
00208   __comment = strndup(comment, FVFF_COMMENT_SIZE);
00209   strncpy(__header->comment, comment, FVFF_COMMENT_SIZE);
00210 }
00211
00212 
00213 /** Lets the file take over the ownership and give up the ownership of the blocks,
00214  * respectively. By default, the file is the owner of the blocks. If a file owns
00215  * the blocks they will be deleted in the files destructor.
00216  * @param owns_blocks if true file owns the blocks
00217  */
00218 void
00219 FireVisionDataFile::set_owns_blocks(bool owns_blocks)
00220 {
00221   __owns_blocks = owns_blocks;
00222 }
00223
00224 
00225 /** Get the number of available info blocks.
00226  * @return number of available info blocks
00227  */
00228 size_t
00229 FireVisionDataFile::num_blocks()
00230 {
00231   return __blocks.size();
00232 }
00233
00234 
00235 /** Add a block.
00236  * @param block block to add
00237  */
00238 void
00239 FireVisionDataFile::add_block(FireVisionDataFileBlock *block)
00240 {
00241   __blocks.push_back(block);
00242 }
00243
00244 
00245 /** Get blocks.
00246  * @return block list
00247  */
00248 FireVisionDataFile::BlockList &
00249 FireVisionDataFile::blocks()
00250 {
00251   return __blocks;
00252 }
00253
00254 
00255 /** Write file.
00256  * @param file_name file to write to
00257  */
00258 void
00259 FireVisionDataFile::write(const char *file_name)
00260 {
00261   FILE *f = fopen(file_name, "w");
00262   if ( f == NULL ) {
00263     throw CouldNotOpenFileException(file_name, errno, "Could not open rectlut file "
00264                                                       "for writing");
00265   }
00266
00267   __header->num_blocks = (unsigned int)__blocks.size();
00268   timeval t;
00269   gettimeofday(&t, NULL);
00270   __header->created_sec  = t.tv_sec;
00271   __header->created_usec = t.tv_usec;
00272   __header->spec_head_size = _spec_header_size;
00273
00274   //printf("Writing %zu bytes for header\n", sizeof(fvff_header_t));
00275   if ( fwrite(__header, sizeof(fvff_header_t), 1, f) != 1 ) {
00276     fclose(f);
00277     throw FileWriteException(file_name, errno, "Writing fvff header failed");
00278   }
00279
00280   if ( _spec_header_size > 0 ) {
00281     //printf("Writing %zu bytes for spec header\n", _spec_header_size);
00282     if ( fwrite(_spec_header, _spec_header_size, 1, f) != 1 ) {
00283       fclose(f);
00284       throw FileWriteException(file_name, errno, "Writing content specific header failed");
00285     }
00286   }
00287
00288   for (__bi = __blocks.begin(); __bi != __blocks.end(); ++__bi) {
00289     // write this info block
00290     //printf("Writing %zu bytes for block\n", (*__bi)->block_size());
00291     if ( fwrite((*__bi)->block_memptr(), (*__bi)->block_size(), 1, f) != 1 ) {
00292       fclose(f);
00293       throw FileWriteException(file_name, errno, "Failed to write info block");
00294     }
00295   }
00296
00297   fclose(f);
00298 }
00299
00300 
00301 /** Read file.
00302  * @param file_name file to read from
00303  */
00304 void
00305 FireVisionDataFile::read(const char *file_name)
00306 {
00307   FILE *f = fopen(file_name, "r");
00308   if ( f == NULL ) {
00309     throw CouldNotOpenFileException(file_name, errno, "Could not open rectlut file "
00310                                                       "for reading");
00311   }
00312
00313   clear();
00314
00315   //printf("Reading %zu bytes for header\n", sizeof(fvff_header_t));
00316   if ( fread(__header, sizeof(fvff_header_t), 1, f) != 1) {
00317     fclose(f);
00318     throw FileReadException(file_name, errno, "Reading header failed");
00319   }
00320
00321   if ( __header->magic_token != htons(__magic_token) ) {
00322     fclose(f);
00323     throw Exception("Unknown magic in fvff file (read: 0x%04x req: 0x%04x)",
00324                     __header->magic_token, __magic_token);
00325   }
00326
00327   if ( __header->version != __version ) {
00328     fclose(f);
00329     throw Exception("Unsupported version of fvff file (read: %u req: %u)",
00330                     __header->version, __version);
00331   }
00332
00333   if ( __header->endianess ==
00334 #if __BYTE_ORDER == __BIG_ENDIAN
00335        0
00336 #else
00337        1
00338 #endif
00339        ) {
00340     fclose(f);
00341     throw Exception("FVFile header cannot be translated for endianess by now");
00342   }
00343
00344   free(__comment);
00345   __comment = strndup(__header->comment, FVFF_COMMENT_SIZE);
00346
00347   if ( _spec_header ) {
00348     free(_spec_header);
00349   }
00350   _spec_header = calloc(1, __header->spec_head_size);
00351   if ( ! _spec_header ) {
00352     throw OutOfMemoryException("Cannot allocate memory for content specific header");
00353   }
00354
00355   if ( __header->spec_head_size > 0 ) {
00356     //printf("Reading %u bytes for spec header\n", __header->spec_head_size);
00357     if ( fread(_spec_header, __header->spec_head_size, 1, f) != 1) {
00358       fclose(f);
00359       throw FileReadException(file_name, errno, "Reading content specific header failed");
00360     }
00361   }
00362
00363   //printf("Reading %u blocks\n", __header->num_blocks);
00364   for (uint8_t b = 0; b < __header->num_blocks && !feof(f); ++b) {
00365     fvff_block_header_t bh;
00366     //printf("Reading %zu bytes for block header\n", sizeof(bh));
00367     if ( fread(&bh, sizeof(bh), 1, f) != 1 ) {
00368       fclose(f);
00369       throw FileReadException(file_name, errno,
00370                               "Could not read block info header while there should be one");
00371     }
00372     void *spec_header = NULL;
00373
00374     if ( bh.spec_head_size > 0 ) {
00375       // Read specific header
00376       spec_header = malloc(bh.spec_head_size);
00377       if ( ! spec_header ) {
00378         throw OutOfMemoryException("Could not allocate %u bytes for content specific header",
00379                                    bh.spec_head_size);
00380       }
00381
00382       //printf("Reading %u bytes for block spec header\n", bh.spec_head_size);
00383       if ( fread(spec_header, bh.spec_head_size, 1, f) != 1 ) {
00384         fclose(f);
00385         free(spec_header);
00386         throw FileReadException(file_name, errno,
00387                                 "Could not read content specific block header");
00388       }
00389     }
00390
00391     FireVisionDataFileBlock *block = new FireVisionDataFileBlock(bh.type, bh.size,
00392                                                                  spec_header, bh.spec_head_size);
00393
00394     free(spec_header);
00395
00396     //printf("Reading %u bytes for block data\n", bh.size);
00397     if ( bh.size && fread(block->data_ptr(), bh.size, 1, f) != 1 ) {
00398       fclose(f);
00399       delete block;
00400       throw FileReadException(file_name, errno,
00401                               "Could not read block data");
00402     }
00403
00404     __blocks.push_back(block);
00405   }
00406
00407   fclose(f);
00408 }
00409
00410 
00411 /** Get magic token from file.
00412  * @param filename name of file to read the magic token from
00413  * @return magic token
00414  */
00415 unsigned short int
00416 FireVisionDataFile::read_magic_token(const char *filename)
00417 {
00418   uint16_t magic_token = 0;
00419
00420   FILE *f;
00421   f = fopen(filename, "r");
00422   if (f != NULL) {
00423     if ( fread((char *)&magic_token, sizeof(magic_token), 1, f) != 1 ) {
00424       fclose(f);
00425       throw FileReadException(filename, errno, "Could not read magic token from file");
00426     }
00427   }
00428   fclose(f);
00429
00430   return magic_token;
00431 }
00432
00433 
00434 /** Check if file has a certain magic token.
00435  * @param filename name of file to read the magic token from
00436  * @param magic_token magic token to look for
00437  * @return true if magic token was  found, false otherwise
00438  */
00439 bool
00440 FireVisionDataFile::has_magic_token(const char *filename, unsigned short int magic_token)
00441 {
00442   uint16_t file_magic_token = read_magic_token(filename);
00443   return (htons(magic_token) == file_magic_token);
00444 }