annotate data/fileio/MatrixFile.cpp @ 537:3cc4b7cd2aa5

* Merge from one-fftdataserver-per-fftmodel branch. This bit of reworking (which is not described very accurately by the title of the branch) turns the MatrixFile object into something that either reads or writes, but not both, and separates the FFT file cache reader and writer implementations separately. This allows the FFT data server to have a single thread owning writers and one reader per "customer" thread, and for all locking to be vastly simplified and concentrated in the data server alone (because none of the classes it makes use of is used in more than one thread at a time). The result is faster and more trustworthy code.
author Chris Cannam
date Tue, 27 Jan 2009 13:25:10 +0000
parents 3e0f1f7bec85
children 95391b480e17
rev   line source
Chris@148 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@148 2
Chris@148 3 /*
Chris@148 4 Sonic Visualiser
Chris@148 5 An audio file viewer and annotation editor.
Chris@148 6 Centre for Digital Music, Queen Mary, University of London.
Chris@537 7 This file copyright 2006-2009 Chris Cannam and QMUL.
Chris@148 8
Chris@148 9 This program is free software; you can redistribute it and/or
Chris@148 10 modify it under the terms of the GNU General Public License as
Chris@148 11 published by the Free Software Foundation; either version 2 of the
Chris@148 12 License, or (at your option) any later version. See the file
Chris@148 13 COPYING included with this distribution for more information.
Chris@148 14 */
Chris@148 15
Chris@148 16 #include "MatrixFile.h"
Chris@148 17 #include "base/TempDirectory.h"
Chris@150 18 #include "system/System.h"
Chris@148 19 #include "base/Profiler.h"
Chris@148 20 #include "base/Exceptions.h"
Chris@408 21 #include "base/Thread.h"
Chris@148 22
Chris@148 23 #include <sys/types.h>
Chris@148 24 #include <sys/stat.h>
Chris@148 25 #include <fcntl.h>
Chris@148 26 #include <unistd.h>
Chris@148 27
Chris@148 28 #include <iostream>
Chris@148 29
Chris@148 30 #include <cstdio>
Chris@148 31 #include <cassert>
Chris@148 32
Chris@405 33 #include <cstdlib>
Chris@405 34
Chris@148 35 #include <QFileInfo>
Chris@148 36 #include <QDir>
Chris@148 37
Chris@537 38 #define DEBUG_MATRIX_FILE 1
Chris@403 39 //#define DEBUG_MATRIX_FILE_READ_SET 1
Chris@148 40
Chris@148 41 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@153 42 #ifndef DEBUG_MATRIX_FILE
Chris@148 43 #define DEBUG_MATRIX_FILE 1
Chris@148 44 #endif
Chris@153 45 #endif
Chris@148 46
Chris@148 47 std::map<QString, int> MatrixFile::m_refcount;
Chris@537 48 QMutex MatrixFile::m_createMutex;
Chris@148 49
Chris@148 50 static size_t totalStorage = 0;
Chris@148 51 static size_t totalCount = 0;
Chris@537 52 static size_t openCount = 0;
Chris@148 53
Chris@148 54 MatrixFile::MatrixFile(QString fileBase, Mode mode,
Chris@537 55 size_t cellSize, size_t width, size_t height) :
Chris@148 56 m_fd(-1),
Chris@148 57 m_mode(mode),
Chris@148 58 m_flags(0),
Chris@148 59 m_fmode(0),
Chris@148 60 m_cellSize(cellSize),
Chris@537 61 m_width(width),
Chris@537 62 m_height(height),
Chris@537 63 m_headerSize(2 * sizeof(size_t))
Chris@148 64 {
Chris@148 65 Profiler profiler("MatrixFile::MatrixFile", true);
Chris@148 66
Chris@537 67 #ifdef DEBUG_MATRIX_FILE
Chris@537 68 std::cerr << "MatrixFile::MatrixFile(" << fileBase.toStdString() << ", " << int(mode) << ", " << cellSize << ", " << width << ", " << height << ")" << std::endl;
Chris@537 69 #endif
Chris@148 70
Chris@537 71 QMutexLocker locker(&m_createMutex);
Chris@148 72
Chris@148 73 QDir tempDir(TempDirectory::getInstance()->getPath());
Chris@148 74 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
Chris@148 75 bool newFile = !QFileInfo(fileName).exists();
Chris@148 76
Chris@148 77 if (newFile && m_mode == ReadOnly) {
Chris@148 78 std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode "
Chris@148 79 << "specified, but cache file does not exist" << std::endl;
Chris@148 80 throw FileNotFound(fileName);
Chris@148 81 }
Chris@148 82
Chris@537 83 if (!newFile && m_mode == WriteOnly) {
Chris@537 84 std::cerr << "ERROR: MatrixFile::MatrixFile: Write-only mode "
Chris@537 85 << "specified, but file already exists" << std::endl;
Chris@537 86 throw FileOperationFailed(fileName, "create");
Chris@148 87 }
Chris@148 88
Chris@148 89 m_flags = 0;
Chris@148 90 m_fmode = S_IRUSR | S_IWUSR;
Chris@148 91
Chris@537 92 if (m_mode == WriteOnly) {
Chris@537 93 m_flags = O_WRONLY | O_CREAT;
Chris@148 94 } else {
Chris@148 95 m_flags = O_RDONLY;
Chris@148 96 }
Chris@148 97
Chris@233 98 #ifdef _WIN32
Chris@233 99 m_flags |= O_BINARY;
Chris@233 100 #endif
Chris@233 101
Chris@148 102 #ifdef DEBUG_MATRIX_FILE
Chris@537 103 std::cerr << "MatrixFile(" << this << ")::MatrixFile: opening " << fileName.toStdString() << "..." << std::endl;
Chris@148 104 #endif
Chris@148 105
Chris@148 106 if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) {
Chris@148 107 ::perror("Open failed");
Chris@148 108 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@148 109 << "Failed to open cache file \""
Chris@148 110 << fileName.toStdString() << "\"";
Chris@537 111 if (m_mode == WriteOnly) std::cerr << " for writing";
Chris@148 112 std::cerr << std::endl;
Chris@148 113 throw FailedToOpenFile(fileName);
Chris@148 114 }
Chris@148 115
Chris@537 116 #ifdef DEBUG_MATRIX_FILE
Chris@537 117 std::cerr << "MatrixFile(" << this << ")::MatrixFile: fd is " << m_fd << std::endl;
Chris@537 118 #endif
Chris@537 119
Chris@148 120 if (newFile) {
Chris@537 121 initialise(); // write header and "unwritten" column tags
Chris@148 122 } else {
Chris@148 123 size_t header[2];
Chris@148 124 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
Chris@236 125 ::perror("MatrixFile::MatrixFile: read failed");
Chris@148 126 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@148 127 << "Failed to read header (fd " << m_fd << ", file \""
Chris@148 128 << fileName.toStdString() << "\")" << std::endl;
Chris@148 129 throw FileReadFailed(fileName);
Chris@148 130 }
Chris@537 131 if (header[0] != m_width || header[1] != m_height) {
Chris@537 132 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@537 133 << "Dimensions in file header (" << header[0] << "x"
Chris@537 134 << header[1] << ") differ from expected dimensions "
Chris@537 135 << m_width << "x" << m_height << std::endl;
Chris@537 136 throw FailedToOpenFile(fileName);
Chris@537 137 }
Chris@148 138 }
Chris@148 139
Chris@148 140 m_fileName = fileName;
Chris@148 141 ++m_refcount[fileName];
Chris@148 142
Chris@537 143 #ifdef DEBUG_MATRIX_FILE
Chris@537 144 std::cerr << "MatrixFile[" << m_fd << "]::MatrixFile: File " << fileName.toStdString() << ", ref " << m_refcount[fileName] << std::endl;
Chris@148 145
Chris@537 146 std::cerr << "MatrixFile[" << m_fd << "]::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
Chris@537 147 #endif
Chris@148 148
Chris@148 149 ++totalCount;
Chris@537 150 ++openCount;
Chris@148 151 }
Chris@148 152
Chris@148 153 MatrixFile::~MatrixFile()
Chris@148 154 {
Chris@148 155 if (m_fd >= 0) {
Chris@148 156 if (::close(m_fd) < 0) {
Chris@148 157 ::perror("MatrixFile::~MatrixFile: close failed");
Chris@148 158 }
Chris@148 159 }
Chris@148 160
Chris@537 161 QMutexLocker locker(&m_createMutex);
Chris@537 162
Chris@148 163 if (m_fileName != "") {
Chris@148 164
Chris@148 165 if (--m_refcount[m_fileName] == 0) {
Chris@148 166
Chris@148 167 if (::unlink(m_fileName.toLocal8Bit())) {
Chris@537 168 std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
Chris@148 169 } else {
Chris@537 170 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
Chris@148 171 }
Chris@148 172 }
Chris@148 173 }
Chris@537 174
Chris@537 175 if (m_mode == WriteOnly) {
Chris@537 176 totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize) + m_width);
Chris@537 177 }
Chris@148 178 totalCount --;
Chris@537 179 openCount --;
Chris@148 180
Chris@537 181 #ifdef DEBUG_MATRIX_FILE
Chris@537 182 std::cerr << "MatrixFile[" << m_fd << "]::~MatrixFile: " << std::endl;
Chris@537 183 std::cerr << "MatrixFile: Total storage now " << totalStorage/1024 << "K in " << totalCount << " instances (" << openCount << " open)" << std::endl;
Chris@537 184 #endif
Chris@148 185 }
Chris@148 186
Chris@148 187 void
Chris@537 188 MatrixFile::initialise()
Chris@148 189 {
Chris@537 190 Profiler profiler("MatrixFile::initialise", true);
Chris@148 191
Chris@537 192 assert(m_mode == WriteOnly);
Chris@148 193
Chris@537 194 off_t off = m_headerSize + (m_width * m_height * m_cellSize) + m_width;
Chris@148 195
Chris@148 196 #ifdef DEBUG_MATRIX_FILE
Chris@537 197 std::cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): cell size " << m_cellSize << ", header size " << m_headerSize << ", resizing file" << std::endl;
Chris@148 198 #endif
Chris@148 199
Chris@537 200 if (::lseek(m_fd, off - 1, SEEK_SET) < 0) {
Chris@537 201 ::perror("ERROR: MatrixFile::initialise: seek to end failed");
Chris@537 202 throw FileOperationFailed(m_fileName, "lseek");
Chris@148 203 }
Chris@148 204
Chris@537 205 unsigned char byte = 0;
Chris@537 206 if (::write(m_fd, &byte, 1) != 1) {
Chris@537 207 ::perror("ERROR: MatrixFile::initialise: write at end failed");
Chris@537 208 throw FileOperationFailed(m_fileName, "write");
Chris@537 209 }
Chris@148 210
Chris@537 211 if (::lseek(m_fd, 0, SEEK_SET) < 0) {
Chris@148 212 ::perror("ERROR: MatrixFile::resize: Seek to write header failed");
Chris@148 213 throw FileOperationFailed(m_fileName, "lseek");
Chris@148 214 }
Chris@148 215
Chris@148 216 size_t header[2];
Chris@537 217 header[0] = m_width;
Chris@537 218 header[1] = m_height;
Chris@148 219 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
Chris@148 220 ::perror("ERROR: MatrixFile::resize: Failed to write header");
Chris@148 221 throw FileOperationFailed(m_fileName, "write");
Chris@148 222 }
Chris@148 223
Chris@537 224 if (m_mode == WriteOnly) {
Chris@537 225 totalStorage += (m_headerSize + (m_width * m_height * m_cellSize) + m_width);
Chris@148 226 }
Chris@148 227
Chris@537 228 #ifdef DEBUG_MATRIX_FILE
Chris@537 229 std::cerr << "MatrixFile[" << m_fd << "]::resize(" << m_width << ", " << m_height << "): storage "
Chris@537 230 << (m_headerSize + m_width * m_height * m_cellSize + m_width) << std::endl;
Chris@148 231
Chris@537 232 std::cerr << "MatrixFile: Total storage " << totalStorage/1024 << "K" << std::endl;
Chris@148 233 #endif
Chris@148 234
Chris@148 235 seekTo(0, 0);
Chris@148 236 }
Chris@148 237
Chris@148 238 void
Chris@537 239 MatrixFile::close()
Chris@148 240 {
Chris@537 241 #ifdef DEBUG_MATRIX_FILE
Chris@537 242 std::cerr << "MatrixFile::close()" << std::endl;
Chris@537 243 #endif
Chris@537 244 if (m_fd >= 0) {
Chris@537 245 if (::close(m_fd) < 0) {
Chris@537 246 ::perror("MatrixFile::close: close failed");
Chris@537 247 }
Chris@537 248 m_fd = -1;
Chris@537 249 -- openCount;
Chris@148 250 }
Chris@148 251 }
Chris@148 252
Chris@148 253 void
Chris@148 254 MatrixFile::getColumnAt(size_t x, void *data)
Chris@148 255 {
Chris@537 256 assert(m_mode == ReadOnly);
Chris@537 257
Chris@537 258 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 259 std::cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << ")" << std::endl;
Chris@537 260 #endif
Chris@537 261
Chris@408 262 Profiler profiler("MatrixFile::getColumnAt");
Chris@148 263
Chris@537 264 unsigned char set = 0;
Chris@537 265 if (!seekTo(x, 0)) {
Chris@537 266 std::cerr << "ERROR: MatrixFile::getColumnAt(" << x << "): Seek failed" << std::endl;
Chris@537 267 throw FileOperationFailed(m_fileName, "seek");
Chris@148 268 }
Chris@148 269
Chris@537 270 ssize_t r = -1;
Chris@537 271 r = ::read(m_fd, &set, 1);
Chris@537 272 if (r < 0) {
Chris@537 273 ::perror("MatrixFile::getColumnAt: read failed");
Chris@537 274 throw FileReadFailed(m_fileName);
Chris@537 275 }
Chris@537 276 if (!set) {
Chris@537 277 std::cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << "): Column has not been set" << std::endl;
Chris@537 278 return;
Chris@537 279 }
Chris@537 280
Chris@537 281 r = ::read(m_fd, data, m_height * m_cellSize);
Chris@537 282 if (r < 0) {
Chris@537 283 ::perror("MatrixFile::getColumnAt: read failed");
Chris@537 284 throw FileReadFailed(m_fileName);
Chris@537 285 }
Chris@537 286 }
Chris@537 287
Chris@537 288 bool
Chris@537 289 MatrixFile::haveSetColumnAt(size_t x) const
Chris@537 290 {
Chris@537 291 assert(m_mode == ReadOnly);
Chris@537 292
Chris@537 293 Profiler profiler("MatrixFile::haveSetColumnAt");
Chris@537 294
Chris@537 295 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 296 std::cerr << "MatrixFile[" << m_fd << "]::haveSetColumnAt(" << x << ")" << std::endl;
Chris@537 297 // std::cerr << ".";
Chris@148 298 #endif
Chris@148 299
Chris@537 300 unsigned char set = 0;
Chris@537 301 if (!seekTo(x, 0)) {
Chris@537 302 std::cerr << "ERROR: MatrixFile::haveSetColumnAt(" << x << "): Seek failed" << std::endl;
Chris@537 303 throw FileOperationFailed(m_fileName, "seek");
Chris@537 304 }
Chris@148 305
Chris@537 306 ssize_t r = -1;
Chris@537 307 r = ::read(m_fd, &set, 1);
Chris@148 308 if (r < 0) {
Chris@537 309 ::perror("MatrixFile::haveSetColumnAt: read failed");
Chris@148 310 throw FileReadFailed(m_fileName);
Chris@148 311 }
Chris@148 312
Chris@537 313 return set;
Chris@148 314 }
Chris@148 315
Chris@148 316 void
Chris@148 317 MatrixFile::setColumnAt(size_t x, const void *data)
Chris@148 318 {
Chris@537 319 assert(m_mode == WriteOnly);
Chris@148 320
Chris@148 321 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 322 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << ")" << std::endl;
Chris@537 323 // std::cerr << ".";
Chris@148 324 #endif
Chris@148 325
Chris@148 326 ssize_t w = 0;
Chris@148 327
Chris@537 328 if (!seekTo(x, 0)) {
Chris@537 329 std::cerr << "ERROR: MatrixFile::setColumnAt(" << x << "): Seek failed" << std::endl;
Chris@537 330 throw FileOperationFailed(m_fileName, "seek");
Chris@148 331 }
Chris@148 332
Chris@537 333 unsigned char set = 0;
Chris@537 334 w = ::write(m_fd, &set, 1);
Chris@537 335 if (w != 1) {
Chris@537 336 ::perror("WARNING: MatrixFile::setColumnAt: write failed (1)");
Chris@148 337 throw FileOperationFailed(m_fileName, "write");
Chris@537 338 }
Chris@537 339
Chris@537 340 w = ::write(m_fd, data, m_height * m_cellSize);
Chris@537 341 if (w != ssize_t(m_height * m_cellSize)) {
Chris@537 342 ::perror("WARNING: MatrixFile::setColumnAt: write failed (2)");
Chris@537 343 throw FileOperationFailed(m_fileName, "write");
Chris@537 344 }
Chris@537 345 /*
Chris@537 346 if (x == 0) {
Chris@537 347 std::cerr << "Wrote " << m_height * m_cellSize << " bytes, as follows:" << std::endl;
Chris@537 348 for (int i = 0; i < m_height * m_cellSize; ++i) {
Chris@537 349 std::cerr << (int)(((char *)data)[i]) << " ";
Chris@537 350 }
Chris@537 351 std::cerr << std::endl;
Chris@537 352 }
Chris@537 353 */
Chris@537 354 if (!seekTo(x, 0)) {
Chris@537 355 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Seek failed" << std::endl;
Chris@148 356 throw FileOperationFailed(m_fileName, "seek");
Chris@537 357 }
Chris@537 358
Chris@537 359 set = 1;
Chris@537 360 w = ::write(m_fd, &set, 1);
Chris@537 361 if (w != 1) {
Chris@537 362 ::perror("WARNING: MatrixFile::setColumnAt: write failed (3)");
Chris@537 363 throw FileOperationFailed(m_fileName, "write");
Chris@148 364 }
Chris@148 365 }
Chris@148 366
Chris@537 367 bool
Chris@537 368 MatrixFile::seekTo(size_t x, size_t y) const
Chris@148 369 {
Chris@537 370 if (m_fd < 0) {
Chris@537 371 std::cerr << "ERROR: MatrixFile::seekTo: File not open" << std::endl;
Chris@537 372 return false;
Chris@148 373 }
Chris@148 374
Chris@537 375 off_t off = m_headerSize + x * m_height * m_cellSize + x + y * m_cellSize;
Chris@148 376
Chris@148 377 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 378 std::cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "," << y << "): off = " << off << std::endl;
Chris@148 379 #endif
Chris@148 380
Chris@148 381 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
Chris@148 382 ::perror("Seek failed");
Chris@148 383 std::cerr << "ERROR: MatrixFile::seekTo(" << x << ", " << y
Chris@537 384 << ") = " << off << " failed" << std::endl;
Chris@148 385 return false;
Chris@148 386 }
Chris@148 387
Chris@148 388 return true;
Chris@148 389 }
Chris@148 390