- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / sessions / session_backend.cc
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.
4
5 #include "chrome/browser/sessions/session_backend.h"
6
7 #include <limits>
8
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"
15
16 using base::TimeTicks;
17
18 // File version number.
19 static const int32 kFileCurrentVersion = 1;
20
21 // The signature at the beginning of the file = SSNS (Sessions).
22 static const int32 kFileSignature = 0x53534E53;
23
24 namespace {
25
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;
31 };
32
33 // SessionFileReader ----------------------------------------------------------
34
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).
38
39 class SessionFileReader {
40  public:
41   typedef SessionCommand::id_type id_type;
42   typedef SessionCommand::size_type size_type;
43
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);
53   }
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);
59
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();
66
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();
72
73   // Whether an error condition has been detected (
74   bool errored_;
75
76   // As we read from the file, data goes here.
77   std::string buffer_;
78
79   // The file.
80   scoped_ptr<net::FileStream> file_;
81
82   // Position in buffer_ of the data.
83   size_t buffer_position_;
84
85   // Number of available bytes; relative to buffer_position_.
86   size_t available_count_;
87
88   DISALLOW_COPY_AND_ASSIGN(SessionFileReader);
89 };
90
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;
103
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);
116   }
117   return !errored_;
118 }
119
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;
130     }
131   }
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);
137
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;
142   }
143
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;
152     }
153   }
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));
163   }
164   buffer_position_ += command_size;
165   available_count_ -= command_size;
166   return command;
167 }
168
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_);
173   }
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;
182   }
183   if (read_count == 0)
184     return false;
185   available_count_ += read_count;
186   return true;
187 }
188
189 }  // namespace
190
191 // SessionBackend -------------------------------------------------------------
192
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";
196
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";
200
201 // static
202 const int SessionBackend::kFileReadBufferSize = 1024;
203
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.
212 }
213
214 void SessionBackend::Init() {
215   if (inited_)
216     return;
217
218   inited_ = true;
219
220   // Create the directory for session info.
221   file_util::CreateDirectory(path_to_dir_);
222
223   MoveCurrentSessionToLastSession();
224 }
225
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();
235   }
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);
240   }
241   empty_file_ = false;
242   STLDeleteElements(commands);
243   delete commands;
244 }
245
246 void SessionBackend::ReadLastSessionCommands(
247     const CancelableTaskTracker::IsCanceledCallback& is_canceled,
248     const BaseSessionService::InternalGetCommandsCallback& callback) {
249   if (is_canceled.Run())
250     return;
251
252   Init();
253
254   ScopedVector<SessionCommand> commands;
255   ReadLastSessionCommandsImpl(&(commands.get()));
256   callback.Run(commands.Pass());
257 }
258
259 bool SessionBackend::ReadLastSessionCommandsImpl(
260     std::vector<SessionCommand*>* commands) {
261   Init();
262   SessionFileReader file_reader(GetLastSessionPath());
263   return file_reader.Read(type_, commands);
264 }
265
266 void SessionBackend::DeleteLastSession() {
267   Init();
268   base::DeleteFile(GetLastSessionPath(), false);
269 }
270
271 void SessionBackend::MoveCurrentSessionToLastSession() {
272   Init();
273   current_session_file_.reset(NULL);
274
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 (file_util::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));
288       }
289     }
290     last_session_valid_ = base::Move(current_session_path, last_session_path);
291   }
292
293   if (base::PathExists(current_session_path))
294     base::DeleteFile(current_session_path, false);
295
296   // Create and open the file for the current session.
297   ResetFile();
298 }
299
300 bool SessionBackend::ReadCurrentSessionCommandsImpl(
301     std::vector<SessionCommand*>* commands) {
302   Init();
303   SessionFileReader file_reader(GetCurrentSessionPath());
304   return file_reader.Read(type_, commands);
305 }
306
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;
323     }
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;
330     }
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;
337       }
338     }
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
344   }
345   return true;
346 }
347
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();
354   }
355 }
356
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);
367   }
368   if (!current_session_file_.get())
369     current_session_file_.reset(OpenAndWriteHeader(GetCurrentSessionPath()));
370   empty_file_ = true;
371 }
372
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();
389 }
390
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;
398 }
399
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;
407 }