1 // Copyright 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 "components/sessions/session_backend.h"
9 #include "base/files/file.h"
10 #include "base/files/file_util.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
;
19 // File version number.
20 static const int32 kFileCurrentVersion
= 1;
22 // The signature at the beginning of the file = SSNS (Sessions).
23 static const int32 kFileSignature
= 0x53534E53;
27 // The file header is the first bytes written to the file,
28 // and is used to identify the file as one written by us.
34 // SessionFileReader ----------------------------------------------------------
36 // SessionFileReader is responsible for reading the set of SessionCommands that
37 // describe a Session back from a file. SessionFileRead does minimal error
38 // checking on the file (pretty much only that the header is valid).
40 class SessionFileReader
{
42 typedef sessions::SessionCommand::id_type id_type
;
43 typedef sessions::SessionCommand::size_type size_type
;
45 explicit SessionFileReader(const base::FilePath
& path
)
47 buffer_(SessionBackend::kFileReadBufferSize
, 0),
50 file_
.reset(new base::File(
51 path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
));
53 // Reads the contents of the file specified in the constructor, returning
54 // true on success. It is up to the caller to free all SessionCommands
56 bool Read(sessions::BaseSessionService::SessionType type
,
57 ScopedVector
<sessions::SessionCommand
>* commands
);
60 // Reads a single command, returning it. A return value of NULL indicates
61 // either there are no commands, or there was an error. Use errored_ to
62 // distinguish the two. If NULL is returned, and there is no error, it means
63 // the end of file was successfully reached.
64 sessions::SessionCommand
* ReadCommand();
66 // Shifts the unused portion of buffer_ to the beginning and fills the
67 // remaining portion with data from the file. Returns false if the buffer
68 // couldn't be filled. A return value of false only signals an error if
69 // errored_ is set to true.
72 // Whether an error condition has been detected (
75 // As we read from the file, data goes here.
79 scoped_ptr
<base::File
> file_
;
81 // Position in buffer_ of the data.
82 size_t buffer_position_
;
84 // Number of available bytes; relative to buffer_position_.
85 size_t available_count_
;
87 DISALLOW_COPY_AND_ASSIGN(SessionFileReader
);
90 bool SessionFileReader::Read(sessions::BaseSessionService::SessionType type
,
91 ScopedVector
<sessions::SessionCommand
>* commands
) {
92 if (!file_
->IsValid())
96 TimeTicks start_time
= TimeTicks::Now();
97 read_count
= file_
->ReadAtCurrentPos(reinterpret_cast<char*>(&header
),
99 if (read_count
!= sizeof(header
) || header
.signature
!= kFileSignature
||
100 header
.version
!= kFileCurrentVersion
)
103 ScopedVector
<sessions::SessionCommand
> read_commands
;
104 for (sessions::SessionCommand
* command
= ReadCommand(); command
&& !errored_
;
105 command
= ReadCommand())
106 read_commands
.push_back(command
);
108 read_commands
.swap(*commands
);
109 if (type
== sessions::BaseSessionService::TAB_RESTORE
) {
110 UMA_HISTOGRAM_TIMES("TabRestore.read_session_file_time",
111 TimeTicks::Now() - start_time
);
113 UMA_HISTOGRAM_TIMES("SessionRestore.read_session_file_time",
114 TimeTicks::Now() - start_time
);
119 sessions::SessionCommand
* SessionFileReader::ReadCommand() {
120 // Make sure there is enough in the buffer for the size of the next command.
121 if (available_count_
< sizeof(size_type
)) {
124 if (available_count_
< sizeof(size_type
)) {
125 VLOG(1) << "SessionFileReader::ReadCommand, file incomplete";
126 // Still couldn't read a valid size for the command, assume write was
127 // incomplete and return NULL.
131 // Get the size of the command.
132 size_type command_size
;
133 memcpy(&command_size
, &(buffer_
[buffer_position_
]), sizeof(command_size
));
134 buffer_position_
+= sizeof(command_size
);
135 available_count_
-= sizeof(command_size
);
137 if (command_size
== 0) {
138 VLOG(1) << "SessionFileReader::ReadCommand, empty command";
139 // Empty command. Shouldn't happen if write was successful, fail.
143 // Make sure buffer has the complete contents of the command.
144 if (command_size
> available_count_
) {
145 if (command_size
> buffer_
.size())
146 buffer_
.resize((command_size
/ 1024 + 1) * 1024, 0);
147 if (!FillBuffer() || command_size
> available_count_
) {
148 // Again, assume the file was ok, and just the last chunk was lost.
149 VLOG(1) << "SessionFileReader::ReadCommand, last chunk lost";
153 const id_type command_id
= buffer_
[buffer_position_
];
154 // NOTE: command_size includes the size of the id, which is not part of
155 // the contents of the SessionCommand.
156 sessions::SessionCommand
* command
=
157 new sessions::SessionCommand(command_id
, command_size
- sizeof(id_type
));
158 if (command_size
> sizeof(id_type
)) {
159 memcpy(command
->contents(),
160 &(buffer_
[buffer_position_
+ sizeof(id_type
)]),
161 command_size
- sizeof(id_type
));
163 buffer_position_
+= command_size
;
164 available_count_
-= command_size
;
168 bool SessionFileReader::FillBuffer() {
169 if (available_count_
> 0 && buffer_position_
> 0) {
170 // Shift buffer to beginning.
171 memmove(&(buffer_
[0]), &(buffer_
[buffer_position_
]), available_count_
);
173 buffer_position_
= 0;
174 DCHECK(buffer_position_
+ available_count_
< buffer_
.size());
175 int to_read
= static_cast<int>(buffer_
.size() - available_count_
);
176 int read_count
= file_
->ReadAtCurrentPos(&(buffer_
[available_count_
]),
178 if (read_count
< 0) {
184 available_count_
+= read_count
;
190 // SessionBackend -------------------------------------------------------------
192 // File names (current and previous) for a type of TAB.
193 static const char* kCurrentTabSessionFileName
= "Current Tabs";
194 static const char* kLastTabSessionFileName
= "Last Tabs";
196 // File names (current and previous) for a type of SESSION.
197 static const char* kCurrentSessionFileName
= "Current Session";
198 static const char* kLastSessionFileName
= "Last Session";
201 const int SessionBackend::kFileReadBufferSize
= 1024;
203 SessionBackend::SessionBackend(sessions::BaseSessionService::SessionType type
,
204 const base::FilePath
& path_to_dir
)
206 path_to_dir_(path_to_dir
),
207 last_session_valid_(false),
210 // NOTE: this is invoked on the main thread, don't do file access here.
213 void SessionBackend::Init() {
219 // Create the directory for session info.
220 base::CreateDirectory(path_to_dir_
);
222 MoveCurrentSessionToLastSession();
225 void SessionBackend::AppendCommands(
226 ScopedVector
<sessions::SessionCommand
> commands
,
229 // Make sure and check current_session_file_, if opening the file failed
230 // current_session_file_ will be NULL.
231 if ((reset_first
&& !empty_file_
) || !current_session_file_
.get() ||
232 !current_session_file_
->IsValid()) {
235 // Need to check current_session_file_ again, ResetFile may fail.
236 if (current_session_file_
.get() && current_session_file_
->IsValid() &&
237 !AppendCommandsToFile(current_session_file_
.get(), commands
)) {
238 current_session_file_
.reset(NULL
);
243 void SessionBackend::ReadLastSessionCommands(
244 const base::CancelableTaskTracker::IsCanceledCallback
& is_canceled
,
245 const sessions::BaseSessionService::GetCommandsCallback
& callback
) {
246 if (is_canceled
.Run())
251 ScopedVector
<sessions::SessionCommand
> commands
;
252 ReadLastSessionCommandsImpl(&commands
);
253 callback
.Run(commands
.Pass());
256 bool SessionBackend::ReadLastSessionCommandsImpl(
257 ScopedVector
<sessions::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_
== sessions::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 ScopedVector
<sessions::SessionCommand
>* commands
) {
300 SessionFileReader
file_reader(GetCurrentSessionPath());
301 return file_reader
.Read(type_
, commands
);
304 bool SessionBackend::AppendCommandsToFile(base::File
* file
,
305 const ScopedVector
<sessions::SessionCommand
>& commands
) {
306 for (ScopedVector
<sessions::SessionCommand
>::const_iterator i
=
308 i
!= commands
.end(); ++i
) {
310 const size_type content_size
= static_cast<size_type
>((*i
)->size());
311 const size_type total_size
= content_size
+ sizeof(id_type
);
312 if (type_
== sessions::BaseSessionService::TAB_RESTORE
)
313 UMA_HISTOGRAM_COUNTS("TabRestore.command_size", total_size
);
315 UMA_HISTOGRAM_COUNTS("SessionRestore.command_size", total_size
);
316 wrote
= file
->WriteAtCurrentPos(reinterpret_cast<const char*>(&total_size
),
318 if (wrote
!= sizeof(total_size
)) {
319 NOTREACHED() << "error writing";
322 id_type command_id
= (*i
)->id();
323 wrote
= file
->WriteAtCurrentPos(reinterpret_cast<char*>(&command_id
),
325 if (wrote
!= sizeof(command_id
)) {
326 NOTREACHED() << "error writing";
329 if (content_size
> 0) {
330 wrote
= file
->WriteAtCurrentPos(reinterpret_cast<char*>((*i
)->contents()),
332 if (wrote
!= content_size
) {
333 NOTREACHED() << "error writing";
338 #if defined(OS_CHROMEOS)
344 SessionBackend::~SessionBackend() {
345 if (current_session_file_
.get()) {
346 // Destructor performs file IO because file is open in sync mode.
348 base::ThreadRestrictions::ScopedAllowIO allow_io
;
349 current_session_file_
.reset();
353 void SessionBackend::ResetFile() {
355 if (current_session_file_
.get()) {
356 // File is already open, truncate it. We truncate instead of closing and
357 // reopening to avoid the possibility of scanners locking the file out
358 // from under us once we close it. If truncation fails, we'll try to
360 const int header_size
= static_cast<int>(sizeof(FileHeader
));
361 if (current_session_file_
->Seek(
362 base::File::FROM_BEGIN
, header_size
) != header_size
||
363 !current_session_file_
->SetLength(header_size
))
364 current_session_file_
.reset(NULL
);
366 if (!current_session_file_
.get())
367 current_session_file_
.reset(OpenAndWriteHeader(GetCurrentSessionPath()));
371 base::File
* SessionBackend::OpenAndWriteHeader(const base::FilePath
& path
) {
372 DCHECK(!path
.empty());
373 scoped_ptr
<base::File
> file(new base::File(
375 base::File::FLAG_CREATE_ALWAYS
| base::File::FLAG_WRITE
|
376 base::File::FLAG_EXCLUSIVE_WRITE
| base::File::FLAG_EXCLUSIVE_READ
));
377 if (!file
->IsValid())
380 header
.signature
= kFileSignature
;
381 header
.version
= kFileCurrentVersion
;
382 int wrote
= file
->WriteAtCurrentPos(reinterpret_cast<char*>(&header
),
384 if (wrote
!= sizeof(header
))
386 return file
.release();
389 base::FilePath
SessionBackend::GetLastSessionPath() {
390 base::FilePath path
= path_to_dir_
;
391 if (type_
== sessions::BaseSessionService::TAB_RESTORE
)
392 path
= path
.AppendASCII(kLastTabSessionFileName
);
394 path
= path
.AppendASCII(kLastSessionFileName
);
398 base::FilePath
SessionBackend::GetCurrentSessionPath() {
399 base::FilePath path
= path_to_dir_
;
400 if (type_
== sessions::BaseSessionService::TAB_RESTORE
)
401 path
= path
.AppendASCII(kCurrentTabSessionFileName
);
403 path
= path
.AppendASCII(kCurrentSessionFileName
);
407 } // namespace sessions