/*
*
* Panbox - encryption for cloud storage
* Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG
*
* 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/>.
*
* Additonally, third party code may be provided with notices and open source
* licenses from communities and third parties that govern the use of those
* portions, and any licenses granted hereunder do not alter any rights and
* obligations you may have under such open source licenses, however, the
* disclaimer of warranty and limitation of liability provisions of the GPLv3
* will apply to all the product.
*
*/
package org.panbox.desktop.linux;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.net.BindException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.panbox.PanboxConstants;
import org.panbox.Settings;
import org.panbox.core.crypto.AbstractObfuscatorFactory;
import org.panbox.core.crypto.FileObfuscatorFactory;
import org.panbox.core.crypto.randomness.SecureRandomWrapper;
import org.panbox.desktop.common.clipboard.ClipboardHandler;
import org.panbox.desktop.common.clipboard.ClipboardObserver;
import org.panbox.desktop.common.devicemgmt.DeviceManagerException;
import org.panbox.desktop.common.gui.AboutWindow;
import org.panbox.desktop.common.gui.OperationAbortedException;
import org.panbox.desktop.common.gui.PanboxClientGUI;
import org.panbox.desktop.common.gui.shares.PanboxShare;
import org.panbox.desktop.common.sharemgmt.IPanboxService;
import org.panbox.desktop.common.sharemgmt.ShareManagerException;
import org.panbox.desktop.common.urihandler.PanboxHTTPServer;
import org.panbox.desktop.common.utils.DesktopApi;
import org.panbox.desktop.common.utils.SingleInstanceLock;
import org.panbox.desktop.linux.dbus.DBusService;
import org.panbox.desktop.linux.tray.PanboxTrayIcon;
import org.panbox.desktop.linux.tray.PanboxTrayIcon.TrayIconException;
/**
* @author palige
*
* Control class implementation for application startup and shutdown
*/
public class PanboxClient extends org.panbox.desktop.common.PanboxClient {
// static {
// PropertyConfigurator.configure("log4j.properties");
// }
private static boolean NO_DBUS_OPTION = false;
private static String NO_DBUS = "--no-dbus";
private static boolean FALLBACK_TRAY_JAVA_OPTION = false;
private static String FALLBACK_TRAY_JAVA = "--fallback-tray-java";
private static boolean FALLBACK_TRAY_GTK_OPTION = false;
private static String FALLBACK_TRAY_GTK = "--fallback-tray-gtk";
private static String[] vfsoptions = null;
public static void main(String[] args) {
setGuiLookAndFeel();
// params
boolean showGui = true;
ArrayList<String> vfsopts = new ArrayList<String>();
for (int i = 0; i < args.length; i++) {
if (args[i].equalsIgnoreCase(NO_DBUS)) {
NO_DBUS_OPTION = true;
// no dbus alo means fallbaxk tray icon implicitely
FALLBACK_TRAY_JAVA_OPTION = true;
logger.info("Found argument " + NO_DBUS
+ ", falling back to java tray support");
} else if (args[i].equalsIgnoreCase(FALLBACK_TRAY_JAVA)) {
FALLBACK_TRAY_JAVA_OPTION = true;
logger.info("Found argument " + FALLBACK_TRAY_JAVA);
} else if (args[i].equalsIgnoreCase(FALLBACK_TRAY_GTK)) {
FALLBACK_TRAY_GTK_OPTION = true;
logger.info("Found argument " + FALLBACK_TRAY_GTK);
} else if (args[i].equals("-d") || args[i].equals("-f")
|| args[i].equals("-s")) {
vfsopts.add(args[i]);
} else if (args[i].equals("-o")) {
if ((args.length > (i + 1)) && (!args[i + 1].startsWith("-"))) {
vfsopts.add(args[i]);
vfsopts.add(args[i + 1]);
} else if ((args.length > (i + 1))
&& (args[i + 1].startsWith("-"))) {
logger.error("Invalid fuse argument: " + (args[i + 1]));
} else {
logger.error("Invalid fuse argument.");
}
} else if (args[i].equalsIgnoreCase("minimized")) {
showGui = false;
} else {
if (!((i > 0) && (args[i - 1].equals("-o")))) {
logger.error("Unknown argument: " + args[i]);
}
}
}
vfsoptions = vfsopts.toArray(new String[vfsopts.size()]);
try {
vfsControl = VFSControl.getInstance(vfsoptions);
SecureRandomWrapper.getInstance();
AbstractObfuscatorFactory.getFactory(FileObfuscatorFactory.class);
instance = new PanboxClient(new LinuxPanboxService());
instance.registerMainwindow(new PanboxClientGUI(instance));
if (FALLBACK_TRAY_JAVA_OPTION && (instance.fallbackTrayApp == null)) {
logger.fatal("No TrayIcon could be created - overriding default close operation for main window");
instance.mainWindow
.setDefaultCloseOperation(PanboxClientGUI.DO_NOTHING_ON_CLOSE);
instance.mainWindow
.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(
java.awt.event.WindowEvent windowEvent) {
if (instance.checkShutdown()) {
System.exit(0);
}
}
});
}
if (showGui) {
instance.showGui();
}
instance.executeVersionCheck();
instance.showTrayMessage(bundle
.getString("PanboxClient.panboxStartedMessage"));
} catch (IOException e) {
// do nothing
} catch (OperationAbortedException e) {
logger.error("PanboxClient : Wizard has been aborted.");
System.exit(DesktopApi.EXIT_ERR_WIZARD_ABORTED);
} catch (ShareManagerException | DeviceManagerException e) {
logger.error("ShareManager or DeviceManager may be broken.", e);
JOptionPane.showMessageDialog(null, bundle
.getString("client.startup.failedCorruptManager.message"),
bundle.getString("client.startup.error.title"),
JOptionPane.ERROR_MESSAGE);
System.exit(DesktopApi.EXIT_ERR_SERVICE_NOT_AVAILBLE);
} catch (UnsatisfiedLinkError e) {
logger.error("Could not find a specified library.", e);
JOptionPane.showMessageDialog(null,
bundle.getString("client.startup.failedToLoadLib.message"),
bundle.getString("client.startup.error.title"),
JOptionPane.ERROR_MESSAGE);
System.exit(DesktopApi.EXIT_ERR_UNKNOWN);
} catch (Exception e) {
logger.error("An unknown error occurred while Panbox startup.", e);
JOptionPane.showMessageDialog(null,
bundle.getString("client.startup.failedUnknown.message"),
bundle.getString("client.startup.error.title"),
JOptionPane.ERROR_MESSAGE);
System.exit(DesktopApi.EXIT_ERR_UNKNOWN);
}
}
private void showTrayMessage(String string) {
showTrayMessage("", string, MessageType.INFO);
}
private static void setGuiLookAndFeel() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException
| IllegalAccessException | UnsupportedLookAndFeelException e) {
logger.debug("Could not load system specific look and feel. Will use default one.");
}
}
private static PanboxClient instance;
/**
* @return the instance
*/
public static PanboxClient getInstance() {
return instance;
}
private PanboxClient(IPanboxService service) throws Exception {
super(service);
logger.info("Finished initialization");
if (splash != null) { // Splashscreen is shown!
try {
splash.close(); // we need to close splash here because
// minimized option could be used
} catch (Exception ex) {
// This should be ignored as this means that the splashScreen has been closed already!
logger.debug("LIN:PanboxClient : Tried to close splashscreen but there was none available.");
}
}
}
@Override
public void informAddShare(PanboxShare share) throws Exception {
MessageFormat formatter = new MessageFormat("", Settings.getInstance()
.getLocale());
formatter.applyPattern(bundle.getString("tray.addedShareMessage"));
showTrayMessage(formatter.format(new Object[] { share.getName() }));
}
@Override
public void informRemoveShare(PanboxShare share) throws Exception {
MessageFormat formatter = new MessageFormat("", Settings.getInstance()
.getLocale());
formatter.applyPattern(bundle.getString("tray.removedShareMessage"));
showTrayMessage(formatter.format(new Object[] { share.getName() }));
logger.debug("Share will be added to VFS view: " + share.getName());
}
@Override
public void panboxFolderChanged(String path) {
}
private static ClipboardHandler ch;
private static DBusService dBusService = DBusService.getInstance();
private static VFSControl vfsControl;
private void startClipboardHandler() {
// register clipboard handler to get access to panbox urls in
// clipboard
ClipboardObserver co = new ClipboardObserver();
ch = new ClipboardHandler();
ch.addObserver(co);
ch.start();
}
private void startURIServer() {
try {
PanboxHTTPServer.getInstance(this).start();
} catch (BindException e) {
logger.error("Error binding Panbox URI Handler to default port", e);
int ret = JOptionPane.showConfirmDialog(null, MessageFormat.format(
bundle.getString("PanboxClient.BindException.message"),
String.valueOf(PanboxConstants.PANBOX_DEFAULT_PORT)),
bundle.getString("error"), JOptionPane.YES_NO_OPTION);
if (ret == JOptionPane.YES_OPTION) {
Settings.getInstance().setUriHandlerSupported(false);
}
} catch (Exception e) {
logger.error("Error starting the Panbox URI Handler", e);
}
}
private void stopClipboardHandler() {
if (ch != null)
ch.stop();
}
public void restartTrayIcon() {
try {
PanboxTrayIcon.getInstance(FALLBACK_TRAY_GTK_OPTION).restart();
} catch (TrayIconException e) {
JOptionPane.showMessageDialog(null, e.getMessage(),
"TrayIcon error", JOptionPane.ERROR_MESSAGE);
logger.fatal("Could not start TrayIcon", e);
System.exit(-1);
}
}
private boolean startVFS() {
return vfsControl.mount();
}
private void stopVFS() {
vfsControl.unmount();
}
public void showGui() {
getMainWindow().setVisible(true);
getMainWindow().toFront();
// gui.setState(JFrame.NORMAL);
}
@Override
public void shutdown() {
stopClipboardHandler();
PanboxTrayIcon.getInstance(FALLBACK_TRAY_GTK_OPTION).stop();
dBusService.stop();
stopVFS();
SingleInstanceLock.forceUnlock();
}
@Override
public void setup() throws Exception {
if (!NO_DBUS_OPTION) {
dBusService.start();
} else {
logger.info("DBUS service startup canceled ...");
}
// mountShares();
if (!startVFS()) {
logger.fatal("Failed to mount VFS - exiting application");
System.exit(-1);
}
if (Settings.getInstance().isUriHandlerSupported()) {
startURIServer();
if (Settings.getInstance().isClipboardHandlerSupported()) {
startClipboardHandler();
}
}
try {
if (FALLBACK_TRAY_JAVA_OPTION) {
logger.info("Fallback tray icon support encabled. Starting fallback tray icon...");
fallbackJavaTray();
} else {
PanboxTrayIcon.getInstance(FALLBACK_TRAY_GTK_OPTION).start();
}
} catch (TrayIconException e) {
JOptionPane.showMessageDialog(null, e.getMessage(),
"TrayIcon error", JOptionPane.ERROR_MESSAGE);
logger.fatal("Could not start TrayIcon", e);
System.exit(-1);
}
}
private TrayIcon fallbackTrayApp;
private void fallbackJavaTray() {
logger.debug("initTray");
Image image = getPanboxIcon();
if (SystemTray.isSupported() && (image != null)) {
fallbackTrayApp = new TrayIcon(image);
} else {
logger.fatal("System is missing tray icon support!");
return;
}
fallbackTrayApp.setToolTip(bundle.getString("tray.toolTip"));
PopupMenu popupMenu = new PopupMenu();
MenuItem showClientItem = new MenuItem(
bundle.getString("tray.showPanboxClient"));
MenuItem openFolderItem = new MenuItem(
bundle.getString("tray.openPanboxFolder"));
MenuItem aboutItem = new MenuItem(bundle.getString("tray.about"));
MenuItem exitClientItem = new MenuItem(bundle.getString("tray.exit"));
ActionListener showPanboxActionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
logger.debug("showClientItem action called");
showGui();
}
};
showClientItem.addActionListener(showPanboxActionListener);
openFolderItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
logger.debug("openFolderItem action called");
try {
// try to open panbox folder
DesktopApi.open(vfsControl.getMountpoint());
} catch (IllegalArgumentException e1) {
JOptionPane.showMessageDialog(null,
bundle.getString("tray.CouldNotFindPanboxDrive"),
bundle.getString("tray.PanboxError"),
JOptionPane.ERROR_MESSAGE);
logger.error(
getClass().getName()
+ " : Error while determining Panbox drive on system. ",
e1);
}
}
});
aboutItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
logger.debug("LINUX:PanboxClient : aboutItem action called");
AboutWindow.getInstance().showWindow(5);
}
});
exitClientItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
logger.debug("exitClientItem action called");
System.exit(0);
}
});
popupMenu.add(showClientItem);
popupMenu.add(openFolderItem);
popupMenu.add(aboutItem);
popupMenu.add(exitClientItem);
fallbackTrayApp.setPopupMenu(popupMenu);
fallbackTrayApp.addActionListener(showPanboxActionListener);
SystemTray systemTray = SystemTray.getSystemTray();
try {
systemTray.add(fallbackTrayApp);
} catch (AWTException e) {
logger.debug("Could not add the PanboxClient tray app to the system tray.");
System.exit(0);
}
fallbackTrayApp.displayMessage(bundle.getString("tray.panboxStarted"),
bundle.getString("tray.nowReayMessage"), MessageType.INFO);
}
private static Image getPanboxIcon() {
Image image = new ImageIcon().getImage();
try {
InputStream stream = null;
stream = ClassLoader.class
.getResourceAsStream("/img/panbox-icon.png");
if (stream == null) {
stream = ClassLoader.class
.getResourceAsStream("/res/img/panbox-icon.png");
}
image = (stream != null) ? ImageIO.read(stream) : null;
} catch (IllegalArgumentException | IOException e) {
logger.warn("Could not obtain icon resource. Will use empty picture instead.");
}
return image;
}
@Override
protected boolean checkPanboxProcessesRunning() {
return CrashHandler.getInstance().checkPanboxProcessRunning();
}
@Override
protected boolean mountPointHandler() {
return CrashHandler.getInstance().umountPanbox();
}
@Override
protected boolean panboxMounted() {
return CrashHandler.getInstance().panboxMounted();
}
@Override
public void showTrayMessage(String title, String message, MessageType type) {
if (!FALLBACK_TRAY_JAVA_OPTION) {
try {
PanboxTrayIcon.getInstance(FALLBACK_TRAY_GTK_OPTION)
.showNotification(message);
} catch (TrayIconException e) {
logger.error("Error sending notification via tray icon!", e);
}
} else if (fallbackTrayApp != null) {
fallbackTrayApp.displayMessage(title, message, type);
}
}
/**
* Sun property pointing the main class and its arguments. Might not be
* defined on non Hotspot VM implementations.
*/
public static final String SUN_JAVA_COMMAND = "sun.java.command";
/**
* Restart the current Java application
*
* @throws IOException
*/
public void restartApplication() throws IOException {
if (!vfsControl.isUmountSafe() && !checkShutdown()) {
return;
} else {
try {
// java binary
String java = System.getProperty("java.home") + "/bin/java";
// vm arguments
List<String> vmArguments = ManagementFactory.getRuntimeMXBean()
.getInputArguments();
StringBuffer vmArgsOneLine = new StringBuffer();
for (String arg : vmArguments) {
// if it's the agent argument : we ignore it otherwise the
// address of the old application and the new one will be in
// conflict
if (!arg.contains("-agentlib")) {
vmArgsOneLine.append(arg);
vmArgsOneLine.append(" ");
}
}
// init the command to execute, add the vm args
final StringBuffer cmd = new StringBuffer(java + " "
+ vmArgsOneLine);
// program main and program arguments
String[] mainCommand = System.getProperty(SUN_JAVA_COMMAND)
.split(" ");
// program main is a jar
if (mainCommand[0].endsWith(".jar")) {
// if it's a jar, add -jar mainJar
cmd.append("-jar " + new File(mainCommand[0]).getPath());
} else {
// else it's a .class, add the classpath and mainClass
cmd.append("-cp \"" + System.getProperty("java.class.path")
+ "\" " + mainCommand[0]);
}
// finally add program arguments
for (int i = 1; i < mainCommand.length; i++) {
cmd.append(" ");
cmd.append(mainCommand[i]);
}
// execute the command in a shutdown hook, to be sure that all
// the
// resources have been disposed before restarting the
// application
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
Runtime.getRuntime().exec(cmd.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
});
shutdown();
// exit
System.exit(0);
} catch (Exception e) {
// something went wrong
throw new IOException(
"Error while trying to restart the application", e);
}
}
}
public boolean checkShutdown() {
boolean res;
if (vfsControl.isUmountSafe()) {
int ret = JOptionPane.showConfirmDialog(null,
bundle.getString("client.reallyShutdown.message"),
bundle.getString("client.reallyShutdown.title"),
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (ret == JOptionPane.YES_OPTION) {
res = true;
} else {
res = false;
}
} else {
int ret = JOptionPane.showConfirmDialog(null,
bundle.getString("client.reallyShutdown.warning"),
bundle.getString("client.reallyShutdown.title"),
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
if (ret == JOptionPane.YES_OPTION) {
res = true;
} else {
res = false;
}
}
return res;
}
@Override
public void openShareFolder(String name) {
File mpoint = vfsControl.getMountpoint();
File vshare = new File(mpoint, name);
DesktopApi.open(vshare);
}
}