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"
9 #include "base/file_util.h"
10 #include "base/files/file.h"
11 #include "base/memory/scoped_vector.h"
12 #include "base/metrics/histogram.h"
13 #include "base/threading/thread_restrictions.h"
15 using base::TimeTicks
;
17 // File version number.
18 static const int32 kFileCurrentVersion
= 1;
20 // The signature at the beginning of the file = SSNS (Sessions).
21 static const int32 kFileSignature
= 0x53534E53;
25 // The file header is the first bytes written to the file,
26 // and is used to identify the file as one written by us.
32 // SessionFileReader ----------------------------------------------------------
34 // SessionFileReader is responsible for reading the set of SessionCommands that
35 // describe a Session back from a file. SessionFileRead does minimal error
36 // checking on the file (pretty much only that the header is valid).
38 class SessionFileReader
{
40 typedef SessionCommand::id_type id_type
;
41 typedef SessionCommand::size_type size_type
;
43 explicit SessionFileReader(const base::FilePath
& path
)
45 buffer_(SessionBackend::kFileReadBufferSize
, 0),
48 file_
.reset(new base::File(
49 path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
));
51 // Reads the contents of the file specified in the constructor, returning
52 // true on success. It is up to the caller to free all SessionCommands
54 bool Read(BaseSessionService::SessionType type
,
55 std::vector
<SessionCommand
*>* commands
);
58 // Reads a single command, returning it. A return value of NULL indicates
59 // either there are no commands, or there was an error. Use errored_ to
60 // distinguish the two. If NULL is returned, and there is no error, it means
61 // the end of file was successfully reached.
62 SessionCommand
* ReadCommand();
64 // Shifts the unused portion of buffer_ to the beginning and fills the
65 // remaining portion with data from the file. Returns false if the buffer
66 // couldn't be filled. A return value of false only signals an error if
67 // errored_ is set to true.
70 // Whether an error condition has been detected (
73 // As we read from the file, data goes here.
77 scoped_ptr
<base::File
> file_
;
79 // Position in buffer_ of the data.
80 size_t buffer_position_
;
82 // Number of available bytes; relative to buffer_position_.
83 size_t available_count_
;
85 DISALLOW_COPY_AND_ASSIGN(SessionFileReader
);
88 bool SessionFileReader::Read(BaseSessionService::SessionType type
,
89 std::vector
<SessionCommand
*>* commands
) {
90 if (!file_
->IsValid())
94 TimeTicks start_time
= TimeTicks::Now();
95 read_count
= file_
->ReadAtCurrentPos(reinterpret_cast<char*>(&header
),
97 if (read_count
!= sizeof(header
) || header
.signature
!= kFileSignature
||
98 header
.version
!= kFileCurrentVersion
)
101 ScopedVector
<SessionCommand
> read_commands
;
102 for (SessionCommand
* command
= ReadCommand(); command
&& !errored_
;
103 command
= ReadCommand())
104 read_commands
.push_back(command
);
106 read_commands
.swap(*commands
);
107 if (type
== BaseSessionService::TAB_RESTORE
) {
108 UMA_HISTOGRAM_TIMES("TabRestore.read_session_file_time",
109 TimeTicks::Now() - start_time
);
111 UMA_HISTOGRAM_TIMES("SessionRestore.read_session_file_time",
112 TimeTicks::Now() - start_time
);
117 SessionCommand
* SessionFileReader::ReadCommand() {
118 // Make sure there is enough in the buffer for the size of the next command.
119 if (available_count_
< sizeof(size_type
)) {
122 if (available_count_
< sizeof(size_type
)) {
123 VLOG(1) << "SessionFileReader::ReadCommand, file incomplete";
124 // Still couldn't read a valid size for the command, assume write was
125 // incomplete and return NULL.
129 // Get the size of the command.
130 size_type command_size
;
131 memcpy(&command_size
, &(buffer_
[buffer_position_
]), sizeof(command_size
));
132 buffer_position_
+= sizeof(command_size
);
133 available_count_
-= sizeof(command_size
);
135 if (command_size
== 0) {
136 VLOG(1) << "SessionFileReader::ReadCommand, empty command";
137 // Empty command. Shouldn't happen if write was successful, fail.
141 // Make sure buffer has the complete contents of the command.
142 if (command_size
> available_count_
) {
143 if (command_size
> buffer_
.size())
144 buffer_
.resize((command_size
/ 1024 + 1) * 1024, 0);
145 if (!FillBuffer() || command_size
> available_count_
) {
146 // Again, assume the file was ok, and just the last chunk was lost.
147 VLOG(1) << "SessionFileReader::ReadCommand, last chunk lost";
151 const id_type command_id
= buffer_
[buffer_position_
];
152 // NOTE: command_size includes the size of the id, which is not part of
153 // the contents of the SessionCommand.
154 SessionCommand
* command
=
155 new SessionCommand(command_id
, command_size
- sizeof(id_type
));
156 if (command_size
> sizeof(id_type
)) {
157 memcpy(command
->contents(),
158 &(buffer_
[buffer_position_
+ sizeof(id_type
)]),
159 command_size
- sizeof(id_type
));
161 buffer_position_
+= command_size
;
162 available_count_
-= command_size
;
166 bool SessionFileReader::FillBuffer() {
167 if (available_count_
> 0 && buffer_position_
> 0) {
168 // Shift buffer to beginning.
169 memmove(&(buffer_
[0]), &(buffer_
[buffer_position_
]), available_count_
);
171 buffer_position_
= 0;
172 DCHECK(buffer_position_
+ available_count_
< buffer_
.size());
173 int to_read
= static_cast<int>(buffer_
.size() - available_count_
);
174 int read_count
= file_
->ReadAtCurrentPos(&(buffer_
[available_count_
]),
176 if (read_count
< 0) {
182 available_count_
+= read_count
;
188 // SessionBackend -------------------------------------------------------------
190 // File names (current and previous) for a type of TAB.
191 static const char* kCurrentTabSessionFileName
= "Current Tabs";
192 static const char* kLastTabSessionFileName
= "Last Tabs";
194 // File names (current and previous) for a type of SESSION.
195 static const char* kCurrentSessionFileName
= "Current Session";
196 static const char* kLastSessionFileName
= "Last Session";
199 const int SessionBackend::kFileReadBufferSize
= 1024;
201 SessionBackend::SessionBackend(BaseSessionService::SessionType type
,
202 const base::FilePath
& path_to_dir
)
204 path_to_dir_(path_to_dir
),
205 last_session_valid_(false),
208 // NOTE: this is invoked on the main thread, don't do file access here.
211 void SessionBackend::Init() {
217 // Create the directory for session info.
218 base::CreateDirectory(path_to_dir_
);
220 MoveCurrentSessionToLastSession();
223 void SessionBackend::AppendCommands(
224 std::vector
<SessionCommand
*>* commands
,
227 // Make sure and check current_session_file_, if opening the file failed
228 // current_session_file_ will be NULL.
229 if ((reset_first
&& !empty_file_
) || !current_session_file_
.get() ||
230 !current_session_file_
->IsValid()) {
233 // Need to check current_session_file_ again, ResetFile may fail.
234 if (current_session_file_
.get() && current_session_file_
->IsValid() &&
235 !AppendCommandsToFile(current_session_file_
.get(), *commands
)) {
236 current_session_file_
.reset(NULL
);
239 STLDeleteElements(commands
);
243 void SessionBackend::ReadLastSessionCommands(
244 const base::CancelableTaskTracker::IsCanceledCallback
& is_canceled
,
245 const BaseSessionService::InternalGetCommandsCallback
& callback
) {
246 if (is_canceled
.Run())
251 ScopedVector
<SessionCommand
> commands
;
252 ReadLastSessionCommandsImpl(&(commands
.get()));
253 callback
.Run(commands
.Pass());
256 bool SessionBackend::ReadLastSessionCommandsImpl(
257 std::vector
<SessionCommand
*>* commands
) {
259 SessionFileReader
file_reader(GetLastSessionPath());
260 return file_reader
.Read(type_
, commands
);
263 void SessionBackend::DeleteLastSession() {
265 base::DeleteFile(GetLastSessionPath(), false);
268 void SessionBackend::MoveCurrentSessionToLastSession() {
270 current_session_file_
.reset(NULL
);
272 const base::FilePath current_session_path
= GetCurrentSessionPath();
273 const base::FilePath last_session_path
= GetLastSessionPath();
274 if (base::PathExists(last_session_path
))
275 base::DeleteFile(last_session_path
, false);
276 if (base::PathExists(current_session_path
)) {
278 if (base::GetFileSize(current_session_path
, &file_size
)) {
279 if (type_
== BaseSessionService::TAB_RESTORE
) {
280 UMA_HISTOGRAM_COUNTS("TabRestore.last_session_file_size",
281 static_cast<int>(file_size
/ 1024));
283 UMA_HISTOGRAM_COUNTS("SessionRestore.last_session_file_size",
284 static_cast<int>(file_size
/ 1024));
287 last_session_valid_
= base::Move(current_session_path
, last_session_path
);
290 if (base::PathExists(current_session_path
))
291 base::DeleteFile(current_session_path
, false);
293 // Create and open the file for the current session.
297 bool SessionBackend::ReadCurrentSessionCommandsImpl(
298 std::vector
<SessionCommand
*>* commands
) {
300 SessionFileReader
file_reader(GetCurrentSessionPath());
301 return file_reader
.Read(type_
, commands
);
304 bool SessionBackend::AppendCommandsToFile(base::File
* file
,
305 const std::vector
<SessionCommand
*>& commands
) {
306 for (std::vector
<SessionCommand
*>::const_iterator i
= commands
.begin();
307 i
!= commands
.end(); ++i
) {
309 const size_type content_size
= static_cast<size_type
>((*i
)->size());
310 const size_type total_size
= content_size
+ sizeof(id_type
);
311 if (type_
== BaseSessionService::TAB_RESTORE
)
312 UMA_HISTOGRAM_COUNTS("TabRestore.command_size", total_size
);
314 UMA_HISTOGRAM_COUNTS("SessionRestore.command_size", total_size
);
315 wrote
= file
->WriteAtCurrentPos(reinterpret_cast<const char*>(&total_size
),
317 if (wrote
!= sizeof(total_size
)) {
318 NOTREACHED() << "error writing";
321 id_type command_id
= (*i
)->id();
322 wrote
= file
->WriteAtCurrentPos(reinterpret_cast<char*>(&command_id
),
324 if (wrote
!= sizeof(command_id
)) {
325 NOTREACHED() << "error writing";
328 if (content_size
> 0) {
329 wrote
= file
->WriteAtCurrentPos(reinterpret_cast<char*>((*i
)->contents()),
331 if (wrote
!= content_size
) {
332 NOTREACHED() << "error writing";
337 #if defined(OS_CHROMEOS)
343 SessionBackend::~SessionBackend() {
344 if (current_session_file_
.get()) {
345 // Destructor performs file IO because file is open in sync mode.
347 base::ThreadRestrictions::ScopedAllowIO allow_io
;
348 current_session_file_
.reset();
352 void SessionBackend::ResetFile() {
354 if (current_session_file_
.get()) {
355 // File is already open, truncate it. We truncate instead of closing and
356 // reopening to avoid the possibility of scanners locking the file out
357 // from under us once we close it. If truncation fails, we'll try to
359 const int header_size
= static_cast<int>(sizeof(FileHeader
));
360 if (current_session_file_
->Seek(
361 base::File::FROM_BEGIN
, header_size
) != header_size
||
362 !current_session_file_
->SetLength(header_size
))
363 current_session_file_
.reset(NULL
);
365 if (!current_session_file_
.get())
366 current_session_file_
.reset(OpenAndWriteHeader(GetCurrentSessionPath()));
370 base::File
* SessionBackend::OpenAndWriteHeader(const base::FilePath
& path
) {
371 DCHECK(!path
.empty());
372 scoped_ptr
<base::File
> file(new base::File(
374 base::File::FLAG_CREATE_ALWAYS
| base::File::FLAG_WRITE
|
375 base::File::FLAG_EXCLUSIVE_WRITE
| base::File::FLAG_EXCLUSIVE_READ
));
376 if (!file
->IsValid())
379 header
.signature
= kFileSignature
;
380 header
.version
= kFileCurrentVersion
;
381 int wrote
= file
->WriteAtCurrentPos(reinterpret_cast<char*>(&header
),
383 if (wrote
!= sizeof(header
))
385 return file
.release();
388 base::FilePath
SessionBackend::GetLastSessionPath() {
389 base::FilePath path
= path_to_dir_
;
390 if (type_
== BaseSessionService::TAB_RESTORE
)
391 path
= path
.AppendASCII(kLastTabSessionFileName
);
393 path
= path
.AppendASCII(kLastSessionFileName
);
397 base::FilePath
SessionBackend::GetCurrentSessionPath() {
398 base::FilePath path
= path_to_dir_
;
399 if (type_
== BaseSessionService::TAB_RESTORE
)
400 path
= path
.AppendASCII(kCurrentTabSessionFileName
);
402 path
= path
.AppendASCII(kCurrentSessionFileName
);