annotate data/fileio/CachedFile.cpp @ 819:51cf0c35e9b0

Avoid deleting unallocated buffer in dtor
author Chris Cannam
date Mon, 08 Jul 2013 14:21:50 +0100
parents b99dc5465b80
children e802e550a1f2
rev   line source
Chris@465 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@465 2
Chris@465 3 /*
Chris@465 4 Sonic Visualiser
Chris@465 5 An audio file viewer and annotation editor.
Chris@465 6 Centre for Digital Music, Queen Mary, University of London.
Chris@465 7 This file copyright 2008 QMUL.
Chris@465 8
Chris@465 9 This program is free software; you can redistribute it and/or
Chris@465 10 modify it under the terms of the GNU General Public License as
Chris@465 11 published by the Free Software Foundation; either version 2 of the
Chris@465 12 License, or (at your option) any later version. See the file
Chris@465 13 COPYING included with this distribution for more information.
Chris@465 14 */
Chris@465 15
Chris@465 16 #include "CachedFile.h"
Chris@465 17
Chris@465 18 #include "base/TempDirectory.h"
Chris@465 19 #include "base/ProgressReporter.h"
Chris@465 20 #include "base/Exceptions.h"
Chris@465 21
Chris@466 22 #include "FileSource.h"
Chris@466 23
Chris@465 24 #include <QFileInfo>
Chris@465 25 #include <QSettings>
Chris@465 26 #include <QVariant>
Chris@465 27 #include <QMap>
Chris@465 28 #include <QDir>
Chris@465 29 #include <QCryptographicHash>
Chris@465 30
Chris@481 31 #include "base/Profiler.h"
Chris@481 32
Chris@466 33 #include <iostream>
Chris@466 34
Chris@481 35 CachedFile::OriginLocalFilenameMap
Chris@481 36 CachedFile::m_knownGoodCaches;
Chris@481 37
Chris@465 38 QString
Chris@465 39 CachedFile::getLocalFilenameFor(QUrl url)
Chris@465 40 {
Chris@481 41 Profiler p("CachedFile::getLocalFilenameFor");
Chris@481 42
Chris@465 43 QDir dir(getCacheDirectory());
Chris@465 44
Chris@465 45 QString filename =
Chris@465 46 QString::fromLocal8Bit
Chris@465 47 (QCryptographicHash::hash(url.toString().toLocal8Bit(),
Chris@465 48 QCryptographicHash::Sha1).toHex());
Chris@465 49
Chris@465 50 return dir.filePath(filename);
Chris@465 51 }
Chris@465 52
Chris@465 53 QString
Chris@465 54 CachedFile::getCacheDirectory()
Chris@465 55 {
Chris@465 56 QDir dir = TempDirectory::getInstance()->getContainingPath();
Chris@465 57
Chris@465 58 QString cacheDirName("cache");
Chris@465 59
Chris@465 60 QFileInfo fi(dir.filePath(cacheDirName));
Chris@465 61
Chris@465 62 if ((fi.exists() && !fi.isDir()) ||
Chris@465 63 (!fi.exists() && !dir.mkdir(cacheDirName))) {
Chris@465 64
Chris@465 65 throw DirectoryCreationFailed(fi.filePath());
Chris@465 66 }
Chris@465 67
Chris@465 68 return fi.filePath();
Chris@465 69 }
Chris@465 70
Chris@520 71 CachedFile::CachedFile(QString origin,
Chris@520 72 ProgressReporter *reporter,
Chris@520 73 QString preferredContentType) :
Chris@468 74 m_origin(origin),
Chris@520 75 m_preferredContentType(preferredContentType),
Chris@466 76 m_reporter(reporter),
Chris@465 77 m_ok(false)
Chris@465 78 {
Chris@481 79 Profiler p("CachedFile::CachedFile[1]");
Chris@481 80
Chris@690 81 SVDEBUG << "CachedFile::CachedFile: origin is \""
Chris@687 82 << origin << "\"" << endl;
Chris@691 83 checkFile();
Chris@467 84 }
Chris@467 85
Chris@520 86 CachedFile::CachedFile(QUrl url,
Chris@520 87 ProgressReporter *reporter,
Chris@520 88 QString preferredContentType) :
Chris@468 89 m_origin(url.toString()),
Chris@520 90 m_preferredContentType(preferredContentType),
Chris@467 91 m_reporter(reporter),
Chris@467 92 m_ok(false)
Chris@467 93 {
Chris@481 94 Profiler p("CachedFile::CachedFile[2]");
Chris@481 95
Chris@690 96 SVDEBUG << "CachedFile::CachedFile: url is \""
Chris@687 97 << url.toString() << "\"" << endl;
Chris@691 98 checkFile();
Chris@467 99 }
Chris@467 100
Chris@467 101 CachedFile::~CachedFile()
Chris@467 102 {
Chris@465 103 }
Chris@465 104
Chris@465 105 bool
Chris@465 106 CachedFile::isOK() const
Chris@465 107 {
Chris@465 108 return m_ok;
Chris@465 109 }
Chris@465 110
Chris@465 111 QString
Chris@465 112 CachedFile::getLocalFilename() const
Chris@465 113 {
Chris@465 114 return m_localFilename;
Chris@465 115 }
Chris@465 116
Chris@465 117 void
Chris@691 118 CachedFile::checkFile()
Chris@465 119 {
Chris@465 120 //!!! n.b. obvious race condition here if different CachedFile
Chris@465 121 // objects for same url used in more than one thread -- need to
Chris@465 122 // lock appropriately. also consider race condition between
Chris@481 123 // separate instances of the program!
Chris@481 124
Chris@481 125 OriginLocalFilenameMap::const_iterator i = m_knownGoodCaches.find(m_origin);
Chris@481 126 if (i != m_knownGoodCaches.end()) {
Chris@481 127 m_ok = true;
Chris@481 128 m_localFilename = i->second;
Chris@481 129 return;
Chris@481 130 }
Chris@481 131
Chris@481 132 m_localFilename = getLocalFilenameFor(m_origin);
Chris@465 133
Chris@465 134 if (!QFileInfo(m_localFilename).exists()) {
Chris@690 135 SVDEBUG << "CachedFile::check: Local file does not exist, making a note that it hasn't been retrieved" << endl;
Chris@465 136 updateLastRetrieval(false); // empirically!
Chris@465 137 }
Chris@465 138
Chris@465 139 QDateTime lastRetrieval = getLastRetrieval();
Chris@465 140
Chris@465 141 if (lastRetrieval.isValid()) {
Chris@690 142 SVDEBUG << "CachedFile::check: Valid last retrieval at "
Chris@687 143 << lastRetrieval.toString() << endl;
Chris@465 144 // this will not be the case if the file is missing, after
Chris@465 145 // updateLastRetrieval(false) was called above
Chris@465 146 m_ok = true;
Chris@465 147 if (lastRetrieval.addDays(2) < QDateTime::currentDateTime()) { //!!!
Chris@690 148 SVDEBUG << "CachedFile::check: Out of date; trying to retrieve again" << endl;
Chris@465 149 // doesn't matter if retrieval fails -- we just don't
Chris@465 150 // update the last retrieval time
Chris@465 151
Chris@465 152 //!!! but we do want an additional last-attempted
Chris@465 153 // timestamp so as to ensure we aren't retrying the
Chris@465 154 // retrieval every single time if it isn't working
Chris@465 155
Chris@465 156 if (retrieve()) {
Chris@690 157 SVDEBUG << "CachedFile::check: Retrieval succeeded" << endl;
Chris@465 158 updateLastRetrieval(true);
Chris@467 159 } else {
Chris@467 160 std::cerr << "CachedFile::check: Retrieval failed, will try again later (using existing file for now)" << std::endl;
Chris@467 161 }
Chris@465 162 }
Chris@465 163 } else {
Chris@690 164 SVDEBUG << "CachedFile::check: No valid last retrieval" << endl;
Chris@465 165 // there is no acceptable file
Chris@465 166 if (retrieve()) {
Chris@690 167 SVDEBUG << "CachedFile::check: Retrieval succeeded" << endl;
Chris@465 168 m_ok = true;
Chris@465 169 updateLastRetrieval(true);
Chris@465 170 } else {
Chris@467 171 std::cerr << "CachedFile::check: Retrieval failed, remaining in invalid state" << std::endl;
Chris@465 172 // again, we don't need to do anything here -- the last
Chris@465 173 // retrieval timestamp is already invalid
Chris@465 174 }
Chris@465 175 }
Chris@481 176
Chris@481 177 if (m_ok) {
Chris@481 178 m_knownGoodCaches[m_origin] = m_localFilename;
Chris@481 179 }
Chris@465 180 }
Chris@465 181
Chris@465 182 bool
Chris@465 183 CachedFile::retrieve()
Chris@465 184 {
Chris@465 185 //!!! need to work by retrieving the file to another name, and
Chris@465 186 //!!! then "atomically" moving it to its proper place (I'm not
Chris@465 187 //!!! sure we can do an atomic move to replace an existing file
Chris@465 188 //!!! using Qt classes, but a plain delete then copy is probably
Chris@465 189 //!!! good enough)
Chris@465 190
Chris@520 191 FileSource fs(m_origin, m_reporter, m_preferredContentType);
Chris@465 192
Chris@466 193 if (!fs.isOK() || !fs.isAvailable()) {
Chris@690 194 SVDEBUG << "CachedFile::retrieve: ERROR: FileSource reported unavailable or failure" << endl;
Chris@466 195 return false;
Chris@466 196 }
Chris@465 197
Chris@466 198 fs.waitForData();
Chris@465 199
Chris@466 200 if (!fs.isOK()) {
Chris@690 201 SVDEBUG << "CachedFile::retrieve: ERROR: FileSource reported failure during receive" << endl;
Chris@466 202 return false;
Chris@466 203 }
Chris@466 204
Chris@466 205 QString tempName = fs.getLocalFilename();
Chris@466 206 QFile tempFile(tempName);
Chris@466 207 if (!tempFile.exists()) {
Chris@690 208 SVDEBUG << "CachedFile::retrieve: ERROR: FileSource reported success, but local temporary file \"" << tempName << "\" does not exist" << endl;
Chris@466 209 return false;
Chris@466 210 }
Chris@466 211
Chris@466 212 QFile previous(m_localFilename);
Chris@466 213 if (previous.exists()) {
Chris@466 214 if (!previous.remove()) {
Chris@686 215 std::cerr << "CachedFile::retrieve: ERROR: Failed to remove previous copy of cached file at \"" << m_localFilename << "\"" << std::endl;
Chris@466 216 return false;
Chris@466 217 }
Chris@466 218 }
Chris@466 219
Chris@466 220 //!!! This is not ideal, could leave us with nothing (old file
Chris@466 221 //!!! removed, new file not able to be copied in because e.g. no
Chris@466 222 //!!! disk space left)
Chris@466 223
Chris@466 224 if (!tempFile.copy(m_localFilename)) {
Chris@686 225 std::cerr << "CachedFile::retrieve: ERROR: Failed to copy newly retrieved file from \"" << tempName << "\" to \"" << m_localFilename << "\"" << std::endl;
Chris@466 226 return false;
Chris@466 227 }
Chris@466 228
Chris@690 229 SVDEBUG << "CachedFile::retrieve: Successfully copied newly retrieved file \"" << tempName << "\" to its home at \"" << m_localFilename << "\"" << endl;
Chris@468 230
Chris@466 231 return true;
Chris@465 232 }
Chris@465 233
Chris@465 234 QDateTime
Chris@465 235 CachedFile::getLastRetrieval()
Chris@465 236 {
Chris@465 237 QSettings settings;
Chris@465 238 settings.beginGroup("FileCache");
Chris@465 239
Chris@465 240 QString key("last-retrieval-times");
Chris@465 241
Chris@465 242 QMap<QString, QVariant> timeMap = settings.value(key).toMap();
Chris@465 243 QDateTime lastTime = timeMap[m_localFilename].toDateTime();
Chris@465 244
Chris@465 245 settings.endGroup();
Chris@465 246 return lastTime;
Chris@465 247 }
Chris@465 248
Chris@465 249 void
Chris@465 250 CachedFile::updateLastRetrieval(bool successful)
Chris@465 251 {
Chris@465 252 //!!! note !successful does not mean "we failed to update the
Chris@465 253 //!!! file" (and so it remains the same as before); it means "the
Chris@465 254 //!!! file is not there at all"
Chris@465 255
Chris@465 256 QSettings settings;
Chris@465 257 settings.beginGroup("FileCache");
Chris@465 258
Chris@465 259 QString key("last-retrieval-times");
Chris@465 260
Chris@465 261 QMap<QString, QVariant> timeMap = settings.value(key).toMap();
Chris@465 262
Chris@465 263 QDateTime dt;
Chris@465 264 if (successful) dt = QDateTime::currentDateTime();
Chris@465 265
Chris@465 266 timeMap[m_localFilename] = dt;
Chris@465 267 settings.setValue(key, timeMap);
Chris@465 268
Chris@465 269 settings.endGroup();
Chris@465 270 }
Chris@465 271
Chris@465 272