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