annotate base/MatrixFile.cpp @ 99:9a44ccae165c

...
author Chris Cannam
date Fri, 05 May 2006 11:33:51 +0000
parents 22494cc28c9f
children
rev   line source
Chris@87 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@87 2
Chris@87 3 /*
Chris@87 4 Sonic Visualiser
Chris@87 5 An audio file viewer and annotation editor.
Chris@87 6 Centre for Digital Music, Queen Mary, University of London.
Chris@87 7 This file copyright 2006 Chris Cannam.
Chris@87 8
Chris@87 9 This program is free software; you can redistribute it and/or
Chris@87 10 modify it under the terms of the GNU General Public License as
Chris@87 11 published by the Free Software Foundation; either version 2 of the
Chris@87 12 License, or (at your option) any later version. See the file
Chris@87 13 COPYING included with this distribution for more information.
Chris@87 14 */
Chris@87 15
Chris@96 16 #include "MatrixFile.h"
Chris@87 17 #include "base/TempDirectory.h"
Chris@90 18 #include "base/System.h"
Chris@87 19
Chris@87 20 #include <sys/types.h>
Chris@87 21 #include <sys/stat.h>
Chris@87 22 #include <fcntl.h>
Chris@87 23 #include <unistd.h>
Chris@87 24
Chris@87 25 #include <iostream>
Chris@87 26
Chris@87 27 #include <cstdio>
Chris@87 28
Chris@87 29 #include <QFileInfo>
Chris@87 30 #include <QDir>
Chris@87 31
Chris@96 32 std::map<QString, int> MatrixFile::m_refcount;
Chris@96 33 QMutex MatrixFile::m_refcountMutex;
Chris@91 34
Chris@96 35 MatrixFile::MatrixFile(QString fileBase, Mode mode) :
Chris@87 36 m_fd(-1),
Chris@87 37 m_mode(mode),
Chris@87 38 m_width(0),
Chris@87 39 m_height(0),
Chris@90 40 m_headerSize(2 * sizeof(size_t)),
Chris@97 41 m_defaultCacheWidth(512),
Chris@95 42 m_prevX(0),
Chris@97 43 m_requestToken(-1),
Chris@97 44 m_spareData(0)
Chris@87 45 {
Chris@95 46 m_cache.data = 0;
Chris@90 47
Chris@87 48 QDir tempDir(TempDirectory::instance()->getPath());
Chris@87 49 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
Chris@87 50 bool newFile = !QFileInfo(fileName).exists();
Chris@87 51
Chris@87 52 if (newFile && mode == ReadOnly) {
Chris@96 53 std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode "
Chris@87 54 << "specified, but cache file does not exist" << std::endl;
Chris@87 55 return;
Chris@87 56 }
Chris@87 57
Chris@87 58 int flags = 0;
Chris@87 59 mode_t fmode = S_IRUSR | S_IWUSR;
Chris@87 60
Chris@87 61 if (mode == ReadWrite) {
Chris@87 62 flags = O_RDWR | O_CREAT;
Chris@87 63 } else {
Chris@87 64 flags = O_RDONLY;
Chris@87 65 }
Chris@87 66
Chris@90 67 if ((m_fd = ::open(fileName.toLocal8Bit(), flags, fmode)) < 0) {
Chris@87 68 ::perror("Open failed");
Chris@96 69 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@87 70 << "Failed to open cache file \""
Chris@87 71 << fileName.toStdString() << "\"";
Chris@87 72 if (mode == ReadWrite) std::cerr << " for writing";
Chris@87 73 std::cerr << std::endl;
Chris@91 74 return;
Chris@87 75 }
Chris@87 76
Chris@87 77 if (newFile) {
Chris@87 78 resize(0, 0); // write header
Chris@87 79 } else {
Chris@87 80 size_t header[2];
Chris@90 81 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
Chris@87 82 perror("Read failed");
Chris@96 83 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@90 84 << "Failed to read header (fd " << m_fd << ", file \""
Chris@90 85 << fileName.toStdString() << "\")" << std::endl;
Chris@87 86 return;
Chris@87 87 }
Chris@87 88 m_width = header[0];
Chris@87 89 m_height = header[1];
Chris@87 90 seekTo(0, 0);
Chris@87 91 }
Chris@87 92
Chris@91 93 m_fileName = fileName;
Chris@95 94
Chris@95 95 m_readThread.start();
Chris@95 96
Chris@91 97 QMutexLocker locker(&m_refcountMutex);
Chris@91 98 ++m_refcount[fileName];
Chris@91 99
Chris@96 100 std::cerr << "MatrixFile::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
Chris@87 101
Chris@87 102 }
Chris@87 103
Chris@96 104 MatrixFile::~MatrixFile()
Chris@87 105 {
Chris@95 106 float *requestData = 0;
Chris@95 107
Chris@95 108 if (m_requestToken >= 0) {
Chris@95 109 FileReadThread::Request request;
Chris@95 110 if (m_readThread.getRequest(m_requestToken, request)) {
Chris@95 111 requestData = (float *)request.data;
Chris@95 112 }
Chris@90 113 }
Chris@90 114
Chris@95 115 m_readThread.finish();
Chris@95 116 m_readThread.wait();
Chris@95 117
Chris@97 118 if (requestData) free(requestData);
Chris@97 119 if (m_cache.data) free(m_cache.data);
Chris@97 120 if (m_spareData) free(m_spareData);
Chris@95 121
Chris@87 122 if (m_fd >= 0) {
Chris@87 123 if (::close(m_fd) < 0) {
Chris@96 124 ::perror("MatrixFile::~MatrixFile: close failed");
Chris@87 125 }
Chris@87 126 }
Chris@90 127
Chris@91 128 if (m_fileName != "") {
Chris@91 129 QMutexLocker locker(&m_refcountMutex);
Chris@91 130 if (--m_refcount[m_fileName] == 0) {
Chris@95 131 if (::unlink(m_fileName.toLocal8Bit())) {
Chris@95 132 ::perror("Unlink failed");
Chris@96 133 std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
Chris@91 134 } else {
Chris@91 135 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
Chris@91 136 }
Chris@91 137 }
Chris@91 138 }
Chris@87 139 }
Chris@87 140
Chris@87 141 size_t
Chris@96 142 MatrixFile::getWidth() const
Chris@87 143 {
Chris@87 144 return m_width;
Chris@87 145 }
Chris@87 146
Chris@87 147 size_t
Chris@96 148 MatrixFile::getHeight() const
Chris@87 149 {
Chris@87 150 return m_height;
Chris@87 151 }
Chris@87 152
Chris@87 153 void
Chris@96 154 MatrixFile::resize(size_t w, size_t h)
Chris@87 155 {
Chris@87 156 if (m_mode != ReadWrite) {
Chris@96 157 std::cerr << "ERROR: MatrixFile::resize called on read-only cache"
Chris@87 158 << std::endl;
Chris@87 159 return;
Chris@87 160 }
Chris@87 161
Chris@95 162 QMutexLocker locker(&m_fdMutex);
Chris@95 163
Chris@87 164 off_t off = m_headerSize + (w * h * sizeof(float));
Chris@87 165
Chris@87 166 if (w * h > m_width * m_height) {
Chris@87 167
Chris@95 168 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
Chris@95 169 ::perror("Seek failed");
Chris@96 170 std::cerr << "ERROR: MatrixFile::resize(" << w << ", "
Chris@95 171 << h << "): seek failed, cannot resize" << std::endl;
Chris@95 172 return;
Chris@95 173 }
Chris@95 174
Chris@95 175 // guess this requires efficient support for sparse files
Chris@90 176
Chris@95 177 float f(0);
Chris@95 178 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
Chris@96 179 ::perror("WARNING: MatrixFile::resize: write failed");
Chris@87 180 }
Chris@95 181
Chris@87 182 } else {
Chris@87 183
Chris@87 184 if (::ftruncate(m_fd, off) < 0) {
Chris@96 185 ::perror("WARNING: MatrixFile::resize: ftruncate failed");
Chris@87 186 }
Chris@87 187 }
Chris@87 188
Chris@87 189 m_width = 0;
Chris@87 190 m_height = 0;
Chris@87 191
Chris@87 192 if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) {
Chris@96 193 ::perror("ERROR: MatrixFile::resize: Seek to write header failed");
Chris@87 194 return;
Chris@87 195 }
Chris@87 196
Chris@87 197 size_t header[2];
Chris@87 198 header[0] = w;
Chris@87 199 header[1] = h;
Chris@87 200 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
Chris@96 201 ::perror("ERROR: MatrixFile::resize: Failed to write header");
Chris@87 202 return;
Chris@87 203 }
Chris@87 204
Chris@87 205 m_width = w;
Chris@87 206 m_height = h;
Chris@87 207
Chris@87 208 seekTo(0, 0);
Chris@87 209 }
Chris@87 210
Chris@87 211 void
Chris@96 212 MatrixFile::reset()
Chris@87 213 {
Chris@87 214 if (m_mode != ReadWrite) {
Chris@96 215 std::cerr << "ERROR: MatrixFile::reset called on read-only cache"
Chris@87 216 << std::endl;
Chris@87 217 return;
Chris@87 218 }
Chris@87 219
Chris@95 220 QMutexLocker locker(&m_fdMutex);
Chris@95 221
Chris@90 222 float *emptyCol = new float[m_height];
Chris@90 223 for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f;
Chris@90 224
Chris@90 225 seekTo(0, 0);
Chris@90 226 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol);
Chris@90 227
Chris@90 228 delete[] emptyCol;
Chris@87 229 }
Chris@87 230
Chris@95 231 float
Chris@96 232 MatrixFile::getValueAt(size_t x, size_t y)
Chris@87 233 {
Chris@95 234 float value = 0.f;
Chris@95 235 if (getValuesFromCache(x, y, 1, &value)) return value;
Chris@90 236
Chris@95 237 ssize_t r = 0;
Chris@87 238
Chris@96 239 // std::cout << "MatrixFile::getValueAt(" << x << ", " << y << ")"
Chris@95 240 // << ": reading the slow way" << std::endl;
Chris@95 241
Chris@95 242 m_fdMutex.lock();
Chris@95 243
Chris@95 244 if (seekTo(x, y)) {
Chris@95 245 r = ::read(m_fd, &value, sizeof(float));
Chris@87 246 }
Chris@87 247
Chris@95 248 m_fdMutex.unlock();
Chris@95 249
Chris@91 250 if (r < 0) {
Chris@96 251 ::perror("MatrixFile::getValueAt: Read failed");
Chris@91 252 }
Chris@90 253 if (r != sizeof(float)) {
Chris@90 254 value = 0.f;
Chris@87 255 }
Chris@95 256
Chris@87 257 return value;
Chris@87 258 }
Chris@87 259
Chris@87 260 void
Chris@96 261 MatrixFile::getColumnAt(size_t x, float *values)
Chris@87 262 {
Chris@95 263 if (getValuesFromCache(x, 0, m_height, values)) return;
Chris@95 264
Chris@95 265 ssize_t r = 0;
Chris@95 266
Chris@96 267 std::cout << "MatrixFile::getColumnAt(" << x << ")"
Chris@95 268 << ": reading the slow way" << std::endl;
Chris@95 269
Chris@95 270 m_fdMutex.lock();
Chris@95 271
Chris@95 272 if (seekTo(x, 0)) {
Chris@95 273 r = ::read(m_fd, values, m_height * sizeof(float));
Chris@87 274 }
Chris@87 275
Chris@95 276 m_fdMutex.unlock();
Chris@95 277
Chris@91 278 if (r < 0) {
Chris@96 279 ::perror("MatrixFile::getColumnAt: read failed");
Chris@87 280 }
Chris@95 281 }
Chris@95 282
Chris@95 283 bool
Chris@96 284 MatrixFile::getValuesFromCache(size_t x, size_t ystart, size_t ycount,
Chris@95 285 float *values)
Chris@95 286 {
Chris@95 287 m_cacheMutex.lock();
Chris@95 288
Chris@95 289 if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) {
Chris@95 290 bool left = (m_cache.data && x < m_cache.x);
Chris@95 291 m_cacheMutex.unlock();
Chris@95 292 primeCache(x, left); // this doesn't take effect until a later callback
Chris@95 293 m_prevX = x;
Chris@95 294 return false;
Chris@95 295 }
Chris@95 296
Chris@95 297 for (size_t y = ystart; y < ystart + ycount; ++y) {
Chris@95 298 values[y - ystart] = m_cache.data[(x - m_cache.x) * m_height + y];
Chris@95 299 }
Chris@95 300 m_cacheMutex.unlock();
Chris@95 301
Chris@95 302 if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) {
Chris@95 303 primeCache(x, true);
Chris@95 304 }
Chris@95 305
Chris@95 306 if (m_cache.x + m_cache.width < m_width &&
Chris@95 307 x > m_prevX &&
Chris@95 308 x > m_cache.x + (m_cache.width * 3) / 4) {
Chris@95 309 primeCache(x, false);
Chris@95 310 }
Chris@95 311
Chris@95 312 m_prevX = x;
Chris@95 313 return true;
Chris@87 314 }
Chris@87 315
Chris@87 316 void
Chris@96 317 MatrixFile::setValueAt(size_t x, size_t y, float value)
Chris@87 318 {
Chris@87 319 if (m_mode != ReadWrite) {
Chris@96 320 std::cerr << "ERROR: MatrixFile::setValueAt called on read-only cache"
Chris@87 321 << std::endl;
Chris@87 322 return;
Chris@87 323 }
Chris@87 324
Chris@95 325 ssize_t w = 0;
Chris@95 326 bool seekFailed = false;
Chris@95 327
Chris@95 328 m_fdMutex.lock();
Chris@95 329
Chris@95 330 if (seekTo(x, y)) {
Chris@95 331 w = ::write(m_fd, &value, sizeof(float));
Chris@95 332 } else {
Chris@95 333 seekFailed = true;
Chris@95 334 }
Chris@95 335
Chris@95 336 m_fdMutex.unlock();
Chris@95 337
Chris@95 338 if (!seekFailed && w != sizeof(float)) {
Chris@96 339 ::perror("WARNING: MatrixFile::setValueAt: write failed");
Chris@87 340 }
Chris@87 341
Chris@95 342 //... update cache as appropriate
Chris@87 343 }
Chris@87 344
Chris@87 345 void
Chris@96 346 MatrixFile::setColumnAt(size_t x, float *values)
Chris@87 347 {
Chris@87 348 if (m_mode != ReadWrite) {
Chris@96 349 std::cerr << "ERROR: MatrixFile::setColumnAt called on read-only cache"
Chris@87 350 << std::endl;
Chris@87 351 return;
Chris@87 352 }
Chris@87 353
Chris@95 354 ssize_t w = 0;
Chris@95 355 bool seekFailed = false;
Chris@95 356
Chris@95 357 m_fdMutex.lock();
Chris@95 358
Chris@95 359 if (seekTo(x, 0)) {
Chris@95 360 w = ::write(m_fd, values, m_height * sizeof(float));
Chris@95 361 } else {
Chris@95 362 seekFailed = true;
Chris@95 363 }
Chris@95 364
Chris@95 365 m_fdMutex.unlock();
Chris@95 366
Chris@95 367 if (!seekFailed && w != ssize_t(m_height * sizeof(float))) {
Chris@96 368 ::perror("WARNING: MatrixFile::setColumnAt: write failed");
Chris@87 369 }
Chris@87 370
Chris@95 371 //... update cache as appropriate
Chris@90 372 }
Chris@90 373
Chris@95 374 void
Chris@96 375 MatrixFile::primeCache(size_t x, bool goingLeft)
Chris@90 376 {
Chris@96 377 // std::cerr << "MatrixFile::primeCache(" << x << ", " << goingLeft << ")" << std::endl;
Chris@90 378
Chris@95 379 size_t rx = x;
Chris@95 380 size_t rw = m_defaultCacheWidth;
Chris@90 381
Chris@95 382 size_t left = rw / 3;
Chris@95 383 if (goingLeft) left = (rw * 2) / 3;
Chris@95 384
Chris@95 385 if (rx > left) rx -= left;
Chris@95 386 else rx = 0;
Chris@95 387
Chris@95 388 if (rx + rw > m_width) rw = m_width - rx;
Chris@95 389
Chris@95 390 QMutexLocker locker(&m_cacheMutex);
Chris@95 391
Chris@96 392 FileReadThread::Request request;
Chris@96 393
Chris@96 394 if (m_requestToken >= 0 &&
Chris@96 395 m_readThread.getRequest(m_requestToken, request)) {
Chris@95 396
Chris@95 397 if (x >= m_requestingX &&
Chris@95 398 x < m_requestingX + m_requestingWidth) {
Chris@95 399
Chris@95 400 if (m_readThread.isReady(m_requestToken)) {
Chris@95 401
Chris@95 402 std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")" << std::endl;
Chris@95 403
Chris@96 404 m_cache.x = (request.start - m_headerSize) / (m_height * sizeof(float));
Chris@96 405 m_cache.width = request.size / (m_height * sizeof(float));
Chris@96 406
Chris@96 407 std::cerr << "actual: " << m_cache.x << ", " << m_cache.width << std::endl;
Chris@95 408
Chris@97 409 if (m_cache.data) {
Chris@97 410 if (m_spareData) free(m_spareData);
Chris@97 411 m_spareData = (char *)m_cache.data;
Chris@97 412 }
Chris@96 413 m_cache.data = (float *)request.data;
Chris@95 414
Chris@95 415 m_readThread.done(m_requestToken);
Chris@95 416 m_requestToken = -1;
Chris@95 417 }
Chris@95 418
Chris@96 419 // already requested something covering this area; wait for it
Chris@95 420 return;
Chris@95 421 }
Chris@95 422
Chris@96 423 // the current request is no longer of any use
Chris@95 424 m_readThread.cancel(m_requestToken);
Chris@96 425
Chris@96 426 // crude way to avoid leaking the data
Chris@96 427 while (!m_readThread.isCancelled(m_requestToken)) {
Chris@96 428 usleep(10000);
Chris@96 429 }
Chris@96 430
Chris@97 431 if (m_spareData) free(m_spareData);
Chris@97 432 m_spareData = request.data;
Chris@96 433 m_readThread.done(m_requestToken);
Chris@96 434
Chris@95 435 m_requestToken = -1;
Chris@95 436 }
Chris@95 437
Chris@95 438 request.fd = m_fd;
Chris@95 439 request.mutex = &m_fdMutex;
Chris@95 440 request.start = m_headerSize + rx * m_height * sizeof(float);
Chris@95 441 request.size = rw * m_height * sizeof(float);
Chris@97 442 request.data = (char *)realloc(m_spareData, rw * m_height * sizeof(float));
Chris@96 443 MUNLOCK(request.data, rw * m_height * sizeof(float));
Chris@97 444 m_spareData = 0;
Chris@95 445
Chris@95 446 m_requestingX = rx;
Chris@95 447 m_requestingWidth = rw;
Chris@95 448
Chris@95 449 int token = m_readThread.request(request);
Chris@96 450 std::cerr << "MatrixFile::primeCache: request token is "
Chris@96 451 << token << " (x = " << rx << ", w = " << rw << ", left = " << goingLeft << ")" << std::endl;
Chris@95 452
Chris@95 453 m_requestToken = token;
Chris@95 454 }
Chris@95 455
Chris@90 456 bool
Chris@96 457 MatrixFile::seekTo(size_t x, size_t y)
Chris@87 458 {
Chris@87 459 off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
Chris@90 460
Chris@87 461 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
Chris@87 462 ::perror("Seek failed");
Chris@96 463 std::cerr << "ERROR: MatrixFile::seekTo(" << x << ", " << y
Chris@87 464 << ") failed" << std::endl;
Chris@87 465 return false;
Chris@87 466 }
Chris@87 467
Chris@87 468 return true;
Chris@87 469 }
Chris@87 470