Fix transcript in transfer window.
[cyberduck.git] / source / ch / cyberduck / ui / cocoa / ConnectionController.java
blob1fc1e25c0c3a6ad1aff5099be3b8c72ec79fd34b
1 package ch.cyberduck.ui.cocoa;
3 /*
4 * Copyright (c) 2005 David Kocher. All rights reserved.
5 * http://cyberduck.ch/
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * Bug fixes, suggestions and comments should be sent to:
18 * dkocher@cyberduck.ch
21 import ch.cyberduck.binding.ProxyController;
22 import ch.cyberduck.binding.application.NSButton;
23 import ch.cyberduck.binding.application.NSCell;
24 import ch.cyberduck.binding.application.NSColor;
25 import ch.cyberduck.binding.application.NSComboBox;
26 import ch.cyberduck.binding.application.NSControl;
27 import ch.cyberduck.binding.application.NSImage;
28 import ch.cyberduck.binding.application.NSMenuItem;
29 import ch.cyberduck.binding.application.NSOpenPanel;
30 import ch.cyberduck.binding.application.NSPanel;
31 import ch.cyberduck.binding.application.NSPopUpButton;
32 import ch.cyberduck.binding.application.NSTextField;
33 import ch.cyberduck.binding.application.NSWindow;
34 import ch.cyberduck.binding.foundation.NSArray;
35 import ch.cyberduck.binding.foundation.NSAttributedString;
36 import ch.cyberduck.binding.foundation.NSNotification;
37 import ch.cyberduck.binding.foundation.NSNotificationCenter;
38 import ch.cyberduck.binding.foundation.NSObject;
39 import ch.cyberduck.binding.foundation.NSString;
40 import ch.cyberduck.core.*;
41 import ch.cyberduck.core.diagnostics.ReachabilityFactory;
42 import ch.cyberduck.core.exception.BackgroundException;
43 import ch.cyberduck.core.ftp.FTPConnectMode;
44 import ch.cyberduck.core.preferences.Preferences;
45 import ch.cyberduck.core.preferences.PreferencesFactory;
46 import ch.cyberduck.core.resources.IconCacheFactory;
47 import ch.cyberduck.core.threading.AbstractBackgroundAction;
49 import org.apache.commons.lang3.StringUtils;
50 import org.apache.commons.lang3.math.NumberUtils;
51 import org.apache.log4j.Logger;
52 import org.rococoa.Foundation;
53 import org.rococoa.ID;
54 import org.rococoa.cocoa.foundation.NSInteger;
55 import org.rococoa.cocoa.foundation.NSSize;
57 /**
58 * @version $Id$
60 public class ConnectionController extends SheetController {
61 private static Logger log = Logger.getLogger(ConnectionController.class);
63 private final HostPasswordStore keychain
64 = PasswordStoreFactory.get();
66 private Preferences preferences
67 = PreferencesFactory.get();
69 @Override
70 public void invalidate() {
71 hostField.setDelegate(null);
72 hostField.setDataSource(null);
73 super.invalidate();
76 @Override
77 public boolean isSingleton() {
78 return true;
81 public ConnectionController(final WindowController parent) {
82 super(parent);
83 this.loadBundle();
86 @Override
87 protected String getBundleName() {
88 return "Connection";
91 @Override
92 public void awakeFromNib() {
93 this.protocolSelectionDidChange(null);
94 this.setState(toggleOptionsButton, preferences.getBoolean("connection.toggle.options"));
95 super.awakeFromNib();
98 @Override
99 public void beginSheet() {
100 // Reset password input
101 passField.setStringValue(StringUtils.EMPTY);
102 super.beginSheet();
105 @Override
106 public void setWindow(final NSWindow window) {
107 window.setContentMinSize(window.frame().size);
108 window.setContentMaxSize(new NSSize(600, window.frame().size.height.doubleValue()));
109 super.setWindow(window);
112 @Outlet
113 private NSPopUpButton protocolPopup;
115 public void setProtocolPopup(NSPopUpButton protocolPopup) {
116 this.protocolPopup = protocolPopup;
117 this.protocolPopup.setEnabled(true);
118 this.protocolPopup.setTarget(this.id());
119 this.protocolPopup.setAction(Foundation.selector("protocolSelectionDidChange:"));
120 this.protocolPopup.removeAllItems();
121 for(Protocol protocol : ProtocolFactory.getEnabledProtocols()) {
122 final String title = protocol.getDescription();
123 this.protocolPopup.addItemWithTitle(title);
124 final NSMenuItem item = this.protocolPopup.itemWithTitle(title);
125 item.setRepresentedObject(String.valueOf(protocol.hashCode()));
126 item.setImage(IconCacheFactory.<NSImage>get().iconNamed(protocol.icon(), 16));
128 final Protocol defaultProtocol
129 = ProtocolFactory.forName(preferences.getProperty("connection.protocol.default"));
130 this.protocolPopup.selectItemAtIndex(
131 protocolPopup.indexOfItemWithRepresentedObject(String.valueOf(defaultProtocol.hashCode()))
135 public void protocolSelectionDidChange(final NSPopUpButton sender) {
136 log.debug("protocolSelectionDidChange:" + sender);
137 final Protocol protocol = ProtocolFactory.forName(protocolPopup.selectedItem().representedObject());
138 portField.setIntValue(protocol.getDefaultPort());
139 portField.setEnabled(protocol.isPortConfigurable());
140 if(!protocol.isHostnameConfigurable()) {
141 hostField.setStringValue(protocol.getDefaultHostname());
142 hostField.setEnabled(false);
143 pathField.setEnabled(true);
145 else {
146 if(!hostField.isEnabled()) {
147 // Was previously configured with a static configuration
148 hostField.setStringValue(protocol.getDefaultHostname());
150 if(!pathField.isEnabled()) {
151 // Was previously configured with a static configuration
152 pathField.setStringValue(StringUtils.EMPTY);
154 if(StringUtils.isNotBlank(protocol.getDefaultHostname())) {
155 // Prefill with default hostname
156 hostField.setStringValue(protocol.getDefaultHostname());
158 usernameField.setEnabled(true);
159 hostField.setEnabled(true);
160 pathField.setEnabled(true);
161 usernameField.cell().setPlaceholderString(StringUtils.EMPTY);
162 passField.cell().setPlaceholderString(StringUtils.EMPTY);
164 hostField.cell().setPlaceholderString(protocol.getDefaultHostname());
165 usernameField.cell().setPlaceholderString(protocol.getUsernamePlaceholder());
166 passField.cell().setPlaceholderString(protocol.getPasswordPlaceholder());
167 connectmodePopup.setEnabled(protocol.getType() == Protocol.Type.ftp);
168 if(!protocol.isEncodingConfigurable()) {
169 encodingPopup.selectItemWithTitle(DEFAULT);
171 encodingPopup.setEnabled(protocol.isEncodingConfigurable());
172 anonymousCheckbox.setEnabled(protocol.isAnonymousConfigurable());
174 this.updateIdentity();
175 this.updateURLLabel();
176 this.readPasswordFromKeychain();
177 this.reachable();
181 * Update Private Key selection
183 private void updateIdentity() {
184 final Protocol protocol = ProtocolFactory.forName(protocolPopup.selectedItem().representedObject());
185 pkCheckbox.setEnabled(protocol.getType() == Protocol.Type.ssh);
186 if(StringUtils.isNotEmpty(hostField.stringValue())) {
187 final Credentials credentials = CredentialsConfiguratorFactory.get(protocol).configure(new Host(hostField.stringValue()));
188 if(credentials.isPublicKeyAuthentication()) {
189 // No previously manually selected key
190 pkLabel.setStringValue(credentials.getIdentity().getAbbreviatedPath());
191 pkCheckbox.setState(NSCell.NSOnState);
193 else {
194 pkCheckbox.setState(NSCell.NSOffState);
195 pkLabel.setStringValue(LocaleFactory.localizedString("No private key selected"));
197 if(StringUtils.isNotBlank(credentials.getUsername())) {
198 usernameField.setStringValue(credentials.getUsername());
203 private NSComboBox hostField;
204 private ProxyController hostFieldModel = new HostFieldModel();
206 public void setHostPopup(NSComboBox hostPopup) {
207 this.hostField = hostPopup;
208 this.hostField.setTarget(this.id());
209 this.hostField.setAction(Foundation.selector("hostPopupSelectionDidChange:"));
210 this.hostField.setUsesDataSource(true);
211 this.hostField.setDataSource(hostFieldModel.id());
212 NSNotificationCenter.defaultCenter().addObserver(this.id(),
213 Foundation.selector("hostFieldTextDidChange:"),
214 NSControl.NSControlTextDidChangeNotification,
215 this.hostField);
218 private static class HostFieldModel extends ProxyController implements NSComboBox.DataSource {
219 @Override
220 public NSInteger numberOfItemsInComboBox(final NSComboBox sender) {
221 return new NSInteger(BookmarkCollection.defaultCollection().size());
224 @Override
225 public NSObject comboBox_objectValueForItemAtIndex(final NSComboBox sender, final NSInteger row) {
226 return NSString.stringWithString(
227 BookmarkNameProvider.toString(BookmarkCollection.defaultCollection().get(row.intValue()))
232 @Action
233 public void hostPopupSelectionDidChange(final NSControl sender) {
234 String input = sender.stringValue();
235 if(StringUtils.isBlank(input)) {
236 return;
238 input = input.trim();
239 // First look for equivalent bookmarks
240 for(Host h : BookmarkCollection.defaultCollection()) {
241 if(BookmarkNameProvider.toString(h).equals(input)) {
242 this.hostChanged(h);
243 this.updateURLLabel();
244 this.readPasswordFromKeychain();
245 this.reachable();
246 break;
251 public void hostFieldTextDidChange(final NSNotification sender) {
252 final Host parsed = HostParser.parse(hostField.stringValue());
253 if(ProtocolFactory.isURL(hostField.stringValue())) {
254 this.hostChanged(parsed);
256 else {
257 this.updateField(hostField, parsed.getHostname());
259 this.updateURLLabel();
260 this.readPasswordFromKeychain();
261 this.reachable();
264 private void hostChanged(final Host host) {
265 this.updateField(hostField, host.getHostname());
266 this.protocolPopup.selectItemAtIndex(
267 protocolPopup.indexOfItemWithRepresentedObject(String.valueOf(host.getProtocol().hashCode()))
269 this.updateField(portField, String.valueOf(host.getPort()));
270 this.updateField(usernameField, host.getCredentials().getUsername());
271 this.updateField(pathField, host.getDefaultPath());
272 anonymousCheckbox.setState(host.getCredentials().isAnonymousLogin() ? NSCell.NSOnState : NSCell.NSOffState);
273 this.anonymousCheckboxClicked(anonymousCheckbox);
274 this.updateIdentity();
278 * Run the connection reachability test in the background
280 private void reachable() {
281 final String hostname = hostField.stringValue();
282 if(StringUtils.isNotBlank(hostname)) {
283 this.background(new AbstractBackgroundAction<Boolean>() {
284 boolean reachable = false;
286 @Override
287 public Boolean run() throws BackgroundException {
288 return reachable = ReachabilityFactory.get().isReachable(new Host(hostname));
291 @Override
292 public void cleanup() {
293 alertIcon.setEnabled(!reachable);
294 alertIcon.setImage(reachable ? null : IconCacheFactory.<NSImage>get().iconNamed("alert.tiff"));
298 else {
299 alertIcon.setImage(IconCacheFactory.<NSImage>get().iconNamed("alert.tiff"));
300 alertIcon.setEnabled(false);
304 @Outlet
305 private NSButton alertIcon;
307 public void setAlertIcon(NSButton alertIcon) {
308 this.alertIcon = alertIcon;
309 this.alertIcon.setTarget(this.id());
310 this.alertIcon.setAction(Foundation.selector("launchNetworkAssistant:"));
313 @Action
314 public void launchNetworkAssistant(final NSButton sender) {
315 ReachabilityFactory.get().diagnose(HostParser.parse(urlLabel.stringValue()));
318 @Outlet
319 private NSTextField pathField;
321 public void setPathField(NSTextField pathField) {
322 this.pathField = pathField;
323 NSNotificationCenter.defaultCenter().addObserver(this.id(),
324 Foundation.selector("pathInputDidEndEditing:"),
325 NSControl.NSControlTextDidEndEditingNotification,
326 this.pathField);
329 public void pathInputDidEndEditing(final NSNotification sender) {
330 this.updateURLLabel();
333 @Outlet
334 private NSTextField portField;
336 public void setPortField(NSTextField portField) {
337 this.portField = portField;
338 NSNotificationCenter.defaultCenter().addObserver(this.id(),
339 Foundation.selector("portFieldTextDidChange:"),
340 NSControl.NSControlTextDidChangeNotification,
341 this.portField);
344 public void portFieldTextDidChange(final NSNotification sender) {
345 if(StringUtils.isBlank(this.portField.stringValue())) {
346 final Protocol protocol = ProtocolFactory.forName(protocolPopup.selectedItem().representedObject());
347 this.portField.setIntValue(protocol.getDefaultPort());
349 this.updateURLLabel();
350 this.reachable();
353 @Outlet
354 private NSTextField usernameField;
356 public void setUsernameField(NSTextField usernameField) {
357 this.usernameField = usernameField;
358 this.usernameField.setStringValue(preferences.getProperty("connection.login.name"));
359 NSNotificationCenter.defaultCenter().addObserver(this.id(),
360 Foundation.selector("usernameFieldTextDidChange:"),
361 NSControl.NSControlTextDidChangeNotification,
362 this.usernameField);
363 NSNotificationCenter.defaultCenter().addObserver(this.id(),
364 Foundation.selector("usernameFieldTextDidEndEditing:"),
365 NSControl.NSControlTextDidEndEditingNotification,
366 this.usernameField);
369 public void usernameFieldTextDidChange(final NSNotification sender) {
370 this.updateURLLabel();
373 public void usernameFieldTextDidEndEditing(final NSNotification sender) {
374 this.readPasswordFromKeychain();
377 @Outlet
378 private NSTextField passField;
380 public void setPassField(NSTextField passField) {
381 this.passField = passField;
384 @Outlet
385 private NSTextField pkLabel;
387 public void setPkLabel(NSTextField pkLabel) {
388 this.pkLabel = pkLabel;
389 this.pkLabel.setStringValue(LocaleFactory.localizedString("No private key selected"));
390 this.pkLabel.setTextColor(NSColor.disabledControlTextColor());
393 @Outlet
394 private NSButton keychainCheckbox;
396 public void setKeychainCheckbox(NSButton keychainCheckbox) {
397 this.keychainCheckbox = keychainCheckbox;
398 this.keychainCheckbox.setState(preferences.getBoolean("connection.login.useKeychain")
399 && preferences.getBoolean("connection.login.addKeychain") ? NSCell.NSOnState : NSCell.NSOffState);
400 this.keychainCheckbox.setTarget(this.id());
401 this.keychainCheckbox.setAction(Foundation.selector("keychainCheckboxClicked:"));
404 public void keychainCheckboxClicked(final NSButton sender) {
405 final boolean enabled = sender.state() == NSCell.NSOnState;
406 preferences.setProperty("connection.login.addKeychain", enabled);
409 @Outlet
410 private NSButton anonymousCheckbox;
412 public void setAnonymousCheckbox(NSButton anonymousCheckbox) {
413 this.anonymousCheckbox = anonymousCheckbox;
414 this.anonymousCheckbox.setTarget(this.id());
415 this.anonymousCheckbox.setAction(Foundation.selector("anonymousCheckboxClicked:"));
416 this.anonymousCheckbox.setState(NSCell.NSOffState);
419 @Action
420 public void anonymousCheckboxClicked(final NSButton sender) {
421 if(sender.state() == NSCell.NSOnState) {
422 this.usernameField.setEnabled(false);
423 this.usernameField.setStringValue(preferences.getProperty("connection.login.anon.name"));
424 this.passField.setEnabled(false);
425 this.passField.setStringValue(StringUtils.EMPTY);
427 if(sender.state() == NSCell.NSOffState) {
428 this.usernameField.setEnabled(true);
429 this.usernameField.setStringValue(preferences.getProperty("connection.login.name"));
430 this.passField.setEnabled(true);
432 this.updateURLLabel();
435 @Outlet
436 private NSButton pkCheckbox;
438 public void setPkCheckbox(NSButton pkCheckbox) {
439 this.pkCheckbox = pkCheckbox;
440 this.pkCheckbox.setTarget(this.id());
441 this.pkCheckbox.setAction(Foundation.selector("pkCheckboxSelectionDidChange:"));
442 this.pkCheckbox.setState(NSCell.NSOffState);
445 private NSOpenPanel publicKeyPanel;
447 @Action
448 public void pkCheckboxSelectionDidChange(final NSButton sender) {
449 log.debug("pkCheckboxSelectionDidChange");
450 if(sender.state() == NSCell.NSOnState) {
451 publicKeyPanel = NSOpenPanel.openPanel();
452 publicKeyPanel.setCanChooseDirectories(false);
453 publicKeyPanel.setCanChooseFiles(true);
454 publicKeyPanel.setAllowsMultipleSelection(false);
455 publicKeyPanel.setMessage(LocaleFactory.localizedString("Select the private key in PEM or PuTTY format", "Credentials"));
456 publicKeyPanel.setPrompt(LocaleFactory.localizedString("Choose"));
457 publicKeyPanel.beginSheetForDirectory(LocalFactory.get("~/.ssh").getAbsolute(),
458 null, this.window(), this.id(),
459 Foundation.selector("pkSelectionPanelDidEnd:returnCode:contextInfo:"), null);
461 else {
462 passField.setEnabled(true);
463 pkCheckbox.setState(NSCell.NSOffState);
464 pkLabel.setStringValue(LocaleFactory.localizedString("No private key selected"));
465 pkLabel.setTextColor(NSColor.disabledControlTextColor());
469 public void pkSelectionPanelDidEnd_returnCode_contextInfo(NSOpenPanel window, int returncode, ID contextInfo) {
470 if(NSPanel.NSOKButton == returncode) {
471 final NSObject selected = window.filenames().lastObject();
472 if(selected != null) {
473 pkLabel.setAttributedStringValue(NSAttributedString.attributedStringWithAttributes(
474 LocalFactory.get(selected.toString()).getAbbreviatedPath(), TRUNCATE_MIDDLE_ATTRIBUTES));
475 pkLabel.setTextColor(NSColor.textColor());
477 passField.setEnabled(false);
479 if(NSPanel.NSCancelButton == returncode) {
480 passField.setEnabled(true);
481 pkCheckbox.setState(NSCell.NSOffState);
482 pkLabel.setStringValue(LocaleFactory.localizedString("No private key selected"));
483 pkLabel.setTextColor(NSColor.disabledControlTextColor());
485 publicKeyPanel = null;
488 @Outlet
489 private NSTextField urlLabel;
491 public void setUrlLabel(NSTextField urlLabel) {
492 this.urlLabel = urlLabel;
493 this.urlLabel.setAllowsEditingTextAttributes(true);
494 this.urlLabel.setSelectable(true);
497 @Outlet
498 private NSPopUpButton encodingPopup;
500 public void setEncodingPopup(NSPopUpButton encodingPopup) {
501 this.encodingPopup = encodingPopup;
502 this.encodingPopup.setEnabled(true);
503 this.encodingPopup.removeAllItems();
504 this.encodingPopup.addItemWithTitle(DEFAULT);
505 this.encodingPopup.menu().addItem(NSMenuItem.separatorItem());
506 this.encodingPopup.addItemsWithTitles(NSArray.arrayWithObjects(new DefaultCharsetProvider().availableCharsets()));
507 this.encodingPopup.selectItemWithTitle(DEFAULT);
510 @Outlet
511 private NSPopUpButton connectmodePopup;
513 public void setConnectmodePopup(NSPopUpButton connectmodePopup) {
514 this.connectmodePopup = connectmodePopup;
515 this.connectmodePopup.removeAllItems();
516 for(FTPConnectMode m : FTPConnectMode.values()) {
517 this.connectmodePopup.addItemWithTitle(m.toString());
518 this.connectmodePopup.lastItem().setRepresentedObject(m.name());
519 if(m.equals(FTPConnectMode.unknown)) {
520 this.connectmodePopup.selectItem(this.connectmodePopup.lastItem());
521 this.connectmodePopup.menu().addItem(NSMenuItem.separatorItem());
526 @Outlet
527 private NSButton toggleOptionsButton;
529 public void setToggleOptionsButton(NSButton b) {
530 this.toggleOptionsButton = b;
534 * Updating the password field with the actual password if any
535 * is available for this hostname
537 public void readPasswordFromKeychain() {
538 if(preferences.getBoolean("connection.login.useKeychain")) {
539 if(StringUtils.isBlank(hostField.stringValue())) {
540 return;
542 if(StringUtils.isBlank(portField.stringValue())) {
543 return;
545 if(StringUtils.isBlank(usernameField.stringValue())) {
546 return;
548 final Protocol protocol = ProtocolFactory.forName(protocolPopup.selectedItem().representedObject());
549 final String password = keychain.getPassword(protocol.getScheme(),
550 NumberUtils.toInt(portField.stringValue(), -1),
551 hostField.stringValue(), usernameField.stringValue());
552 if(StringUtils.isNotBlank(password)) {
553 this.updateField(passField, password);
558 private void updateURLLabel() {
559 if(StringUtils.isNotBlank(hostField.stringValue())) {
560 final Protocol protocol = ProtocolFactory.forName(protocolPopup.selectedItem().representedObject());
561 final String url = String.format("%s://%s@%s:%d%s",
562 protocol.getScheme(),
563 usernameField.stringValue(),
564 hostField.stringValue(),
565 NumberUtils.toInt(portField.stringValue(), -1),
566 PathNormalizer.normalize(pathField.stringValue()));
567 urlLabel.setAttributedStringValue(HyperlinkAttributedStringFactory.create(url));
569 else {
570 urlLabel.setStringValue(StringUtils.EMPTY);
574 public void helpButtonClicked(final ID sender) {
575 new DefaultProviderHelpService().help(
576 ProtocolFactory.forName(protocolPopup.selectedItem().representedObject())
580 @Override
581 protected boolean validateInput() {
582 if(StringUtils.isBlank(hostField.stringValue())) {
583 return false;
585 if(StringUtils.isBlank(usernameField.stringValue())) {
586 return false;
588 return true;
591 @Override
592 public void callback(final int returncode) {
593 if(returncode == DEFAULT_OPTION) {
594 this.window().endEditingFor(null);
595 final Protocol protocol = ProtocolFactory.forName(protocolPopup.selectedItem().representedObject());
596 final Host host = new Host(
597 protocol,
598 hostField.stringValue(),
599 NumberUtils.toInt(portField.stringValue(), -1),
600 pathField.stringValue());
601 if(protocol.getType() == Protocol.Type.ftp) {
602 host.setFTPConnectMode(FTPConnectMode.valueOf(connectmodePopup.selectedItem().representedObject()));
604 final Credentials credentials = host.getCredentials();
605 credentials.setUsername(usernameField.stringValue());
606 credentials.setPassword(passField.stringValue());
607 credentials.setSaved(keychainCheckbox.state() == NSCell.NSOnState);
608 if(protocol.getScheme().equals(Scheme.sftp)) {
609 if(pkCheckbox.state() == NSCell.NSOnState) {
610 credentials.setIdentity(LocalFactory.get(pkLabel.stringValue()));
613 if(encodingPopup.titleOfSelectedItem().equals(DEFAULT)) {
614 host.setEncoding(null);
616 else {
617 host.setEncoding(encodingPopup.titleOfSelectedItem());
619 ((BrowserController) parent).mount(host);
621 preferences.setProperty("connection.toggle.options", this.toggleOptionsButton.state());