/*
* Copyright 2016-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.eden;
import com.facebook.buck.io.unixsocket.UnixDomainSocket;
import com.facebook.eden.thrift.EdenError;
import com.facebook.eden.thrift.EdenService;
import com.facebook.eden.thrift.MountInfo;
import com.facebook.thrift.TException;
import com.facebook.thrift.protocol.TBinaryProtocol;
import com.facebook.thrift.protocol.TProtocol;
import com.facebook.thrift.transport.TSocket;
import com.facebook.thrift.transport.TTransport;
import com.facebook.thrift.transport.TTransportException;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
/**
* Client of Eden's fbthrift API. Note that there should be at most one Eden client per machine,
* though it may have zero or more mount points.
*/
public final class EdenClient {
/**
* Instances of {@link com.facebook.eden.thrift.EdenService.Client} are not thread-safe, so we
* create them via a {@link ThreadLocal} rather than create a new client for each Thrift call.
*/
private final ThreadLocal<EdenService.Client> clientFactory;
/**
* @param clientFactory creates one Thrift client per thread through which Eden Thrift API calls
* should be made.
*/
private EdenClient(ThreadLocal<EdenService.Client> clientFactory) {
this.clientFactory = clientFactory;
}
@VisibleForTesting
EdenClient(final EdenService.Client client) {
this(
new ThreadLocal<EdenService.Client>() {
@Override
protected EdenService.Client initialValue() {
return client;
}
});
}
public static Optional<EdenClient> newInstance() {
// The default path for the Eden socket is ~/local/.eden/socket.
Path socketFile = Paths.get(System.getProperty("user.home"), "local/.eden/socket");
return newInstance(socketFile);
}
private static Optional<EdenClient> newInstance(final Path socketFile) {
ThreadLocal<EdenService.Client> clientFactory =
new ThreadLocal<EdenService.Client>() {
/**
* @return {@code null} if there is no instance of Eden to connect to because this method
* cannot throw checked exceptions to signal a failure.
*/
@Override
@Nullable
protected EdenService.Client initialValue() {
TTransport transport;
try {
UnixDomainSocket socket = UnixDomainSocket.createSocketWithPath(socketFile);
transport = new TSocket(socket);
} catch (IOException | TTransportException e) {
return null;
}
// No need to invoke transport.open() because the UnixDomainSocket is already connected.
TProtocol protocol = new TBinaryProtocol(transport);
return new EdenService.Client(protocol);
}
};
// We forcibly try to create an EdenService.Client as a way of verifying that `socketFile` is a
// valid UNIX domain socket for talking to Eden. If this is not the case, then we should not
// return a new EdenClient.
EdenService.Client client = clientFactory.get();
if (client != null) {
return Optional.of(new EdenClient(clientFactory));
} else {
return Optional.empty();
}
}
public List<MountInfo> getMountInfos() throws EdenError, TException {
return clientFactory.get().listMounts();
}
/** @return an Eden mount point if {@code projectRoot} is backed by Eden or {@code null}. */
@Nullable
public EdenMount getMountFor(Path projectRoot) throws EdenError, TException {
for (MountInfo info : getMountInfos()) {
// Note that we cannot use Paths.get() here because that will break unit tests where we mix
// java.nio.file.Path and Jimfs Path objects.
Path mountPoint = projectRoot.getFileSystem().getPath(info.mountPoint);
if (projectRoot.startsWith(mountPoint)) {
return new EdenMount(clientFactory, mountPoint, projectRoot);
}
}
return null;
}
}