/*
* Kontalk Java client
* Copyright (C) 2016 Kontalk Devteam <devteam@kontalk.org>
*
* 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 org.kontalk;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang.SystemUtils;
import org.kontalk.crypto.PGPUtils;
import org.kontalk.misc.KonException;
import org.kontalk.system.Control;
import org.kontalk.util.CryptoUtils;
import org.kontalk.util.EncodingUtils;
import org.kontalk.util.Tr;
import org.kontalk.view.View;
/**
* @author Alexander Bikadorov {@literal <bikaejkb@mail.tu-berlin.de>}
*/
public final class Kontalk {
private static final Logger LOGGER = Logger.getLogger(Kontalk.class.getName());
public static final String VERSION = "3.1.2";
private final Path mAppDir;
private ServerSocket mRunLock = null;
Kontalk() {
// platform dependent configuration directory
this(Paths.get(System.getProperty("user.home"),
SystemUtils.IS_OS_WINDOWS ? "Kontalk" : ".kontalk"));
}
Kontalk(Path appDir) {
mAppDir = appDir.toAbsolutePath();
}
int start(boolean ui) {
// check if already running
int port = (1 << 14) + (1 << 15) + mAppDir.hashCode() % (1 << 14);
try {
InetAddress addr = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
mRunLock = new ServerSocket(port, 10, addr);
} catch(java.net.BindException ex) {
LOGGER.severe("already running");
return 2;
} catch(IOException ex) {
LOGGER.log(Level.WARNING, "can't create socket", ex);
}
// initialize translation
Tr.init();
// check java version
String jVersion = System.getProperty("java.version");
if (jVersion.startsWith("1.7")) {
// NOTE: we wont be here if 7 is used; still here for the bright
// future
View.showWrongJavaVersionDialog();
LOGGER.severe("java too old: "+jVersion);
return 3;
}
// create app directory
boolean created = mAppDir.toFile().mkdirs();
if (created)
LOGGER.info("created application directory: "+mAppDir);
if (!Files.isWritable(mAppDir)) {
LOGGER.severe("invalid app directory: "+mAppDir);
return 4;
}
// logging
Logger logger = Logger.getLogger("");
logger.setLevel(Level.CONFIG);
for (Handler h : logger.getHandlers()) {
if (h instanceof ConsoleHandler)
h.setLevel(Level.CONFIG);
}
String logPath = mAppDir.resolve("debug.log").toString();
Handler fileHandler = null;
try {
fileHandler = new FileHandler(logPath, 1024*1000, 1, true);
} catch (IOException | SecurityException ex) {
LOGGER.log(Level.WARNING, "can't log to file", ex);
}
if (fileHandler != null) {
fileHandler.setLevel(Level.INFO);
fileHandler.setFormatter(new SimpleFormatter());
logger.addHandler(fileHandler);
}
LOGGER.info("--START, version: "+VERSION+"--");
// fix crypto restriction
CryptoUtils.removeCryptographyRestrictions();
// register security provider
PGPUtils.registerProvider();
Control control;
try {
control = new Control(mAppDir);
} catch (KonException ex) {
LOGGER.log(Level.SEVERE, "can't create application", ex);
return 5;
}
// handle shutdown signals/System.exit() calls
Runtime.getRuntime().addShutdownHook(new Thread("Shutdown Hook") {
@Override
public void run() {
// NOTE: logging does not work here anymore
control.shutDown(false);
Kontalk.this.removeLock();
System.out.println("Kontalk: shutdown finished");
}
});
control.launch(ui);
return 0;
}
private void removeLock() {
if (mRunLock == null) {
LOGGER.warning("no lock");
return;
}
try {
mRunLock.close();
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "can't close run socket", ex);
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
LOGGER.setLevel(Level.ALL);
// parse args, i18n?
Options options = new Options();
options.addOption("h", "help", false, "show this help message");
options.addOption(Option.builder("d")
.argName("app_dir")
.hasArg()
.longOpt("app-dir")
.desc("set custom configuration directory")
.build()
);
options.addOption("c", "no-gui", false, "run without user interface");
CommandLineParser parser = new DefaultParser();
CommandLine cmd;
try {
cmd = parser.parse(options, args);
} catch (ParseException e) {
showHelp(options);
return;
}
if (cmd.hasOption("h")) {
showHelp(options);
return;
}
String appDir = cmd.getOptionValue("d", "");
Kontalk app = !appDir.isEmpty() ?
new Kontalk(Paths.get(appDir)) :
new Kontalk();
int returnCode = app.start(!cmd.hasOption("c"));
if (returnCode != 0)
// didn't work
System.exit(returnCode);
new Thread("Kontalk Main") {
@Override
public void run() {
try {
// wait until exit call
Object lock = new Object();
synchronized (lock) {
lock.wait();
}
} catch (InterruptedException ex) {
LOGGER.log(Level.WARNING, "interrupted while waiting", ex);
}
}
}.start();
}
private static void showHelp(Options options) {
HelpFormatter formatter = new HelpFormatter();
String eol = EncodingUtils.EOL;
formatter.printHelp("java -jar [kontalk_jar]",
eol + "Kontalk Java Desktop Client" + eol,
options,
"",
true);
}
}