annotate base/MatrixFile.cpp @ 96:1aebdc68ec6d

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