org.cryptomator.ui.Cryptomator.java Source code

Java tutorial

Introduction

Here is the source code for org.cryptomator.ui.Cryptomator.java

Source

/*******************************************************************************
 * Copyright (c) 2014, 2016 cryptomator.org
 * This file is licensed under the terms of the MIT license.
 * See the LICENSE.txt file for more info.
 * 
 * Contributors:
 *     Tillmann Gaida - initial implementation
 *     Sebastian Stenzel - refactoring
 ******************************************************************************/
package org.cryptomator.ui;

import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.util.ApplicationVersion;
import org.cryptomator.ui.util.SingleInstanceManager;
import org.cryptomator.ui.util.SingleInstanceManager.RemoteInstance;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javafx.application.Application;

public class Cryptomator {

    public static final CompletableFuture<Consumer<File>> OPEN_FILE_HANDLER = new CompletableFuture<>();
    private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
    private static final Set<Runnable> SHUTDOWN_TASKS = new ConcurrentHashSet<>();

    public static void main(String[] args) {
        LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"),
                SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);

        if (SystemUtils.IS_OS_MAC_OSX) {
            addOsxFileOpenHandler();
        }

        new CleanShutdownPerformer().registerShutdownHook();

        final Optional<RemoteInstance> runningInstance = SingleInstanceManager
                .getRemoteInstance(MainApplication.APPLICATION_KEY);
        if (runningInstance.isPresent()) {
            sendArgsToRunningInstance(args, runningInstance);
        } else {
            Application.launch(MainApplication.class, args);
        }
    }

    private static void addOsxFileOpenHandler() {
        /*
         * On OSX we're in an awkward position. We need to register a handler in the main thread of this application. However, we can't
         * even pass objects to the application, so we're forced to use a static CompletableFuture for the handler, which actually opens
         * the file in the application.
         * 
         * Code taken from https://github.com/axet/desktop/blob/master/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
         */
        try {
            final Class<?> applicationClass = Class.forName("com.apple.eawt.Application");
            final Class<?> openFilesHandlerClass = Class.forName("com.apple.eawt.OpenFilesHandler");
            final Method getApplication = applicationClass.getMethod("getApplication");
            final Object application = getApplication.invoke(null);
            final Method setOpenFileHandler = applicationClass.getMethod("setOpenFileHandler",
                    openFilesHandlerClass);

            final ClassLoader openFilesHandlerClassLoader = openFilesHandlerClass.getClassLoader();
            final OpenFilesHandlerClassHandler openFilesHandlerHandler = new OpenFilesHandlerClassHandler();
            final Object openFilesHandlerObject = Proxy.newProxyInstance(openFilesHandlerClassLoader,
                    new Class<?>[] { openFilesHandlerClass }, openFilesHandlerHandler);

            setOpenFileHandler.invoke(application, openFilesHandlerObject);
        } catch (ReflectiveOperationException | RuntimeException e) {
            // Since we're trying to call OS-specific code, we'll just have
            // to hope for the best.
            LOG.error("exception adding OSX file open handler", e);
        }
    }

    private static void sendArgsToRunningInstance(String[] args, final Optional<RemoteInstance> remoteInstance) {
        try (RemoteInstance instance = remoteInstance.get()) {
            LOG.info("An instance of Cryptomator is already running at {}.", instance.getRemotePort());
            for (int i = 0; i < args.length; i++) {
                remoteInstance.get().sendMessage(args[i], 100);
            }
        } catch (Exception e) {
            LOG.error("Error forwarding arguments to remote instance", e);
        }
    }

    public static void addShutdownTask(Runnable r) {
        SHUTDOWN_TASKS.add(r);
    }

    public static void removeShutdownTask(Runnable r) {
        SHUTDOWN_TASKS.remove(r);
    }

    private static class CleanShutdownPerformer extends Thread {
        @Override
        public void run() {
            LOG.debug("Shutting down");
            SHUTDOWN_TASKS.forEach(r -> {
                try {
                    r.run();
                } catch (RuntimeException e) {
                    LOG.error("exception while shutting down", e);
                }
            });
            SHUTDOWN_TASKS.clear();
        }

        public void registerShutdownHook() {
            Runtime.getRuntime().addShutdownHook(this);
        }
    }

    private static void handleOpenFileRequest(File file) {
        try {
            OPEN_FILE_HANDLER.get().accept(file);
        } catch (Exception e) {
            LOG.error("exception handling file open event for file " + file.getAbsolutePath(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Handler class taken from https://github.com/axet/desktop/blob/master/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
     */
    private static class OpenFilesHandlerClassHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("openFiles")) {
                final Class<?> openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent");
                final Method getFiles = openFilesEventClass.getMethod("getFiles");
                Object e = args[0];
                try {
                    @SuppressWarnings("unchecked")
                    final List<File> ff = (List<File>) getFiles.invoke(e);
                    for (File f : ff) {
                        handleOpenFileRequest(f);
                    }
                } catch (RuntimeException ee) {
                    throw ee;
                } catch (Exception ee) {
                    throw new RuntimeException(ee);
                }
            }
            return null;
        }
    }
}