Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / file_system_provider / fileapi / file_stream_reader.cc
index e5c1d30..b8f1332 100644 (file)
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.h"
 
+#include "base/debug/trace_event.h"
 #include "base/files/file.h"
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/chromeos/file_system_provider/fileapi/provider_async_file_util.h"
@@ -29,173 +30,287 @@ void Int64ToIntCompletionCallback(net::CompletionCallback callback,
   callback.Run(static_cast<int>(result));
 }
 
-// Opens a file for reading and calls the completion callback. Must be called
-// on UI thread.
-void OpenFileOnUIThread(
-    const fileapi::FileSystemURL& url,
-    const FileStreamReader::InitializeCompletedCallback& callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  // TODO(mtomasz): Check if the modification time of the file is as expected.
-  util::FileSystemURLParser parser(url);
-  if (!parser.Parse()) {
-    callback.Run(base::WeakPtr<ProvidedFileSystemInterface>(),
-                 base::FilePath(),
-                 0 /* file_handle */,
-                 base::File::FILE_ERROR_SECURITY);
-    return;
+}  // namespace
+
+class FileStreamReader::OperationRunner
+    : public base::RefCountedThreadSafe<FileStreamReader::OperationRunner> {
+ public:
+  OperationRunner() : file_handle_(-1) {}
+
+  // Opens a file for reading and calls the completion callback. Must be called
+  // on UI thread.
+  void OpenFileOnUIThread(
+      const storage::FileSystemURL& url,
+      const storage::AsyncFileUtil::StatusCallback& callback) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    util::FileSystemURLParser parser(url);
+    if (!parser.Parse()) {
+      BrowserThread::PostTask(
+          BrowserThread::IO,
+          FROM_HERE,
+          base::Bind(callback, base::File::FILE_ERROR_SECURITY));
+      return;
+    }
+
+    file_system_ = parser.file_system()->GetWeakPtr();
+    file_path_ = parser.file_path();
+    abort_callback_ = parser.file_system()->OpenFile(
+        file_path_,
+        ProvidedFileSystemInterface::OPEN_FILE_MODE_READ,
+        base::Bind(
+            &OperationRunner::OnOpenFileCompletedOnUIThread, this, callback));
   }
 
-  parser.file_system()->OpenFile(
-      parser.file_path(),
-      ProvidedFileSystemInterface::OPEN_FILE_MODE_READ,
-      false /* create */,
-      base::Bind(
-          callback, parser.file_system()->GetWeakPtr(), parser.file_path()));
-}
+  // Closes a file. Ignores result, since it is called from a constructor.
+  // Must be called on UI thread.
+  void CloseFileOnUIThread() {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+    if (file_system_.get() && file_handle_ != -1) {
+      // Closing a file must not be aborted, since we could end up on files
+      // which are never closed.
+      file_system_->CloseFile(file_handle_, base::Bind(&EmptyStatusCallback));
+    }
+  }
 
-// Forwards results of calling OpenFileOnUIThread back to the IO thread.
-void OnOpenFileCompletedOnUIThread(
-    const FileStreamReader::InitializeCompletedCallback& callback,
-    base::WeakPtr<ProvidedFileSystemInterface> file_system,
-    const base::FilePath& file_path,
-    int file_handle,
-    base::File::Error result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  BrowserThread::PostTask(
-      BrowserThread::IO,
-      FROM_HERE,
-      base::Bind(callback, file_system, file_path, file_handle, result));
-}
+  // Requests reading contents of a file. In case of either success or a failure
+  // |callback| is executed. It can be called many times, until |has_more| is
+  // set to false. This function guarantees that it will succeed only if the
+  // file has not been changed while reading. Must be called on UI thread.
+  void ReadFileOnUIThread(
+      scoped_refptr<net::IOBuffer> buffer,
+      int64 offset,
+      int length,
+      const ProvidedFileSystemInterface::ReadChunkReceivedCallback& callback) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    // If the file system got unmounted, then abort the reading operation.
+    if (!file_system_.get()) {
+      BrowserThread::PostTask(
+          BrowserThread::IO,
+          FROM_HERE,
+          base::Bind(
+              callback, 0, false /* has_more */, base::File::FILE_ERROR_ABORT));
+      return;
+    }
+
+    abort_callback_ = file_system_->ReadFile(
+        file_handle_,
+        buffer.get(),
+        offset,
+        length,
+        base::Bind(
+            &OperationRunner::OnReadFileCompletedOnUIThread, this, callback));
+  }
 
