Chris@465: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@465: Chris@465: /* Chris@465: Sonic Visualiser Chris@465: An audio file viewer and annotation editor. Chris@465: Centre for Digital Music, Queen Mary, University of London. Chris@465: This file copyright 2008 QMUL. Chris@465: Chris@465: This program is free software; you can redistribute it and/or Chris@465: modify it under the terms of the GNU General Public License as Chris@465: published by the Free Software Foundation; either version 2 of the Chris@465: License, or (at your option) any later version. See the file Chris@465: COPYING included with this distribution for more information. Chris@465: */ Chris@465: Chris@465: #include "CachedFile.h" Chris@465: Chris@465: #include "base/TempDirectory.h" Chris@465: #include "base/ProgressReporter.h" Chris@465: #include "base/Exceptions.h" Chris@465: Chris@466: #include "FileSource.h" Chris@466: Chris@465: #include Chris@465: #include Chris@465: #include Chris@465: #include Chris@465: #include Chris@465: #include Chris@465: Chris@481: #include "base/Profiler.h" Chris@481: Chris@466: #include Chris@466: Chris@481: CachedFile::OriginLocalFilenameMap Chris@481: CachedFile::m_knownGoodCaches; Chris@481: Chris@465: QString Chris@465: CachedFile::getLocalFilenameFor(QUrl url) Chris@465: { Chris@481: Profiler p("CachedFile::getLocalFilenameFor"); Chris@481: Chris@465: QDir dir(getCacheDirectory()); Chris@465: Chris@465: QString filename = Chris@465: QString::fromLocal8Bit Chris@465: (QCryptographicHash::hash(url.toString().toLocal8Bit(), Chris@465: QCryptographicHash::Sha1).toHex()); Chris@465: Chris@465: return dir.filePath(filename); Chris@465: } Chris@465: Chris@465: QString Chris@465: CachedFile::getCacheDirectory() Chris@465: { Chris@465: QDir dir = TempDirectory::getInstance()->getContainingPath(); Chris@465: Chris@465: QString cacheDirName("cache"); Chris@465: Chris@465: QFileInfo fi(dir.filePath(cacheDirName)); Chris@465: Chris@465: if ((fi.exists() && !fi.isDir()) || Chris@465: (!fi.exists() && !dir.mkdir(cacheDirName))) { Chris@465: Chris@465: throw DirectoryCreationFailed(fi.filePath()); Chris@465: } Chris@465: Chris@465: return fi.filePath(); Chris@465: } Chris@465: Chris@520: CachedFile::CachedFile(QString origin, Chris@520: ProgressReporter *reporter, Chris@520: QString preferredContentType) : Chris@468: m_origin(origin), Chris@520: m_preferredContentType(preferredContentType), Chris@466: m_reporter(reporter), Chris@465: m_ok(false) Chris@465: { Chris@481: Profiler p("CachedFile::CachedFile[1]"); Chris@481: Chris@690: SVDEBUG << "CachedFile::CachedFile: origin is \"" Chris@687: << origin << "\"" << endl; Chris@691: checkFile(); Chris@467: } Chris@467: Chris@520: CachedFile::CachedFile(QUrl url, Chris@520: ProgressReporter *reporter, Chris@520: QString preferredContentType) : Chris@468: m_origin(url.toString()), Chris@520: m_preferredContentType(preferredContentType), Chris@467: m_reporter(reporter), Chris@467: m_ok(false) Chris@467: { Chris@481: Profiler p("CachedFile::CachedFile[2]"); Chris@481: Chris@690: SVDEBUG << "CachedFile::CachedFile: url is \"" Chris@687: << url.toString() << "\"" << endl; Chris@691: checkFile(); Chris@467: } Chris@467: Chris@467: CachedFile::~CachedFile() Chris@467: { Chris@465: } Chris@465: Chris@465: bool Chris@465: CachedFile::isOK() const Chris@465: { Chris@465: return m_ok; Chris@465: } Chris@465: Chris@465: QString Chris@465: CachedFile::getLocalFilename() const Chris@465: { Chris@465: return m_localFilename; Chris@465: } Chris@465: Chris@465: void Chris@691: CachedFile::checkFile() Chris@465: { Chris@465: //!!! n.b. obvious race condition here if different CachedFile Chris@465: // objects for same url used in more than one thread -- need to Chris@465: // lock appropriately. also consider race condition between Chris@481: // separate instances of the program! Chris@481: Chris@481: OriginLocalFilenameMap::const_iterator i = m_knownGoodCaches.find(m_origin); Chris@481: if (i != m_knownGoodCaches.end()) { Chris@481: m_ok = true; Chris@481: m_localFilename = i->second; Chris@481: return; Chris@481: } Chris@481: Chris@481: m_localFilename = getLocalFilenameFor(m_origin); Chris@465: Chris@465: if (!QFileInfo(m_localFilename).exists()) { Chris@690: SVDEBUG << "CachedFile::check: Local file does not exist, making a note that it hasn't been retrieved" << endl; Chris@465: updateLastRetrieval(false); // empirically! Chris@465: } Chris@465: Chris@465: QDateTime lastRetrieval = getLastRetrieval(); Chris@465: Chris@465: if (lastRetrieval.isValid()) { Chris@690: SVDEBUG << "CachedFile::check: Valid last retrieval at " Chris@687: << lastRetrieval.toString() << endl; Chris@465: // this will not be the case if the file is missing, after Chris@465: // updateLastRetrieval(false) was called above Chris@465: m_ok = true; Chris@465: if (lastRetrieval.addDays(2) < QDateTime::currentDateTime()) { //!!! Chris@690: SVDEBUG << "CachedFile::check: Out of date; trying to retrieve again" << endl; Chris@465: // doesn't matter if retrieval fails -- we just don't Chris@465: // update the last retrieval time Chris@465: Chris@465: //!!! but we do want an additional last-attempted Chris@465: // timestamp so as to ensure we aren't retrying the Chris@465: // retrieval every single time if it isn't working Chris@465: Chris@465: if (retrieve()) { Chris@690: SVDEBUG << "CachedFile::check: Retrieval succeeded" << endl; Chris@465: updateLastRetrieval(true); Chris@467: } else { Chris@1486: SVCERR << "CachedFile::check: Retrieval failed, will try again later (using existing file for now)" << endl; Chris@467: } Chris@465: } Chris@465: } else { Chris@690: SVDEBUG << "CachedFile::check: No valid last retrieval" << endl; Chris@465: // there is no acceptable file Chris@465: if (retrieve()) { Chris@690: SVDEBUG << "CachedFile::check: Retrieval succeeded" << endl; Chris@465: m_ok = true; Chris@465: updateLastRetrieval(true); Chris@465: } else { Chris@1486: SVCERR << "CachedFile::check: Retrieval failed, remaining in invalid state" << endl; Chris@465: // again, we don't need to do anything here -- the last Chris@465: // retrieval timestamp is already invalid Chris@465: } Chris@465: } Chris@481: Chris@481: if (m_ok) { Chris@481: m_knownGoodCaches[m_origin] = m_localFilename; Chris@481: } Chris@465: } Chris@465: Chris@465: bool Chris@465: CachedFile::retrieve() Chris@465: { Chris@465: //!!! need to work by retrieving the file to another name, and Chris@465: //!!! then "atomically" moving it to its proper place (I'm not Chris@465: //!!! sure we can do an atomic move to replace an existing file Chris@465: //!!! using Qt classes, but a plain delete then copy is probably Chris@465: //!!! good enough) Chris@465: Chris@520: FileSource fs(m_origin, m_reporter, m_preferredContentType); Chris@465: Chris@466: if (!fs.isOK() || !fs.isAvailable()) { Chris@690: SVDEBUG << "CachedFile::retrieve: ERROR: FileSource reported unavailable or failure" << endl; Chris@466: return false; Chris@466: } Chris@465: Chris@466: fs.waitForData(); Chris@465: Chris@466: if (!fs.isOK()) { Chris@690: SVDEBUG << "CachedFile::retrieve: ERROR: FileSource reported failure during receive" << endl; Chris@466: return false; Chris@466: } Chris@466: Chris@466: QString tempName = fs.getLocalFilename(); Chris@466: QFile tempFile(tempName); Chris@466: if (!tempFile.exists()) { Chris@690: SVDEBUG << "CachedFile::retrieve: ERROR: FileSource reported success, but local temporary file \"" << tempName << "\" does not exist" << endl; Chris@466: return false; Chris@466: } Chris@466: Chris@466: QFile previous(m_localFilename); Chris@466: if (previous.exists()) { Chris@466: if (!previous.remove()) { Chris@1486: SVCERR << "CachedFile::retrieve: ERROR: Failed to remove previous copy of cached file at \"" << m_localFilename << "\"" << endl; Chris@466: return false; Chris@466: } Chris@466: } Chris@466: Chris@466: //!!! This is not ideal, could leave us with nothing (old file Chris@466: //!!! removed, new file not able to be copied in because e.g. no Chris@466: //!!! disk space left) Chris@466: Chris@466: if (!tempFile.copy(m_localFilename)) { Chris@1486: SVCERR << "CachedFile::retrieve: ERROR: Failed to copy newly retrieved file from \"" << tempName << "\" to \"" << m_localFilename << "\"" << endl; Chris@466: return false; Chris@466: } Chris@466: Chris@690: SVDEBUG << "CachedFile::retrieve: Successfully copied newly retrieved file \"" << tempName << "\" to its home at \"" << m_localFilename << "\"" << endl; Chris@468: Chris@466: return true; Chris@465: } Chris@465: Chris@465: QDateTime Chris@465: CachedFile::getLastRetrieval() Chris@465: { Chris@465: QSettings settings; Chris@465: settings.beginGroup("FileCache"); Chris@465: Chris@465: QString key("last-retrieval-times"); Chris@465: Chris@465: QMap timeMap = settings.value(key).toMap(); Chris@465: QDateTime lastTime = timeMap[m_localFilename].toDateTime(); Chris@465: Chris@465: settings.endGroup(); Chris@465: return lastTime; Chris@465: } Chris@465: Chris@465: void Chris@465: CachedFile::updateLastRetrieval(bool successful) Chris@465: { Chris@465: //!!! note !successful does not mean "we failed to update the Chris@465: //!!! file" (and so it remains the same as before); it means "the Chris@465: //!!! file is not there at all" Chris@465: Chris@465: QSettings settings; Chris@465: settings.beginGroup("FileCache"); Chris@465: Chris@465: QString key("last-retrieval-times"); Chris@465: Chris@465: QMap timeMap = settings.value(key).toMap(); Chris@465: Chris@465: QDateTime dt; Chris@465: if (successful) dt = QDateTime::currentDateTime(); Chris@465: Chris@465: timeMap[m_localFilename] = dt; Chris@465: settings.setValue(key, timeMap); Chris@465: Chris@465: settings.endGroup(); Chris@465: } Chris@465: Chris@465: