1 /* 2 * libjingle 3 * Copyright 2004--2005, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <time.h> 29 30 #ifdef WIN32 31 #include "talk/base/win32.h" 32 #endif 33 34 #include "talk/base/common.h" 35 #include "talk/base/diskcache.h" 36 #include "talk/base/fileutils.h" 37 #include "talk/base/pathutils.h" 38 #include "talk/base/stream.h" 39 #include "talk/base/stringencode.h" 40 #include "talk/base/stringutils.h" 41 42 #ifdef _DEBUG 43 #define TRANSPARENT_CACHE_NAMES 1 44 #else // !_DEBUG 45 #define TRANSPARENT_CACHE_NAMES 0 46 #endif // !_DEBUG 47 48 namespace talk_base { 49 50 class DiskCache; 51 52 /////////////////////////////////////////////////////////////////////////////// 53 // DiskCacheAdapter 54 /////////////////////////////////////////////////////////////////////////////// 55 56 class DiskCacheAdapter : public StreamAdapterInterface { 57 public: 58 DiskCacheAdapter(const DiskCache* cache, const std::string& id, size_t index, 59 StreamInterface* stream) 60 : StreamAdapterInterface(stream), cache_(cache), id_(id), index_(index) 61 { } 62 virtual ~DiskCacheAdapter() { 63 Close(); 64 cache_->ReleaseResource(id_, index_); 65 } 66 67 private: 68 const DiskCache* cache_; 69 std::string id_; 70 size_t index_; 71 }; 72 73 /////////////////////////////////////////////////////////////////////////////// 74 // DiskCache 75 /////////////////////////////////////////////////////////////////////////////// 76 77 DiskCache::DiskCache() : max_cache_(0), total_size_(0), total_accessors_(0) { 78 } 79 80 DiskCache::~DiskCache() { 81 ASSERT(0 == total_accessors_); 82 } 83 84 bool DiskCache::Initialize(const std::string& folder, size_t size) { 85 if (!folder_.empty() || !Filesystem::CreateFolder(folder)) 86 return false; 87 88 folder_ = folder; 89 max_cache_ = size; 90 ASSERT(0 == total_size_); 91 92 if (!InitializeEntries()) 93 return false; 94 95 return CheckLimit(); 96 } 97 98 bool DiskCache::Purge() { 99 if (folder_.empty()) 100 return false; 101 102 if (total_accessors_ > 0) { 103 LOG_F(LS_WARNING) << "Cache files open"; 104 return false; 105 } 106 107 if (!PurgeFiles()) 108 return false; 109 110 map_.clear(); 111 return true; 112 } 113 114 bool DiskCache::LockResource(const std::string& id) { 115 Entry* entry = GetOrCreateEntry(id, true); 116 if (LS_LOCKED == entry->lock_state) 117 return false; 118 if ((LS_UNLOCKED == entry->lock_state) && (entry->accessors > 0)) 119 return false; 120 if ((total_size_ > max_cache_) && !CheckLimit()) { 121 LOG_F(LS_WARNING) << "Cache overfull"; 122 return false; 123 } 124 entry->lock_state = LS_LOCKED; 125 return true; 126 } 127 128 StreamInterface* DiskCache::WriteResource(const std::string& id, size_t index) { 129 Entry* entry = GetOrCreateEntry(id, false); 130 if (LS_LOCKED != entry->lock_state) 131 return NULL; 132 133 size_t previous_size = 0; 134 std::string filename(IdToFilename(id, index)); 135 FileStream::GetSize(filename, &previous_size); 136 ASSERT(previous_size <= entry->size); 137 if (previous_size > entry->size) { 138 previous_size = entry->size; 139 } 140 141 scoped_ptr<FileStream> file(new FileStream); 142 if (!file->Open(filename, "wb", NULL)) { 143 LOG_F(LS_ERROR) << "Couldn't create cache file"; 144 return NULL; 145 } 146 147 entry->streams = stdmax(entry->streams, index + 1); 148 entry->size -= previous_size; 149 total_size_ -= previous_size; 150 151 entry->accessors += 1; 152 total_accessors_ += 1; 153 return new DiskCacheAdapter(this, id, index, file.release()); 154 } 155 156 bool DiskCache::UnlockResource(const std::string& id) { 157 Entry* entry = GetOrCreateEntry(id, false); 158 if (LS_LOCKED != entry->lock_state) 159 return false; 160 161 if (entry->accessors > 0) { 162 entry->lock_state = LS_UNLOCKING; 163 } else { 164 entry->lock_state = LS_UNLOCKED; 165 entry->last_modified = time(0); 166 CheckLimit(); 167 } 168 return true; 169 } 170 171 StreamInterface* DiskCache::ReadResource(const std::string& id, 172 size_t index) const { 173 const Entry* entry = GetEntry(id); 174 if (LS_UNLOCKED != entry->lock_state) 175 return NULL; 176 if (index >= entry->streams) 177 return NULL; 178 179 scoped_ptr<FileStream> file(new FileStream); 180 if (!file->Open(IdToFilename(id, index), "rb", NULL)) 181 return NULL; 182 183 entry->accessors += 1; 184 total_accessors_ += 1; 185 return new DiskCacheAdapter(this, id, index, file.release()); 186 } 187 188 bool DiskCache::HasResource(const std::string& id) const { 189 const Entry* entry = GetEntry(id); 190 return (NULL != entry) && (entry->streams > 0); 191 } 192 193 bool DiskCache::HasResourceStream(const std::string& id, size_t index) const { 194 const Entry* entry = GetEntry(id); 195 if ((NULL == entry) || (index >= entry->streams)) 196 return false; 197 198 std::string filename = IdToFilename(id, index); 199 200 return FileExists(filename); 201 } 202 203 bool DiskCache::DeleteResource(const std::string& id) { 204 Entry* entry = GetOrCreateEntry(id, false); 205 if (!entry) 206 return true; 207 208 if ((LS_UNLOCKED != entry->lock_state) || (entry->accessors > 0)) 209 return false; 210 211 bool success = true; 212 for (size_t index = 0; index < entry->streams; ++index) { 213 std::string filename = IdToFilename(id, index); 214 215 if (!FileExists(filename)) 216 continue; 217 218 if (!DeleteFile(filename)) { 219 LOG_F(LS_ERROR) << "Couldn't remove cache file: " << filename; 220 success = false; 221 } 222 } 223 224 total_size_ -= entry->size; 225 map_.erase(id); 226 return success; 227 } 228 229 bool DiskCache::CheckLimit() { 230 #ifdef _DEBUG 231 // Temporary check to make sure everything is working correctly. 232 size_t cache_size = 0; 233 for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) { 234 cache_size += it->second.size; 235 } 236 ASSERT(cache_size == total_size_); 237 #endif // _DEBUG 238 239 // TODO: Replace this with a non-brain-dead algorithm for clearing out the 240 // oldest resources... something that isn't O(n^2) 241 while (total_size_ > max_cache_) { 242 EntryMap::iterator oldest = map_.end(); 243 for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) { 244 if ((LS_UNLOCKED != it->second.lock_state) || (it->second.accessors > 0)) 245 continue; 246 oldest = it; 247 break; 248 } 249 if (oldest == map_.end()) { 250 LOG_F(LS_WARNING) << "All resources are locked!"; 251 return false; 252 } 253 for (EntryMap::iterator it = oldest++; it != map_.end(); ++it) { 254 if (it->second.last_modified < oldest->second.last_modified) { 255 oldest = it; 256 } 257 } 258 if (!DeleteResource(oldest->first)) { 259 LOG_F(LS_ERROR) << "Couldn't delete from cache!"; 260 return false; 261 } 262 } 263 return true; 264 } 265 266 std::string DiskCache::IdToFilename(const std::string& id, size_t index) const { 267 #ifdef TRANSPARENT_CACHE_NAMES 268 // This escapes colons and other filesystem characters, so the user can't open 269 // special devices (like "COM1:"), or access other directories. 270 size_t buffer_size = id.length()*3 + 1; 271 char* buffer = new char[buffer_size]; 272 encode(buffer, buffer_size, id.data(), id.length(), 273 unsafe_filename_characters(), '%'); 274 // TODO: ASSERT(strlen(buffer) < FileSystem::MaxBasenameLength()); 275 #else // !TRANSPARENT_CACHE_NAMES 276 // We might want to just use a hash of the filename at some point, both for 277 // obfuscation, and to avoid both filename length and escaping issues. 278 ASSERT(false); 279 #endif // !TRANSPARENT_CACHE_NAMES 280 281 char extension[32]; 282 sprintfn(extension, ARRAY_SIZE(extension), ".%u", index); 283 284 Pathname pathname; 285 pathname.SetFolder(folder_); 286 pathname.SetBasename(buffer); 287 pathname.SetExtension(extension); 288 289 #ifdef TRANSPARENT_CACHE_NAMES 290 delete [] buffer; 291 #endif // TRANSPARENT_CACHE_NAMES 292 293 return pathname.pathname(); 294 } 295 296 bool DiskCache::FilenameToId(const std::string& filename, std::string* id, 297 size_t* index) const { 298 Pathname pathname(filename); 299 unsigned tempdex; 300 if (1 != sscanf(pathname.extension().c_str(), ".%u", &tempdex)) 301 return false; 302 303 *index = static_cast<size_t>(tempdex); 304 305 size_t buffer_size = pathname.basename().length() + 1; 306 char* buffer = new char[buffer_size]; 307 decode(buffer, buffer_size, pathname.basename().data(), 308 pathname.basename().length(), '%'); 309 id->assign(buffer); 310 delete [] buffer; 311 return true; 312 } 313 314 DiskCache::Entry* DiskCache::GetOrCreateEntry(const std::string& id, 315 bool create) { 316 EntryMap::iterator it = map_.find(id); 317 if (it != map_.end()) 318 return &it->second; 319 if (!create) 320 return NULL; 321 Entry e; 322 e.lock_state = LS_UNLOCKED; 323 e.accessors = 0; 324 e.size = 0; 325 e.streams = 0; 326 e.last_modified = time(0); 327 it = map_.insert(EntryMap::value_type(id, e)).first; 328 return &it->second; 329 } 330 331 void DiskCache::ReleaseResource(const std::string& id, size_t index) const { 332 const Entry* entry = GetEntry(id); 333 if (!entry) { 334 LOG_F(LS_WARNING) << "Missing cache entry"; 335 ASSERT(false); 336 return; 337 } 338 339 entry->accessors -= 1; 340 total_accessors_ -= 1; 341 342 if (LS_UNLOCKED != entry->lock_state) { 343 // This is safe, because locked resources only issue WriteResource, which 344 // is non-const. Think about a better way to handle it. 345 DiskCache* this2 = const_cast<DiskCache*>(this); 346 Entry* entry2 = this2->GetOrCreateEntry(id, false); 347 348 size_t new_size = 0; 349 std::string filename(IdToFilename(id, index)); 350 FileStream::GetSize(filename, &new_size); 351 entry2->size += new_size; 352 this2->total_size_ += new_size; 353 354 if ((LS_UNLOCKING == entry->lock_state) && (0 == entry->accessors)) { 355 entry2->last_modified = time(0); 356 entry2->lock_state = LS_UNLOCKED; 357 this2->CheckLimit(); 358 } 359 } 360 } 361 362 /////////////////////////////////////////////////////////////////////////////// 363 364 } // namespace talk_base 365