Files
Webcam/include/platform/filesystem/io_cache.cpp

281 lines
7.7 KiB
C++

/* -----------------------------------------------------------------------------
GSFramework
Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */
#include "filesystem/io_cache.h"
#include "hash/nsha1.h"
#include "platform.h"
#include "log/log.h"
#include "ntypes.h"
using namespace GS::IO;
//------------------------------------------------------------------------------
struct FreeEntry // should be in ReserveOnStore but local type on template is prohibited until C++0x
{
Cache::Entry *entry;
int score;
FreeEntry(Cache::Entry *e, int s) : entry(e), score(s) {}
static int ComputeScore(const Cache::Entry *e, size_t request_size, const GS::Time &ctime)
{
int time_bonus = (int)(ctime - e->last_use).toSec();
int size_bonus = e->size - request_size;
return time_bonus + size_bonus / 8;
}
static int CompareScore(FreeEntry *a, FreeEntry *b)
{ return b->score - a->score; }
};
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
GS::String Cache::ReserveOnStore(size_t size)
{
String id = store.Reserve(size);
if (!id.IsEmpty())
return id;
// Build a list of reference free entries.
Time ctime = Platform::Get().GetTime();
// No need to drop anything if the request can't fit anyway...
size_t total_freeable_store = 0;
ListForeachPtr(Entry *, e, entries)
if (e->refc == 0)
total_freeable_store += e->size;
if (size > (store.GetFreeStore() + total_freeable_store))
return "";
// Drop entries until the request fits in the store.
List <FreeEntry *> free_entries;
ListForeachPtr(Entry *, e, entries)
if (e->refc == 0)
free_entries.Add(new FreeEntry(e, FreeEntry::ComputeScore(e, size, ctime)));
free_entries.MergeSort(FreeEntry::CompareScore);
ListForeachPtr(FreeEntry *, e, free_entries)
{
__LOG_V__ << "IO::Cache: Disposing of cache entry '" << e->entry->path << "' (score: " << e->score << ").\n";
DeleteCacheEntry(e->entry);
id = store.Reserve(size);
if (!id.IsEmpty())
break;
}
ListDeleteAllPtr(FreeEntry *, free_entries)
store.Save();
return id;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
Cache::Entry *Cache::GetCacheEntry(const char *path) const
{
ListForeachPtr(Entry *, entry, entries)
if (entry->path == path)
return entry;
return NULL;
}
Cache::Entry *Cache::CreateCacheEntry(const char *path)
{
__LOG_V__ << "Create cache entry for '" << path << "'.\n";
Array <char> data;
if (!io->FileLoad(path, data))
return NULL;
AutoPtr <Entry> entry(new Entry);
if (entry.IsNull())
return NULL;
entry->path = path;
entry->id = ReserveOnStore(data.GetSize());
if (entry->id.IsEmpty())
return NULL; // store full
Entry *e = entry.Detach();
entries.Add(e);
return UpdateCacheEntry(e, &data) ? e : NULL;
}
bool Cache::UpdateCacheEntry(Entry *entry, GS::Array <char> *preloaded_data)
{
__LOG_V__ << "Update cache entry for '" << entry->path << "'.\n";
Array <char> data;
if (preloaded_data)
data.Transfer(*preloaded_data);
else
if (!io->FileLoad(entry->path, data))
return false;
// Check current store entry size.
if (store.GetEntrySize(entry->id) != data.GetSize())
{
store.Free(entry->id);
entry->id = ReserveOnStore(data.GetSize());
if (entry->id.IsEmpty())
return false; // store is full
}
// Update store data.
if (!store.Store(entry->id, data.c_ptr(), data.GetSize(), entry->path))
return false;
store.Save();
entry->hash = SHA1::ComputeHexa(data);
entry->size = data.GetSize();
return true;
}
bool Cache::DeleteCacheEntry(Entry *entry)
{
if (entry->refc > 0)
return false;
store.Free(entry->id);
store.Save();
return entries.Remove(entry);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
uint Cache::GetCaps() const { return io->GetCaps(); }
Handle *Cache::Open(const char *path, Mode mode)
{
if (mode == ModeRead)
{
Threading::MutexLock lock(&mutex);
// Update cache.
Entry *entry = GetCacheEntry(path);
if (entry)
{
__LOG_V__ << "Entry match for '" << path << "'.\n";
if (entry->hash != io->Hash(path))
{
__LOG_V__ << "Hash mismatch for '" << path << "'.\n";
if (entry->refc > 0)
__LOG_W__ << "IO::Cache: Support file '" << entry->path << "' has changed but its cached version is in use and cannot be updated.\n";
else
{
if (!UpdateCacheEntry(entry)) // contention risk here due to the mutex lock and a potentially long update (eg. networked fs)
DeleteCacheEntry(entry);
// Check for a match in updated cache.
entry = GetCacheEntry(path);
}
}
}
else
entry = CreateCacheEntry(path);
// Return handler to store fs.
if (entry)
{
__LOG_V__ << "Opening '" << path << "' from cache.\n";
entry->last_use = Platform::Get().GetTime();
entry->refc++;
return new CacheHandle(this, path, store.GetIO()->Open(entry->id, mode));
}
}
// Direct access to the underlying fs.
return io->Open(path, mode);
}
void Cache::Close(Handle *h)
{
if (CacheHandle *c_h = (CacheHandle *)h)
{
Threading::MutexLock lock(&mutex);
if (Entry *entry = GetCacheEntry(c_h->path))
entry->refc--;
c_h->handle = NULL;
}
}
bool Cache::Delete(const char *path)
{
/*
[EJ] Minor synchronization issue warning.
If a cached handle is already in use and the support fs file is deleted
the cached handle will remain valid. Further open requests will then
unexpectedly succeed as long as a single cached handler remains open.
The correct fix would be to prevent deleting a support file as long as
a cached entry with a non-zero reference count exists for it.
*/
return io->Delete(path);
}
size_t Cache::Tell(Handle *h)
{ return ((CacheHandle *)h)->handle->Tell(); }
size_t Cache::Seek(Handle *h, ptrdiff_t offset, SeekRef seek)
{ return ((CacheHandle *)h)->handle->Seek(offset, seek); }
size_t Cache::Read(Handle *h, void *data, size_t size)
{ return ((CacheHandle *)h)->handle->Read(data, size); }
size_t Cache::Write(Handle *h, const void *data, size_t size)
{ return ((CacheHandle *)h)->handle->Write(data, size); }
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool Cache::SynchronizeWithStore(const char *store_path)
{
if (!store.Load(store_path))
return false;
entries.Clear();
__LOG_H__ << "IO::Cache: Synchronizing with store.\n";
ListForeachPtr(DataStore::Entry *, e, store.GetEntries())
{
Entry *entry = new Entry;
entry->id = e->id;
entry->size = e->size;
entry->path = e->user;
entry->hash = store.GetIO()->Hash(entry->id);
entries.Add(entry);
}
__LOG__ << "Done, " << entries.GetCount() << " entries synchronized.\n";
return true;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool Cache::MkDir(const char *path)
{ return io->MkDir(path); }
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
Cache::Cache(Base *base, Base *store_io, size_t store_size) : io(base), store(store_io, store_size) {}
//------------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CacheHandle::~CacheHandle()
{ GetIOSystem()->Close(this); }
//-----------------------------------------------------------------------------