// 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.

#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_
#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_

#include <vector>

#include "base/callback.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/process/process.h"
#include "storage/browser/fileapi/file_system_operation_context.h"
#include "storage/browser/storage_browser_export.h"
#include "storage/common/fileapi/directory_entry.h"

namespace base {
class Time;
}

namespace net {
class URLRequest;
}

namespace storage {
class ShareableFileReference;
}

class GURL;

namespace storage {

class FileSystemContext;
class FileSystemURL;
class FileWriterDelegate;

// The interface class for FileSystemOperation implementations.
//
// This interface defines file system operations required to implement
// "File API: Directories and System"
// http://www.w3.org/TR/file-system-api/
//
// DESIGN NOTES
//
// This class is designed to
//
// 1) Serve one-time file system operation per instance.  Only one
// method(CreateFile, CreateDirectory, Copy, Move, DirectoryExists,
// GetMetadata, ReadDirectory and Remove) may be called during the
// lifetime of this object and it should be called no more than once.
//
// 2) Deliver the results of operations to the client via the callback function
// passed as the last parameter of the method.
//
// Note that it is valid to delete an operation while it is running.
// The callback will NOT be fired if the operation is deleted before
// it gets called.
class FileSystemOperation {
 public:
  STORAGE_EXPORT static FileSystemOperation* Create(
      const FileSystemURL& url,
      FileSystemContext* file_system_context,
      scoped_ptr<FileSystemOperationContext> operation_context);

  virtual ~FileSystemOperation() {}

  // Used for CreateFile(), etc. |result| is the return code of the operation.
  typedef base::Callback<void(base::File::Error result)> StatusCallback;

  // Used for GetMetadata(). |result| is the return code of the operation,
  // |file_info| is the obtained file info.
  typedef base::Callback<
      void(base::File::Error result,
           const base::File::Info& file_info)> GetMetadataCallback;

  // Used for OpenFile(). |on_close_callback| will be called after the file is
  // closed in the child process. It can be null, if no operation is needed on
  // closing a file.
  typedef base::Callback<
      void(base::File file,
           const base::Closure& on_close_callback)> OpenFileCallback;

  // Used for ReadDirectoryCallback.
  typedef std::vector<DirectoryEntry> FileEntryList;

  // Used for ReadDirectory(). |result| is the return code of the operation,
  // |file_list| is the list of files read, and |has_more| is true if some files
  // are yet to be read.
  typedef base::Callback<
      void(base::File::Error result,
           const FileEntryList& file_list,
           bool has_more)> ReadDirectoryCallback;

  // Used for CreateSnapshotFile(). (Please see the comment at
  // CreateSnapshotFile() below for how the method is called)
  // |result| is the return code of the operation.
  // |file_info| is the metadata of the snapshot file created.
  // |platform_path| is the path to the snapshot file created.
  //
  // The snapshot file could simply be of the local file pointed by the given
  // filesystem URL in local filesystem cases; remote filesystems
  // may want to download the file into a temporary snapshot file and then
  // return the metadata of the temporary file.
  //
  // |file_ref| is used to manage the lifetime of the returned
  // snapshot file.  It can be set to let the chromium backend take
  // care of the life time of the snapshot file.  Otherwise (if the returned
  // file does not require any handling) the implementation can just
  // return NULL.  In a more complex case, the implementaiton can manage
  // the lifetime of the snapshot file on its own (e.g. by its cache system)
  // but also can be notified via the reference when the file becomes no
  // longer necessary in the javascript world.
  // Please see the comment for ShareableFileReference for details.
  //
  typedef base::Callback<
      void(base::File::Error result,
           const base::File::Info& file_info,
           const base::FilePath& platform_path,
           const scoped_refptr<storage::ShareableFileReference>& file_ref)>
      SnapshotFileCallback;

