/*
* Copyright (C) 2013-2016 Per Lundqvist
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.perlundq.yajsync;
import java.io.Console;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.channels.Pipe;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.github.perlundq.yajsync.attr.FileInfo;
import com.github.perlundq.yajsync.internal.session.ClientSessionConfig;
import com.github.perlundq.yajsync.internal.session.FilterMode;
import com.github.perlundq.yajsync.internal.session.Generator;
import com.github.perlundq.yajsync.internal.session.Receiver;
import com.github.perlundq.yajsync.internal.session.RsyncTaskExecutor;
import com.github.perlundq.yajsync.internal.session.Sender;
import com.github.perlundq.yajsync.internal.session.SessionStatistics;
import com.github.perlundq.yajsync.internal.session.SessionStatus;
import com.github.perlundq.yajsync.internal.text.Text;
import com.github.perlundq.yajsync.internal.util.BitOps;
import com.github.perlundq.yajsync.internal.util.Pair;
import com.github.perlundq.yajsync.internal.util.Util;
public final class RsyncClient
{
private enum Mode
{
LOCAL_COPY, LOCAL_LIST, REMOTE_SEND, REMOTE_RECEIVE, REMOTE_LIST
}
public static class Result
{
private final boolean _isOK;
private final Statistics _statistics;
private Result(boolean isOK, Statistics statistics)
{
_isOK = isOK;
_statistics = statistics;
}
public static Result failure()
{
return new Result(false, new SessionStatistics());
}
public static Result success()
{
return new Result(true, new SessionStatistics());
}
public boolean isOK()
{
return _isOK;
}
public Statistics statistics()
{
return _statistics;
}
}
public class FileListing
{
private final Future<Result> _future;
private final CountDownLatch _isListingAvailable;
private BlockingQueue<Pair<Boolean, FileInfo>> _listing;
private FileListing(final Sender sender,
final Generator generator,
final Receiver receiver)
{
Callable<Result> callable = new Callable<Result>() {
@Override
public Result call() throws RsyncException, InterruptedException
{
try {
boolean isOK = _rsyncTaskExecutor.exec(sender,
generator,
receiver);
return new Result(isOK, receiver.statistics());
} finally {
if (_isOwnerOfExecutorService) {
if (_log.isLoggable(Level.FINE)) {
_log.fine("shutting down " + _executorService);
}
_executorService.shutdown();
}
}
}
};
_future = _executorService.submit(callable);
_listing = generator.files();
_isListingAvailable = new CountDownLatch(0);
}
private FileListing(final ClientSessionConfig cfg,
final String moduleName,
final List<String> serverArgs,
final AuthProvider authProvider,
final ReadableByteChannel in,
final WritableByteChannel out,
final boolean isInterruptible,
final FileSelection fileSelection)
{
Callable<Result> callable = new Callable<Result>() {
@Override
public Result call() throws RsyncException, InterruptedException
{
try {
SessionStatus status = cfg.handshake(moduleName,
serverArgs,
authProvider);
if (_log.isLoggable(Level.FINE)) {
_log.fine("handshake status: " + status);
}
if (status == SessionStatus.ERROR) {
return Result.failure();
} else if (status == SessionStatus.EXIT) {
return Result.success();
}
Generator generator = new Generator.Builder(out,
cfg.checksumSeed()).
charset(cfg.charset()).
fileSelection(fileSelection).
isDelete(_isDelete).
isPreserveDevices(_isPreserveDevices).
isPreserveSpecials(_isPreserveSpecials).
isPreserveLinks(_isPreserveLinks).
isPreservePermissions(_isPreservePermissions).
isPreserveTimes(_isPreserveTimes).
isPreserveUser(_isPreserveUser).
isPreserveGroup(_isPreserveGroup).
isNumericIds(_isNumericIds).
isIgnoreTimes(_isIgnoreTimes).
isAlwaysItemize(_verbosity > 1).
isInterruptible(isInterruptible).build();
_listing = generator.files();
_isListingAvailable.countDown();
Receiver receiver = Receiver.Builder.newListing(generator, in).
filterMode(FilterMode.SEND).
isDeferWrite(_isDeferWrite).
isExitAfterEOF(true).
isExitEarlyIfEmptyList(true).
isReceiveStatistics(true).
isSafeFileList(cfg.isSafeFileList()).build();
boolean isOK = _rsyncTaskExecutor.exec(generator,
receiver);
return new Result(isOK, receiver.statistics());
} finally {
if (_listing == null) {
_listing = new LinkedBlockingQueue<>();
_listing.put(new Pair<Boolean, FileInfo>(false, null));
_isListingAvailable.countDown();
}
if (_isOwnerOfExecutorService) {
if (_log.isLoggable(Level.FINE)) {
_log.fine("shutting down " + _executorService);
}
_executorService.shutdown();
}
}
}
};
_future = _executorService.submit(callable);
_isListingAvailable = new CountDownLatch(1);
}
public Future<Result> futureResult()
{
return _future;
}
public Result get() throws InterruptedException,
RsyncException
{
try {
return _future.get();
} catch (Throwable e) {
RsyncTaskExecutor.throwUnwrappedException(e);
throw new AssertionError();
}
}
/**
* @return the next available file information or null if there is
* nothing left available.
*/
public FileInfo take() throws InterruptedException
{
_isListingAvailable.await();
Pair<Boolean, FileInfo> res = _listing.take();
boolean isDone = res.first() == null;
if (isDone) {
return null;
} else {
return res.second();
}
}
}
public class ModuleListing
{
private final Future<Result> _future;
private final BlockingQueue<Pair<Boolean, String>> _moduleNames;
private ModuleListing(final ClientSessionConfig cfg,
final List<String> serverArgs)
{
Callable<Result> callable = new Callable<Result>() {
@Override
public Result call() throws Exception {
try {
SessionStatus status = cfg.handshake("",
serverArgs,
_authProvider);
if (_log.isLoggable(Level.FINE)) {
_log.fine("handshake status: " + status);
}
if (status == SessionStatus.ERROR) {
return Result.failure();
} else if (status == SessionStatus.EXIT) {
return Result.success();
}
throw new AssertionError();
} finally {
if (_isOwnerOfExecutorService) {
if (_log.isLoggable(Level.FINE)) {
_log.fine("shutting down " + _executorService);
}
_executorService.shutdown();
}
}
}
};
_future = _executorService.submit(callable);
_moduleNames = cfg.modules();
}
public Future<Result> futureResult()
{
return _future;
}
public Result get() throws InterruptedException,
RsyncException
{
try {
return _future.get();
} catch (Throwable e) {
RsyncTaskExecutor.throwUnwrappedException(e);
throw new AssertionError();
}
}
/**
* @return the next line of the remote module or motd listing or null if
* there are no more lines available
*/
public String take() throws InterruptedException
{
Pair<Boolean, String> res = _moduleNames.take();
boolean isDone = res.first() == null;
if (isDone) {
return null;
} else {
return res.second();
}
}
}
private static Pipe[] pipePair()
{
try {
return new Pipe[] { Pipe.open(), Pipe.open() };
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public class Local
{
public class Copy
{
private final Iterable<Path> _srcPaths;
private Copy(Iterable<Path> srcPaths)
{
assert srcPaths != null;
_srcPaths = srcPaths;
}
public Result to(Path dstPath) throws RsyncException,
InterruptedException
{
assert dstPath != null;
return localTransfer(_srcPaths, dstPath);
}
}
public Copy copy(Iterable<Path> paths)
{
assert paths != null;
return new Copy(paths);
}
public Copy copy(Path[] paths)
{
return copy(Arrays.asList(paths));
}
public FileListing list(Iterable<Path> srcPaths)
{
assert srcPaths != null;
Pipe[] pipePair = pipePair();
Pipe toSender = pipePair[0];
Pipe toReceiver = pipePair[1];
FileSelection fileSelection =
Util.defaultIfNull(_fileSelectionOrNull,
FileSelection.TRANSFER_DIRS);
byte[] seed = BitOps.toLittleEndianBuf((int) System.currentTimeMillis());
Sender sender = new Sender.Builder(toSender.source(),
toReceiver.sink(),
srcPaths,
seed).
isExitEarlyIfEmptyList(true).
charset(_charset).
isPreserveDevices(_isPreserveDevices).
isPreserveSpecials(_isPreserveSpecials).
isPreserveLinks(_isPreserveLinks).
isPreserveUser(_isPreserveUser).
isPreserveGroup(_isPreserveGroup).
isNumericIds(_isNumericIds).
fileSelection(fileSelection).build();
Generator generator = new Generator.Builder(toSender.sink(), seed).
charset(_charset).
fileSelection(fileSelection).
isDelete(_isDelete).
isPreserveDevices(_isPreserveDevices).
isPreserveSpecials(_isPreserveSpecials).
isPreserveLinks(_isPreserveLinks).
isPreservePermissions(_isPreservePermissions).
isPreserveTimes(_isPreserveTimes).
isPreserveUser(_isPreserveUser).
isPreserveGroup(_isPreserveGroup).
isNumericIds(_isNumericIds).
isIgnoreTimes(_isIgnoreTimes).
isAlwaysItemize(_isAlwaysItemize).build();
Receiver receiver = Receiver.Builder.newListing(generator,
toReceiver.source()).
isExitEarlyIfEmptyList(true).
isDeferWrite(_isDeferWrite).build();
return new FileListing(sender, generator, receiver);
}
public FileListing list(Path[] paths)
{
return list(Arrays.asList(paths));
}
private Result localTransfer(Iterable<Path> srcPaths, Path dstPath)
throws RsyncException, InterruptedException
{
assert srcPaths != null;
assert dstPath != null;
Pipe[] pipePair = pipePair();
Pipe toSender = pipePair[0];
Pipe toReceiver = pipePair[1];
FileSelection fileSelection =
Util.defaultIfNull(_fileSelectionOrNull,
FileSelection.EXACT);
byte[] seed = BitOps.toLittleEndianBuf((int) System.currentTimeMillis());
Sender sender = new Sender.Builder(toSender.source(),
toReceiver.sink(),
srcPaths,
seed).
isExitEarlyIfEmptyList(true).
charset(_charset).
isPreserveDevices(_isPreserveDevices).
isPreserveSpecials(_isPreserveSpecials).
isPreserveLinks(_isPreserveLinks).
isPreserveUser(_isPreserveUser).
isPreserveGroup(_isPreserveGroup).
isNumericIds(_isNumericIds).
fileSelection(fileSelection).build();
Generator generator = new Generator.Builder(toSender.sink(), seed).
charset(_charset).
fileSelection(fileSelection).
isDelete(_isDelete).
isPreserveDevices(_isPreserveDevices).
isPreserveSpecials(_isPreserveSpecials).
isPreserveLinks(_isPreserveLinks).
isPreservePermissions(_isPreservePermissions).
isPreserveTimes(_isPreserveTimes).
isPreserveUser(_isPreserveUser).
isPreserveGroup(_isPreserveGroup).
isNumericIds(_isNumericIds).
isIgnoreTimes(_isIgnoreTimes).
isAlwaysItemize(_isAlwaysItemize).build();
Receiver receiver = new Receiver.Builder(generator,
toReceiver.source(),
dstPath).
isExitEarlyIfEmptyList(true).
isDeferWrite(_isDeferWrite).build();
try {
boolean isOK = _rsyncTaskExecutor.exec(sender, generator,
receiver);
return new Result(isOK, receiver.statistics());
} finally {
if (_isOwnerOfExecutorService) {
_executorService.shutdown();
}
}
}
}
public class Remote
{
private final boolean _isInterruptible;
private final ReadableByteChannel _in;
private final WritableByteChannel _out;
public Remote(ReadableByteChannel in, WritableByteChannel out,
boolean isInterruptible)
{
assert in != null;
assert out != null;
_in = in;
_out = out;
_isInterruptible = isInterruptible;
}
public FileListing list(String moduleName,
Iterable<String> srcPathNames)
{
assert moduleName != null;
assert srcPathNames != null;
FileSelection fileSelection =
Util.defaultIfNull(_fileSelectionOrNull,
FileSelection.TRANSFER_DIRS);
List<String> serverArgs = createServerArgs(Mode.REMOTE_LIST,
fileSelection);
for (String s : srcPathNames) {
assert s.startsWith(Text.SLASH) : s;
serverArgs.add(moduleName + s);
}
if (_log.isLoggable(Level.FINE)) {
_log.fine(String.format(
"file selection: %s, src: %s, remote args: %s",
fileSelection, srcPathNames, serverArgs));
}
ClientSessionConfig cfg = new ClientSessionConfig(_in,
_out,
_charset,
fileSelection == FileSelection.RECURSE,
_stderr);
return new FileListing(cfg,
moduleName,
serverArgs,
_authProvider,
_in,
_out,
_isInterruptible,
fileSelection);
}
public FileListing list(String moduleName, String[] paths)
{
return list(moduleName, Arrays.asList(paths));
}
public ModuleListing listModules()
{
FileSelection fileSelection = FileSelection.EXACT;
Iterable<String> srcPathNames = Collections.emptyList();
List<String> serverArgs = createServerArgs(Mode.REMOTE_LIST,
fileSelection);
for (String src : srcPathNames) {
serverArgs.add(src);
}
if (_log.isLoggable(Level.FINE)) {
_log.fine(String.format("file selection: %s, src: %s, remote " +
"args: %s", fileSelection, srcPathNames,
serverArgs));
}
ClientSessionConfig cfg = new ClientSessionConfig(_in,
_out,
_charset,
fileSelection == FileSelection.RECURSE,
_stderr);
return new ModuleListing(cfg, serverArgs);
}
public Send send(Iterable<Path> paths)
{
assert paths != null;
return new Send(paths);
}
public Send send(Path[] paths)
{
return send(Arrays.asList(paths));
}
public Receive receive(String moduleName, Iterable<String> pathNames)
{
assert moduleName != null;
assert pathNames != null;
return new Receive(moduleName, pathNames);
}
public Receive receive(String moduleName, String[] pathNames)
{
return receive(moduleName, Arrays.asList(pathNames));
}
public class Send
{
private final Iterable<Path> _srcPaths;
private Send(Iterable<Path> srcPaths)
{
assert srcPaths != null;
_srcPaths = srcPaths;
}
public Result to(String moduleName, String dstPathName)
throws RsyncException, InterruptedException
{
assert moduleName != null;
assert dstPathName != null;
assert dstPathName.startsWith(Text.SLASH);
FileSelection fileSelection =
Util.defaultIfNull(_fileSelectionOrNull,
FileSelection.EXACT);
List<String> serverArgs = createServerArgs(Mode.REMOTE_SEND,
fileSelection);
serverArgs.add(moduleName + dstPathName);
if (_log.isLoggable(Level.FINE)) {
_log.fine(String.format(
"file selection: %s, src: %s, dst: %s, remote " +
"args: %s",
fileSelection, _srcPaths, dstPathName, serverArgs));
}
ClientSessionConfig cfg = new ClientSessionConfig(_in,
_out,
_charset,
fileSelection == FileSelection.RECURSE,
_stderr);
SessionStatus status = cfg.handshake(moduleName, serverArgs,
_authProvider);
if (_log.isLoggable(Level.FINE)) {
_log.fine("handshake status: " + status);
}
if (status == SessionStatus.ERROR) {
return Result.failure();
} else if (status == SessionStatus.EXIT) {
return Result.success();
}
try {
Sender sender = Sender.Builder.newClient(_in,
_out,
_srcPaths,
cfg.checksumSeed()).
filterMode(_isDelete ? FilterMode.SEND
: FilterMode.NONE).
charset(_charset).
fileSelection(fileSelection).
isPreserveLinks(_isPreserveLinks).
isPreserveUser(_isPreserveUser).
isPreserveGroup(_isPreserveGroup).
isNumericIds(_isNumericIds).
isInterruptible(_isInterruptible).
isSafeFileList(cfg.isSafeFileList()).build();
boolean isOK = _rsyncTaskExecutor.exec(sender);
return new Result(isOK, sender.statistics());
} finally {
if (_isOwnerOfExecutorService) {
if (_log.isLoggable(Level.FINE)) {
_log.fine("shutting down " + _executorService);
}
_executorService.shutdown();
}
}
}
}
public class Receive
{
private final String _moduleName;
private final Iterable<String> _srcPathNames;
private Receive(String moduleName, Iterable<String> srcPathNames)
{
assert moduleName != null;
assert srcPathNames != null;
_moduleName = moduleName;
_srcPathNames = srcPathNames;
}
public Result to(Path dstPath)
throws RsyncException, InterruptedException
{
assert dstPath != null;
FileSelection fileSelection =
Util.defaultIfNull(_fileSelectionOrNull,
FileSelection.EXACT);
List<String> serverArgs = createServerArgs(Mode.REMOTE_RECEIVE,
fileSelection);
for (String s : _srcPathNames) {
assert s.startsWith(Text.SLASH) : s;
serverArgs.add(_moduleName + s);
}
if (_log.isLoggable(Level.FINE)) {
_log.fine(String.format("file selection: %s, src: %s, dst: " +
"%s, remote args: %s",
fileSelection, _srcPathNames,
dstPath, serverArgs));
}
ClientSessionConfig cfg = new ClientSessionConfig(_in,
_out,
_charset,
fileSelection == FileSelection.RECURSE,
_stderr);
SessionStatus status = cfg.handshake(_moduleName, serverArgs,
_authProvider);
if (_log.isLoggable(Level.FINE)) {
_log.fine("handshake status: " + status);
}
if (status == SessionStatus.ERROR) {
return Result.failure();
} else if (status == SessionStatus.EXIT) {
return Result.success();
}
try {
Generator generator = new Generator.Builder(_out,
cfg.checksumSeed()).
charset(cfg.charset()).
fileSelection(fileSelection).
isDelete(_isDelete).
isPreserveLinks(_isPreserveLinks).
isPreservePermissions(_isPreservePermissions).
isPreserveTimes(_isPreserveTimes).
isPreserveUser(_isPreserveUser).
isPreserveGroup(_isPreserveGroup).
isNumericIds(_isNumericIds).
isIgnoreTimes(_isIgnoreTimes).
isAlwaysItemize(_verbosity > 1).
isInterruptible(_isInterruptible).build();
Receiver receiver = new Receiver.Builder(generator, _in,
dstPath).
filterMode(FilterMode.SEND).
isDeferWrite(_isDeferWrite).
isExitAfterEOF(true).
isExitEarlyIfEmptyList(true).
isReceiveStatistics(true).
isSafeFileList(cfg.isSafeFileList()).build();
boolean isOK = _rsyncTaskExecutor.exec(generator, receiver);
return new Result(isOK, receiver.statistics());
} finally {
if (_isOwnerOfExecutorService) {
if (_log.isLoggable(Level.FINE)) {
_log.fine("shutting down " + _executorService);
}
_executorService.shutdown();
}
}
}
}
List<String> toListOfStrings(Iterable<Path> paths)
{
List<String> srcPathNames = new LinkedList<>();
for (Path p : paths) {
srcPathNames.add(p.toString());
}
return srcPathNames;
}
private List<String> createServerArgs(Mode mode,
FileSelection fileSelection)
{
assert mode != null;
assert fileSelection != null;
List<String> serverArgs = new LinkedList<>();
serverArgs.add("--server");
boolean isPeerSender = mode != Mode.REMOTE_SEND;
if (isPeerSender) {
serverArgs.add("--sender");
}
StringBuilder sb = new StringBuilder();
sb.append("-");
for (int i = 0; i < _verbosity; i++) {
sb.append("v");
}
if (_isPreserveLinks) {
sb.append("l");
}
if (fileSelection == FileSelection.TRANSFER_DIRS) {
sb.append("d");
}
if (_isPreservePermissions) {
sb.append("p");
}
if (_isPreserveTimes) {
sb.append("t");
}
if (_isPreserveUser) {
sb.append("o");
}
if (_isPreserveGroup) {
sb.append("g");
}
if (_isPreserveDevices) {
sb.append("D");
}
if (_isIgnoreTimes) {
sb.append("I");
}
if (fileSelection == FileSelection.RECURSE) {
sb.append("r");
}
sb.append("e");
sb.append(".");
if (fileSelection == FileSelection.RECURSE) {
sb.append("i");
}
sb.append("s");
sb.append("f");
serverArgs.add(sb.toString());
if (_isDelete && mode == Mode.REMOTE_SEND) {
serverArgs.add("--delete");
}
if (_isNumericIds) {
serverArgs.add("--numeric-ids");
}
if (_isDelete &&
_fileSelectionOrNull == FileSelection.TRANSFER_DIRS)
{
// seems like it's only safe to use --delete and --dirs with
// rsync versions that happens to support --no-r
serverArgs.add("--no-r");
}
if (_isPreserveDevices && !_isPreserveSpecials) {
serverArgs.add("--no-specials");
}
serverArgs.add("."); // arg delimiter
return serverArgs;
}
}
private static class ConsoleAuthProvider implements AuthProvider
{
private final Console console = System.console();
@Override
public String getUser() throws IOException
{
if (console == null) {
throw new IOException("no console available");
}
return console.readLine("User name: ");
}
@Override
public char[] getPassword() throws IOException
{
if (console == null) {
throw new IOException("no console available");
}
return console.readPassword("Password: ");
}
}
public static class Builder
{
private AuthProvider _authProvider = new ConsoleAuthProvider();
private boolean _isAlwaysItemize;
private boolean _isDeferWrite;
private boolean _isDelete;
private boolean _isIgnoreTimes;
private boolean _isPreserveDevices;
private boolean _isPreserveSpecials;
private boolean _isPreserveLinks;
private boolean _isPreserveUser;
private boolean _isPreserveGroup;
private boolean _isNumericIds;
private boolean _isPreservePermissions;
private boolean _isPreserveTimes;
private Charset _charset = Charset.forName(Text.UTF8_NAME);
private ExecutorService _executorService;
private FileSelection _fileSelection;
private int _verbosity;
private PrintStream _stderr = System.err;
public Local buildLocal()
{
return new RsyncClient(this).new Local();
}
public Remote buildRemote(ReadableByteChannel in,
WritableByteChannel out,
boolean isInterruptible)
{
return new RsyncClient(this).new Remote(in, out, isInterruptible);
}
public Builder authProvider(AuthProvider authProvider)
{
_authProvider = authProvider;
return this;
}
public Builder isAlwaysItemize(boolean isAlwaysItemize)
{
_isAlwaysItemize = isAlwaysItemize;
return this;
}
public Builder isDeferWrite(boolean isDeferWrite)
{
_isDeferWrite = isDeferWrite;
return this;
}
public Builder isDelete(boolean isDelete)
{
_isDelete = isDelete;
return this;
}
public Builder isIgnoreTimes(boolean isIgnoreTimes)
{
_isIgnoreTimes = isIgnoreTimes;
return this;
}
public Builder isPreserveDevices(boolean isPreserveDevices)
{
_isPreserveDevices = isPreserveDevices;
return this;
}
public Builder isPreserveSpecials(boolean isPreserveSpecials)
{
_isPreserveSpecials = isPreserveSpecials;
return this;
}
public Builder isPreserveLinks(boolean isPreserveLinks)
{
_isPreserveLinks = isPreserveLinks;
return this;
}
public Builder isPreserveUser(boolean isPreserveUser)
{
_isPreserveUser = isPreserveUser;
return this;
}
public Builder isPreserveGroup(boolean isPreserveGroup)
{
_isPreserveGroup = isPreserveGroup;
return this;
}
public Builder isNumericIds(boolean isNumericIds)
{
_isNumericIds = isNumericIds;
return this;
}
public Builder isPreservePermissions(boolean isPreservePermissions)
{
_isPreservePermissions = isPreservePermissions;
return this;
}
public Builder isPreserveTimes(boolean isPreserveTimes)
{
_isPreserveTimes = isPreserveTimes;
return this;
}
/**
*
* @throws UnsupportedCharsetException if charset is not supported
*/
public Builder charset(Charset charset)
{
Util.validateCharset(charset);
_charset = charset;
return this;
}
public Builder executorService(ExecutorService executorService)
{
_executorService = executorService;
return this;
}
public Builder fileSelection(FileSelection fileSelection)
{
_fileSelection = fileSelection;
return this;
}
public Builder stderr(PrintStream stderr)
{
_stderr = stderr;
return this;
}
public Builder verbosity(int verbosity)
{
_verbosity = verbosity;
return this;
}
}
private static final Logger _log =
Logger.getLogger(RsyncClient.class.getName());
private final AuthProvider _authProvider;
private final boolean _isAlwaysItemize;
private final boolean _isDeferWrite;
private final boolean _isDelete;
private final boolean _isIgnoreTimes;
private final boolean _isOwnerOfExecutorService;
private final boolean _isPreserveDevices;
private final boolean _isPreserveSpecials;
private final boolean _isPreserveLinks;
private final boolean _isPreserveUser;
private final boolean _isPreserveGroup;
private final boolean _isNumericIds;
private final boolean _isPreservePermissions;
private final boolean _isPreserveTimes;
private final Charset _charset;
private final ExecutorService _executorService;
private final FileSelection _fileSelectionOrNull;
private final int _verbosity;
private final PrintStream _stderr;
private final RsyncTaskExecutor _rsyncTaskExecutor;
private RsyncClient(Builder builder)
{
assert builder != null;
_authProvider = builder._authProvider;
_isAlwaysItemize = builder._isAlwaysItemize;
_isDeferWrite = builder._isDeferWrite;
_isDelete = builder._isDelete;
_isIgnoreTimes = builder._isIgnoreTimes;
_isPreserveDevices = builder._isPreserveDevices;
_isPreserveSpecials = builder._isPreserveSpecials;
_isPreserveUser = builder._isPreserveUser;
_isPreserveGroup = builder._isPreserveGroup;
_isPreserveLinks = builder._isPreserveLinks;
_isNumericIds = builder._isNumericIds;
_isPreservePermissions = builder._isPreservePermissions;
_isPreserveTimes = builder._isPreserveTimes;
_charset = builder._charset;
if (builder._executorService == null) {
_executorService = Executors.newCachedThreadPool();
_isOwnerOfExecutorService = true;
} else {
_executorService = builder._executorService;
_isOwnerOfExecutorService = false;
}
_rsyncTaskExecutor = new RsyncTaskExecutor(_executorService);
_fileSelectionOrNull = builder._fileSelection;
_verbosity = builder._verbosity;
_stderr = builder._stderr;
}
}