-// Closes a file. Ignores result, since it is called from a constructor.
-// Must be called on UI thread.
-void CloseFileOnUIThread(base::WeakPtr<ProvidedFileSystemInterface> file_system,
-                         int file_handle) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (file_system.get())
-    file_system->CloseFile(file_handle, base::Bind(&EmptyStatusCallback));
-}
+  // Requests metadata of a file. In case of either succes or a failure,
+  // |callback| is executed. Must be called on UI thread.
+  void GetMetadataOnUIThread(
+      const ProvidedFileSystemInterface::GetMetadataCallback& callback) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    // If the file system got unmounted, then abort the get length operation.
+    if (!file_system_.get()) {
+      BrowserThread::PostTask(
+          BrowserThread::IO,
+          FROM_HERE,
+          base::Bind(callback,
+                     base::Passed(make_scoped_ptr<EntryMetadata>(NULL)),
+                     base::File::FILE_ERROR_ABORT));
+      return;
+    }
+
+    abort_callback_ = file_system_->GetMetadata(
+        file_path_,
+        ProvidedFileSystemInterface::METADATA_FIELD_DEFAULT,
+        base::Bind(&OperationRunner::OnGetMetadataCompletedOnUIThread,
+                   this,
+                   callback));
+  }
 
-// Requests reading contents of a file. In case of either success or a failure
-// |callback| is executed. It can be called many times, until |has_more| is set
-// to false. This function guarantees that it will succeed only if the file has
-// not been changed while reading. Must be called on UI thread.
-void ReadFileOnUIThread(
-    base::WeakPtr<ProvidedFileSystemInterface> file_system,
-    int file_handle,
-    scoped_refptr<net::IOBuffer> buffer,
-    int64 offset,
-    int length,
-    const ProvidedFileSystemInterface::ReadChunkReceivedCallback& callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  // If the file system got unmounted, then abort the reading operation.
-  if (!file_system.get()) {
-    callback.Run(0, false /* has_more */, base::File::FILE_ERROR_ABORT);
-    return;
+  // Aborts the most recent operation (if exists), and calls the callback.
+  void AbortOnUIThread(const storage::AsyncFileUtil::StatusCallback& callback) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    if (abort_callback_.is_null()) {
+      // No operation to be cancelled. At most a callback call, which will be
+      // discarded.
+      BrowserThread::PostTask(BrowserThread::IO,
+                              FROM_HERE,
+                              base::Bind(callback, base::File::FILE_OK));
+      return;
+    }
+
+    const ProvidedFileSystemInterface::AbortCallback abort_callback =
+        abort_callback_;
+    abort_callback_ = ProvidedFileSystemInterface::AbortCallback();
+    abort_callback.Run(base::Bind(
+        &OperationRunner::OnAbortCompletedOnUIThread, this, callback));
   }
 
-  file_system->ReadFile(file_handle, buffer, offset, length, callback);
-}
+ private:
+  friend class base::RefCountedThreadSafe<OperationRunner>;
 
-// Forward the completion callback to IO thread.
-void OnReadChunkReceivedOnUIThread(
-    const ProvidedFileSystemInterface::ReadChunkReceivedCallback&
-        chunk_received_callback,
-    int chunk_length,
-    bool has_more,
-    base::File::Error result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  BrowserThread::PostTask(
-      BrowserThread::IO,
-      FROM_HERE,
-      base::Bind(chunk_received_callback, chunk_length, has_more, result));
-}
+  virtual ~OperationRunner() {}
 
-// Requests metadata of a file. In case of either succes or a failure,
-// |callback is executed. Must be called on UI thread.
-void GetMetadataOnUIThread(
-    base::WeakPtr<ProvidedFileSystemInterface> file_system,
-    const base::FilePath& file_path,
-    const fileapi::AsyncFileUtil::GetFileInfoCallback& callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  // If the file system got unmounted, then abort the get length operation.
-  if (!file_system.get()) {
-    callback.Run(base::File::FILE_ERROR_ABORT, base::File::Info());
-    return;
+  // Remembers a file handle for further operations and forwards the result to
+  // the IO thread.
+  void OnOpenFileCompletedOnUIThread(
+      const storage::AsyncFileUtil::StatusCallback& callback,
+      int file_handle,
+      base::File::Error result) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    file_handle_ = file_handle;
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE, base::Bind(callback, result));
   }
 
