// Copyright 2013 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_RUNNER_H_
#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_

#include <map>
#include <set>
#include <vector>

#include "base/basictypes.h"
#include "base/id_map.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/fileapi/file_system_operation.h"
#include "storage/browser/fileapi/file_system_url.h"
#include "storage/browser/storage_browser_export.h"

namespace net {
class URLRequestContext;
}

namespace storage {

class FileSystemURL;
class FileSystemContext;

// A central interface for running FileSystem API operations.
// All operation methods take callback and returns OperationID, which is
// an integer value which can be used for cancelling an operation.
// All operation methods return kErrorOperationID if running (posting) an
// operation fails, in addition to dispatching the callback with an error
// code (therefore in most cases the caller does not need to check the
// returned operation ID).
class STORAGE_EXPORT FileSystemOperationRunner
    : public base::SupportsWeakPtr<FileSystemOperationRunner> {
 public:
  typedef FileSystemOperation::GetMetadataCallback GetMetadataCallback;
  typedef FileSystemOperation::ReadDirectoryCallback ReadDirectoryCallback;
  typedef FileSystemOperation::SnapshotFileCallback SnapshotFileCallback;
  typedef FileSystemOperation::StatusCallback StatusCallback;
  typedef FileSystemOperation::WriteCallback WriteCallback;
  typedef FileSystemOperation::OpenFileCallback OpenFileCallback;
  typedef FileSystemOperation::CopyProgressCallback CopyProgressCallback;
  typedef FileSystemOperation::CopyFileProgressCallback
      CopyFileProgressCallback;
  typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption;

  typedef int OperationID;

  virtual ~FileSystemOperationRunner();

  // Cancels all inflight operations.
  void Shutdown();

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

  OperationID CreateDirectory(const FileSystemURL& url,
                              bool exclusive,
                              bool recursive,
                              const StatusCallback& callback);

  // Copies a file or directory from |src_url| to |dest_url|. If
  // |src_url| is a directory, the contents of |src_url| are copied to
  // |dest_url| recursively. A new file or directory is created at
  // |dest_url| as needed.
  // For |option| and |progress_callback|, see file_system_operation.h for
  // details.
  OperationID Copy(const FileSystemURL& src_url,
                   const FileSystemURL& dest_url,
                   CopyOrMoveOption option,
                   const CopyProgressCallback& progress_callback,
                   const StatusCallback& callback);

  // Moves a file or directory from |src_url| to |dest_url|. A new file
  // or directory is created at |dest_url| as needed.
  // For |option|, see file_system_operation.h for details.
  OperationID Move(const FileSystemURL& src_url,
                   const FileSystemURL& dest_url,
                   CopyOrMoveOption option,
                   const StatusCallback& callback);

  // Checks if a directory is present at |url|.
  OperationID DirectoryExists(const FileSystemURL& url,
                              const StatusCallback& callback);

  // Checks if a file is present at |url|.
  OperationID FileExists(const FileSystemURL& url,
                         const StatusCallback& callback);

  // Gets the metadata of a file or directory at |url|.
  OperationID GetMetadata(const FileSystemURL& url,
                          const GetMetadataCallback& callback);

  // Reads contents of a directory at |url|.
  OperationID ReadDirectory(const FileSystemURL& url,
                            const ReadDirectoryCallback& callback);

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

  // Writes contents of |blob_url| to |url| at |offset|.
  // |url_request_context| is used to read contents in |blob|.
  OperationID Write(const net::URLRequestContext* url_request_context,
                    const FileSystemURL& url,
                    scoped_ptr<storage::BlobDataHandle> blob,
                    int64 offset,
                    const WriteCallback& callback);

  // Truncates a file at |url| 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.
  OperationID Truncate(const FileSystemURL& url, int64 length,
                       const StatusCallback& callback);

  // Tries to cancel the operation |id| [we support cancelling write or
  // truncate only]. Reports failure for the current operation, then reports
  // success for the cancel operation itself via the |callback|.
  void Cancel(OperationID id, const StatusCallback& callback);

  // Modifies timestamps of a file or directory at |url| 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.
  OperationID TouchFile(const FileSystemURL& url,
                        const base::Time& last_access_time,
                        const base::Time& last_modified_time,
                        const StatusCallback& callback);

  // Opens a file at |url| with |file_flags|, where flags are OR'ed
  // values of base::PlatformFileFlags.
  //
  // |peer_handle| is the process handle of a pepper plugin process, which
  // is necessary for underlying IPC calls with Pepper plugins.
  //
  // This function is used only by Pepper as of writing.
  OperationID OpenFile(const FileSystemURL& url,
                       int file_flags,
                       const OpenFileCallback& callback);

  // Creates a local snapshot file for a given |url| and returns the
  // metadata and platform url 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 |url| it can simply return the url to the cache.
  OperationID CreateSnapshotFile(const FileSystemURL& url,
                                 const SnapshotFileCallback& callback);

  // 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.
  //
  OperationID CopyInForeignFile(const base::FilePath& src_local_disk_path,
                                const FileSystemURL& dest_url,
                                const StatusCallback& callback);

  // 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.
  //
  OperationID RemoveFile(const FileSystemURL& url,
                         const StatusCallback& callback);

  // 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.
  //
  OperationID RemoveDirectory(const FileSystemURL& url,
                              const StatusCallback& callback);

  // 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).
  // For |option| and |progress_callback|, see file_system_operation.h 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.
  //
  OperationID CopyFileLocal(const FileSystemURL& src_url,
                            const FileSystemURL& dest_url,
                            CopyOrMoveOption option,
                            const CopyFileProgressCallback& progress_callback,
                            const StatusCallback& callback);

  // 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).
  // For |option|, see file_system_operation.h 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.
  //
  OperationID MoveFileLocal(const FileSystemURL& src_url,
                            const FileSystemURL& dest_url,
                            CopyOrMoveOption option,
                            const StatusCallback& callback);

  // This is called only by pepper plugin as of writing to synchronously get
  // the underlying platform path to upload a file in the sandboxed filesystem
  // (e.g. TEMPORARY or PERSISTENT).
  base::File::Error SyncGetPlatformPath(const FileSystemURL& url,
                                        base::FilePath* platform_path);

 private:
  class BeginOperationScoper;

  struct OperationHandle {
    OperationID id;
    base::WeakPtr<BeginOperationScoper> scope;

    OperationHandle();
    ~OperationHandle();
  };

  friend class FileSystemContext;
  explicit FileSystemOperationRunner(FileSystemContext* file_system_context);

  void DidFinish(const OperationHandle& handle,
                 const StatusCallback& callback,
                 base::File::Error rv);
  void DidGetMetadata(const OperationHandle& handle,
                      const GetMetadataCallback& callback,
                      base::File::Error rv,
                      const base::File::Info& file_info);
  void DidReadDirectory(const OperationHandle& handle,
                        const ReadDirectoryCallback& callback,
                        base::File::Error rv,
                        const std::vector<DirectoryEntry>& entries,
                        bool has_more);
  void DidWrite(const OperationHandle& handle,
                const WriteCallback& callback,
                base::File::Error rv,
                int64 bytes,
                bool complete);
  void DidOpenFile(
      const OperationHandle& handle,
      const OpenFileCallback& callback,
      base::File file,
      const base::Closure& on_close_callback);
  void DidCreateSnapshot(
      const OperationHandle& handle,
      const SnapshotFileCallback& callback,
      base::File::Error rv,
      const base::File::Info& file_info,
      const base::FilePath& platform_path,
      const scoped_refptr<storage::ShareableFileReference>& file_ref);

  void OnCopyProgress(
      const OperationHandle& handle,
      const CopyProgressCallback& callback,
      FileSystemOperation::CopyProgressType type,
      const FileSystemURL& source_url,
      const FileSystemURL& dest_url,
      int64 size);

  void PrepareForWrite(OperationID id, const FileSystemURL& url);
  void PrepareForRead(OperationID id, const FileSystemURL& url);

  // These must be called at the beginning and end of any async operations.
  OperationHandle BeginOperation(FileSystemOperation* operation,
                                 base::WeakPtr<BeginOperationScoper> scope);
  void FinishOperation(OperationID id);

  // Not owned; file_system_context owns this.
  FileSystemContext* file_system_context_;

  // IDMap<FileSystemOperation, IDMapOwnPointer> operations_;
  IDMap<FileSystemOperation, IDMapOwnPointer> operations_;

  // We keep track of the file to be modified by each operation so that
  // we can notify observers when we're done.
  typedef std::map<OperationID, FileSystemURLSet> OperationToURLSet;
  OperationToURLSet write_target_urls_;

  // Operations that are finished but not yet fire their callbacks.
  std::set<OperationID> finished_operations_;

  // Callbacks for stray cancels whose target operation is already finished.
  std::map<OperationID, StatusCallback> stray_cancel_callbacks_;

  DISALLOW_COPY_AND_ASSIGN(FileSystemOperationRunner);
};

}  // namespace storage

#endif  // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_
