diff options
author | mtomasz@chromium.org <mtomasz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-22 01:26:31 +0000 |
---|---|---|
committer | mtomasz@chromium.org <mtomasz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-22 01:26:31 +0000 |
commit | 839b0795344532853f699161c44b69a454397366 (patch) | |
tree | 20908d8509127fd3ee35f93baf4f290711cea794 | |
parent | 0c5703d1a2552eb3d15a7c589f34e3acb6bfc881 (diff) | |
download | chromium_src-839b0795344532853f699161c44b69a454397366.zip chromium_src-839b0795344532853f699161c44b69a454397366.tar.gz chromium_src-839b0795344532853f699161c44b69a454397366.tar.bz2 |
[fsp] Add FileStreamReader for the reading operation.
This patch adds a FileStreamReader implementation for provided file systeme.
Note, that this is an initial version, which doesn't validate modification time.
That will be added in a separate patch.
TEST=browser_tests: *FileSystemProvider*ReadFile*,
unit_tests: *FileSystemProvider*FileStreamReader*
BUG=248427
R=benwells@chromium.org, hirono@chromium.org, kinaba@chromium.org
Review URL: https://codereview.chromium.org/288113004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@272041 0039d316-1c4b-4281-b951-d872f2087c98
21 files changed, 1185 insertions, 64 deletions
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc index db78129..1cf3ea8 100644 --- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc +++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_apitest.cc @@ -41,4 +41,10 @@ IN_PROC_BROWSER_TEST_F(FileSystemProviderApiTest, ReadDirectory) { << message_; } +IN_PROC_BROWSER_TEST_F(FileSystemProviderApiTest, ReadFile) { + ASSERT_TRUE(RunPlatformAppTestWithFlags("file_system_provider/read_file", + kFlagLoadAsComponent)) + << message_; +} + } // namespace extensions diff --git a/chrome/browser/chromeos/file_system_provider/fake_provided_file_system.cc b/chrome/browser/chromeos/file_system_provider/fake_provided_file_system.cc index f8fd16e..52e1c68 100644 --- a/chrome/browser/chromeos/file_system_provider/fake_provided_file_system.cc +++ b/chrome/browser/chromeos/file_system_provider/fake_provided_file_system.cc @@ -31,9 +31,17 @@ void AddDirectoryEntry(fileapi::AsyncFileUtil::EntryList* entry_list, } // namespace +const char kFakeFileName[] = "hello.txt"; +const char kFakeFilePath[] = "/hello.txt"; +const char kFakeFileText[] = + "This is a testing file. Lorem ipsum dolor sit amet est."; +const size_t kFakeFileSize = sizeof(kFakeFileText) - 1u; + FakeProvidedFileSystem::FakeProvidedFileSystem( const ProvidedFileSystemInfo& file_system_info) - : file_system_info_(file_system_info), last_file_handle_(0) { + : file_system_info_(file_system_info), + last_file_handle_(0), + weak_ptr_factory_(this) { } FakeProvidedFileSystem::~FakeProvidedFileSystem() {} @@ -47,27 +55,42 @@ void FakeProvidedFileSystem::RequestUnmount( void FakeProvidedFileSystem::GetMetadata( const base::FilePath& entry_path, const fileapi::AsyncFileUtil::GetFileInfoCallback& callback) { - // Return fake metadata for the root directory only. - if (entry_path.AsUTF8Unsafe() != "/") { + if (entry_path.AsUTF8Unsafe() == "/") { + base::File::Info file_info; + file_info.size = 0; + file_info.is_directory = true; + file_info.is_symbolic_link = false; + base::Time last_modified_time; + const bool result = base::Time::FromString("Thu Apr 24 00:46:52 UTC 2014", + &last_modified_time); + DCHECK(result); + file_info.last_modified = last_modified_time; + base::MessageLoopProxy::current()->PostTask( - FROM_HERE, - base::Bind( - callback, base::File::FILE_ERROR_NOT_FOUND, base::File::Info())); + FROM_HERE, base::Bind(callback, base::File::FILE_OK, file_info)); return; } - base::File::Info file_info; - file_info.size = 0; - file_info.is_directory = true; - file_info.is_symbolic_link = false; - base::Time last_modified_time; - const bool result = base::Time::FromString("Thu Apr 24 00:46:52 UTC 2014", - &last_modified_time); - DCHECK(result); - file_info.last_modified = last_modified_time; + if (entry_path.AsUTF8Unsafe() == kFakeFilePath) { + base::File::Info file_info; + file_info.size = kFakeFileSize; + file_info.is_directory = false; + file_info.is_symbolic_link = false; + base::Time last_modified_time; + const bool result = base::Time::FromString("Fri Apr 25 01:47:53 UTC 2014", + &last_modified_time); + DCHECK(result); + file_info.last_modified = last_modified_time; + + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(callback, base::File::FILE_OK, file_info)); + return; + } base::MessageLoopProxy::current()->PostTask( - FROM_HERE, base::Bind(callback, base::File::FILE_OK, file_info)); + FROM_HERE, + base::Bind( + callback, base::File::FILE_ERROR_NOT_FOUND, base::File::Info())); } void FakeProvidedFileSystem::ReadDirectory( @@ -87,9 +110,9 @@ void FakeProvidedFileSystem::ReadDirectory( { fileapi::AsyncFileUtil::EntryList entry_list; AddDirectoryEntry(&entry_list, - "hello.txt", + kFakeFileName, fileapi::DirectoryEntry::FILE, - 1024 /* size */, + kFakeFileSize, "Thu Apr 24 00:46:52 UTC 2014"); AddDirectoryEntry(&entry_list, @@ -130,14 +153,14 @@ void FakeProvidedFileSystem::OpenFile(const base::FilePath& file_path, } const int file_handle = ++last_file_handle_; - opened_files_.insert(file_handle); + opened_files_[file_handle] = file_path; callback.Run(file_handle, base::File::FILE_OK); } void FakeProvidedFileSystem::CloseFile( int file_handle, const fileapi::AsyncFileUtil::StatusCallback& callback) { - const std::set<int>::iterator opened_file_it = + const OpenedFilesMap::iterator opened_file_it = opened_files_.find(file_handle); if (opened_file_it == opened_files_.end()) { base::MessageLoopProxy::current()->PostTask( @@ -156,13 +179,41 @@ void FakeProvidedFileSystem::ReadFile( int64 offset, int length, const ProvidedFileSystemInterface::ReadChunkReceivedCallback& callback) { - // TODO(mtomasz): Implement together with the FileStreamReader. - base::MessageLoopProxy::current()->PostTask( - FROM_HERE, - base::Bind(callback, - 0 /* chunk_length */, - false /* has_next */, - base::File::FILE_ERROR_SECURITY)); + const OpenedFilesMap::iterator opened_file_it = + opened_files_.find(file_handle); + if (opened_file_it == opened_files_.end() || + opened_file_it->second.AsUTF8Unsafe() != kFakeFilePath) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(callback, + 0 /* chunk_length */, + false /* has_next */, + base::File::FILE_ERROR_SECURITY)); + return; + } + + // Send the response byte by byte. + size_t current_offset = static_cast<size_t>(offset); + size_t current_length = static_cast<size_t>(length); + + // Reading behind EOF, is fine, it will just read 0 bytes. + if (current_offset > kFakeFileSize || !current_length) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(callback, + 0 /* chunk_length */, + false /* has_next */, + base::File::FILE_OK)); + } + + while (current_offset < kFakeFileSize && current_length) { + buffer->data()[current_offset - offset] = kFakeFileText[current_offset]; + const bool has_next = + (current_offset + 1 < kFakeFileSize) && (current_length - 1); + callback.Run(1, has_next, base::File::FILE_OK); + current_offset++; + current_length--; + } } const ProvidedFileSystemInfo& FakeProvidedFileSystem::GetFileSystemInfo() @@ -181,5 +232,10 @@ ProvidedFileSystemInterface* FakeProvidedFileSystem::Create( return new FakeProvidedFileSystem(file_system_info); } +base::WeakPtr<ProvidedFileSystemInterface> +FakeProvidedFileSystem::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); +} + } // namespace file_system_provider } // namespace chromeos diff --git a/chrome/browser/chromeos/file_system_provider/fake_provided_file_system.h b/chrome/browser/chromeos/file_system_provider/fake_provided_file_system.h index 3401dc0..18feb7b 100644 --- a/chrome/browser/chromeos/file_system_provider/fake_provided_file_system.h +++ b/chrome/browser/chromeos/file_system_provider/fake_provided_file_system.h @@ -5,8 +5,9 @@ #ifndef CHROME_BROWSER_CHROMEOS_FILE_SYSTEM_PROVIDER_FAKE_PROVIDED_FILE_SYSTEM_H_ #define CHROME_BROWSER_CHROMEOS_FILE_SYSTEM_PROVIDER_FAKE_PROVIDED_FILE_SYSTEM_H_ -#include <set> +#include <map> +#include "base/memory/weak_ptr.h" #include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h" #include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h" @@ -23,6 +24,11 @@ namespace file_system_provider { class RequestManager; +extern const char kFakeFileName[]; +extern const char kFakeFilePath[]; +extern const char kFakeFileText[]; +extern const size_t kFakeFileSize; + // Fake provided file system implementation. Does not communicate with target // extensions. Used for unit tests. class FakeProvidedFileSystem : public ProvidedFileSystemInterface { @@ -54,6 +60,7 @@ class FakeProvidedFileSystem : public ProvidedFileSystemInterface { const ReadChunkReceivedCallback& callback) OVERRIDE; virtual const ProvidedFileSystemInfo& GetFileSystemInfo() const OVERRIDE; virtual RequestManager* GetRequestManager() OVERRIDE; + virtual base::WeakPtr<ProvidedFileSystemInterface> GetWeakPtr() OVERRIDE; // Factory callback, to be used in Service::SetFileSystemFactory(). The // |event_router| argument can be NULL. @@ -62,10 +69,13 @@ class FakeProvidedFileSystem : public ProvidedFileSystemInterface { const ProvidedFileSystemInfo& file_system_info); private: + typedef std::map<int, base::FilePath> OpenedFilesMap; + ProvidedFileSystemInfo file_system_info_; - std::set<int> opened_files_; + OpenedFilesMap opened_files_; int last_file_handle_; + base::WeakPtrFactory<ProvidedFileSystemInterface> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(FakeProvidedFileSystem); }; diff --git a/chrome/browser/chromeos/file_system_provider/fileapi/backend_delegate.cc b/chrome/browser/chromeos/file_system_provider/fileapi/backend_delegate.cc index 2b41fe3..c63e8a2 100644 --- a/chrome/browser/chromeos/file_system_provider/fileapi/backend_delegate.cc +++ b/chrome/browser/chromeos/file_system_provider/fileapi/backend_delegate.cc @@ -5,6 +5,7 @@ #include "chrome/browser/chromeos/file_system_provider/fileapi/backend_delegate.h" #include "base/memory/scoped_ptr.h" +#include "chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.h" #include "chrome/browser/chromeos/file_system_provider/fileapi/provider_async_file_util.h" #include "content/public/browser/browser_thread.h" #include "webkit/browser/blob/file_stream_reader.h" @@ -36,8 +37,9 @@ BackendDelegate::CreateFileStreamReader( fileapi::FileSystemContext* context) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_EQ(fileapi::kFileSystemTypeProvided, url.type()); - NOTIMPLEMENTED(); - return scoped_ptr<webkit_blob::FileStreamReader>(); + + return scoped_ptr<webkit_blob::FileStreamReader>( + new FileStreamReader(context, url, offset, expected_modification_time)); } scoped_ptr<fileapi::FileStreamWriter> BackendDelegate::CreateFileStreamWriter( diff --git a/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.cc b/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.cc new file mode 100644 index 0000000..c7a5aec --- /dev/null +++ b/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.cc @@ -0,0 +1,330 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.h" + +#include "base/files/file.h" +#include "chrome/browser/chromeos/file_system_provider/fileapi/provider_async_file_util.h" +#include "chrome/browser/chromeos/file_system_provider/mount_path_util.h" +#include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" + +using content::BrowserThread; + +namespace chromeos { +namespace file_system_provider { +namespace { + +// Dicards the callback from CloseFile(). +void EmptyStatusCallback(base::File::Error /* result */) { +} + +// Converts net::CompletionCallback to net::Int64CompletionCallback. +void Int64ToIntCompletionCallback(net::CompletionCallback callback, + int64 result) { + 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; + } + + parser.file_system()->OpenFile( + parser.file_path(), + ProvidedFileSystemInterface::OPEN_FILE_MODE_READ, + false /* create */, + base::Bind( + callback, parser.file_system()->GetWeakPtr(), parser.file_path())); +} + +// 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)); +} + +// 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 reading contents of a file. In case of either success or a failure +// |callback| is executed. It can be called many times, until |has_next| 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, + 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_next */, base::File::FILE_ERROR_ABORT); + return; + } + + file_system->ReadFile(file_handle, buffer, offset, length, callback); +} + +// Forward the completion callback to IO thread. +void OnReadChunkReceivedOnUIThread( + const ProvidedFileSystemInterface::ReadChunkReceivedCallback& + chunk_received_callback, + int chunk_length, + bool has_next, + base::File::Error result) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(chunk_received_callback, chunk_length, has_next, result)); +} + +// 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; + } + + file_system->GetMetadata(file_path, callback); +} + +// 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)); +} + +} // namespace + +FileStreamReader::FileStreamReader(fileapi::FileSystemContext* context, + const fileapi::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), + weak_ptr_factory_(this) { +} + +FileStreamReader::~FileStreamReader() { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&CloseFileOnUIThread, file_system_, file_handle_)); +} + +void FileStreamReader::Initialize( + const base::Closure& pending_closure, + const net::Int64CompletionCallback& error_callback) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&OpenFileOnUIThread, + url_, + base::Bind(&OnOpenFileCompletedOnUIThread, + 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, + base::WeakPtr<ProvidedFileSystemInterface> file_system, + const base::FilePath& file_path, + int file_handle, + base::File::Error result) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + // In case of an error, return immediately using the |error_callback| of the + // Read() or GetLength() pending request. + if (result != base::File::FILE_OK) { + error_callback.Run(net::FileErrorToNetError(result)); + return; + } + + file_system_ = file_system; + file_path_ = file_path; + file_handle_ = file_handle; + DCHECK_LT(0, file_handle); + + // Run the task waiting for the initialization to be completed. + pending_closure.Run(); +} + +int FileStreamReader::Read(net::IOBuffer* buffer, + int buffer_length, + const net::CompletionCallback& callback) { + // Lazily initialize with the first call to Read(). + if (!file_handle_) { + Initialize(base::Bind(&FileStreamReader::ReadAfterInitialized, + weak_ptr_factory_.GetWeakPtr(), + buffer, + buffer_length, + callback), + base::Bind(&Int64ToIntCompletionCallback, callback)); + return net::ERR_IO_PENDING; + } + + ReadAfterInitialized(buffer, buffer_length, callback); + return net::ERR_IO_PENDING; +} + +int64 FileStreamReader::GetLength( + const net::Int64CompletionCallback& callback) { + // Lazily initialize with the first call to GetLength(). + if (!file_handle_) { + Initialize(base::Bind(&FileStreamReader::GetLengthAfterInitialized, + weak_ptr_factory_.GetWeakPtr(), + callback), + callback); + return net::ERR_IO_PENDING; + } + + GetLengthAfterInitialized(callback); + return net::ERR_IO_PENDING; +} + +void FileStreamReader::ReadAfterInitialized( + net::IOBuffer* buffer, + 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; + } + + current_length_ = 0; + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&ReadFileOnUIThread, + file_system_, + file_handle_, + buffer, + current_offset_, + buffer_length, + base::Bind(&OnReadChunkReceivedOnUIThread, + 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; + } + + 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)))); +} + +void FileStreamReader::OnReadChunkReceived( + const net::CompletionCallback& callback, + int chunk_length, + bool has_next, + base::File::Error result) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + current_length_ += chunk_length; + + // If this is the last chunk with a success, then finalize. + if (!has_next && result == base::File::FILE_OK) { + current_offset_ += current_length_; + callback.Run(current_length_); + return; + } + + // In case of an error, abort. + if (result != base::File::FILE_OK) { + DCHECK(!has_next); + callback.Run(net::FileErrorToNetError(result)); + return; + } + + // More data is about to come, so do not call the callback yet. + DCHECK(has_next); +} + +void FileStreamReader::OnGetMetadataForGetLengthReceived( + const net::Int64CompletionCallback& callback, + base::File::Error result, + const base::File::Info& file_info) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + // In case of an error, abort. + if (result != base::File::FILE_OK) { + callback.Run(net::FileErrorToNetError(result)); + return; + } + + DCHECK_EQ(result, base::File::FILE_OK); + callback.Run(file_info.size); +} + +} // namespace file_system_provider +} // namespace chromeos diff --git a/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.h b/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.h new file mode 100644 index 0000000..7c0b91e --- /dev/null +++ b/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.h @@ -0,0 +1,103 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_FILE_SYSTEM_PROVIDER_FILEAPI_FILE_STREAM_READER_H_ +#define CHROME_BROWSER_CHROMEOS_FILE_SYSTEM_PROVIDER_FILEAPI_FILE_STREAM_READER_H_ + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "webkit/browser/blob/file_stream_reader.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace fileapi { +class AsyncFileUtil; +} // namespace fileapi + +namespace chromeos { +namespace file_system_provider { + +class ProvidedFileSystemInterface; + +// Implements a streamed file reader. It is lazily initialized by the first call +// to Read(). +class FileStreamReader : public webkit_blob::FileStreamReader { + public: + typedef base::Callback< + void(base::WeakPtr<ProvidedFileSystemInterface> file_system, + const base::FilePath& file_path, + int file_handle, + base::File::Error result)> InitializeCompletedCallback; + + FileStreamReader(fileapi::FileSystemContext* context, + const fileapi::FileSystemURL& url, + int64 initial_offset, + const base::Time& expected_modification_time); + + virtual ~FileStreamReader(); + + // webkit_blob::FileStreamReader overrides. + virtual int Read(net::IOBuffer* buf, + int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int64 GetLength( + const net::Int64CompletionCallback& callback) OVERRIDE; + + private: + // Initializes the reader by opening the file. When completed with success, + // runs the |pending_closure|. Otherwise, calls the |error_callback|. + void Initialize(const base::Closure& pending_closure, + const net::Int64CompletionCallback& error_callback); + + // Called when initializing is completed with either a success or an error. + void OnInitializeCompleted( + 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); + + // Called when a file system provider returns chunk of read data. Note, that + // this may be called multiple times per single Read() call, as long as + // |has_next| is set to true. |result| is set to success only if reading is + // successful, and the file has not changed while reading. + void OnReadChunkReceived(const net::CompletionCallback& callback, + int chunk_length, + bool has_next, + base::File::Error result); + + // Called when fetching length of the file is completed with either a success + // or an error. + void OnGetMetadataForGetLengthReceived( + const net::Int64CompletionCallback& callback, + base::File::Error result, + const base::File::Info& file_info); + + // Same as Read(), but called after initializing is completed. + void ReadAfterInitialized(net::IOBuffer* buffer, + int buffer_length, + const net::CompletionCallback& callback); + + // Same as GetLength(), but called after initializing is completed. + void GetLengthAfterInitialized(const net::Int64CompletionCallback& callback); + + fileapi::FileSystemURL url_; + int64 current_offset_; + int64 current_length_; + base::Time expected_modification_time_; + + // Set during initialization (in case of a success). + base::WeakPtr<ProvidedFileSystemInterface> file_system_; + base::FilePath file_path_; + int file_handle_; + + base::WeakPtrFactory<FileStreamReader> weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(FileStreamReader); +}; + +} // namespace file_system_provider +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_FILE_SYSTEM_PROVIDER_FILEAPI_FILE_STREAM_READER_H_ diff --git a/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader_unittest.cc b/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader_unittest.cc new file mode 100644 index 0000000..dfd7e67 --- /dev/null +++ b/chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader_unittest.cc @@ -0,0 +1,294 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <vector> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "base/run_loop.h" +#include "chrome/browser/chromeos/file_system_provider/fake_provided_file_system.h" +#include "chrome/browser/chromeos/file_system_provider/fileapi/file_stream_reader.h" +#include "chrome/browser/chromeos/file_system_provider/service.h" +#include "chrome/browser/chromeos/file_system_provider/service_factory.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "chrome/test/base/testing_profile_manager.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "content/public/test/test_file_system_context.h" +#include "extensions/browser/extension_registry.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_util.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace chromeos { +namespace file_system_provider { +namespace { + +const char kExtensionId[] = "mbflcebpggnecokmikipoihdbecnjfoj"; + +// Logs callbacks invocations on the file stream reader. +class EventLogger { + public: + EventLogger() : weak_ptr_factory_(this) {} + virtual ~EventLogger() {} + + void OnRead(int result) { results_.push_back(result); } + void OnGetLength(int64 result) { results_.push_back(result); } + + base::WeakPtr<EventLogger> GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + const std::vector<int64>& results() const { return results_; } + + private: + std::vector<int64> results_; + base::WeakPtrFactory<EventLogger> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(EventLogger); +}; + +// Creates a cracked FileSystemURL for tests. +fileapi::FileSystemURL CreateFileSystemURL(const std::string& mount_point_name, + const base::FilePath& file_path) { + const std::string origin = std::string("chrome-extension://") + kExtensionId; + const fileapi::ExternalMountPoints* const mount_points = + fileapi::ExternalMountPoints::GetSystemInstance(); + return mount_points->CreateCrackedFileSystemURL( + GURL(origin), + fileapi::kFileSystemTypeExternal, + base::FilePath::FromUTF8Unsafe(mount_point_name).Append(file_path)); +} + +// Creates a Service instance. Used to be able to destroy the service in +// TearDown(). +KeyedService* CreateService(content::BrowserContext* context) { + return new Service(Profile::FromBrowserContext(context), + extensions::ExtensionRegistry::Get(context)); +} + +} // namespace + +class FileSystemProviderFileStreamReader : public testing::Test { + protected: + FileSystemProviderFileStreamReader() {} + virtual ~FileSystemProviderFileStreamReader() {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + profile_manager_.reset( + new TestingProfileManager(TestingBrowserProcess::GetGlobal())); + ASSERT_TRUE(profile_manager_->SetUp()); + profile_ = profile_manager_->CreateTestingProfile("testing-profile"); + + ServiceFactory::GetInstance()->SetTestingFactory(profile_, &CreateService); + Service* service = Service::Get(profile_); // Owned by its factory. + service->SetFileSystemFactoryForTests( + base::Bind(&FakeProvidedFileSystem::Create)); + + const int file_system_id = + service->MountFileSystem(kExtensionId, "testing-file-system"); + ASSERT_LT(0, file_system_id); + const ProvidedFileSystemInfo& file_system_info = + service->GetProvidedFileSystem(kExtensionId, file_system_id) + ->GetFileSystemInfo(); + const std::string mount_point_name = + file_system_info.mount_path().BaseName().AsUTF8Unsafe(); + + file_url_ = CreateFileSystemURL( + mount_point_name, base::FilePath::FromUTF8Unsafe(kFakeFilePath + 1)); + ASSERT_TRUE(file_url_.is_valid()); + wrong_file_url_ = CreateFileSystemURL( + mount_point_name, base::FilePath::FromUTF8Unsafe("im-not-here.txt")); + ASSERT_TRUE(wrong_file_url_.is_valid()); + } + + virtual void TearDown() OVERRIDE { + // Setting the testing factory to NULL will destroy the created service + // associated with the testing profile. + ServiceFactory::GetInstance()->SetTestingFactory(profile_, NULL); + } + + content::TestBrowserThreadBundle thread_bundle_; + base::ScopedTempDir data_dir_; + scoped_ptr<TestingProfileManager> profile_manager_; + TestingProfile* profile_; // Owned by TestingProfileManager. + fileapi::FileSystemURL file_url_; + fileapi::FileSystemURL wrong_file_url_; +}; + +TEST_F(FileSystemProviderFileStreamReader, Read_AllAtOnce) { + EventLogger logger; + + const int64 initial_offset = 0; + FileStreamReader reader(NULL, + file_url_, + initial_offset, + base::Time::Now()); // Not used yet. + scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(kFakeFileSize)); + + const int result = + reader.Read(io_buffer.get(), + kFakeFileSize, + base::Bind(&EventLogger::OnRead, logger.GetWeakPtr())); + EXPECT_EQ(net::ERR_IO_PENDING, result); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(1u, logger.results().size()); + EXPECT_LT(0, logger.results()[0]); + EXPECT_EQ(kFakeFileSize, static_cast<size_t>(logger.results()[0])); + + std::string buffer_as_string(io_buffer->data(), kFakeFileSize); + EXPECT_EQ(kFakeFileText, buffer_as_string); +} + +TEST_F(FileSystemProviderFileStreamReader, Read_WrongFile) { + EventLogger logger; + + const int64 initial_offset = 0; + FileStreamReader reader(NULL, + wrong_file_url_, + initial_offset, + base::Time::Now()); // Not used yet. + scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(kFakeFileSize)); + + const int result = + reader.Read(io_buffer.get(), + kFakeFileSize, + base::Bind(&EventLogger::OnRead, logger.GetWeakPtr())); + EXPECT_EQ(net::ERR_IO_PENDING, result); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(1u, logger.results().size()); + EXPECT_EQ(net::ERR_FAILED, logger.results()[0]); +} + +TEST_F(FileSystemProviderFileStreamReader, Read_InChunks) { + EventLogger logger; + + const int64 initial_offset = 0; + FileStreamReader reader(NULL, + file_url_, + initial_offset, + base::Time::Now()); // Not used yet. + + for (size_t offset = 0; offset < kFakeFileSize; ++offset) { + scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(1)); + const int result = + reader.Read(io_buffer.get(), + 1, + base::Bind(&EventLogger::OnRead, logger.GetWeakPtr())); + EXPECT_EQ(net::ERR_IO_PENDING, result); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(offset + 1u, logger.results().size()); + EXPECT_EQ(1, logger.results()[offset]); + EXPECT_EQ(kFakeFileText[offset], io_buffer->data()[0]); + } +} + +TEST_F(FileSystemProviderFileStreamReader, Read_Slice) { + EventLogger logger; + + // Trim first 3 and last 3 characters. + const int64 initial_offset = 3; + const int length = static_cast<int>(kFakeFileSize) - initial_offset - 3; + ASSERT_GT(kFakeFileSize, static_cast<size_t>(initial_offset)); + ASSERT_LT(0, length); + + FileStreamReader reader(NULL, + file_url_, + initial_offset, + base::Time::Now()); // Not used yet. + scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(length)); + + const int result = + reader.Read(io_buffer.get(), + length, + base::Bind(&EventLogger::OnRead, logger.GetWeakPtr())); + EXPECT_EQ(net::ERR_IO_PENDING, result); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(1u, logger.results().size()); + EXPECT_EQ(length, logger.results()[0]); + + std::string buffer_as_string(io_buffer->data(), length); + std::string expected_buffer(kFakeFileText + initial_offset, length); + EXPECT_EQ(expected_buffer, buffer_as_string); +} + +TEST_F(FileSystemProviderFileStreamReader, Read_Beyond) { + EventLogger logger; + + // Request reading 1KB more than available. + const int64 initial_offset = 0; + const int length = static_cast<int>(kFakeFileSize) + 1024; + + FileStreamReader reader(NULL, + file_url_, + initial_offset, + base::Time::Now()); // Not used yet. + scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(length)); + + const int result = + reader.Read(io_buffer.get(), + length, + base::Bind(&EventLogger::OnRead, logger.GetWeakPtr())); + EXPECT_EQ(net::ERR_IO_PENDING, result); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(1u, logger.results().size()); + EXPECT_LT(0, logger.results()[0]); + EXPECT_EQ(kFakeFileSize, static_cast<size_t>(logger.results()[0])); + + std::string buffer_as_string(io_buffer->data(), kFakeFileSize); + EXPECT_EQ(kFakeFileText, buffer_as_string); +} + +TEST_F(FileSystemProviderFileStreamReader, GetLength) { + EventLogger logger; + + const int64 initial_offset = 0; + FileStreamReader reader(NULL, + file_url_, + initial_offset, + base::Time::Now()); // Not used yet. + + const int result = reader.GetLength( + base::Bind(&EventLogger::OnGetLength, logger.GetWeakPtr())); + EXPECT_EQ(net::ERR_IO_PENDING, result); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(1u, logger.results().size()); + EXPECT_LT(0, logger.results()[0]); + EXPECT_EQ(kFakeFileSize, static_cast<size_t>(logger.results()[0])); +} + +TEST_F(FileSystemProviderFileStreamReader, GetLength_WrongFile) { + EventLogger logger; + + const int64 initial_offset = 0; + FileStreamReader reader(NULL, + wrong_file_url_, + initial_offset, + base::Time::Now()); // Not used yet. + + const int result = reader.GetLength( + base::Bind(&EventLogger::OnGetLength, logger.GetWeakPtr())); + EXPECT_EQ(net::ERR_IO_PENDING, result); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(1u, logger.results().size()); + EXPECT_EQ(net::ERR_FAILED, logger.results()[0]); +} + +} // namespace file_system_provider +} // namespace chromeos diff --git a/chrome/browser/chromeos/file_system_provider/operations/read_file.cc b/chrome/browser/chromeos/file_system_provider/operations/read_file.cc index 55ba7b0..7dafd4c 100644 --- a/chrome/browser/chromeos/file_system_provider/operations/read_file.cc +++ b/chrome/browser/chromeos/file_system_provider/operations/read_file.cc @@ -54,7 +54,7 @@ ReadFile::ReadFile( buffer_(buffer), offset_(offset), length_(length), - current_offset_(offset), + current_offset_(0), callback_(callback) { } diff --git a/chrome/browser/chromeos/file_system_provider/operations/read_file_unittest.cc b/chrome/browser/chromeos/file_system_provider/operations/read_file_unittest.cc index 3f39e4a..01108cf 100644 --- a/chrome/browser/chromeos/file_system_provider/operations/read_file_unittest.cc +++ b/chrome/browser/chromeos/file_system_provider/operations/read_file_unittest.cc @@ -6,10 +6,10 @@ #include "base/files/file.h" #include "base/files/file_path.h" -#include "base/json/json_reader.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" +#include "base/values.h" #include "chrome/browser/chromeos/file_system_provider/operations/read_file.h" #include "chrome/common/extensions/api/file_system_provider.h" #include "chrome/common/extensions/api/file_system_provider_internal.h" @@ -181,7 +181,6 @@ TEST_F(FileSystemProviderOperationsReadFileTest, Execute_NoListener) { } TEST_F(FileSystemProviderOperationsReadFileTest, OnSuccess) { - using extensions::api::file_system_provider::EntryMetadata; using extensions::api::file_system_provider_internal:: ReadFileRequestedSuccess::Params; @@ -202,44 +201,33 @@ TEST_F(FileSystemProviderOperationsReadFileTest, OnSuccess) { EXPECT_TRUE(read_file.Execute(kRequestId)); - // Sample input as JSON. Keep in sync with file_system_provider_api.idl. - // As for now, it is impossible to create *::Params class directly, not from - // base::Value. - const std::string input = - "[\n" - " 1,\n" // kFileSystemId - " 2,\n" // kRequestId - " \"ABCDE\",\n" // 5 bytes - " false\n" // has_next - "]\n"; - - int json_error_code; - std::string json_error_msg; - scoped_ptr<base::Value> value(base::JSONReader::ReadAndReturnError( - input, base::JSON_PARSE_RFC, &json_error_code, &json_error_msg)); - ASSERT_TRUE(value.get()) << json_error_msg; - - base::ListValue* value_as_list; - ASSERT_TRUE(value->GetAsList(&value_as_list)); - scoped_ptr<Params> params(Params::Create(*value_as_list)); + const std::string data = "ABCDE"; + const bool has_next = false; + + base::ListValue value_as_list; + value_as_list.Set(0, new base::FundamentalValue(kFileSystemId)); + value_as_list.Set(1, new base::FundamentalValue(kRequestId)); + value_as_list.Set( + 2, base::BinaryValue::CreateWithCopiedBuffer(data.c_str(), data.size())); + value_as_list.Set(3, new base::FundamentalValue(has_next)); + + scoped_ptr<Params> params(Params::Create(value_as_list)); ASSERT_TRUE(params.get()); scoped_ptr<RequestValue> request_value( RequestValue::CreateForReadFileSuccess(params.Pass())); ASSERT_TRUE(request_value.get()); - const bool has_next = false; read_file.OnSuccess(kRequestId, request_value.Pass(), has_next); ASSERT_EQ(1u, callback_logger.events().size()); CallbackLogger::Event* event = callback_logger.events()[0]; EXPECT_EQ(kLength, event->chunk_length()); EXPECT_FALSE(event->has_next()); - EXPECT_EQ("ABCDE", std::string(io_buffer_->data() + kOffset, kLength)); + EXPECT_EQ(data, std::string(io_buffer_->data(), kLength)); EXPECT_EQ(base::File::FILE_OK, event->result()); } TEST_F(FileSystemProviderOperationsReadFileTest, OnError) { - using extensions::api::file_system_provider::EntryMetadata; using extensions::api::file_system_provider_internal::ReadFileRequestedError:: Params; diff --git a/chrome/browser/chromeos/file_system_provider/provided_file_system.cc b/chrome/browser/chromeos/file_system_provider/provided_file_system.cc index b619e4e..406619f 100644 --- a/chrome/browser/chromeos/file_system_provider/provided_file_system.cc +++ b/chrome/browser/chromeos/file_system_provider/provided_file_system.cc @@ -15,13 +15,19 @@ #include "chrome/common/extensions/api/file_system_provider.h" #include "extensions/browser/event_router.h" +namespace net { +class IOBuffer; +} // namespace net + namespace chromeos { namespace file_system_provider { ProvidedFileSystem::ProvidedFileSystem( extensions::EventRouter* event_router, const ProvidedFileSystemInfo& file_system_info) - : event_router_(event_router), file_system_info_(file_system_info) { + : event_router_(event_router), + file_system_info_(file_system_info), + weak_ptr_factory_(this) { } ProvidedFileSystem::~ProvidedFileSystem() {} @@ -120,5 +126,9 @@ RequestManager* ProvidedFileSystem::GetRequestManager() { return &request_manager_; } +base::WeakPtr<ProvidedFileSystemInterface> ProvidedFileSystem::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); +} + } // namespace file_system_provider } // namespace chromeos diff --git a/chrome/browser/chromeos/file_system_provider/provided_file_system.h b/chrome/browser/chromeos/file_system_provider/provided_file_system.h index dffc73e..91606ae 100644 --- a/chrome/browser/chromeos/file_system_provider/provided_file_system.h +++ b/chrome/browser/chromeos/file_system_provider/provided_file_system.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_CHROMEOS_FILE_SYSTEM_PROVIDER_PROVIDED_FILE_SYSTEM_H_ #define CHROME_BROWSER_CHROMEOS_FILE_SYSTEM_PROVIDER_PROVIDED_FILE_SYSTEM_H_ +#include "base/memory/weak_ptr.h" #include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h" #include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h" #include "chrome/browser/chromeos/file_system_provider/request_manager.h" @@ -56,12 +57,14 @@ class ProvidedFileSystem : public ProvidedFileSystemInterface { const ReadChunkReceivedCallback& callback) OVERRIDE; virtual const ProvidedFileSystemInfo& GetFileSystemInfo() const OVERRIDE; virtual RequestManager* GetRequestManager() OVERRIDE; + virtual base::WeakPtr<ProvidedFileSystemInterface> GetWeakPtr() OVERRIDE; private: extensions::EventRouter* event_router_; RequestManager request_manager_; ProvidedFileSystemInfo file_system_info_; + base::WeakPtrFactory<ProvidedFileSystemInterface> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(ProvidedFileSystem); }; diff --git a/chrome/browser/chromeos/file_system_provider/provided_file_system_info.cc b/chrome/browser/chromeos/file_system_provider/provided_file_system_info.cc index b552436..ff98eda 100644 --- a/chrome/browser/chromeos/file_system_provider/provided_file_system_info.cc +++ b/chrome/browser/chromeos/file_system_provider/provided_file_system_info.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/chromeos/file_system_provider/provided_file_system.h" +#include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h" namespace chromeos { namespace file_system_provider { diff --git a/chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h b/chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h index b78b2a4..b228e67 100644 --- a/chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h +++ b/chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h @@ -8,6 +8,7 @@ #include "base/callback.h" #include "base/files/file.h" #include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" #include "webkit/browser/fileapi/async_file_util.h" class EventRouter; @@ -84,6 +85,9 @@ class ProvidedFileSystemInterface { // Returns a request manager for the file system. virtual RequestManager* GetRequestManager() = 0; + + // Returns a weak pointer to this object. + virtual base::WeakPtr<ProvidedFileSystemInterface> GetWeakPtr() = 0; }; } // namespace file_system_provider diff --git a/chrome/browser/chromeos/fileapi/file_system_backend.cc b/chrome/browser/chromeos/fileapi/file_system_backend.cc index a040fc7..04fd83f 100644 --- a/chrome/browser/chromeos/fileapi/file_system_backend.cc +++ b/chrome/browser/chromeos/fileapi/file_system_backend.cc @@ -277,7 +277,8 @@ fileapi::FileSystemOperation* FileSystemBackend::CreateFileSystemOperation( bool FileSystemBackend::SupportsStreaming( const fileapi::FileSystemURL& url) const { - return url.type() == fileapi::kFileSystemTypeDrive; + return url.type() == fileapi::kFileSystemTypeDrive || + url.type() == fileapi::kFileSystemTypeProvided; } scoped_ptr<webkit_blob::FileStreamReader> diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi index 6e41a27..49aa3fc 100644 --- a/chrome/chrome_browser_chromeos.gypi +++ b/chrome/chrome_browser_chromeos.gypi @@ -376,6 +376,8 @@ 'browser/chromeos/file_manager/zip_file_creator.h', 'browser/chromeos/file_system_provider/fileapi/backend_delegate.cc', 'browser/chromeos/file_system_provider/fileapi/backend_delegate.h', + 'browser/chromeos/file_system_provider/fileapi/file_stream_reader.cc', + 'browser/chromeos/file_system_provider/fileapi/file_stream_reader.h', 'browser/chromeos/file_system_provider/fileapi/provider_async_file_util.cc', 'browser/chromeos/file_system_provider/fileapi/provider_async_file_util.h', 'browser/chromeos/file_system_provider/mount_path_util.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index a97c1b1..dcceebb 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -708,6 +708,7 @@ 'browser/chromeos/file_manager/volume_manager_unittest.cc', 'browser/chromeos/file_system_provider/fake_provided_file_system.cc', 'browser/chromeos/file_system_provider/fake_provided_file_system.h', + 'browser/chromeos/file_system_provider/fileapi/file_stream_reader_unittest.cc', 'browser/chromeos/file_system_provider/fileapi/provider_async_file_util_unittest.cc', 'browser/chromeos/file_system_provider/mount_path_util_unittest.cc', 'browser/chromeos/file_system_provider/operations/close_file_unittest.cc', diff --git a/chrome/common/extensions/api/file_system_provider.idl b/chrome/common/extensions/api/file_system_provider.idl index 8b02cff..bee9903 100644 --- a/chrome/common/extensions/api/file_system_provider.idl +++ b/chrome/common/extensions/api/file_system_provider.idl @@ -85,7 +85,7 @@ namespace fileSystemProvider { // data will be returned, then <code>hasNext</code> must be true, and it // has to be called again with additional entries. If no more data is // available, then <code>hasNext</code> must be set to false. - callback FileDataCallback = void(DOMString data, bool hasNext); + callback FileDataCallback = void(ArrayBuffer data, bool hasNext); interface Functions { // Mounts a file system with the given <code>displayName</code>. @@ -145,6 +145,7 @@ namespace fileSystemProvider { // exist, then it should be created. [maxListeners=1] static void onOpenFileRequested( long fileSystemId, + long requestId, DOMString filePath, OpenFileMode mode, boolean create, diff --git a/chrome/common/extensions/api/file_system_provider_internal.idl b/chrome/common/extensions/api/file_system_provider_internal.idl index f63cc0e..818c3fb 100644 --- a/chrome/common/extensions/api/file_system_provider_internal.idl +++ b/chrome/common/extensions/api/file_system_provider_internal.idl @@ -80,7 +80,7 @@ namespace fileSystemProviderInternal { static void readFileRequestedSuccess( long fileSystemId, long requestId, - DOMString data, + ArrayBuffer data, boolean hasNext); // Internal. Error callback of the <code>onReadFileRequested</code> diff --git a/chrome/renderer/resources/extensions/file_system_provider_custom_bindings.js b/chrome/renderer/resources/extensions/file_system_provider_custom_bindings.js index cb06cda..9f7384f 100644 --- a/chrome/renderer/resources/extensions/file_system_provider_custom_bindings.js +++ b/chrome/renderer/resources/extensions/file_system_provider_custom_bindings.js @@ -195,8 +195,8 @@ eventBindings.registerArgumentMassager( fileSystemProviderInternal.closeFileRequestedError( fileSystemId, requestId, error); } - dispatch([fileSystemId, openRequestId, openRequestId, onSuccessCallback, - onErrorCallback]); + dispatch([fileSystemId, openRequestId, onSuccessCallback, + onErrorCallback]); }); eventBindings.registerArgumentMassager( diff --git a/chrome/test/data/extensions/api_test/file_system_provider/read_file/manifest.json b/chrome/test/data/extensions/api_test/file_system_provider/read_file/manifest.json new file mode 100644 index 0000000..339c3cf --- /dev/null +++ b/chrome/test/data/extensions/api_test/file_system_provider/read_file/manifest.json @@ -0,0 +1,18 @@ +{ + "key": "MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDOuXEIuoK1kAkBe0SKiJn/N9oNn3oUxGa4dwj40MnJqPn+w0aR2vuyocm0R4Drp67aYwtLjOVPF4CICRq6ICP6eU07gGwQxGdZ7HJASXV8hm0tab5I70oJmRLfFJyVAMCeWlFaOGq05v2i6EbifZM0qO5xALKNGQt+yjXi5INM5wIBIw==", + "name": "chrome.fileSystemProvider.onReadFileRequested", + "version": "0.1", + "manifest_version": 2, + "description": + "Test for chrome.fileSystemProvider.onReadFileRequested().", + "permissions": [ + "fileSystemProvider", + "fileBrowserPrivate", + "fileBrowserHandler" + ], + "app": { + "background": { + "scripts": ["test.js"] + } + } +} diff --git a/chrome/test/data/extensions/api_test/file_system_provider/read_file/test.js b/chrome/test/data/extensions/api_test/file_system_provider/read_file/test.js new file mode 100644 index 0000000..b9d7b08 --- /dev/null +++ b/chrome/test/data/extensions/api_test/file_system_provider/read_file/test.js @@ -0,0 +1,292 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +/** + * @type {number} + */ +var fileSystemId = 0; + +/** + * @type {?DOMFileSystem} + */ +var fileSystem = null; + +/** + * Map of opened files, from a <code>openRequestId</code> to <code>filePath + * </code>. + * @type {Object.<number, string>} + */ +var openedFiles = {}; + +/** + * @type {Object} + * @const + */ +var TESTING_ROOT = Object.freeze({ + isDirectory: true, + name: '', + size: 0, + modificationTime: new Date(2014, 4, 28, 10, 39, 15) +}); + +/** + * Testing contents for files. + * @type {string} + * @const + */ +var TESTING_TEXT = 'I have a basket full of fruits.'; + +/** + * Metadata of a healthy file used to read contents from. + * @type {Object} + * @const + */ +var TESTING_TIRAMISU_FILE = Object.freeze({ + isDirectory: false, + name: 'tiramisu.txt', + size: TESTING_TEXT.length, + modificationTime: new Date(2014, 1, 25, 7, 36, 12) +}); + +/** + * Metadata of a broken file used to read contents from. + * @type {Object} + * @const + */ +var TESTING_BROKEN_TIRAMISU_FILE = Object.freeze({ + isDirectory: false, + name: 'broken-tiramisu.txt', + size: TESTING_TEXT.length, + modificationTime: new Date(2014, 1, 25, 7, 36, 12) +}); + +/** + * Returns metadata for the requested entry. + * + * To successfully acquire a DirectoryEntry, or even a DOMFileSystem, this event + * must be implemented and return correct values. + * + * @param {number} inFileSystemId ID of the file system. + * @param {string} entryPath Path of the requested entry. + * @param {function(Object)} onSuccess Success callback with metadata passed + * an argument. + * @param {function(string)} onError Error callback with an error code. + */ +function onGetMetadataRequested( + inFileSystemId, entryPath, onSuccess, onError) { + if (inFileSystemId != fileSystemId) { + onError('SECURITY_ERROR'); // enum ProviderError. + return; + } + + if (entryPath == '/') { + onSuccess(TESTING_ROOT); + return; + } + + if (entryPath == '/' + TESTING_TIRAMISU_FILE.name) { + onSuccess(TESTING_TIRAMISU_FILE); + return; + } + + if (entryPath == '/' + TESTING_BROKEN_TIRAMISU_FILE.name) { + onSuccess(TESTING_BROKEN_TIRAMISU_FILE); + return; + } + + onError('NOT_FOUND'); // enum ProviderError. +} + +/** + * Requests opening a file at <code>filePath</code>. Further file operations + * will be associated with the <code>requestId</code> + * + * @param {number} inFileSystemId ID of the file system. + * @param {number} requestId ID of the opening request. Used later for reading. + * @param {string} filePath Path of the file to be opened. + * @param {string} mode Mode, either reading or writing. + * @param {boolean} create True to create if doesn't exist. + * @param {function()} onSuccess Success callback. + * @param {function(string)} onError Error callback. + */ +function onOpenFileRequested( + inFileSystemId, requestId, filePath, mode, create, onSuccess, onError) { + if (inFileSystemId != fileSystemId || mode != 'READ' || create) { + onError('SECURITY_ERROR'); // enum ProviderError. + return; + } + + if (filePath == '/' + TESTING_TIRAMISU_FILE.name || + filePath == '/' + TESTING_BROKEN_TIRAMISU_FILE.name) { + openedFiles[requestId] = filePath; + onSuccess(); + } else { + onError('NOT_FOUND'); // enum ProviderError. + } +} + +/** + * Requests closing a file previously opened with <code>openRequestId</code>. + * + * @param {number} inFileSystemId ID of the file system. + * @param {number} openRequestId ID of the request used to open the file. + * @param {function()} onSuccess Success callback. + * @param {function(string)} onError Error callback. + */ +function onCloseFileRequested( + inFileSystemId, openRequestId, onSuccess, onError) { + if (inFileSystemId != fileSystemId || !openedFiles[openRequestId]) { + onError('SECURITY_ERROR'); // enum ProviderError. + return; + } + + delete openedFiles[requestId]; + onSuccess(); +} + +/** + * Requests reading contents of a file, previously opened with <code> + * openRequestId</code>. + * + * @param {number} inFileSystemId ID of the file system. + * @param {number} openRequestId ID of the request used to open the file. + * @param {number} offset Offset of the file. + * @param {number} length Number of bytes to read. + * @param {function(ArrayBuffer, boolean)} onSuccess Success callback with a + * chunk of data, and information if more data will be provided later. + * @param {function(string)} onError Error callback. + */ +function onReadFileRequested( + inFileSystemId, openRequestId, offset, length, onSuccess, onError) { + var filePath = openedFiles[openRequestId]; + if (inFileSystemId != fileSystemId || !filePath) { + onError('SECURITY_ERROR'); // enum ProviderError. + return; + } + + if (filePath == '/' + TESTING_TIRAMISU_FILE.name) { + var textToSend = TESTING_TEXT.substr(offset, length); + var textToSendInChunks = textToSend.split(/(?= )/); + + textToSendInChunks.forEach(function(item, index) { + // Convert item (string) to an ArrayBuffer. + var reader = new FileReader(); + + reader.onload = function(e) { + onSuccess( + e.target.result, + index < textToSendInChunks.length - 1 /* has_next */); + }; + + reader.readAsArrayBuffer(new Blob([item])); + }); + return; + } + + if (filePath == '/' + TESTING_BROKEN_TIRAMISU_FILE.name) { + onError('ACCESS_DENIED'); // enum ProviderError. + return; + } + + onError('INVALID_OPERATION'); // enum ProviderError. +} + +/** + * Sets up the tests. Called once per all test cases. In case of a failure, + * the callback is not called. + * + * @param {function()} callback Success callback. + */ +function setUp(callback) { + chrome.fileSystemProvider.mount('chocolate.zip', function(id) { + fileSystemId = id; + chrome.fileSystemProvider.onGetMetadataRequested.addListener( + onGetMetadataRequested); + chrome.fileSystemProvider.onOpenFileRequested.addListener( + onOpenFileRequested); + chrome.fileSystemProvider.onReadFileRequested.addListener( + onReadFileRequested); + var volumeId = + 'provided:' + chrome.runtime.id + '-' + fileSystemId + '-user'; + + chrome.fileBrowserPrivate.requestFileSystem( + volumeId, + function(inFileSystem) { + chrome.test.assertTrue(!!inFileSystem); + + fileSystem = inFileSystem; + callback(); + }); + }, function() { + chrome.test.fail(); + }); +} + +/** + * Runs all of the test cases, one by one. + */ +function runTests() { + chrome.test.runTests([ + // Read contents of the /tiramisu.txt file. This file exists, so it should + // succeed. + function readFileSuccess() { + var onTestSuccess = chrome.test.callbackPass(); + fileSystem.root.getFile( + TESTING_TIRAMISU_FILE.name, + {create: false}, + function(fileEntry) { + fileEntry.file(function(file) { + var fileReader = new FileReader(); + fileReader.onload = function(e) { + var text = fileReader.result; + chrome.test.assertEq(TESTING_TEXT, text); + onTestSuccess(); + }; + fileReader.onerror = function(e) { + chrome.test.fail(fileReader.error.name); + }; + fileReader.readAsText(file); + }, + function(error) { + chrome.test.fail(error.name); + }); + }, + function(error) { + chrome.test.fail(error.name); + }); + }, + // Read contents of a file file, but with an error on the way. This should + // result in an error. + function readEntriesError() { + var onTestSuccess = chrome.test.callbackPass(); + fileSystem.root.getFile( + TESTING_BROKEN_TIRAMISU_FILE.name, + {create: false}, + function(fileEntry) { + fileEntry.file(function(file) { + var fileReader = new FileReader(); + fileReader.onload = function(e) { + chrome.test.fail(); + }; + fileReader.onerror = function(e) { + chrome.test.assertEq('NotReadableError', fileReader.error.name); + onTestSuccess(); + }; + fileReader.readAsText(file); + }, + function(error) { + chrome.test.fail(); + }); + }, + function(error) { + chrome.test.fail(error.name); + }); + } + ]); +} + +// Setup and run all of the test cases. +setUp(runTests); |