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