| // Copyright (c) 2012 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 "webkit/fileapi/file_util_helper.h" |
| |
| #include <stack> |
| |
| #include "webkit/fileapi/file_system_file_util.h" |
| #include "webkit/fileapi/file_system_operation_context.h" |
| #include "webkit/fileapi/file_system_url.h" |
| |
| using base::PlatformFileError; |
| |
| namespace fileapi { |
| |
| namespace { |
| |
| // A helper class to delete a temporary file. |
| class ScopedFileDeleter { |
| public: |
| explicit ScopedFileDeleter(const FilePath& path) : path_(path) {} |
| ~ScopedFileDeleter() { |
| file_util::Delete(path_, false /* recursive */); |
| } |
| |
| private: |
| FilePath path_; |
| }; |
| |
| // A helper class for cross-FileUtil Copy/Move operations. |
| class CrossFileUtilHelper { |
| public: |
| enum Operation { |
| OPERATION_COPY, |
| OPERATION_MOVE |
| }; |
| |
| CrossFileUtilHelper(FileSystemOperationContext* context, |
| FileSystemFileUtil* src_util, |
| FileSystemFileUtil* dest_util, |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url, |
| Operation operation); |
| ~CrossFileUtilHelper(); |
| |
| base::PlatformFileError DoWork(); |
| |
| private: |
| // Performs common pre-operation check and preparation. |
| // This may delete the destination directory if it's empty. |
| base::PlatformFileError PerformErrorCheckAndPreparation(); |
| |
| // This assumes that the root exists. |
| bool ParentExists(const FileSystemURL& url, FileSystemFileUtil* file_util); |
| |
| // Performs recursive copy or move by calling CopyOrMoveFile for individual |
| // files. Operations for recursive traversal are encapsulated in this method. |
| // It assumes src_url and dest_url have passed |
| // PerformErrorCheckAndPreparationForMoveAndCopy(). |
| base::PlatformFileError CopyOrMoveDirectory( |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url); |
| |
| // Determines whether a simple same-filesystem move or copy can be done. If |
| // so, it delegates to CopyOrMoveFile. Otherwise it looks up the true |
| // platform path of the source file, delegates to CopyInForeignFile, and [for |
| // move] calls DeleteFile on the source file. |
| base::PlatformFileError CopyOrMoveFile( |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url); |
| |
| FileSystemOperationContext* context_; |
| FileSystemFileUtil* src_util_; // Not owned. |
| FileSystemFileUtil* dest_util_; // Not owned. |
| const FileSystemURL& src_root_url_; |
| const FileSystemURL& dest_root_url_; |
| Operation operation_; |
| bool same_file_system_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrossFileUtilHelper); |
| }; |
| |
| CrossFileUtilHelper::CrossFileUtilHelper( |
| FileSystemOperationContext* context, |
| FileSystemFileUtil* src_util, |
| FileSystemFileUtil* dest_util, |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url, |
| Operation operation) |
| : context_(context), |
| src_util_(src_util), |
| dest_util_(dest_util), |
| src_root_url_(src_url), |
| dest_root_url_(dest_url), |
| operation_(operation) { |
| same_file_system_ = |
| src_root_url_.origin() == dest_root_url_.origin() && |
| src_root_url_.type() == dest_root_url_.type(); |
| } |
| |
| CrossFileUtilHelper::~CrossFileUtilHelper() {} |
| |
| base::PlatformFileError CrossFileUtilHelper::DoWork() { |
| base::PlatformFileError error = PerformErrorCheckAndPreparation(); |
| if (error != base::PLATFORM_FILE_OK) |
| return error; |
| if (FileUtilHelper::DirectoryExists(context_, src_util_, src_root_url_)) |
| return CopyOrMoveDirectory(src_root_url_, dest_root_url_); |
| return CopyOrMoveFile(src_root_url_, dest_root_url_); |
| } |
| |
| PlatformFileError CrossFileUtilHelper::PerformErrorCheckAndPreparation() { |
| // Exits earlier if the source path does not exist. |
| if (!FileUtilHelper::PathExists(context_, src_util_, src_root_url_)) |
| return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| |
| // The parent of the |dest_root_url_| does not exist. |
| if (!ParentExists(dest_root_url_, dest_util_)) |
| return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| |
| // It is an error to try to copy/move an entry into its child. |
| if (same_file_system_ && src_root_url_.path().IsParent(dest_root_url_.path())) |
| return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; |
| |
| // Now it is ok to return if the |dest_root_url_| does not exist. |
| if (!FileUtilHelper::PathExists(context_, dest_util_, dest_root_url_)) |
| return base::PLATFORM_FILE_OK; |
| |
| // |src_root_url_| exists and is a directory. |
| // |dest_root_url_| exists and is a file. |
| bool src_is_directory = |
| FileUtilHelper::DirectoryExists(context_, src_util_, src_root_url_); |
| bool dest_is_directory = |
| FileUtilHelper::DirectoryExists(context_, dest_util_, dest_root_url_); |
| |
| // Either one of |src_root_url_| or |dest_root_url_| is directory, |
| // while the other is not. |
| if (src_is_directory != dest_is_directory) |
| return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; |
| |
| // It is an error to copy/move an entry into the same path. |
| if (same_file_system_ && |
| src_root_url_.path() == dest_root_url_.path()) |
| return base::PLATFORM_FILE_ERROR_EXISTS; |
| |
| if (dest_is_directory) { |
| // It is an error to copy/move an entry to a non-empty directory. |
| // Otherwise the copy/move attempt must overwrite the destination, but |
| // the file_util's Copy or Move method doesn't perform overwrite |
| // on all platforms, so we delete the destination directory here. |
| if (base::PLATFORM_FILE_OK != |
| dest_util_->DeleteSingleDirectory(context_, dest_root_url_)) { |
| if (!dest_util_->IsDirectoryEmpty(context_, dest_root_url_)) |
| return base::PLATFORM_FILE_ERROR_NOT_EMPTY; |
| return base::PLATFORM_FILE_ERROR_FAILED; |
| } |
| } |
| return base::PLATFORM_FILE_OK; |
| } |
| |
| bool CrossFileUtilHelper::ParentExists( |
| const FileSystemURL& url, FileSystemFileUtil* file_util) { |
| // If path is in the root, path.DirName() will be ".", |
| // since we use paths with no leading '/'. |
| FilePath parent = url.path().DirName(); |
| if (parent == FilePath(FILE_PATH_LITERAL("."))) |
| return true; |
| return FileUtilHelper::DirectoryExists(context_, file_util, |
| url.WithPath(parent)); |
| } |
| |
| PlatformFileError CrossFileUtilHelper::CopyOrMoveDirectory( |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url) { |
| // At this point we must have gone through |
| // PerformErrorCheckAndPreparationForMoveAndCopy so this must be true. |
| DCHECK(!same_file_system_ || |
| !src_url.path().IsParent(dest_url.path())); |
| |
| PlatformFileError error = dest_util_->CreateDirectory( |
| context_, dest_url, false /* exclusive */, false /* recursive */); |
| if (error != base::PLATFORM_FILE_OK) |
| return error; |
| |
| typedef std::pair<FileSystemURL, base::Time> MovedDirectoryPair; |
| typedef std::vector<MovedDirectoryPair> MovedDirectories; |
| MovedDirectories directories; |
| |
| // Store modified timestamp of the root directory. |
| if (operation_ == OPERATION_MOVE) { |
| base::PlatformFileInfo file_info; |
| FilePath platform_file_path; |
| error = src_util_->GetFileInfo( |
| context_, src_url, &file_info, &platform_file_path); |
| if (error != base::PLATFORM_FILE_OK) |
| return error; |
| directories.push_back( |
| std::make_pair(dest_url, file_info.last_modified)); |
| } |
| |
| scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum( |
| src_util_->CreateFileEnumerator(context_, |
| src_url, |
| true /* recursive */)); |
| FilePath src_file_path_each; |
| while (!(src_file_path_each = file_enum->Next()).empty()) { |
| FilePath dest_file_path_each(dest_url.path()); |
| src_url.path().AppendRelativePath( |
| src_file_path_each, &dest_file_path_each); |
| |
| if (file_enum->IsDirectory()) { |
| error = dest_util_->CreateDirectory( |
| context_, |
| dest_url.WithPath(dest_file_path_each), |
| true /* exclusive */, false /* recursive */); |
| if (error != base::PLATFORM_FILE_OK) |
| return error; |
| |
| directories.push_back(std::make_pair( |
| dest_url.WithPath(dest_file_path_each), |
| file_enum->LastModifiedTime())); |
| } else { |
| error = CopyOrMoveFile(src_url.WithPath(src_file_path_each), |
| dest_url.WithPath(dest_file_path_each)); |
| if (error != base::PLATFORM_FILE_OK) |
| return error; |
| } |
| } |
| |
| if (operation_ == OPERATION_MOVE) { |
| #ifndef OS_WIN |
| // TODO(nhiroki): Support Windows (http://crbug.com/137807). |
| // Restore modified timestamp of destination directories. |
| for (MovedDirectories::const_iterator it(directories.begin()); |
| it != directories.end(); ++it) { |
| error = dest_util_->Touch(context_, it->first, it->second, it->second); |
| if (error != base::PLATFORM_FILE_OK) |
| return error; |
| } |
| #endif |
| |
| error = FileUtilHelper::Delete( |
| context_, src_util_, src_url, true /* recursive */); |
| if (error != base::PLATFORM_FILE_OK) |
| return error; |
| } |
| |
| return base::PLATFORM_FILE_OK; |
| } |
| |
| PlatformFileError CrossFileUtilHelper::CopyOrMoveFile( |
| const FileSystemURL& src_url, |
| const FileSystemURL& dest_url) { |
| if (same_file_system_) { |
| DCHECK(src_util_ == dest_util_); |
| // Source and destination are in the same FileSystemFileUtil; now we can |
| // safely call FileSystemFileUtil method on src_util_ (== dest_util_). |
| return src_util_->CopyOrMoveFile(context_, src_url, dest_url, |
| operation_ == OPERATION_COPY); |
| } |
| |
| // Resolve the src_url's underlying file path. |
| base::PlatformFileInfo file_info; |
| FilePath platform_file_path; |
| FileSystemFileUtil::SnapshotFilePolicy snapshot_policy; |
| |
| PlatformFileError error = src_util_->CreateSnapshotFile( |
| context_, src_url, &file_info, &platform_file_path, &snapshot_policy); |
| if (error != base::PLATFORM_FILE_OK) |
| return error; |
| |
| scoped_ptr<ScopedFileDeleter> file_deleter; |
| if (snapshot_policy == FileSystemFileUtil::kSnapshotFileTemporary) |
| file_deleter.reset(new ScopedFileDeleter(platform_file_path)); |
| |
| // Call CopyInForeignFile() on the dest_util_ with the resolved source path |
| // to perform limited cross-FileSystemFileUtil copy/move. |
| error = dest_util_->CopyInForeignFile( |
| context_, platform_file_path, dest_url); |
| |
| if (operation_ == OPERATION_COPY || error != base::PLATFORM_FILE_OK) |
| return error; |
| return src_util_->DeleteFile(context_, src_url); |
| } |
| |
| } // anonymous namespace |
| |
| // static |
| bool FileUtilHelper::PathExists(FileSystemOperationContext* context, |
| FileSystemFileUtil* file_util, |
| const FileSystemURL& url) { |
| base::PlatformFileInfo file_info; |
| FilePath platform_path; |
| PlatformFileError error = file_util->GetFileInfo( |
| context, url, &file_info, &platform_path); |
| return error == base::PLATFORM_FILE_OK; |
| } |
| |
| // static |
| bool FileUtilHelper::DirectoryExists(FileSystemOperationContext* context, |
| FileSystemFileUtil* file_util, |
| const FileSystemURL& url) { |
| if (url.path().empty()) |
| return true; |
| |
| base::PlatformFileInfo file_info; |
| FilePath platform_path; |
| PlatformFileError error = file_util->GetFileInfo( |
| context, url, &file_info, &platform_path); |
| return error == base::PLATFORM_FILE_OK && file_info.is_directory; |
| } |
| |
| // static |
| base::PlatformFileError FileUtilHelper::Copy( |
| FileSystemOperationContext* context, |
| FileSystemFileUtil* src_file_util, |
| FileSystemFileUtil* dest_file_util, |
| const FileSystemURL& src_root_url, |
| const FileSystemURL& dest_root_url) { |
| return CrossFileUtilHelper(context, src_file_util, dest_file_util, |
| src_root_url, dest_root_url, |
| CrossFileUtilHelper::OPERATION_COPY).DoWork(); |
| } |
| |
| // static |
| base::PlatformFileError FileUtilHelper::Move( |
| FileSystemOperationContext* context, |
| FileSystemFileUtil* src_file_util, |
| FileSystemFileUtil* dest_file_util, |
| const FileSystemURL& src_root_url, |
| const FileSystemURL& dest_root_url) { |
| return CrossFileUtilHelper(context, src_file_util, dest_file_util, |
| src_root_url, dest_root_url, |
| CrossFileUtilHelper::OPERATION_MOVE).DoWork(); |
| } |
| |
| // static |
| base::PlatformFileError FileUtilHelper::Delete( |
| FileSystemOperationContext* context, |
| FileSystemFileUtil* file_util, |
| const FileSystemURL& url, |
| bool recursive) { |
| if (DirectoryExists(context, file_util, url)) { |
| if (!recursive) |
| return file_util->DeleteSingleDirectory(context, url); |
| else |
| return DeleteDirectoryRecursive(context, file_util, url); |
| } else { |
| return file_util->DeleteFile(context, url); |
| } |
| } |
| |
| // static |
| base::PlatformFileError FileUtilHelper::ReadDirectory( |
| FileSystemOperationContext* context, |
| FileSystemFileUtil* file_util, |
| const FileSystemURL& url, |
| std::vector<base::FileUtilProxy::Entry>* entries) { |
| DCHECK(entries); |
| |
| // TODO(kkanetkar): Implement directory read in multiple chunks. |
| if (!DirectoryExists(context, file_util, url)) |
| return base::PLATFORM_FILE_ERROR_NOT_FOUND; |
| |
| scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum( |
| file_util->CreateFileEnumerator(context, url, false /* recursive */)); |
| |
| FilePath current; |
| while (!(current = file_enum->Next()).empty()) { |
| base::FileUtilProxy::Entry entry; |
| entry.is_directory = file_enum->IsDirectory(); |
| entry.name = current.BaseName().value(); |
| entry.size = file_enum->Size(); |
| entry.last_modified_time = file_enum->LastModifiedTime(); |
| entries->push_back(entry); |
| } |
| return base::PLATFORM_FILE_OK; |
| } |
| |
| // static |
| base::PlatformFileError FileUtilHelper::DeleteDirectoryRecursive( |
| FileSystemOperationContext* context, |
| FileSystemFileUtil* file_util, |
| const FileSystemURL& url) { |
| |
| scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum( |
| file_util->CreateFileEnumerator(context, url, true /* recursive */)); |
| FilePath file_path_each; |
| std::stack<FilePath> directories; |
| while (!(file_path_each = file_enum->Next()).empty()) { |
| if (file_enum->IsDirectory()) { |
| directories.push(file_path_each); |
| } else { |
| PlatformFileError error = file_util->DeleteFile( |
| context, url.WithPath(file_path_each)); |
| if (error != base::PLATFORM_FILE_ERROR_NOT_FOUND && |
| error != base::PLATFORM_FILE_OK) |
| return error; |
| } |
| } |
| |
| while (!directories.empty()) { |
| PlatformFileError error = file_util->DeleteSingleDirectory( |
| context, url.WithPath(directories.top())); |
| if (error != base::PLATFORM_FILE_ERROR_NOT_FOUND && |
| error != base::PLATFORM_FILE_OK) |
| return error; |
| directories.pop(); |
| } |
| return file_util->DeleteSingleDirectory(context, url); |
| } |
| |
| } // namespace fileapi |