annotate data/fileio/CachedFile.cpp @ 469:a8a7b8f698c8

* Revert revisions 1244 and 1245 on FileSource -- backing out its built-in persistent cache support, as CachedFile now does the same thing and will do so more properly
author Chris Cannam
date Tue, 28 Oct 2008 11:07:08 +0000
parents 70b333085952
children a82645e788fc
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@466 31 #include <iostream>
Chris@466 32
Chris@465 33 QString
Chris@465 34 CachedFile::getLocalFilenameFor(QUrl url)
Chris@465 35 {
Chris@465 36 QDir dir(getCacheDirectory());
Chris@465 37
Chris@465 38 QString filename =
Chris@465 39 QString::fromLocal8Bit
Chris@465 40 (QCryptographicHash::hash(url.toString().toLocal8Bit(),
Chris@465 41 QCryptographicHash::Sha1).toHex());
Chris@465 42
Chris@465 43 return dir.filePath(filename);
Chris@465 44 }
Chris@465 45
Chris@465 46 QString
Chris@465 47 CachedFile::getCacheDirectory()
Chris@465 48 {
Chris@465 49 QDir dir = TempDirectory::getInstance()->getContainingPath();
Chris@465 50
Chris@465 51 QString cacheDirName("cache");
Chris@465 52
Chris@465 53 QFileInfo fi(dir.filePath(cacheDirName));
Chris@465 54
Chris@465 55 if ((fi.exists() && !fi.isDir()) ||
Chris@465 56 (!fi.exists() && !dir.mkdir(cacheDirName))) {
Chris@465 57
Chris@465 58 throw DirectoryCreationFailed(fi.filePath());
Chris@465 59 }
Chris@465 60
Chris@465 61 return fi.filePath();
Chris@465 62 }
Chris@465 63
Chris@468 64 CachedFile::CachedFile(QString origin, ProgressReporter *reporter) :
Chris@468 65 m_origin(origin),
Chris@468 66 m_localFilename(getLocalFilenameFor(m_origin)),
Chris@466 67 m_reporter(reporter),
Chris@465 68 m_ok(false)
Chris@465 69 {
Chris@468 70 std::cerr << "CachedFile::CachedFile: origin is \""
Chris@468 71 << origin.toStdString() << "\"" << std::endl;
Chris@467 72 check();
Chris@467 73 }
Chris@467 74
Chris@467 75 CachedFile::CachedFile(QUrl url, ProgressReporter *reporter) :
Chris@468 76 m_origin(url.toString()),
Chris@468 77 m_localFilename(getLocalFilenameFor(m_origin)),
Chris@467 78 m_reporter(reporter),
Chris@467 79 m_ok(false)
Chris@467 80 {
Chris@467 81 std::cerr << "CachedFile::CachedFile: url is \""
Chris@467 82 << url.toString().toStdString() << "\"" << std::endl;
Chris@467 83 check();
Chris@467 84 }
Chris@467 85
Chris@467 86 CachedFile::~CachedFile()
Chris@467 87 {
Chris@465 88 }
Chris@465 89
Chris@465 90 bool
Chris@465 91 CachedFile::isOK() const
Chris@465 92 {
Chris@465 93 return m_ok;
Chris@465 94 }
Chris@465 95
Chris@465 96 QString
Chris@465 97 CachedFile::getLocalFilename() const
Chris@465 98 {
Chris@465 99 return m_localFilename;
Chris@465 100 }
Chris@465 101
Chris@465 102 void
Chris@467 103 CachedFile::check()
Chris@465 104 {
Chris@465 105 //!!! n.b. obvious race condition here if different CachedFile
Chris@465 106 // objects for same url used in more than one thread -- need to
Chris@465 107 // lock appropriately. also consider race condition between
Chris@465 108 // separate instances of the program
Chris@465 109
Chris@465 110 if (!QFileInfo(m_localFilename).exists()) {
Chris@467 111 std::cerr << "CachedFile::check: Local file does not exist, making a note that it hasn't been retrieved" << std::endl;
Chris@465 112 updateLastRetrieval(false); // empirically!
Chris@465 113 }
Chris@465 114
Chris@465 115 QDateTime lastRetrieval = getLastRetrieval();
Chris@465 116
Chris@465 117 if (lastRetrieval.isValid()) {
Chris@467 118 std::cerr << "CachedFile::check: Valid last retrieval at "
Chris@467 119 << lastRetrieval.toString().toStdString() << std::endl;
Chris@465 120 // this will not be the case if the file is missing, after
Chris@465 121 // updateLastRetrieval(false) was called above
Chris@465 122 m_ok = true;
Chris@465 123 if (lastRetrieval.addDays(2) < QDateTime::currentDateTime()) { //!!!
Chris@467 124 std::cerr << "CachedFile::check: Out of date; trying to retrieve again" << std::endl;
Chris@465 125 // doesn't matter if retrieval fails -- we just don't
Chris@465 126 // update the last retrieval time
Chris@465 127
Chris@465 128 //!!! but we do want an additional last-attempted
Chris@465 129 // timestamp so as to ensure we aren't retrying the
Chris@465 130 // retrieval every single time if it isn't working
Chris@465 131
Chris@465 132 if (retrieve()) {
Chris@467 133 std::cerr << "CachedFile::check: Retrieval succeeded" << std::endl;
Chris@465 134 updateLastRetrieval(true);
Chris@467 135 } else {
Chris@467 136 std::cerr << "CachedFile::check: Retrieval failed, will try again later (using existing file for now)" << std::endl;
Chris@467 137 }
Chris@465 138 }
Chris@465 139 } else {
Chris@467 140 std::cerr << "CachedFile::check: No valid last retrieval" << std::endl;
Chris@465 141 // there is no acceptable file
Chris@465 142 if (retrieve()) {
Chris@467 143 std::cerr << "CachedFile::check: Retrieval succeeded" << std::endl;
Chris@465 144 m_ok = true;
Chris@465 145 updateLastRetrieval(true);
Chris@465 146 } else {
Chris@467 147 std::cerr << "CachedFile::check: Retrieval failed, remaining in invalid state" << std::endl;
Chris@465 148 // again, we don't need to do anything here -- the last
Chris@465 149 // retrieval timestamp is already invalid
Chris@465 150 }
Chris@465 151 }
Chris@465 152 }
Chris@465 153
Chris@465 154 bool
Chris@465 155 CachedFile::retrieve()
Chris@465 156 {
Chris@465 157 //!!! need to work by retrieving the file to another name, and
Chris@465 158 //!!! then "atomically" moving it to its proper place (I'm not
Chris@465 159 //!!! sure we can do an atomic move to replace an existing file
Chris@465 160 //!!! using Qt classes, but a plain delete then copy is probably
Chris@465 161 //!!! good enough)
Chris@465 162
Chris@468 163 FileSource fs(m_origin, m_reporter);
Chris@465 164
Chris@466 165 if (!fs.isOK() || !fs.isAvailable()) {
Chris@468 166 std::cerr << "CachedFile::retrieve: ERROR: FileSource reported unavailable or failure" << std::endl;
Chris@466 167 return false;
Chris@466 168 }
Chris@465 169
Chris@466 170 fs.waitForData();
Chris@465 171
Chris@466 172 if (!fs.isOK()) {
Chris@468 173 std::cerr << "CachedFile::retrieve: ERROR: FileSource reported failure during receive" << std::endl;
Chris@466 174 return false;
Chris@466 175 }
Chris@466 176
Chris@466 177 QString tempName = fs.getLocalFilename();
Chris@466 178 QFile tempFile(tempName);
Chris@466 179 if (!tempFile.exists()) {
Chris@466 180 std::cerr << "CachedFile::retrieve: ERROR: FileSource reported success, but local temporary file \"" << tempName.toStdString() << "\" does not exist" << std::endl;
Chris@466 181 return false;
Chris@466 182 }
Chris@466 183
Chris@466 184 QFile previous(m_localFilename);
Chris@466 185 if (previous.exists()) {
Chris@466 186 if (!previous.remove()) {
Chris@466 187 std::cerr << "CachedFile::retrieve: ERROR: Failed to remove previous copy of cached file at \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@466 188 return false;
Chris@466 189 }
Chris@466 190 }
Chris@466 191
Chris@466 192 //!!! This is not ideal, could leave us with nothing (old file
Chris@466 193 //!!! removed, new file not able to be copied in because e.g. no
Chris@466 194 //!!! disk space left)
Chris@466 195
Chris@466 196 if (!tempFile.copy(m_localFilename)) {
Chris@466 197 std::cerr << "CachedFile::retrieve: ERROR: Failed to copy newly retrieved file from \"" << tempName.toStdString() << "\" to \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@466 198 return false;
Chris@466 199 }
Chris@466 200
Chris@468 201 std::cerr << "CachedFile::retrieve: Successfully copied newly retrieved file \"" << tempName.toStdString() << "\" to its home at \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@468 202
Chris@466 203 return true;
Chris@465 204 }
Chris@465 205
Chris@465 206 QDateTime
Chris@465 207 CachedFile::getLastRetrieval()
Chris@465 208 {
Chris@465 209 QSettings settings;
Chris@465 210 settings.beginGroup("FileCache");
Chris@465 211
Chris@465 212 QString key("last-retrieval-times");
Chris@465 213
Chris@465 214 QMap<QString, QVariant> timeMap = settings.value(key).toMap();
Chris@465 215 QDateTime lastTime = timeMap[m_localFilename].toDateTime();
Chris@465 216
Chris@465 217 settings.endGroup();
Chris@465 218 return lastTime;
Chris@465 219 }
Chris@465 220
Chris@465 221 void
Chris@465 222 CachedFile::updateLastRetrieval(bool successful)
Chris@465 223 {
Chris@465 224 //!!! note !successful does not mean "we failed to update the
Chris@465 225 //!!! file" (and so it remains the same as before); it means "the
Chris@465 226 //!!! file is not there at all"
Chris@465 227
Chris@465 228 QSettings settings;
Chris@465 229 settings.beginGroup("FileCache");
Chris@465 230
Chris@465 231 QString key("last-retrieval-times");
Chris@465 232
Chris@465 233 QMap<QString, QVariant> timeMap = settings.value(key).toMap();
Chris@465 234
Chris@465 235 QDateTime dt;
Chris@465 236 if (successful) dt = QDateTime::currentDateTime();
Chris@465 237
Chris@465 238 timeMap[m_localFilename] = dt;
Chris@465 239 settings.setValue(key, timeMap);
Chris@465 240
Chris@465 241 settings.endGroup();
Chris@465 242 }
Chris@465 243
Chris@465 244