281 lines
7.7 KiB
C++
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); }
|
|
//-----------------------------------------------------------------------------
|