file_stream_reader.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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_reader.h"
6
7#include "base/debug/trace_event.h"
8#include "base/files/file.h"
9#include "base/memory/ref_counted.h"
10#include "chrome/browser/chromeos/file_system_provider/fileapi/provider_async_file_util.h"
11#include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
12#include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
13#include "content/public/browser/browser_thread.h"
14#include "net/base/io_buffer.h"
15#include "net/base/net_errors.h"
16
17using content::BrowserThread;
18
19namespace chromeos {
20namespace file_system_provider {
21namespace {
22
23// Dicards the callback from CloseFile().
24void EmptyStatusCallback(base::File::Error /* result */) {
25}
26
27// Converts net::CompletionCallback to net::Int64CompletionCallback.
28void Int64ToIntCompletionCallback(net::CompletionCallback callback,
29                                  int64 result) {
30  callback.Run(static_cast<int>(result));
31}
32
33}  // namespace
34
35class FileStreamReader::OperationRunner
36    : public base::RefCountedThreadSafe<FileStreamReader::OperationRunner> {
37 public:
38  OperationRunner() : file_handle_(-1) {}
39
40  // Opens a file for reading and calls the completion callback. Must be called
41  // on UI thread.
42  void OpenFileOnUIThread(
43      const storage::FileSystemURL& url,
44      const storage::AsyncFileUtil::StatusCallback& callback) {
45    DCHECK_CURRENTLY_ON(BrowserThread::UI);
46
47    util::FileSystemURLParser parser(url);
48    if (!parser.Parse()) {
49      BrowserThread::PostTask(
50          BrowserThread::IO,
51          FROM_HERE,
52          base::Bind(callback, base::File::FILE_ERROR_SECURITY));
53      return;
54    }
55
56    file_system_ = parser.file_system()->GetWeakPtr();
57    file_path_ = parser.file_path();
58    abort_callback_ = parser.file_system()->OpenFile(
59        file_path_,
60        ProvidedFileSystemInterface::OPEN_FILE_MODE_READ,
61        base::Bind(
62            &OperationRunner::OnOpenFileCompletedOnUIThread, this, callback));
63  }
64
65  // Closes a file. Ignores result, since it is called from a constructor.
66  // Must be called on UI thread.
67  void CloseFileOnUIThread() {
68    DCHECK_CURRENTLY_ON(BrowserThread::UI);
69    if (file_system_.get() && file_handle_ != -1) {
70      // Closing a file must not be aborted, since we could end up on files
71      // which are never closed.
72      file_system_->CloseFile(file_handle_, base::Bind(&EmptyStatusCallback));
73    }
74  }
75
76  // Requests reading contents of a file. In case of either success or a failure
77  // |callback| is executed. It can be called many times, until |has_more| is
78  // set to false. This function guarantees that it will succeed only if the
79  // file has not been changed while reading. Must be called on UI thread.
80  void ReadFileOnUIThread(
81      scoped_refptr<net::IOBuffer> buffer,
82      int64 offset,
83      int length,
84      const ProvidedFileSystemInterface::ReadChunkReceivedCallback& callback) {
85    DCHECK_CURRENTLY_ON(BrowserThread::UI);
86
87    // If the file system got unmounted, then abort the reading operation.
88    if (!file_system_.get()) {
89      BrowserThread::PostTask(
90          BrowserThread::IO,
91          FROM_HERE,
92          base::Bind(
93              callback, 0, false /* has_more */, base::File::FILE_ERROR_ABORT));
94      return;
95    }
96
97    abort_callback_ = file_system_->ReadFile(
98        file_handle_,
99        buffer.get(),
100        offset,
101        length,
102        base::Bind(
103            &OperationRunner::OnReadFileCompletedOnUIThread, this, callback));
104  }
105
106  // Requests metadata of a file. In case of either succes or a failure,
107  // |callback| is executed. Must be called on UI thread.
108  void GetMetadataOnUIThread(
109      const ProvidedFileSystemInterface::GetMetadataCallback& callback) {
110    DCHECK_CURRENTLY_ON(BrowserThread::UI);
111
112    // If the file system got unmounted, then abort the get length operation.
113    if (!file_system_.get()) {
114      BrowserThread::PostTask(
115          BrowserThread::IO,
116          FROM_HERE,
117          base::Bind(callback,
118                     base::Passed(make_scoped_ptr<EntryMetadata>(NULL)),
119                     base::File::FILE_ERROR_ABORT));
120      return;
121    }
122
123    abort_callback_ = file_system_->GetMetadata(
124        file_path_,
125        ProvidedFileSystemInterface::METADATA_FIELD_DEFAULT,
126        base::Bind(&OperationRunner::OnGetMetadataCompletedOnUIThread,
127                   this,
128                   callback));
129  }
130
131  // Aborts the most recent operation (if exists), and calls the callback.
132  void AbortOnUIThread(const storage::AsyncFileUtil::StatusCallback& callback) {
133    DCHECK_CURRENTLY_ON(BrowserThread::UI);
134
135    if (abort_callback_.is_null()) {
136      // No operation to be cancelled. At most a callback call, which will be
137      // discarded.
138      BrowserThread::PostTask(BrowserThread::IO,
139                              FROM_HERE,
140                              base::Bind(callback, base::File::FILE_OK));
141      return;
142    }
143
144    const ProvidedFileSystemInterface::AbortCallback abort_callback =
145        abort_callback_;
146    abort_callback_ = ProvidedFileSystemInterface::AbortCallback();
147    abort_callback.Run(base::Bind(
148        &OperationRunner::OnAbortCompletedOnUIThread, this, callback));
149  }
150
151 private:
152  friend class base::RefCountedThreadSafe<OperationRunner>;
153
154  virtual ~OperationRunner() {}
155
156  // Remembers a file handle for further operations and forwards the result to
157  // the IO thread.
158  void OnOpenFileCompletedOnUIThread(
159      const storage::AsyncFileUtil::StatusCallback& callback,
160      int file_handle,
161      base::File::Error result) {
162    DCHECK_CURRENTLY_ON(BrowserThread::UI);
163
164    file_handle_ = file_handle;
165    BrowserThread::PostTask(
166        BrowserThread::IO, FROM_HERE, base::Bind(callback, result));
167  }
168
169  // Forwards a metadata to the IO thread.
170  void OnGetMetadataCompletedOnUIThread(
171      const ProvidedFileSystemInterface::GetMetadataCallback& callback,
172      scoped_ptr<EntryMetadata> metadata,
173      base::File::Error result) {
174    DCHECK_CURRENTLY_ON(BrowserThread::UI);
175    BrowserThread::PostTask(
176        BrowserThread::IO,
177        FROM_HERE,
178        base::Bind(callback, base::Passed(&metadata), result));
179  }
180
181  // Forwards a response of reading from a file to the IO thread.
182  void OnReadFileCompletedOnUIThread(
183      const ProvidedFileSystemInterface::ReadChunkReceivedCallback&
184          chunk_received_callback,
185      int chunk_length,
186      bool has_more,
187      base::File::Error result) {
188    DCHECK_CURRENTLY_ON(BrowserThread::UI);
189    BrowserThread::PostTask(
190        BrowserThread::IO,
191        FROM_HERE,
192        base::Bind(chunk_received_callback, chunk_length, has_more, result));
193  }
194
195  // Forwards a response of aborting an operation to the IO thread.
196  void OnAbortCompletedOnUIThread(
197      const storage::AsyncFileUtil::StatusCallback& callback,
198      base::File::Error result) {
199    DCHECK_CURRENTLY_ON(BrowserThread::UI);
200    BrowserThread::PostTask(
201        BrowserThread::IO, FROM_HERE, base::Bind(callback, result));
202  }
203
204  ProvidedFileSystemInterface::AbortCallback abort_callback_;
205  base::WeakPtr<ProvidedFileSystemInterface> file_system_;
206  base::FilePath file_path_;
207  int file_handle_;
208
209  DISALLOW_COPY_AND_ASSIGN(OperationRunner);
210};
211
212FileStreamReader::FileStreamReader(storage::FileSystemContext* context,
213                                   const storage::FileSystemURL& url,
214                                   int64 initial_offset,
215                                   const base::Time& expected_modification_time)
216    : url_(url),
217      current_offset_(initial_offset),
218      current_length_(0),
219      expected_modification_time_(expected_modification_time),
220      runner_(new OperationRunner),
221      state_(NOT_INITIALIZED),
222      weak_ptr_factory_(this) {
223}
224
225FileStreamReader::~FileStreamReader() {
226  // FileStreamReader doesn't have a Cancel() method like in FileStreamWriter.
227  // Therefore, aborting is done from the destructor.
228  BrowserThread::PostTask(BrowserThread::UI,
229                          FROM_HERE,
230                          base::Bind(&OperationRunner::AbortOnUIThread,
231                                     runner_,
232                                     base::Bind(&EmptyStatusCallback)));
233
234  BrowserThread::PostTask(
235      BrowserThread::UI,
236      FROM_HERE,
237      base::Bind(&OperationRunner::CloseFileOnUIThread, runner_));
238}
239
240void FileStreamReader::Initialize(
241    const base::Closure& pending_closure,
242    const net::Int64CompletionCallback& error_callback) {
243  DCHECK_EQ(NOT_INITIALIZED, state_);
244  state_ = INITIALIZING;
245
246  BrowserThread::PostTask(
247      BrowserThread::UI,
248      FROM_HERE,
249      base::Bind(&OperationRunner::OpenFileOnUIThread,
250                 runner_,
251                 url_,
252                 base::Bind(&FileStreamReader::OnOpenFileCompleted,
253                            weak_ptr_factory_.GetWeakPtr(),
254                            pending_closure,
255                            error_callback)));
256}
257
258void FileStreamReader::OnOpenFileCompleted(
259    const base::Closure& pending_closure,
260    const net::Int64CompletionCallback& error_callback,
261    base::File::Error result) {
262  DCHECK_CURRENTLY_ON(BrowserThread::IO);
263  DCHECK_EQ(INITIALIZING, state_);
264
265  // In case of an error, return immediately using the |error_callback| of the
266  // Read() or GetLength() pending request.
267  if (result != base::File::FILE_OK) {
268    state_ = FAILED;
269    error_callback.Run(net::FileErrorToNetError(result));
270    return;
271  }
272
273  DCHECK_EQ(base::File::FILE_OK, result);
274
275  // Verify the last modification time.
276  BrowserThread::PostTask(
277      BrowserThread::UI,
278      FROM_HERE,
279      base::Bind(&OperationRunner::GetMetadataOnUIThread,
280                 runner_,
281                 base::Bind(&FileStreamReader::OnInitializeCompleted,
282                            weak_ptr_factory_.GetWeakPtr(),
283                            pending_closure,
284                            error_callback)));
285}
286
287void FileStreamReader::OnInitializeCompleted(
288    const base::Closure& pending_closure,
289    const net::Int64CompletionCallback& error_callback,
290    scoped_ptr<EntryMetadata> metadata,
291    base::File::Error result) {
292  DCHECK_CURRENTLY_ON(BrowserThread::IO);
293  DCHECK_EQ(INITIALIZING, state_);
294
295  // In case of an error, abort.
296  if (result != base::File::FILE_OK) {
297    state_ = FAILED;
298    error_callback.Run(net::FileErrorToNetError(result));
299    return;
300  }
301
302  // If the file modification time has changed, then abort. Note, that the file
303  // may be changed without affecting the modification time.
304  DCHECK(metadata.get());
305  if (!expected_modification_time_.is_null() &&
306      metadata->modification_time != expected_modification_time_) {
307    state_ = FAILED;
308    error_callback.Run(net::ERR_UPLOAD_FILE_CHANGED);
309    return;
310  }
311
312  DCHECK_EQ(base::File::FILE_OK, result);
313  state_ = INITIALIZED;
314
315  // Run the task waiting for the initialization to be completed.
316  pending_closure.Run();
317}
318
319int FileStreamReader::Read(net::IOBuffer* buffer,
320                           int buffer_length,
321                           const net::CompletionCallback& callback) {
322  DCHECK_CURRENTLY_ON(BrowserThread::IO);
323  TRACE_EVENT_ASYNC_BEGIN1("file_system_provider",
324                           "FileStreamReader::Read",
325                           this,
326                           "buffer_length",
327                           buffer_length);
328
329  switch (state_) {
330    case NOT_INITIALIZED:
331      // Lazily initialize with the first call to Read().
332    Initialize(base::Bind(&FileStreamReader::ReadAfterInitialized,
333                          weak_ptr_factory_.GetWeakPtr(),
334                          make_scoped_refptr(buffer),
335                          buffer_length,
336                          base::Bind(&FileStreamReader::OnReadCompleted,
337                                     weak_ptr_factory_.GetWeakPtr(),
338                                     callback)),
339               base::Bind(&Int64ToIntCompletionCallback,
340                          base::Bind(&FileStreamReader::OnReadCompleted,
341                                     weak_ptr_factory_.GetWeakPtr(),
342                                     callback)));
343    break;
344
345    case INITIALIZING:
346      NOTREACHED();
347      break;
348
349    case INITIALIZED:
350      ReadAfterInitialized(buffer,
351                           buffer_length,
352                           base::Bind(&FileStreamReader::OnReadCompleted,
353                                      weak_ptr_factory_.GetWeakPtr(),
354                                      callback));
355      break;
356
357    case FAILED:
358      NOTREACHED();
359      break;
360  }
361
362  return net::ERR_IO_PENDING;
363}
364
365void FileStreamReader::OnReadCompleted(net::CompletionCallback callback,
366                                       int result) {
367  DCHECK_CURRENTLY_ON(BrowserThread::IO);
368  callback.Run(static_cast<int>(result));
369  TRACE_EVENT_ASYNC_END0(
370      "file_system_provider", "FileStreamReader::Read", this);
371}
372
373int64 FileStreamReader::GetLength(
374    const net::Int64CompletionCallback& callback) {
375  DCHECK_CURRENTLY_ON(BrowserThread::IO);
376
377  switch (state_) {
378    case NOT_INITIALIZED:
379      // Lazily initialize with the first call to GetLength().
380    Initialize(base::Bind(&FileStreamReader::GetLengthAfterInitialized,
381                          weak_ptr_factory_.GetWeakPtr(),
382                          callback),
383               callback);
384    break;
385
386    case INITIALIZING:
387      NOTREACHED();
388      break;
389
390    case INITIALIZED:
391      GetLengthAfterInitialized(callback);
392      break;
393
394    case FAILED:
395      NOTREACHED();
396      break;
397  }
398
399  return net::ERR_IO_PENDING;
400}
401
402void FileStreamReader::ReadAfterInitialized(
403    scoped_refptr<net::IOBuffer> buffer,
404    int buffer_length,
405    const net::CompletionCallback& callback) {
406  DCHECK_CURRENTLY_ON(BrowserThread::IO);
407  DCHECK_EQ(INITIALIZED, state_);
408
409  current_length_ = 0;
410  BrowserThread::PostTask(
411      BrowserThread::UI,
412      FROM_HERE,
413      base::Bind(&OperationRunner::ReadFileOnUIThread,
414                 runner_,
415                 buffer,
416                 current_offset_,
417                 buffer_length,
418                 base::Bind(&FileStreamReader::OnReadChunkReceived,
419                            weak_ptr_factory_.GetWeakPtr(),
420                            callback)));
421}
422
423void FileStreamReader::GetLengthAfterInitialized(
424    const net::Int64CompletionCallback& callback) {
425  DCHECK_CURRENTLY_ON(BrowserThread::IO);
426  DCHECK_EQ(INITIALIZED, state_);
427
428  BrowserThread::PostTask(
429      BrowserThread::UI,
430      FROM_HERE,
431      base::Bind(
432          &OperationRunner::GetMetadataOnUIThread,
433          runner_,
434          base::Bind(&FileStreamReader::OnGetMetadataForGetLengthReceived,
435                     weak_ptr_factory_.GetWeakPtr(),
436                     callback)));
437}
438
439void FileStreamReader::OnReadChunkReceived(
440    const net::CompletionCallback& callback,
441    int chunk_length,
442    bool has_more,
443    base::File::Error result) {
444  DCHECK_CURRENTLY_ON(BrowserThread::IO);
445  DCHECK_EQ(INITIALIZED, state_);
446
447  current_length_ += chunk_length;
448
449  // If this is the last chunk with a success, then finalize.
450  if (!has_more && result == base::File::FILE_OK) {
451    current_offset_ += current_length_;
452    callback.Run(current_length_);
453    return;
454  }
455
456  // In case of an error, abort.
457  if (result != base::File::FILE_OK) {
458    DCHECK(!has_more);
459    state_ = FAILED;
460    callback.Run(net::FileErrorToNetError(result));
461    return;
462  }
463
464  // More data is about to come, so do not call the callback yet.
465  DCHECK(has_more);
466}
467
468void FileStreamReader::OnGetMetadataForGetLengthReceived(
469    const net::Int64CompletionCallback& callback,
470    scoped_ptr<EntryMetadata> metadata,
471    base::File::Error result) {
472  DCHECK_CURRENTLY_ON(BrowserThread::IO);
473  DCHECK_EQ(INITIALIZED, state_);
474
475  // In case of an error, abort.
476  if (result != base::File::FILE_OK) {
477    state_ = FAILED;
478    callback.Run(net::FileErrorToNetError(result));
479    return;
480  }
481
482  // If the file modification time has changed, then abort. Note, that the file
483  // may be changed without affecting the modification time.
484  DCHECK(metadata.get());
485  if (!expected_modification_time_.is_null() &&
486      metadata->modification_time != expected_modification_time_) {
487    callback.Run(net::ERR_UPLOAD_FILE_CHANGED);
488    return;
489  }
490
491  DCHECK_EQ(base::File::FILE_OK, result);
492  callback.Run(metadata->size);
493}
494
495}  // namespace file_system_provider
496}  // namespace chromeos
497