-  file_system->GetMetadata(file_path, callback);
-}
+  // Forwards a metadata to the IO thread.
+  void OnGetMetadataCompletedOnUIThread(
+      const ProvidedFileSystemInterface::GetMetadataCallback& callback,
+      scoped_ptr<EntryMetadata> metadata,
+      base::File::Error result) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+    BrowserThread::PostTask(
+        BrowserThread::IO,
+        FROM_HERE,
+        base::Bind(callback, base::Passed(&metadata), result));
+  }
 
-// Forward the completion callback to IO thread.
-void OnGetMetadataReceivedOnUIThread(
-    const fileapi::AsyncFileUtil::GetFileInfoCallback& callback,
-    base::File::Error result,
-    const base::File::Info& file_info) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE, base::Bind(callback, result, file_info));
-}
+  // Forwards a response of reading from a file to the IO thread.
+  void OnReadFileCompletedOnUIThread(
+      const ProvidedFileSystemInterface::ReadChunkReceivedCallback&
+          chunk_received_callback,
+      int chunk_length,
+      bool has_more,
+      base::File::Error result) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+    BrowserThread::PostTask(
+        BrowserThread::IO,
+        FROM_HERE,
+        base::Bind(chunk_received_callback, chunk_length, has_more, result));
+  }
 
-}  // namespace
+  // Forwards a response of aborting an operation to the IO thread.
+  void OnAbortCompletedOnUIThread(
+      const storage::AsyncFileUtil::StatusCallback& callback,
+      base::File::Error result) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE, base::Bind(callback, result));
+  }
+
+  ProvidedFileSystemInterface::AbortCallback abort_callback_;
+  base::WeakPtr<ProvidedFileSystemInterface> file_system_;
+  base::FilePath file_path_;
+  int file_handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(OperationRunner);
+};
 
-FileStreamReader::FileStreamReader(fileapi::FileSystemContext* context,
-                                   const fileapi::FileSystemURL& url,
+FileStreamReader::FileStreamReader(storage::FileSystemContext* context,
+                                   const storage::FileSystemURL& url,
                                    int64 initial_offset,
                                    const base::Time& expected_modification_time)
     : url_(url),
       current_offset_(initial_offset),
       current_length_(0),
       expected_modification_time_(expected_modification_time),
-      file_handle_(0),
+      runner_(new OperationRunner),
+      state_(NOT_INITIALIZED),
       weak_ptr_factory_(this) {
 }
 
 FileStreamReader::~FileStreamReader() {
+  // FileStreamReader doesn't have a Cancel() method like in FileStreamWriter.
+  // Therefore, aborting is done from the destructor.
+  BrowserThread::PostTask(BrowserThread::UI,
+                          FROM_HERE,
+                          base::Bind(&OperationRunner::AbortOnUIThread,
+                                     runner_,
+                                     base::Bind(&EmptyStatusCallback)));
+
   BrowserThread::PostTask(
       BrowserThread::UI,
       FROM_HERE,
-      base::Bind(&CloseFileOnUIThread, file_system_, file_handle_));
+      base::Bind(&OperationRunner::CloseFileOnUIThread, runner_));
 }
 
 void FileStreamReader::Initialize(
     const base::Closure& pending_closure,
     const net::Int64CompletionCallback& error_callback) {
+  DCHECK_EQ(NOT_INITIALIZED, state_);
+  state_ = INITIALIZING;
+
   BrowserThread::PostTask(
       BrowserThread::UI,
       FROM_HERE,
-      base::Bind(&OpenFileOnUIThread,
+      base::Bind(&OperationRunner::OpenFileOnUIThread,
+                 runner_,
                  url_,
-                 base::Bind(&OnOpenFileCompletedOnUIThread,
-                            base::Bind(&FileStreamReader::OnInitializeCompleted,
-                                       weak_ptr_factory_.GetWeakPtr(),
-                                       pending_closure,
-                                       error_callback))));
+                 base::Bind(&FileStreamReader::OnOpenFileCompleted,
+                            weak_ptr_factory_.GetWeakPtr(),
+                            pending_closure,
+                            error_callback)));
 }
 
