/** * Check if a file has already been cached. * @param cache_key Cache key. * @return Filename in the cache, or empty string if not found. */ string CacheManager::findInCache(const string &cache_key) { // Get the cache key filename. string cache_filename = getCacheFilename(cache_key); if (cache_filename.empty()) { // Error obtaining the cache key filename. return string(); } // Return the filename if the file exists. return (!access(cache_filename, R_OK) ? cache_filename : string()); }
/** * Download a file. * * @param url URL. * @param cache_key Cache key. * * If the file is present in the cache, the cached version * will be retrieved. Otherwise, the file will be downloaded. * * If the file was not found on the server, or it was not found * the last time it was requested, an empty string will be * returned, and a zero-byte file will be stored in the cache. * * @return Absolute path to the cached file. */ string CacheManager::download( const string &url, const string &cache_key) { // Check the main cache key. string cache_filename = getCacheFilename(cache_key); if (cache_filename.empty()) { // Error obtaining the cache key filename. return string(); } // Lock the semaphore to make sure we don't // download too many files at once. SemaphoreLocker locker(m_dlsem); // Check if the file already exists. if (!access(cache_filename, R_OK)) { // File exists. // Is it larger than 0 bytes? int64_t sz = filesize(cache_filename); if (sz == 0) { // File is 0 bytes, which indicates it didn't exist // on the server. If the file is older than a week, // try to redownload it. // TODO: Configurable time. // TODO: How should we handle errors? time_t filetime; if (get_mtime(cache_filename, &filetime) != 0) return string(); struct timeval systime; if (gettimeofday(&systime, nullptr) != 0) return string(); if ((systime.tv_sec - filetime) < (86400*7)) { // Less than a week old. return string(); } // More than a week old. // Delete the cache file and redownload it. if (delete_file(cache_filename) != 0) return string(); } else if (sz > 0) { // File is larger than 0 bytes, which indicates // it was cached successfully. return cache_filename; } } // Check if the URL is blank. // This is allowed for some databases that are only available offline. if (url.empty()) { // Blank URL. Don't try to download anything. // Don't mark the file as unavailable by creating a // 0-byte dummy file, either. return string(); } // Make sure the subdirectories exist. // NOTE: The filename portion MUST be kept in cache_filename, // since the last component is ignored by rmkdir(). if (rmkdir(cache_filename) != 0) { // Error creating subdirectories. return string(); } // TODO: Keep-alive cURL connections (one per server)? m_downloader->setUrl(url); m_downloader->setProxyUrl(m_proxyUrl); int ret = m_downloader->download(); // Write the file to the cache. unique_ptr<IRpFile> file(new RpFile(cache_filename, RpFile::FM_CREATE_WRITE)); if (ret != 0 || !file || !file->isOpen()) { // Error downloading the file, or error opening // the file in the local cache. // TODO: Only keep a negative cache if it's a 404. // Keep the cached file as a 0-byte file to indicate // a "negative" hit, but return an empty filename. return string(); } // Write the file. file->write((void*)m_downloader->data(), m_downloader->dataSize()); file->close(); // Set the file's mtime if it was obtained by the downloader. // TODO: IRpFile::set_mtime()? time_t mtime = m_downloader->mtime(); if (mtime >= 0) { set_mtime(cache_filename, mtime); } // Return the cache filename. return cache_filename; }
Readable* ModuleMap::getMapLoadingReadable(uint32 mapID, uint32 loadMapRequestType, const char* handshake, byte zoomlevel, MapSafeVector* loadedMaps ) { FileUtils::FilePtr file; MC2String cacheFileName = getCacheFilename(mapID, loadMapRequestType, zoomlevel); mc2dbg << "[ModuleMap]: Filename would be \"" << cacheFileName << '"' << endl ; bool useCache = cacheFileName.length(); if ( useCache ) { // If the cache filename exists we should try the cache. file.reset( fopen( cacheFileName.c_str(), "r" ) ); } TCPSocket* tcpsock = NULL; uint32 mapVersion = MAX_UINT32; uint32 generatorVersion = MAX_UINT32; if ( file.get() != NULL ) { mc2dbg << "[ModuleMap]: Cached map found" << endl; // Check versions of map in file. ScopedArray<byte> headerBuf( new byte[CACHED_MAP_HEADER_SIZE] ); uint32 fileMapVersion = MAX_UINT32; uint32 fileGeneratorVersion = MAX_UINT32; uint32 fileMapID = MAX_UINT32; if ( fread( headerBuf.get(), CACHED_MAP_HEADER_SIZE, 1, file.get() ) == 1 ) { DataBuffer dataBuf(headerBuf.get(), CACHED_MAP_HEADER_SIZE); uint32 first = dataBuf.readNextLong(); first = first; fileMapID = dataBuf.readNextLong(); fileMapVersion = dataBuf.readNextLong(); fileGeneratorVersion = dataBuf.readNextLong(); if ( fileMapID == mapID ) { mapVersion = fileMapVersion; generatorVersion = fileGeneratorVersion; } else { mc2dbg << "[ModuleMap]: Wrong mapid in file - was " << fileMapID << " should be " << mapID << endl; } } headerBuf.reset( NULL ); mc2dbg << "[ModuleMap]: Version from file 0x" << hex << fileMapVersion << ":" << dec << fileGeneratorVersion << endl; tcpsock = getMapLoadingSocket(mapID, loadMapRequestType, handshake, zoomlevel, loadedMaps, &mapVersion, &generatorVersion); mc2dbg << "[ModuleMap]: Version from getMapLoadingSocket " << hex << "0x" << mapVersion << dec << ":" << generatorVersion << endl; if ( (tcpsock == NULL ) && ( mapVersion == fileMapVersion ) && ( generatorVersion == fileGeneratorVersion ) ) { mc2dbg << "[ModuleMap]: Cached file OK" << endl; return new FileReadable(file.release()); } else { if ( tcpsock != NULL ) { mc2dbg << "[ModuleMap]: Versions differ (file/MM)." << endl; } else { mc2dbg << "[ModuleMap]: Wrong version or no socket." << endl; } } } else { mc2dbg << "[ModuleMap]: No cache file" << endl; // Just get the socket and load. tcpsock = getMapLoadingSocket(mapID, loadMapRequestType, handshake, zoomlevel, loadedMaps, &mapVersion, &generatorVersion); } if ( tcpsock != NULL && useCache ) { mc2dbg << "[ModuleMap]: Will write to cache" << endl; // We do not have the map cached or it may have been the wrong // version. int headerSize = 0; ScopedArray<byte> header( makeHeader( headerSize, mapID, mapVersion, generatorVersion) ); TCPSocketReadable* tsockRead = new TCPSocketReadable( tcpsock ); return new AutoWritingReadable( tsockRead, cacheFileName.c_str(), header.get(), headerSize ); } else if ( tcpsock ) { mc2dbg << "[ModuleMap]: Will not write to cache" << endl; return new TCPSocketReadable(tcpsock); } else { mc2dbg << "[ModuleMap]: No socket" << endl; // Loading failed return NULL; } }