ch.cyberduck.ui.cocoa.controller.MainController.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.ui.cocoa.controller.MainController.java

Source

package ch.cyberduck.ui.cocoa.controller;

/*
 * Copyright (c) 2002-2016 iterate GmbH. All rights reserved.
 * https://cyberduck.io/
 *
 * 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 2 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.
 */

import ch.cyberduck.binding.Action;
import ch.cyberduck.binding.AlertController;
import ch.cyberduck.binding.BundleController;
import ch.cyberduck.binding.Delegate;
import ch.cyberduck.binding.Outlet;
import ch.cyberduck.binding.ProxyController;
import ch.cyberduck.binding.SheetController;
import ch.cyberduck.binding.application.*;
import ch.cyberduck.binding.foundation.NSAppleEventDescriptor;
import ch.cyberduck.binding.foundation.NSAppleEventManager;
import ch.cyberduck.binding.foundation.NSArray;
import ch.cyberduck.binding.foundation.NSAttributedString;
import ch.cyberduck.binding.foundation.NSBundle;
import ch.cyberduck.binding.foundation.NSDictionary;
import ch.cyberduck.binding.foundation.NSNotification;
import ch.cyberduck.binding.foundation.NSNotificationCenter;
import ch.cyberduck.binding.foundation.NSObject;
import ch.cyberduck.core.*;
import ch.cyberduck.core.aquaticprime.DisabledLicenseVerifierCallback;
import ch.cyberduck.core.aquaticprime.License;
import ch.cyberduck.core.aquaticprime.LicenseFactory;
import ch.cyberduck.core.bonjour.NotificationRendezvousListener;
import ch.cyberduck.core.bonjour.Rendezvous;
import ch.cyberduck.core.bonjour.RendezvousFactory;
import ch.cyberduck.core.exception.AccessDeniedException;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.importer.CrossFtpBookmarkCollection;
import ch.cyberduck.core.importer.Expandrive3BookmarkCollection;
import ch.cyberduck.core.importer.Expandrive4BookmarkCollection;
import ch.cyberduck.core.importer.Expandrive5BookmarkCollection;
import ch.cyberduck.core.importer.FetchBookmarkCollection;
import ch.cyberduck.core.importer.FilezillaBookmarkCollection;
import ch.cyberduck.core.importer.FireFtpBookmarkCollection;
import ch.cyberduck.core.importer.FlowBookmarkCollection;
import ch.cyberduck.core.importer.InterarchyBookmarkCollection;
import ch.cyberduck.core.importer.ThirdpartyBookmarkCollection;
import ch.cyberduck.core.importer.Transmit4BookmarkCollection;
import ch.cyberduck.core.local.Application;
import ch.cyberduck.core.local.BrowserLauncherFactory;
import ch.cyberduck.core.local.TemporaryFileServiceFactory;
import ch.cyberduck.core.notification.NotificationServiceFactory;
import ch.cyberduck.core.oauth.OAuth2TokenListenerRegistry;
import ch.cyberduck.core.pool.SessionPool;
import ch.cyberduck.core.preferences.Preferences;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.resources.IconCacheFactory;
import ch.cyberduck.core.serializer.HostDictionary;
import ch.cyberduck.core.threading.AbstractBackgroundAction;
import ch.cyberduck.core.threading.DefaultBackgroundExecutor;
import ch.cyberduck.core.transfer.DownloadTransfer;
import ch.cyberduck.core.transfer.TransferItem;
import ch.cyberduck.core.transfer.TransferOptions;
import ch.cyberduck.core.transfer.UploadTransfer;
import ch.cyberduck.core.updater.PeriodicUpdateChecker;
import ch.cyberduck.core.updater.PeriodicUpdateCheckerFactory;
import ch.cyberduck.core.urlhandler.SchemeHandlerFactory;
import ch.cyberduck.ui.browser.Column;
import ch.cyberduck.ui.browser.DownloadDirectoryFinder;
import ch.cyberduck.ui.cocoa.delegate.ArchiveMenuDelegate;
import ch.cyberduck.ui.cocoa.delegate.BookmarkMenuDelegate;
import ch.cyberduck.ui.cocoa.delegate.CopyURLMenuDelegate;
import ch.cyberduck.ui.cocoa.delegate.EditMenuDelegate;
import ch.cyberduck.ui.cocoa.delegate.OpenURLMenuDelegate;
import ch.cyberduck.ui.cocoa.delegate.URLMenuDelegate;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.rococoa.Foundation;
import org.rococoa.ID;
import org.rococoa.Rococoa;
import org.rococoa.cocoa.foundation.NSInteger;
import org.rococoa.cocoa.foundation.NSRect;
import org.rococoa.cocoa.foundation.NSUInteger;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * Setting the main menu and implements application delegate methods
 */
public class MainController extends BundleController implements NSApplication.Delegate {
    private static final Logger log = Logger.getLogger(MainController.class);

    /**
     * Apple event constants<br>
     * **********************************************************************************************<br>
     * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:117</i>
     */
    public static final int kInternetEventClass = 1196773964;
    /**
     * Apple event constants<br>
     * **********************************************************************************************<br>
     * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:118</i>
     */
    public static final int kAEGetURL = 1196773964;
    /**
     * Apple event constants<br>
     * **********************************************************************************************<br>
     * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:119</i>
     */
    public static final int kAEFetchURL = 1179996748;

    /// 0x2d2d2d2d
    public static final int keyAEResult = 757935405;

    private final Preferences preferences = PreferencesFactory.get();

    private final PeriodicUpdateChecker updater = PeriodicUpdateCheckerFactory.get(this);

    private final PathKindDetector detector = new DefaultPathKindDetector();
    /**
     *
     */
    private static final List<BrowserController> browsers = new ArrayList<BrowserController>();

    /**
     * Saved browsers
     */
    private final AbstractHostCollection sessions = new FolderBookmarkCollection(
            LocalFactory.get(preferences.getProperty("application.support.path"), "Sessions"), "session");

    /**
     * Display donation reminder dialog
     */
    private boolean displayDonationPrompt = true;

