File: WatchmanClient.h

package info (click to toggle)
watchman 4.9.0-9
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,992 kB
  • sloc: cpp: 27,459; python: 6,538; java: 3,404; php: 3,257; ansic: 2,803; javascript: 1,116; makefile: 671; ruby: 364; sh: 124; xml: 102; lisp: 4
file content (199 lines) | stat: -rw-r--r-- 6,251 bytes parent folder | download | duplicates (3)
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