Chris@148: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@148: Chris@148: /* Chris@148: Sonic Visualiser Chris@148: An audio file viewer and annotation editor. Chris@148: Centre for Digital Music, Queen Mary, University of London. Chris@537: This file copyright 2006-2009 Chris Cannam and QMUL. Chris@148: Chris@148: This program is free software; you can redistribute it and/or Chris@148: modify it under the terms of the GNU General Public License as Chris@148: published by the Free Software Foundation; either version 2 of the Chris@148: License, or (at your option) any later version. See the file Chris@148: COPYING included with this distribution for more information. Chris@148: */ Chris@148: Chris@148: #include "MatrixFile.h" Chris@148: #include "base/TempDirectory.h" Chris@150: #include "system/System.h" Chris@148: #include "base/Profiler.h" Chris@148: #include "base/Exceptions.h" Chris@408: #include "base/Thread.h" Chris@148: Chris@148: #include Chris@148: #include Chris@148: #include Chris@148: #include Chris@148: Chris@148: #include Chris@148: Chris@148: #include Chris@148: #include Chris@148: Chris@405: #include Chris@405: Chris@148: #include Chris@148: #include Chris@148: Chris@537: #define DEBUG_MATRIX_FILE 1 Chris@403: //#define DEBUG_MATRIX_FILE_READ_SET 1 Chris@148: Chris@148: #ifdef DEBUG_MATRIX_FILE_READ_SET Chris@153: #ifndef DEBUG_MATRIX_FILE Chris@148: #define DEBUG_MATRIX_FILE 1 Chris@148: #endif Chris@153: #endif Chris@148: Chris@148: std::map MatrixFile::m_refcount; Chris@537: QMutex MatrixFile::m_createMutex; Chris@148: Chris@148: static size_t totalStorage = 0; Chris@148: static size_t totalCount = 0; Chris@537: static size_t openCount = 0; Chris@148: Chris@148: MatrixFile::MatrixFile(QString fileBase, Mode mode, Chris@537: size_t cellSize, size_t width, size_t height) : Chris@148: m_fd(-1), Chris@148: m_mode(mode), Chris@148: m_flags(0), Chris@148: m_fmode(0), Chris@148: m_cellSize(cellSize), Chris@537: m_width(width), Chris@537: m_height(height), Chris@537: m_headerSize(2 * sizeof(size_t)) Chris@148: { Chris@148: Profiler profiler("MatrixFile::MatrixFile", true); Chris@148: Chris@537: #ifdef DEBUG_MATRIX_FILE Chris@537: std::cerr << "MatrixFile::MatrixFile(" << fileBase.toStdString() << ", " << int(mode) << ", " << cellSize << ", " << width << ", " << height << ")" << std::endl; Chris@537: #endif Chris@148: Chris@537: QMutexLocker locker(&m_createMutex); Chris@148: Chris@148: QDir tempDir(TempDirectory::getInstance()->getPath()); Chris@148: QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase))); Chris@148: bool newFile = !QFileInfo(fileName).exists(); Chris@148: Chris@148: if (newFile && m_mode == ReadOnly) { Chris@148: std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode " Chris@148: << "specified, but cache file does not exist" << std::endl; Chris@148: throw FileNotFound(fileName); Chris@148: } Chris@148: Chris@537: if (!newFile && m_mode == WriteOnly) { Chris@537: std::cerr << "ERROR: MatrixFile::MatrixFile: Write-only mode " Chris@537: << "specified, but file already exists" << std::endl; Chris@537: throw FileOperationFailed(fileName, "create"); Chris@148: } Chris@148: Chris@148: m_flags = 0; Chris@148: m_fmode = S_IRUSR | S_IWUSR; Chris@148: Chris@537: if (m_mode == WriteOnly) { Chris@537: m_flags = O_WRONLY | O_CREAT; Chris@148: } else { Chris@148: m_flags = O_RDONLY; Chris@148: } Chris@148: Chris@233: #ifdef _WIN32 Chris@233: m_flags |= O_BINARY; Chris@233: #endif Chris@233: Chris@148: #ifdef DEBUG_MATRIX_FILE Chris@537: std::cerr << "MatrixFile(" << this << ")::MatrixFile: opening " << fileName.toStdString() << "..." << std::endl; Chris@148: #endif Chris@148: Chris@148: if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) { Chris@148: ::perror("Open failed"); Chris@148: std::cerr << "ERROR: MatrixFile::MatrixFile: " Chris@148: << "Failed to open cache file \"" Chris@148: << fileName.toStdString() << "\""; Chris@537: if (m_mode == WriteOnly) std::cerr << " for writing"; Chris@148: std::cerr << std::endl; Chris@148: throw FailedToOpenFile(fileName); Chris@148: } Chris@148: Chris@537: #ifdef DEBUG_MATRIX_FILE Chris@537: std::cerr << "MatrixFile(" << this << ")::MatrixFile: fd is " << m_fd << std::endl; Chris@537: #endif Chris@537: Chris@148: if (newFile) { Chris@537: initialise(); // write header and "unwritten" column tags Chris@148: } else { Chris@148: size_t header[2]; Chris@148: if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) { Chris@236: ::perror("MatrixFile::MatrixFile: read failed"); Chris@148: std::cerr << "ERROR: MatrixFile::MatrixFile: " Chris@148: << "Failed to read header (fd " << m_fd << ", file \"" Chris@148: << fileName.toStdString() << "\")" << std::endl; Chris@148: throw FileReadFailed(fileName); Chris@148: } Chris@537: if (header[0] != m_width || header[1] != m_height) { Chris@537: std::cerr << "ERROR: MatrixFile::MatrixFile: " Chris@537: << "Dimensions in file header (" << header[0] << "x" Chris@537: << header[1] << ") differ from expected dimensions " Chris@537: << m_width << "x" << m_height << std::endl; Chris@537: throw FailedToOpenFile(fileName); Chris@537: } Chris@148: } Chris@148: Chris@148: m_fileName = fileName; Chris@148: ++m_refcount[fileName]; Chris@148: Chris@537: #ifdef DEBUG_MATRIX_FILE Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::MatrixFile: File " << fileName.toStdString() << ", ref " << m_refcount[fileName] << std::endl; Chris@148: Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl; Chris@537: #endif Chris@148: Chris@148: ++totalCount; Chris@537: ++openCount; Chris@148: } Chris@148: Chris@148: MatrixFile::~MatrixFile() Chris@148: { Chris@148: if (m_fd >= 0) { Chris@148: if (::close(m_fd) < 0) { Chris@148: ::perror("MatrixFile::~MatrixFile: close failed"); Chris@148: } Chris@148: } Chris@148: Chris@537: QMutexLocker locker(&m_createMutex); Chris@537: Chris@148: if (m_fileName != "") { Chris@148: Chris@148: if (--m_refcount[m_fileName] == 0) { Chris@148: Chris@148: if (::unlink(m_fileName.toLocal8Bit())) { Chris@537: std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl; Chris@148: } else { Chris@537: std::cerr << "deleted " << m_fileName.toStdString() << std::endl; Chris@148: } Chris@148: } Chris@148: } Chris@537: Chris@537: if (m_mode == WriteOnly) { Chris@537: totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize) + m_width); Chris@537: } Chris@148: totalCount --; Chris@537: openCount --; Chris@148: Chris@537: #ifdef DEBUG_MATRIX_FILE Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::~MatrixFile: " << std::endl; Chris@537: std::cerr << "MatrixFile: Total storage now " << totalStorage/1024 << "K in " << totalCount << " instances (" << openCount << " open)" << std::endl; Chris@537: #endif Chris@148: } Chris@148: Chris@148: void Chris@537: MatrixFile::initialise() Chris@148: { Chris@537: Profiler profiler("MatrixFile::initialise", true); Chris@148: Chris@537: assert(m_mode == WriteOnly); Chris@148: Chris@537: off_t off = m_headerSize + (m_width * m_height * m_cellSize) + m_width; Chris@148: Chris@148: #ifdef DEBUG_MATRIX_FILE Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): cell size " << m_cellSize << ", header size " << m_headerSize << ", resizing file" << std::endl; Chris@148: #endif Chris@148: Chris@537: if (::lseek(m_fd, off - 1, SEEK_SET) < 0) { Chris@537: ::perror("ERROR: MatrixFile::initialise: seek to end failed"); Chris@537: throw FileOperationFailed(m_fileName, "lseek"); Chris@148: } Chris@148: Chris@537: unsigned char byte = 0; Chris@537: if (::write(m_fd, &byte, 1) != 1) { Chris@537: ::perror("ERROR: MatrixFile::initialise: write at end failed"); Chris@537: throw FileOperationFailed(m_fileName, "write"); Chris@537: } Chris@148: Chris@537: if (::lseek(m_fd, 0, SEEK_SET) < 0) { Chris@148: ::perror("ERROR: MatrixFile::resize: Seek to write header failed"); Chris@148: throw FileOperationFailed(m_fileName, "lseek"); Chris@148: } Chris@148: Chris@148: size_t header[2]; Chris@537: header[0] = m_width; Chris@537: header[1] = m_height; Chris@148: if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) { Chris@148: ::perror("ERROR: MatrixFile::resize: Failed to write header"); Chris@148: throw FileOperationFailed(m_fileName, "write"); Chris@148: } Chris@148: Chris@537: if (m_mode == WriteOnly) { Chris@537: totalStorage += (m_headerSize + (m_width * m_height * m_cellSize) + m_width); Chris@148: } Chris@148: Chris@537: #ifdef DEBUG_MATRIX_FILE Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::resize(" << m_width << ", " << m_height << "): storage " Chris@537: << (m_headerSize + m_width * m_height * m_cellSize + m_width) << std::endl; Chris@148: Chris@537: std::cerr << "MatrixFile: Total storage " << totalStorage/1024 << "K" << std::endl; Chris@148: #endif Chris@148: Chris@148: seekTo(0, 0); Chris@148: } Chris@148: Chris@148: void Chris@537: MatrixFile::close() Chris@148: { Chris@537: #ifdef DEBUG_MATRIX_FILE Chris@537: std::cerr << "MatrixFile::close()" << std::endl; Chris@537: #endif Chris@537: if (m_fd >= 0) { Chris@537: if (::close(m_fd) < 0) { Chris@537: ::perror("MatrixFile::close: close failed"); Chris@537: } Chris@537: m_fd = -1; Chris@537: -- openCount; Chris@148: } Chris@148: } Chris@148: Chris@148: void Chris@148: MatrixFile::getColumnAt(size_t x, void *data) Chris@148: { Chris@537: assert(m_mode == ReadOnly); Chris@537: Chris@537: #ifdef DEBUG_MATRIX_FILE_READ_SET Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << ")" << std::endl; Chris@537: #endif Chris@537: Chris@408: Profiler profiler("MatrixFile::getColumnAt"); Chris@148: Chris@537: unsigned char set = 0; Chris@537: if (!seekTo(x, 0)) { Chris@537: std::cerr << "ERROR: MatrixFile::getColumnAt(" << x << "): Seek failed" << std::endl; Chris@537: throw FileOperationFailed(m_fileName, "seek"); Chris@148: } Chris@148: Chris@537: ssize_t r = -1; Chris@537: r = ::read(m_fd, &set, 1); Chris@537: if (r < 0) { Chris@537: ::perror("MatrixFile::getColumnAt: read failed"); Chris@537: throw FileReadFailed(m_fileName); Chris@537: } Chris@537: if (!set) { Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << "): Column has not been set" << std::endl; Chris@537: return; Chris@537: } Chris@537: Chris@537: r = ::read(m_fd, data, m_height * m_cellSize); Chris@537: if (r < 0) { Chris@537: ::perror("MatrixFile::getColumnAt: read failed"); Chris@537: throw FileReadFailed(m_fileName); Chris@537: } Chris@537: } Chris@537: Chris@537: bool Chris@537: MatrixFile::haveSetColumnAt(size_t x) const Chris@537: { Chris@537: assert(m_mode == ReadOnly); Chris@537: Chris@537: Profiler profiler("MatrixFile::haveSetColumnAt"); Chris@537: Chris@537: #ifdef DEBUG_MATRIX_FILE_READ_SET Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::haveSetColumnAt(" << x << ")" << std::endl; Chris@537: // std::cerr << "."; Chris@148: #endif Chris@148: Chris@537: unsigned char set = 0; Chris@537: if (!seekTo(x, 0)) { Chris@537: std::cerr << "ERROR: MatrixFile::haveSetColumnAt(" << x << "): Seek failed" << std::endl; Chris@537: throw FileOperationFailed(m_fileName, "seek"); Chris@537: } Chris@148: Chris@537: ssize_t r = -1; Chris@537: r = ::read(m_fd, &set, 1); Chris@148: if (r < 0) { Chris@537: ::perror("MatrixFile::haveSetColumnAt: read failed"); Chris@148: throw FileReadFailed(m_fileName); Chris@148: } Chris@148: Chris@537: return set; Chris@148: } Chris@148: Chris@148: void Chris@148: MatrixFile::setColumnAt(size_t x, const void *data) Chris@148: { Chris@537: assert(m_mode == WriteOnly); Chris@148: Chris@148: #ifdef DEBUG_MATRIX_FILE_READ_SET Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << ")" << std::endl; Chris@537: // std::cerr << "."; Chris@148: #endif Chris@148: Chris@148: ssize_t w = 0; Chris@148: Chris@537: if (!seekTo(x, 0)) { Chris@537: std::cerr << "ERROR: MatrixFile::setColumnAt(" << x << "): Seek failed" << std::endl; Chris@537: throw FileOperationFailed(m_fileName, "seek"); Chris@148: } Chris@148: Chris@537: unsigned char set = 0; Chris@537: w = ::write(m_fd, &set, 1); Chris@537: if (w != 1) { Chris@537: ::perror("WARNING: MatrixFile::setColumnAt: write failed (1)"); Chris@148: throw FileOperationFailed(m_fileName, "write"); Chris@537: } Chris@537: Chris@537: w = ::write(m_fd, data, m_height * m_cellSize); Chris@537: if (w != ssize_t(m_height * m_cellSize)) { Chris@537: ::perror("WARNING: MatrixFile::setColumnAt: write failed (2)"); Chris@537: throw FileOperationFailed(m_fileName, "write"); Chris@537: } Chris@537: /* Chris@537: if (x == 0) { Chris@537: std::cerr << "Wrote " << m_height * m_cellSize << " bytes, as follows:" << std::endl; Chris@537: for (int i = 0; i < m_height * m_cellSize; ++i) { Chris@537: std::cerr << (int)(((char *)data)[i]) << " "; Chris@537: } Chris@537: std::cerr << std::endl; Chris@537: } Chris@537: */ Chris@537: if (!seekTo(x, 0)) { Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Seek failed" << std::endl; Chris@148: throw FileOperationFailed(m_fileName, "seek"); Chris@537: } Chris@537: Chris@537: set = 1; Chris@537: w = ::write(m_fd, &set, 1); Chris@537: if (w != 1) { Chris@537: ::perror("WARNING: MatrixFile::setColumnAt: write failed (3)"); Chris@537: throw FileOperationFailed(m_fileName, "write"); Chris@148: } Chris@148: } Chris@148: Chris@537: bool Chris@537: MatrixFile::seekTo(size_t x, size_t y) const Chris@148: { Chris@537: if (m_fd < 0) { Chris@537: std::cerr << "ERROR: MatrixFile::seekTo: File not open" << std::endl; Chris@537: return false; Chris@148: } Chris@148: Chris@537: off_t off = m_headerSize + x * m_height * m_cellSize + x + y * m_cellSize; Chris@148: Chris@148: #ifdef DEBUG_MATRIX_FILE_READ_SET Chris@537: std::cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "," << y << "): off = " << off << std::endl; Chris@148: #endif Chris@148: Chris@148: if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) { Chris@148: ::perror("Seek failed"); Chris@148: std::cerr << "ERROR: MatrixFile::seekTo(" << x << ", " << y Chris@537: << ") = " << off << " failed" << std::endl; Chris@148: return false; Chris@148: } Chris@148: Chris@148: return true; Chris@148: } Chris@148: