Chromium Code Reviews| Index: content/browser/service_worker/service_worker_fetch_stores.cc |
| diff --git a/content/browser/service_worker/service_worker_fetch_stores.cc b/content/browser/service_worker/service_worker_fetch_stores.cc |
| index 643ed4493953533f54d5e43c06b3a1ded40013ea..835cf8b0621068c80e25205612c01a22cbd7c2bd 100644 |
| --- a/content/browser/service_worker/service_worker_fetch_stores.cc |
| +++ b/content/browser/service_worker/service_worker_fetch_stores.cc |
| @@ -6,17 +6,148 @@ |
| #include <string> |
| +#include "base/file_util.h" |
| +#include "base/files/memory_mapped_file.h" |
| +#include "base/pickle.h" |
| +#include "base/sha1.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/strings/string_util.h" |
| +#include "content/browser/service_worker/service_worker_fetch_store.h" |
| #include "content/public/browser/browser_thread.h" |
| +#include "net/base/directory_lister.h" |
| namespace content { |
| +// Handles the loading of ServiceWorkerFetchStore and any extra clean up other |
| +// than deleting the ServiceWorkerFetchStore object. |
| +class ServiceWorkerFetchStores::StoresLoader { |
| + public: |
| + virtual ~StoresLoader() {}; |
| + virtual ServiceWorkerFetchStore* LoadStore(const std::string& key) = 0; |
| + // Creates a new store, deleting any pre-existing store of the same name. |
| + virtual ServiceWorkerFetchStore* CreateStore(const std::string& key) = 0; |
| + virtual bool CleanUpDeletedStore(const std::string& key) = 0; |
| + virtual bool WriteIndex(StoreMap* stores) = 0; |
| + virtual void LoadIndex(std::vector<std::string>* names) = 0; |
| +}; |
| + |
| +class ServiceWorkerFetchStores::MemoryLoader |
| + : public ServiceWorkerFetchStores::StoresLoader { |
| + public: |
| + virtual content::ServiceWorkerFetchStore* LoadStore( |
| + const std::string& key) OVERRIDE { |
| + NOTREACHED(); |
| + return NULL; |
| + } |
| + |
| + virtual ServiceWorkerFetchStore* CreateStore( |
| + const std::string& key) OVERRIDE { |
| + return ServiceWorkerFetchStore::CreateMemoryStore(key); |
| + } |
| + |
| + virtual bool CleanUpDeletedStore(const std::string& key) OVERRIDE { |
| + return true; |
| + } |
| + |
| + virtual bool WriteIndex(StoreMap* stores) OVERRIDE { return false; } |
| + |
| + virtual void LoadIndex(std::vector<std::string>* names) OVERRIDE { return; } |
| +}; |
| + |
| +class ServiceWorkerFetchStores::SimpleCacheLoader |
| + : public ServiceWorkerFetchStores::StoresLoader { |
| + public: |
| + explicit SimpleCacheLoader(const base::FilePath& origin_path) |
| + : origin_path_(origin_path) {} |
| + |
| + virtual ServiceWorkerFetchStore* LoadStore(const std::string& key) OVERRIDE { |
| + base::CreateDirectory(CreatePersistentStorePath(origin_path_, key)); |
| + return ServiceWorkerFetchStore::CreatePersistentStore( |
| + CreatePersistentStorePath(origin_path_, key), key); |
| + } |
| + |
| + virtual ServiceWorkerFetchStore* CreateStore( |
| + const std::string& key) OVERRIDE { |
| + base::FilePath store_path = CreatePersistentStorePath(origin_path_, key); |
| + if (base::PathExists(store_path)) |
| + base::DeleteFile(store_path, /* recursive */ true); |
| + return LoadStore(key); |
| + } |
| + |
| + virtual bool CleanUpDeletedStore(const std::string& key) OVERRIDE { |
| + return base::DeleteFile(CreatePersistentStorePath(origin_path_, key), true); |
| + } |
| + |
| + virtual bool WriteIndex(StoreMap* stores) OVERRIDE { |
| + scoped_ptr<Pickle> pickle(new Pickle()); |
|
michaeln
2014/08/07 00:10:13
We prefer to use formats on disk that are endian a
jkarlin
2014/08/07 16:02:35
Done. Note I renamed the protobuf target from 'ser
|
| + |
| + for (StoreMap::const_iterator iter(stores); !iter.IsAtEnd(); |
| + iter.Advance()) { |
| + const ServiceWorkerFetchStore* store = iter.GetCurrentValue(); |
| + pickle->WriteString(store->name()); |
| + } |
| + |
| + base::FilePath tmp_path = origin_path_.AppendASCII("index.txt.tmp"); |
| + base::FilePath index_path = origin_path_.AppendASCII("index.txt"); |
| + |
| + int bytes_written = base::WriteFile( |
| + tmp_path, static_cast<const char*>(pickle->data()), pickle->size()); |
| + if (bytes_written != implicit_cast<int>(pickle->size())) { |
| + base::DeleteFile(tmp_path, /* recursive */ false); |
| + return false; |
| + } |
| + |
| + // Atomically rename the temporary index file to become the real one. |
| + return base::ReplaceFile(tmp_path, index_path, NULL); |
| + } |
| + |
| + virtual void LoadIndex(std::vector<std::string>* names) OVERRIDE { |
| + base::FilePath index_path = origin_path_.AppendASCII("index.txt"); |
| + base::MemoryMappedFile index_file_map; |
|
michaeln
2014/08/07 00:10:13
memmapped files seems like an odd choice here, is
jkarlin
2014/08/07 16:02:35
Yes, ReadFileToString is better. Thanks. Done.
|
| + if (!index_file_map.Initialize(index_path)) { |
| + base::DeleteFile(index_path, /* recursive */ false); |
| + return; |
| + } |
| + |
| + Pickle pickle(reinterpret_cast<const char*>(index_file_map.data()), |
| + index_file_map.length()); |
| + if (!pickle.data()) |
| + return; |
| + |
| + PickleIterator pickle_it(pickle); |
| + std::string name; |
| + while (pickle.ReadString(&pickle_it, &name)) |
| + names->push_back(name); |
| + return; |
| + |
| + // TODO(jkarlin): Delete stores that are in the directory and not returned |
| + // in LoadIndex. |
| + } |
| + |
| + private: |
| + std::string HexedHash(const std::string& value) { |
| + std::string value_hash = base::SHA1HashString(value); |
| + return StringToLowerASCII( |
| + base::HexEncode(value_hash.c_str(), value_hash.length())); |
| + } |
| + |
| + base::FilePath CreatePersistentStorePath(const base::FilePath& origin_path, |
| + const std::string& store_name) { |
| + return origin_path.AppendASCII(HexedHash(store_name)); |
| + } |
| + |
| + const base::FilePath origin_path_; |
| +}; |
| + |
| ServiceWorkerFetchStores::ServiceWorkerFetchStores( |
| const base::FilePath& path, |
| - BackendType backend_type, |
| + bool memory_only, |
| const scoped_refptr<base::MessageLoopProxy>& callback_loop) |
| - : origin_path_(path), |
| - backend_type_(backend_type), |
| - callback_loop_(callback_loop) { |
| + : initialized_(false), origin_path_(path), callback_loop_(callback_loop) { |
| + if (memory_only) |
| + stores_loader_.reset(new MemoryLoader()); |
| + else |
| + stores_loader_.reset(new SimpleCacheLoader(origin_path_)); |
| } |
| ServiceWorkerFetchStores::~ServiceWorkerFetchStores() { |
| @@ -25,47 +156,174 @@ ServiceWorkerFetchStores::~ServiceWorkerFetchStores() { |
| void ServiceWorkerFetchStores::CreateStore( |
| const std::string& key, |
| const StoreAndErrorCallback& callback) { |
| - // TODO(jkarlin): Implement this. |
| + LazyInit(); |
| + |
| + if (key.empty()) { |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_EMPTY_KEY)); |
| + return; |
| + } |
| + |
| + if (GetLoadedStore(key)) { |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_EXISTS)); |
| + return; |
| + } |
| + |
| + ServiceWorkerFetchStore* store = stores_loader_->CreateStore(key); |
| + |
| + if (!store) { |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_STORAGE)); |
| + return; |
| + } |
| + |
| + InitStore(store); |
| - callback_loop_->PostTask(FROM_HERE, |
| - base::Bind(callback, 0, FETCH_STORES_ERROR_EXISTS)); |
| - return; |
| + stores_loader_->WriteIndex(&store_map_); |
| + |
| + store->InitializeIfNeeded( |
| + base::Bind(&ServiceWorkerFetchStores::InitializeStoreCallback, |
| + base::Unretained(this), |
| + store, |
| + callback)); |
| + |
| + callback_loop_->PostTask( |
|
michaeln
2014/08/07 00:10:13
i think this is an extra call to 'callback' that p
jkarlin
2014/08/07 16:02:35
Done.
|
| + FROM_HERE, |
| + base::Bind(callback, store->id(), FETCH_STORES_ERROR_NO_ERROR)); |
| } |
| void ServiceWorkerFetchStores::Get(const std::string& key, |
|
michaeln
2014/08/07 00:10:13
Another thing that could help with Store vs Stores
jkarlin
2014/08/07 16:02:35
I renamed key -> store_name in the stores* classes
|
| const StoreAndErrorCallback& callback) { |
| - // TODO(jkarlin): Implement this. |
| + LazyInit(); |
| + |
| + if (key.empty()) { |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_EMPTY_KEY)); |
| + return; |
| + } |
| - callback_loop_->PostTask(FROM_HERE, |
| - base::Bind(callback, 0, FETCH_STORES_ERROR_EXISTS)); |
| - return; |
| + ServiceWorkerFetchStore* store = GetLoadedStore(key); |
| + if (!store) { |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_NOT_FOUND)); |
| + return; |
| + } |
| + |
| + store->InitializeIfNeeded( |
| + base::Bind(&ServiceWorkerFetchStores::InitializeStoreCallback, |
| + base::Unretained(this), |
| + store, |
| + callback)); |
| } |
| void ServiceWorkerFetchStores::Has(const std::string& key, |
| - const BoolAndErrorCallback& callback) const { |
| - // TODO(jkarlin): Implement this. |
| + const BoolAndErrorCallback& callback) { |
| + LazyInit(); |
| + |
| + if (key.empty()) { |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_EMPTY_KEY)); |
| + return; |
| + } |
| callback_loop_->PostTask( |
| - FROM_HERE, base::Bind(callback, false, FETCH_STORES_ERROR_EXISTS)); |
| - return; |
| + FROM_HERE, |
| + base::Bind(callback, GetLoadedStore(key), FETCH_STORES_ERROR_NO_ERROR)); |
| } |
| void ServiceWorkerFetchStores::Delete(const std::string& key, |
| - const StoreAndErrorCallback& callback) { |
| - // TODO(jkarlin): Implement this. |
| + const BoolAndErrorCallback& callback) { |
| + LazyInit(); |
| + |
| + if (key.empty()) { |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, false, FETCH_STORES_ERROR_EMPTY_KEY)); |
| + return; |
| + } |
| - callback_loop_->PostTask(FROM_HERE, |
| - base::Bind(callback, 0, FETCH_STORES_ERROR_EXISTS)); |
| - return; |
| + ServiceWorkerFetchStore* store = GetLoadedStore(key); |
| + if (!store) { |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, false, FETCH_STORES_ERROR_NOT_FOUND)); |
| + return; |
| + } |
| + |
| + name_map_.erase(key); |
| + store_map_.Remove(store->id()); // deletes store |
| + |
| + stores_loader_->WriteIndex(&store_map_); // Update the index. |
| + |
| + stores_loader_->CleanUpDeletedStore(key); |
| + |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, true, FETCH_STORES_ERROR_NO_ERROR)); |
| } |
| -void ServiceWorkerFetchStores::Keys( |
| - const StringsAndErrorCallback& callback) const { |
| - // TODO(jkarlin): Implement this. |
| - std::vector<std::string> out; |
| +void ServiceWorkerFetchStores::Keys(const StringsAndErrorCallback& callback) { |
| + LazyInit(); |
| + |
| + std::vector<std::string> names; |
| + for (NameMap::const_iterator it = name_map_.begin(); it != name_map_.end(); |
| + ++it) { |
| + names.push_back(it->first); |
| + } |
| + |
| callback_loop_->PostTask( |
| - FROM_HERE, base::Bind(callback, out, FETCH_STORES_ERROR_EXISTS)); |
| - return; |
| + FROM_HERE, base::Bind(callback, names, FETCH_STORES_ERROR_NO_ERROR)); |
| +} |
| + |
| +void ServiceWorkerFetchStores::InitializeStoreCallback( |
| + const ServiceWorkerFetchStore* store, |
| + const StoreAndErrorCallback& callback, |
| + bool success) { |
| + if (!success) { |
| + // TODO(jkarlin): This should delete the directory and try again in case |
| + // the cache is simply corrupt. |
| + callback_loop_->PostTask( |
| + FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_STORAGE)); |
| + return; |
| + } |
| + callback_loop_->PostTask( |
| + FROM_HERE, |
| + base::Bind(callback, store->id(), FETCH_STORES_ERROR_NO_ERROR)); |
| +} |
| + |
| +// Init is run lazily so that it is called on the proper MessageLoop. |
| +void ServiceWorkerFetchStores::LazyInit() { |
| + if (initialized_) |
| + return; |
| + |
| + std::vector<std::string> indexed_store_names; |
| + stores_loader_->LoadIndex(&indexed_store_names); |
| + |
| + for (std::vector<std::string>::const_iterator it = |
| + indexed_store_names.begin(); |
| + it != indexed_store_names.end(); |
| + ++it) { |
| + ServiceWorkerFetchStore* store = stores_loader_->LoadStore(*it); |
| + InitStore(store); |
| + } |
| + initialized_ = true; |
| +} |
| + |
| +void ServiceWorkerFetchStores::InitStore(ServiceWorkerFetchStore* store) { |
| + StoreID id = store_map_.Add(store); |
| + name_map_.insert(std::make_pair(store->name(), id)); |
| + store->set_id(id); |
| +} |
| + |
| +ServiceWorkerFetchStore* ServiceWorkerFetchStores::GetLoadedStore( |
| + const std::string& key) const { |
| + DCHECK(initialized_); |
| + |
| + NameMap::const_iterator it = name_map_.find(key); |
| + if (it == name_map_.end()) |
| + return NULL; |
| + |
| + ServiceWorkerFetchStore* store = store_map_.Lookup(it->second); |
| + DCHECK(store); |
| + return store; |
| } |
| } // namespace content |