diff options
Diffstat (limited to 'webkit/chromeos')
-rw-r--r-- | webkit/chromeos/OWNERS | 3 | ||||
-rw-r--r-- | webkit/chromeos/fileapi/async_file_stream.h | 56 | ||||
-rw-r--r-- | webkit/chromeos/fileapi/file_util_async.h | 123 | ||||
-rw-r--r-- | webkit/chromeos/fileapi/memory_file_util.cc | 586 | ||||
-rw-r--r-- | webkit/chromeos/fileapi/memory_file_util.h | 150 | ||||
-rw-r--r-- | webkit/chromeos/fileapi/memory_file_util_unittest.cc | 825 |
6 files changed, 1743 insertions, 0 deletions
diff --git a/webkit/chromeos/OWNERS b/webkit/chromeos/OWNERS new file mode 100644 index 0000000..427afde --- /dev/null +++ b/webkit/chromeos/OWNERS @@ -0,0 +1,3 @@ +kinuko@chromium.org +satorux@chromium.org +zelidrag@chromium.org diff --git a/webkit/chromeos/fileapi/async_file_stream.h b/webkit/chromeos/fileapi/async_file_stream.h new file mode 100644 index 0000000..ef0eeed --- /dev/null +++ b/webkit/chromeos/fileapi/async_file_stream.h @@ -0,0 +1,56 @@ +// Copyright (c) 2011 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 WEBKIT_CHROMEOS_FILEAPI_ASYNC_FILE_STREAM_H_ +#define WEBKIT_CHROMEOS_FILEAPI_ASYNC_FILE_STREAM_H_ + +#include "base/callback.h" +#include "base/platform_file.h" + +namespace fileapi { + +using base::PlatformFileError; + +// This class is used for implementing Open() in FileUtilAsync. This class +// is similar to net::FileStream, but supporting only the asynchronous +// operations and not necessarily relying on PlatformFile. +class AsyncFileStream { + public: + // Used for Read() and Write(). |result| is the return code of the + // operation, and |length| is the length of data read or written. + typedef base::Callback<void(PlatformFileError result, + int64 length)> ReadWriteCallback; + + // Used for Seek(). |result| is the return code of the operation. + typedef base::Callback<void(PlatformFileError)> SeekCallback; + + virtual ~AsyncFileStream() {}; + + // Reads data from the current stream position. Up to |length| bytes + // will be read from |buffer|. The memory pointed to by |buffer| must + // remain valid until the callback is called. On success, + // PLATFORM_FILE_OK is passed to |callback| with the number of bytes + // read. On failure, an error code is passed instead. + virtual void Read(char* buffer, + int64 length, + const ReadWriteCallback& callback) = 0; + + // Writes data at the current stream position. Up to |length| bytes will + // be written from |buffer|. The memory pointed to by |buffer| must + // remain valid until the callback is called. On success, + // PLATFORM_FILE_OK is passed to |callback| with the number of bytes + // written. On failure, an error code is passed instead. + virtual void Write(const char* buffer, + int64 length, + const ReadWriteCallback& callback) = 0; + + // Moves the stream position. On success, PLATFORM_FILE_OK is passed to + // |callback|. On error, an error code is passed instead. + virtual void Seek(int64 offset, + const SeekCallback& callback) = 0; +}; + +} // namespace fileapi + +#endif // WEBKIT_CHROMEOS_FILEAPI_ASYNC_FILE_STREAM_H_ diff --git a/webkit/chromeos/fileapi/file_util_async.h b/webkit/chromeos/fileapi/file_util_async.h new file mode 100644 index 0000000..58cb2b1 --- /dev/null +++ b/webkit/chromeos/fileapi/file_util_async.h @@ -0,0 +1,123 @@ +// Copyright (c) 2011 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 WEBKIT_CHROMEOS_FILEAPI_FILE_SYSTEM_FILE_UTIL_ASYNC_H_ +#define WEBKIT_CHROMEOS_FILEAPI_FILE_SYSTEM_FILE_UTIL_ASYNC_H_ + +#include "base/callback.h" +#include "base/file_util_proxy.h" +#include "base/platform_file.h" +#include "webkit/chromeos/fileapi/async_file_stream.h" + +namespace fileapi { + +using base::PlatformFileError; + +// The interface class of asynchronous file utils. All functions are pure +// virtual. +class FileUtilAsync { + public: + virtual ~FileUtilAsync() {} + + typedef base::FileUtilProxy::Entry DirectoryEntry; + + // Used for GetFileInfo(). |result| is the return code of the operation, + // and |file_info| is the obtained file info. + typedef base::Callback<void( + PlatformFileError result, + const base::PlatformFileInfo& file_info)> GetFileInfoCallback; + + // Used for Create(), etc. |result| is the return code of the operation. + typedef base::Callback<void(PlatformFileError)> StatusCallback; + + // Used for Open(). |result| is the return code of the operation, and + // |stream| is the stream of the opened file. + typedef base::Callback<void( + PlatformFileError result, + AsyncFileStream* stream)> OpenCallback; + + typedef std::vector<DirectoryEntry> FileList; + + // Used for ReadDirectoryCallback(). |result| is the return code of the + // operation, |file_list| is the list of files read, and |completed| is + // true if all files are read. + typedef base::Callback<void( + PlatformFileError result, + const FileList& file_list, + bool completed)> ReadDirectoryCallback; + + // Opens a file of the given |file_path| with |flags|. On success, + // PLATFORM_FILE_OK is passed to |callback| with a pointer to newly + // created AsyncFileStream object. The caller should delete the + // stream. On failure, an error code is passed instead. + virtual void Open(const FilePath& file_path, + int file_flags, // PlatformFileFlags + const OpenCallback& callback) = 0; + + // Gets file info of the given |file_path|. On success, + // PLATFORM_FILE_OK is passed to |callback| with the the obtained file + // info. On failure, an error code is passed instead. + virtual void GetFileInfo(const FilePath& file_path, + const GetFileInfoCallback& callback) = 0; + + // Creates a file of the given |file_path|. On success, + // PLATFORM_FILE_OK is passed to |callback|. On failure, an error code + // is passed instead. + virtual void Create(const FilePath& file_path, + const StatusCallback& callback) = 0; + + // Truncates a file of the given |file_path| to |length|. On success, + // PLATFORM_FILE_OK is passed to |callback|. On failure, an error code + // is passed instead. + virtual void Truncate(const FilePath& file_path, + int64 length, + const StatusCallback& callback) = 0; + + // Modifies the timestamps of a file of the given |file_path|. On + // success, PLATFORM_FILE_OK is passed to |callback|. On failure, an + // error code is passed instead. + virtual void Touch(const FilePath& file_path, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) = 0; + + // Removes a file or directory of the given |file_path|. If |recursive| + // is true, removes the contents of the given directory recursively. On + // success, PLATFORM_FILE_OK is passed to |callback|. On failure, an + // error code is passed instead. + virtual void Remove(const FilePath& file_path, + bool recursive, + const StatusCallback& callback) = 0; + + // Creates a directory of the given |dir_path|. On success, + // PLATFORM_FILE_OK is passed to |callback|. On failure, an error code + // is passed instead. + virtual void CreateDirectory(const FilePath& dir_path, + const StatusCallback& callback) = 0; + + // Reads a directory of the given |dir_path|. On success, + // PLATFORM_FILE_OK is passed to |callback| with the list of files, and + // a boolean value indicating if all files are read. On failure, an + // error code is passed instead. + // + // The ReadDirectoryCallback may be called several times, returning the + // portions of the whole directory listing. The reference to vector with + // DirectoryEntry objects, passed to callback is guaranteed to contain the + // results of the operation only while callback is running. + // + // The implementations of FileUtilAsync should be careful to populate the + // vector on the same thread, on which the callback is called. Otherwise, + // if callback is called through PostTask, the data might get overwritten + // before callback is actually called. + // + // TODO(olege): Maybe make it possible to read only a part of the directory. + virtual void ReadDirectory(const FilePath& dir_path, + const ReadDirectoryCallback& callback) = 0; + + // TODO(olege): Add LocalCopy and LocalMove. +}; + +} // fileapi + +#endif // WEBKIT_CHROMEOS_FILEAPI_FILE_SYSTEM_FILE_UTIL_ASYNC_H_ diff --git a/webkit/chromeos/fileapi/memory_file_util.cc b/webkit/chromeos/fileapi/memory_file_util.cc new file mode 100644 index 0000000..2d4d53e --- /dev/null +++ b/webkit/chromeos/fileapi/memory_file_util.cc @@ -0,0 +1,586 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "webkit/chromeos/fileapi/memory_file_util.h" + +namespace { +const int kDefaultReadDirectoryBufferSize = 100; +} // namespace + +namespace fileapi { + +// In-memory implementation of AsyncFileStream. +class MemoryFileUtilAsyncFileStream : public AsyncFileStream { + public: + // |file_entry| is owned by MemoryFileUtil. + MemoryFileUtilAsyncFileStream(MemoryFileUtil::FileEntry* file_entry, + int flags) + : file_entry_(file_entry), + flags_(flags), + offset_(0), + weak_ptr_factory_(this) { + } + + // AsyncFileStream override. + virtual void Read(char* buffer, + int64 length, + const ReadWriteCallback& callback) OVERRIDE { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtilAsyncFileStream::DoRead, + weak_ptr_factory_.GetWeakPtr(), + buffer, length, callback)); + } + + // AsyncFileStream override. + virtual void Write(const char* buffer, + int64 length, + const ReadWriteCallback& callback) OVERRIDE { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtilAsyncFileStream::DoWrite, + weak_ptr_factory_.GetWeakPtr(), + buffer, length, callback)); + } + + // AsyncFileStream override. + virtual void Seek(int64 offset, + const SeekCallback& callback) OVERRIDE { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtilAsyncFileStream::DoSeek, + weak_ptr_factory_.GetWeakPtr(), + offset, callback)); + } + + private: + // Callback used for Read(). + void DoRead(char* buffer, + int64 length, + const ReadWriteCallback& callback) { + + if ((flags_ & base::PLATFORM_FILE_READ) == 0) { + callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, 0); + return; + } + + // Shorten the length so the read does not overrun. + length = std::min(length, file_size() - offset_); + + const std::string& contents = file_entry_->contents; + std::copy(contents.begin() + offset_, + contents.begin() + offset_ + length, + buffer); + offset_ += length; + + callback.Run(base::PLATFORM_FILE_OK, length); + } + + // Callback used for Write(). + void DoWrite(const char* buffer, + int64 length, + const ReadWriteCallback& callback) { + if ((flags_ & base::PLATFORM_FILE_WRITE) == 0) { + callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, 0); + return; + } + + // Extend the contents if needed. + std::string* contents = &file_entry_->contents; + if (offset_ + length > file_size()) + contents->resize(offset_ + length, 0); // Fill with 0. + + std::copy(buffer, buffer + length, + contents->begin() + offset_); + file_entry_->last_modified = base::Time::Now(); + offset_ += length; + + callback.Run(base::PLATFORM_FILE_OK, length); + } + + // Callback used for Seek(). + void DoSeek(int64 offset, + const SeekCallback& callback) { + if (offset > file_size()) { + // Unlike lseek(2), we don't allow an offset larger than the file + // size for this file implementation. + callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); + return; + } + + offset_ = offset; + callback.Run(base::PLATFORM_FILE_OK); + } + + // Returns the file size as int64. + int64 file_size() const { + return static_cast<int64>(file_entry_->contents.size()); + } + + MemoryFileUtil::FileEntry* file_entry_; + const int flags_; + int64 offset_; + base::WeakPtrFactory<MemoryFileUtilAsyncFileStream> weak_ptr_factory_; +}; + +MemoryFileUtil::FileEntry::FileEntry() + : is_directory(false) { +} + +MemoryFileUtil::FileEntry::~FileEntry() { +} + +MemoryFileUtil::MemoryFileUtil(const FilePath& root_path) + : read_directory_buffer_size_(kDefaultReadDirectoryBufferSize) { + FileEntry root; + root.is_directory = true; + root.last_modified = base::Time::Now(); + + files_[root_path] = root; +} + +MemoryFileUtil::~MemoryFileUtil() { +} + +// Depending on the flags value the flow of file opening will be one of +// the following: +// +// PLATFORM_FILE_OPEN: +// - GetFileInfo +// - DidGetFileInfoForOpen +// - OpenVerifiedFile +// +// PLATFORM_FILE_CREATE: +// - Create +// - DidCreateOrTruncateForOpen +// - OpenVerifiedFile +// +// PLATFORM_FILE_OPEN_ALWAYS: +// - GetFileInfo +// - DidGetFileInfoForOpen +// - OpenVerifiedFile OR Create +// DidCreateOrTruncateForOpen +// OpenVerifiedFile +// +// PLATFORM_FILE_CREATE_ALWAYS: +// - Truncate +// - OpenTruncatedFileOrCreate +// - OpenVerifiedFile OR Create +// DidCreateOrTruncateForOpen +// OpenVerifiedFile +// +// PLATFORM_FILE_OPEN_TRUNCATED: +// - Truncate +// - DidCreateOrTruncateForOpen +// - OpenVerifiedFile +// +void MemoryFileUtil::Open( + const FilePath& file_path, + int flags, + const OpenCallback& callback) { + int create_flag = flags & (base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_OPEN_ALWAYS | + base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_OPEN_TRUNCATED); + switch (create_flag) { + case base::PLATFORM_FILE_OPEN: + case base::PLATFORM_FILE_OPEN_ALWAYS: + GetFileInfo( + file_path, + base::Bind(&MemoryFileUtil::DidGetFileInfoForOpen, + base::Unretained(this), file_path, flags, callback)); + + break; + + case base::PLATFORM_FILE_CREATE: + Create( + file_path, + base::Bind(&MemoryFileUtil::DidCreateOrTruncateForOpen, + base::Unretained(this), file_path, flags, 0, callback)); + break; + + case base::PLATFORM_FILE_CREATE_ALWAYS: + Truncate( + file_path, + 0, + base::Bind(&MemoryFileUtil::OpenTruncatedFileOrCreate, + base::Unretained(this), file_path, flags, callback)); + + case base::PLATFORM_FILE_OPEN_TRUNCATED: + Truncate( + file_path, + 0, + base::Bind(&MemoryFileUtil::DidCreateOrTruncateForOpen, + base::Unretained(this), file_path, flags, 0, callback)); + break; + + default: + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(callback, + base::PLATFORM_FILE_ERROR_INVALID_OPERATION, + static_cast<AsyncFileStream*>(NULL))); + } +} + +void MemoryFileUtil::GetFileInfo( + const FilePath& file_path, + const GetFileInfoCallback& callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoGetFileInfo, base::Unretained(this), + file_path.StripTrailingSeparators(), callback)); +} + +void MemoryFileUtil::Create( + const FilePath& file_path, + const StatusCallback& callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoCreate, base::Unretained(this), + file_path.StripTrailingSeparators(), false, callback)); +} + +void MemoryFileUtil::Truncate( + const FilePath& file_path, + int64 length, + const StatusCallback& callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoTruncate, base::Unretained(this), + file_path.StripTrailingSeparators(), length, callback)); +} + +void MemoryFileUtil::Touch( + const FilePath& file_path, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoTouch, base::Unretained(this), + file_path.StripTrailingSeparators(), + last_modified_time, callback)); +} + +void MemoryFileUtil::Remove( + const FilePath& file_path, + bool recursive, + const StatusCallback& callback) { + if (recursive) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoRemoveRecursive, + base::Unretained(this), file_path.StripTrailingSeparators(), + callback)); + } else { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoRemoveSingleFile, + base::Unretained(this), file_path.StripTrailingSeparators(), + callback)); + } +} + +void MemoryFileUtil::CreateDirectory( + const FilePath& dir_path, + const StatusCallback& callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoCreate, + base::Unretained(this), dir_path.StripTrailingSeparators(), + true, callback)); +} + +void MemoryFileUtil::ReadDirectory( + const FilePath& dir_path, + const ReadDirectoryCallback& callback) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoReadDirectory, + base::Unretained(this), dir_path.StripTrailingSeparators(), + FilePath(), callback)); +} + +void MemoryFileUtil::DoGetFileInfo(const FilePath& file_path, + const GetFileInfoCallback& callback) { + base::PlatformFileInfo file_info; + + FileIterator file_it = files_.find(file_path); + + if (file_it == files_.end()) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND, file_info); + return; + } + const FileEntry& file_entry = file_it->second; + + file_info.size = file_entry.contents.size(); + file_info.is_directory = file_entry.is_directory; + file_info.is_symbolic_link = false; + + // In this file system implementation we store only one datetime. Many + // popular file systems do the same. + file_info.last_modified = file_entry.last_modified; + file_info.last_accessed = file_entry.last_modified; + file_info.creation_time = file_entry.last_modified; + + callback.Run(base::PLATFORM_FILE_OK, file_info); +} + +void MemoryFileUtil::DoCreate( + const FilePath& file_path, + bool is_directory, + const StatusCallback& callback) { + if (FileExists(file_path)) { + callback.Run(base::PLATFORM_FILE_ERROR_EXISTS); + return; + } + + if (!IsDirectory(file_path.DirName())) { + callback.Run(base::PLATFORM_FILE_ERROR_FAILED); + return; + } + + FileEntry file; + file.is_directory = is_directory; + file.last_modified = base::Time::Now(); + + files_[file_path] = file; + callback.Run(base::PLATFORM_FILE_OK); +} + +void MemoryFileUtil::DoTruncate( + const FilePath& file_path, + int64 length, + const StatusCallback& callback) { + FileIterator file_it = files_.find(file_path); + if (file_it == files_.end()) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND); + return; + } + + FileEntry& file = file_it->second; + + // Fill the extended part with 0 if |length| is larger than the original + // contents size. + file.contents.resize(length, 0); + callback.Run(base::PLATFORM_FILE_OK); +} + +void MemoryFileUtil::DoTouch( + const FilePath& file_path, + const base::Time& last_modified_time, + const StatusCallback& callback) { + FileIterator file_it = files_.find(file_path); + if (file_it == files_.end()) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND); + return; + } + + FileEntry& file = file_it->second; + + file.last_modified = last_modified_time; + callback.Run(base::PLATFORM_FILE_OK); +} + +void MemoryFileUtil::DoRemoveSingleFile( + const FilePath& file_path, + const StatusCallback& callback) { + FileIterator file_it = files_.find(file_path); + if (file_it == files_.end()) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND); + return; + } + + FileEntry& file = file_it->second; + if (file.is_directory) { + // Check if the directory is empty. This can be done by checking if + // the next file is present under the directory. Note that |files_| is + // a map hence the file names are sorted by names. + FileIterator tmp_it = file_it; + ++tmp_it; + if (tmp_it != files_.end() && file_path.IsParent(tmp_it->first)) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_A_FILE); + return; + } + } + + files_.erase(file_it); + callback.Run(base::PLATFORM_FILE_OK); +} + +void MemoryFileUtil::DoRemoveRecursive( + const FilePath& file_path, + const StatusCallback& callback) { + FileIterator file_it = files_.find(file_path); + if (file_it == files_.end()) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND); + return; + } + + FileEntry& file = file_it->second; + if (!file.is_directory) { + files_.erase(file_it); + callback.Run(base::PLATFORM_FILE_OK); + return; + } + + // Remove the directory itself. + files_.erase(file_it++); + // Remove files under the directory. + while (file_it != files_.end()) { + if (file_path.IsParent(file_it->first)) { + files_.erase(file_it++); + } else { + break; + } + } + callback.Run(base::PLATFORM_FILE_OK); +} + +void MemoryFileUtil::DoReadDirectory( + const FilePath& dir_path, + const FilePath& in_from, + const ReadDirectoryCallback& callback) { + FilePath from = in_from; + read_directory_buffer_.clear(); + + if (!FileExists(dir_path)) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND, + read_directory_buffer_, true); + return; + } + + if (!IsDirectory(dir_path)) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, + read_directory_buffer_, true); + return; + } + + if (from.empty()) + from = dir_path; + + bool completed = true; + + // Here we iterate over all paths in files_ starting with the prefix |from|. + // It is not very efficient in case of a deep tree with many files in + // subdirectories. If ever we'll need efficiency from this implementation of + // FS, this should be changed. + for (ConstFileIterator it = files_.lower_bound(from); + it != files_.end(); + ++it) { + if (dir_path == it->first) // Skip the directory itself. + continue; + if (!dir_path.IsParent(it->first)) // Not in the directory. + break; + if (it->first.DirName() != dir_path) // a file in subdirectory + continue; + + if (read_directory_buffer_.size() == read_directory_buffer_size_) { + from = it->first; + completed = false; + break; + } + + const FileEntry& file = it->second; + DirectoryEntry entry; + entry.name = it->first.BaseName().value(); + entry.is_directory = file.is_directory; + entry.size = file.contents.size(); + entry.last_modified_time = file.last_modified; + + read_directory_buffer_.push_back(entry); + } + + callback.Run(base::PLATFORM_FILE_OK, read_directory_buffer_, completed); + + if (!completed) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MemoryFileUtil::DoReadDirectory, + base::Unretained(this), dir_path, + from, callback)); + } +} + +void MemoryFileUtil::OpenVerifiedFile( + const FilePath& file_path, + int flags, + const OpenCallback& callback) { + FileIterator file_it = files_.find(file_path); + // The existence of the file is guranteed here. + DCHECK(file_it != files_.end()); + + FileEntry* file_entry = &file_it->second; + callback.Run(base::PLATFORM_FILE_OK, + new MemoryFileUtilAsyncFileStream(file_entry, flags)); +} + +void MemoryFileUtil::DidGetFileInfoForOpen( + const FilePath& file_path, + int flags, + const OpenCallback& callback, + PlatformFileError get_info_result, + const base::PlatformFileInfo& file_info) { + if (get_info_result == base::PLATFORM_FILE_OK && file_info.is_directory) { + callback.Run(base::PLATFORM_FILE_ERROR_NOT_A_FILE, NULL); + return; + } + + if (get_info_result == base::PLATFORM_FILE_OK) { + OpenVerifiedFile(file_path, flags, callback); + return; + } + + if (get_info_result == base::PLATFORM_FILE_ERROR_NOT_FOUND && + flags & base::PLATFORM_FILE_CREATE_ALWAYS) { + Create(file_path, + base::Bind(&MemoryFileUtil::DidCreateOrTruncateForOpen, + base::Unretained(this), file_path, flags, 0, callback)); + return; + } + + callback.Run(get_info_result, NULL); +} + +void MemoryFileUtil::OpenTruncatedFileOrCreate( + const FilePath& file_path, + int flags, + const OpenCallback& callback, + PlatformFileError result) { + if (result == base::PLATFORM_FILE_OK) { + OpenVerifiedFile(file_path, flags, callback); + return; + } + + if (result == base::PLATFORM_FILE_ERROR_NOT_FOUND) { + Create( + file_path, + base::Bind(&MemoryFileUtil::DidCreateOrTruncateForOpen, + base::Unretained(this), file_path, flags, 0, callback)); + return; + } + + callback.Run(result, NULL); +} + +void MemoryFileUtil::DidCreateOrTruncateForOpen( + const FilePath& file_path, + int flags, + int64 size, + const OpenCallback& callback, + PlatformFileError result) { + if (result != base::PLATFORM_FILE_OK) { + callback.Run(result, NULL); + return; + } + + OpenVerifiedFile(file_path, flags, callback); +} + +} // namespace file_api diff --git a/webkit/chromeos/fileapi/memory_file_util.h b/webkit/chromeos/fileapi/memory_file_util.h new file mode 100644 index 0000000..357b934 --- /dev/null +++ b/webkit/chromeos/fileapi/memory_file_util.h @@ -0,0 +1,150 @@ +// Copyright (c) 2011 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 WEBKIT_CHROMEOS_FILEAPI_FILE_SYSTEM_FILE_UTIL_MEMORY_H_ +#define WEBKIT_CHROMEOS_FILEAPI_FILE_SYSTEM_FILE_UTIL_MEMORY_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/time.h" +#include "webkit/chromeos/fileapi/file_util_async.h" + +namespace fileapi { + +// The in-memory file system. Primarily for the purpose of testing +// FileUtilAsync. +class MemoryFileUtil : public FileUtilAsync { + public: + struct FileEntry { + FileEntry(); + ~FileEntry(); + + bool is_directory; + std::string contents; + base::Time last_modified; + }; + + MemoryFileUtil(const FilePath& root_path); + virtual ~MemoryFileUtil(); + + // FileUtilAsync overrides. + virtual void Open(const FilePath& file_path, + int file_flags, // PlatformFileFlags + const OpenCallback& callback) OVERRIDE; + virtual void GetFileInfo(const FilePath& file_path, + const GetFileInfoCallback& callback) OVERRIDE; + virtual void Create(const FilePath& file_path, + const StatusCallback& callback) OVERRIDE; + virtual void Truncate(const FilePath& file_path, + int64 length, + const StatusCallback& callback) OVERRIDE; + // This FS ignores last_access_time. + virtual void Touch(const FilePath& file_path, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) OVERRIDE; + virtual void Remove(const FilePath& file_path, + bool recursive, + const StatusCallback& callback) OVERRIDE; + virtual void CreateDirectory(const FilePath& dir_path, + const StatusCallback& callback) OVERRIDE; + virtual void ReadDirectory(const FilePath& dir_path, + const ReadDirectoryCallback& callback) OVERRIDE; + + private: + friend class MemoryFileUtilTest; + + typedef std::map<FilePath, FileEntry>::iterator FileIterator; + typedef std::map<FilePath, FileEntry>::const_iterator ConstFileIterator; + + // Returns true if the given |file_path| is present in the file system. + bool FileExists(const FilePath& file_path) const { + return files_.find(file_path) != files_.end(); + } + + // Returns true if the given |file_path| is present and a directory. + bool IsDirectory(const FilePath& file_path) const { + ConstFileIterator it = files_.find(file_path); + return it != files_.end() && it->second.is_directory; + } + + // Callback function used to implement GetFileInfo(). + void DoGetFileInfo(const FilePath& file_path, + const GetFileInfoCallback& callback); + + // Callback function used to implement Create(). + void DoCreate(const FilePath& file_path, + bool is_directory, + const StatusCallback& callback); + + // Callback function used to implement Truncate(). + void DoTruncate(const FilePath& file_path, + int64 length, + const StatusCallback& callback); + + // Callback function used to implement Touch(). + void DoTouch(const FilePath& file_path, + const base::Time& last_modified_time, + const StatusCallback& callback); + + // Callback function used to implement Remove(). + void DoRemoveSingleFile(const FilePath& file_path, + const StatusCallback& callback); + + // Callback function used to implement Remove(). + void DoRemoveRecursive(const FilePath& file_path, + const StatusCallback& callback); + + // Will start enumerating with file path |from|. If |from| path is + // empty, will start from the beginning. + void DoReadDirectory(const FilePath& dir_path, + const FilePath& from, + const ReadDirectoryCallback& callback); + + // Opens a file of the given |file_path| with |flags|. A file is + // guaranteed to be present at |file_path|. + void OpenVerifiedFile(const FilePath& file_path, + int flags, + const OpenCallback& callback); + + // Callback function used to implement Open(). + void DidGetFileInfoForOpen(const FilePath& file_path, + int flags, + const OpenCallback& callback, + PlatformFileError get_info_result, + const base::PlatformFileInfo& file_info); + + // Callback function used to implement Open(). + void OpenTruncatedFileOrCreate(const FilePath& file_path, + int flags, + const OpenCallback& callback, + PlatformFileError result); + + // Callback function used to implement Open(). + void DidCreateOrTruncateForOpen(const FilePath& file_path, + int flags, + int64 size, + const OpenCallback& callback, + PlatformFileError result); + + // Sets the read directory size buffer. + // Mainly for the purpose of testing. + void set_read_directory_buffer_size(size_t size) { + read_directory_buffer_size_ = size; + } + + // The files in the file system. + std::map<FilePath, FileEntry> files_; + size_t read_directory_buffer_size_; + std::vector<DirectoryEntry> read_directory_buffer_; + + DISALLOW_COPY_AND_ASSIGN(MemoryFileUtil); +}; + +} // fileapi + +#endif // WEBKIT_CHROMEOS_FILEAPI_FILE_SYSTEM_FILE_UTIL_ASYNC_H_ diff --git a/webkit/chromeos/fileapi/memory_file_util_unittest.cc b/webkit/chromeos/fileapi/memory_file_util_unittest.cc new file mode 100644 index 0000000..8e991b00f --- /dev/null +++ b/webkit/chromeos/fileapi/memory_file_util_unittest.cc @@ -0,0 +1,825 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/message_loop.h" +#include "base/platform_file.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/chromeos/fileapi/memory_file_util.h" + +namespace { +const FilePath::CharType kRootPath[] = "/mnt/memory"; +const char kTestString[] = "A test string. A test string."; +const char kTestStringLength = arraysize(kTestString) - 1; +} // namespace + +namespace fileapi { + +// This test is actually testing MemoryFileUtil — an async in-memory file +// system, based on FileUtilAsync. +class MemoryFileUtilTest : public testing::Test { + public: + MemoryFileUtilTest() { + } + + void SetUp() { + file_util_.reset(new MemoryFileUtil(FilePath(kRootPath))); + } + + MemoryFileUtil* file_util() { + return file_util_.get(); + } + + enum CallbackType { + CALLBACK_TYPE_ERROR, + CALLBACK_TYPE_STATUS, + CALLBACK_TYPE_GET_FILE_INFO, + CALLBACK_TYPE_OPEN, + CALLBACK_TYPE_READ_WRITE, + CALLBACK_TYPE_READ_DIRECTORY + }; + + struct CallbackStatus { + CallbackType type; + base::PlatformFileError result; + base::PlatformFileInfo file_info; + // This object should be deleted in the test code. + AsyncFileStream* file_stream; + int64 length; + + // Following 4 fields only for ReadDirectory. + FileUtilAsync::FileList entries; + bool completed; + bool called_after_completed; + int called; + }; + + FileUtilAsync::StatusCallback GetStatusCallback(int request_id) { + max_request_id_ = std::max(request_id, max_request_id_); + return base::Bind(&MemoryFileUtilTest::StatusCallbackImpl, + base::Unretained(this), + request_id); + } + + FileUtilAsync::GetFileInfoCallback GetGetFileInfoCallback( + int request_id) { + max_request_id_ = std::max(request_id, max_request_id_); + return base::Bind(&MemoryFileUtilTest::GetFileInfoCallback, + base::Unretained(this), + request_id); + } + + FileUtilAsync::OpenCallback GetOpenCallback(int request_id) { + max_request_id_ = std::max(request_id, max_request_id_); + return base::Bind(&MemoryFileUtilTest::OpenCallback, + base::Unretained(this), + request_id); + } + + AsyncFileStream::ReadWriteCallback GetReadWriteCallback(int request_id) { + max_request_id_ = std::max(request_id, max_request_id_); + return base::Bind(&MemoryFileUtilTest::ReadWriteCallbackImpl, + base::Unretained(this), + request_id); + } + + FileUtilAsync::ReadDirectoryCallback GetReadDirectoryCallback( + int request_id) { + if (request_id > max_request_id_) { + max_request_id_ = request_id; + } + return base::Bind(&MemoryFileUtilTest::ReadDirectoryCallback, + base::Unretained(this), + request_id); + } + + int CreateEmptyFile(const FilePath& file_path) { + int request_id = GetNextRequestId(); + file_util_->Create(file_path, GetStatusCallback(request_id)); + return request_id; + } + + int CreateNonEmptyFile(const FilePath& file_path, + const char* data, + int length) { + int request_id = GetNextRequestId(); + file_util_->Open( + file_path, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + base::Bind(&MemoryFileUtilTest::WriteToOpenedFile, + base::Unretained(this), + request_id, data, length)); + return request_id; + } + + CallbackType GetStatusType(int request_id) { + if (status_map_.find(request_id) == status_map_.end()) + return CALLBACK_TYPE_ERROR; + return status_map_[request_id].type; + } + + // Return the operation status. + CallbackStatus& GetStatus(int request_id) { + return status_map_[request_id]; + } + + int StatusQueueSize() { + return status_map_.size(); + } + + int GetNextRequestId() { + return ++max_request_id_; + } + + void set_read_directory_buffer_size(int size) { + file_util_->set_read_directory_buffer_size(size); + } + + private: + void StatusCallbackImpl(int request_id, PlatformFileError result) { + CallbackStatus status; + status.type = CALLBACK_TYPE_STATUS; + status.result = result; + status_map_[request_id] = status; + } + + void GetFileInfoCallback(int request_id, + PlatformFileError result, + const base::PlatformFileInfo& file_info) { + CallbackStatus status; + status.type = CALLBACK_TYPE_GET_FILE_INFO; + status.result = result; + status.file_info = file_info; + status_map_[request_id] = status; + } + + void OpenCallback(int request_id, + PlatformFileError result, + AsyncFileStream* file_stream) { + CallbackStatus status; + status.type = CALLBACK_TYPE_OPEN; + status.result = result; + status.file_stream = file_stream; + status_map_[request_id] = status; + } + + void ReadWriteCallbackImpl(int request_id, + PlatformFileError result, + int64 length) { + CallbackStatus status; + status.type = CALLBACK_TYPE_READ_WRITE; + status.result = result; + status.length = length; + status_map_[request_id] = status; + } + + void ReadDirectoryCallback(int request_id, + PlatformFileError result, + const FileUtilAsync::FileList& entries, + bool completed) { + if (status_map_.find(request_id) == status_map_.end()) { + CallbackStatus status; + status.type = CALLBACK_TYPE_READ_DIRECTORY; + status.called_after_completed = false; + status.result = result; + status.completed = completed; + status.called = 1; + status.entries = entries; + status_map_[request_id] = status; + } else { + CallbackStatus& status = status_map_[request_id]; + if (status.completed) + status.called_after_completed = true; + status.result = result; + status.completed = completed; + ++status.called; + status.entries.insert(status.entries.begin(), entries.begin(), + entries.end()); + } + } + + void WriteToOpenedFile(int request_id, + const char* data, + int length, + PlatformFileError result, + AsyncFileStream* stream) { + CallbackStatus status; + status.type = CALLBACK_TYPE_OPEN; + status.result = result; + status_map_[request_id] = status; + stream->Write(data, length, GetReadWriteCallback(GetNextRequestId())); + } + + scoped_ptr<MemoryFileUtil> file_util_; + std::map<int, CallbackStatus> status_map_; + int max_request_id_; + + DISALLOW_COPY_AND_ASSIGN(MemoryFileUtilTest); +}; + +TEST_F(MemoryFileUtilTest, TestCreateGetFileInfo) { + const int request_id1 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/test.txt"), + GetGetFileInfoCallback(request_id1)); + + // In case the file system is truely asynchronous, RunAllPending is not + // enough to wait for answer. In that case the thread should be blocked + // until the callback is called (ex. use Run() instead here, and call + // Quit() from callback). + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_GET_FILE_INFO, GetStatusType(request_id1)); + CallbackStatus status = GetStatus(request_id1); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status.result); + + base::Time start_create = base::Time::Now(); + + const int request_id2 = GetNextRequestId(); + file_util()->Create(FilePath("/mnt/memory/test.txt"), + GetStatusCallback(request_id2)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(CALLBACK_TYPE_STATUS, GetStatusType(request_id2)); + status = GetStatus(request_id2); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + + file_util()->GetFileInfo(FilePath("/mnt/memory/test.txt"), + GetGetFileInfoCallback(3)); + MessageLoop::current()->RunAllPending(); + + base::Time end_create = base::Time::Now(); + + ASSERT_EQ(CALLBACK_TYPE_GET_FILE_INFO, GetStatusType(3)); + status = GetStatus(3); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(0, status.file_info.size); + ASSERT_FALSE(status.file_info.is_directory); + ASSERT_FALSE(status.file_info.is_symbolic_link); + ASSERT_GE(status.file_info.last_modified, start_create); + ASSERT_LE(status.file_info.last_modified, end_create); + ASSERT_GE(status.file_info.creation_time, start_create); + ASSERT_LE(status.file_info.creation_time, end_create); +} + +TEST_F(MemoryFileUtilTest, TestReadWrite) { + // Check that the file does not exist. + + const int request_id1 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/test1.txt"), + GetGetFileInfoCallback(request_id1)); + + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_GET_FILE_INFO, GetStatusType(request_id1)); + CallbackStatus status = GetStatus(request_id1); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status.result); + + // Create & open file for writing. + + base::Time start_create = base::Time::Now(); + const int request_id2 = GetNextRequestId(); + file_util()->Open(FilePath("/mnt/memory/test1.txt"), + base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_WRITE, + GetOpenCallback(request_id2)); + MessageLoop::current()->RunAllPending(); + + base::Time end_create = base::Time::Now(); + + ASSERT_EQ(CALLBACK_TYPE_OPEN, GetStatusType(request_id2)); + status = GetStatus(request_id2); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + + AsyncFileStream* write_file_stream = status.file_stream; + + // Check that file was created and has 0 size. + + const int request_id3 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/test1.txt"), + GetGetFileInfoCallback(request_id3)); + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_GET_FILE_INFO, GetStatusType(request_id3)); + status = GetStatus(request_id3); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(0, status.file_info.size); + ASSERT_FALSE(status.file_info.is_directory); + ASSERT_FALSE(status.file_info.is_symbolic_link); + ASSERT_GE(status.file_info.last_modified, start_create); + ASSERT_LE(status.file_info.last_modified, end_create); + + // Write 10 bytes to file. + + const int request_id4 = GetNextRequestId(); + base::Time start_write = base::Time::Now(); + write_file_stream->Write(kTestString, 10, + GetReadWriteCallback(request_id4)); + MessageLoop::current()->RunAllPending(); + base::Time end_write = base::Time::Now(); + + ASSERT_EQ(CALLBACK_TYPE_READ_WRITE, GetStatusType(request_id4)); + status = GetStatus(request_id4); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(10, status.length); + + // Check that the file has now size 10 and correct modification time. + + const int request_id5 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/test1.txt"), + GetGetFileInfoCallback(request_id5)); + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_GET_FILE_INFO, GetStatusType(request_id5)); + status = GetStatus(request_id5); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(10, status.file_info.size); + ASSERT_GE(status.file_info.last_modified, start_write); + ASSERT_LE(status.file_info.last_modified, end_write); + + // Write the rest of the string to file. + + const int request_id6 = GetNextRequestId(); + start_write = base::Time::Now(); + write_file_stream->Write(kTestString + 10, + kTestStringLength - 10, + GetReadWriteCallback(request_id6)); + MessageLoop::current()->RunAllPending(); + end_write = base::Time::Now(); + + ASSERT_EQ(CALLBACK_TYPE_READ_WRITE, GetStatusType(request_id6)); + status = GetStatus(request_id6); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(static_cast<int64>(kTestStringLength) - 10, status.length); + + // Check the file size & modification time. + + const int request_id7 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/test1.txt"), + GetGetFileInfoCallback(request_id7)); + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_GET_FILE_INFO, GetStatusType(request_id7)); + status = GetStatus(request_id7); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(static_cast<int64>(kTestStringLength), status.file_info.size); + ASSERT_GE(status.file_info.last_modified, start_write); + ASSERT_LE(status.file_info.last_modified, end_write); + + // Open file for reading. + + const int request_id8 = GetNextRequestId(); + file_util()->Open(FilePath("/mnt/memory/test1.txt"), + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + GetOpenCallback(request_id8)); + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_OPEN, GetStatusType(request_id8)); + status = GetStatus(request_id8); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + + scoped_ptr<AsyncFileStream> read_file_stream(status.file_stream); + + // Read the whole file + char buffer[1024]; + const int request_id9 = GetNextRequestId(); + read_file_stream->Read(buffer, 1023, GetReadWriteCallback(request_id9)); + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_READ_WRITE, GetStatusType(request_id9)); + status = GetStatus(request_id9); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(static_cast<int64>(kTestStringLength), status.length); + + buffer[status.length] = '\0'; + std::string result_string(buffer); + ASSERT_EQ(kTestString, result_string); + + // Check that size & modification time have not changed. + + const int request_id10 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/test1.txt"), + GetGetFileInfoCallback(request_id10)); + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_GET_FILE_INFO, GetStatusType(request_id10)); + status = GetStatus(request_id10); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(static_cast<int64>(kTestStringLength), status.file_info.size); + ASSERT_GE(status.file_info.last_modified, start_write); + ASSERT_LE(status.file_info.last_modified, end_write); + + // Open once more for writing. + + const int request_id11 = GetNextRequestId(); + file_util()->Open(FilePath("/mnt/memory/test1.txt"), + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + GetOpenCallback(request_id11)); + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_OPEN, GetStatusType(request_id11)); + status = GetStatus(request_id11); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + + scoped_ptr<AsyncFileStream> write_file_stream2(status.file_stream); + + // Check that the size has not changed. + + const int request_id12 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/test1.txt"), + GetGetFileInfoCallback(request_id12)); + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_GET_FILE_INFO, GetStatusType(request_id12)); + status = GetStatus(request_id12); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(static_cast<int64>(kTestStringLength), status.file_info.size); + + // Seek beyond the end of file. Should return error. + + const int request_id13 = GetNextRequestId(); + write_file_stream2->Seek(1000, GetStatusCallback(request_id13)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(CALLBACK_TYPE_STATUS, GetStatusType(request_id13)); + status = GetStatus(request_id13); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status.result); + + // Try to write to read-only stream. + + const int request_id14 = GetNextRequestId(); + read_file_stream->Write(kTestString, + kTestStringLength, + GetReadWriteCallback(request_id14)); + MessageLoop::current()->RunAllPending(); + status = GetStatus(request_id14); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status.result); + + // Write data overlapping with already written. + + + const int request_id15 = GetNextRequestId(); + write_file_stream2->Seek(10, GetStatusCallback(request_id15)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id15).result); + + const int request_id16 = GetNextRequestId(); + write_file_stream2->Write(kTestString, + kTestStringLength, + GetReadWriteCallback(request_id16)); + MessageLoop::current()->RunAllPending(); + status = GetStatus(request_id16); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(static_cast<int64>(kTestStringLength), status.length); + + // Check size. + + const int request_id17 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/test1.txt"), + GetGetFileInfoCallback(request_id17)); + MessageLoop::current()->RunAllPending(); + status = GetStatus(request_id17); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(static_cast<int64>(kTestStringLength) + 10, + status.file_info.size); + + // Read from 10th byte. + const int request_id18 = GetNextRequestId(); + read_file_stream->Seek(10, GetStatusCallback(request_id18)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id18).result); + + const int request_id19 = GetNextRequestId(); + read_file_stream->Read(buffer, 1023, GetReadWriteCallback(request_id19)); + MessageLoop::current()->RunAllPending(); + status = GetStatus(request_id19); + ASSERT_EQ(base::PLATFORM_FILE_OK, status.result); + ASSERT_EQ(static_cast<int64>(kTestStringLength), status.length); + buffer[status.length] = '\0'; + std::string result_string2(buffer); + ASSERT_EQ(kTestString, result_string2); +} + +// The directory structure we'll be testing on: +// path size +// +// /mnt/memory/a 0 +// /mnt/memory/b/ +// /mnt/memory/b/c kTestStringLength +// /mnt/memory/b/d/ +// /mnt/memory/b/e 0 +// /mnt/memory/b/f kTestStringLength +// /mnt/memory/b/g/ +// /mnt/memory/b/g/h 0 +// /mnt/memory/b/i kTestStringLength +// /mnt/memory/c/ +// /mnt/memory/longer_file_name.txt kTestStringLength +TEST_F(MemoryFileUtilTest, TestDirectoryOperations) { + // Check the directory is empty. + const int request_id0 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory/"), + GetReadDirectoryCallback(request_id0)); + + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(CALLBACK_TYPE_READ_DIRECTORY, GetStatusType(request_id0)); + CallbackStatus& status = GetStatus(request_id0); + ASSERT_TRUE(status.completed); + ASSERT_FALSE(status.called_after_completed); + ASSERT_EQ(1, status.called); + ASSERT_EQ(0u, status.entries.size()); + + // Create /mnt/memory/a, /mnt/memory/b/, /mnt/memory/longer_file_name.txt, + // /mnt/memory/c/ asyncronously (i.e. we do not wait for each operation to + // complete before starting the next one. + + base::Time start_create = base::Time::Now(); + CreateEmptyFile(FilePath("/mnt/memory/a")); + + int request_id1 = GetNextRequestId(); + file_util()->CreateDirectory(FilePath("/mnt/memory/b"), + GetStatusCallback(request_id1)); + + CreateNonEmptyFile(FilePath("/mnt/memory/longer_file_name.txt"), + kTestString, + kTestStringLength); + + int request_id2 = GetNextRequestId(); + file_util()->CreateDirectory(FilePath("/mnt/memory/c"), + GetStatusCallback(request_id2)); + + MessageLoop::current()->RunAllPending(); + base::Time end_create = base::Time::Now(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id2).result); + + // ReadDirectory /mnt/memory, /mnt/memory/a (not a dir), /mnt/memory/b/, + // /mnt/memory/d (not found) + + set_read_directory_buffer_size(5); // Should complete in one go. + + request_id1 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory"), + GetReadDirectoryCallback(request_id1)); + request_id2 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory/a"), + GetReadDirectoryCallback(request_id2)); + const int request_id3 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory/b/"), + GetReadDirectoryCallback(request_id3)); + const int request_id4 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory/d/"), + GetReadDirectoryCallback(request_id4)); + + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + status = GetStatus(request_id1); + ASSERT_TRUE(status.completed); + ASSERT_FALSE(status.called_after_completed); + ASSERT_EQ(1, status.called); // Because the number of entries < 5. + ASSERT_EQ(4u, status.entries.size()); + + std::set<FilePath::StringType> seen; + + for (FileUtilAsync::FileList::const_iterator it = status.entries.begin(); + it != status.entries.end(); + ++it) { + ASSERT_LE(start_create, it->last_modified_time); + ASSERT_GE(end_create, it->last_modified_time); + + ASSERT_EQ(seen.end(), seen.find(it->name)); + seen.insert(it->name); + + if (it->name == "a") { + ASSERT_FALSE(it->is_directory); + ASSERT_EQ(0, it->size); + } else if (it->name == "b") { + ASSERT_TRUE(it->is_directory); + } else if (it->name == "c") { + ASSERT_TRUE(it->is_directory); + } else if (it->name == "longer_file_name.txt") { + ASSERT_FALSE(it->is_directory); + ASSERT_EQ(static_cast<int64>(kTestStringLength), it->size); + } else { + LOG(ERROR) << "Unexpected file: " << it->name; + ASSERT_TRUE(false); + } + } + + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, + GetStatus(request_id2).result); + + status = GetStatus(request_id3); + ASSERT_TRUE(status.completed); + ASSERT_FALSE(status.called_after_completed); + ASSERT_EQ(1, status.called); + ASSERT_EQ(0u, status.entries.size()); + + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, GetStatus(request_id4).result); + + // Create files & dirs inside b/: + // /mnt/memory/b/c kTestStringLength + // /mnt/memory/b/d/ + // /mnt/memory/b/e 0 + // /mnt/memory/b/f kTestStringLength + // /mnt/memory/b/g/ + // /mnt/memory/b/i kTestStringLength + // + // /mnt/memory/b/g/h 0 + + start_create = base::Time::Now(); + CreateNonEmptyFile(FilePath("/mnt/memory/b/c"), + kTestString, + kTestStringLength); + request_id1 = GetNextRequestId(); + file_util()->CreateDirectory(FilePath("/mnt/memory/b/d"), + GetStatusCallback(request_id1)); + CreateEmptyFile(FilePath("/mnt/memory/b/e")); + CreateNonEmptyFile(FilePath("/mnt/memory/b/f"), + kTestString, + kTestStringLength); + request_id2 = GetNextRequestId(); + file_util()->CreateDirectory(FilePath("/mnt/memory/b/g"), + GetStatusCallback(request_id1)); + CreateNonEmptyFile(FilePath("/mnt/memory/b/i"), + kTestString, + kTestStringLength); + + MessageLoop::current()->RunAllPending(); + + CreateEmptyFile(FilePath("/mnt/memory/b/g/h")); + + MessageLoop::current()->RunAllPending(); + end_create = base::Time::Now(); + + // Read /mnt/memory and check that the number of entries is unchanged. + + request_id1 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory"), + GetReadDirectoryCallback(request_id1)); + + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + status = GetStatus(request_id1); + ASSERT_TRUE(status.completed); + ASSERT_FALSE(status.called_after_completed); + ASSERT_EQ(1, status.called); // Because the number of entries < 5. + ASSERT_EQ(4u, status.entries.size()); + + // Read /mnt/memory/b + + request_id1 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory/b"), + GetReadDirectoryCallback(request_id1)); + + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + status = GetStatus(request_id1); + ASSERT_TRUE(status.completed); + ASSERT_FALSE(status.called_after_completed); + ASSERT_EQ(2, status.called); // Because the number of entries > 5. + ASSERT_EQ(6u, status.entries.size()); + + seen.clear(); + + for (FileUtilAsync::FileList::const_iterator it = status.entries.begin(); + it != status.entries.end(); + ++it) { + ASSERT_LE(start_create, it->last_modified_time); + ASSERT_GE(end_create, it->last_modified_time); + + ASSERT_EQ(seen.end(), seen.find(it->name)); + seen.insert(it->name); + + // /mnt/memory/b/c kTestStringLength + // /mnt/memory/b/d/ + // /mnt/memory/b/e 0 + // /mnt/memory/b/f kTestStringLength + // /mnt/memory/b/g/ + // /mnt/memory/b/i kTestStringLength + + if (it->name == "c") { + ASSERT_FALSE(it->is_directory); + ASSERT_EQ(static_cast<int64>(kTestStringLength), it->size); + } else if (it->name == "d") { + ASSERT_TRUE(it->is_directory); + } else if (it->name == "e") { + ASSERT_FALSE(it->is_directory); + ASSERT_EQ(0, it->size); + } else if (it->name == "f") { + ASSERT_FALSE(it->is_directory); + ASSERT_EQ(static_cast<int64>(kTestStringLength), it->size); + } else if (it->name == "g") { + ASSERT_TRUE(it->is_directory); + } else if (it->name == "i") { + ASSERT_FALSE(it->is_directory); + ASSERT_EQ(static_cast<int64>(kTestStringLength), it->size); + } else { + LOG(ERROR) << "Unexpected file: " << it->name; + ASSERT_TRUE(false); + } + } + + // Remove single file: /mnt/memory/b/f + + request_id1 = GetNextRequestId(); + file_util()->Remove(FilePath("/mnt/memory/b/f"), false /* recursive */, + GetStatusCallback(request_id1)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + + // Check the number of files in b/ + + request_id1 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory/b"), + GetReadDirectoryCallback(request_id1)); + + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + status = GetStatus(request_id1); + ASSERT_TRUE(status.completed); + ASSERT_FALSE(status.called_after_completed); + ASSERT_EQ(5u, status.entries.size()); + + // Try remove /mnt/memory/b non-recursively (error) + + request_id1 = GetNextRequestId(); + file_util()->Remove(FilePath("/mnt/memory/b"), false /* recursive */, + GetStatusCallback(request_id1)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE, + GetStatus(request_id1).result); + + // Non-recursively remove empty directory. + + request_id1 = GetNextRequestId(); + file_util()->Remove(FilePath("/mnt/memory/b/d"), false /* recursive */, + GetStatusCallback(request_id1)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + + request_id1 = GetNextRequestId(); + file_util()->GetFileInfo(FilePath("/mnt/memory/b/d"), + GetGetFileInfoCallback(request_id1)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, GetStatus(request_id1).result); + + // Remove /mnt/memory/b recursively. + + request_id1 = GetNextRequestId(); + file_util()->Remove(FilePath("/mnt/memory/b"), true /* recursive */, + GetStatusCallback(request_id1)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + + // ReadDirectory /mnt/memory/b -> not found + + request_id1 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory/b"), + GetReadDirectoryCallback(request_id1)); + MessageLoop::current()->RunAllPending(); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, GetStatus(request_id1).result); + + // ReadDirectory /mnt/memory + + request_id1 = GetNextRequestId(); + file_util()->ReadDirectory(FilePath("/mnt/memory"), + GetReadDirectoryCallback(request_id1)); + + MessageLoop::current()->RunAllPending(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, GetStatus(request_id1).result); + status = GetStatus(request_id1); + ASSERT_TRUE(status.completed); + ASSERT_FALSE(status.called_after_completed); + ASSERT_EQ(3u, status.entries.size()); + + seen.clear(); + + for (FileUtilAsync::FileList::const_iterator it = status.entries.begin(); + it != status.entries.end(); + ++it) { + ASSERT_EQ(seen.end(), seen.find(it->name)); + seen.insert(it->name); + + if (it->name == "a") { + ASSERT_FALSE(it->is_directory); + ASSERT_EQ(0, it->size); + } else if (it->name == "c") { + ASSERT_TRUE(it->is_directory); + } else if (it->name == "longer_file_name.txt") { + ASSERT_FALSE(it->is_directory); + ASSERT_EQ(static_cast<int64>(kTestStringLength), it->size); + } else { + LOG(ERROR) << "Unexpected file: " << it->name; + ASSERT_TRUE(false); + } + } +} + +} // namespace fileapi |