  // Used for progress update callback for Copy().
  //
  // BEGIN_COPY_ENTRY is fired for each copy creation beginning (for both
  // file and directory).
  // The |source_url| is the URL of the source entry. |size| should not be
  // used.
  //
  // END_COPY_ENTRY is fired for each copy creation finishing (for both
  // file and directory).
  // The |source_url| is the URL of the source entry. The |destination_url| is
  // the URL of the destination entry. |size| should not be used.
  //
  // PROGRESS is fired periodically during file copying (not fired for
  // directory copy).
  // The |source_url| is the URL of the source file. |size| is the number
  // of cumulative copied bytes for the currently copied file.
  // Both at beginning and ending of file copying, PROGRESS event should be
  // called. At beginning, |size| should be 0. At ending, |size| should be
  // the size of the file.
  //
  // Here is an example callback sequence of recursive copy. Suppose
  // there are a/b/c.txt (100 bytes) and a/b/d.txt (200 bytes), and trying to
  // copy a to x recursively, then the progress update sequence will be:
  //
  // BEGIN_COPY_ENTRY a  (starting create "a" directory in x/).
  // END_COPY_ENTRY a x/a (creating "a" directory in x/ is finished).
  //
  // BEGIN_COPY_ENTRY a/b (starting create "b" directory in x/a).
  // END_COPY_ENTRY a/b x/a/b (creating "b" directory in x/a/ is finished).
  //
  // BEGIN_COPY_ENTRY a/b/c.txt (starting to copy "c.txt" in x/a/b/).
  // PROGRESS a/b/c.txt 0 (The first PROGRESS's |size| should be 0).
  // PROGRESS a/b/c.txt 10
  //    :
  // PROGRESS a/b/c.txt 90
  // PROGRESS a/b/c.txt 100 (The last PROGRESS's |size| should be the size of
  //                         the file).
  // END_COPY_ENTRY a/b/c.txt x/a/b/c.txt (copying "c.txt" is finished).
  //
  // BEGIN_COPY_ENTRY a/b/d.txt (starting to copy "d.txt" in x/a/b).
  // PROGRESS a/b/d.txt 0 (The first PROGRESS's |size| should be 0).
  // PROGRESS a/b/d.txt 10
  //    :
  // PROGRESS a/b/d.txt 190
  // PROGRESS a/b/d.txt 200 (The last PROGRESS's |size| should be the size of
  //                         the file).
  // END_COPY_ENTRY a/b/d.txt x/a/b/d.txt (copy "d.txt" is finished).
  //
  // Note that event sequence of a/b/c.txt and a/b/d.txt can be interlaced,
  // because they can be done in parallel. Also PROGRESS events are optional,
  // so they may not be appeared.
  // All the progress callback invocation should be done before StatusCallback
  // given to the Copy is called. Especially if an error is found before first
  // progres callback invocation, the progress callback may NOT invoked for the
  // copy.
  //
  // Note for future extension. Currently this callback is only supported on
  // Copy(). We can extend this to Move(), because Move() is sometimes
  // implemented as "copy then delete."
  // In more precise, Move() usually can be implemented either 1) by updating
  // the metadata of resource (e.g. root of moving directory tree), or 2) by
  // copying directory tree and them removing the source tree.
  // For 1)'s case, we can simply add BEGIN_MOVE_ENTRY and END_MOVE_ENTRY
  // for root directory.
  // For 2)'s case, we can add BEGIN_DELETE_ENTRY and END_DELETE_ENTRY for each
  // entry.
  // For both cases, we probably won't need to use PROGRESS event because
  // these operations should be done quickly (at least much faster than copying
  // usually).
  enum CopyProgressType {
    BEGIN_COPY_ENTRY,
    END_COPY_ENTRY,
    PROGRESS,
  };
  typedef base::Callback<void(CopyProgressType type,
                              const FileSystemURL& source_url,
                              const FileSystemURL& destination_url,
                              int64 size)>
      CopyProgressCallback;

  // Used for CopyFileLocal() to report progress update.
  // |size| is the cumulative copied bytes for the copy.
  // At the beginning the progress callback should be called with |size| = 0,
  // and also at the ending the progress callback should be called with |size|
  // set to the copied file size.
  typedef base::Callback<void(int64 size)> CopyFileProgressCallback;

  // The option for copy or move operation.
  enum CopyOrMoveOption {
    // No additional operation.
    OPTION_NONE,

    // Preserves last modified time if possible. If the operation to update
    // last modified time is not supported on the file system for the
    // destination file, this option would be simply ignored (i.e. Copy would
    // be successfully done without preserving last modified time).
    OPTION_PRESERVE_LAST_MODIFIED,
  };

  // Used for Write().
  typedef base::Callback<void(base::File::Error result,
                              int64 bytes,
                              bool complete)> WriteCallback;

  // Creates a file at |path|. If |exclusive| is true, an error is raised
  // in case a file is already present at the URL.
  virtual void CreateFile(const FileSystemURL& path,
                          bool exclusive,
                          const StatusCallback& callback) = 0;

  // Creates a directory at |path|. If |exclusive| is true, an error is
  // raised in case a directory is already present at the URL. If
  // |recursive| is true, create parent directories as needed just like
  // mkdir -p does.
  virtual void CreateDirectory(const FileSystemURL& path,
                               bool exclusive,
                               bool recursive,
                               const StatusCallback& callback) = 0;

