annotate data/fileio/MatrixFile.cpp @ 661:a4faa1840384

* If a FileSource URL won't convert at all in strict mode, try again in tolerant mode (necessary for e.g. filenames with square brackets in them)
author Chris Cannam
date Tue, 19 Oct 2010 21:47:55 +0100
parents dfc4dd561bb6
children b4a8d8221eaf
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@570 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@550 63 m_headerSize(2 * sizeof(size_t)),
Chris@550 64 m_setColumns(0),
Chris@554 65 m_autoClose(false),
Chris@554 66 m_readyToReadColumn(-1)
Chris@148 67 {
Chris@148 68 Profiler profiler("MatrixFile::MatrixFile", true);
Chris@148 69
Chris@537 70 #ifdef DEBUG_MATRIX_FILE
Chris@537 71 std::cerr << "MatrixFile::MatrixFile(" << fileBase.toStdString() << ", " << int(mode) << ", " << cellSize << ", " << width << ", " << height << ")" << std::endl;
Chris@537 72 #endif
Chris@148 73
Chris@548 74 m_createMutex.lock();
Chris@148 75
Chris@148 76 QDir tempDir(TempDirectory::getInstance()->getPath());
Chris@148 77 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
Chris@148 78 bool newFile = !QFileInfo(fileName).exists();
Chris@148 79
Chris@148 80 if (newFile && m_mode == ReadOnly) {
Chris@148 81 std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode "
Chris@148 82 << "specified, but cache file does not exist" << std::endl;
Chris@148 83 throw FileNotFound(fileName);
Chris@148 84 }
Chris@148 85
Chris@537 86 if (!newFile && m_mode == WriteOnly) {
Chris@537 87 std::cerr << "ERROR: MatrixFile::MatrixFile: Write-only mode "
Chris@537 88 << "specified, but file already exists" << std::endl;
Chris@537 89 throw FileOperationFailed(fileName, "create");
Chris@148 90 }
Chris@148 91
Chris@148 92 m_flags = 0;
Chris@148 93 m_fmode = S_IRUSR | S_IWUSR;
Chris@148 94
Chris@537 95 if (m_mode == WriteOnly) {
Chris@537 96 m_flags = O_WRONLY | O_CREAT;
Chris@148 97 } else {
Chris@148 98 m_flags = O_RDONLY;
Chris@148 99 }
Chris@148 100
Chris@233 101 #ifdef _WIN32
Chris@233 102 m_flags |= O_BINARY;
Chris@233 103 #endif
Chris@233 104
Chris@148 105 #ifdef DEBUG_MATRIX_FILE
Chris@537 106 std::cerr << "MatrixFile(" << this << ")::MatrixFile: opening " << fileName.toStdString() << "..." << std::endl;
Chris@148 107 #endif
Chris@148 108
Chris@148 109 if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) {
Chris@148 110 ::perror("Open failed");
Chris@148 111 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@148 112 << "Failed to open cache file \""
Chris@148 113 << fileName.toStdString() << "\"";
Chris@537 114 if (m_mode == WriteOnly) std::cerr << " for writing";
Chris@148 115 std::cerr << std::endl;
Chris@148 116 throw FailedToOpenFile(fileName);
Chris@148 117 }
Chris@148 118
Chris@548 119 m_createMutex.unlock();
Chris@548 120
Chris@537 121 #ifdef DEBUG_MATRIX_FILE
Chris@537 122 std::cerr << "MatrixFile(" << this << ")::MatrixFile: fd is " << m_fd << std::endl;
Chris@537 123 #endif
Chris@537 124
Chris@148 125 if (newFile) {
Chris@537 126 initialise(); // write header and "unwritten" column tags
Chris@148 127 } else {
Chris@148 128 size_t header[2];
Chris@148 129 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
Chris@236 130 ::perror("MatrixFile::MatrixFile: read failed");
Chris@148 131 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@148 132 << "Failed to read header (fd " << m_fd << ", file \""
Chris@148 133 << fileName.toStdString() << "\")" << std::endl;
Chris@148 134 throw FileReadFailed(fileName);
Chris@148 135 }
Chris@537 136 if (header[0] != m_width || header[1] != m_height) {
Chris@537 137 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@537 138 << "Dimensions in file header (" << header[0] << "x"
Chris@537 139 << header[1] << ") differ from expected dimensions "
Chris@537 140 << m_width << "x" << m_height << std::endl;
Chris@537 141 throw FailedToOpenFile(fileName);
Chris@537 142 }
Chris@148 143 }
Chris@148 144
Chris@148 145 m_fileName = fileName;
Chris@148 146 ++m_refcount[fileName];
Chris@148 147
Chris@537 148 #ifdef DEBUG_MATRIX_FILE
Chris@537 149 std::cerr << "MatrixFile[" << m_fd << "]::MatrixFile: File " << fileName.toStdString() << ", ref " << m_refcount[fileName] << std::endl;
Chris@148 150
Chris@537 151 std::cerr << "MatrixFile[" << m_fd << "]::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
Chris@537 152 #endif
Chris@148 153
Chris@148 154 ++totalCount;
Chris@537 155 ++openCount;
Chris@148 156 }
Chris@148 157
Chris@148 158 MatrixFile::~MatrixFile()
Chris@148 159 {
Chris@148 160 if (m_fd >= 0) {
Chris@148 161 if (::close(m_fd) < 0) {
Chris@148 162 ::perror("MatrixFile::~MatrixFile: close failed");
Chris@148 163 }
Chris@546 164 openCount --;
Chris@148 165 }
Chris@148 166
Chris@537 167 QMutexLocker locker(&m_createMutex);
Chris@537 168
Chris@550 169 delete m_setColumns;
Chris@550 170
Chris@148 171 if (m_fileName != "") {
Chris@148 172
Chris@148 173 if (--m_refcount[m_fileName] == 0) {
Chris@148 174
Chris@148 175 if (::unlink(m_fileName.toLocal8Bit())) {
Chris@537 176 std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
Chris@148 177 } else {
Chris@537 178 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
Chris@148 179 }
Chris@148 180 }
Chris@148 181 }
Chris@537 182
Chris@537 183 if (m_mode == WriteOnly) {
Chris@537 184 totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize) + m_width);
Chris@537 185 }
Chris@148 186 totalCount --;
Chris@148 187
Chris@537 188 #ifdef DEBUG_MATRIX_FILE
Chris@537 189 std::cerr << "MatrixFile[" << m_fd << "]::~MatrixFile: " << std::endl;
Chris@537 190 std::cerr << "MatrixFile: Total storage now " << totalStorage/1024 << "K in " << totalCount << " instances (" << openCount << " open)" << std::endl;
Chris@537 191 #endif
Chris@148 192 }
Chris@148 193
Chris@148 194 void
Chris@537 195 MatrixFile::initialise()
Chris@148 196 {
Chris@537 197 Profiler profiler("MatrixFile::initialise", true);
Chris@148 198
Chris@537 199 assert(m_mode == WriteOnly);
Chris@550 200
Chris@550 201 m_setColumns = new ResizeableBitset(m_width);
Chris@148 202
Chris@537 203 off_t off = m_headerSize + (m_width * m_height * m_cellSize) + m_width;
Chris@148 204
Chris@148 205 #ifdef DEBUG_MATRIX_FILE
Chris@537 206 std::cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): cell size " << m_cellSize << ", header size " << m_headerSize << ", resizing file" << std::endl;
Chris@148 207 #endif
Chris@148 208
Chris@537 209 if (::lseek(m_fd, off - 1, SEEK_SET) < 0) {
Chris@537 210 ::perror("ERROR: MatrixFile::initialise: seek to end failed");
Chris@537 211 throw FileOperationFailed(m_fileName, "lseek");
Chris@148 212 }
Chris@148 213
Chris@537 214 unsigned char byte = 0;
Chris@537 215 if (::write(m_fd, &byte, 1) != 1) {
Chris@537 216 ::perror("ERROR: MatrixFile::initialise: write at end failed");
Chris@537 217 throw FileOperationFailed(m_fileName, "write");
Chris@537 218 }
Chris@148 219
Chris@537 220 if (::lseek(m_fd, 0, SEEK_SET) < 0) {
Chris@547 221 ::perror("ERROR: MatrixFile::initialise: Seek to write header failed");
Chris@148 222 throw FileOperationFailed(m_fileName, "lseek");
Chris@148 223 }
Chris@148 224
Chris@148 225 size_t header[2];
Chris@537 226 header[0] = m_width;
Chris@537 227 header[1] = m_height;
Chris@148 228 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
Chris@547 229 ::perror("ERROR: MatrixFile::initialise: Failed to write header");
Chris@148 230 throw FileOperationFailed(m_fileName, "write");
Chris@148 231 }
Chris@148 232
Chris@537 233 if (m_mode == WriteOnly) {
Chris@537 234 totalStorage += (m_headerSize + (m_width * m_height * m_cellSize) + m_width);
Chris@148 235 }
Chris@148 236
Chris@537 237 #ifdef DEBUG_MATRIX_FILE
Chris@547 238 std::cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): storage "
Chris@537 239 << (m_headerSize + m_width * m_height * m_cellSize + m_width) << std::endl;
Chris@148 240
Chris@537 241 std::cerr << "MatrixFile: Total storage " << totalStorage/1024 << "K" << std::endl;
Chris@148 242 #endif
Chris@148 243
Chris@554 244 seekTo(0);
Chris@148 245 }
Chris@148 246
Chris@148 247 void
Chris@537 248 MatrixFile::close()
Chris@148 249 {
Chris@537 250 #ifdef DEBUG_MATRIX_FILE
Chris@537 251 std::cerr << "MatrixFile::close()" << std::endl;
Chris@537 252 #endif
Chris@537 253 if (m_fd >= 0) {
Chris@537 254 if (::close(m_fd) < 0) {
Chris@537 255 ::perror("MatrixFile::close: close failed");
Chris@537 256 }
Chris@537 257 m_fd = -1;
Chris@537 258 -- openCount;
Chris@550 259 #ifdef DEBUG_MATRIX_FILE
Chris@550 260 std::cerr << "MatrixFile: Now " << openCount << " open instances" << std::endl;
Chris@550 261 #endif
Chris@148 262 }
Chris@148 263 }
Chris@148 264
Chris@148 265 void
Chris@148 266 MatrixFile::getColumnAt(size_t x, void *data)
Chris@148 267 {
Chris@537 268 assert(m_mode == ReadOnly);
Chris@537 269
Chris@537 270 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 271 std::cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << ")" << std::endl;
Chris@537 272 #endif
Chris@537 273
Chris@408 274 Profiler profiler("MatrixFile::getColumnAt");
Chris@148 275
Chris@554 276 ssize_t r = -1;
Chris@148 277
Chris@554 278 if (m_readyToReadColumn < 0 ||
Chris@554 279 size_t(m_readyToReadColumn) != x) {
Chris@554 280
Chris@554 281 unsigned char set = 0;
Chris@554 282 if (!seekTo(x)) {
Chris@554 283 std::cerr << "ERROR: MatrixFile::getColumnAt(" << x << "): Seek failed" << std::endl;
Chris@554 284 throw FileOperationFailed(m_fileName, "seek");
Chris@554 285 }
Chris@554 286
Chris@554 287 r = ::read(m_fd, &set, 1);
Chris@554 288 if (r < 0) {
Chris@554 289 ::perror("MatrixFile::getColumnAt: read failed");
Chris@554 290 throw FileReadFailed(m_fileName);
Chris@554 291 }
Chris@554 292 if (!set) {
Chris@554 293 std::cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << "): Column has not been set" << std::endl;
Chris@554 294 return;
Chris@554 295 }
Chris@537 296 }
Chris@537 297
Chris@537 298 r = ::read(m_fd, data, m_height * m_cellSize);
Chris@537 299 if (r < 0) {
Chris@537 300 ::perror("MatrixFile::getColumnAt: read failed");
Chris@537 301 throw FileReadFailed(m_fileName);
Chris@537 302 }
Chris@537 303 }
Chris@537 304
Chris@537 305 bool
Chris@537 306 MatrixFile::haveSetColumnAt(size_t x) const
Chris@537 307 {
Chris@550 308 if (m_mode == WriteOnly) {
Chris@550 309 return m_setColumns->get(x);
Chris@550 310 }
Chris@537 311
Chris@554 312 if (m_readyToReadColumn >= 0 &&
Chris@554 313 size_t(m_readyToReadColumn) == x) return true;
Chris@554 314
Chris@537 315 Profiler profiler("MatrixFile::haveSetColumnAt");
Chris@537 316
Chris@537 317 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 318 std::cerr << "MatrixFile[" << m_fd << "]::haveSetColumnAt(" << x << ")" << std::endl;
Chris@537 319 // std::cerr << ".";
Chris@148 320 #endif
Chris@148 321
Chris@537 322 unsigned char set = 0;
Chris@554 323 if (!seekTo(x)) {
Chris@537 324 std::cerr << "ERROR: MatrixFile::haveSetColumnAt(" << x << "): Seek failed" << std::endl;
Chris@537 325 throw FileOperationFailed(m_fileName, "seek");
Chris@537 326 }
Chris@148 327
Chris@537 328 ssize_t r = -1;
Chris@537 329 r = ::read(m_fd, &set, 1);
Chris@148 330 if (r < 0) {
Chris@537 331 ::perror("MatrixFile::haveSetColumnAt: read failed");
Chris@148 332 throw FileReadFailed(m_fileName);
Chris@148 333 }
Chris@148 334
Chris@554 335 if (set) m_readyToReadColumn = int(x);
Chris@554 336
Chris@537 337 return set;
Chris@148 338 }
Chris@148 339
Chris@148 340 void
Chris@148 341 MatrixFile::setColumnAt(size_t x, const void *data)
Chris@148 342 {
Chris@537 343 assert(m_mode == WriteOnly);
Chris@550 344 if (m_fd < 0) return; // closed
Chris@148 345
Chris@148 346 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@537 347 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << ")" << std::endl;
Chris@537 348 // std::cerr << ".";
Chris@148 349 #endif
Chris@148 350
Chris@148 351 ssize_t w = 0;
Chris@148 352
Chris@554 353 if (!seekTo(x)) {
Chris@537 354 std::cerr << "ERROR: MatrixFile::setColumnAt(" << x << "): Seek failed" << std::endl;
Chris@537 355 throw FileOperationFailed(m_fileName, "seek");
Chris@148 356 }
Chris@148 357
Chris@537 358 unsigned char set = 0;
Chris@537 359 w = ::write(m_fd, &set, 1);
Chris@537 360 if (w != 1) {
Chris@537 361 ::perror("WARNING: MatrixFile::setColumnAt: write failed (1)");
Chris@148 362 throw FileOperationFailed(m_fileName, "write");
Chris@537 363 }
Chris@537 364
Chris@537 365 w = ::write(m_fd, data, m_height * m_cellSize);
Chris@537 366 if (w != ssize_t(m_height * m_cellSize)) {
Chris@537 367 ::perror("WARNING: MatrixFile::setColumnAt: write failed (2)");
Chris@537 368 throw FileOperationFailed(m_fileName, "write");
Chris@537 369 }
Chris@537 370 /*
Chris@537 371 if (x == 0) {
Chris@537 372 std::cerr << "Wrote " << m_height * m_cellSize << " bytes, as follows:" << std::endl;
Chris@537 373 for (int i = 0; i < m_height * m_cellSize; ++i) {
Chris@537 374 std::cerr << (int)(((char *)data)[i]) << " ";
Chris@537 375 }
Chris@537 376 std::cerr << std::endl;
Chris@537 377 }
Chris@537 378 */
Chris@554 379 if (!seekTo(x)) {
Chris@537 380 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Seek failed" << std::endl;
Chris@148 381 throw FileOperationFailed(m_fileName, "seek");
Chris@537 382 }
Chris@537 383
Chris@537 384 set = 1;
Chris@537 385 w = ::write(m_fd, &set, 1);
Chris@537 386 if (w != 1) {
Chris@537 387 ::perror("WARNING: MatrixFile::setColumnAt: write failed (3)");
Chris@537 388 throw FileOperationFailed(m_fileName, "write");
Chris@148 389 }
Chris@550 390
Chris@550 391 m_setColumns->set(x);
Chris@550 392 if (m_autoClose) {
Chris@550 393 if (m_setColumns->isAllOn()) {
Chris@571 394 #ifdef DEBUG_MATRIX_FILE
Chris@550 395 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): All columns set: auto-closing" << std::endl;
Chris@571 396 #endif
Chris@550 397 close();
Chris@550 398 /*
Chris@550 399 } else {
Chris@550 400 int set = 0;
Chris@550 401 for (int i = 0; i < m_width; ++i) {
Chris@550 402 if (m_setColumns->get(i)) ++set;
Chris@550 403 }
Chris@550 404 std::cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Auto-close on, but not all columns set yet (" << set << " of " << m_width << ")" << std::endl;
Chris@550 405 */
Chris@550 406 }
Chris@550 407 }
Chris@148 408 }
Chris@148 409
Chris@537 410 bool
Chris@554 411 MatrixFile::seekTo(size_t x) const
Chris@148 412 {
Chris@537 413 if (m_fd < 0) {
Chris@537 414 std::cerr << "ERROR: MatrixFile::seekTo: File not open" << std::endl;
Chris@537 415 return false;
Chris@148 416 }
Chris@148 417
Chris@554 418 m_readyToReadColumn = -1; // not ready, unless this is subsequently re-set
Chris@554 419
Chris@554 420 off_t off = m_headerSize + x * m_height * m_cellSize + x;
Chris@554 421
Chris@555 422 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@554 423 if (m_mode == ReadOnly) {
Chris@554 424 std::cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "): off = " << off << std::endl;
Chris@554 425 }
Chris@555 426 #endif
Chris@148 427
Chris@148 428 #ifdef DEBUG_MATRIX_FILE_READ_SET
Chris@554 429 std::cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "): off = " << off << std::endl;
Chris@148 430 #endif
Chris@148 431
Chris@148 432 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
Chris@148 433 ::perror("Seek failed");
Chris@554 434 std::cerr << "ERROR: MatrixFile::seekTo(" << x
Chris@537 435 << ") = " << off << " failed" << std::endl;
Chris@148 436 return false;
Chris@148 437 }
Chris@148 438
Chris@148 439 return true;
Chris@148 440 }
Chris@148 441