/*
* Copyright 2014 Omeed Safi
*
* 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 ms.safi.btsync;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
/**
* BTSyncApp is used for starting an instance of BitTorrent Sync. A BitTorrent Sync v1.3.105
* executable for linux and windows platforms are bundled with this library.
*
* @author Omeed Safi
*/
public class BTSyncApp implements AutoCloseable {
private String deviceName;
private int listeningPort;
private boolean useUpnp;
private String storagePath;
private boolean useGui;
private String listen;
private String login;
private String password;
private String apiKey;
private File btSyncTmpFolder = new File(System.getProperty("java.io.tmpdir"), "BTSyncJava");
private File btSyncExecutable;
private File btSyncConf = new File(btSyncTmpFolder, "sync.conf");
private Process runningAppProcess;
/**
* Constructs a new BTSyncApp used to start a BitTorrent Sync instance with default
* settings.
*
* <p>Default Settings:
* <ul>
* <li>device_name = "BTSync-Java"
* <li>listening_port = 0 (Meaning random port assignment)
* <li>use_upnp = false
* <li>storage_path = ./
* <li>use_gui = false
* <li>listen = 127.0.0.1:18080
* <li>login = user
* <li>password = password
* </ul>
*
* @param apiKey the BitTorrent Sync API Key
*/
public BTSyncApp(String apiKey) {
this.deviceName = "BTSyncJava";
this.listeningPort = 0;
this.useUpnp = false;
this.storagePath = "./";
this.useGui = false;
this.listen = "127.0.0.1:18080";
this.login = "user";
this.password = "password";
this.apiKey = apiKey;
try {
FileUtils.deleteDirectory(btSyncTmpFolder);
Files.createDirectory(btSyncTmpFolder.toPath());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Sets the device_name for BitTorrent Sync to use. The default is <code>BTSync-Java</code>
*
* @param deviceName the deviceName to set
* @return the updated BTSyncApp instance
*/
public BTSyncApp setDeviceName(String deviceName) {
this.deviceName = deviceName;
return this;
}
/**
* Sets the listening_port for BitTorrent Sync to use. The default is <code>0</code> which
* means a port is randomly assigned
*
* @param listeningPort the listeningPort to set
* @return the updated BTSyncApp instance
*/
public BTSyncApp setListeningPort(int listeningPort) {
this.listeningPort = listeningPort;
return this;
}
/**
* Sets the use_upnp flag for BitTorrent Sync. The default is <code>false</code>.
* Need more research to understand what this flag actually is for better description.
*
* @param useUpnp whether BitTorrent Sync will use UPNP or not
* @return the updated BTSyncApp instance
*/
public BTSyncApp setUseUpnp(boolean useUpnp) {
this.useUpnp = useUpnp;
return this;
}
/**
* Sets the storage_path for BitTorrent Sync to use. The default is <code>./</code>
*
* @param storagePath the storagePath to set
* @return the updated BTSyncApp instance
*/
public BTSyncApp setStoragePath(String storagePath) {
this.storagePath = storagePath;
return this;
}
/**
* Sets the use_gui for BitTorrent Sync. The default is <code>false</code>.
* The use_gui option sets whether or not BitTorrent Sync should be shown when running.
* Effects of setting the use_gui value to <code>true</code> on linux is unknown as linux only has webui
*
* @param useGui whether the BT Sync GUI is shown or not
* @return the updated BTSyncApp instance
*/
public BTSyncApp setUseGui(boolean useGui) {
this.useGui = useGui;
return this;
}
/**
* Sets the address:port that BitTorrent Sync will listen on. The default is <code>127.0.0.1:18080</code>.
*
* @param listen the address:port
* @return the updated BTSyncApp instance
*/
public BTSyncApp setListen(String listen) {
this.listen = listen;
return this;
}
/**
* Sets the username for clients to use to accessing BitTorrent Sync API methods.
* The default is <code>user</code>.
*
* @param login the username
* @return the updated BTSyncApp instance
*/
public BTSyncApp setLogin(String login) {
this.login = login;
return this;
}
/**
* Sets the password for clients to use to accessing BitTorrent Sync API methods.
* The default is <code>password</code>.
*
* @param password the password
* @return the updated BTSyncApp instance
*/
public BTSyncApp setPassword(String password) {
this.password = password;
return this;
}
/**
* Starts BitTorrent Sync and returns an instance of BTSyncClient for accessing
* the BitTorrent Sync API.
*
* <p>See the class description for the current limitations of this method.
*
* @return a BTSyncClient preconfigured for accessing the BitTorrent Sync started
*/
public BTSyncClient startBtSync() {
if(SystemUtils.IS_OS_WINDOWS) {
btSyncExecutable = new File(btSyncTmpFolder, "btsync.exe");
return this.startBtSyncWindows();
} else if(SystemUtils.IS_OS_UNIX) {
btSyncExecutable = new File(btSyncTmpFolder, "btsync");
return this.startBtSyncLinux();
} else if(SystemUtils.IS_OS_MAC_OSX) {
return this.startBtSyncMacOsx();
} else {
return null;
}
}
private BTSyncClient startBtSyncWindows() {
this.extractWinBtSync();
this.buildConf();
try {
//Print this line using logging api
//System.out.println(btSyncExecutable.getCanonicalPath() + " /config " + btSyncConf.getCanonicalPath());
runningAppProcess = Runtime.getRuntime().exec(btSyncExecutable.getCanonicalPath() + " /config " + btSyncConf.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
return new BTSyncClient(this.listen, this.login, this.password);
}
private BTSyncClient startBtSyncLinux() {
this.extractLinuxBtSync();
this.buildConf();
try {
//Print this line using logging api
//System.out.println(btSyncExecutable.getCanonicalPath() + " --config " + btSyncConf.getCanonicalPath());
runningAppProcess = Runtime.getRuntime().exec(btSyncExecutable.getCanonicalPath() + " --config " + btSyncConf.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
return new BTSyncClient(this.listen, this.login, this.password);
}
private BTSyncClient startBtSyncMacOsx() {
return null;
}
private void buildConf() {
try {
Files.createFile(btSyncConf.toPath());
btSyncConf.setReadable(true, false);
btSyncConf.setWritable(true, false);
JsonFactory jFactory = new JsonFactory();
JsonGenerator jGenerator = jFactory.createJsonGenerator(btSyncConf, JsonEncoding.UTF8);
jGenerator.useDefaultPrettyPrinter();
jGenerator.writeStartObject(); // {
jGenerator.writeStringField("device_name", this.deviceName);
jGenerator.writeNumberField("listening_port", this.listeningPort);
jGenerator.writeBooleanField("check_for_updates", false); // NEVER CHECK FOR UPDATES
jGenerator.writeBooleanField("use_upnp", useUpnp);
jGenerator.writeStringField("storage_path", this.storagePath);
jGenerator.writeBooleanField("use_gui", this.useGui);
jGenerator.writeFieldName("webui");
jGenerator.writeStartObject(); // {
jGenerator.writeStringField("listen", this.listen);
jGenerator.writeStringField("login", this.login);
jGenerator.writeStringField("password", this.password);
jGenerator.writeStringField("api_key", this.apiKey);
jGenerator.writeEndObject(); // }
jGenerator.writeEndObject(); // }
jGenerator.close();
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void extractWinBtSync() {
try {
URL url = getClass().getClassLoader().getResource("btsync-win.exe");
InputStream in = url.openStream();
Files.copy(in, btSyncExecutable.toPath());
btSyncExecutable.setExecutable(true, false);
btSyncExecutable.setReadable(true, false);
btSyncExecutable.setWritable(true, false);
} catch (IOException e) {
e.printStackTrace();
}
}
private void extractLinuxBtSync() {
try {
URL url = getClass().getClassLoader().getResource("btsync-linux-i386");
InputStream in = url.openStream();
Files.copy(in, btSyncExecutable.toPath());
btSyncExecutable.setExecutable(true, false);
btSyncExecutable.setReadable(true, false);
btSyncExecutable.setWritable(true, false);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void close() {
// Close BTSync if running
if(isAlive(runningAppProcess)) {
runningAppProcess.destroy();
try {
runningAppProcess.waitFor();
} catch (InterruptedException e) {
System.out.println("Exception while waiting for the BTSync process to end");
e.printStackTrace();
}
}
// Cleanup files
try {
FileUtils.deleteDirectory(btSyncTmpFolder);
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean isAlive(Process p) {
try {
p.exitValue();
return false;
} catch (IllegalThreadStateException e) {
return true;
}
}
}