  // Copies a file or directory from |src_path| to |dest_path|. If
  // |src_path| is a directory, the contents of |src_path| are copied to
  // |dest_path| recursively. A new file or directory is created at
  // |dest_path| as needed.
  // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's
  // comment for details.
  // |progress_callback| is periodically called to report the progress
  // update. See also the comment of CopyProgressCallback. This callback is
  // optional.
  //
  // For recursive case this internally creates new FileSystemOperations and
  // calls:
  // - ReadDirectory, CopyFileLocal and CreateDirectory
  //   for same-filesystem case, or
  // - ReadDirectory and CreateSnapshotFile on source filesystem and
  //   CopyInForeignFile and CreateDirectory on dest filesystem
  //   for cross-filesystem case.
  //
  virtual void Copy(const FileSystemURL& src_path,
                    const FileSystemURL& dest_path,
                    CopyOrMoveOption option,
                    const CopyProgressCallback& progress_callback,
                    const StatusCallback& callback) = 0;

  // Moves a file or directory from |src_path| to |dest_path|. A new file
  // or directory is created at |dest_path| as needed.
  // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's
  // comment for details.
  //
  // For recursive case this internally creates new FileSystemOperations and
  // calls:
  // - ReadDirectory, MoveFileLocal, CreateDirectory and Remove
  //   for same-filesystem case, or
  // - ReadDirectory, CreateSnapshotFile and Remove on source filesystem and
  //   CopyInForeignFile and CreateDirectory on dest filesystem
  //   for cross-filesystem case.
  //
  virtual void Move(const FileSystemURL& src_path,
                    const FileSystemURL& dest_path,
                    CopyOrMoveOption option,
                    const StatusCallback& callback) = 0;

  // Checks if a directory is present at |path|.
  virtual void DirectoryExists(const FileSystemURL& path,
                               const StatusCallback& callback) = 0;

  // Checks if a file is present at |path|.
  virtual void FileExists(const FileSystemURL& path,
                          const StatusCallback& callback) = 0;

  // Gets the metadata of a file or directory at |path|.
  virtual void GetMetadata(const FileSystemURL& path,
                           const GetMetadataCallback& callback) = 0;

  // Reads contents of a directory at |path|.
  virtual void ReadDirectory(const FileSystemURL& path,
                             const ReadDirectoryCallback& callback) = 0;

  // Removes a file or directory at |path|. If |recursive| is true, remove
  // all files and directories under the directory at |path| recursively.
  virtual void Remove(const FileSystemURL& path, bool recursive,
                      const StatusCallback& callback) = 0;

  // Writes the data read from |blob_request| using |writer_delegate|.
  virtual void Write(
    const FileSystemURL& url,
    scoped_ptr<FileWriterDelegate> writer_delegate,
    scoped_ptr<net::URLRequest> blob_request,
    const WriteCallback& callback) = 0;

  // Truncates a file at |path| to |length|. If |length| is larger than
  // the original file size, the file will be extended, and the extended
  // part is filled with null bytes.
  virtual void Truncate(const FileSystemURL& path, int64 length,
                        const StatusCallback& callback) = 0;

  // Tries to cancel the current operation [we support cancelling write or
  // truncate only]. Reports failure for the current operation, then reports
  // success for the cancel operation itself via the |cancel_dispatcher|.
  //
  // E.g. a typical cancel implementation would look like:
  //
  //   virtual void SomeOperationImpl::Cancel(
  //       const StatusCallback& cancel_callback) {
  //     // Abort the current inflight operation first.
  //     ...
  //
  //     // Dispatch ABORT error for the current operation by invoking
  //     // the callback function for the ongoing operation,
  //     operation_callback.Run(base::File::FILE_ERROR_ABORT, ...);
  //
  //     // Dispatch 'success' for the cancel (or dispatch appropriate
  //     // error code with DidFail() if the cancel has somehow failed).
  //     cancel_callback.Run(base::File::FILE_OK);
  //   }
  //
  // Note that, for reporting failure, the callback function passed to a
  // cancellable operations are kept around with the operation instance
  // (as |operation_callback_| in the code example).
  virtual void Cancel(const StatusCallback& cancel_callback) = 0;

  // Modifies timestamps of a file or directory at |path| with
  // |last_access_time| and |last_modified_time|. The function DOES NOT
  // create a file unlike 'touch' command on Linux.
  //
  // This function is used only by Pepper as of writing.
  virtual void TouchFile(const FileSystemURL& path,
                         const base::Time& last_access_time,
                         const base::Time& last_modified_time,
                         const StatusCallback& callback) = 0;

