svcore  1.9
MatrixFile.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006     Centre for Digital Music, Queen Mary, University of London.
00007     This file copyright 2006-2009 Chris Cannam and QMUL.
00008     
00009     This program is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU General Public License as
00011     published by the Free Software Foundation; either version 2 of the
00012     License, or (at your option) any later version.  See the file
00013     COPYING included with this distribution for more information.
00014 */
00015 
00016 #include "MatrixFile.h"
00017 #include "base/TempDirectory.h"
00018 #include "system/System.h"
00019 #include "base/Profiler.h"
00020 #include "base/Exceptions.h"
00021 #include "base/Thread.h"
00022 
00023 #include <sys/types.h>
00024 #include <sys/stat.h>
00025 #include <fcntl.h>
00026 #include <unistd.h>
00027 
00028 #include <iostream>
00029 
00030 #include <cstdio>
00031 #include <cassert>
00032 
00033 #include <cstdlib>
00034 
00035 #include <QFileInfo>
00036 #include <QDir>
00037 
00038 //#define DEBUG_MATRIX_FILE 1
00039 //#define DEBUG_MATRIX_FILE_READ_SET 1
00040 
00041 #ifdef DEBUG_MATRIX_FILE_READ_SET
00042 #ifndef DEBUG_MATRIX_FILE
00043 #define DEBUG_MATRIX_FILE 1
00044 #endif
00045 #endif
00046 
00047 std::map<QString, int> MatrixFile::m_refcount;
00048 QMutex MatrixFile::m_createMutex;
00049 
00050 static size_t totalStorage = 0;
00051 static size_t totalCount = 0;
00052 static size_t openCount = 0;
00053 
00054 MatrixFile::MatrixFile(QString fileBase, Mode mode,
00055                        int cellSize, int width, int height) :
00056     m_fd(-1),
00057     m_mode(mode),
00058     m_flags(0),
00059     m_fmode(0),
00060     m_cellSize(cellSize),
00061     m_width(width),
00062     m_height(height),
00063     m_headerSize(2 * sizeof(int)),
00064     m_setColumns(0),
00065     m_autoClose(false),
00066     m_readyToReadColumn(-1)
00067 {
00068     Profiler profiler("MatrixFile::MatrixFile", true);
00069 
00070 #ifdef DEBUG_MATRIX_FILE
00071     SVDEBUG << "MatrixFile::MatrixFile(" << fileBase << ", " << int(mode) << ", " << cellSize << ", " << width << ", " << height << ")" << endl;
00072 #endif
00073 
00074     m_createMutex.lock();
00075 
00076     QDir tempDir(TempDirectory::getInstance()->getPath());
00077     QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
00078     bool newFile = !QFileInfo(fileName).exists();
00079 
00080     if (newFile && m_mode == ReadOnly) {
00081         cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode "
00082                   << "specified, but cache file does not exist" << endl;
00083         throw FileNotFound(fileName);
00084     }
00085 
00086     if (!newFile && m_mode == WriteOnly) {
00087         cerr << "ERROR: MatrixFile::MatrixFile: Write-only mode "
00088                   << "specified, but file already exists" << endl;
00089         throw FileOperationFailed(fileName, "create");
00090     }
00091 
00092     m_flags = 0;
00093     m_fmode = S_IRUSR | S_IWUSR;
00094 
00095     if (m_mode == WriteOnly) {
00096         m_flags = O_WRONLY | O_CREAT;
00097     } else {
00098         m_flags = O_RDONLY;
00099     }
00100 
00101 #ifdef _WIN32
00102     m_flags |= O_BINARY;
00103 #endif
00104 
00105 #ifdef DEBUG_MATRIX_FILE
00106     cerr << "MatrixFile(" << this << ")::MatrixFile: opening " << fileName << "..." << endl;
00107 #endif
00108 
00109     if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) {
00110         ::perror("Open failed");
00111         cerr << "ERROR: MatrixFile::MatrixFile: "
00112                   << "Failed to open cache file \""
00113                   << fileName << "\"";
00114         if (m_mode == WriteOnly) cerr << " for writing";
00115         cerr << endl;
00116         throw FailedToOpenFile(fileName);
00117     }
00118 
00119     m_createMutex.unlock();
00120 
00121 #ifdef DEBUG_MATRIX_FILE
00122     cerr << "MatrixFile(" << this << ")::MatrixFile: fd is " << m_fd << endl;
00123 #endif
00124 
00125     if (newFile) {
00126         initialise(); // write header and "unwritten" column tags
00127     } else {
00128         int header[2];
00129         if (::read(m_fd, header, 2 * sizeof(int)) < 0) {
00130             ::perror("MatrixFile::MatrixFile: read failed");
00131             cerr << "ERROR: MatrixFile::MatrixFile: "
00132                       << "Failed to read header (fd " << m_fd << ", file \""
00133                       << fileName << "\")" << endl;
00134             throw FileReadFailed(fileName);
00135         }
00136         if (header[0] != m_width || header[1] != m_height) {
00137             cerr << "ERROR: MatrixFile::MatrixFile: "
00138                       << "Dimensions in file header (" << header[0] << "x"
00139                       << header[1] << ") differ from expected dimensions "
00140                       << m_width << "x" << m_height << endl;
00141             throw FailedToOpenFile(fileName);
00142         }
00143     }
00144 
00145     m_fileName = fileName;
00146     ++m_refcount[fileName];
00147 
00148 #ifdef DEBUG_MATRIX_FILE
00149     cerr << "MatrixFile[" << m_fd << "]::MatrixFile: File " << fileName << ", ref " << m_refcount[fileName] << endl;
00150 
00151     cerr << "MatrixFile[" << m_fd << "]::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << endl;
00152 #endif
00153 
00154     ++totalCount;
00155     ++openCount;
00156 }
00157 
00158 MatrixFile::~MatrixFile()
00159 {
00160     if (m_fd >= 0) {
00161         if (::close(m_fd) < 0) {
00162             ::perror("MatrixFile::~MatrixFile: close failed");
00163         }
00164         openCount --;
00165     }
00166 
00167     QMutexLocker locker(&m_createMutex);
00168 
00169     delete m_setColumns;
00170 
00171     if (m_fileName != "") {
00172 
00173         if (--m_refcount[m_fileName] == 0) {
00174 
00175             if (::unlink(m_fileName.toLocal8Bit())) {
00176                 cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName << "\"" << endl;
00177             } else {
00178                 cerr << "deleted " << m_fileName << endl;
00179             }
00180         }
00181     }
00182 
00183     if (m_mode == WriteOnly) {
00184         totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize) + m_width);
00185     }
00186     totalCount --;
00187 
00188 #ifdef DEBUG_MATRIX_FILE
00189     cerr << "MatrixFile[" << m_fd << "]::~MatrixFile: " << endl;
00190     cerr << "MatrixFile: Total storage now " << totalStorage/1024 << "K in " << totalCount << " instances (" << openCount << " open)" << endl;
00191 #endif
00192 }
00193 
00194 void
00195 MatrixFile::initialise()
00196 {
00197     Profiler profiler("MatrixFile::initialise", true);
00198 
00199     assert(m_mode == WriteOnly);
00200 
00201     m_setColumns = new ResizeableBitset(m_width);
00202     
00203     off_t off = m_headerSize + (m_width * m_height * m_cellSize) + m_width;
00204 
00205 #ifdef DEBUG_MATRIX_FILE
00206     cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): cell size " << m_cellSize << ", header size " << m_headerSize << ", resizing file" << endl;
00207 #endif
00208 
00209     if (::lseek(m_fd, off - 1, SEEK_SET) < 0) {
00210         ::perror("ERROR: MatrixFile::initialise: seek to end failed");
00211         throw FileOperationFailed(m_fileName, "lseek");
00212     }
00213 
00214     unsigned char byte = 0;
00215     if (::write(m_fd, &byte, 1) != 1) {
00216         ::perror("ERROR: MatrixFile::initialise: write at end failed");
00217         throw FileOperationFailed(m_fileName, "write");
00218     }
00219 
00220     if (::lseek(m_fd, 0, SEEK_SET) < 0) {
00221         ::perror("ERROR: MatrixFile::initialise: Seek to write header failed");
00222         throw FileOperationFailed(m_fileName, "lseek");
00223     }
00224 
00225     int header[2];
00226     header[0] = m_width;
00227     header[1] = m_height;
00228     if (::write(m_fd, header, 2 * sizeof(int)) != 2 * sizeof(int)) {
00229         ::perror("ERROR: MatrixFile::initialise: Failed to write header");
00230         throw FileOperationFailed(m_fileName, "write");
00231     }
00232 
00233     if (m_mode == WriteOnly) {
00234         totalStorage += (m_headerSize + (m_width * m_height * m_cellSize) + m_width);
00235     }
00236 
00237 #ifdef DEBUG_MATRIX_FILE
00238     cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): storage "
00239               << (m_headerSize + m_width * m_height * m_cellSize + m_width) << endl;
00240 
00241     cerr << "MatrixFile: Total storage " << totalStorage/1024 << "K" << endl;
00242 #endif
00243 
00244     seekTo(0);
00245 }
00246 
00247 void
00248 MatrixFile::close()
00249 {
00250 #ifdef DEBUG_MATRIX_FILE
00251     SVDEBUG << "MatrixFile::close()" << endl;
00252 #endif
00253     if (m_fd >= 0) {
00254         if (::close(m_fd) < 0) {
00255             ::perror("MatrixFile::close: close failed");
00256         }
00257         m_fd = -1;
00258         -- openCount;
00259 #ifdef DEBUG_MATRIX_FILE
00260         cerr << "MatrixFile: Now " << openCount << " open instances" << endl;
00261 #endif
00262     }
00263 }
00264 
00265 void
00266 MatrixFile::getColumnAt(int x, void *data)
00267 {
00268     assert(m_mode == ReadOnly);
00269     
00270 #ifdef DEBUG_MATRIX_FILE_READ_SET
00271     cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << ")" << endl;
00272 #endif
00273 
00274     Profiler profiler("MatrixFile::getColumnAt");
00275 
00276     ssize_t r = -1;
00277 
00278     if (m_readyToReadColumn < 0 ||
00279         m_readyToReadColumn != x) {
00280 
00281         unsigned char set = 0;
00282         if (!seekTo(x)) {
00283             cerr << "ERROR: MatrixFile::getColumnAt(" << x << "): Seek failed" << endl;
00284             throw FileOperationFailed(m_fileName, "seek");
00285         }
00286 
00287         r = ::read(m_fd, &set, 1);
00288         if (r < 0) {
00289             ::perror("MatrixFile::getColumnAt: read failed");
00290             throw FileReadFailed(m_fileName);
00291         }
00292         if (!set) {
00293             cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << "): Column has not been set" << endl;
00294             return;
00295         }
00296     }
00297 
00298     r = ::read(m_fd, data, m_height * m_cellSize);
00299     if (r < 0) {
00300         ::perror("MatrixFile::getColumnAt: read failed");
00301         throw FileReadFailed(m_fileName);
00302     }
00303 }
00304 
00305 bool
00306 MatrixFile::haveSetColumnAt(int x) const
00307 {
00308     if (m_mode == WriteOnly) {
00309         return m_setColumns->get(x);
00310     }
00311 
00312     if (m_readyToReadColumn >= 0 &&
00313         int(m_readyToReadColumn) == x) return true;
00314     
00315     Profiler profiler("MatrixFile::haveSetColumnAt");
00316 
00317 #ifdef DEBUG_MATRIX_FILE_READ_SET
00318     cerr << "MatrixFile[" << m_fd << "]::haveSetColumnAt(" << x << ")" << endl;
00319 //    cerr << ".";
00320 #endif
00321 
00322     unsigned char set = 0;
00323     if (!seekTo(x)) {
00324         cerr << "ERROR: MatrixFile::haveSetColumnAt(" << x << "): Seek failed" << endl;
00325         throw FileOperationFailed(m_fileName, "seek");
00326     }
00327 
00328     ssize_t r = -1;
00329     r = ::read(m_fd, &set, 1);
00330     if (r < 0) {
00331         ::perror("MatrixFile::haveSetColumnAt: read failed");
00332         throw FileReadFailed(m_fileName);
00333     }
00334 
00335     if (set) m_readyToReadColumn = int(x);
00336 
00337     return set;
00338 }
00339 
00340 void
00341 MatrixFile::setColumnAt(int x, const void *data)
00342 {
00343     assert(m_mode == WriteOnly);
00344     if (m_fd < 0) return; // closed
00345 
00346 #ifdef DEBUG_MATRIX_FILE_READ_SET
00347     cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << ")" << endl;
00348 //    cerr << ".";
00349 #endif
00350 
00351     ssize_t w = 0;
00352 
00353     if (!seekTo(x)) {
00354         cerr << "ERROR: MatrixFile::setColumnAt(" << x << "): Seek failed" << endl;
00355         throw FileOperationFailed(m_fileName, "seek");
00356     }
00357 
00358     unsigned char set = 0;
00359     w = ::write(m_fd, &set, 1);
00360     if (w != 1) {
00361         ::perror("WARNING: MatrixFile::setColumnAt: write failed (1)");
00362         throw FileOperationFailed(m_fileName, "write");
00363     }
00364 
00365     w = ::write(m_fd, data, m_height * m_cellSize);
00366     if (w != ssize_t(m_height * m_cellSize)) {
00367         ::perror("WARNING: MatrixFile::setColumnAt: write failed (2)");
00368         throw FileOperationFailed(m_fileName, "write");
00369     }
00370 /*
00371     if (x == 0) {
00372         cerr << "Wrote " << m_height * m_cellSize << " bytes, as follows:" << endl;
00373         for (int i = 0; i < m_height * m_cellSize; ++i) {
00374             cerr << (int)(((char *)data)[i]) << " ";
00375         }
00376         cerr << endl;
00377     }
00378 */
00379     if (!seekTo(x)) {
00380         cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Seek failed" << endl;
00381         throw FileOperationFailed(m_fileName, "seek");
00382     }
00383 
00384     set = 1;
00385     w = ::write(m_fd, &set, 1);
00386     if (w != 1) {
00387         ::perror("WARNING: MatrixFile::setColumnAt: write failed (3)");
00388         throw FileOperationFailed(m_fileName, "write");
00389     }
00390 
00391     m_setColumns->set(x);
00392     if (m_autoClose) {
00393         if (m_setColumns->isAllOn()) {
00394 #ifdef DEBUG_MATRIX_FILE
00395             cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): All columns set: auto-closing" << endl;
00396 #endif
00397             close();
00398 /*
00399         } else {
00400             int set = 0;
00401             for (int i = 0; i < m_width; ++i) {
00402                 if (m_setColumns->get(i)) ++set;
00403             }
00404             cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Auto-close on, but not all columns set yet (" << set << " of " << m_width << ")" << endl;
00405 */
00406         }
00407     }
00408 }
00409 
00410 bool
00411 MatrixFile::seekTo(int x) const
00412 {
00413     if (m_fd < 0) {
00414         cerr << "ERROR: MatrixFile::seekTo: File not open" << endl;
00415         return false;
00416     }
00417 
00418     m_readyToReadColumn = -1; // not ready, unless this is subsequently re-set
00419 
00420     off_t off = m_headerSize + x * m_height * m_cellSize + x;
00421 
00422 #ifdef DEBUG_MATRIX_FILE_READ_SET
00423     if (m_mode == ReadOnly) {
00424         cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "): off = " << off << endl;
00425     }
00426 #endif
00427 
00428 #ifdef DEBUG_MATRIX_FILE_READ_SET
00429     cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "): off = " << off << endl;
00430 #endif
00431 
00432     if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
00433         ::perror("Seek failed");
00434         cerr << "ERROR: MatrixFile::seekTo(" << x 
00435                   << ") = " << off << " failed" << endl;
00436         return false;
00437     }
00438 
00439     return true;
00440 }
00441