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/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;
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.
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
{
41 typedef SessionCommand::id_type id_type
;
42 typedef SessionCommand::size_type size_type
;
44 explicit SessionFileReader(const base::FilePath
& path
)
46 buffer_(SessionBackend::kFileReadBufferSize
, 0),
49 file_
.reset(new net::FileStream(NULL
));
50 if (base::PathExists(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
57 bool Read(BaseSessionService::SessionType type
,
58 std::vector
<SessionCommand
*>* commands
);
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.
73 // Whether an error condition has been detected (
76 // As we read from the file, data goes here.
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
) {
97 TimeTicks start_time
= TimeTicks::Now();
98 read_count
= file_
->ReadUntilComplete(reinterpret_cast<char*>(&header
),
100 if (read_count
!= sizeof(header
) || header
.signature
!= kFileSignature
||
101 header
.version
!= kFileCurrentVersion
)
104 ScopedVector
<SessionCommand
> read_commands
;
105 SessionCommand
* command
;
106 while ((command
= ReadCommand()) && !errored_
)
107 read_commands
.push_back(command
);
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
);
114 UMA_HISTOGRAM_TIMES("SessionRestore.read_session_file_time",
115 TimeTicks::Now() - start_time
);
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
)) {
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.
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.
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";
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
;
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_
]),
179 if (read_count
< 0) {
185 available_count_
+= read_count
;
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";
202 const int SessionBackend::kFileReadBufferSize
= 1024;
204 SessionBackend::SessionBackend(BaseSessionService::SessionType type
,
205 const base::FilePath
& path_to_dir
)
207 path_to_dir_(path_to_dir
),
208 last_session_valid_(false),
211 // NOTE: this is invoked on the main thread, don't do file access here.
214 void SessionBackend::Init() {
220 // Create the directory for session info.
221 base::CreateDirectory(path_to_dir_
);
223 MoveCurrentSessionToLastSession();
226 void SessionBackend::AppendCommands(
227 std::vector
<SessionCommand
*>* commands
,
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()) {
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
);
242 STLDeleteElements(commands
);
246 void SessionBackend::ReadLastSessionCommands(
247 const CancelableTaskTracker::IsCanceledCallback
& is_canceled
,
248 const BaseSessionService::InternalGetCommandsCallback
& callback
) {
249 if (is_canceled
.Run())
254 ScopedVector
<SessionCommand
> commands
;
255 ReadLastSessionCommandsImpl(&(commands
.get()));
256 callback
.Run(commands
.Pass());
259 bool SessionBackend::ReadLastSessionCommandsImpl(
260 std::vector
<SessionCommand
*>* commands
) {
262 SessionFileReader
file_reader(GetLastSessionPath());
263 return file_reader
.Read(type_
, commands
);
266 void SessionBackend::DeleteLastSession() {
268 base::DeleteFile(GetLastSessionPath(), false);
271 void SessionBackend::MoveCurrentSessionToLastSession() {
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
)) {
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));
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.
300 bool SessionBackend::ReadCurrentSessionCommandsImpl(
301 std::vector
<SessionCommand
*>* commands
) {
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
) {
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
);
317 UMA_HISTOGRAM_COUNTS("SessionRestore.command_size", total_size
);
318 wrote
= file
->WriteSync(reinterpret_cast<const char*>(&total_size
),
320 if (wrote
!= sizeof(total_size
)) {
321 NOTREACHED() << "error writing";
324 id_type command_id
= (*i
)->id();
325 wrote
= file
->WriteSync(reinterpret_cast<char*>(&command_id
),
327 if (wrote
!= sizeof(command_id
)) {
328 NOTREACHED() << "error writing";
331 if (content_size
> 0) {
332 wrote
= file
->WriteSync(reinterpret_cast<char*>((*i
)->contents()),
334 if (wrote
!= content_size
) {
335 NOTREACHED() << "error writing";
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
348 SessionBackend::~SessionBackend() {
349 if (current_session_file_
.get()) {
350 // Destructor performs file IO because file is open in sync mode.
352 base::ThreadRestrictions::ScopedAllowIO allow_io
;
353 current_session_file_
.reset();
357 void SessionBackend::ResetFile() {
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
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()));
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
)
382 header
.signature
= kFileSignature
;
383 header
.version
= kFileCurrentVersion
;
384 int wrote
= file
->WriteSync(reinterpret_cast<char*>(&header
),
386 if (wrote
!= sizeof(header
))
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
);
396 path
= path
.AppendASCII(kLastSessionFileName
);
400 base::FilePath
SessionBackend::GetCurrentSessionPath() {
401 base::FilePath path
= path_to_dir_
;
402 if (type_
== BaseSessionService::TAB_RESTORE
)
403 path
= path
.AppendASCII(kCurrentTabSessionFileName
);
405 path
= path
.AppendASCII(kCurrentSessionFileName
);