    @Outlet
    private SheetController donationController;
    @Outlet
    private NSMenu applicationMenu;
    @Outlet
    private NSMenu encodingMenu;
    @Outlet
    private NSMenu columnMenu;
    @Outlet
    private NSMenu editMenu;
    @Delegate
    private EditMenuDelegate editMenuDelegate;
    @Outlet
    private NSMenu urlMenu;
    @Delegate
    private URLMenuDelegate urlMenuDelegate;
    @Outlet
    private NSMenu openUrlMenu;
    @Delegate
    private URLMenuDelegate openUrlMenuDelegate;
    @Outlet
    private NSMenu archiveMenu;
    @Delegate
    private ArchiveMenuDelegate archiveMenuDelegate;
    @Outlet
    private NSMenu bookmarkMenu;
    @Delegate
    private BookmarkMenuDelegate bookmarkMenuDelegate;

    /**
     * @param frame Frame autosave name
     */
    public static BrowserController newDocument(final boolean force, final String frame) {
        final List<BrowserController> browsers = getBrowsers();
        if (!force) {
            for (BrowserController controller : browsers) {
                if (controller.isIdle() && !controller.isMounted()) {
                    controller.window().makeKeyAndOrderFront(null);
                    return controller;
                }
            }
        }
        final BrowserController controller = new BrowserController();
        controller.addListener(new WindowListener() {
            @Override
            public void windowWillClose() {
                browsers.remove(controller);
            }
        });
        if (StringUtils.isNotBlank(frame)) {
            controller.window().setFrameUsingName(frame);
        }
        controller.window().makeKeyAndOrderFront(null);
        browsers.add(controller);
        return controller;
    }

    /**
     * Makes a unmounted browser window the key window and brings it to the front
     *
     * @return A reference to a browser window
     */
    public static BrowserController newDocument() {
        return newDocument(false);
    }

    public static List<BrowserController> getBrowsers() {
        return browsers;
    }

    /**
     * Browser with key focus
     *
     * @return Null if no browser window is open
     */
    public BrowserController getBrowser() {
        for (BrowserController browser : browsers) {
            if (browser.window().isKeyWindow()) {
                return browser;
            }
        }
        return null;
    }

    /**
     * Makes a unmounted browser window the key window and brings it to the front
     *
     * @param force If true, open a new browser regardless of any unused browser window
     */
    public static BrowserController newDocument(final boolean force) {
        return newDocument(force, null);
    }

    @Override
    protected String getBundleName() {
        return "Main";
    }

    public void setApplicationMenu(NSMenu menu) {
        this.applicationMenu = menu;
        this.updateLicenseMenu();
        this.updateUpdateMenu();
    }

    /**
     * Set name of key in menu item
     */
    protected void updateLicenseMenu() {
        final License key = LicenseFactory.find();
        if (key.isReceipt()) {
            this.applicationMenu.removeItemAtIndex(new NSInteger(5));
            this.applicationMenu.removeItemAtIndex(new NSInteger(4));
        } else {
            NSDictionary KEY_FONT_ATTRIBUTES = NSDictionary.dictionaryWithObjectsForKeys(
                    NSArray.arrayWithObjects(NSFont.userFontOfSize(NSFont.smallSystemFontSize()),
                            NSColor.darkGrayColor()),
                    NSArray.arrayWithObjects(NSAttributedString.FontAttributeName,
                            NSAttributedString.ForegroundColorAttributeName));
            this.applicationMenu.itemAtIndex(new NSInteger(5)).setAttributedTitle(
                    NSAttributedString.attributedStringWithAttributes(key.toString(), KEY_FONT_ATTRIBUTES));
        }
    }

    /**
     * Remove software update menu item if no update feed available
     */
    private void updateUpdateMenu() {
        if (!updater.hasUpdatePrivileges()) {
            this.applicationMenu.removeItemAtIndex(new NSInteger(1));
        }
    }

    public void setEncodingMenu(NSMenu encodingMenu) {
        this.encodingMenu = encodingMenu;
        for (String charset : new DefaultCharsetProvider().availableCharsets()) {
            this.encodingMenu.addItemWithTitle_action_keyEquivalent(charset,
                    Foundation.selector("encodingMenuClicked:"), StringUtils.EMPTY);
        }
    }

    public void setColumnMenu(NSMenu columnMenu) {
        this.columnMenu = columnMenu;
        Map<String, String> columns = new HashMap<String, String>();
        columns.put(String.format("browser.column.%s", Column.kind.name()), LocaleFactory.localizedString("Kind"));
        columns.put(String.format("browser.column.%s", Column.extension.name()),
                LocaleFactory.localizedString("Extension"));
        columns.put(String.format("browser.column.%s", Column.size.name()), LocaleFactory.localizedString("Size"));
        columns.put(String.format("browser.column.%s", Column.modified.name()),
                LocaleFactory.localizedString("Modified"));
        columns.put(String.format("browser.column.%s", Column.owner.name()),
                LocaleFactory.localizedString("Owner"));
        columns.put(String.format("browser.column.%s", Column.group.name()),
                LocaleFactory.localizedString("Group"));
        columns.put(String.format("browser.column.%s", Column.permission.name()),
                LocaleFactory.localizedString("Permissions"));
        columns.put(String.format("browser.column.%s", Column.region.name()),
                LocaleFactory.localizedString("Region"));
        columns.put(String.format("browser.column.%s", Column.version.name()),
                LocaleFactory.localizedString("Version"));
        for (Map.Entry<String, String> entry : columns.entrySet()) {
            NSMenuItem item = this.columnMenu.addItemWithTitle_action_keyEquivalent(entry.getValue(),
                    Foundation.selector("columnMenuClicked:"), StringUtils.EMPTY);
            final String identifier = entry.getKey();
            item.setState(preferences.getBoolean(identifier) ? NSCell.NSOnState : NSCell.NSOffState);
            item.setRepresentedObject(identifier);
        }
    }

    @Action
    public void columnMenuClicked(final NSMenuItem sender) {
        final String identifier = sender.representedObject();
        final boolean enabled = !preferences.getBoolean(identifier);
        sender.setState(enabled ? NSCell.NSOnState : NSCell.NSOffState);
        preferences.setProperty(identifier, enabled);
        BrowserController.updateBrowserTableColumns();
    }

