blob: d5bac295636062076cdb20fffdbdedaf3c1a6fd8 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_ARC_FILEAPI_ARC_DOCUMENTS_PROVIDER_ROOT_H_
#define CHROME_BROWSER_CHROMEOS_ARC_FILEAPI_ARC_DOCUMENTS_PROVIDER_ROOT_H_
#include <stdint.h>
#include <map>
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "chrome/browser/chromeos/arc/fileapi/arc_file_system_operation_runner.h"
#include "chrome/browser/chromeos/arc/fileapi/arc_file_system_operation_runner_util.h"
#include "components/arc/common/file_system.mojom.h"
#include "storage/browser/fileapi/async_file_util.h"
#include "storage/browser/fileapi/watcher_manager.h"
class GURL;
namespace arc {
// Represents a file system root in Android Documents Provider.
//
// All methods must be called on the IO thread.
// If this object is deleted while there are in-flight operations, callbacks
// for those operations will be never called.
class ArcDocumentsProviderRoot : public ArcFileSystemOperationRunner::Observer {
public:
using GetFileInfoCallback = storage::AsyncFileUtil::GetFileInfoCallback;
using ReadDirectoryCallback = storage::AsyncFileUtil::ReadDirectoryCallback;
using ChangeType = storage::WatcherManager::ChangeType;
using WatcherCallback = base::Callback<void(ChangeType type)>;
using StatusCallback = base::Callback<void(base::File::Error error)>;
using ResolveToContentUrlCallback =
base::Callback<void(const GURL& content_url)>;
ArcDocumentsProviderRoot(const std::string& authority,
const std::string& root_document_id);
~ArcDocumentsProviderRoot() override;
// Queries information of a file just like AsyncFileUtil.GetFileInfo().
void GetFileInfo(const base::FilePath& path,
const GetFileInfoCallback& callback);
// Queries a list of files under a directory just like
// AsyncFileUtil.ReadDirectory().
void ReadDirectory(const base::FilePath& path,
const ReadDirectoryCallback& callback);
// Installs a document watcher.
//
// It is not allowed to install multiple watchers at the same file path;
// if attempted, duplicated requests will fail.
//
// Currently, watchers can be installed only on directories, and only
// directory content changes are notified. The result of installing a watcher
// to a non-directory in unspecified.
//
// NOTES ABOUT CORRECTNESS AND CONSISTENCY:
//
// Document watchers are not always correct and they may miss some updates or
// even notify incorrect update events for several reasons, such as:
//
// - Directory moves: Currently a watcher will misbehave if the watched
// directory is moved to another location. This is acceptable for now
// since we whitelist MediaDocumentsProvider only.
// - Duplicated file name handling in this class: The same reason as
// directory moves. This may happen even with MediaDocumentsProvider,
// but the chance will not be very high.
// - File system operation races: For example, an watcher can be installed
// to a non-directory in a race condition.
// - Broken DocumentsProviders: For example, we get no notification if a
// document provider does not call setNotificationUri().
//
// However, consistency of installed watchers is guaranteed. That is, after
// a watcher is installed on a file path X, an attempt to uninstall a watcher
// at X will always succeed.
//
// Unfortunately it is too difficult (or maybe theoretically impossible) to
// implement a perfect Android document watcher which never misses document
// updates. So the current implementation gives up correctness, but instead
// focuses on following two goals:
//
// 1. Keep the implementation simple, rather than trying hard to catch
// race conditions or minor cases. Even if we return wrong results, the
// worst consequence is just that users do not see the latest contents
// until they refresh UI.
//
// 2. Keep consistency of installed watchers so that the caller can avoid
// dangling watchers.
void AddWatcher(const base::FilePath& path,
const WatcherCallback& watcher_callback,
const StatusCallback& callback);
// Uninstalls a document watcher.
// See the documentation of AddWatcher() above.
void RemoveWatcher(const base::FilePath& path,
const StatusCallback& callback);
// Resolves a file path into a content:// URL pointing to the file
// on DocumentsProvider. Returns URL that can be passed to
// ArcContentFileSystemFileSystemReader to read the content.
// On errors, an invalid GURL is returned.
void ResolveToContentUrl(const base::FilePath& path,
const ResolveToContentUrlCallback& callback);
// ArcFileSystemOperationRunner::Observer overrides:
void OnWatchersCleared() override;
private:
// Thin representation of a document in documents provider.
struct ThinDocument {
std::string document_id;
bool is_directory;
};
// Represents the status of a document watcher.
struct WatcherData {
// ID of a watcher in the remote file system service.
//
// Valid IDs are represented by positive integers. An invalid watcher is
// represented by |kInvalidWatcherId|, which occurs in several cases:
//
// - AddWatcher request is still in-flight. In this case, a valid ID is set
// to |inflight_request_id|.
//
// - The remote file system service notified us that it stopped and all
// watchers were forgotten. Such watchers are still tracked here, but they
// are not known by the remote service.
int64_t id;
// A unique ID of AddWatcher() request.
//
// While AddWatcher() is in-flight, a positive integer is set to this
// variable, and |id| is |kInvalidWatcherId|. Otherwise it is set to
// |kInvalidWatcherRequestId|.
uint64_t inflight_request_id;
};
static const int64_t kInvalidWatcherId;
static const uint64_t kInvalidWatcherRequestId;
static const WatcherData kInvalidWatcherData;
// Mapping from a file name to a ThinDocument.
using NameToThinDocumentMap =
std::map<base::FilePath::StringType, ThinDocument>;
using ResolveToDocumentIdCallback =
base::Callback<void(const std::string& document_id)>;
using ReadDirectoryInternalCallback =
base::Callback<void(base::File::Error error,
NameToThinDocumentMap mapping)>;
void GetFileInfoWithDocumentId(const GetFileInfoCallback& callback,
const std::string& document_id);
void GetFileInfoWithDocument(const GetFileInfoCallback& callback,
mojom::DocumentPtr document);
void ReadDirectoryWithDocumentId(const ReadDirectoryCallback& callback,
const std::string& document_id);
void ReadDirectoryWithNameToThinDocumentMap(
const ReadDirectoryCallback& callback,
base::File::Error error,
NameToThinDocumentMap mapping);
void AddWatcherWithDocumentId(const base::FilePath& path,
uint64_t watcher_request_id,
const WatcherCallback& watcher_callback,
const std::string& document_id);
void OnWatcherAdded(const base::FilePath& path,
uint64_t watcher_request_id,
int64_t watcher_id);
void OnWatcherAddedButRemoved(bool success);
void OnWatcherRemoved(const StatusCallback& callback, bool success);
// Returns true if the specified watcher request has been canceled.
// This function should be called only while the request is in-flight.
bool IsWatcherInflightRequestCanceled(const base::FilePath& path,
uint64_t watcher_request_id) const;
void ResolveToContentUrlWithDocumentId(
const ResolveToContentUrlCallback& callback,
const std::string& document_id);
// Resolves |path| to a document ID. Failures are indicated by an empty
// document ID.
void ResolveToDocumentId(const base::FilePath& path,
const ResolveToDocumentIdCallback& callback);
void ResolveToDocumentIdRecursively(
const std::string& document_id,
const std::vector<base::FilePath::StringType>& components,
const ResolveToDocumentIdCallback& callback);
void ResolveToDocumentIdRecursivelyWithNameToThinDocumentMap(
const std::vector<base::FilePath::StringType>& components,
const ResolveToDocumentIdCallback& callback,
base::File::Error error,
NameToThinDocumentMap mapping);
// Enumerates child documents of a directory specified by |document_id|.
// The result is returned as a NameToThinDocumentMap.
void ReadDirectoryInternal(const std::string& document_id,
const ReadDirectoryInternalCallback& callback);
void ReadDirectoryInternalWithChildDocuments(
const ReadDirectoryInternalCallback& callback,
base::Optional<std::vector<mojom::DocumentPtr>> maybe_children);
const std::string authority_;
const std::string root_document_id_;
// Map from a file path to a watcher data.
//
// Note that we do not use a document ID as a key here to guarantee that
// a watch installed by AddWatcher() can be always identified in
// RemoveWatcher() with the same file path specified.
// See the documentation of AddWatcher() for more details.
std::map<base::FilePath, WatcherData> path_to_watcher_data_;
uint64_t next_watcher_request_id_ = 1;
// Can be null if this instance is not observing ArcFileSystemOperationRunner.
// Observation is started on the first call of AddWatcher().
scoped_refptr<file_system_operation_runner_util::ObserverIOThreadWrapper>
observer_wrapper_;
base::WeakPtrFactory<ArcDocumentsProviderRoot> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ArcDocumentsProviderRoot);
};
} // namespace arc
#endif // CHROME_BROWSER_CHROMEOS_ARC_FILEAPI_ARC_DOCUMENTS_PROVIDER_ROOT_H_