1 // Copyright 2014 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/chromeos/file_system_provider/fileapi/file_stream_writer.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/macros.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/threading/thread_task_runner_handle.h"
13 #include "base/trace_event/trace_event.h"
14 #include "chrome/browser/chromeos/file_system_provider/abort_callback.h"
15 #include "chrome/browser/chromeos/file_system_provider/fileapi/provider_async_file_util.h"
16 #include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
17 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
18 #include "chrome/browser/chromeos/file_system_provider/scoped_file_opener.h"
19 #include "content/public/browser/browser_task_traits.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "net/base/io_buffer.h"
22 #include "net/base/net_errors.h"
23
24 using content::BrowserThread;
25
26 namespace chromeos {
27 namespace file_system_provider {
28
29 class FileStreamWriter::OperationRunner
30 : public base::RefCountedThreadSafe<
31 FileStreamWriter::OperationRunner,
32 content::BrowserThread::DeleteOnUIThread> {
33 public:
OperationRunner()34 OperationRunner() : file_handle_(0) {}
35
36 // Opens a file for writing and calls the completion callback. Must be called
37 // on UI thread.
OpenFileOnUIThread(const storage::FileSystemURL & url,storage::AsyncFileUtil::StatusCallback callback)38 void OpenFileOnUIThread(const storage::FileSystemURL& url,
39 storage::AsyncFileUtil::StatusCallback callback) {
40 DCHECK_CURRENTLY_ON(BrowserThread::UI);
41 DCHECK(abort_callback_.is_null());
42
43 util::FileSystemURLParser parser(url);
44 if (!parser.Parse()) {
45 content::GetIOThreadTaskRunner({})->PostTask(
46 FROM_HERE,
47 base::BindOnce(std::move(callback), base::File::FILE_ERROR_SECURITY));
48 return;
49 }
50
51 file_system_ = parser.file_system()->GetWeakPtr();
52 file_opener_.reset(new ScopedFileOpener(
53 parser.file_system(), parser.file_path(), OPEN_FILE_MODE_WRITE,
54 base::Bind(&OperationRunner::OnOpenFileCompletedOnUIThread, this,
55 base::Passed(&callback))));
56 }
57
58 // Requests writing bytes to the file. In case of either success or a failure
59 // |callback| is executed. Must be called on UI thread.
WriteFileOnUIThread(scoped_refptr<net::IOBuffer> buffer,int64_t offset,int length,storage::AsyncFileUtil::StatusCallback callback)60 void WriteFileOnUIThread(scoped_refptr<net::IOBuffer> buffer,
61 int64_t offset,
62 int length,
63 storage::AsyncFileUtil::StatusCallback callback) {
64 DCHECK_CURRENTLY_ON(BrowserThread::UI);
65 DCHECK(abort_callback_.is_null());
66
67 // If the file system got unmounted, then abort the writing operation.
68 if (!file_system_.get()) {
69 content::GetIOThreadTaskRunner({})->PostTask(
70 FROM_HERE,
71 base::BindOnce(std::move(callback), base::File::FILE_ERROR_ABORT));
72 return;
73 }
74
75 abort_callback_ = file_system_->WriteFile(
76 file_handle_, buffer.get(), offset, length,
77 base::BindOnce(&OperationRunner::OnWriteFileCompletedOnUIThread, this,
78 std::move(callback)));
79 }
80
81 // Aborts the most recent operation (if exists) and closes a file if opened.
82 // The runner must not be used anymore after calling this method.
CloseRunnerOnUIThread()83 void CloseRunnerOnUIThread() {
84 DCHECK_CURRENTLY_ON(BrowserThread::UI);
85
86 if (!abort_callback_.is_null())
87 std::move(abort_callback_).Run();
88
89 // Close the file (if opened).
90 file_opener_.reset();
91 }
92
93 private:
94 friend struct content::BrowserThread::DeleteOnThread<
95 content::BrowserThread::UI>;
96 friend class base::DeleteHelper<OperationRunner>;
97
~OperationRunner()98 virtual ~OperationRunner() {}
99
100 // Remembers a file handle for further operations and forwards the result to
101 // the IO thread.
OnOpenFileCompletedOnUIThread(storage::AsyncFileUtil::StatusCallback callback,int file_handle,base::File::Error result)102 void OnOpenFileCompletedOnUIThread(
103 storage::AsyncFileUtil::StatusCallback callback,
104 int file_handle,
105 base::File::Error result) {
106 DCHECK_CURRENTLY_ON(BrowserThread::UI);
107
108 abort_callback_.Reset();
109 if (result == base::File::FILE_OK)
110 file_handle_ = file_handle;
111
112 content::GetIOThreadTaskRunner({})->PostTask(
113 FROM_HERE, base::BindOnce(std::move(callback), result));
114 }
115
116 // Forwards a response of writing to a file to the IO thread.
OnWriteFileCompletedOnUIThread(storage::AsyncFileUtil::StatusCallback callback,base::File::Error result)117 void OnWriteFileCompletedOnUIThread(
118 storage::AsyncFileUtil::StatusCallback callback,
119 base::File::Error result) {
120 DCHECK_CURRENTLY_ON(BrowserThread::UI);
121
122 abort_callback_.Reset();
123 content::GetIOThreadTaskRunner({})->PostTask(
124 FROM_HERE, base::BindOnce(std::move(callback), result));
125 }
126
127 AbortCallback abort_callback_;
128 base::WeakPtr<ProvidedFileSystemInterface> file_system_;
129 std::unique_ptr<ScopedFileOpener> file_opener_;
130 int file_handle_;
131
132 DISALLOW_COPY_AND_ASSIGN(OperationRunner);
133 };
134
FileStreamWriter(const storage::FileSystemURL & url,int64_t initial_offset)135 FileStreamWriter::FileStreamWriter(const storage::FileSystemURL& url,
136 int64_t initial_offset)
137 : url_(url),
138 current_offset_(initial_offset),
139 runner_(new OperationRunner),
140 state_(NOT_INITIALIZED) {}
141
~FileStreamWriter()142 FileStreamWriter::~FileStreamWriter() {
143 // Close the runner explicitly if the file streamer is
144 if (state_ != CANCELLING) {
145 content::GetUIThreadTaskRunner({})->PostTask(
146 FROM_HERE,
147 base::BindOnce(&OperationRunner::CloseRunnerOnUIThread, runner_));
148 }
149
150 // If a write is in progress, mark it as completed.
151 TRACE_EVENT_NESTABLE_ASYNC_END0("file_system_provider",
152 "FileStreamWriter::Write", this);
153 }
154
Initialize(base::OnceClosure pending_closure,net::CompletionOnceCallback error_callback)155 void FileStreamWriter::Initialize(base::OnceClosure pending_closure,
156 net::CompletionOnceCallback error_callback) {
157 DCHECK_CURRENTLY_ON(BrowserThread::IO);
158 DCHECK_EQ(NOT_INITIALIZED, state_);
159 state_ = INITIALIZING;
160
161 content::GetUIThreadTaskRunner({})->PostTask(
162 FROM_HERE,
163 base::BindOnce(&OperationRunner::OpenFileOnUIThread, runner_, url_,
164 base::BindOnce(&FileStreamWriter::OnOpenFileCompleted,
165 weak_ptr_factory_.GetWeakPtr(),
166 std::move(pending_closure),
167 std::move(error_callback))));
168 }
169
OnOpenFileCompleted(base::OnceClosure pending_closure,net::CompletionOnceCallback error_callback,base::File::Error result)170 void FileStreamWriter::OnOpenFileCompleted(
171 base::OnceClosure pending_closure,
172 net::CompletionOnceCallback error_callback,
173 base::File::Error result) {
174 DCHECK_CURRENTLY_ON(BrowserThread::IO);
175 DCHECK(state_ == INITIALIZING || state_ == CANCELLING);
176 if (state_ == CANCELLING)
177 return;
178
179 // In case of an error, return immediately using the |error_callback| of the
180 // Write() pending request.
181 if (result != base::File::FILE_OK) {
182 state_ = FAILED;
183 std::move(error_callback).Run(net::FileErrorToNetError(result));
184 return;
185 }
186
187 DCHECK_EQ(base::File::FILE_OK, result);
188 state_ = INITIALIZED;
189
190 // Run the task waiting for the initialization to be completed.
191 std::move(pending_closure).Run();
192 }
193
Write(net::IOBuffer * buffer,int buffer_length,net::CompletionOnceCallback callback)194 int FileStreamWriter::Write(net::IOBuffer* buffer,
195 int buffer_length,
196 net::CompletionOnceCallback callback) {
197 DCHECK_CURRENTLY_ON(BrowserThread::IO);
198 TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("file_system_provider",
199 "FileStreamWriter::Write", this,
200 "buffer_length", buffer_length);
201
202 write_callback_ = std::move(callback);
203 switch (state_) {
204 case NOT_INITIALIZED:
205 // Lazily initialize with the first call to Write().
206 Initialize(
207 base::BindOnce(&FileStreamWriter::WriteAfterInitialized,
208 weak_ptr_factory_.GetWeakPtr(),
209 base::WrapRefCounted(buffer), buffer_length,
210 base::BindOnce(&FileStreamWriter::OnWriteCompleted,
211 weak_ptr_factory_.GetWeakPtr())),
212 base::BindOnce(&FileStreamWriter::OnWriteCompleted,
213 weak_ptr_factory_.GetWeakPtr()));
214 break;
215
216 case INITIALIZING:
217 NOTREACHED();
218 break;
219
220 case INITIALIZED:
221 WriteAfterInitialized(buffer, buffer_length,
222 base::BindOnce(&FileStreamWriter::OnWriteCompleted,
223 weak_ptr_factory_.GetWeakPtr()));
224 break;
225
226 case EXECUTING:
227 case FAILED:
228 case CANCELLING:
229 NOTREACHED();
230 break;
231 }
232
233 return net::ERR_IO_PENDING;
234 }
235
Cancel(net::CompletionOnceCallback callback)236 int FileStreamWriter::Cancel(net::CompletionOnceCallback callback) {
237 DCHECK_CURRENTLY_ON(BrowserThread::IO);
238
239 if (state_ != INITIALIZING && state_ != EXECUTING)
240 return net::ERR_UNEXPECTED;
241
242 state_ = CANCELLING;
243
244 // Abort and optimistically return an OK result code, as the aborting
245 // operation is always forced and can't be cancelled. Similarly, for closing
246 // files.
247 content::GetUIThreadTaskRunner({})->PostTask(
248 FROM_HERE,
249 base::BindOnce(&OperationRunner::CloseRunnerOnUIThread, runner_));
250 base::ThreadTaskRunnerHandle::Get()->PostTask(
251 FROM_HERE, base::BindOnce(std::move(callback), net::OK));
252
253 // If a write is in progress, mark it as completed.
254 TRACE_EVENT_NESTABLE_ASYNC_END0("file_system_provider",
255 "FileStreamWriter::Write", this);
256
257 return net::ERR_IO_PENDING;
258 }
259
Flush(net::CompletionOnceCallback callback)260 int FileStreamWriter::Flush(net::CompletionOnceCallback callback) {
261 DCHECK_CURRENTLY_ON(BrowserThread::IO);
262 DCHECK_NE(CANCELLING, state_);
263
264 base::ThreadTaskRunnerHandle::Get()->PostTask(
265 FROM_HERE,
266 base::BindOnce(std::move(callback),
267 state_ == INITIALIZED ? net::OK : net::ERR_FAILED));
268
269 return net::ERR_IO_PENDING;
270 }
271
OnWriteFileCompleted(int buffer_length,net::CompletionOnceCallback callback,base::File::Error result)272 void FileStreamWriter::OnWriteFileCompleted(
273 int buffer_length,
274 net::CompletionOnceCallback callback,
275 base::File::Error result) {
276 DCHECK_CURRENTLY_ON(BrowserThread::IO);
277 DCHECK(state_ == EXECUTING || state_ == CANCELLING);
278 if (state_ == CANCELLING)
279 return;
280
281 state_ = INITIALIZED;
282
283 if (result != base::File::FILE_OK) {
284 state_ = FAILED;
285 std::move(callback).Run(net::FileErrorToNetError(result));
286 return;
287 }
288
289 current_offset_ += buffer_length;
290 std::move(callback).Run(buffer_length);
291 }
292
OnWriteCompleted(int result)293 void FileStreamWriter::OnWriteCompleted(int result) {
294 DCHECK_CURRENTLY_ON(BrowserThread::IO);
295 if (state_ != CANCELLING)
296 std::move(write_callback_).Run(result);
297
298 TRACE_EVENT_NESTABLE_ASYNC_END0("file_system_provider",
299 "FileStreamWriter::Write", this);
300 }
301
WriteAfterInitialized(scoped_refptr<net::IOBuffer> buffer,int buffer_length,net::CompletionOnceCallback callback)302 void FileStreamWriter::WriteAfterInitialized(
303 scoped_refptr<net::IOBuffer> buffer,
304 int buffer_length,
305 net::CompletionOnceCallback callback) {
306 DCHECK_CURRENTLY_ON(BrowserThread::IO);
307 DCHECK(state_ == INITIALIZED || state_ == CANCELLING);
308 if (state_ == CANCELLING)
309 return;
310
311 state_ = EXECUTING;
312
313 content::GetUIThreadTaskRunner({})->PostTask(
314 FROM_HERE,
315 base::BindOnce(&OperationRunner::WriteFileOnUIThread, runner_, buffer,
316 current_offset_, buffer_length,
317 base::BindOnce(&FileStreamWriter::OnWriteFileCompleted,
318 weak_ptr_factory_.GetWeakPtr(),
319 buffer_length, std::move(callback))));
320 }
321
322 } // namespace file_system_provider
323 } // namespace chromeos
324