annotate data/fileio/MatrixFile.cpp @ 1078:ce82bcdc95d0

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