| /* |
| * Copyright 2010 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Author: abliss@google.com (Adam Bliss) |
| |
| #include "pagespeed/kernel/base/mem_file_system.h" |
| |
| #include <cstddef> |
| #include <map> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "pagespeed/kernel/base/abstract_mutex.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/file_system.h" |
| #include "pagespeed/kernel/base/message_handler.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/timer.h" |
| #include "pagespeed/kernel/base/thread_system.h" |
| #include "pagespeed/kernel/base/mock_timer.h" |
| |
| namespace net_instaweb { |
| |
| class MemInputFile : public FileSystem::InputFile { |
| public: |
| MemInputFile(const StringPiece& filename, const GoogleString& contents) |
| : contents_(contents), |
| filename_(filename.data(), filename.size()), |
| offset_(0) { |
| } |
| |
| virtual bool Close(MessageHandler* message_handler) { |
| offset_ = contents_.length(); |
| return true; |
| } |
| |
| virtual const char* filename() { return filename_.c_str(); } |
| |
| virtual int Read(char* buf, int size, MessageHandler* message_handler) { |
| if (size + offset_ > static_cast<int>(contents_.length())) { |
| size = contents_.length() - offset_; |
| } |
| memcpy(buf, contents_.c_str() + offset_, size); |
| offset_ += size; |
| return size; |
| } |
| |
| virtual bool ReadFile(GoogleString* buf, MessageHandler* message_handler) { |
| *buf = contents_; |
| return true; |
| } |
| |
| private: |
| const GoogleString contents_; |
| const GoogleString filename_; |
| int offset_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MemInputFile); |
| }; |
| |
| |
| class MemOutputFile : public FileSystem::OutputFile { |
| public: |
| MemOutputFile( |
| const StringPiece& filename, GoogleString* contents, bool append) |
| : contents_(contents), filename_(filename.data(), filename.size()) { |
| if (!append) { |
| contents_->clear(); |
| } |
| } |
| |
| virtual bool Close(MessageHandler* message_handler) { |
| Flush(message_handler); |
| return true; |
| } |
| |
| virtual const char* filename() { return filename_.c_str(); } |
| |
| virtual bool Flush(MessageHandler* message_handler) { |
| contents_->append(written_); |
| written_.clear(); |
| return true; |
| } |
| |
| virtual bool SetWorldReadable(MessageHandler* message_handler) { |
| return true; |
| } |
| |
| virtual bool Write(const StringPiece& buf, MessageHandler* handler) { |
| buf.AppendToString(&written_); |
| return true; |
| } |
| |
| private: |
| GoogleString* contents_; |
| const GoogleString filename_; |
| GoogleString written_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MemOutputFile); |
| }; |
| |
| MemFileSystem::MemFileSystem(ThreadSystem* threads, Timer* timer) |
| : lock_map_mutex_(threads->NewMutex()), |
| all_else_mutex_(threads->NewMutex()), |
| enabled_(true), |
| timer_(timer), |
| mock_timer_(NULL), |
| temp_file_index_(0), |
| atime_enabled_(true), |
| advance_time_on_update_(false) { |
| ClearStats(); |
| } |
| |
| MemFileSystem::~MemFileSystem() { |
| } |
| |
| void MemFileSystem::UpdateAtime(const StringPiece& path) { |
| if (atime_enabled_) { |
| int64 now_us = timer_->NowUs(); |
| int64 now_s = now_us / Timer::kSecondUs; |
| if (advance_time_on_update_) { |
| mock_timer_->AdvanceUs(Timer::kSecondUs); |
| } |
| atime_map_[path.as_string()] = now_s; |
| } |
| } |
| |
| void MemFileSystem::UpdateMtime(const StringPiece& path) { |
| int64 now_us = timer_->NowUs(); |
| int64 now_s = now_us / Timer::kSecondUs; |
| mtime_map_[path.as_string()] = now_s; |
| } |
| |
| void MemFileSystem::Clear() { |
| ScopedMutex lock(all_else_mutex_.get()); |
| string_map_.clear(); |
| } |
| |
| BoolOrError MemFileSystem::Exists(const char* path, MessageHandler* handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| StringStringMap::const_iterator iter = string_map_.find(path); |
| return BoolOrError(iter != string_map_.end()); |
| } |
| |
| BoolOrError MemFileSystem::IsDir(const char* path, MessageHandler* handler) { |
| return Exists(path, handler).is_true() |
| ? BoolOrError(EndsInSlash(path)) : BoolOrError(); |
| } |
| |
| bool MemFileSystem::MakeDir(const char* path, MessageHandler* handler) { |
| // We store directories as empty files with trailing slashes. |
| ScopedMutex lock(all_else_mutex_.get()); |
| GoogleString path_string = path; |
| EnsureEndsInSlash(&path_string); |
| string_map_[path_string] = ""; |
| UpdateAtime(path_string); |
| UpdateMtime(path_string); |
| return true; |
| } |
| |
| bool MemFileSystem::RemoveDir(const char* path, MessageHandler* handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| GoogleString path_string = path; |
| EnsureEndsInSlash(&path_string); |
| |
| StringStringMap::const_iterator iter = string_map_.find(path_string); |
| |
| // Verify that this directory exists |
| if (iter == string_map_.end()) { |
| handler->Message(kError, "Failed to remove directory %s: directory does " |
| "not exist", path); |
| return false; |
| } |
| |
| // Verify that no files are stored in this directory. We can do this by |
| // checking to see if the next string in the map starts with this directory |
| // path. Note this depends on using a data structure that keeps its elements |
| // sorted by key (such as a map). |
| StringStringMap::const_iterator next_iter = iter; |
| ++next_iter; |
| if (next_iter != string_map_.end() && |
| next_iter->first.find(iter->first) == 0) { |
| handler->Message(kError, "Failed to remove directory %s: directory is not " |
| "empty", path); |
| return false; |
| } |
| |
| // This directory exists and is empty, so remove it |
| atime_map_.erase(path_string); |
| mtime_map_.erase(path_string); |
| string_map_.erase(path_string); |
| return true; |
| } |
| |
| FileSystem::InputFile* MemFileSystem::OpenInputFile( |
| const char* filename, MessageHandler* message_handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| |
| ++num_input_file_opens_; |
| if (!enabled_) { |
| return NULL; |
| } |
| |
| StringStringMap::const_iterator iter = string_map_.find(filename); |
| if (iter == string_map_.end()) { |
| message_handler->Error(filename, 0, "opening input file: %s", |
| "file not found"); |
| return NULL; |
| } else { |
| UpdateAtime(filename); |
| return new MemInputFile(filename, iter->second); |
| } |
| } |
| |
| FileSystem::OutputFile* MemFileSystem::OpenOutputFileHelper( |
| const char* filename, bool append, MessageHandler* message_handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| UpdateAtime(filename); |
| UpdateMtime(filename); |
| ++num_output_file_opens_; |
| return new MemOutputFile(filename, &(string_map_[filename]), append); |
| } |
| |
| FileSystem::OutputFile* MemFileSystem::OpenTempFileHelper( |
| const StringPiece& prefix, MessageHandler* message_handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| GoogleString filename = StringPrintf("tmpfile%d", temp_file_index_++); |
| UpdateAtime(filename); |
| UpdateMtime(filename); |
| ++num_temp_file_opens_; |
| return new MemOutputFile(filename, &string_map_[filename], false); |
| } |
| |
| bool MemFileSystem::RecursivelyMakeDir(const StringPiece& full_path_const, |
| MessageHandler* handler) { |
| // This is called to make sure that files can be written under the |
| // named directory. We don't have directories and files can be |
| // written anywhere, so just return true. |
| return true; |
| } |
| |
| bool MemFileSystem::RemoveFile(const char* filename, |
| MessageHandler* handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| atime_map_.erase(filename); |
| mtime_map_.erase(filename); |
| return (string_map_.erase(filename) == 1); |
| } |
| |
| bool MemFileSystem::RenameFileHelper(const char* old_file, |
| const char* new_file, |
| MessageHandler* handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| |
| if (strcmp(old_file, new_file) == 0) { |
| handler->Error(old_file, 0, "Cannot move a file to itself"); |
| return false; |
| } |
| |
| StringStringMap::iterator iter = string_map_.find(old_file); |
| if (iter == string_map_.end()) { |
| handler->Error(old_file, 0, "File not found"); |
| return false; |
| } |
| |
| string_map_[new_file] = iter->second; |
| string_map_.erase(iter); |
| |
| UpdateAtime(new_file); |
| atime_map_.erase(old_file); |
| mtime_map_[new_file] = mtime_map_[old_file]; |
| mtime_map_.erase(old_file); |
| |
| return true; |
| } |
| |
| bool MemFileSystem::ListContents(const StringPiece& dir, StringVector* files, |
| MessageHandler* handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| GoogleString prefix = dir.as_string(); |
| EnsureEndsInSlash(&prefix); |
| const size_t prefix_length = prefix.size(); |
| // We don't have directories, so we just list everything in the |
| // filesystem that matches the prefix and doesn't have another |
| // internal slash. |
| for (StringStringMap::iterator it = string_map_.begin(), |
| end = string_map_.end(); it != end; it++) { |
| const GoogleString& path = (*it).first; |
| if ((0 == path.compare(0, prefix_length, prefix)) && |
| path.length() > prefix_length) { |
| const size_t next_slash = path.find("/", prefix_length + 1); |
| // Only want to list files without another slash, unless that |
| // slash is the last char in the filename. |
| if ((next_slash == GoogleString::npos) |
| || (next_slash == path.length() - 1)) { |
| files->push_back(path); |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool MemFileSystem::Atime(const StringPiece& path, int64* timestamp_sec, |
| MessageHandler* handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| *timestamp_sec = atime_map_[path.as_string()]; |
| return true; |
| } |
| |
| bool MemFileSystem::Mtime(const StringPiece& path, int64* timestamp_sec, |
| MessageHandler* handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| ++num_input_file_stats_; |
| *timestamp_sec = mtime_map_[path.as_string()]; |
| return true; |
| } |
| |
| bool MemFileSystem::Size(const StringPiece& path, int64* size, |
| MessageHandler* handler) { |
| ScopedMutex lock(all_else_mutex_.get()); |
| const GoogleString path_string = path.as_string(); |
| StringStringMap::const_iterator iter = string_map_.find(path_string); |
| if (iter != string_map_.end()) { |
| *size = iter->second.size(); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| BoolOrError MemFileSystem::TryLock(const StringPiece& lock_name, |
| MessageHandler* handler) { |
| ScopedMutex lock(lock_map_mutex_.get()); |
| |
| if (lock_map_.count(lock_name.as_string()) != 0) { |
| return BoolOrError(false); |
| } else { |
| lock_map_[lock_name.as_string()] = timer_->NowMs(); |
| return BoolOrError(true); |
| } |
| } |
| |
| BoolOrError MemFileSystem::TryLockWithTimeout(const StringPiece& lock_name, |
| int64 timeout_ms, |
| const Timer* timer, |
| MessageHandler* handler) { |
| ScopedMutex lock(lock_map_mutex_.get()); |
| |
| DCHECK_EQ(timer, timer_); |
| GoogleString name = lock_name.as_string(); |
| int64 now = timer->NowMs(); |
| if (lock_map_.count(name) != 0 && |
| now <= lock_map_[name] + timeout_ms) { |
| return BoolOrError(false); |
| } else { |
| lock_map_[name] = timer->NowMs(); |
| return BoolOrError(true); |
| } |
| } |
| |
| bool MemFileSystem::Unlock(const StringPiece& lock_name, |
| MessageHandler* handler) { |
| ScopedMutex lock(lock_map_mutex_.get()); |
| return (lock_map_.erase(lock_name.as_string()) == 1); |
| } |
| |
| bool MemFileSystem::WriteFile(const char* filename, |
| const StringPiece& buffer, |
| MessageHandler* handler) { |
| bool ret = FileSystem::WriteFile(filename, buffer, handler); |
| if (write_callback_.get() != NULL) { |
| write_callback_.release()->Run(filename); |
| } |
| return ret; |
| } |
| |
| bool MemFileSystem::WriteTempFile(const StringPiece& prefix_name, |
| const StringPiece& buffer, |
| GoogleString* filename, |
| MessageHandler* handler) { |
| bool ret = FileSystem::WriteTempFile(prefix_name, buffer, filename, handler); |
| if (write_callback_.get() != NULL) { |
| write_callback_.release()->Run(*filename); |
| } |
| return ret; |
| } |
| |
| } // namespace net_instaweb |