    public void setEditMenu(NSMenu editMenu) {
        this.editMenu = editMenu;
        this.editMenuDelegate = new EditMenuDelegate() {
            @Override
            protected Path getEditable() {
                final List<BrowserController> b = MainController.getBrowsers();
                for (BrowserController controller : b) {
                    if (controller.window().isKeyWindow()) {
                        final Path selected = controller.getSelectedPath();
                        if (null == selected) {
                            return null;
                        }
                        if (controller.isEditable(selected)) {
                            return selected;
                        }
                        return null;
                    }
                }
                return null;
            }

            @Override
            protected ID getTarget() {
                return MainController.this.getBrowser().id();
            }
        };
        this.editMenu.setDelegate(editMenuDelegate.id());
    }

    public void setUrlMenu(NSMenu urlMenu) {
        this.urlMenu = urlMenu;
        this.urlMenuDelegate = new CopyURLMenuDelegate() {
            @Override
            protected SessionPool getSession() {
                final List<BrowserController> b = MainController.getBrowsers();
                for (BrowserController controller : b) {
                    if (controller.window().isKeyWindow()) {
                        if (controller.isMounted()) {
                            return controller.getSession();
                        }
                    }
                }
                return null;
            }

            @Override
            protected List<Path> getSelected() {
                final List<BrowserController> b = MainController.getBrowsers();
                for (BrowserController controller : b) {
                    if (controller.window().isKeyWindow()) {
                        List<Path> selected = controller.getSelectedPaths();
                        if (selected.isEmpty()) {
                            if (controller.isMounted()) {
                                return Collections.singletonList(controller.workdir());
                            }
                        }
                        return selected;
                    }
                }
                return Collections.emptyList();
            }
        };
        this.urlMenu.setDelegate(urlMenuDelegate.id());
    }

    public void setOpenUrlMenu(NSMenu openUrlMenu) {
        this.openUrlMenu = openUrlMenu;
        this.openUrlMenuDelegate = new OpenURLMenuDelegate() {
            @Override
            protected SessionPool getSession() {
                final List<BrowserController> b = MainController.getBrowsers();
                for (BrowserController controller : b) {
                    if (controller.window().isKeyWindow()) {
                        if (controller.isMounted()) {
                            return controller.getSession();
                        }
                    }
                }
                return null;
            }

            @Override
            protected List<Path> getSelected() {
                final List<BrowserController> b = MainController.getBrowsers();
                for (BrowserController controller : b) {
                    if (controller.window().isKeyWindow()) {
                        List<Path> selected = controller.getSelectedPaths();
                        if (selected.isEmpty()) {
                            if (controller.isMounted()) {
                                return Collections.singletonList(controller.workdir());
                            }
                        }
                        return selected;
                    }
                }
                return Collections.emptyList();
            }
        };
        this.openUrlMenu.setDelegate(openUrlMenuDelegate.id());
    }

    public void setArchiveMenu(NSMenu archiveMenu) {
        this.archiveMenu = archiveMenu;
        this.archiveMenuDelegate = new ArchiveMenuDelegate();
        this.archiveMenu.setDelegate(archiveMenuDelegate.id());
    }

    public void setBookmarkMenu(NSMenu bookmarkMenu) {
        this.bookmarkMenu = bookmarkMenu;
        this.bookmarkMenuDelegate = new BookmarkMenuDelegate();
        this.bookmarkMenu.setDelegate(bookmarkMenuDelegate.id());
    }

    @Action
    public void bugreportMenuClicked(final ID sender) {
        BrowserLauncherFactory.get().open(MessageFormat.format(preferences.getProperty("website.bug"),
                preferences.getProperty("application.version")));
    }

    @Action
    public void helpMenuClicked(final ID sender) {
        BrowserLauncherFactory.get().open(ProviderHelpServiceFactory.get().help());
    }

    @Action
    public void licenseMenuClicked(final ID sender) {
        BrowserLauncherFactory.get().open(preferences.getProperty("website.license"));
    }

    @Action
    public void acknowledgmentsMenuClicked(final ID sender) {
        BrowserLauncherFactory.get().open(preferences.getProperty("website.acknowledgments"));
    }

    @Action
    public void websiteMenuClicked(final ID sender) {
        BrowserLauncherFactory.get().open(preferences.getProperty("website.home"));
    }

    @Action
    public void donateMenuClicked(final ID sender) {
        BrowserLauncherFactory.get().open(preferences.getProperty("website.donate"));
    }

    @Action
    public void aboutMenuClicked(final ID sender) {
        final NSDictionary dict = NSDictionary.dictionaryWithObjectsForKeys(
                NSArray.arrayWithObjects(preferences.getProperty("application.name"),
                        preferences.getProperty("application.version"),
                        preferences.getProperty("application.revision")),
                NSArray.arrayWithObjects("ApplicationName", "ApplicationVersion", "Version"));
        NSApplication.sharedApplication().orderFrontStandardAboutPanelWithOptions(dict);
    }

    @Action
    public void feedbackMenuClicked(final ID sender) {
        BrowserLauncherFactory.get()
                .open(preferences.getProperty("mail.feedback") + "?subject="
                        + preferences.getProperty("application.name") + "-"
                        + preferences.getProperty("application.version"));
    }

    @Action
    public void preferencesMenuClicked(final ID sender) {
        PreferencesController controller = PreferencesControllerFactory.instance();
        controller.window().makeKeyAndOrderFront(null);
    }

    @Action
    public void newDownloadMenuClicked(final ID sender) {
        this.showTransferQueueClicked(sender);
        final DownloadController c = new DownloadController();
        c.beginSheet(TransferControllerFactory.get());
    }

    @Action
    public void newBrowserMenuClicked(final ID sender) {
        this.openDefaultBookmark(newDocument(true));
    }

    @Action
    public void newWindowForTab(final ID sender) {
        this.openDefaultBookmark(newDocument(true));
    }

    /**
     * Mounts the default bookmark if any
     */
    protected void openDefaultBookmark(final BrowserController controller) {
        String defaultBookmark = preferences.getProperty("browser.open.bookmark.default");
        if (null == defaultBookmark) {
            log.info("No default bookmark configured");
            return; //No default bookmark given
        }
        final Host bookmark = BookmarkCollection.defaultCollection().lookup(defaultBookmark);
        if (null == bookmark) {
            log.info("Default bookmark no more available");
            return;
        }
        for (BrowserController browser : getBrowsers()) {
            if (bookmark.equals(browser.getSession().getHost())) {
                log.debug("Default bookmark already mounted");
                return;
            }
        }
        if (log.isDebugEnabled()) {
            log.debug(String.format("Mounting default bookmark %s", bookmark));
        }
        controller.mount(bookmark);
    }

