OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/service_worker/service_worker_fetch_stores.h" | 5 #include "content/browser/service_worker/service_worker_fetch_stores.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 | 8 |
9 #include "base/file_util.h" | |
10 #include "base/files/memory_mapped_file.h" | |
11 #include "base/pickle.h" | |
12 #include "base/sha1.h" | |
13 #include "base/strings/string_number_conversions.h" | |
14 #include "base/strings/string_util.h" | |
15 #include "content/browser/service_worker/service_worker_fetch_store.h" | |
9 #include "content/public/browser/browser_thread.h" | 16 #include "content/public/browser/browser_thread.h" |
17 #include "net/base/directory_lister.h" | |
10 | 18 |
11 namespace content { | 19 namespace content { |
12 | 20 |
21 // Handles the loading of ServiceWorkerFetchStore and any extra clean up other | |
22 // than deleting the ServiceWorkerFetchStore object. | |
23 class ServiceWorkerFetchStores::StoresLoader { | |
24 public: | |
25 virtual ~StoresLoader() {}; | |
26 virtual ServiceWorkerFetchStore* LoadStore(const std::string& key) = 0; | |
27 // Creates a new store, deleting any pre-existing store of the same name. | |
28 virtual ServiceWorkerFetchStore* CreateStore(const std::string& key) = 0; | |
29 virtual bool CleanUpDeletedStore(const std::string& key) = 0; | |
30 virtual bool WriteIndex(StoreMap* stores) = 0; | |
31 virtual void LoadIndex(std::vector<std::string>* names) = 0; | |
32 }; | |
33 | |
34 class ServiceWorkerFetchStores::MemoryLoader | |
35 : public ServiceWorkerFetchStores::StoresLoader { | |
36 public: | |
37 virtual content::ServiceWorkerFetchStore* LoadStore( | |
38 const std::string& key) OVERRIDE { | |
39 NOTREACHED(); | |
40 return NULL; | |
41 } | |
42 | |
43 virtual ServiceWorkerFetchStore* CreateStore( | |
44 const std::string& key) OVERRIDE { | |
45 return ServiceWorkerFetchStore::CreateMemoryStore(key); | |
46 } | |
47 | |
48 virtual bool CleanUpDeletedStore(const std::string& key) OVERRIDE { | |
49 return true; | |
50 } | |
51 | |
52 virtual bool WriteIndex(StoreMap* stores) OVERRIDE { return false; } | |
53 | |
54 virtual void LoadIndex(std::vector<std::string>* names) OVERRIDE { return; } | |
55 }; | |
56 | |
57 class ServiceWorkerFetchStores::SimpleCacheLoader | |
58 : public ServiceWorkerFetchStores::StoresLoader { | |
59 public: | |
60 explicit SimpleCacheLoader(const base::FilePath& origin_path) | |
61 : origin_path_(origin_path) {} | |
62 | |
63 virtual ServiceWorkerFetchStore* LoadStore(const std::string& key) OVERRIDE { | |
64 base::CreateDirectory(CreatePersistentStorePath(origin_path_, key)); | |
65 return ServiceWorkerFetchStore::CreatePersistentStore( | |
66 CreatePersistentStorePath(origin_path_, key), key); | |
67 } | |
68 | |
69 virtual ServiceWorkerFetchStore* CreateStore( | |
70 const std::string& key) OVERRIDE { | |
71 base::FilePath store_path = CreatePersistentStorePath(origin_path_, key); | |
72 if (base::PathExists(store_path)) | |
73 base::DeleteFile(store_path, /* recursive */ true); | |
74 return LoadStore(key); | |
75 } | |
76 | |
77 virtual bool CleanUpDeletedStore(const std::string& key) OVERRIDE { | |
78 return base::DeleteFile(CreatePersistentStorePath(origin_path_, key), true); | |
79 } | |
80 | |
81 virtual bool WriteIndex(StoreMap* stores) OVERRIDE { | |
82 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
| |
83 | |
84 for (StoreMap::const_iterator iter(stores); !iter.IsAtEnd(); | |
85 iter.Advance()) { | |
86 const ServiceWorkerFetchStore* store = iter.GetCurrentValue(); | |
87 pickle->WriteString(store->name()); | |
88 } | |
89 | |
90 base::FilePath tmp_path = origin_path_.AppendASCII("index.txt.tmp"); | |
91 base::FilePath index_path = origin_path_.AppendASCII("index.txt"); | |
92 | |
93 int bytes_written = base::WriteFile( | |
94 tmp_path, static_cast<const char*>(pickle->data()), pickle->size()); | |
95 if (bytes_written != implicit_cast<int>(pickle->size())) { | |
96 base::DeleteFile(tmp_path, /* recursive */ false); | |
97 return false; | |
98 } | |
99 | |
100 // Atomically rename the temporary index file to become the real one. | |
101 return base::ReplaceFile(tmp_path, index_path, NULL); | |
102 } | |
103 | |
104 virtual void LoadIndex(std::vector<std::string>* names) OVERRIDE { | |
105 base::FilePath index_path = origin_path_.AppendASCII("index.txt"); | |
106 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.
| |
107 if (!index_file_map.Initialize(index_path)) { | |
108 base::DeleteFile(index_path, /* recursive */ false); | |
109 return; | |
110 } | |
111 | |
112 Pickle pickle(reinterpret_cast<const char*>(index_file_map.data()), | |
113 index_file_map.length()); | |
114 if (!pickle.data()) | |
115 return; | |
116 | |
117 PickleIterator pickle_it(pickle); | |
118 std::string name; | |
119 while (pickle.ReadString(&pickle_it, &name)) | |
120 names->push_back(name); | |
121 return; | |
122 | |
123 // TODO(jkarlin): Delete stores that are in the directory and not returned | |
124 // in LoadIndex. | |
125 } | |
126 | |
127 private: | |
128 std::string HexedHash(const std::string& value) { | |
129 std::string value_hash = base::SHA1HashString(value); | |
130 return StringToLowerASCII( | |
131 base::HexEncode(value_hash.c_str(), value_hash.length())); | |
132 } | |
133 | |
134 base::FilePath CreatePersistentStorePath(const base::FilePath& origin_path, | |
135 const std::string& store_name) { | |
136 return origin_path.AppendASCII(HexedHash(store_name)); | |
137 } | |
138 | |
139 const base::FilePath origin_path_; | |
140 }; | |
141 | |
13 ServiceWorkerFetchStores::ServiceWorkerFetchStores( | 142 ServiceWorkerFetchStores::ServiceWorkerFetchStores( |
14 const base::FilePath& path, | 143 const base::FilePath& path, |
15 BackendType backend_type, | 144 bool memory_only, |
16 const scoped_refptr<base::MessageLoopProxy>& callback_loop) | 145 const scoped_refptr<base::MessageLoopProxy>& callback_loop) |
17 : origin_path_(path), | 146 : initialized_(false), origin_path_(path), callback_loop_(callback_loop) { |
18 backend_type_(backend_type), | 147 if (memory_only) |
19 callback_loop_(callback_loop) { | 148 stores_loader_.reset(new MemoryLoader()); |
149 else | |
150 stores_loader_.reset(new SimpleCacheLoader(origin_path_)); | |
20 } | 151 } |
21 | 152 |
22 ServiceWorkerFetchStores::~ServiceWorkerFetchStores() { | 153 ServiceWorkerFetchStores::~ServiceWorkerFetchStores() { |
23 } | 154 } |
24 | 155 |
25 void ServiceWorkerFetchStores::CreateStore( | 156 void ServiceWorkerFetchStores::CreateStore( |
26 const std::string& key, | 157 const std::string& key, |
27 const StoreAndErrorCallback& callback) { | 158 const StoreAndErrorCallback& callback) { |
28 // TODO(jkarlin): Implement this. | 159 LazyInit(); |
29 | 160 |
30 callback_loop_->PostTask(FROM_HERE, | 161 if (key.empty()) { |
31 base::Bind(callback, 0, FETCH_STORES_ERROR_EXISTS)); | 162 callback_loop_->PostTask( |
32 return; | 163 FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_EMPTY_KEY)); |
164 return; | |
165 } | |
166 | |
167 if (GetLoadedStore(key)) { | |
168 callback_loop_->PostTask( | |
169 FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_EXISTS)); | |
170 return; | |
171 } | |
172 | |
173 ServiceWorkerFetchStore* store = stores_loader_->CreateStore(key); | |
174 | |
175 if (!store) { | |
176 callback_loop_->PostTask( | |
177 FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_STORAGE)); | |
178 return; | |
179 } | |
180 | |
181 InitStore(store); | |
182 | |
183 stores_loader_->WriteIndex(&store_map_); | |
184 | |
185 store->InitializeIfNeeded( | |
186 base::Bind(&ServiceWorkerFetchStores::InitializeStoreCallback, | |
187 base::Unretained(this), | |
188 store, | |
189 callback)); | |
190 | |
191 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.
| |
192 FROM_HERE, | |
193 base::Bind(callback, store->id(), FETCH_STORES_ERROR_NO_ERROR)); | |
33 } | 194 } |
34 | 195 |
35 void ServiceWorkerFetchStores::Get(const std::string& key, | 196 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
| |
36 const StoreAndErrorCallback& callback) { | 197 const StoreAndErrorCallback& callback) { |
37 // TODO(jkarlin): Implement this. | 198 LazyInit(); |
38 | 199 |
39 callback_loop_->PostTask(FROM_HERE, | 200 if (key.empty()) { |
40 base::Bind(callback, 0, FETCH_STORES_ERROR_EXISTS)); | 201 callback_loop_->PostTask( |
41 return; | 202 FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_EMPTY_KEY)); |
203 return; | |
204 } | |
205 | |
206 ServiceWorkerFetchStore* store = GetLoadedStore(key); | |
207 if (!store) { | |
208 callback_loop_->PostTask( | |
209 FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_NOT_FOUND)); | |
210 return; | |
211 } | |
212 | |
213 store->InitializeIfNeeded( | |
214 base::Bind(&ServiceWorkerFetchStores::InitializeStoreCallback, | |
215 base::Unretained(this), | |
216 store, | |
217 callback)); | |
42 } | 218 } |
43 | 219 |
44 void ServiceWorkerFetchStores::Has(const std::string& key, | 220 void ServiceWorkerFetchStores::Has(const std::string& key, |
45 const BoolAndErrorCallback& callback) const { | 221 const BoolAndErrorCallback& callback) { |
46 // TODO(jkarlin): Implement this. | 222 LazyInit(); |
223 | |
224 if (key.empty()) { | |
225 callback_loop_->PostTask( | |
226 FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_EMPTY_KEY)); | |
227 return; | |
228 } | |
47 | 229 |
48 callback_loop_->PostTask( | 230 callback_loop_->PostTask( |
49 FROM_HERE, base::Bind(callback, false, FETCH_STORES_ERROR_EXISTS)); | 231 FROM_HERE, |
50 return; | 232 base::Bind(callback, GetLoadedStore(key), FETCH_STORES_ERROR_NO_ERROR)); |
51 } | 233 } |
52 | 234 |
53 void ServiceWorkerFetchStores::Delete(const std::string& key, | 235 void ServiceWorkerFetchStores::Delete(const std::string& key, |
54 const StoreAndErrorCallback& callback) { | 236 const BoolAndErrorCallback& callback) { |
55 // TODO(jkarlin): Implement this. | 237 LazyInit(); |
56 | 238 |
57 callback_loop_->PostTask(FROM_HERE, | 239 if (key.empty()) { |
58 base::Bind(callback, 0, FETCH_STORES_ERROR_EXISTS)); | 240 callback_loop_->PostTask( |
59 return; | 241 FROM_HERE, base::Bind(callback, false, FETCH_STORES_ERROR_EMPTY_KEY)); |
242 return; | |
243 } | |
244 | |
245 ServiceWorkerFetchStore* store = GetLoadedStore(key); | |
246 if (!store) { | |
247 callback_loop_->PostTask( | |
248 FROM_HERE, base::Bind(callback, false, FETCH_STORES_ERROR_NOT_FOUND)); | |
249 return; | |
250 } | |
251 | |
252 name_map_.erase(key); | |
253 store_map_.Remove(store->id()); // deletes store | |
254 | |
255 stores_loader_->WriteIndex(&store_map_); // Update the index. | |
256 | |
257 stores_loader_->CleanUpDeletedStore(key); | |
258 | |
259 callback_loop_->PostTask( | |
260 FROM_HERE, base::Bind(callback, true, FETCH_STORES_ERROR_NO_ERROR)); | |
60 } | 261 } |
61 | 262 |
62 void ServiceWorkerFetchStores::Keys( | 263 void ServiceWorkerFetchStores::Keys(const StringsAndErrorCallback& callback) { |
63 const StringsAndErrorCallback& callback) const { | 264 LazyInit(); |
64 // TODO(jkarlin): Implement this. | 265 |
65 std::vector<std::string> out; | 266 std::vector<std::string> names; |
267 for (NameMap::const_iterator it = name_map_.begin(); it != name_map_.end(); | |
268 ++it) { | |
269 names.push_back(it->first); | |
270 } | |
271 | |
66 callback_loop_->PostTask( | 272 callback_loop_->PostTask( |
67 FROM_HERE, base::Bind(callback, out, FETCH_STORES_ERROR_EXISTS)); | 273 FROM_HERE, base::Bind(callback, names, FETCH_STORES_ERROR_NO_ERROR)); |
68 return; | 274 } |
275 | |
276 void ServiceWorkerFetchStores::InitializeStoreCallback( | |
277 const ServiceWorkerFetchStore* store, | |
278 const StoreAndErrorCallback& callback, | |
279 bool success) { | |
280 if (!success) { | |
281 // TODO(jkarlin): This should delete the directory and try again in case | |
282 // the cache is simply corrupt. | |
283 callback_loop_->PostTask( | |
284 FROM_HERE, base::Bind(callback, 0, FETCH_STORES_ERROR_STORAGE)); | |
285 return; | |
286 } | |
287 callback_loop_->PostTask( | |
288 FROM_HERE, | |
289 base::Bind(callback, store->id(), FETCH_STORES_ERROR_NO_ERROR)); | |
290 } | |
291 | |
292 // Init is run lazily so that it is called on the proper MessageLoop. | |
293 void ServiceWorkerFetchStores::LazyInit() { | |
294 if (initialized_) | |
295 return; | |
296 | |
297 std::vector<std::string> indexed_store_names; | |
298 stores_loader_->LoadIndex(&indexed_store_names); | |
299 | |
300 for (std::vector<std::string>::const_iterator it = | |
301 indexed_store_names.begin(); | |
302 it != indexed_store_names.end(); | |
303 ++it) { | |
304 ServiceWorkerFetchStore* store = stores_loader_->LoadStore(*it); | |
305 InitStore(store); | |
306 } | |
307 initialized_ = true; | |
308 } | |
309 | |
310 void ServiceWorkerFetchStores::InitStore(ServiceWorkerFetchStore* store) { | |
311 StoreID id = store_map_.Add(store); | |
312 name_map_.insert(std::make_pair(store->name(), id)); | |
313 store->set_id(id); | |
314 } | |
315 | |
316 ServiceWorkerFetchStore* ServiceWorkerFetchStores::GetLoadedStore( | |
317 const std::string& key) const { | |
318 DCHECK(initialized_); | |
319 | |
320 NameMap::const_iterator it = name_map_.find(key); | |
321 if (it == name_map_.end()) | |
322 return NULL; | |
323 | |
324 ServiceWorkerFetchStore* store = store_map_.Lookup(it->second); | |
325 DCHECK(store); | |
326 return store; | |
69 } | 327 } |
70 | 328 |
71 } // namespace content | 329 } // namespace content |
OLD | NEW |