/**
 * 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;
   }
}