    @Action
    public void showTransferQueueClicked(final ID sender) {
        TransferController c = TransferControllerFactory.get();
        c.window().makeKeyAndOrderFront(null);
    }

    @Action
    public void showActivityWindowClicked(final ID sender) {
        ActivityController c = ActivityControllerFactory.get();
        if (c.isVisible()) {
            c.window().orderOut(null);
        } else {
            c.window().orderFront(null);
        }
    }

    @Override
    public boolean application_openFile(final NSApplication app, final String filename) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Open file %s", filename));
        }
        final Local f = LocalFactory.get(filename);
        if (f.exists()) {
            if ("duck".equals(f.getExtension())) {
                final Host bookmark;
                try {
                    bookmark = HostReaderFactory.get().read(f);
                    if (null == bookmark) {
                        return false;
                    }
                    newDocument().mount(bookmark);
                    return true;
                } catch (AccessDeniedException e) {
                    log.error(e.getMessage());
                    return false;
                }
            } else if ("cyberducklicense".equals(f.getExtension())) {
                final License l = LicenseFactory.get(f);
                if (l.verify(new DisabledLicenseVerifierCallback())) {
                    try {
                        f.copy(LocalFactory.get(preferences.getProperty("application.support.path"), f.getName()));
                        final NSAlert alert = NSAlert.alert(l.toString(), LocaleFactory.localizedString(
                                "Thanks for your support! Your contribution helps to further advance development to make Cyberduck even better.",
                                "License")
                                + "\n\n"
                                + LocaleFactory.localizedString(
                                        "Your donation key has been copied to the Application Support folder.",
                                        "License"),
                                LocaleFactory.localizedString("Continue", "License"), //default
                                null, //other
                                null);
                        alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
                        if (new AlertSheetReturnCodeMapper()
                                .getOption(alert.runModal()) == SheetCallback.DEFAULT_OPTION) {
                            for (BrowserController c : MainController.getBrowsers()) {
                                c.removeDonateWindowTitle();
                            }
                            this.updateLicenseMenu();
                        }
                    } catch (AccessDeniedException e) {
                        log.error(e.getMessage());
                        return false;
                    }
                } else {
                    final NSAlert alert = NSAlert.alert(
                            LocaleFactory.localizedString("Not a valid registration key", "License"),
                            LocaleFactory.localizedString("This donation key does not appear to be valid.",
                                    "License"),
                            LocaleFactory.localizedString("Continue", "License"), //default
                            null, //other
                            null);
                    alert.setAlertStyle(NSAlert.NSWarningAlertStyle);
                    alert.setShowsHelp(true);
                    alert.setDelegate(new ProxyController() {
                        @Action
                        public boolean alertShowHelp(NSAlert alert) {
                            BrowserLauncherFactory.get().open(ProviderHelpServiceFactory.get().help());
                            return true;
                        }

                    }.id());
                    alert.runModal();
                }
                return true;
            } else if ("cyberduckprofile".equals(f.getExtension())) {
                try {
                    final Protocol profile = ProfileReaderFactory.get().read(f);
                    if (null == profile) {
                        return false;
                    }
                    if (profile.isEnabled()) {
                        if (log.isDebugEnabled()) {
                            log.debug(String.format("Register profile %s", profile));
                        }
                        ProtocolFactory.get().register(profile);
                        final Host host = new Host(profile, profile.getDefaultHostname(), profile.getDefaultPort());
                        newDocument().addBookmark(host);
                        // Register in application support
                        final Local profiles = LocalFactory.get(preferences.getProperty("application.support.path"),
                                PreferencesFactory.get().getProperty("profiles.folder.name"));
                        if (!profiles.exists()) {
                            profiles.mkdir();
                        }
                        f.copy(LocalFactory.get(profiles, f.getName()));
                    }
                } catch (AccessDeniedException e) {
                    log.error(e.getMessage());
                    return false;
                }
            } else {
                // Upload file
                this.background(new AbstractBackgroundAction<Void>() {
                    @Override
                    public Void run() throws BackgroundException {
                        // Wait until bookmarks are loaded
                        try {
                            bookmarksSemaphore.await();
                        } catch (InterruptedException e) {
                            log.error(String.format("Error awaiting bookmarks to load %s", e.getMessage()));
                        }
                        return null;
                    }

                    @Override
                    public void cleanup() {
                        upload(f);
                    }

                    @Override
                    public String getActivity() {
                        return "Open File";
                    }
                });
                return true;
            }
        }
        return false;
    }

    private boolean upload(final Local f) {
        return this.upload(Collections.singletonList(f));
    }

    private boolean upload(final List<Local> files) {
        // Selected bookmark
        Host open = null;
        Path workdir = null;
        for (BrowserController browser : MainController.getBrowsers()) {
            if (browser.isMounted()) {
                open = browser.getSession().getHost();
                workdir = browser.workdir();
                if (1 == MainController.getBrowsers().size()) {
                    // If only one browser window upload to current working directory with no bookmark selection
                    this.upload(open, files, workdir);
                    return true;
                }
                break;
            }
        }
        if (BookmarkCollection.defaultCollection().isEmpty()) {
            log.warn("No bookmark for upload");
            return false;
        }
        final NSPopUpButton bookmarksPopup = NSPopUpButton.buttonWithFrame(new NSRect(0, 26));
        bookmarksPopup.setToolTip(LocaleFactory.localizedString("Bookmarks"));
        for (Host b : BookmarkCollection.defaultCollection()) {
            String title = BookmarkNameProvider.toString(b);
            int i = 1;
            while (bookmarksPopup.itemWithTitle(title) != null) {
                title = BookmarkNameProvider.toString(b) + "-" + i;
                i++;
            }
            bookmarksPopup.addItemWithTitle(title);
            bookmarksPopup.lastItem()
                    .setImage(IconCacheFactory.<NSImage>get().iconNamed(b.getProtocol().icon(), 16));
            bookmarksPopup.lastItem().setRepresentedObject(b.getUuid());
            if (b.equals(open)) {
                bookmarksPopup.selectItemAtIndex(bookmarksPopup.indexOfItem(bookmarksPopup.lastItem()));
            }
        }
        if (null == open) {
            int i = 0;
            for (Host bookmark : BookmarkCollection.defaultCollection()) {
                boolean found = false;
                // Pick the bookmark with the same download location
                for (Local file : files) {
                    if (file.isChild(new DownloadDirectoryFinder().find(bookmark))) {
                        bookmarksPopup.selectItemAtIndex(new NSInteger(i));
                        found = true;
                        break;
                    }
                }
                if (found) {
                    break;
                }
                i++;
            }
        }
        if (-1 == bookmarksPopup.indexOfSelectedItem().intValue()) {
            // No bookmark for current browser found
            bookmarksPopup.selectItemAtIndex(new NSInteger(0));
        }
        final Host mount = open;
        final Path destination = workdir;
        final NSAlert alert = NSAlert.alert("Select Bookmark",
                MessageFormat.format("Upload {0} to the selected bookmark.",
                        files.size() == 1 ? files.iterator().next().getName()
                                : MessageFormat.format(LocaleFactory.localizedString("{0} Files"),
                                        String.valueOf(files.size()))),
                LocaleFactory.localizedString("Upload", "Transfer"), LocaleFactory.localizedString("Cancel"), null);
        alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
        final AlertController controller = new AlertController() {
            @Override
            public void loadBundle() {
                this.loadBundle(alert);
            }

            @Override
            public NSView getAccessoryView(final NSAlert alert) {
                return bookmarksPopup;
            }

            @Override
            public void callback(int returncode) {
                if (DEFAULT_OPTION == returncode) {
                    final String selected = bookmarksPopup.selectedItem().representedObject();
                    for (Host bookmark : BookmarkCollection.defaultCollection()) {
                        // Determine selected bookmark
                        if (bookmark.getUuid().equals(selected)) {
                            if (bookmark.equals(mount)) {
                                // Use current working directory of browser for destination
                                upload(bookmark, files, destination);
                            } else {
                                // No mounted browser
                                if (StringUtils.isNotBlank(bookmark.getDefaultPath())) {
                                    upload(bookmark, files,
                                            new Path(bookmark.getDefaultPath(), EnumSet.of(Path.Type.directory)));
                                } else {
                                    upload(bookmark, files, destination);
                                }
                            }
                            break;
                        }
                    }
                }
            }

            @Override
            public boolean validate() {
                return StringUtils.isNotEmpty(bookmarksPopup.selectedItem().representedObject());
            }
        };
        controller.beginSheet(TransferControllerFactory.get());
        return true;
    }

    private void upload(final Host bookmark, final List<Local> files, final Path destination) {
        final List<TransferItem> roots = new ArrayList<TransferItem>();
        for (Local file : files) {
            roots.add(new TransferItem(
                    new Path(destination, file.getName(),
                            file.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)),
                    file));
        }
        final TransferController t = TransferControllerFactory.get();
        t.start(new UploadTransfer(bookmark, roots), new TransferOptions());
    }

    /**
     * Sent directly by theApplication to the delegate. The method should attempt to open the file filename,
     * returning true if the file is successfully opened, and false otherwise. By design, a
     * file opened through this method is assumed to be temporary its the application's
     * responsibility to remove the file at the appropriate time.
     */
    @Override
    public boolean application_openTempFile(NSApplication app, String filename) {
        if (log.isDebugEnabled()) {
            log.debug("applicationOpenTempFile:" + filename);
        }
        return this.application_openFile(app, filename);
    }

    /**
     * Invoked immediately before opening an untitled file. Return false to prevent
     * the application from opening an untitled file; return true otherwise.
     * Note that applicationOpenUntitledFile is invoked if this method returns true.
     */
    @Override
    public boolean applicationShouldOpenUntitledFile(NSApplication sender) {
        if (log.isDebugEnabled()) {
            log.debug("applicationShouldOpenUntitledFile");
        }
        return preferences.getBoolean("browser.open.untitled");
    }

    /**
     * @return true if the file was successfully opened, false otherwise.
     */
    @Override
    public boolean applicationOpenUntitledFile(NSApplication app) {
        if (log.isDebugEnabled()) {
            log.debug("applicationOpenUntitledFile");
        }
        return false;
    }

    /**
     * These events are sent whenever the Finder reactivates an already running application
     * because someone double-clicked it again or used the dock to activate it. By default
     * the Application Kit will handle this event by checking whether there are any visible
     * NSWindows (not NSPanels), and, if there are none, it goes through the standard untitled
     * document creation (the same as it does if theApplication is launched without any document
     * to open). For most document-based applications, an untitled document will be created.
     * The application delegate will also get a chance to respond to the normal untitled document
     * delegations. If you implement this method in your application delegate, it will be called
     * before any of the default behavior happens. If you return true, then NSApplication will
     * go on to do its normal thing. If you return false, then NSApplication will do nothing.
     * So, you can either implement this method, do nothing, and return false if you do not
     * want anything to happen at all (not recommended), or you can implement this method,
     * handle the event yourself in some custom way, and return false.
     */
    @Override
    public boolean applicationShouldHandleReopen_hasVisibleWindows(final NSApplication app,
            final boolean visibleWindowsFound) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Should handle reopen with windows %s", visibleWindowsFound));
        } // While an application is open, the Dock icon has a symbol below it.
        // When a user clicks an open applications icon in the Dock, the application
        // becomes active and all open unminimized windows are brought to the front;
        // minimized document windows remain in the Dock. If there are no unminimized
        // windows when the user clicks the Dock icon, the last minimized window should
        // be expanded and made active. If no documents are open, the application should
        // open a new window. (If your application is not document-based, display the
        // applications main window.)
        if (MainController.getBrowsers().isEmpty() && !TransferControllerFactory.get().isVisible()) {
            this.openDefaultBookmark(newDocument());
        }
        NSWindow miniaturized = null;
        for (BrowserController browser : MainController.getBrowsers()) {
            if (!browser.window().isMiniaturized()) {
                return false;
            }
            if (null == miniaturized) {
                miniaturized = browser.window();
            }
        }
        if (null == miniaturized) {
            return false;
        }
        miniaturized.deminiaturize(null);
        return false;
    }

    // User bookmarks and thirdparty applications
    private final CountDownLatch bookmarksSemaphore = new CountDownLatch(1);
    private final CountDownLatch thirdpartySemaphore = new CountDownLatch(1);

    /**
     * Sent by the default notification center after the application has been launched and initialized but
     * before it has received its first event. aNotification is always an
     * ApplicationDidFinishLaunchingNotification. You can retrieve the NSApplication
     * object in question by sending object to aNotification. The delegate can implement
     * this method to perform further initialization. If the user started up the application
     * by double-clicking a file, the delegate receives the applicationOpenFile message before receiving
     * applicationDidFinishLaunching. (applicationWillFinishLaunching is sent before applicationOpenFile.)
     */
    @Override
    public void applicationDidFinishLaunching(NSNotification notification) {
        // Opt-in of automatic window tabbing
        NSWindow.setAllowsAutomaticWindowTabbing(true);
        // Load main menu
        this.loadBundle();
        // Open default windows
        if (preferences.getBoolean("browser.open.untitled")) {
            final BrowserController c = newDocument();
            c.window().makeKeyAndOrderFront(null);
        }
        if (preferences.getBoolean("queue.window.open.default")) {
            TransferController c = TransferControllerFactory.get();
            c.window().makeKeyAndOrderFront(null);
        }
        if (preferences.getBoolean("browser.serialize")) {
            this.background(new AbstractBackgroundAction<Void>() {
                @Override
                public Void run() throws BackgroundException {
                    sessions.load();
                    return null;
                }

                @Override
                public void cleanup() {
                    for (Host host : sessions) {
                        if (log.isInfoEnabled()) {
                            log.info(String.format("New browser for saved session %s", host));
                        }
                        final BrowserController browser = newDocument(false, host.getUuid());
                        browser.mount(host);
                    }
                    sessions.clear();
                }
            });
        }
        // Load all bookmarks in background
        this.background(new AbstractBackgroundAction<Void>() {
            @Override
            public Void run() throws BackgroundException {
                final BookmarkCollection c = BookmarkCollection.defaultCollection();
                c.load();
                bookmarksSemaphore.countDown();
                return null;
            }

            @Override
            public void cleanup() {
                if (preferences.getBoolean("browser.open.untitled")) {
                    if (preferences.getProperty("browser.open.bookmark.default") != null) {
                        openDefaultBookmark(newDocument());
                    }
                }
                // Set delegate for NSService
                NSApplication.sharedApplication().setServicesProvider(MainController.this.id());
            }

            @Override
            public String getActivity() {
                return "Loading Bookmarks";
            }
        });
        this.background(new AbstractBackgroundAction<Void>() {
            @Override
            public Void run() throws BackgroundException {
                HistoryCollection.defaultCollection().load();
                return null;
            }

            @Override
            public String getActivity() {
                return "Loading History";
            }
        });
        this.background(new AbstractBackgroundAction<Void>() {
            @Override
            public Void run() throws BackgroundException {
                TransferCollection.defaultCollection().load();
                return null;
            }

            @Override
            public String getActivity() {
                return "Loading Transfers";
            }
        });
        final Rendezvous bonjour = RendezvousFactory.instance();

        bonjour.addListener(new NotificationRendezvousListener(bonjour));
        if (preferences.getBoolean("defaulthandler.reminder") && preferences.getInteger("uses") > 0) {
            if (!SchemeHandlerFactory.get().isDefaultHandler(Arrays.asList(Scheme.ftp, Scheme.ftps, Scheme.sftp),
                    new Application(preferences.getProperty("application.identifier")))) {
                final NSAlert alert = NSAlert.alert(LocaleFactory.localizedString(
                        "Set Cyberduck as default application for FTP and SFTP locations?", "Configuration"),
                        LocaleFactory.localizedString(
                                "As the default application, Cyberduck will open when you click on FTP or SFTP links "
                                        + "in other applications, such as your web browser. You can change this setting in the Preferences later.",
                                "Configuration"),
                        LocaleFactory.localizedString("Change", "Configuration"), //default
                        null, //other
                        LocaleFactory.localizedString("Cancel", "Configuration"));
                alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
                alert.setShowsSuppressionButton(true);
                alert.suppressionButton()
                        .setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
                int choice = new AlertSheetReturnCodeMapper().getOption(alert.runModal());
                if (alert.suppressionButton().state() == NSCell.NSOnState) {
                    // Never show again.
                    preferences.setProperty("defaulthandler.reminder", false);
                }
                if (choice == SheetCallback.DEFAULT_OPTION) {
                    SchemeHandlerFactory.get().setDefaultHandler(
                            Arrays.asList(Scheme.ftp, Scheme.ftps, Scheme.sftp),
                            new Application(preferences.getProperty("application.identifier")));
                }
            }
        }
        // NSWorkspace notifications are posted to a notification center provided by
        // the NSWorkspace object, instead of going through the applications default
        // notification center as most notifications do. To receive NSWorkspace notifications,
        // your application must register an observer with the NSWorkspace notification center.
        NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
                Foundation.selector("workspaceWillPowerOff:"), NSWorkspace.WorkspaceWillPowerOffNotification, null);
        NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
                Foundation.selector("workspaceWillLogout:"),
                NSWorkspace.WorkspaceSessionDidResignActiveNotification, null);
        NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
                Foundation.selector("workspaceWillSleep:"), NSWorkspace.WorkspaceWillSleepNotification, null);
        NSNotificationCenter.defaultCenter().addObserver(this.id(),
                Foundation.selector("applicationWillRestartAfterUpdate:"), "SUUpdaterWillRestartNotificationName",
                null);
        this.background(new AbstractBackgroundAction<Void>() {
            @Override
            public Void run() throws BackgroundException {
                bonjour.init();
                return null;
            }
        });
        // Import thirdparty bookmarks.
        this.background(new AbstractBackgroundAction<Void>() {
            private List<ThirdpartyBookmarkCollection> thirdpartyBookmarkCollections = Collections.emptyList();

            @Override
            public Void run() {
                thirdpartyBookmarkCollections = this.getThirdpartyBookmarks();
                for (ThirdpartyBookmarkCollection t : thirdpartyBookmarkCollections) {
                    if (!t.isInstalled()) {
                        if (log.isInfoEnabled()) {
                            log.info(String.format("No application installed for %s", t.getBundleIdentifier()));
                        }
                        continue;
                    }
                    try {
                        t.load();
                    } catch (AccessDeniedException e) {
                        log.warn(String.format("Failure %s loading bookmarks from %s", e, t));
                    }
                    if (t.isEmpty()) {
                        // Flag as imported
                        preferences.setProperty(t.getConfiguration(), true);
                    }
                }
                try {
                    bookmarksSemaphore.await();
                } catch (InterruptedException e) {
                    log.error(String.format("Error awaiting bookmarks to load %s", e.getMessage()));
                }
                return null;
            }

            @Override
            public void cleanup() {
                for (ThirdpartyBookmarkCollection t : thirdpartyBookmarkCollections) {
                    final BookmarkCollection bookmarks = BookmarkCollection.defaultCollection();
                    t.filter(bookmarks);
                    if (t.isEmpty()) {
                        preferences.setProperty(t.getConfiguration(), true);
                        continue;
                    }
                    final NSAlert alert = NSAlert.alert(MessageFormat.format(
                            LocaleFactory.localizedString("Import {0} Bookmarks", "Configuration"), t.getName()),
                            MessageFormat.format(LocaleFactory.localizedString(
                                    "{0} bookmarks found. Do you want to add these to your bookmarks?",
                                    "Configuration"), t.size()),
                            LocaleFactory.localizedString("Import", "Configuration"), //default
                            null, //other
                            LocaleFactory.localizedString("Cancel", "Configuration"));
                    alert.setShowsSuppressionButton(true);
                    alert.suppressionButton()
                            .setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
                    alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
                    int choice = new AlertSheetReturnCodeMapper().getOption(alert.runModal()); //alternate
                    if (alert.suppressionButton().state() == NSCell.NSOnState) {
                        // Never show again.
                        preferences.setProperty(t.getConfiguration(), true);
                    }
                    if (choice == SheetCallback.DEFAULT_OPTION) {
                        bookmarks.addAll(t);
                        // Flag as imported
                        preferences.setProperty(t.getConfiguration(), true);
                    }
                }
                thirdpartySemaphore.countDown();
            }

            @Override
            public String getActivity() {
                return "Loading thirdparty bookmarks";
            }

            private List<ThirdpartyBookmarkCollection> getThirdpartyBookmarks() {
                return Arrays.asList(new Transmit4BookmarkCollection(), new FilezillaBookmarkCollection(),
                        new FetchBookmarkCollection(), new FlowBookmarkCollection(),
                        new InterarchyBookmarkCollection(), new CrossFtpBookmarkCollection(),
                        new FireFtpBookmarkCollection(), new Expandrive3BookmarkCollection(),
                        new Expandrive4BookmarkCollection(), new Expandrive5BookmarkCollection());
            }
        });
        final CrashReporter reporter = CrashReporter.create();
        if (log.isInfoEnabled()) {
            log.info("Check for crash report");
        }
        reporter.checkForCrash(preferences.getProperty("website.crash"));
        if (updater.hasUpdatePrivileges()) {
            if (PreferencesFactory.get().getBoolean("update.check")) {
                final long next = preferences.getLong("update.check.timestamp")
                        + preferences.getLong("update.check.interval") * 1000;
                if (next < System.currentTimeMillis()) {
                    updater.check(true);
                }
                updater.register();
            }
        }
        NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID(
                this.id(), Foundation.selector("handleGetURLEvent:withReplyEvent:"), kInternetEventClass,
                kAEGetURL);
    }

    /**
     * NSService implementation
     */
    @Action
    public void serviceUploadFileUrl_(final NSPasteboard pboard, final String userData) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("serviceUploadFileUrl_: with user data %s", userData));
        }
        if (pboard.availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) {
            NSObject o = pboard.propertyListForType(NSPasteboard.FilenamesPboardType);
            if (o != null) {
                if (o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) {
                    final NSArray elements = Rococoa.cast(o, NSArray.class);
                    List<Local> files = new ArrayList<Local>();
                    for (int i = 0; i < elements.count().intValue(); i++) {
                        files.add(LocalFactory.get(elements.objectAtIndex(new NSUInteger(i)).toString()));
                    }
                    this.upload(files);
                }
            }
        }
    }

    /**
     * Invoked from within the terminate method immediately before the
     * application terminates. sender is the NSApplication to be terminated.
     * If this method returns false, the application is not terminated,
     * and control returns to the main event loop.
     *
     * @param app Application instance
     * @return Return true to allow the application to terminate.
     */
    @Override
    public NSUInteger applicationShouldTerminate(final NSApplication app) {
        if (log.isDebugEnabled()) {
            log.debug("Application should quit with notification");
        }
        // Determine if there are any running transfers
        final NSUInteger result = TransferControllerFactory.applicationShouldTerminate(app);
        if (!result.equals(NSApplication.NSTerminateNow)) {
            return result;
        }
        // Determine if there are any open connections
        for (BrowserController browser : MainController.getBrowsers()) {
            if (preferences.getBoolean("browser.serialize")) {
                if (browser.isMounted()) {
                    // The workspace should be saved. Serialize all open browser sessions
                    final Host serialized = new HostDictionary()
                            .deserialize(browser.getSession().getHost().serialize(SerializerFactory.get()));
                    serialized.setWorkdir(browser.workdir());
                    sessions.add(serialized);
                    browser.window().saveFrameUsingName(serialized.getUuid());
                }
            }
            if (browser.isConnected()) {
                if (preferences.getBoolean("browser.disconnect.confirm")) {
                    final NSAlert alert = NSAlert.alert(LocaleFactory.localizedString("Quit"),
                            LocaleFactory.localizedString(
                                    "You are connected to at least one remote site. Do you want to review open browsers?"),
                            LocaleFactory.localizedString("Quit Anyway"), //default
                            LocaleFactory.localizedString("Cancel"), //other
                            LocaleFactory.localizedString("Review"));
                    alert.setAlertStyle(NSAlert.NSWarningAlertStyle);
                    alert.setShowsSuppressionButton(true);
                    alert.suppressionButton()
                            .setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
                    int choice = new AlertSheetReturnCodeMapper().getOption(alert.runModal());
                    if (alert.suppressionButton().state() == NSCell.NSOnState) {
                        // Never show again.
                        preferences.setProperty("browser.disconnect.confirm", false);
                    }
                    if (choice == SheetCallback.ALTERNATE_OPTION) {
                        // Cancel. Quit has been interrupted. Delete any saved sessions so far.
                        sessions.clear();
                        return NSApplication.NSTerminateCancel;
                    }
                    if (choice == SheetCallback.CANCEL_OPTION) {
                        // Review if at least one window requested to terminate later, we shall wait.
                        // This will iterate over all mounted browsers.
                        if (NSApplication.NSTerminateNow
                                .equals(BrowserController.applicationShouldTerminate(app))) {
                            return this.applicationShouldTerminateAfterDonationPrompt(app);
                        }
                        return NSApplication.NSTerminateLater;
                    }
                    if (choice == SheetCallback.DEFAULT_OPTION) {
                        // Quit immediatly
                        return this.applicationShouldTerminateAfterDonationPrompt(app);
                    }
                } else {
                    browser.windowShouldClose(browser.window());
                }
            }
        }
        return this.applicationShouldTerminateAfterDonationPrompt(app);
    }

    public NSUInteger applicationShouldTerminateAfterDonationPrompt(final NSApplication app) {
        if (log.isDebugEnabled()) {
            log.debug("applicationShouldTerminateAfterDonationPrompt");
        }
        if (!displayDonationPrompt) {
            // Already displayed
            return NSApplication.NSTerminateNow;
        }
        final License key = LicenseFactory.find();
        if (!key.verify(new DisabledLicenseVerifierCallback())) {
            final String lastversion = preferences.getProperty("donate.reminder");
            if (NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleShortVersionString").toString()
                    .equals(lastversion)) {
                // Do not display if same version is installed
                return NSApplication.NSTerminateNow;
            }
            final Calendar nextreminder = Calendar.getInstance();
            nextreminder.setTimeInMillis(preferences.getLong("donate.reminder.date"));
            // Display donationPrompt every n days
            nextreminder.add(Calendar.DAY_OF_YEAR, preferences.getInteger("y"));
            if (log.isDebugEnabled()) {
                log.debug(String.format("Next reminder %s", nextreminder.getTime().toString()));
            }
            // Display after upgrade
            if (nextreminder.getTime().after(new Date(System.currentTimeMillis()))) {
                // Do not display if shown in the reminder interval
                return NSApplication.NSTerminateNow;
            }
            // Make sure prompt is not loaded twice upon next quit event
            displayDonationPrompt = false;
            donationController = new DonateAlertController(app);
            donationController.setCallback(donationController);
            donationController.loadBundle();
            donationController.window().center();
            donationController.window().makeKeyAndOrderFront(null);
            // Delay application termination. Dismissing the donation dialog will reply to quit.
            return NSApplication.NSTerminateLater;
        }
        return NSApplication.NSTerminateNow;
    }

    /**
     * Quits the Rendezvous daemon and saves all preferences
     *
     * @param notification Notification name
     */
    @Override
    public void applicationWillTerminate(NSNotification notification) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Application will quit with notification %s", notification));
        }
        this.invalidate();
        // Clear temporary files
        TemporaryFileServiceFactory.get().shutdown();
        //Terminating rendezvous discovery
        RendezvousFactory.instance().quit();
        // Remove notifications from center
        NotificationServiceFactory.get().unregister();
        // Disable update
        updater.unregister();
        //Writing usage info
        preferences.setProperty("uses", preferences.getInteger("uses") + 1);
        preferences.save();
        DefaultBackgroundExecutor.get().shutdown();
    }

    @Action
    public void applicationWillRestartAfterUpdate(ID updater) {
        // Disable donation prompt after udpate install
        displayDonationPrompt = false;
    }

    /**
     * We are not a Windows application. Long live the application wide menu bar.
     */
    @Override
    public boolean applicationShouldTerminateAfterLastWindowClosed(NSApplication app) {
        return false;
    }

    @Action
    public void updateMenuClicked(ID sender) {
        updater.check(false);
    }

    /**
     * Extract the URL from the Apple event and handle it here.
     */
    @Action
    public void handleGetURLEvent_withReplyEvent(NSAppleEventDescriptor event, NSAppleEventDescriptor reply) {
        log.debug("Received URL from Apple Event:" + event);
        final NSAppleEventDescriptor param = event.paramDescriptorForKeyword(keyAEResult);
        if (null == param) {
            log.error("No URL parameter");
            return;
        }
        final String url = param.stringValue();
        if (StringUtils.isEmpty(url)) {
            log.error("URL parameter is empty");
            return;
        }
        if (StringUtils.startsWith(url, "x-cyberduck-action:")) {
            final String action = StringUtils.removeStart(url, "x-cyberduck-action:");
            switch (action) {
            case "update":
                updater.check(false);
                break;
            default:
                if (StringUtils.startsWith(action, "oauth?token=")) {
                    final OAuth2TokenListenerRegistry oauth = OAuth2TokenListenerRegistry.get();
                    final String token = StringUtils.removeStart(action, "oauth?token=");
                    oauth.notify(token);
                    break;
                }
            }
        } else {
            final Host h = HostParser.parse(url);
            if (Path.Type.file == detector.detect(h.getDefaultPath())) {
                final Path file = new Path(h.getDefaultPath(), EnumSet.of(Path.Type.file));
                TransferControllerFactory.get()
                        .start(new DownloadTransfer(h, file,
                                LocalFactory.get(preferences.getProperty("queue.download.folder"), file.getName())),
                                new TransferOptions());
            } else {
                for (BrowserController browser : MainController.getBrowsers()) {
                    if (browser.isMounted()) {
                        if (new HostUrlProvider().get(browser.getSession().getHost())
                                .equals(new HostUrlProvider().get(h))) {
                            // Handle browser window already connected to the same host. #4215
                            browser.window().makeKeyAndOrderFront(null);
                            return;
                        }
                    }
                }
                final BrowserController browser = newDocument(false);
                browser.mount(h);
            }
        }
    }

    /**
     * Posted when the user has requested a logout or that the machine be powered off.
     *
     * @param notification Notification name
     */
    @Action
    public void workspaceWillPowerOff(NSNotification notification) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Workspace will power off with notification %s", notification));
        }
    }

    /**
     * Posted before a user session is switched out. This allows an application to
     * disable some processing when its user session is switched out, and reenable when that
     * session gets switched back in, for example.
     *
     * @param notification Notification name
     */
    @Action
    public void workspaceWillLogout(NSNotification notification) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Workspace will logout with notification %s", notification));
        }
    }

    @Action
    public void workspaceWillSleep(NSNotification notification) {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Workspace will sleep with notification %s", notification));
        }
    }

}