blob: d0e540539fd8b87f205cfd9e17e7c23c13d3d787 [file] [log] [blame]
henrike@webrtc.orgf0488722014-05-13 18:00:26 +00001/*
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include <time.h>
12
13#if defined(WEBRTC_WIN)
14#include "webrtc/base/win32.h"
15#endif
16
andresp@webrtc.orgff689be2015-02-12 11:54:26 +000017#include <algorithm>
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000018#include "webrtc/base/common.h"
19#include "webrtc/base/diskcache.h"
20#include "webrtc/base/fileutils.h"
21#include "webrtc/base/pathutils.h"
22#include "webrtc/base/stream.h"
23#include "webrtc/base/stringencode.h"
24#include "webrtc/base/stringutils.h"
25
26#ifdef _DEBUG
27#define TRANSPARENT_CACHE_NAMES 1
28#else // !_DEBUG
29#define TRANSPARENT_CACHE_NAMES 0
30#endif // !_DEBUG
31
32namespace rtc {
33
34class DiskCache;
35
36///////////////////////////////////////////////////////////////////////////////
37// DiskCacheAdapter
38///////////////////////////////////////////////////////////////////////////////
39
40class DiskCacheAdapter : public StreamAdapterInterface {
41public:
42 DiskCacheAdapter(const DiskCache* cache, const std::string& id, size_t index,
43 StreamInterface* stream)
44 : StreamAdapterInterface(stream), cache_(cache), id_(id), index_(index)
45 { }
46 virtual ~DiskCacheAdapter() {
47 Close();
48 cache_->ReleaseResource(id_, index_);
49 }
50
51private:
52 const DiskCache* cache_;
53 std::string id_;
54 size_t index_;
55};
56
57///////////////////////////////////////////////////////////////////////////////
58// DiskCache
59///////////////////////////////////////////////////////////////////////////////
60
61DiskCache::DiskCache() : max_cache_(0), total_size_(0), total_accessors_(0) {
62}
63
64DiskCache::~DiskCache() {
65 ASSERT(0 == total_accessors_);
66}
67
68bool DiskCache::Initialize(const std::string& folder, size_t size) {
69 if (!folder_.empty() || !Filesystem::CreateFolder(folder))
70 return false;
71
72 folder_ = folder;
73 max_cache_ = size;
74 ASSERT(0 == total_size_);
75
76 if (!InitializeEntries())
77 return false;
78
79 return CheckLimit();
80}
81
82bool DiskCache::Purge() {
83 if (folder_.empty())
84 return false;
85
86 if (total_accessors_ > 0) {
87 LOG_F(LS_WARNING) << "Cache files open";
88 return false;
89 }
90
91 if (!PurgeFiles())
92 return false;
93
94 map_.clear();
95 return true;
96}
97
98bool DiskCache::LockResource(const std::string& id) {
99 Entry* entry = GetOrCreateEntry(id, true);
100 if (LS_LOCKED == entry->lock_state)
101 return false;
102 if ((LS_UNLOCKED == entry->lock_state) && (entry->accessors > 0))
103 return false;
104 if ((total_size_ > max_cache_) && !CheckLimit()) {
105 LOG_F(LS_WARNING) << "Cache overfull";
106 return false;
107 }
108 entry->lock_state = LS_LOCKED;
109 return true;
110}
111
112StreamInterface* DiskCache::WriteResource(const std::string& id, size_t index) {
113 Entry* entry = GetOrCreateEntry(id, false);
114 if (LS_LOCKED != entry->lock_state)
115 return NULL;
116
117 size_t previous_size = 0;
118 std::string filename(IdToFilename(id, index));
119 FileStream::GetSize(filename, &previous_size);
120 ASSERT(previous_size <= entry->size);
121 if (previous_size > entry->size) {
122 previous_size = entry->size;
123 }
124
125 scoped_ptr<FileStream> file(new FileStream);
126 if (!file->Open(filename, "wb", NULL)) {
127 LOG_F(LS_ERROR) << "Couldn't create cache file";
128 return NULL;
129 }
130
andresp@webrtc.orgff689be2015-02-12 11:54:26 +0000131 entry->streams = std::max(entry->streams, index + 1);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000132 entry->size -= previous_size;
133 total_size_ -= previous_size;
134
135 entry->accessors += 1;
136 total_accessors_ += 1;
137 return new DiskCacheAdapter(this, id, index, file.release());
138}
139
140bool DiskCache::UnlockResource(const std::string& id) {
141 Entry* entry = GetOrCreateEntry(id, false);
142 if (LS_LOCKED != entry->lock_state)
143 return false;
144
145 if (entry->accessors > 0) {
146 entry->lock_state = LS_UNLOCKING;
147 } else {
148 entry->lock_state = LS_UNLOCKED;
149 entry->last_modified = time(0);
150 CheckLimit();
151 }
152 return true;
153}
154
155StreamInterface* DiskCache::ReadResource(const std::string& id,
156 size_t index) const {
157 const Entry* entry = GetEntry(id);
158 if (LS_UNLOCKED != entry->lock_state)
159 return NULL;
160 if (index >= entry->streams)
161 return NULL;
162
163 scoped_ptr<FileStream> file(new FileStream);
164 if (!file->Open(IdToFilename(id, index), "rb", NULL))
165 return NULL;
166
167 entry->accessors += 1;
168 total_accessors_ += 1;
169 return new DiskCacheAdapter(this, id, index, file.release());
170}
171
172bool DiskCache::HasResource(const std::string& id) const {
173 const Entry* entry = GetEntry(id);
174 return (NULL != entry) && (entry->streams > 0);
175}
176
177bool DiskCache::HasResourceStream(const std::string& id, size_t index) const {
178 const Entry* entry = GetEntry(id);
179 if ((NULL == entry) || (index >= entry->streams))
180 return false;
181
182 std::string filename = IdToFilename(id, index);
183
184 return FileExists(filename);
185}
186
187bool DiskCache::DeleteResource(const std::string& id) {
188 Entry* entry = GetOrCreateEntry(id, false);
189 if (!entry)
190 return true;
191
192 if ((LS_UNLOCKED != entry->lock_state) || (entry->accessors > 0))
193 return false;
194
195 bool success = true;
196 for (size_t index = 0; index < entry->streams; ++index) {
197 std::string filename = IdToFilename(id, index);
198
199 if (!FileExists(filename))
200 continue;
201
202 if (!DeleteFile(filename)) {
203 LOG_F(LS_ERROR) << "Couldn't remove cache file: " << filename;
204 success = false;
205 }
206 }
207
208 total_size_ -= entry->size;
209 map_.erase(id);
210 return success;
211}
212
213bool DiskCache::CheckLimit() {
214#ifdef _DEBUG
215 // Temporary check to make sure everything is working correctly.
216 size_t cache_size = 0;
217 for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) {
218 cache_size += it->second.size;
219 }
220 ASSERT(cache_size == total_size_);
221#endif // _DEBUG
222
223 // TODO: Replace this with a non-brain-dead algorithm for clearing out the
224 // oldest resources... something that isn't O(n^2)
225 while (total_size_ > max_cache_) {
226 EntryMap::iterator oldest = map_.end();
227 for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) {
228 if ((LS_UNLOCKED != it->second.lock_state) || (it->second.accessors > 0))
229 continue;
230 oldest = it;
231 break;
232 }
233 if (oldest == map_.end()) {
234 LOG_F(LS_WARNING) << "All resources are locked!";
235 return false;
236 }
237 for (EntryMap::iterator it = oldest++; it != map_.end(); ++it) {
238 if (it->second.last_modified < oldest->second.last_modified) {
239 oldest = it;
240 }
241 }
242 if (!DeleteResource(oldest->first)) {
243 LOG_F(LS_ERROR) << "Couldn't delete from cache!";
244 return false;
245 }
246 }
247 return true;
248}
249
250std::string DiskCache::IdToFilename(const std::string& id, size_t index) const {
251#ifdef TRANSPARENT_CACHE_NAMES
252 // This escapes colons and other filesystem characters, so the user can't open
253 // special devices (like "COM1:"), or access other directories.
254 size_t buffer_size = id.length()*3 + 1;
255 char* buffer = new char[buffer_size];
256 encode(buffer, buffer_size, id.data(), id.length(),
257 unsafe_filename_characters(), '%');
258 // TODO: ASSERT(strlen(buffer) < FileSystem::MaxBasenameLength());
259#else // !TRANSPARENT_CACHE_NAMES
260 // We might want to just use a hash of the filename at some point, both for
261 // obfuscation, and to avoid both filename length and escaping issues.
262 ASSERT(false);
263#endif // !TRANSPARENT_CACHE_NAMES
264
265 char extension[32];
266 sprintfn(extension, ARRAY_SIZE(extension), ".%u", index);
267
268 Pathname pathname;
269 pathname.SetFolder(folder_);
270 pathname.SetBasename(buffer);
271 pathname.SetExtension(extension);
272
273#ifdef TRANSPARENT_CACHE_NAMES
274 delete [] buffer;
275#endif // TRANSPARENT_CACHE_NAMES
276
277 return pathname.pathname();
278}
279
280bool DiskCache::FilenameToId(const std::string& filename, std::string* id,
281 size_t* index) const {
282 Pathname pathname(filename);
283 unsigned tempdex;
284 if (1 != sscanf(pathname.extension().c_str(), ".%u", &tempdex))
285 return false;
286
287 *index = static_cast<size_t>(tempdex);
288
289 size_t buffer_size = pathname.basename().length() + 1;
290 char* buffer = new char[buffer_size];
291 decode(buffer, buffer_size, pathname.basename().data(),
292 pathname.basename().length(), '%');
293 id->assign(buffer);
294 delete [] buffer;
295 return true;
296}
297
298DiskCache::Entry* DiskCache::GetOrCreateEntry(const std::string& id,
299 bool create) {
300 EntryMap::iterator it = map_.find(id);
301 if (it != map_.end())
302 return &it->second;
303 if (!create)
304 return NULL;
305 Entry e;
306 e.lock_state = LS_UNLOCKED;
307 e.accessors = 0;
308 e.size = 0;
309 e.streams = 0;
310 e.last_modified = time(0);
311 it = map_.insert(EntryMap::value_type(id, e)).first;
312 return &it->second;
313}
314
315void DiskCache::ReleaseResource(const std::string& id, size_t index) const {
316 const Entry* entry = GetEntry(id);
317 if (!entry) {
318 LOG_F(LS_WARNING) << "Missing cache entry";
319 ASSERT(false);
320 return;
321 }
322
323 entry->accessors -= 1;
324 total_accessors_ -= 1;
325
326 if (LS_UNLOCKED != entry->lock_state) {
327 // This is safe, because locked resources only issue WriteResource, which
328 // is non-const. Think about a better way to handle it.
329 DiskCache* this2 = const_cast<DiskCache*>(this);
330 Entry* entry2 = this2->GetOrCreateEntry(id, false);
331
332 size_t new_size = 0;
333 std::string filename(IdToFilename(id, index));
334 FileStream::GetSize(filename, &new_size);
335 entry2->size += new_size;
336 this2->total_size_ += new_size;
337
338 if ((LS_UNLOCKING == entry->lock_state) && (0 == entry->accessors)) {
339 entry2->last_modified = time(0);
340 entry2->lock_state = LS_UNLOCKED;
341 this2->CheckLimit();
342 }
343 }
344}
345
346///////////////////////////////////////////////////////////////////////////////
347
348} // namespace rtc