1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
|
/* Copyright 2016-present Facebook, Inc.
* Licensed under the Apache License, Version 2.0 */
#pragma once
/* A C++ client library for accessing Watchman. This builds on top of
* WatchmanConnection to provide a C++ friendly subscription API and maybe
* later other convienence functions. The entry point for this API is via
* the WatchmanClient type.
*
* Example usage:
* // Set-up an EventBase to execute I/O operations in.
* folly::EventBase eb = ...;
*
* // Set-up a session with a subscription using chained future actions to:
* // * Connect to the watchman server (and print out Watchman version)
* // * Set-up a watch for /some/path
* // * Subscribe to a query of name data for any updated files
* WatchmanClient client(&eb);
* auto subFuture = client.connect().then([](folly::dynamic&& response) {
* std::cout << "Server version " << response["version"] << std::endl;
*
* return client.watch("/some/path").then([](WatchPathPtr watch) {
* folly::dynamic query = folly::dynamic::object("fields", {"name"});
*
* return client.subscribe(query, watch, &eb, [](folly::dynamic&& data) {
* std::cout << "Got file update data: " << data << std::endl;
* });
* });
* });
*
* // Wait for chain of futures to complete or raise an exception.
* auto subscription = subFuture.wait().value();
*
* // Run a one-shot Watchman command and extract the output.
* auto watchman_version_str =
* client.run({"version"}).wait().value()["version"];
*
* // ... do stuff ...
*
* // Unsubscribe from query above. Note this is not strictly needed if we're
* // going to immediately shut down the connection anyway.
* client.unsubscribe(subscription);
*
* // Close connection. Note again this is not strictly needed as
* // deconstruction will also cause the connection to close. An explicit
* // close() call may be needed for example to ensure disconnection happens
* // before the EventBase is destroyed.
* client.close();
*/
#include "WatchmanConnection.h"
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <folly/Executor.h>
#include <folly/Optional.h>
#include <folly/dynamic.h>
#include <folly/futures/Future.h>
#include <folly/Try.h>
namespace watchman {
struct WatchmanClient;
struct Subscription;
struct WatchPath {
friend WatchmanClient;
WatchPath(
const std::string& root,
const folly::Optional<std::string>& relativePath);
private:
const std::string root_;
const folly::Optional<std::string> relativePath_;
};
using WatchPathPtr = std::shared_ptr<WatchPath>;
using SubscriptionCallback = std::function<void(folly::Try<folly::dynamic>&&)>;
using ErrorCallback = std::function<void(folly::exception_wrapper&)>;
struct Subscription {
friend WatchmanClient;
Subscription(
folly::Executor* executor,
SubscriptionCallback&& callback,
const std::string& name,
WatchPathPtr watchPath);
private:
folly::Executor* executor_;
SubscriptionCallback callback_;
const std::string name_;
WatchPathPtr watchPath_;
bool active_{true};
};
using SubscriptionPtr = std::shared_ptr<Subscription>;
struct WatchmanClient {
explicit WatchmanClient(
folly::EventBase* eventBase,
folly::Optional<std::string>&& sockPath = {},
folly::Executor* cpuExecutor = {},
ErrorCallback errCb = {});
/**
* Establishes a connection, returning version and capability information per
* https://facebook.github.io/watchman/docs/cmd/version.html#capabilities
*/
folly::Future<folly::dynamic> connect(
folly::dynamic versionArgs = folly::dynamic::object()(
"required",
folly::dynamic::array("relative_root")));
/**
* Close the underlying connection to Watchman, including automatically
* unsubscribing from all subscriptions.
*/
void close();
/**
* Returns true if the underlying connection is closed or broken.
*/
bool isDead() {
return conn_->isDead();
}
/**
* Execute a watchman command, yielding the command response.
* cmd is typically an array.
* See
* https://facebook.github.io/watchman/docs/socket-interface.html#watchman-protocol
* for the conventions.
*
* Errors, both at the transport layer and at the watchman protocol layer
* (where the "error" field is set in the response) are captured in the Future
* as exceptions. Watchman protocol response errors are represented by the
* WatchmanResponseError type.
*/
folly::Future<folly::dynamic> run(const folly::dynamic& cmd);
/**
* Create a watch for a path, automatically sharing scarce OS resources
* between multiple watchers of the same (super-)tree. This should be the
* preferred way to create a WatchPath instance unless you want to explicitly
* avoid sharing Watchman tree configurations between independent watchers.
*
* See https://facebook.github.io/watchman/docs/cmd/watch-project.html for
* details.
*/
folly::Future<WatchPathPtr> watch(folly::StringPiece path);
/**
* Establishes a subscription that will trigger callback (via your specified
* executor) whenever matching files change.
*/
folly::Future<SubscriptionPtr> subscribe(
folly::dynamic query,
WatchPathPtr path,
folly::Executor* executor,
SubscriptionCallback&& callback);
/**
* As-per subscribe above but automatically creates a WatchPath from a string.
* This is probably what you want but see comments on watch() for details.
*/
folly::Future<SubscriptionPtr> subscribe(
const folly::dynamic& query,
folly::StringPiece path,
folly::Executor* executor,
SubscriptionCallback&& callback);
/** Cancels an existing subscription. */
folly::Future<folly::dynamic> unsubscribe(SubscriptionPtr subscription);
/** Intended for test only. */
WatchmanConnection& getConnection() {
return *conn_;
}
private:
void connectionCallback(folly::Try<folly::dynamic>&& try_data);
std::shared_ptr<WatchmanConnection> conn_;
ErrorCallback errorCallback_;
std::unordered_map<std::string, SubscriptionPtr> subscriptionMap_;
std::mutex mutex_;
int nextSubID_{0};
};
} // namespace watchman
|