diff options
Diffstat (limited to 'chrome/browser/session_backend.cc')
-rw-r--r-- | chrome/browser/session_backend.cc | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/chrome/browser/session_backend.cc b/chrome/browser/session_backend.cc new file mode 100644 index 0000000..01fdf99 --- /dev/null +++ b/chrome/browser/session_backend.cc @@ -0,0 +1,402 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/session_backend.h" + +#include <limits> + +#include "base/file_util.h" +#include "base/histogram.h" +#include "base/pickle.h" +#include "chrome/common/scoped_vector.h" + +// File version number. +static const int32 kFileCurrentVersion = 1; + +// The signature at the beginning of the file = SSNS (Sessions). +static const int32 kFileSignature = 0x53534E53; + +namespace { + +// SessionFileReader ---------------------------------------------------------- + +// SessionFileReader is responsible for reading the set of SessionCommands that +// describe a Session back from a file. SessionFileRead does minimal error +// checking on the file (pretty much only that the header is valid). + +class SessionFileReader { + public: + typedef SessionCommand::id_type id_type; + typedef SessionCommand::size_type size_type; + + SessionFileReader(const std::wstring& path) + : handle_(CreateFile(path.c_str(), GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)), + buffer_(SessionBackend::kFileReadBufferSize, 0), + buffer_position_(0), + available_count_(0), + errored_(false) { + } + // Reads the contents of the file specified in the constructor, returning + // true on success. It is up to the caller to free all SessionCommands + // added to commands. + bool Read(std::vector<SessionCommand*>* commands); + + private: + // Reads a single command, returning it. A return value of NULL indicates + // either there are no commands, or there was an error. Use errored_ to + // distinguish the two. If NULL is returned, and there is no error, it means + // the end of file was sucessfully reached. + SessionCommand* ReadCommand(); + + // Shifts the unused portion of buffer_ to the beginning and fills the + // remaining portion with data from the file. Returns false if the buffer + // couldn't be filled. A return value of false only signals an error if + // errored_ is set to true. + bool FillBuffer(); + + // Whether an error condition has been detected ( + bool errored_; + + // As we read from the file, data goes here. + std::string buffer_; + + // The file. + ScopedHandle handle_; + + // Position in buffer_ of the data. + size_t buffer_position_; + + // Number of available bytes; relative to buffer_position_. + size_t available_count_; + + DISALLOW_EVIL_CONSTRUCTORS(SessionFileReader); +}; + +bool SessionFileReader::Read(std::vector<SessionCommand*>* commands) { + if (!handle_.IsValid()) + return false; + int32 header[2]; + DWORD read_count; + TimeTicks start_time = TimeTicks::Now(); + if (!ReadFile(handle_, &header, sizeof(header), &read_count, NULL) || + read_count != sizeof(header) || header[0] != kFileSignature || + header[1] != kFileCurrentVersion) + return false; + + ScopedVector<SessionCommand> read_commands; + SessionCommand* command; + while ((command = ReadCommand()) && !errored_) + read_commands->push_back(command); + if (!errored_) + read_commands->swap(*commands); + UMA_HISTOGRAM_TIMES(L"SessionRestore.read_session_file_time", + TimeTicks::Now() - start_time); + return !errored_; +} + +SessionCommand* SessionFileReader::ReadCommand() { + // Make sure there is enough in the buffer for the size of the next command. + if (available_count_ < sizeof(size_type)) { + if (!FillBuffer()) + return NULL; + if (available_count_ < sizeof(size_type)) { + // Still couldn't read a valid size for the command, assume write was + // incomplete and return NULL. + return NULL; + } + } + // Get the size of the command. + size_type command_size; + memcpy(&command_size, &(buffer_[buffer_position_]), sizeof(command_size)); + buffer_position_ += sizeof(command_size); + available_count_ -= sizeof(command_size); + + if (command_size == 0) { + // Empty command. Shouldn't happen if write was successful, fail. + return NULL; + } + + // Make sure buffer has the complete contents of the command. + if (command_size > available_count_) { + if (command_size > buffer_.size()) + buffer_.resize((command_size / 1024 + 1) * 1024, 0); + if (!FillBuffer() || command_size > available_count_) { + // Again, assume the file was ok, and just the last chunk was lost. + return NULL; + } + } + const id_type command_id = buffer_[buffer_position_]; + // NOTE: command_size includes the size of the id, which is not part of + // the contents of the SessionCommand. + SessionCommand* command = + new SessionCommand(command_id, command_size - sizeof(id_type)); + if (command_size > sizeof(id_type)) { + memcpy(command->contents(), + &(buffer_[buffer_position_ + sizeof(id_type)]), + command_size - sizeof(id_type)); + } + buffer_position_ += command_size; + available_count_ -= command_size; + return command; +} + +bool SessionFileReader::FillBuffer() { + if (available_count_ > 0 && buffer_position_ > 0) { + // Shift buffer to beginning. + memmove(&(buffer_[0]), &(buffer_[buffer_position_]), available_count_); + } + buffer_position_ = 0; + DCHECK(buffer_position_ + available_count_ < buffer_.size()); + DWORD read_count; + if (!ReadFile(handle_, &(buffer_[available_count_]), + static_cast<DWORD>(buffer_.size() - available_count_), + &read_count, NULL)) { + errored_ = true; + return false; + } + if (read_count == 0) + return false; + available_count_ += static_cast<int>(read_count); + return true; +} + +} // namespace + +// SessionCommand ------------------------------------------------------------- + +SessionCommand::SessionCommand(id_type id, size_type size) + : id_(id), + contents_(size, 0) { +} + +SessionCommand::SessionCommand(id_type id, const Pickle& pickle) + : id_(id), + contents_(pickle.size(), 0) { + DCHECK(pickle.size() < std::numeric_limits<size_type>::max()); + memcpy(contents(), pickle.data(), pickle.size()); +} + +bool SessionCommand::GetPayload(void* dest, size_t count) const { + if (size() != count) + return false; + memcpy(dest, &(contents_[0]), count); + return true; +} + +Pickle* SessionCommand::PayloadAsPickle() const { + return new Pickle(contents(), static_cast<int>(size())); +} + +// SessionBackend ------------------------------------------------------------- + +// Target file name. +static const wchar_t* const kCurrentSessionFileName = L"Current Session"; + +// Previous target file. +static const wchar_t* const kLastSessionFileName = L"Last Session"; + +// Saved session file name. +static const wchar_t* const kSavedSessionFileName = L"Saved Session"; + +// static +const int SessionBackend::kFileReadBufferSize = 1024; + +SessionBackend::SessionBackend(const std::wstring& path_to_dir) + : path_to_dir_(path_to_dir), + last_session_valid_(false), + inited_(false), + empty_file_(true) { + // NOTE: this is invoked on the main thread, don't do file access here. +} + +void SessionBackend::Init() { + if (inited_) + return; + + inited_ = true; + + // Create the directory for session info. + file_util::CreateDirectory(path_to_dir_); + + MoveCurrentSessionToLastSession(); +} + +void SessionBackend::SaveSession( + const std::vector<SessionCommand*>& commands) { + Init(); + ScopedHandle handle(OpenAndWriteHeader(GetSavedSessionPath())); + if (handle.IsValid()) + AppendCommandsToFile(handle, commands); + STLDeleteContainerPointers(commands.begin(), commands.end()); +} + +void SessionBackend::AppendCommands( + std::vector<SessionCommand*>* commands, + bool reset_first) { + Init(); + if ((reset_first && !empty_file_) || !current_session_handle_.IsValid()) + ResetFile(); + if (current_session_handle_.IsValid() && + !AppendCommandsToFile(current_session_handle_, *commands)) { + current_session_handle_.Set(NULL); + } + empty_file_ = false; + STLDeleteElements(commands); + delete commands; +} + +void SessionBackend::ReadSession( + scoped_refptr<SessionService::InternalSavedSessionRequest> request) { + if (request->canceled()) + return; + Init(); + ReadSessionImpl(request->is_saved_session, &(request->commands)); + request->ForwardResult( + SessionService::InternalSavedSessionRequest::TupleType(request->handle(), + request)); +} + +bool SessionBackend::ReadSessionImpl(bool use_save_file, + std::vector<SessionCommand*>* commands) { + Init(); + const std::wstring path = + use_save_file ? GetSavedSessionPath() : GetLastSessionPath(); + SessionFileReader file_reader(path); + return file_reader.Read(commands); +} + +void SessionBackend::DeleteSession(bool saved_session) { + Init(); + const std::wstring path = + saved_session ? GetSavedSessionPath() : GetLastSessionPath(); + file_util::Delete(path, false); +} + +void SessionBackend::CopyLastSessionToSavedSession() { + Init(); + file_util::CopyFile(GetLastSessionPath(), GetSavedSessionPath()); +} + +void SessionBackend::MoveCurrentSessionToLastSession() { + Init(); + current_session_handle_.Set(NULL); + + const std::wstring current_session_path = GetCurrentSessionPath(); + const std::wstring last_session_path = GetLastSessionPath(); + if (file_util::PathExists(last_session_path)) + file_util::Delete(last_session_path, false); + if (file_util::PathExists(current_session_path)) { + int64 file_size; + if (file_util::GetFileSize(current_session_path, &file_size)) { + UMA_HISTOGRAM_COUNTS(L"SessionRestore.last_session_file_size", + static_cast<int>(file_size / 1024)); + } + last_session_valid_ = file_util::Move(current_session_path, + last_session_path); + } + + if (file_util::PathExists(current_session_path)) + file_util::Delete(current_session_path, false); + + // Create and open the file for the current session. + ResetFile(); +} + +bool SessionBackend::AppendCommandsToFile( + HANDLE handle, + const std::vector<SessionCommand*>& commands) { + for (std::vector<SessionCommand*>::const_iterator i = commands.begin(); + i != commands.end(); ++i) { + DWORD wrote; + const size_type content_size = static_cast<size_type>((*i)->size()); + const size_type total_size = content_size + sizeof(id_type); + UMA_HISTOGRAM_COUNTS(L"SessionRestore.command_size", total_size); + if (!WriteFile(handle, &total_size, sizeof(total_size), &wrote, NULL) || + wrote != sizeof(total_size)) { + NOTREACHED() << "error writing"; + return false; + } + id_type command_id = (*i)->id(); + if (!WriteFile(handle, &command_id, sizeof(command_id), &wrote, NULL) || + wrote != sizeof(command_id)) { + NOTREACHED() << "error writing"; + return false; + } + if (!WriteFile(handle, (*i)->contents(), content_size, &wrote, NULL) || + wrote != content_size) { + NOTREACHED() << "error writing"; + return false; + } + } + return true; +} + +void SessionBackend::ResetFile() { + DCHECK(inited_); + // Do Set(NULL) first to make sure we close current file (if open). + current_session_handle_.Set(NULL); + current_session_handle_.Set(OpenAndWriteHeader(GetCurrentSessionPath())); + empty_file_ = true; +} + +HANDLE SessionBackend::OpenAndWriteHeader(const std::wstring& path) { + DCHECK(!path.empty()); + ScopedHandle hfile( + CreateFile(path.c_str(), GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); + if (!hfile.IsValid()) + return NULL; + int32 header[2]; + header[0] = kFileSignature; + header[1] = kFileCurrentVersion; + DWORD wrote; + if (!WriteFile(hfile, &header, sizeof(header), &wrote, NULL) || + wrote != sizeof(header)) + return NULL; + return hfile.Take(); +} + +std::wstring SessionBackend::GetLastSessionPath() { + std::wstring path = path_to_dir_; + file_util::AppendToPath(&path, kLastSessionFileName); + return path; +} + +std::wstring SessionBackend::GetSavedSessionPath() { + std::wstring path = path_to_dir_; + file_util::AppendToPath(&path, kSavedSessionFileName); + return path; +} + +std::wstring SessionBackend::GetCurrentSessionPath() { + std::wstring path = path_to_dir_; + file_util::AppendToPath(&path, kCurrentSessionFileName); + return path; +} |