/* ----------------------------------------------------------------------------- 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 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 data; if (!io->FileLoad(path, data)) return NULL; AutoPtr 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 *preloaded_data) { __LOG_V__ << "Update cache entry for '" << entry->path << "'.\n"; Array 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); } //-----------------------------------------------------------------------------