DiskImageCache.cpp [plain text]
#include "config.h"
#include "DiskImageCache.h"
#if ENABLE(DISK_IMAGE_CACHE)
#include "FileSystem.h"
#include "Logging.h"
#include "WebCoreThread.h"
#include "WebCoreThreadRun.h"
#include <errno.h>
#include <sys/mman.h>
#include <wtf/text/CString.h>
namespace WebCore {
DiskImageCache* diskImageCache()
{
DEFINE_STATIC_LOCAL(DiskImageCache, staticCache, ());
return &staticCache;
}
DiskImageCache::Entry::Entry(SharedBuffer* buffer, disk_cache_id_t id)
: m_buffer(buffer)
, m_id(id)
, m_size(0)
, m_mapping(0)
{
ASSERT(WebThreadIsCurrent());
ASSERT(WebThreadIsLocked());
ASSERT(m_buffer);
m_buffer->ref();
}
DiskImageCache::Entry::~Entry()
{
ASSERT(WebThreadIsCurrent());
ASSERT(WebThreadIsLocked());
ASSERT(!m_buffer);
ASSERT(!m_mapping);
}
bool DiskImageCache::Entry::mapInternal(const String& path)
{
ASSERT(m_buffer);
ASSERT(!m_mapping);
m_path = path;
m_size = m_buffer->size();
PlatformFileHandle handle = open(m_path.utf8().data(), O_CREAT | O_RDWR | O_TRUNC, (mode_t)0600);
if (!isHandleValid(handle))
return false;
if (writeToFile(handle, m_buffer->data(), m_size) == -1) {
closeFile(handle);
deleteFile(m_path);
return false;
}
if (seekFile(handle, 0, SeekFromBeginning) == -1) {
closeFile(handle);
deleteFile(m_path);
return false;
}
m_mapping = mmap(0, m_size, PROT_READ, MAP_SHARED, handle, 0);
closeFile(handle);
if (m_mapping == MAP_FAILED) {
LOG(DiskImageCache, "DiskImageCache: mapping failed (%d): (%s)", errno, strerror(errno));
m_mapping = NULL;
deleteFile(m_path);
return false;
}
return true;
}
void DiskImageCache::Entry::map(const String& path)
{
ASSERT(m_buffer);
ASSERT(!m_mapping);
DiskImageCache::Entry *thisEntry = this;
bool fileMapped = mapInternal(path);
if (!fileMapped) {
WebThreadRun(^{
m_buffer->failedMemoryMap();
m_buffer->deref();
m_buffer = 0;
thisEntry->deref();
});
return;
}
WebThreadRun(^{
m_buffer->markAsMemoryMapped();
m_buffer->deref();
m_buffer = 0;
thisEntry->deref();
});
}
void DiskImageCache::Entry::unmap()
{
if (!m_mapping) {
ASSERT(!m_size);
return;
}
if (munmap(m_mapping, m_size) == -1)
LOG_ERROR("DiskImageCache: Could not munmap a memory mapped file with id (%d)", m_id);
m_mapping = NULL;
m_size = 0;
}
void DiskImageCache::Entry::removeFile()
{
ASSERT(!m_mapping);
ASSERT(!m_size);
if (!deleteFile(m_path))
LOG_ERROR("DiskImageCache: Could not delete memory mapped file (%s)", m_path.utf8().data());
}
void DiskImageCache::Entry::clearDataWithoutMapping()
{
ASSERT(!m_mapping);
ASSERT(m_buffer);
m_buffer->deref();
m_buffer = 0;
}
DiskImageCache::DiskImageCache()
: m_enabled(false)
, m_size(0)
, m_maximumCacheSize(100 * 1024 * 1024)
, m_minimumImageSize(100 * 1024)
, m_nextAvailableId(DiskImageCache::invalidDiskCacheId + 1)
{
}
disk_cache_id_t DiskImageCache::writeItem(PassRefPtr<SharedBuffer> item)
{
if (!isEnabled() || !createDirectoryIfNeeded())
return DiskImageCache::invalidDiskCacheId;
if (isFull()) {
LOG(DiskImageCache, "DiskImageCache: could not process an item because the cache was full at (%d). The \"max\" being (%d)", m_size, m_maximumCacheSize);
return DiskImageCache::invalidDiskCacheId;
}
disk_cache_id_t id = nextAvailableId();
RefPtr<SharedBuffer> buffer = item;
RefPtr<DiskImageCache::Entry> entry = DiskImageCache::Entry::create(buffer.get(), id);
m_table.add(id, entry);
String path = temporaryFile();
LOG(DiskImageCache, "DiskImageCache: creating entry (%d) at (%s)", id, path.utf8().data());
if (path.isNull())
return DiskImageCache::invalidDiskCacheId;
DiskImageCache::Entry *localEntryForBlock = entry.get();
localEntryForBlock->ref();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (diskImageCache()->isFull()) {
WebThreadRun(^{
localEntryForBlock->clearDataWithoutMapping();
localEntryForBlock->deref();
});
return;
}
localEntryForBlock->map(path);
if (localEntryForBlock->isMapped())
diskImageCache()->updateSize(localEntryForBlock->size());
});
return id;
}
void DiskImageCache::updateSize(unsigned delta)
{
MutexLocker lock(m_mutex);
m_size += delta;
}
void DiskImageCache::removeItem(disk_cache_id_t id)
{
LOG(DiskImageCache, "DiskImageCache: removeItem (%d)", id);
RefPtr<DiskImageCache::Entry> entry = m_table.get(id);
m_table.remove(id);
if (!entry->isMapped())
return;
updateSize(-(entry->size()));
DiskImageCache::Entry *localEntryForBlock = entry.get();
localEntryForBlock->ref();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
localEntryForBlock->unmap();
localEntryForBlock->removeFile();
WebThreadRun(^{ localEntryForBlock->deref(); });
});
}
void* DiskImageCache::dataForItem(disk_cache_id_t id)
{
ASSERT(id);
RefPtr<DiskImageCache::Entry> entry = m_table.get(id);
ASSERT(entry->isMapped());
return entry->data();
}
bool DiskImageCache::createDirectoryIfNeeded()
{
if (!m_cacheDirectory.isNull())
return true;
m_cacheDirectory = temporaryDirectory();
LOG(DiskImageCache, "DiskImageCache: Created temporary directory (%s)", m_cacheDirectory.utf8().data());
if (m_cacheDirectory.isNull()) {
LOG_ERROR("DiskImageCache: could not create cache directory");
return false;
}
if (m_client)
m_client->didCreateDiskImageCacheDirectory(m_cacheDirectory);
return true;
}
disk_cache_id_t DiskImageCache::nextAvailableId()
{
disk_cache_id_t nextId = m_nextAvailableId;
m_nextAvailableId++;
return nextId;
}
}
#endif // ENABLE(DISK_IMAGE_CACHE)