  // Opens a file at |path| with |file_flags|, where flags are OR'ed
  // values of base::File::Flags.
  //
  // This function is used only by Pepper as of writing.
  virtual void OpenFile(const FileSystemURL& path,
                        int file_flags,
                        const OpenFileCallback& callback) = 0;

  // Creates a local snapshot file for a given |path| and returns the
  // metadata and platform path of the snapshot file via |callback|.
  // In local filesystem cases the implementation may simply return
  // the metadata of the file itself (as well as GetMetadata does),
  // while in remote filesystem case the backend may want to download the file
  // into a temporary snapshot file and return the metadata of the
  // temporary file.  Or if the implementaiton already has the local cache
  // data for |path| it can simply return the path to the cache.
  virtual void CreateSnapshotFile(const FileSystemURL& path,
                                  const SnapshotFileCallback& callback) = 0;

  // Copies in a single file from a different filesystem.
  //
  // This returns:
  // - File::FILE_ERROR_NOT_FOUND if |src_file_path|
  //   or the parent directory of |dest_url| does not exist.
  // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
  //   is not a file.
  // - File::FILE_ERROR_FAILED if |dest_url| does not exist and
  //   its parent path is a file.
  //
  virtual void CopyInForeignFile(const base::FilePath& src_local_disk_path,
                                 const FileSystemURL& dest_url,
                                 const StatusCallback& callback) = 0;

  // Removes a single file.
  //
  // This returns:
  // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
  // - File::FILE_ERROR_NOT_A_FILE if |url| is not a file.
  //
  virtual void RemoveFile(const FileSystemURL& url,
                          const StatusCallback& callback) = 0;

  // Removes a single empty directory.
  //
  // This returns:
  // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
  // - File::FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory.
  // - File::FILE_ERROR_NOT_EMPTY if |url| is not empty.
  //
  virtual void RemoveDirectory(const FileSystemURL& url,
                               const StatusCallback& callback) = 0;

  // Copies a file from |src_url| to |dest_url|.
  // This must be called for files that belong to the same filesystem
  // (i.e. type() and origin() of the |src_url| and |dest_url| must match).
  // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's
  // comment for details.
  // |progress_callback| is periodically called to report the progress
  // update. See also the comment of CopyFileProgressCallback. This callback is
  // optional.
  //
  // This returns:
  // - File::FILE_ERROR_NOT_FOUND if |src_url|
  //   or the parent directory of |dest_url| does not exist.
  // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
  // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
  //   is not a file.
  // - File::FILE_ERROR_FAILED if |dest_url| does not exist and
  //   its parent path is a file.
  //
  virtual void CopyFileLocal(const FileSystemURL& src_url,
                             const FileSystemURL& dest_url,
                             CopyOrMoveOption option,
                             const CopyFileProgressCallback& progress_callback,
                             const StatusCallback& callback) = 0;

  // Moves a local file from |src_url| to |dest_url|.
  // This must be called for files that belong to the same filesystem
  // (i.e. type() and origin() of the |src_url| and |dest_url| must match).
  // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's
  // comment for details.
  //
  // This returns:
  // - File::FILE_ERROR_NOT_FOUND if |src_url|
  //   or the parent directory of |dest_url| does not exist.
  // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
  // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
  //   is not a file.
  // - File::FILE_ERROR_FAILED if |dest_url| does not exist and
  //   its parent path is a file.
  //
  virtual void MoveFileLocal(const FileSystemURL& src_url,
                             const FileSystemURL& dest_url,
                             CopyOrMoveOption option,
                             const StatusCallback& callback) = 0;

  // Synchronously gets the platform path for the given |url|.
  // This may fail if the given |url|'s filesystem type is neither
  // temporary nor persistent.
  // In such a case, base::File::FILE_ERROR_INVALID_OPERATION will be
  // returned.
  virtual base::File::Error SyncGetPlatformPath(
      const FileSystemURL& url,
      base::FilePath* platform_path) = 0;

 protected:
  // Used only for internal assertions.
  enum OperationType {
    kOperationNone,
    kOperationCreateFile,
    kOperationCreateDirectory,
    kOperationCreateSnapshotFile,
    kOperationCopy,
    kOperationCopyInForeignFile,
    kOperationMove,
    kOperationDirectoryExists,
    kOperationFileExists,
    kOperationGetMetadata,
    kOperationReadDirectory,
    kOperationRemove,
    kOperationWrite,
    kOperationTruncate,
    kOperationTouchFile,
    kOperationOpenFile,
    kOperationCloseFile,
    kOperationGetLocalPath,
    kOperationCancel,
  };
};

}  // namespace storage

#endif  // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_