-void FileStreamReader::OnInitializeCompleted(
+void FileStreamReader::OnOpenFileCompleted(
     const base::Closure& pending_closure,
     const net::Int64CompletionCallback& error_callback,
-    base::WeakPtr<ProvidedFileSystemInterface> file_system,
-    const base::FilePath& file_path,
-    int file_handle,
     base::File::Error result) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_EQ(INITIALIZING, state_);
 
   // In case of an error, return immediately using the |error_callback| of the
   // Read() or GetLength() pending request.
   if (result != base::File::FILE_OK) {
+    state_ = FAILED;
     error_callback.Run(net::FileErrorToNetError(result));
     return;
   }
 
-  file_system_ = file_system;
-  file_path_ = file_path;
-  file_handle_ = file_handle;
-  DCHECK_LT(0, file_handle);
+  DCHECK_EQ(base::File::FILE_OK, result);
+
+  // Verify the last modification time.
+  BrowserThread::PostTask(
+      BrowserThread::UI,
+      FROM_HERE,
+      base::Bind(&OperationRunner::GetMetadataOnUIThread,
+                 runner_,
+                 base::Bind(&FileStreamReader::OnInitializeCompleted,
+                            weak_ptr_factory_.GetWeakPtr(),
+                            pending_closure,
+                            error_callback)));
+}
+
+void FileStreamReader::OnInitializeCompleted(
+    const base::Closure& pending_closure,
+    const net::Int64CompletionCallback& error_callback,
+    scoped_ptr<EntryMetadata> metadata,
+    base::File::Error result) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_EQ(INITIALIZING, state_);
+
+  // In case of an error, abort.
+  if (result != base::File::FILE_OK) {
+    state_ = FAILED;
+    error_callback.Run(net::FileErrorToNetError(result));
+    return;
+  }
+
+  // If the file modification time has changed, then abort. Note, that the file
+  // may be changed without affecting the modification time.
+  DCHECK(metadata.get());
+  if (!expected_modification_time_.is_null() &&
+      metadata->modification_time != expected_modification_time_) {
+    state_ = FAILED;
+    error_callback.Run(net::ERR_UPLOAD_FILE_CHANGED);
+    return;
+  }
+
+  DCHECK_EQ(base::File::FILE_OK, result);
+  state_ = INITIALIZED;
 
   // Run the task waiting for the initialization to be completed.
   pending_closure.Run();
@@ -204,33 +319,83 @@ void FileStreamReader::OnInitializeCompleted(
 int FileStreamReader::Read(net::IOBuffer* buffer,
                            int buffer_length,
                            const net::CompletionCallback& callback) {
-  // Lazily initialize with the first call to Read().
-  if (!file_handle_) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  TRACE_EVENT_ASYNC_BEGIN1("file_system_provider",
+                           "FileStreamReader::Read",
+                           this,
+                           "buffer_length",
+                           buffer_length);
+
+  switch (state_) {
+    case NOT_INITIALIZED:
+      // Lazily initialize with the first call to Read().
     Initialize(base::Bind(&FileStreamReader::ReadAfterInitialized,
                           weak_ptr_factory_.GetWeakPtr(),
                           make_scoped_refptr(buffer),
                           buffer_length,
-                          callback),
-               base::Bind(&Int64ToIntCompletionCallback, callback));
-    return net::ERR_IO_PENDING;
+                          base::Bind(&FileStreamReader::OnReadCompleted,
+                                     weak_ptr_factory_.GetWeakPtr(),
+                                     callback)),
+               base::Bind(&Int64ToIntCompletionCallback,
+                          base::Bind(&FileStreamReader::OnReadCompleted,
+                                     weak_ptr_factory_.GetWeakPtr(),
+                                     callback)));
+    break;
+
+    case INITIALIZING:
+      NOTREACHED();
+      break;
+
+    case INITIALIZED:
+      ReadAfterInitialized(buffer,
+                           buffer_length,
+                           base::Bind(&FileStreamReader::OnReadCompleted,
+                                      weak_ptr_factory_.GetWeakPtr(),
+                                      callback));
+      break;
+
+    case FAILED:
+      NOTREACHED();
+      break;
   }
 
-  ReadAfterInitialized(buffer, buffer_length, callback);
   return net::ERR_IO_PENDING;
 }
 
+void FileStreamReader::OnReadCompleted(net::CompletionCallback callback,
+                                       int result) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  callback.Run(static_cast<int>(result));
+  TRACE_EVENT_ASYNC_END0(
+      "file_system_provider", "FileStreamReader::Read", this);
+}
+
 int64 FileStreamReader::GetLength(
     const net::Int64CompletionCallback& callback) {
-  // Lazily initialize with the first call to GetLength().
-  if (!file_handle_) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  switch (state_) {
+    case NOT_INITIALIZED:
+      // Lazily initialize with the first call to GetLength().
     Initialize(base::Bind(&FileStreamReader::GetLengthAfterInitialized,
                           weak_ptr_factory_.GetWeakPtr(),
                           callback),
                callback);
-    return net::ERR_IO_PENDING;
+    break;
+
+    case INITIALIZING:
+      NOTREACHED();
+      break;
+
+    case INITIALIZED:
+      GetLengthAfterInitialized(callback);
+      break;
+
+    case FAILED:
+      NOTREACHED();
+      break;
   }
 
-  GetLengthAfterInitialized(callback);
   return net::ERR_IO_PENDING;
 }
 
