Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sessions / session_backend.cc
blobca120516f156d1125dd24b3265ef073f7147486f
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/sessions/session_backend.h"
7 #include <limits>
9 #include "base/file_util.h"
10 #include "base/memory/scoped_vector.h"
11 #include "base/metrics/histogram.h"
12 #include "base/threading/thread_restrictions.h"
13 #include "net/base/file_stream.h"
14 #include "net/base/net_errors.h"
16 using base::TimeTicks;
18 // File version number.
19 static const int32 kFileCurrentVersion = 1;
21 // The signature at the beginning of the file = SSNS (Sessions).
22 static const int32 kFileSignature = 0x53534E53;
24 namespace {
26 // The file header is the first bytes written to the file,
27 // and is used to identify the file as one written by us.
28 struct FileHeader {
29 int32 signature;
30 int32 version;
33 // SessionFileReader ----------------------------------------------------------
35 // SessionFileReader is responsible for reading the set of SessionCommands that
36 // describe a Session back from a file. SessionFileRead does minimal error
37 // checking on the file (pretty much only that the header is valid).
39 class SessionFileReader {
40 public:
41 typedef SessionCommand::id_type id_type;
42 typedef SessionCommand::size_type size_type;
44 explicit SessionFileReader(const base::FilePath& path)
45 : errored_(false),
46 buffer_(SessionBackend::kFileReadBufferSize, 0),
47 buffer_position_(0),
48 available_count_(0) {
49 file_.reset(new net::FileStream(NULL));
50 if (base::PathExists(path))
51 file_->OpenSync(path,
52 base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ);
54 // Reads the contents of the file specified in the constructor, returning
55 // true on success. It is up to the caller to free all SessionCommands
56 // added to commands.
57 bool Read(BaseSessionService::SessionType type,
58 std::vector<SessionCommand*>* commands);
60 private:
61 // Reads a single command, returning it. A return value of NULL indicates
62 // either there are no commands, or there was an error. Use errored_ to
63 // distinguish the two. If NULL is returned, and there is no error, it means
64 // the end of file was successfully reached.
65 SessionCommand* ReadCommand();
67 // Shifts the unused portion of buffer_ to the beginning and fills the
68 // remaining portion with data from the file. Returns false if the buffer
69 // couldn't be filled. A return value of false only signals an error if
70 // errored_ is set to true.
71 bool FillBuffer();
73 // Whether an error condition has been detected (
74 bool errored_;
76 // As we read from the file, data goes here.
77 std::string buffer_;
79 // The file.
80 scoped_ptr<net::FileStream> file_;
82 // Position in buffer_ of the data.
83 size_t buffer_position_;
85 // Number of available bytes; relative to buffer_position_.
86 size_t available_count_;
88 DISALLOW_COPY_AND_ASSIGN(SessionFileReader);
91 bool SessionFileReader::Read(BaseSessionService::SessionType type,
92 std::vector<SessionCommand*>* commands) {
93 if (!file_->IsOpen())
94 return false;
95 FileHeader header;
96 int read_count;
97 TimeTicks start_time = TimeTicks::Now();
98 read_count = file_->ReadUntilComplete(reinterpret_cast<char*>(&header),
99 sizeof(header));
100 if (read_count != sizeof(header) || header.signature != kFileSignature ||
101 header.version != kFileCurrentVersion)
102 return false;
104 ScopedVector<SessionCommand> read_commands;
105 SessionCommand* command;
106 while ((command = ReadCommand()) && !errored_)
107 read_commands.push_back(command);
108 if (!errored_)
109 read_commands.swap(*commands);
110 if (type == BaseSessionService::TAB_RESTORE) {
111 UMA_HISTOGRAM_TIMES("TabRestore.read_session_file_time",
112 TimeTicks::Now() - start_time);
113 } else {
114 UMA_HISTOGRAM_TIMES("SessionRestore.read_session_file_time",
115 TimeTicks::Now() - start_time);
117 return !errored_;
120 SessionCommand* SessionFileReader::ReadCommand() {
121 // Make sure there is enough in the buffer for the size of the next command.
122 if (available_count_ < sizeof(size_type)) {
123 if (!FillBuffer())
124 return NULL;
125 if (available_count_ < sizeof(size_type)) {
126 VLOG(1) << "SessionFileReader::ReadCommand, file incomplete";
127 // Still couldn't read a valid size for the command, assume write was
128 // incomplete and return NULL.
129 return NULL;
132 // Get the size of the command.
133 size_type command_size;
134 memcpy(&command_size, &(buffer_[buffer_position_]), sizeof(command_size));
135 buffer_position_ += sizeof(command_size);
136 available_count_ -= sizeof(command_size);
138 if (command_size == 0) {
139 VLOG(1) << "SessionFileReader::ReadCommand, empty command";
140 // Empty command. Shouldn't happen if write was successful, fail.
141 return NULL;
144 // Make sure buffer has the complete contents of the command.
145 if (command_size > available_count_) {
146 if (command_size > buffer_.size())
147 buffer_.resize((command_size / 1024 + 1) * 1024, 0);
148 if (!FillBuffer() || command_size > available_count_) {
149 // Again, assume the file was ok, and just the last chunk was lost.
150 VLOG(1) << "SessionFileReader::ReadCommand, last chunk lost";
151 return NULL;
154 const id_type command_id = buffer_[buffer_position_];
155 // NOTE: command_size includes the size of the id, which is not part of
156 // the contents of the SessionCommand.
157 SessionCommand* command =
158 new SessionCommand(command_id, command_size - sizeof(id_type));
159 if (command_size > sizeof(id_type)) {
160 memcpy(command->contents(),
161 &(buffer_[buffer_position_ + sizeof(id_type)]),
162 command_size - sizeof(id_type));
164 buffer_position_ += command_size;
165 available_count_ -= command_size;
166 return command;
169 bool SessionFileReader::FillBuffer() {
170 if (available_count_ > 0 && buffer_position_ > 0) {
171 // Shift buffer to beginning.
172 memmove(&(buffer_[0]), &(buffer_[buffer_position_]), available_count_);
174 buffer_position_ = 0;
175 DCHECK(buffer_position_ + available_count_ < buffer_.size());
176 int to_read = static_cast<int>(buffer_.size() - available_count_);
177 int read_count = file_->ReadUntilComplete(&(buffer_[available_count_]),
178 to_read);
179 if (read_count < 0) {
180 errored_ = true;
181 return false;
183 if (read_count == 0)
184 return false;
185 available_count_ += read_count;
186 return true;
189 } // namespace
191 // SessionBackend -------------------------------------------------------------
193 // File names (current and previous) for a type of TAB.
194 static const char* kCurrentTabSessionFileName = "Current Tabs";
195 static const char* kLastTabSessionFileName = "Last Tabs";
197 // File names (current and previous) for a type of SESSION.
198 static const char* kCurrentSessionFileName = "Current Session";
199 static const char* kLastSessionFileName = "Last Session";
201 // static
202 const int SessionBackend::kFileReadBufferSize = 1024;
204 SessionBackend::SessionBackend(BaseSessionService::SessionType type,
205 const base::FilePath& path_to_dir)
206 : type_(type),
207 path_to_dir_(path_to_dir),
208 last_session_valid_(false),
209 inited_(false),
210 empty_file_(true) {
211 // NOTE: this is invoked on the main thread, don't do file access here.
214 void SessionBackend::Init() {
215 if (inited_)
216 return;
218 inited_ = true;
220 // Create the directory for session info.
221 base::CreateDirectory(path_to_dir_);
223 MoveCurrentSessionToLastSession();
226 void SessionBackend::AppendCommands(
227 std::vector<SessionCommand*>* commands,
228 bool reset_first) {
229 Init();
230 // Make sure and check current_session_file_, if opening the file failed
231 // current_session_file_ will be NULL.
232 if ((reset_first && !empty_file_) || !current_session_file_.get() ||
233 !current_session_file_->IsOpen()) {
234 ResetFile();
236 // Need to check current_session_file_ again, ResetFile may fail.
237 if (current_session_file_.get() && current_session_file_->IsOpen() &&
238 !AppendCommandsToFile(current_session_file_.get(), *commands)) {
239 current_session_file_.reset(NULL);
241 empty_file_ = false;
242 STLDeleteElements(commands);
243 delete commands;
246 void SessionBackend::ReadLastSessionCommands(
247 const CancelableTaskTracker::IsCanceledCallback& is_canceled,
248 const BaseSessionService::InternalGetCommandsCallback& callback) {
249 if (is_canceled.Run())
250 return;
252 Init();
254 ScopedVector<SessionCommand> commands;
255 ReadLastSessionCommandsImpl(&(commands.get()));
256 callback.Run(commands.Pass());
259 bool SessionBackend::ReadLastSessionCommandsImpl(
260 std::vector<SessionCommand*>* commands) {
261 Init();
262 SessionFileReader file_reader(GetLastSessionPath());
263 return file_reader.Read(type_, commands);
266 void SessionBackend::DeleteLastSession() {
267 Init();
268 base::DeleteFile(GetLastSessionPath(), false);
271 void SessionBackend::MoveCurrentSessionToLastSession() {
272 Init();
273 current_session_file_.reset(NULL);
275 const base::FilePath current_session_path = GetCurrentSessionPath();
276 const base::FilePath last_session_path = GetLastSessionPath();
277 if (base::PathExists(last_session_path))
278 base::DeleteFile(last_session_path, false);
279 if (base::PathExists(current_session_path)) {
280 int64 file_size;
281 if (base::GetFileSize(current_session_path, &file_size)) {
282 if (type_ == BaseSessionService::TAB_RESTORE) {
283 UMA_HISTOGRAM_COUNTS("TabRestore.last_session_file_size",
284 static_cast<int>(file_size / 1024));
285 } else {
286 UMA_HISTOGRAM_COUNTS("SessionRestore.last_session_file_size",
287 static_cast<int>(file_size / 1024));
290 last_session_valid_ = base::Move(current_session_path, last_session_path);
293 if (base::PathExists(current_session_path))
294 base::DeleteFile(current_session_path, false);
296 // Create and open the file for the current session.
297 ResetFile();
300 bool SessionBackend::ReadCurrentSessionCommandsImpl(
301 std::vector<SessionCommand*>* commands) {
302 Init();
303 SessionFileReader file_reader(GetCurrentSessionPath());
304 return file_reader.Read(type_, commands);
307 bool SessionBackend::AppendCommandsToFile(net::FileStream* file,
308 const std::vector<SessionCommand*>& commands) {
309 for (std::vector<SessionCommand*>::const_iterator i = commands.begin();
310 i != commands.end(); ++i) {
311 int wrote;
312 const size_type content_size = static_cast<size_type>((*i)->size());
313 const size_type total_size = content_size + sizeof(id_type);
314 if (type_ == BaseSessionService::TAB_RESTORE)
315 UMA_HISTOGRAM_COUNTS("TabRestore.command_size", total_size);
316 else
317 UMA_HISTOGRAM_COUNTS("SessionRestore.command_size", total_size);
318 wrote = file->WriteSync(reinterpret_cast<const char*>(&total_size),
319 sizeof(total_size));
320 if (wrote != sizeof(total_size)) {
321 NOTREACHED() << "error writing";
322 return false;
324 id_type command_id = (*i)->id();
325 wrote = file->WriteSync(reinterpret_cast<char*>(&command_id),
326 sizeof(command_id));
327 if (wrote != sizeof(command_id)) {
328 NOTREACHED() << "error writing";
329 return false;
331 if (content_size > 0) {
332 wrote = file->WriteSync(reinterpret_cast<char*>((*i)->contents()),
333 content_size);
334 if (wrote != content_size) {
335 NOTREACHED() << "error writing";
336 return false;
339 #if defined(OS_CHROMEOS)
340 // TODO(gspencer): Remove this once we find a better place to do it.
341 // See issue http://crbug.com/245015
342 file->FlushSync();
343 #endif
345 return true;
348 SessionBackend::~SessionBackend() {
349 if (current_session_file_.get()) {
350 // Destructor performs file IO because file is open in sync mode.
351 // crbug.com/112512.
352 base::ThreadRestrictions::ScopedAllowIO allow_io;
353 current_session_file_.reset();
357 void SessionBackend::ResetFile() {
358 DCHECK(inited_);
359 if (current_session_file_.get()) {
360 // File is already open, truncate it. We truncate instead of closing and
361 // reopening to avoid the possibility of scanners locking the file out
362 // from under us once we close it. If truncation fails, we'll try to
363 // recreate.
364 const int header_size = static_cast<int>(sizeof(FileHeader));
365 if (current_session_file_->Truncate(header_size) != header_size)
366 current_session_file_.reset(NULL);
368 if (!current_session_file_.get())
369 current_session_file_.reset(OpenAndWriteHeader(GetCurrentSessionPath()));
370 empty_file_ = true;
373 net::FileStream* SessionBackend::OpenAndWriteHeader(
374 const base::FilePath& path) {
375 DCHECK(!path.empty());
376 scoped_ptr<net::FileStream> file(new net::FileStream(NULL));
377 if (file->OpenSync(path, base::PLATFORM_FILE_CREATE_ALWAYS |
378 base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_WRITE |
379 base::PLATFORM_FILE_EXCLUSIVE_READ) != net::OK)
380 return NULL;
381 FileHeader header;
382 header.signature = kFileSignature;
383 header.version = kFileCurrentVersion;
384 int wrote = file->WriteSync(reinterpret_cast<char*>(&header),
385 sizeof(header));
386 if (wrote != sizeof(header))
387 return NULL;
388 return file.release();
391 base::FilePath SessionBackend::GetLastSessionPath() {
392 base::FilePath path = path_to_dir_;
393 if (type_ == BaseSessionService::TAB_RESTORE)
394 path = path.AppendASCII(kLastTabSessionFileName);
395 else
396 path = path.AppendASCII(kLastSessionFileName);
397 return path;
400 base::FilePath SessionBackend::GetCurrentSessionPath() {
401 base::FilePath path = path_to_dir_;
402 if (type_ == BaseSessionService::TAB_RESTORE)
403 path = path.AppendASCII(kCurrentTabSessionFileName);
404 else
405 path = path.AppendASCII(kCurrentSessionFileName);
406 return path;