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