@@ -239,51 +404,36 @@ void FileStreamReader::ReadAfterInitialized(
     int buffer_length,
     const net::CompletionCallback& callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  // If the file system got unmounted, then abort the reading operation.
-  if (!file_handle_) {
-    callback.Run(net::ERR_ABORTED);
-    return;
-  }
+  DCHECK_EQ(INITIALIZED, state_);
 
   current_length_ = 0;
   BrowserThread::PostTask(
       BrowserThread::UI,
       FROM_HERE,
-      base::Bind(&ReadFileOnUIThread,
-                 file_system_,
-                 file_handle_,
+      base::Bind(&OperationRunner::ReadFileOnUIThread,
+                 runner_,
                  buffer,
                  current_offset_,
                  buffer_length,
-                 base::Bind(&OnReadChunkReceivedOnUIThread,
-                            base::Bind(&FileStreamReader::OnReadChunkReceived,
-                                       weak_ptr_factory_.GetWeakPtr(),
-                                       callback))));
+                 base::Bind(&FileStreamReader::OnReadChunkReceived,
+                            weak_ptr_factory_.GetWeakPtr(),
+                            callback)));
 }
 
 void FileStreamReader::GetLengthAfterInitialized(
     const net::Int64CompletionCallback& callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  // If the file system got unmounted, then abort the length fetching operation.
-  if (!file_handle_) {
-    callback.Run(net::ERR_ABORTED);
-    return;
-  }
+  DCHECK_EQ(INITIALIZED, state_);
 
   BrowserThread::PostTask(
       BrowserThread::UI,
       FROM_HERE,
       base::Bind(
-          &GetMetadataOnUIThread,
-          file_system_,
-          file_path_,
-          base::Bind(
-              &OnGetMetadataReceivedOnUIThread,
-              base::Bind(&FileStreamReader::OnGetMetadataForGetLengthReceived,
-                         weak_ptr_factory_.GetWeakPtr(),
-                         callback))));
+          &OperationRunner::GetMetadataOnUIThread,
+          runner_,
+          base::Bind(&FileStreamReader::OnGetMetadataForGetLengthReceived,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     callback)));
 }
 
 void FileStreamReader::OnReadChunkReceived(
@@ -292,6 +442,8 @@ void FileStreamReader::OnReadChunkReceived(
     bool has_more,
     base::File::Error result) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_EQ(INITIALIZED, state_);
+
   current_length_ += chunk_length;
 
   // If this is the last chunk with a success, then finalize.
@@ -304,6 +456,7 @@ void FileStreamReader::OnReadChunkReceived(
   // In case of an error, abort.
   if (result != base::File::FILE_OK) {
     DCHECK(!has_more);
+    state_ = FAILED;
     callback.Run(net::FileErrorToNetError(result));
     return;
   }
@@ -314,18 +467,29 @@ void FileStreamReader::OnReadChunkReceived(
 
 void FileStreamReader::OnGetMetadataForGetLengthReceived(
     const net::Int64CompletionCallback& callback,
-    base::File::Error result,
-    const base::File::Info& file_info) {
+    scoped_ptr<EntryMetadata> metadata,
+    base::File::Error result) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_EQ(INITIALIZED, state_);
 
   // In case of an error, abort.
   if (result != base::File::FILE_OK) {
+    state_ = FAILED;
     callback.Run(net::FileErrorToNetError(result));
     return;
   }
 
-  DCHECK_EQ(result, base::File::FILE_OK);
-  callback.Run(file_info.size);
+  // If the file modification time has changed, then abort. Note, that the file
+  // may be changed without affecting the modification time.
+  DCHECK(metadata.get());
+  if (!expected_modification_time_.is_null() &&
+      metadata->modification_time != expected_modification_time_) {
+    callback.Run(net::ERR_UPLOAD_FILE_CHANGED);
+    return;
+  }
+
+  DCHECK_EQ(base::File::FILE_OK, result);
+  callback.Run(metadata->size);
 }
 
 }  // namespace file_system_provider