File indexing completed on 2024-03-24 15:14:49

0001 /*
0002     SPDX-FileCopyrightText: 2008 Akarsh Simha <akarshsimha@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #include <QString>
0010 #include <QVector>
0011 
0012 #include <cstdio>
0013 
0014 class QString;
0015 
0016 /**
0017  * @short A structure describing a data field in the binary file. Its size is 16 bytes.
0018  */
0019 typedef struct dataElement
0020 {
0021     char name[10] = ""; /**< Field name (eg. RA) */
0022     qint8 size { 0 };    /**< Field size in bytes (eg. 4) */
0023     quint8 type { 0 };
0024     qint32 scale { 0 }; /**< Field scale. The final field value = raw_value * scale */
0025 } dataElement;
0026 
0027 /**
0028  * @class BinFileHelper
0029  *
0030  * This class provides utility functions to handle binary data files in the format prescribed
0031  * by KStars. The file format is designed specifically to support data that has the form of an
0032  * array of structures. See data/README.fileformat for details.
0033  * The methods use primitive C file I/O routines defined in stdio.h to obtain efficiency
0034  * @short Implements an interface to handle binary data files used by KStars
0035  * @author Akarsh Simha
0036  * @version 1.0
0037  */
0038 class BinFileHelper
0039 {
0040   public:
0041     /** Constructor */
0042     BinFileHelper();
0043 
0044     /** Destructor */
0045     ~BinFileHelper();
0046 
0047     /**
0048      * @short Checks if a file exists. WARNING: Might be incompatible in other locales
0049      */
0050     static bool testFileExists(const QString &fileName);
0051 
0052     /**
0053      * WARNING: This function may not be compatible in other locales, because it calls QString::toAscii
0054      * @short Open a Binary data file and set the handle
0055      * @param fileName  Reference to QString containing the name of the file
0056      * @return Handle to the file if successful, nullptr if an error occurred, sets the error.
0057      */
0058 
0059     FILE *openFile(const QString &fileName);
0060 
0061     /**
0062      * @short  Read the header and index table from the file and fill up the QVector s with the entries
0063      * @return True if successful, false if an error occurred, sets the error.
0064      */
0065     bool readHeader();
0066 
0067     /**
0068      * @short  Close the binary data file
0069      */
0070     void closeFile();
0071 
0072     /**
0073      * @short   Get error number
0074      * @return  A number corresponding to the error
0075      */
0076     int getErrorNumber();
0077 
0078     /**
0079      * @short   Get error string
0080      * @return  A QString containing the error message to be displayed
0081      */
0082     QString getError();
0083 
0084     /**
0085      * @short   Get field by name
0086      * @param   fieldName  Name of the field
0087      * @return  A dataElement structure containing field data if the field exists,
0088      *          containing junk values if the field doesn't exist
0089      */
0090     struct dataElement getField(const QString &fieldName) const;
0091 
0092     /**
0093      * @short  Check whether a field exists
0094      * @param  FieldName   Name of the field
0095      * @return True if the field exists, false if it does not or the FD hasn't been read yet
0096      */
0097     bool isField(const QString &FieldName) const;
0098 
0099     /**
0100      * @short  Check whether file properties are real or bogus
0101      * @return The value of private variable indexUpdated, which is true if data has been updated
0102      */
0103     inline bool propertiesUpdated() const { return indexUpdated; }
0104 
0105     /**
0106      * @short  Get the file handle corresponding to the currently open file
0107      * @return The filehandle if a file is open, nullptr if no file is open
0108      */
0109     inline FILE *getFileHandle() const { return fileHandle; }
0110 
0111     /**
0112      * @short  Returns the offset in the file corresponding to the given index ID
0113      * @param  id  ID of the index entry whose offset is required
0114      * @return The offset corresponding to that index ID, 0 if the index hasn't been read
0115      */
0116     inline long getOffset(int id) const { return (indexUpdated ? indexOffset.at(id) : 0); }
0117 
0118     /**
0119      * @short  Returns the number of records under the given index ID
0120      * @param  id  ID of the index entry
0121      * @return The number of records under index that index ID, or 0 if the index has not been read
0122      */
0123     inline unsigned int getRecordCount(int id) const { return (indexUpdated ? indexCount.at(id) : 0); }
0124 
0125     /**
0126      * @short  Returns the total number of records in the file
0127      * @return The number of records in the file, or 0 if the index has not been read
0128      */
0129     inline unsigned long getRecordCount() const { return (indexUpdated ? recordCount : 0); }
0130 
0131     /**
0132      * @short  Should we do byte swapping?
0133      * @note   To be called only after the header has been parsed
0134      * @return true if we must do byte swapping, false if not
0135      */
0136     inline bool getByteSwap() const { return byteswap; }
0137 
0138     /**
0139      * @short  Return a guessed record size
0140      * @note   The record size returned is guessed from the field descriptor table and may
0141      *         not be correct in many cases
0142      * @return A guessed size of the record, or zero if the FD hasn't been read yet
0143      */
0144     inline int guessRecordSize() const { return (RSUpdated ? recordSize : 0); }
0145 
0146     /**
0147      * @short  Override record size
0148      * @note   To be used to override the guess in case the guess is wrong. This method should be called
0149      *         before calling readHeader(). Use this only if you are sure of the recordSize or are sure
0150      *         that the guess will not work.
0151      * @param  rs The correct recordSize
0152      */
0153     inline void setRecordSize(int rs)
0154     {
0155         recordSize = rs;
0156         RSUpdated  = true;
0157     }
0158 
0159     /**
0160      * @short  Returns the number of fields as suggested by the field descriptor in the header
0161      * @return Number of fields, or zero if the FD hasn't been read yet
0162      */
0163     inline int getFieldCount() const { return (FDUpdated ? nfields : 0); }
0164 
0165     /**
0166      * @short  Returns the header text
0167      * @return QString containing the header text if header has been read, blank QString otherwise
0168      */
0169     inline QString getHeaderText() const { return (preambleUpdated ? headerText : ""); }
0170 
0171     /**
0172      * @short  Returns the offset at which the data begins
0173      * @return The value of dataOffset if indexUpdated, else zero
0174      */
0175     inline long getDataOffset() const { return (indexUpdated ? dataOffset : 0); }
0176 
0177     /**
0178      * @short  Returns the offset at which the index table begins
0179      * @return The value of itableOffset if FDUpdated, else zero
0180      */
0181     inline long getIndexTableOffset() const { return (FDUpdated ? itableOffset : 0); }
0182 
0183     /**
0184      * @short Wrapper around fseek for large offsets
0185      *
0186      * fseek takes a signed long for the offset, since it can be
0187      * either positive or negative. If the argument is a 32-bit
0188      * unsigned integer, fseek will fail in most situations, since
0189      * that will be interpreted as a number of the opposite sign.
0190      * This wrapper circumvents that problem by repeatedly calling
0191      * fseek twice if the offset is too large. Useful when 64-bit
0192      * handling is really not necessary.
0193      *
0194      * @return Zero on success. Else, error code of the first failing fseek call.
0195      * @note Clearly, this method can move forward only. When we need
0196      * one that goes back, we'll implement that.
0197      */
0198     static int unsigned_KDE_fseek(FILE *stream, quint32 offset, int whence);
0199 
0200     /*! @short An enum providing user-friendly names for errors encountered */
0201     enum Errors
0202     {
0203         ERR_NULL,             /*!< No error occurred */
0204         ERR_FILEOPEN,         /*!< File could not be opened */
0205         ERR_FD_TRUNC,         /*!< Field descriptor table is truncated */
0206         ERR_INDEX_TRUNC,      /*!< File ends prematurely, before expected end of index table */
0207         ERR_INDEX_BADID,      /*!< Index table has an invalid ID entry */
0208         ERR_INDEX_IDMISMATCH, /*!< Index table has a mismatched ID entry [ID found in the wrong place] */
0209         ERR_INDEX_BADOFFSET,  /*!< Offset / Record count specified in the Index table is bad */
0210         ERR_BADSEEK           /*!< Premature end of file / bad seek while reading index table */
0211     };
0212 
0213   private:
0214     /**
0215      * @short  Backends for readHeader without cleanup on abort
0216      * @return Return the appropriate error code, or ERR_NULL if successful
0217      */
0218     enum Errors __readHeader();
0219 
0220     /**
0221      * @short  Helper function that clears all field entries in the QVector fields
0222      */
0223     void clearFields();
0224 
0225     /**
0226      * @short  Helper function that sets all variables to their initial values
0227      */
0228     void init();
0229 
0230     /// Handle to the file.
0231     FILE *fileHandle { nullptr};
0232     /// Stores offsets corresponding to each index table entry
0233     QVector<unsigned long> indexOffset;
0234     /// Stores number of records under each index table entry
0235     QVector<unsigned int> indexCount;
0236     /// True if the data from the index, and associated properties have been updated
0237     bool indexUpdated { false };
0238     /// True if the data from the Field Descriptor, and associated properties have been updated
0239     bool FDUpdated { false };
0240     /// True if the recordSize parameter is set correctly, either manually or bye reading the FD
0241     bool RSUpdated { false };
0242     /// True if the data from the preamble and associated properties have been updated
0243     bool preambleUpdated { false };
0244     /// Stores the number corresponding to the previous error
0245     enum Errors errnum { ERR_NULL };
0246     /// True if byteswapping should be done
0247     bool byteswap { false };
0248     /// Stores the size of a record in bytes for quick retrieval
0249     int recordSize { 0 };
0250     /// Maintains a list of fields in the file, along with relevant details
0251     QVector<dataElement *> fields;
0252     /// Stores the file header text
0253     QString headerText;
0254     /// Stores the number of fields as indicated by the field descriptor
0255     qint16 nfields { 0 };
0256     /// Stores the size of the index table in number of entries
0257     quint32 indexSize { 0 };
0258     /// Stores the offset position of the first index table entry
0259     long itableOffset { 0 };
0260     /// Stores the offset position of the start of data
0261     long dataOffset { 0 };
0262     /// Stores the most recent 'unread' error message
0263     QString errorMessage;
0264     /// Stores the total number of records in the file
0265     unsigned long recordCount { 0 };
0266     /// Stores the version number of the file
0267     quint8 versionNumber { 0 };
0268 };