diff options
Diffstat (limited to 'src/input/cache/Manager.cxx')
-rw-r--r-- | src/input/cache/Manager.cxx | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/src/input/cache/Manager.cxx b/src/input/cache/Manager.cxx new file mode 100644 index 000000000..3ff51d88e --- /dev/null +++ b/src/input/cache/Manager.cxx @@ -0,0 +1,163 @@ +/* + * Copyright 2003-2019 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Manager.hxx" +#include "Config.hxx" +#include "Item.hxx" +#include "Lease.hxx" +#include "input/InputStream.hxx" +#include "fs/Traits.hxx" +#include "util/DeleteDisposer.hxx" + +#include <string.h> + +inline bool +InputCacheManager::ItemCompare::operator()(const InputCacheItem &a, + const char *b) const noexcept +{ + return strcmp(a.GetUri(), b) < 0; +} + +inline bool +InputCacheManager::ItemCompare::operator()(const char *a, + const InputCacheItem &b) const noexcept +{ + return strcmp(a, b.GetUri()) < 0; +} + +inline bool +InputCacheManager::ItemCompare::operator()(const InputCacheItem &a, + const InputCacheItem &b) const noexcept +{ + return strcmp(a.GetUri(), b.GetUri()) < 0; +} + +InputCacheManager::InputCacheManager(const InputCacheConfig &config) noexcept + :max_total_size(config.size) +{ +} + +InputCacheManager::~InputCacheManager() noexcept +{ + items_by_time.clear_and_dispose(DeleteDisposer()); +} + +bool +InputCacheManager::IsEligible(const InputStream &input) noexcept +{ + assert(input.IsReady()); + + return input.IsSeekable() && input.KnownSize() && + input.GetSize() > 0 && + input.GetSize() <= max_total_size / 2; +} + +bool +InputCacheManager::Contains(const char *uri) noexcept +{ + return Get(uri, false); +} + +InputCacheLease +InputCacheManager::Get(const char *uri, bool create) +{ + // TODO: allow caching remote files + if (!PathTraitsUTF8::IsAbsolute(uri)) + return {}; + + UriMap::insert_commit_data hint; + auto result = items_by_uri.insert_check(uri, items_by_uri.key_comp(), + hint); + if (!result.second) { + auto &item = *result.first; + + /* refresh */ + items_by_time.erase(items_by_time.iterator_to(item)); + items_by_time.push_back(item); + + // TODO revalidate the cache item using the file's mtime? + // TODO if cache item contains error, retry now? + + return InputCacheLease(item); + } + + if (!create) + return {}; + + // TODO: wait for "ready" without blocking here + auto is = InputStream::OpenReady(uri, mutex); + + if (!IsEligible(*is)) + return {}; + + const size_t size = is->GetSize(); + total_size += size; + + while (total_size > max_total_size && EvictOldestUnused()) {} + + auto *item = new InputCacheItem(std::move(is)); + items_by_uri.insert_commit(*item, hint); + items_by_time.push_back(*item); + + return InputCacheLease(*item); +} + +void +InputCacheManager::Prefetch(const char *uri) +{ + Get(uri, true); +} + +void +InputCacheManager::Remove(InputCacheItem &item) noexcept +{ + assert(total_size >= item.size()); + total_size -= item.size(); + + items_by_time.erase(items_by_time.iterator_to(item)); + items_by_uri.erase(items_by_uri.iterator_to(item)); +} + +void +InputCacheManager::Delete(InputCacheItem *item) noexcept +{ + Remove(*item); + delete item; +} + +InputCacheItem * +InputCacheManager::FindOldestUnused() noexcept +{ + for (auto &i : items_by_time) + if (!i.IsInUse()) + return &i; + + return nullptr; +} + +bool +InputCacheManager::EvictOldestUnused() noexcept +{ + auto *item = FindOldestUnused(); + if (item == nullptr) + return false; + + Delete(item); + return true; +} |