org.cryptomator.frontend.webdav.mount.MacOsXAppleScriptWebDavMounter.java Source code

Java tutorial

Introduction

Here is the source code for org.cryptomator.frontend.webdav.mount.MacOsXAppleScriptWebDavMounter.java

Source

/*******************************************************************************
 * Copyright (c) 2014, 2016 Sebastian Stenzel, Markus Kreusch
 * This file is licensed under the terms of the MIT license.
 * See the LICENSE.txt file for more info.
 * 
 * Contributors:
 *     Sebastian Stenzel - initial API and implementation, strategy fine tuning
 *     Markus Kreusch - Refactored WebDavMounter to use strategy pattern
 ******************************************************************************/
package org.cryptomator.frontend.webdav.mount;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.frontend.CommandFailedException;
import org.cryptomator.frontend.Frontend.MountParam;

@Singleton
final class MacOsXAppleScriptWebDavMounter implements WebDavMounterStrategy {

    private final Comparator<String> semVerComparator;

    @Inject
    MacOsXAppleScriptWebDavMounter(@Named("SemVer") Comparator<String> semVerComparator) {
        this.semVerComparator = semVerComparator;
    }

    @Override
    public boolean shouldWork(Map<MountParam, Optional<String>> mountParams) {
        return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") >= 0;
    }

    @Override
    public void warmUp(int serverPort) {
        // no-op
    }

    @Override
    public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
        try {
            String mountAppleScript = String.format("mount volume \"%s\"", uri.toString());
            ProcessBuilder mount = new ProcessBuilder("/usr/bin/osascript", "-e", mountAppleScript);
            Process mountProcess = mount.start();
            String stdout = IOUtils.toString(mountProcess.getInputStream(), StandardCharsets.UTF_8);
            waitForProcessAndCheckSuccess(mountProcess, 1, TimeUnit.SECONDS);
            String volumeIdentifier = StringUtils.trim(StringUtils.removeStart(stdout, "file "));
            String waitAppleScript1 = String
                    .format("tell application \"Finder\" to repeat while not (\"%s\" exists)", volumeIdentifier);
            String waitAppleScript2 = "delay 0.1";
            String waitAppleScript3 = "end repeat";
            ProcessBuilder wait = new ProcessBuilder("/usr/bin/osascript", "-e", waitAppleScript1, "-e",
                    waitAppleScript2, "-e", waitAppleScript3);
            Process waitProcess = wait.start();
            waitForProcessAndCheckSuccess(waitProcess, 5, TimeUnit.SECONDS);
            return new MacWebDavMount(volumeIdentifier);
        } catch (IOException e) {
            throw new CommandFailedException(e);
        }
    }

    private static class MacWebDavMount extends AbstractWebDavMount {
        private final ProcessBuilder revealCommand;
        private final ProcessBuilder unmountCommand;

        private MacWebDavMount(String volumeIdentifier) {
            String openAppleScript = String.format("tell application \"Finder\" to open \"%s\"", volumeIdentifier);
            String activateAppleScript = String.format("tell application \"Finder\" to activate \"%s\"",
                    volumeIdentifier);
            String ejectAppleScript = String.format(
                    "tell application \"Finder\" to if \"%s\" exists then eject \"%s\"", volumeIdentifier,
                    volumeIdentifier);

            this.revealCommand = new ProcessBuilder("/usr/bin/osascript", "-e", openAppleScript, "-e",
                    activateAppleScript);
            this.unmountCommand = new ProcessBuilder("/usr/bin/osascript", "-e", ejectAppleScript);
        }

        @Override
        public void unmount() throws CommandFailedException {
            try {
                Process proc = unmountCommand.start();
                waitForProcessAndCheckSuccess(proc, 1, TimeUnit.SECONDS);
            } catch (IOException e) {
                throw new CommandFailedException(e);
            }
        }

        @Override
        public void reveal() throws CommandFailedException {
            try {
                Process proc = revealCommand.start();
                waitForProcessAndCheckSuccess(proc, 2, TimeUnit.SECONDS);
            } catch (IOException e) {
                throw new CommandFailedException(e);
            }
        }

    }

    private static void waitForProcessAndCheckSuccess(Process proc, long timeout, TimeUnit unit)
            throws CommandFailedException, IOException {
        try {
            boolean finishedInTime = proc.waitFor(timeout, unit);
            if (!finishedInTime) {
                proc.destroyForcibly();
                throw new CommandFailedException("Command timed out.");
            }
            int exitCode = proc.exitValue();
            if (exitCode != 0) {
                String error = IOUtils.toString(proc.getErrorStream(), StandardCharsets.UTF_8);
                throw new CommandFailedException(
                        "Command failed with exit code " + exitCode + ". Stderr: " + error);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

}