Java tutorial
package ch.cyberduck.ui.cocoa; /* * Copyright (c) 2002-2010 David Kocher. All rights reserved. * * http://cyberduck.ch/ * * 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. * * Bug fixes, suggestions and comments should be sent to: * dkocher@cyberduck.ch */ import ch.cyberduck.binding.Action; import ch.cyberduck.binding.Delegate; import ch.cyberduck.binding.HyperlinkAttributedStringFactory; import ch.cyberduck.binding.Outlet; import ch.cyberduck.binding.application.*; import ch.cyberduck.binding.foundation.NSAttributedString; import ch.cyberduck.binding.foundation.NSIndexSet; import ch.cyberduck.binding.foundation.NSMutableAttributedString; import ch.cyberduck.binding.foundation.NSNotification; import ch.cyberduck.binding.foundation.NSNotificationCenter; import ch.cyberduck.binding.foundation.NSObject; import ch.cyberduck.binding.foundation.NSString; import ch.cyberduck.core.*; import ch.cyberduck.core.analytics.AnalyticsProvider; import ch.cyberduck.core.cdn.Distribution; import ch.cyberduck.core.cdn.DistributionConfiguration; import ch.cyberduck.core.cdn.features.Cname; import ch.cyberduck.core.cdn.features.DistributionLogging; import ch.cyberduck.core.cdn.features.Index; import ch.cyberduck.core.cdn.features.Purge; import ch.cyberduck.core.date.RFC1123DateFormatter; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.AclPermission; import ch.cyberduck.core.features.Encryption; import ch.cyberduck.core.features.Headers; import ch.cyberduck.core.features.Lifecycle; import ch.cyberduck.core.features.Location; import ch.cyberduck.core.features.Logging; import ch.cyberduck.core.features.Move; import ch.cyberduck.core.features.Redundancy; import ch.cyberduck.core.features.UnixPermission; import ch.cyberduck.core.features.Versioning; import ch.cyberduck.core.formatter.SizeFormatterFactory; import ch.cyberduck.core.identity.IdentityConfiguration; import ch.cyberduck.core.io.Checksum; import ch.cyberduck.core.lifecycle.LifecycleConfiguration; import ch.cyberduck.core.local.BrowserLauncherFactory; import ch.cyberduck.core.local.FileDescriptor; import ch.cyberduck.core.local.FileDescriptorFactory; import ch.cyberduck.core.logging.LoggingConfiguration; import ch.cyberduck.core.preferences.Preferences; import ch.cyberduck.core.preferences.PreferencesFactory; import ch.cyberduck.core.resources.IconCacheFactory; import ch.cyberduck.core.s3.S3Protocol; import ch.cyberduck.core.threading.AlertRecursiveCallback; import ch.cyberduck.core.threading.BrowserControllerBackgroundAction; import ch.cyberduck.core.threading.WindowMainAction; import ch.cyberduck.core.threading.WorkerBackgroundAction; import ch.cyberduck.core.worker.BooleanRecursiveCallback; import ch.cyberduck.core.worker.CalculateSizeWorker; import ch.cyberduck.core.worker.ReadAclWorker; import ch.cyberduck.core.worker.ReadMetadataWorker; import ch.cyberduck.core.worker.ReadPermissionWorker; import ch.cyberduck.core.worker.ReadSizeWorker; import ch.cyberduck.core.worker.WriteAclWorker; import ch.cyberduck.core.worker.WriteEncryptionWorker; import ch.cyberduck.core.worker.WriteMetadataWorker; import ch.cyberduck.core.worker.WritePermissionWorker; import ch.cyberduck.core.worker.WriteRedundancyWorker; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.jets3t.service.model.S3Object; import org.rococoa.Foundation; import org.rococoa.ID; import org.rococoa.Selector; import org.rococoa.cocoa.foundation.NSInteger; import org.rococoa.cocoa.foundation.NSPoint; import org.rococoa.cocoa.foundation.NSSize; import org.rococoa.cocoa.foundation.NSUInteger; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TimeZone; public class InfoController extends ToolbarWindowController { private static Logger log = Logger.getLogger(InfoController.class); private final NSNotificationCenter notificationCenter = NSNotificationCenter.defaultCenter(); /** * Selected files */ private List<Path> files; private FileDescriptor descriptor = FileDescriptorFactory.get(); private LoginCallback prompt = LoginCallbackFactory.get(this); private PathContainerService containerService = new PathContainerService(); private Preferences preferences = PreferencesFactory.get(); private Path getSelected() { for (Path file : files) { return file; } return null; } @Outlet private NSTextField filenameField; public void setFilenameField(NSTextField filenameField) { this.filenameField = filenameField; notificationCenter.addObserver(this.id(), Foundation.selector("filenameInputDidEndEditing:"), NSControl.NSControlTextDidEndEditingNotification, filenameField); } @Outlet private NSTextField groupField; public void setGroupField(NSTextField t) { this.groupField = t; } @Outlet private NSTextField kindField; public void setKindField(NSTextField t) { this.kindField = t; } @Outlet private NSTextField modifiedField; public void setModifiedField(NSTextField t) { this.modifiedField = t; } @Outlet private NSTextField createdField; public void setCreatedField(NSTextField t) { this.createdField = t; } @Outlet private NSTextField permissionsField; public void setPermissionsField(NSTextField permissionsField) { this.permissionsField = permissionsField; } @Outlet private NSTextField octalField; public void setOctalField(NSTextField octalField) { this.octalField = octalField; notificationCenter.addObserver(this.id(), Foundation.selector("octalPermissionsInputDidEndEditing:"), NSControl.NSControlTextDidEndEditingNotification, octalField); } @Outlet private NSTextField ownerField; public void setOwnerField(NSTextField ownerField) { this.ownerField = ownerField; } @Outlet private NSTextField sizeField; public void setSizeField(NSTextField sizeField) { this.sizeField = sizeField; } @Outlet private NSTextField checksumField; public void setChecksumField(NSTextField checksumField) { this.checksumField = checksumField; } @Outlet private NSTextField pathField; public void setPathField(NSTextField pathField) { this.pathField = pathField; } @Outlet private NSTextField webUrlField; public void setWebUrlField(NSTextField webUrlField) { this.webUrlField = webUrlField; this.webUrlField.setAllowsEditingTextAttributes(true); this.webUrlField.setSelectable(true); } @Outlet private NSButton recursiveButton; public void setRecursiveButton(NSButton b) { this.recursiveButton = b; this.recursiveButton.setTarget(this.id()); this.recursiveButton.setAction(Foundation.selector("recursiveButtonClicked:")); } @Outlet private NSButton sizeButton; public void setSizeButton(NSButton b) { this.sizeButton = b; this.sizeButton.setTarget(this.id()); this.sizeButton.setAction(Foundation.selector("calculateSizeButtonClicked:")); } @Outlet private NSProgressIndicator sizeProgress; public void setSizeProgress(final NSProgressIndicator p) { this.sizeProgress = p; this.sizeProgress.setDisplayedWhenStopped(false); this.sizeProgress.setStyle(NSProgressIndicator.NSProgressIndicatorSpinningStyle); } @Outlet private NSProgressIndicator permissionProgress; public void setPermissionProgress(final NSProgressIndicator p) { this.permissionProgress = p; this.permissionProgress.setDisplayedWhenStopped(false); this.permissionProgress.setStyle(NSProgressIndicator.NSProgressIndicatorSpinningStyle); } @Outlet private NSProgressIndicator s3Progress; public void setS3Progress(final NSProgressIndicator p) { this.s3Progress = p; this.s3Progress.setDisplayedWhenStopped(false); this.s3Progress.setStyle(NSProgressIndicator.NSProgressIndicatorSpinningStyle); } @Outlet private NSProgressIndicator aclProgress; public void setAclProgress(final NSProgressIndicator p) { this.aclProgress = p; this.aclProgress.setDisplayedWhenStopped(false); this.aclProgress.setStyle(NSProgressIndicator.NSProgressIndicatorSpinningStyle); } @Outlet private NSProgressIndicator metadataProgress; public void setMetadataProgress(final NSProgressIndicator p) { this.metadataProgress = p; this.metadataProgress.setDisplayedWhenStopped(false); this.metadataProgress.setStyle(NSProgressIndicator.NSProgressIndicatorSpinningStyle); } @Outlet private NSProgressIndicator distributionProgress; public void setDistributionProgress(final NSProgressIndicator p) { this.distributionProgress = p; this.distributionProgress.setDisplayedWhenStopped(false); this.distributionProgress.setStyle(NSProgressIndicator.NSProgressIndicatorSpinningStyle); } @Outlet private NSButton distributionEnableButton; public void setDistributionEnableButton(NSButton b) { this.distributionEnableButton = b; this.distributionEnableButton.setTarget(this.id()); this.distributionEnableButton.setAction(Foundation.selector("distributionApplyButtonClicked:")); } @Outlet private NSButton distributionLoggingButton; public void setDistributionLoggingButton(NSButton b) { this.distributionLoggingButton = b; this.distributionLoggingButton.setTarget(this.id()); this.distributionLoggingButton.setAction(Foundation.selector("distributionApplyButtonClicked:")); } private NSPopUpButton distributionLoggingPopup; public void setDistributionLoggingPopup(NSPopUpButton b) { this.distributionLoggingPopup = b; this.distributionLoggingPopup.setTarget(this.id()); this.distributionLoggingPopup.setAction(Foundation.selector("distributionLoggingPopupClicked:")); } @Outlet private NSButton distributionInvalidateObjectsButton; public void setDistributionInvalidateObjectsButton(NSButton b) { this.distributionInvalidateObjectsButton = b; this.distributionInvalidateObjectsButton.setTarget(this.id()); this.distributionInvalidateObjectsButton .setAction(Foundation.selector("distributionInvalidateObjectsButtonClicked:")); } @Outlet private NSTextField distributionInvalidationStatusField; public void setDistributionInvalidationStatusField(NSTextField t) { this.distributionInvalidationStatusField = t; } @Outlet private NSPopUpButton distributionDeliveryPopup; public void setDistributionDeliveryPopup(NSPopUpButton b) { this.distributionDeliveryPopup = b; this.distributionDeliveryPopup.setTarget(this.id()); this.distributionDeliveryPopup.setAction(Foundation.selector("distributionStatusButtonClicked:")); } @Outlet private NSPopUpButton distributionDefaultRootPopup; public void setDistributionDefaultRootPopup(NSPopUpButton b) { this.distributionDefaultRootPopup = b; this.distributionDefaultRootPopup.setTarget(this.id()); this.distributionDefaultRootPopup.setAction(Foundation.selector("distributionApplyButtonClicked:")); } @Outlet private NSTextField bucketLocationField; public void setBucketLocationField(NSTextField t) { this.bucketLocationField = t; } @Outlet private NSPopUpButton storageClassPopup; public void setStorageClassPopup(NSPopUpButton b) { this.storageClassPopup = b; this.storageClassPopup.setTarget(this.id()); this.storageClassPopup.setAction(Foundation.selector("storageClassPopupClicked:")); this.storageClassPopup.setAllowsMixedState(true); } @Action public void storageClassPopupClicked(final NSPopUpButton sender) { if (this.toggleS3Settings(false)) { final Redundancy feature = controller.getSession().getFeature(Redundancy.class); final String redundancy = sender.selectedItem().representedObject(); controller.background(new WorkerBackgroundAction<Boolean>(controller, controller.getSession(), controller.getCache(), new WriteRedundancyWorker(files, redundancy, new AlertRecursiveCallback<String>(this), controller) { @Override public void cleanup(final Boolean v) { toggleS3Settings(true); initS3(); } })); } } @Outlet private NSPopUpButton encryptionPopup; public void setEncryptionPopup(NSPopUpButton b) { this.encryptionPopup = b; this.encryptionPopup.setTarget(this.id()); this.encryptionPopup.setAction(Foundation.selector("encryptionPopupClicked:")); this.encryptionPopup.setAllowsMixedState(true); } @Action public void encryptionPopupClicked(final NSPopUpButton sender) { final Encryption feature = controller.getSession().getFeature(Encryption.class); final String algorithm = sender.selectedItem().representedObject(); if (null != algorithm && this.toggleS3Settings(false)) { final Encryption.Algorithm encryption = Encryption.Algorithm.fromString(algorithm); controller.background(new WorkerBackgroundAction<Boolean>(controller, controller.getSession(), controller.getCache(), new WriteEncryptionWorker(files, encryption, new AlertRecursiveCallback<Encryption.Algorithm>(this), controller) { @Override public void cleanup(final Boolean v) { toggleS3Settings(true); initS3(); } })); } } @Outlet private NSButton bucketLoggingButton; public void setBucketLoggingButton(NSButton b) { this.bucketLoggingButton = b; this.bucketLoggingButton.setAction(Foundation.selector("bucketLoggingButtonClicked:")); } @Action public void bucketLoggingButtonClicked(final NSButton sender) { if (this.toggleS3Settings(false)) { controller.background(new BrowserControllerBackgroundAction(controller) { @Override public Boolean run() throws BackgroundException { controller.getSession().getFeature(Logging.class).setConfiguration( containerService.getContainer(getSelected()), new LoggingConfiguration(bucketLoggingButton.state() == NSCell.NSOnState, null == bucketLoggingPopup.selectedItem() ? null : bucketLoggingPopup.selectedItem().representedObject())); return true; } @Override public void cleanup() { super.cleanup(); toggleS3Settings(true); initS3(); } @Override public String getActivity() { return MessageFormat.format(LocaleFactory.localizedString("Writing metadata of {0}", "Status"), this.toString(files)); } }); } } private NSPopUpButton bucketLoggingPopup; public void setBucketLoggingPopup(NSPopUpButton b) { this.bucketLoggingPopup = b; this.bucketLoggingPopup.setTarget(this.id()); this.bucketLoggingPopup.setAction(Foundation.selector("bucketLoggingPopupClicked:")); } @Action public void bucketLoggingPopupClicked(final NSPopUpButton sender) { if (bucketLoggingButton.state() == NSCell.NSOnState) { // Only write change if logging is already enabled this.bucketLoggingButtonClicked(sender); } } @Outlet private NSButton bucketAnalyticsButton; public void setBucketAnalyticsButton(NSButton b) { this.bucketAnalyticsButton = b; this.bucketAnalyticsButton.setAction(Foundation.selector("bucketAnalyticsButtonClicked:")); } @Action public void bucketAnalyticsButtonClicked(final NSButton sender) { if (this.toggleS3Settings(false)) { controller.background(new BrowserControllerBackgroundAction<Void>(controller) { @Override public Void run() throws BackgroundException { final Session<?> session = controller.getSession(); final IdentityConfiguration iam = session.getFeature(IdentityConfiguration.class); if (bucketAnalyticsButton.state() == NSCell.NSOnState) { final String document = preferences.getProperty("analytics.provider.qloudstat.iam.policy"); iam.create(controller.getSession().getFeature(AnalyticsProvider.class).getName(), document, prompt); } else { iam.delete(controller.getSession().getFeature(AnalyticsProvider.class).getName(), prompt); } return null; } @Override public void cleanup() { super.cleanup(); toggleS3Settings(true); initS3(); } }); } } @Outlet private NSTextField bucketAnalyticsSetupUrlField; public void setBucketAnalyticsSetupUrlField(NSTextField f) { this.bucketAnalyticsSetupUrlField = f; this.bucketAnalyticsSetupUrlField.setAllowsEditingTextAttributes(true); this.bucketAnalyticsSetupUrlField.setSelectable(true); } @Outlet private NSButton bucketVersioningButton; public void setBucketVersioningButton(NSButton b) { this.bucketVersioningButton = b; this.bucketVersioningButton.setAction(Foundation.selector("bucketVersioningButtonClicked:")); } @Action public void bucketVersioningButtonClicked(final NSButton sender) { if (this.toggleS3Settings(false)) { controller.background(new BrowserControllerBackgroundAction<Void>(controller) { @Override public Void run() throws BackgroundException { controller.getSession().getFeature(Versioning.class).setConfiguration( containerService.getContainer(getSelected()), prompt, new VersioningConfiguration(bucketVersioningButton.state() == NSCell.NSOnState, bucketMfaButton.state() == NSCell.NSOnState)); return null; } @Override public void cleanup() { super.cleanup(); toggleS3Settings(true); initS3(); } }); } } @Outlet private NSButton bucketMfaButton; public void setBucketMfaButton(NSButton b) { this.bucketMfaButton = b; this.bucketMfaButton.setAction(Foundation.selector("bucketMfaButtonClicked:")); } @Action public void bucketMfaButtonClicked(final NSButton sender) { if (this.toggleS3Settings(false)) { controller.background(new BrowserControllerBackgroundAction<Void>(controller) { @Override public Void run() throws BackgroundException { controller.getSession().getFeature(Versioning.class).setConfiguration( containerService.getContainer(getSelected()), prompt, new VersioningConfiguration(bucketVersioningButton.state() == NSCell.NSOnState, bucketMfaButton.state() == NSCell.NSOnState)); return null; } @Override public void cleanup() { super.cleanup(); toggleS3Settings(true); initS3(); } }); } } @Outlet private NSTextField s3PublicUrlField; public void setS3PublicUrlField(NSTextField t) { this.s3PublicUrlField = t; this.s3PublicUrlField.setAllowsEditingTextAttributes(true); this.s3PublicUrlField.setSelectable(true); } @Outlet private NSTextField s3PublicUrlValidityField; public void setS3PublicUrlValidityField(NSTextField s3PublicUrlValidityField) { this.s3PublicUrlValidityField = s3PublicUrlValidityField; } @Outlet private NSTextField s3torrentUrlField; public void setS3torrentUrlField(NSTextField t) { this.s3torrentUrlField = t; this.s3torrentUrlField.setAllowsEditingTextAttributes(true); this.s3torrentUrlField.setSelectable(true); } @Outlet private NSButton lifecycleTransitionCheckbox; public void setLifecycleTransitionCheckbox(final NSButton b) { this.lifecycleTransitionCheckbox = b; this.lifecycleTransitionCheckbox.setAction(Foundation.selector("lifecyclePopupClicked:")); } @Outlet private NSPopUpButton lifecycleTransitionPopup; public void setLifecycleTransitionPopup(final NSPopUpButton b) { this.lifecycleTransitionPopup = b; this.lifecycleTransitionPopup.setTarget(this.id()); for (String option : preferences.getList("s3.lifecycle.transition.options")) { this.lifecycleTransitionPopup.addItemWithTitle( MessageFormat.format(LocaleFactory.localizedString("after {0} Days", "S3"), option)); this.lifecycleTransitionPopup.lastItem().setAction(Foundation.selector("lifecyclePopupClicked:")); this.lifecycleTransitionPopup.lastItem().setTarget(this.id()); this.lifecycleTransitionPopup.lastItem().setRepresentedObject(option); } } @Outlet private NSButton lifecycleDeleteCheckbox; public void setLifecycleDeleteCheckbox(final NSButton b) { this.lifecycleDeleteCheckbox = b; this.lifecycleDeleteCheckbox.setAction(Foundation.selector("lifecyclePopupClicked:")); } @Outlet private NSPopUpButton lifecycleDeletePopup; public void setLifecycleDeletePopup(final NSPopUpButton b) { this.lifecycleDeletePopup = b; for (String option : preferences.getList("s3.lifecycle.delete.options")) { this.lifecycleDeletePopup.addItemWithTitle( MessageFormat.format(LocaleFactory.localizedString("after {0} Days", "S3"), option)); this.lifecycleDeletePopup.lastItem().setAction(Foundation.selector("lifecyclePopupClicked:")); this.lifecycleDeletePopup.lastItem().setTarget(this.id()); this.lifecycleDeletePopup.lastItem().setRepresentedObject(option); } } @Action public void lifecyclePopupClicked(final NSButton sender) { if (this.toggleS3Settings(false)) { controller.background(new BrowserControllerBackgroundAction<Void>(controller) { @Override public Void run() throws BackgroundException { controller.getSession().getFeature(Lifecycle.class).setConfiguration( containerService.getContainer(getSelected()), new LifecycleConfiguration( lifecycleTransitionCheckbox.state() == NSCell.NSOnState ? Integer.valueOf( lifecycleTransitionPopup.selectedItem().representedObject()) : null, S3Object.STORAGE_CLASS_GLACIER, lifecycleDeleteCheckbox.state() == NSCell.NSOnState ? Integer.valueOf( lifecycleDeletePopup.selectedItem().representedObject()) : null)); return null; } @Override public void cleanup() { super.cleanup(); toggleS3Settings(true); initS3(); } }); } } @Outlet private NSTextField distributionCnameField; public void setDistributionCnameField(NSTextField t) { this.distributionCnameField = t; notificationCenter.addObserver(this.id(), Foundation.selector("distributionApplyButtonClicked:"), NSControl.NSControlTextDidEndEditingNotification, distributionCnameField); } @Outlet private NSTextField distributionOriginField; public void setDistributionOriginField(NSTextField t) { this.distributionOriginField = t; this.distributionOriginField.setAllowsEditingTextAttributes(true); this.distributionOriginField.setSelectable(true); } @Outlet private NSTextField distributionStatusField; public void setDistributionStatusField(NSTextField t) { this.distributionStatusField = t; } @Outlet private NSTextField distributionUrlField; public void setDistributionUrlField(NSTextField t) { this.distributionUrlField = t; this.distributionUrlField.setAllowsEditingTextAttributes(true); this.distributionUrlField.setSelectable(true); } @Outlet private NSTextField distributionCnameUrlField; public void setDistributionCnameUrlField(NSTextField t) { this.distributionCnameUrlField = t; this.distributionCnameUrlField.setAllowsEditingTextAttributes(true); this.distributionCnameUrlField.setSelectable(true); } @Outlet private NSTextField aclUrlField; public void setAclUrlField(NSTextField t) { this.aclUrlField = t; this.aclUrlField.setAllowsEditingTextAttributes(true); this.aclUrlField.setSelectable(true); } /** * Grant editing model. */ private List<Acl.UserAndRole> acl = new ArrayList<Acl.UserAndRole>(); /** * Replace current metadata model. Will reload the table view. * * @param permissions The updated access control list */ private void setAcl(List<Acl.UserAndRole> permissions) { this.acl.clear(); this.acl.addAll(permissions); this.aclTable.reloadData(); } @Outlet private NSTableView aclTable; @Delegate private ListDataSource aclTableModel; @Delegate private AbstractTableDelegate<Acl.UserAndRole> aclTableDelegate; private enum AclColumns { GRANTEE, PERMISSION, } private final NSComboBoxCell aclPermissionCellPrototype = NSComboBoxCell.comboBoxCell(); public void setAclTable(final NSTableView t) { this.aclTable = t; this.aclTable.setAllowsMultipleSelection(true); this.aclPermissionCellPrototype.setFont(NSFont.systemFontOfSize(NSFont.smallSystemFontSize())); this.aclPermissionCellPrototype.setControlSize(NSCell.NSSmallControlSize); this.aclPermissionCellPrototype.setCompletes(false); this.aclPermissionCellPrototype.setBordered(false); this.aclPermissionCellPrototype.setButtonBordered(false); this.aclTable.setColumnAutoresizingStyle(NSTableView.NSTableViewUniformColumnAutoresizingStyle); this.aclTable.tableColumnWithIdentifier(AclColumns.PERMISSION.name()) .setDataCell(aclPermissionCellPrototype); this.aclTable.setDataSource((aclTableModel = new ListDataSource() { @Override public NSInteger numberOfRowsInTableView(NSTableView view) { return new NSInteger(acl.size()); } public NSObject tableView_objectValueForTableColumn_row(NSTableView view, NSTableColumn tableColumn, NSInteger row) { if (row.intValue() < acl.size()) { final String identifier = tableColumn.identifier(); final Acl.UserAndRole grant = acl.get(row.intValue()); if (identifier.equals(AclColumns.GRANTEE.name())) { return NSString.stringWithString(grant.getUser().getDisplayName()); } if (identifier.equals(AclColumns.PERMISSION.name())) { return NSString.stringWithString(grant.getRole().getName()); } } return null; } @Override public void tableView_setObjectValue_forTableColumn_row(NSTableView view, NSObject value, NSTableColumn c, NSInteger row) { if (row.intValue() < acl.size()) { final Acl.UserAndRole grant = acl.get(row.intValue()); if (c.identifier().equals(AclColumns.GRANTEE.name())) { grant.getUser().setIdentifier(value.toString()); } if (c.identifier().equals(AclColumns.PERMISSION.name())) { grant.getRole().setName(value.toString()); } if (StringUtils.isNotBlank(grant.getUser().getIdentifier()) && StringUtils.isNotBlank(grant.getRole().getName())) { InfoController.this.aclInputDidEndEditing(); } } } }).id()); this.aclTable.setDelegate((aclTableDelegate = new AbstractTableDelegate<Acl.UserAndRole>( aclTable.tableColumnWithIdentifier(AclColumns.GRANTEE.name())) { @Override public boolean isColumnRowEditable(NSTableColumn column, int row) { if (column.identifier().equals(AclColumns.GRANTEE.name())) { final Acl.UserAndRole grant = acl.get(row); if (grant.getUser().isEditable()) { return true; } // Group Grantee identifier is not editable return false; } if (column.identifier().equals(AclColumns.PERMISSION.name())) { final Acl.UserAndRole grant = acl.get(row); if (grant.getRole().isEditable()) { return true; } // Static role that cannot be modified return false; } return true; } @Override public void tableRowDoubleClicked(final ID sender) { this.enterKeyPressed(sender); } @Override public void enterKeyPressed(final ID sender) { aclTable.editRow(aclTable.columnWithIdentifier(AclColumns.GRANTEE.name()), aclTable.selectedRow(), true); } @Override public void deleteKeyPressed(final ID sender) { aclRemoveButtonClicked(sender); } public String tableView_toolTipForCell_rect_tableColumn_row_mouseLocation(NSTableView t, NSCell cell, ID rect, NSTableColumn c, NSInteger row, NSPoint mouseLocation) { return this.tooltip(acl.get(row.intValue())); } @Override public String tooltip(Acl.UserAndRole c) { return c.getUser().getIdentifier(); } @Override public void tableColumnClicked(NSTableView view, NSTableColumn c) { // } @Override public void selectionDidChange(NSNotification notification) { aclRemoveButton.setEnabled(aclTable.numberOfSelectedRows().intValue() > 0); } public void tableView_willDisplayCell_forTableColumn_row(NSTableView view, NSTextFieldCell cell, NSTableColumn c, NSInteger row) { if (c.identifier().equals(AclColumns.GRANTEE.name())) { final Acl.UserAndRole grant = acl.get(row.intValue()); cell.setPlaceholderString(grant.getUser().getPlaceholder()); if (grant.getUser().isEditable()) { cell.setTextColor(NSColor.controlTextColor()); } else { // Group Grantee identifier is not editable cell.setTextColor(NSColor.disabledControlTextColor()); } } } @Override protected boolean isTypeSelectSupported() { return false; } }).id()); this.aclTable.sizeToFit(); } @Outlet private NSPopUpButton aclAddButton; public void setAclAddButton(NSPopUpButton b) { this.aclAddButton = b; this.aclAddButton.setTarget(this.id()); } public void aclAddButtonClicked(NSMenuItem sender) { final AclPermission feature = controller.getSession().getFeature(AclPermission.class); for (Acl.User grantee : feature.getAvailableAclUsers()) { if (sender.representedObject().equals(grantee.getPlaceholder())) { this.addAclItem(new Acl.UserAndRole(grantee, new Acl.Role(StringUtils.EMPTY))); } } } /** * Add to the table, reload data and select inserted row. * * @param update The acl to insert. */ private void addAclItem(Acl.UserAndRole update) { List<Acl.UserAndRole> updated = new ArrayList<Acl.UserAndRole>(acl); final int index = updated.size(); updated.add(index, update); this.setAcl(updated); aclTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(new NSInteger(index)), false); if (update.getUser().isEditable()) { aclTable.editRow(aclTable.columnWithIdentifier(AclColumns.GRANTEE.name()), new NSInteger(index), true); } else { aclTable.editRow(aclTable.columnWithIdentifier(AclColumns.PERMISSION.name()), new NSInteger(index), true); } } @Outlet private NSButton aclRemoveButton; public void setAclRemoveButton(NSButton b) { this.aclRemoveButton = b; // Only enable upon selection change this.aclRemoveButton.setEnabled(false); this.aclRemoveButton.setAction(Foundation.selector("aclRemoveButtonClicked:")); this.aclRemoveButton.setTarget(this.id()); } @Action public void aclRemoveButtonClicked(ID sender) { List<Acl.UserAndRole> updated = new ArrayList<Acl.UserAndRole>(acl); NSIndexSet iterator = aclTable.selectedRowIndexes(); List<Acl.UserAndRole> remove = new ArrayList<Acl.UserAndRole>(); for (NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator .indexGreaterThanIndex(index)) { remove.add(updated.get(index.intValue())); } updated.removeAll(remove); this.setAcl(updated); this.aclInputDidEndEditing(); } private void aclInputDidEndEditing() { if (this.toggleAclSettings(false)) { controller.background( new WorkerBackgroundAction<Boolean>(controller, controller.getSession(), controller.getCache(), new WriteAclWorker(files, new Acl(acl.toArray(new Acl.UserAndRole[acl.size()])), new AlertRecursiveCallback<Acl>(this), controller) { @Override public void cleanup(final Boolean v) { toggleAclSettings(true); initAcl(); } })); } } @Outlet private NSTableView metadataTable; @Delegate private ListDataSource metadataTableModel; @Delegate private AbstractTableDelegate<String> metadataTableDelegate; private enum MetadataColumns { NAME, VALUE } /** * Custom HTTP headers for REST protocols */ private List<Header> metadata = new ArrayList<Header>(); /** * Replace current metadata model. Will reload the table view. * * @param m The new header key and values */ private void setMetadata(List<Header> m) { metadata.clear(); metadata.addAll(m); metadataTable.reloadData(); } public void setMetadataTable(final NSTableView t) { this.metadataTable = t; this.metadataTable.setAllowsMultipleSelection(true); this.metadataTable.setColumnAutoresizingStyle(NSTableView.NSTableViewUniformColumnAutoresizingStyle); this.metadataTable.setDataSource((metadataTableModel = new ListDataSource() { @Override public NSInteger numberOfRowsInTableView(NSTableView view) { return new NSInteger(metadata.size()); } public NSObject tableView_objectValueForTableColumn_row(NSTableView view, NSTableColumn tableColumn, NSInteger row) { if (row.intValue() < metadata.size()) { final String identifier = tableColumn.identifier(); if (identifier.equals(MetadataColumns.NAME.name())) { final String name = metadata.get(row.intValue()).getName(); return NSAttributedString .attributedString(StringUtils.isNotEmpty(name) ? name : StringUtils.EMPTY); } if (identifier.equals(MetadataColumns.VALUE.name())) { final String value = metadata.get(row.intValue()).getValue(); if (StringUtils.isEmpty(value)) { return null; } return NSAttributedString .attributedString(StringUtils.isNotEmpty(value) ? value : StringUtils.EMPTY); } } return null; } @Override public void tableView_setObjectValue_forTableColumn_row(NSTableView view, NSObject value, NSTableColumn c, NSInteger row) { if (row.intValue() < metadata.size()) { Header header = metadata.get(row.intValue()); if (c.identifier().equals(MetadataColumns.NAME.name())) { header.setName(value.toString()); } if (c.identifier().equals(MetadataColumns.VALUE.name())) { header.setValue(value.toString()); } if (StringUtils.isNotBlank(header.getName()) && StringUtils.isNotBlank(header.getValue())) { // Only update if both fields are set metadataInputDidEndEditing(); } } } }).id()); this.metadataTable.setDelegate((metadataTableDelegate = new AbstractTableDelegate<String>( metadataTable.tableColumnWithIdentifier(MetadataColumns.NAME.name())) { @Override public boolean isColumnRowEditable(NSTableColumn column, int row) { return true; } @Override public void tableRowDoubleClicked(final ID sender) { this.enterKeyPressed(sender); } @Override public void enterKeyPressed(final ID sender) { metadataTable.editRow(metadataTable.columnWithIdentifier(MetadataColumns.VALUE.name()), metadataTable.selectedRow(), true); } @Override public void deleteKeyPressed(final ID sender) { metadataRemoveButtonClicked(sender); } @Override public String tooltip(String c) { return c; } @Override public void tableColumnClicked(NSTableView view, NSTableColumn c) { // } @Override public void selectionDidChange(NSNotification notification) { metadataRemoveButton.setEnabled(metadataTable.numberOfSelectedRows().intValue() > 0); } @Override protected boolean isTypeSelectSupported() { return false; } public void tableView_willDisplayCell_forTableColumn_row(NSTableView view, NSTextFieldCell cell, NSTableColumn c, NSInteger row) { if (c.identifier().equals(MetadataColumns.VALUE.name())) { final String value = metadata.get(row.intValue()).getValue(); if (null == value) { cell.setPlaceholderString(LocaleFactory.localizedString("Multiple files")); } } } }).id()); this.metadataTable.sizeToFit(); } @Outlet private NSPopUpButton metadataAddButton; public void setMetadataAddButton(NSPopUpButton b) { this.metadataAddButton = b; this.metadataAddButton.setTarget(this.id()); this.metadataAddButton.addItemWithTitle(StringUtils.EMPTY); this.metadataAddButton.lastItem().setImage(IconCacheFactory.<NSImage>get().iconNamed("gear.tiff")); this.metadataAddButton.addItemWithTitle(LocaleFactory.localizedString("Custom Header", "S3")); this.metadataAddButton.lastItem().setAction(Foundation.selector("metadataAddCustomClicked:")); this.metadataAddButton.lastItem().setTarget(this.id()); this.metadataAddButton.menu().addItem(NSMenuItem.separatorItem()); this.metadataAddButton.addItemWithTitle("Content-Disposition"); this.metadataAddButton.lastItem().setAction(Foundation.selector("metadataAddContentDispositionClicked:")); this.metadataAddButton.lastItem().setTarget(this.id()); this.metadataAddButton.addItemWithTitle("Cache-Control"); this.metadataAddButton.lastItem().setAction(Foundation.selector("metadataAddCacheControlClicked:")); this.metadataAddButton.lastItem().setTarget(this.id()); this.metadataAddButton.addItemWithTitle("Expires"); this.metadataAddButton.lastItem().setAction(Foundation.selector("metadataAddExpiresClicked:")); this.metadataAddButton.lastItem().setTarget(this.id()); this.metadataAddButton.addItemWithTitle("Pragma"); this.metadataAddButton.lastItem().setAction(Foundation.selector("metadataAddPragmaClicked:")); this.metadataAddButton.lastItem().setTarget(this.id()); this.metadataAddButton.addItemWithTitle("Content-Type"); this.metadataAddButton.lastItem().setAction(Foundation.selector("metadataAddContentTypeClicked:")); this.metadataAddButton.lastItem().setTarget(this.id()); this.metadataAddButton.addItemWithTitle("x-amz-website-redirect-location"); this.metadataAddButton.lastItem().setAction(Foundation.selector("metadataAddRedirectLocationClicked:")); this.metadataAddButton.lastItem().setTarget(this.id()); } /** * Add a custom metadata header. This will be prefixed depending on the service. */ @Action public void metadataAddCustomClicked(ID sender) { this.addMetadataItem(); } @Action public void metadataAddContentDispositionClicked(ID sender) { this.addMetadataItem("Content-Disposition", "attachment"); } @Action public void metadataAddCacheControlClicked(ID sender) { this.addMetadataItem("Cache-Control", "public,max-age=" + preferences.getInteger("s3.cache.seconds")); } @Action public void metadataAddContentTypeClicked(ID sender) { this.addMetadataItem("Content-Type", StringUtils.EMPTY, true); } @Action public void metadataAddRedirectLocationClicked(ID sender) { this.addMetadataItem("x-amz-website-redirect-location", StringUtils.EMPTY, true); } @Action public void metadataAddExpiresClicked(ID sender) { final Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC")); time.add(Calendar.SECOND, preferences.getInteger("s3.cache.seconds")); this.addMetadataItem("Expires", new RFC1123DateFormatter().format(time.getTime(), TimeZone.getTimeZone("UTC"))); } @Action public void metadataAddPragmaClicked(ID sender) { this.addMetadataItem("Pragma", StringUtils.EMPTY, true); } private void addMetadataItem() { this.addMetadataItem(StringUtils.EMPTY); } /** * Add new metadata row and selects the name column * * @param name Header name */ private void addMetadataItem(String name) { this.addMetadataItem(name, StringUtils.EMPTY, false); } /** * Add new metadata row and selects the value column * * @param name Header name * @param value Header value */ private void addMetadataItem(String name, String value) { this.addMetadataItem(name, value, true); } /** * @param name HTTP header name * @param value HTTP header value * @param selectValue Select the value field or the name header field */ private void addMetadataItem(String name, String value, boolean selectValue) { int row = metadata.size(); List<Header> updated = new ArrayList<Header>(metadata); updated.add(row, new Header(name, value)); this.setMetadata(updated); metadataTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(new NSInteger(row)), false); metadataTable.editRow( selectValue ? metadataTable.columnWithIdentifier(MetadataColumns.VALUE.name()) : metadataTable.columnWithIdentifier(MetadataColumns.NAME.name()), new NSInteger(row), true); } @Outlet private NSButton metadataRemoveButton; public void setMetadataRemoveButton(NSButton b) { this.metadataRemoveButton = b; // Only enable upon selection change this.metadataRemoveButton.setEnabled(false); this.metadataRemoveButton.setAction(Foundation.selector("metadataRemoveButtonClicked:")); this.metadataRemoveButton.setTarget(this.id()); } @Action public void metadataRemoveButtonClicked(ID sender) { List<Header> updated = new ArrayList<Header>(metadata); NSIndexSet iterator = metadataTable.selectedRowIndexes(); List<Header> remove = new ArrayList<Header>(); for (NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator .indexGreaterThanIndex(index)) { remove.add(updated.get(index.intValue())); } updated.removeAll(remove); this.setMetadata(updated); this.metadataInputDidEndEditing(); } private void metadataInputDidEndEditing() { if (toggleMetadataSettings(false)) { final Map<String, String> update = new HashMap<String, String>(); for (Header header : metadata) { update.put(header.getName(), header.getValue()); } controller.background(new WorkerBackgroundAction<Boolean>(controller, controller.getSession(), controller.getCache(), new WriteMetadataWorker(files, update, new AlertRecursiveCallback<String>(this), controller) { @Override public void cleanup(final Boolean v) { toggleMetadataSettings(true); } })); } } @Outlet private NSButton ownerr; @Outlet private NSButton ownerw; @Outlet private NSButton ownerx; @Outlet private NSButton groupr; @Outlet private NSButton groupw; @Outlet private NSButton groupx; @Outlet private NSButton otherr; @Outlet private NSButton otherw; @Outlet private NSButton otherx; public void setOwnerr(NSButton ownerr) { this.ownerr = ownerr; } public void setOwnerw(NSButton ownerw) { this.ownerw = ownerw; } public void setOwnerx(NSButton ownerx) { this.ownerx = ownerx; } public void setGroupr(NSButton groupr) { this.groupr = groupr; } public void setGroupw(NSButton groupw) { this.groupw = groupw; } public void setGroupx(NSButton groupx) { this.groupx = groupx; } public void setOtherr(NSButton otherr) { this.otherr = otherr; } public void setOtherw(NSButton otherw) { this.otherw = otherw; } public void setOtherx(NSButton otherx) { this.otherx = otherx; } private NSImageView iconImageView; public void setIconImageView(NSImageView iconImageView) { this.iconImageView = iconImageView; } private static NSPoint cascade = new NSPoint(0, 0); @Override public void setWindow(final NSWindow window) { window.setFrameAutosaveName("Info"); window.setShowsResizeIndicator(true); window.setContentMinSize(window.frame().size); window.setContentMaxSize(new NSSize(600, window.frame().size.height.doubleValue())); super.setWindow(window); if (!preferences.getBoolean("browser.info.inspector")) { cascade = this.cascade(cascade); } } @Override public void windowWillClose(final NSNotification notification) { cascade = new NSPoint(this.window().frame().origin.x.doubleValue(), this.window().frame().origin.y.doubleValue() + this.window().frame().size.height.doubleValue()); this.window().endEditingFor(null); super.windowWillClose(notification); } @Override public boolean isSingleton() { return preferences.getBoolean("browser.info.inspector"); } private BrowserController controller; private final WindowListener browserWindowListener = new WindowListener() { @Override public void windowWillClose() { final NSWindow window = window(); if (null != window) { window.close(); } } }; public InfoController(final BrowserController controller, final List<Path> files) { this.controller = controller; this.controller.addListener(browserWindowListener); this.files = files; this.loadBundle(); } private enum InfoToolbarItem { /** * General */ info, permissions, acl, distribution, s3, metadata } @Override protected void initializePanel(final String identifier) { InfoToolbarItem item; try { item = InfoToolbarItem.valueOf(identifier); } catch (IllegalArgumentException e) { item = InfoToolbarItem.info; } switch (item) { case info: this.initGeneral(); this.initPermissions(); break; case permissions: this.initPermissions(); break; case acl: this.initAcl(); break; case distribution: this.initDistribution(); break; case s3: this.initS3(); break; case metadata: this.initMetadata(); break; } } @Override public NSToolbarItem toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar(final NSToolbar toolbar, final String identifier, final boolean flag) { NSToolbarItem item = super.toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar(toolbar, identifier, flag); final Session session = controller.getSession(); switch (InfoToolbarItem.valueOf(identifier)) { case distribution: if (session.getFeature(DistributionConfiguration.class) != null) { // Give icon and label of the given session item.setImage( IconCacheFactory.<NSImage>get().iconNamed(session.getHost().getProtocol().disk(), 32)); } else { // CloudFront is the default for custom distributions item.setImage(IconCacheFactory.<NSImage>get().iconNamed(new S3Protocol().disk(), 32)); } break; case s3: if (session.getHost().getProtocol().getType() == Protocol.Type.s3) { // Set icon of cloud service provider item.setLabel(session.getHost().getProtocol().getName()); item.setImage( IconCacheFactory.<NSImage>get().iconNamed(session.getHost().getProtocol().disk(), 32)); } else { // Currently these settings are only available for Amazon S3 item.setLabel(new S3Protocol().getName()); item.setImage(IconCacheFactory.<NSImage>get().iconNamed(new S3Protocol().disk(), 32)); } break; case metadata: item.setImage(IconCacheFactory.<NSImage>get().iconNamed("pencil.tiff", 32)); break; case acl: item.setImage(IconCacheFactory.<NSImage>get().iconNamed("permissions.tiff", 32)); break; } return item; } @Override protected boolean validateTabWithIdentifier(final String identifier) { final Session session = controller.getSession(); final boolean anonymous = session.getHost().getCredentials().isAnonymousLogin(); switch (InfoToolbarItem.valueOf(identifier)) { case permissions: if (anonymous) { // Anonymous never has the right to updated permissions return false; } return session.getFeature(UnixPermission.class) != null; case acl: if (anonymous) { // Anonymous never has the right to updated permissions return false; } return session.getFeature(AclPermission.class) != null; case distribution: if (anonymous) { return false; } // Not enabled if not a cloud session return session.getFeature(DistributionConfiguration.class) != null; case s3: if (anonymous) { return false; } return session.getHost().getProtocol().getType() == Protocol.Type.s3 || session.getHost().getProtocol().getType() == Protocol.Type.googlestorage; case metadata: if (anonymous) { return false; } // Not enabled if not a cloud session return session.getFeature(Headers.class) != null; } return true; } @Override public String getTitle(NSTabViewItem item) { return String.format("%s %s", item.label(), this.getName()); } @Outlet private NSView panelMetadata; public void setPanelMetadata(NSView v) { this.panelMetadata = v; } @Outlet private NSView panelCloud; public void setPanelCloud(NSView v) { this.panelCloud = v; } @Outlet private NSView panelDistribution; public void setPanelDistribution(NSView v) { this.panelDistribution = v; } @Outlet private NSView panelPermissions; public void setPanelPermissions(NSView v) { this.panelPermissions = v; } @Outlet private NSView panelAcl; public void setPanelAcl(NSView v) { this.panelAcl = v; } @Outlet private NSView panelGeneral; public void setPanelGeneral(NSView v) { this.panelGeneral = v; } @Override public void invalidate() { controller.removeListener(browserWindowListener); notificationCenter.removeObserver(this.id()); super.invalidate(); } @Override protected String getBundleName() { return "Info"; } public void setFiles(List<Path> files) { if (files.isEmpty()) { return; } this.files = files; this.initializePanel(this.getSelectedTab()); this.setTitle(this.getTitle(tabView.selectedTabViewItem())); } @Override public void awakeFromNib() { this.ownerr.setTarget(this.id()); final Selector s = Foundation.selector("permissionSelectionChanged:"); this.ownerr.setAction(s); this.ownerr.setAllowsMixedState(true); this.ownerw.setTarget(this.id()); this.ownerw.setAction(s); this.ownerw.setAllowsMixedState(true); this.ownerx.setTarget(this.id()); this.ownerx.setAction(s); this.ownerx.setAllowsMixedState(true); this.groupr.setTarget(this.id()); this.groupr.setAction(s); this.groupr.setAllowsMixedState(true); this.groupw.setTarget(this.id()); this.groupw.setAction(s); this.groupw.setAllowsMixedState(true); this.groupx.setTarget(this.id()); this.groupx.setAction(s); this.groupx.setAllowsMixedState(true); this.otherr.setTarget(this.id()); this.otherr.setAction(s); this.otherr.setAllowsMixedState(true); this.otherw.setTarget(this.id()); this.otherw.setAction(s); this.otherw.setAllowsMixedState(true); this.otherx.setTarget(this.id()); this.otherx.setAction(s); this.otherx.setAllowsMixedState(true); super.awakeFromNib(); } @Override protected List<NSView> getPanels() { List<NSView> views = new ArrayList<NSView>(); views.add(panelGeneral); if (controller.getSession().getFeature(UnixPermission.class) != null) { views.add(panelPermissions); } if (controller.getSession().getFeature(AclPermission.class) != null) { views.add(panelAcl); } views.add(panelMetadata); views.add(panelDistribution); views.add(panelCloud); return views; } @Override protected List<String> getPanelIdentifiers() { List<String> identifiers = new ArrayList<String>(); identifiers.add(InfoToolbarItem.info.name()); if (controller.getSession().getFeature(UnixPermission.class) != null) { identifiers.add(InfoToolbarItem.permissions.name()); } if (controller.getSession().getFeature(AclPermission.class) != null) { identifiers.add(InfoToolbarItem.acl.name()); } identifiers.add(InfoToolbarItem.metadata.name()); identifiers.add(InfoToolbarItem.distribution.name()); identifiers.add(InfoToolbarItem.s3.name()); return identifiers; } private String getName() { final int count = this.numberOfFiles(); if (count > 1) { return String.format("(%s)", LocaleFactory.localizedString("Multiple files")); } return this.getSelected().getName(); } @Override protected NSUInteger getToolbarSize() { return NSToolbar.NSToolbarSizeModeSmall; } private void initGeneral() { final int count = this.numberOfFiles(); if (count > 0) { filenameField.setStringValue(this.getName()); final Path file = getSelected(); filenameField .setEnabled(1 == count && controller.getSession().getFeature(Move.class).isSupported(file)); // Where String path; if (file.isSymbolicLink()) { path = file.getSymlinkTarget().getAbsolute(); } else { path = file.getParent().getAbsolute(); } this.updateField(pathField, path, TRUNCATE_MIDDLE_ATTRIBUTES); pathField.setToolTip(path); if (count > 1) { kindField.setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); checksumField .setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); } else { this.updateField(kindField, descriptor.getKind(file), TRUNCATE_MIDDLE_ATTRIBUTES); } // Timestamps if (count > 1) { modifiedField .setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); createdField.setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); } else { if (-1 == file.attributes().getModificationDate()) { this.updateField(modifiedField, LocaleFactory.localizedString("Unknown")); } else { this.updateField(modifiedField, UserDateFormatterFactory.get().getLongFormat(file.attributes().getModificationDate()), TRUNCATE_MIDDLE_ATTRIBUTES); } if (-1 == file.attributes().getCreationDate()) { this.updateField(createdField, LocaleFactory.localizedString("Unknown")); } else { this.updateField(createdField, UserDateFormatterFactory.get().getLongFormat(file.attributes().getCreationDate()), TRUNCATE_MIDDLE_ATTRIBUTES); } } // Owner this.updateField(ownerField, count > 1 ? String.format("(%s)", LocaleFactory.localizedString("Multiple files")) : StringUtils.isBlank(file.attributes().getOwner()) ? LocaleFactory.localizedString("Unknown") : file.attributes().getOwner(), TRUNCATE_MIDDLE_ATTRIBUTES); this.updateField(groupField, count > 1 ? String.format("(%s)", LocaleFactory.localizedString("Multiple files")) : StringUtils.isBlank(file.attributes().getGroup()) ? LocaleFactory.localizedString("Unknown") : file.attributes().getGroup(), TRUNCATE_MIDDLE_ATTRIBUTES); // Icon if (count > 1) { iconImageView.setImage(IconCacheFactory.<NSImage>get().iconNamed("NSMultipleDocuments", 32)); } else { if (this.getSelected().isVolume()) { iconImageView.setImage(IconCacheFactory.<NSImage>get() .volumeIcon(controller.getSession().getHost().getProtocol(), 32)); } else { iconImageView.setImage(IconCacheFactory.<NSImage>get().fileIcon(this.getSelected(), 32)); } } } // Sum of files this.initSize(); this.initChecksum(); // Read HTTP URL this.initWebUrl(); } private void initWebUrl() { // Web URL if (this.numberOfFiles() > 1) { this.updateField(webUrlField, String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); webUrlField.setToolTip(StringUtils.EMPTY); } else { this.updateField(webUrlField, LocaleFactory.localizedString("Unknown")); final Session<?> session = controller.getSession(); final Path file = this.getSelected(); final DescriptiveUrl http = session.getFeature(UrlProvider.class).toUrl(file) .find(DescriptiveUrl.Type.http); if (!http.equals(DescriptiveUrl.EMPTY)) { webUrlField.setAttributedStringValue(HyperlinkAttributedStringFactory.create(http)); webUrlField.setToolTip(LocaleFactory.localizedString("Open in Web Browser")); } } } /** * */ private void initPermissions() { permissionsField.setStringValue(LocaleFactory.localizedString("Unknown")); // Disable Apply button and start progress indicator if (this.togglePermissionSettings(false)) { controller.background(new WorkerBackgroundAction<List<Permission>>(controller, controller.getSession(), controller.getCache(), new ReadPermissionWorker(files) { @Override public void cleanup(final List<Permission> permissions) { setPermissions(permissions); togglePermissionSettings(true); } })); } } private void setPermissions(final List<Permission> permissions) { boolean overwrite = true; for (Permission permission : permissions) { updateCheckbox(ownerr, overwrite, permission.getUser().implies(Permission.Action.read)); updateCheckbox(ownerw, overwrite, permission.getUser().implies(Permission.Action.write)); updateCheckbox(ownerx, overwrite, permission.getUser().implies(Permission.Action.execute)); updateCheckbox(groupr, overwrite, permission.getGroup().implies(Permission.Action.read)); updateCheckbox(groupw, overwrite, permission.getGroup().implies(Permission.Action.write)); updateCheckbox(groupx, overwrite, permission.getGroup().implies(Permission.Action.execute)); updateCheckbox(otherr, overwrite, permission.getOther().implies(Permission.Action.read)); updateCheckbox(otherw, overwrite, permission.getOther().implies(Permission.Action.write)); updateCheckbox(otherx, overwrite, permission.getOther().implies(Permission.Action.execute)); // For more than one file selected, take into account permissions of previous file overwrite = false; } final int count = permissions.size(); if (count > 1) { permissionsField.setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); } else { for (Permission permission : permissions) { permissionsField.setStringValue(permission.toString()); octalField.setStringValue(permission.getMode()); } } } /** * @param checkbox The checkbox to update * @param overwrite Overwrite previous state * @param on Set the checkbox to on state */ private void updateCheckbox(NSButton checkbox, boolean overwrite, boolean on) { // Sets the cell's state to value, which can be NSCell.NSOnState, NSCell.NSOffState, or NSCell.MixedState. // If necessary, this method also redraws the receiver. if ((checkbox.state() == NSCell.NSOffState || overwrite) && !on) { checkbox.setState(NSCell.NSOffState); } else if ((checkbox.state() == NSCell.NSOnState || overwrite) && on) { checkbox.setState(NSCell.NSOnState); } else { checkbox.setState(NSCell.NSMixedState); } checkbox.setEnabled(true); } /** * Read content distribution settings */ private void initDistribution() { distributionStatusField.setStringValue(LocaleFactory.localizedString("Unknown")); distributionCnameField.cell().setPlaceholderString(LocaleFactory.localizedString("None")); distributionOriginField.setStringValue(LocaleFactory.localizedString("Unknown")); distributionUrlField.setStringValue(LocaleFactory.localizedString("Unknown")); distributionInvalidationStatusField.setStringValue(LocaleFactory.localizedString("None")); // Remember last selection final String selected = distributionDeliveryPopup.titleOfSelectedItem(); distributionDeliveryPopup.removeAllItems(); distributionDeliveryPopup.addItemWithTitle(LocaleFactory.localizedString("None")); distributionDefaultRootPopup.removeAllItems(); distributionDefaultRootPopup.addItemWithTitle(LocaleFactory.localizedString("None")); distributionDefaultRootPopup.menu().addItem(NSMenuItem.separatorItem()); final Session<?> session = controller.getSession(); final Path container = containerService.getContainer(getSelected()); final DistributionConfiguration cdn = session.getFeature(DistributionConfiguration.class); distributionEnableButton.setTitle(MessageFormat .format(LocaleFactory.localizedString("Enable {0} Distribution", "Status"), cdn.getName())); distributionDeliveryPopup.removeItemWithTitle(LocaleFactory.localizedString("None")); for (Distribution.Method method : cdn.getMethods(container)) { distributionDeliveryPopup.addItemWithTitle(method.toString()); distributionDeliveryPopup.itemWithTitle(method.toString()).setRepresentedObject(method.toString()); } distributionDeliveryPopup.selectItemWithTitle(selected); if (null == distributionDeliveryPopup.selectedItem()) { // Select first distribution option Distribution.Method method = cdn.getMethods(container).iterator().next(); distributionDeliveryPopup.selectItemWithTitle(method.toString()); } distributionLoggingPopup.removeAllItems(); distributionLoggingPopup.addItemWithTitle(LocaleFactory.localizedString("None")); distributionLoggingPopup.itemWithTitle(LocaleFactory.localizedString("None")).setEnabled(false); distributionAnalyticsSetupUrlField.setStringValue(LocaleFactory.localizedString("None")); this.distributionStatusButtonClicked(null); } /** * Updates the size field by iterating over all files and * reading the cached size value in the attributes of the path */ private void initSize() { if (this.toggleSizeSettings(false)) { controller.background(new WorkerBackgroundAction<Long>(controller, controller.getSession(), controller.getCache(), new ReadSizeWorker(files) { @Override public void cleanup(final Long size) { updateSize(size); toggleSizeSettings(true); } })); } } private void updateSize(long size) { sizeField.setAttributedStringValue(NSAttributedString.attributedStringWithAttributes( SizeFormatterFactory.get().format(size, true), TRUNCATE_MIDDLE_ATTRIBUTES)); } private void initChecksum() { if (this.numberOfFiles() > 1) { checksumField.setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); } else { final Path file = this.getSelected(); final Checksum checksum = file.attributes().getChecksum(); if (checksum == null) { checksumField.setStringValue(LocaleFactory.localizedString("Unknown")); } else { this.updateField(checksumField, checksum.hash, TRUNCATE_MIDDLE_ATTRIBUTES); } } } /** * Toggle settings before and after update * * @param stop Enable controls and stop progress spinner * @return True if progress animation has started and settings are toggled */ private boolean toggleS3Settings(final boolean stop) { this.window().endEditingFor(null); final Session<?> session = controller.getSession(); final Credentials credentials = session.getHost().getCredentials(); boolean enable = session.getHost().getProtocol().getType() == Protocol.Type.s3 || session.getHost().getProtocol().getType() == Protocol.Type.googlestorage; if (enable) { enable = !credentials.isAnonymousLogin(); } boolean logging = false; boolean analytics = false; boolean versioning = false; boolean storageclass = false; boolean encryption = false; boolean lifecycle = false; if (enable) { logging = session.getFeature(Logging.class) != null; analytics = session.getFeature(AnalyticsProvider.class) != null; versioning = session.getFeature(Versioning.class) != null; lifecycle = session.getFeature(Lifecycle.class) != null; encryption = session.getFeature(Encryption.class) != null; storageclass = session.getFeature(Redundancy.class) != null; } storageClassPopup.setEnabled(stop && enable && storageclass); encryptionPopup.setEnabled(stop && enable && encryption); bucketVersioningButton.setEnabled(stop && enable && versioning); bucketMfaButton .setEnabled(stop && enable && versioning && bucketVersioningButton.state() == NSCell.NSOnState); bucketLoggingButton.setEnabled(stop && enable && logging); bucketLoggingPopup.setEnabled(stop && enable && logging); if (analytics && Objects.equals( controller.getSession().getFeature(IdentityConfiguration.class).getCredentials( controller.getSession().getFeature(AnalyticsProvider.class).getName()), credentials)) { // No need to create new IAM credentials when same as session credentials bucketAnalyticsButton.setEnabled(false); } else { bucketAnalyticsButton.setEnabled(stop && enable && analytics); } lifecycleDeletePopup.setEnabled(stop && enable && lifecycle); lifecycleDeleteCheckbox.setEnabled(stop && enable && lifecycle); lifecycleTransitionPopup.setEnabled(stop && enable && lifecycle); lifecycleTransitionCheckbox.setEnabled(stop && enable && lifecycle); if (stop) { s3Progress.stopAnimation(null); } else if (enable) { s3Progress.startAnimation(null); } return enable; } /** * */ private void initS3() { bucketLocationField.setStringValue(LocaleFactory.localizedString("Unknown")); bucketAnalyticsSetupUrlField.setStringValue(LocaleFactory.localizedString("None")); bucketLoggingPopup.removeAllItems(); bucketLoggingPopup.addItemWithTitle(LocaleFactory.localizedString("None")); bucketLoggingPopup.lastItem().setEnabled(false); s3PublicUrlField.setStringValue(LocaleFactory.localizedString("None")); s3PublicUrlValidityField.setStringValue(LocaleFactory.localizedString("Unknown")); s3torrentUrlField.setStringValue(LocaleFactory.localizedString("None")); storageClassPopup.removeAllItems(); storageClassPopup.addItemWithTitle(LocaleFactory.localizedString("Unknown")); storageClassPopup.lastItem().setEnabled(false); storageClassPopup.selectItem(storageClassPopup.lastItem()); encryptionPopup.removeAllItems(); encryptionPopup.addItemWithTitle(LocaleFactory.localizedString("Unknown")); encryptionPopup.lastItem().setEnabled(false); encryptionPopup.selectItem(encryptionPopup.lastItem()); final Session<?> session = controller.getSession(); if (this.toggleS3Settings(false)) { if (session.getFeature(Redundancy.class) != null) { for (String redundancy : session.getFeature(Redundancy.class).getClasses()) { storageClassPopup.addItemWithTitle(LocaleFactory.localizedString(redundancy, "S3")); storageClassPopup.lastItem().setRepresentedObject(redundancy); } } if (this.numberOfFiles() > 1) { s3PublicUrlField .setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); s3PublicUrlField.setToolTip(StringUtils.EMPTY); s3torrentUrlField .setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); s3torrentUrlField.setToolTip(StringUtils.EMPTY); } else { Path file = this.getSelected(); final DescriptiveUrl signed = session.getFeature(UrlProvider.class).toUrl(file) .find(DescriptiveUrl.Type.signed); if (!signed.equals(DescriptiveUrl.EMPTY)) { s3PublicUrlField.setAttributedStringValue(HyperlinkAttributedStringFactory.create(signed)); s3PublicUrlField.setToolTip(signed.getHelp()); s3PublicUrlValidityField.setStringValue(signed.getHelp()); } final DescriptiveUrl torrent = session.getFeature(UrlProvider.class).toUrl(file) .find(DescriptiveUrl.Type.torrent); if (!torrent.equals(DescriptiveUrl.EMPTY)) { s3torrentUrlField.setAttributedStringValue(HyperlinkAttributedStringFactory.create(torrent)); s3torrentUrlField.setToolTip(torrent.getHelp()); } } controller.background(new BrowserControllerBackgroundAction<Void>(controller) { Location.Name location; LoggingConfiguration logging; VersioningConfiguration versioning; Set<String> containers = new HashSet<String>(); // Available encryption keys in KMS Set<Encryption.Algorithm> managedEncryptionKeys = new HashSet<Encryption.Algorithm>(); Set<Encryption.Algorithm> selectedEncryptionKeys = new HashSet<Encryption.Algorithm>(); Set<String> selectedStorageClasses = new HashSet<String>(); LifecycleConfiguration lifecycle; Credentials credentials; @Override public Void run() throws BackgroundException { final Path container = containerService.getContainer(getSelected()); if (session.getFeature(Location.class) != null) { location = session.getFeature(Location.class).getLocation(container); } if (session.getFeature(Logging.class) != null) { logging = session.getFeature(Logging.class).getConfiguration(container); for (Path c : session.list(containerService.getContainer(getSelected()).getParent(), new DisabledListProgressListener())) { containers.add(c.getName()); } } if (session.getFeature(Versioning.class) != null) { versioning = session.getFeature(Versioning.class).getConfiguration(container); } if (session.getFeature(Lifecycle.class) != null) { lifecycle = session.getFeature(Lifecycle.class).getConfiguration(container); } if (session.getFeature(AnalyticsProvider.class) != null && session.getFeature(IdentityConfiguration.class) != null) { credentials = session.getFeature(IdentityConfiguration.class) .getCredentials(session.getFeature(AnalyticsProvider.class).getName()); } if (session.getFeature(Redundancy.class) != null) { for (final Path file : files) { selectedStorageClasses.add(session.getFeature(Redundancy.class).getClass(file)); } } if (session.getFeature(Encryption.class) != null) { // Add additional keys stored in KMS managedEncryptionKeys = session.getFeature(Encryption.class).getKeys(container, prompt); for (final Path file : files) { selectedEncryptionKeys.add(session.getFeature(Encryption.class).getEncryption(file)); } managedEncryptionKeys.addAll(selectedEncryptionKeys); } return null; } @Override public void cleanup() { super.cleanup(); if (logging != null) { bucketLoggingButton.setState(logging.isEnabled() ? NSCell.NSOnState : NSCell.NSOffState); if (!containers.isEmpty()) { bucketLoggingPopup.removeAllItems(); } for (String c : containers) { bucketLoggingPopup.addItemWithTitle(c); bucketLoggingPopup.lastItem().setRepresentedObject(c); } if (logging.isEnabled()) { bucketLoggingPopup.selectItemWithTitle(logging.getLoggingTarget()); } else { // Default to write log files to origin bucket bucketLoggingPopup .selectItemWithTitle(containerService.getContainer(getSelected()).getName()); } } if (location != null) { bucketLocationField.setStringValue(location.toString()); } if (versioning != null) { bucketVersioningButton .setState(versioning.isEnabled() ? NSCell.NSOnState : NSCell.NSOffState); bucketMfaButton.setState(versioning.isMultifactor() ? NSCell.NSOnState : NSCell.NSOffState); } for (Encryption.Algorithm algorithm : managedEncryptionKeys) { encryptionPopup .addItemWithTitle(LocaleFactory.localizedString(algorithm.getDescription(), "S3")); encryptionPopup.lastItem().setRepresentedObject(algorithm.toString()); } if (!selectedEncryptionKeys.isEmpty()) { encryptionPopup.selectItemAtIndex(new NSInteger(-1)); if (-1 != encryptionPopup.indexOfItemWithTitle(LocaleFactory.localizedString("Unknown")) .intValue()) { encryptionPopup.removeItemWithTitle(LocaleFactory.localizedString("Unknown")); } } for (Encryption.Algorithm algorithm : selectedEncryptionKeys) { encryptionPopup.selectItemAtIndex( encryptionPopup.indexOfItemWithRepresentedObject(algorithm.toString())); } for (Encryption.Algorithm algorithm : selectedEncryptionKeys) { encryptionPopup .itemAtIndex(encryptionPopup.indexOfItemWithRepresentedObject(algorithm.toString())) .setState(selectedEncryptionKeys.size() == 1 ? NSCell.NSOnState : NSCell.NSMixedState); } if (!selectedStorageClasses.isEmpty()) { storageClassPopup.selectItemAtIndex(new NSInteger(-1)); if (-1 != storageClassPopup.indexOfItemWithTitle(LocaleFactory.localizedString("Unknown")) .intValue()) { storageClassPopup.removeItemWithTitle(LocaleFactory.localizedString("Unknown")); } } for (String storageClass : selectedStorageClasses) { storageClassPopup.selectItemAtIndex( storageClassPopup.indexOfItemWithRepresentedObject(storageClass)); } for (String storageClass : selectedStorageClasses) { storageClassPopup .itemAtIndex(storageClassPopup.indexOfItemWithRepresentedObject(storageClass)) .setState(selectedStorageClasses.size() == 1 ? NSCell.NSOnState : NSCell.NSMixedState); } if (null != credentials) { bucketAnalyticsSetupUrlField.setAttributedStringValue(HyperlinkAttributedStringFactory .create(session.getFeature(AnalyticsProvider.class).getSetup( session.getHost().getProtocol().getDefaultHostname(), session.getHost().getProtocol().getScheme(), containerService.getContainer(getSelected()).getName(), credentials))); } bucketAnalyticsButton.setState(null != credentials ? NSCell.NSOnState : NSCell.NSOffState); if (lifecycle != null) { lifecycleDeleteCheckbox .setState(lifecycle.getExpiration() != null ? NSCell.NSOnState : NSCell.NSOffState); if (lifecycle.getExpiration() != null) { final NSInteger index = lifecycleDeletePopup .indexOfItemWithRepresentedObject(String.valueOf(lifecycle.getExpiration())); if (-1 == index.intValue()) { lifecycleDeletePopup.addItemWithTitle( MessageFormat.format(LocaleFactory.localizedString("after {0} Days", "S3"), String.valueOf(lifecycle.getExpiration()))); lifecycleDeletePopup.lastItem() .setAction(Foundation.selector("lifecyclePopupClicked:")); lifecycleDeletePopup.lastItem().setTarget(id()); lifecycleDeletePopup.lastItem() .setRepresentedObject(String.valueOf(lifecycle.getExpiration())); } lifecycleDeletePopup.selectItemAtIndex(lifecycleDeletePopup .indexOfItemWithRepresentedObject(String.valueOf(lifecycle.getExpiration()))); } lifecycleTransitionCheckbox .setState(lifecycle.getTransition() != null ? NSCell.NSOnState : NSCell.NSOffState); if (lifecycle.getTransition() != null) { final NSInteger index = lifecycleTransitionPopup .indexOfItemWithRepresentedObject(String.valueOf(lifecycle.getTransition())); if (-1 == index.intValue()) { lifecycleTransitionPopup.addItemWithTitle( MessageFormat.format(LocaleFactory.localizedString("after {0} Days", "S3"), String.valueOf(lifecycle.getTransition()))); lifecycleTransitionPopup.lastItem() .setAction(Foundation.selector("lifecyclePopupClicked:")); lifecycleTransitionPopup.lastItem().setTarget(id()); lifecycleTransitionPopup.lastItem() .setRepresentedObject(String.valueOf(lifecycle.getTransition())); } lifecycleTransitionPopup.selectItemAtIndex(lifecycleTransitionPopup .indexOfItemWithRepresentedObject(String.valueOf(lifecycle.getTransition()))); } } toggleS3Settings(true); } @Override public String getActivity() { return MessageFormat.format(LocaleFactory.localizedString("Reading metadata of {0}", "Status"), this.toString(files)); } }); } } /** * Toggle settings before and after update * * @param stop Enable controls and stop progress spinner * @return True if progress animation has started and settings are toggled */ private boolean toggleAclSettings(final boolean stop) { this.window().endEditingFor(null); final Session session = controller.getSession(); final Credentials credentials = session.getHost().getCredentials(); boolean enable = !credentials.isAnonymousLogin() && session.getFeature(AclPermission.class) != null; aclTable.setEnabled(stop && enable); aclAddButton.setEnabled(stop && enable); boolean selection = aclTable.selectedRowIndexes().count().intValue() > 0; aclRemoveButton.setEnabled(stop && enable && selection); if (stop) { aclProgress.stopAnimation(null); } else if (enable) { aclProgress.startAnimation(null); } return enable; } /** * Toggle settings before and after update * * @param stop Enable controls and stop progress spinner * @return True if progress animation has started and settings are toggled */ private boolean toggleMetadataSettings(final boolean stop) { this.window().endEditingFor(null); final Session session = controller.getSession(); final Credentials credentials = session.getHost().getCredentials(); boolean enable = !credentials.isAnonymousLogin() && session.getFeature(Headers.class) != null; metadataTable.setEnabled(stop && enable); metadataAddButton.setEnabled(stop && enable); boolean selection = metadataTable.selectedRowIndexes().count().intValue() > 0; metadataRemoveButton.setEnabled(stop && enable && selection); if (stop) { metadataProgress.stopAnimation(null); } else if (enable) { metadataProgress.startAnimation(null); } return enable; } /** * Read custom metadata HTTP headers from cloud provider */ private void initMetadata() { this.setMetadata(Collections.<Header>emptyList()); if (this.toggleMetadataSettings(false)) { controller.background(new WorkerBackgroundAction<Map<String, String>>(controller, controller.getSession(), controller.getCache(), new ReadMetadataWorker(files) { @Override public void cleanup(final Map<String, String> updated) { final List<Header> m = new ArrayList<Header>(); if (updated != null) { for (Map.Entry<String, String> key : updated.entrySet()) { m.add(new Header(key.getKey(), key.getValue())); } } setMetadata(m); toggleMetadataSettings(true); } })); } } /** * Read grants in the background */ private void initAcl() { this.setAcl(Collections.<Acl.UserAndRole>emptyList()); aclUrlField.setStringValue(LocaleFactory.localizedString("None")); final Session<?> session = controller.getSession(); if (this.toggleAclSettings(false)) { final AclPermission feature = session.getFeature(AclPermission.class); aclAddButton.removeAllItems(); this.aclAddButton.addItemWithTitle(StringUtils.EMPTY); this.aclAddButton.lastItem().setImage(IconCacheFactory.<NSImage>get().iconNamed("gear.tiff")); for (Acl.User user : feature.getAvailableAclUsers()) { this.aclAddButton.addItemWithTitle(user.getPlaceholder()); this.aclAddButton.lastItem().setAction(Foundation.selector("aclAddButtonClicked:")); this.aclAddButton.lastItem().setTarget(this.id()); this.aclAddButton.lastItem().setRepresentedObject(user.getPlaceholder()); } aclPermissionCellPrototype.removeAllItems(); for (Acl.Role permission : feature.getAvailableAclRoles(files)) { aclPermissionCellPrototype.addItemWithObjectValue(NSString.stringWithString(permission.getName())); } if (this.numberOfFiles() > 1) { aclUrlField.setStringValue(String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); aclUrlField.setToolTip(StringUtils.EMPTY); } else { for (Path file : files) { if (file.isFile()) { final DescriptiveUrl authenticated = session.getFeature(UrlProvider.class).toUrl(file) .find(DescriptiveUrl.Type.authenticated); if (!authenticated.equals(DescriptiveUrl.EMPTY)) { aclUrlField.setAttributedStringValue( HyperlinkAttributedStringFactory.create(authenticated)); aclUrlField.setToolTip(authenticated.getHelp()); } } } } controller.background(new WorkerBackgroundAction<List<Acl.UserAndRole>>(controller, controller.getSession(), controller.getCache(), new ReadAclWorker(files) { @Override public void cleanup(final List<Acl.UserAndRole> updated) { if (updated != null) { setAcl(updated); } toggleAclSettings(true); } })); } } /** * Selected files in browser. * * @return The number of selected files to display information for. */ private int numberOfFiles() { return null == files ? 0 : files.size(); } @Action public void filenameInputDidEndEditing(NSNotification sender) { if (this.numberOfFiles() == 1) { final Path current = getSelected(); if (!filenameField.stringValue().equals(current.getName())) { if (StringUtils.contains(filenameField.stringValue(), Path.DELIMITER)) { AppKitFunctionsLibrary.beep(); return; } if (StringUtils.isBlank(filenameField.stringValue())) { filenameField.setStringValue(current.getName()); } else { final Path renamed = new Path(current.getParent(), filenameField.stringValue(), current.getType()); new MoveController(controller).rename(current, renamed); this.initWebUrl(); } } } } @Action public void octalPermissionsInputDidEndEditing(NSNotification sender) { final Permission permission = this.getPermissionFromOctalField(); if (null == permission) { AppKitFunctionsLibrary.beep(); this.initPermissions(); } else { boolean change = false; for (Path file : files) { if (!file.attributes().getPermission().equals(permission)) { change = true; } } if (change) { this.setPermissions(Collections.singletonList(permission)); this.changePermissions(permission, false); } } } /** * Permission value from input field. * * @return Null if invalid string has been entered entered, */ private Permission getPermissionFromOctalField() { if (StringUtils.isNotBlank(octalField.stringValue())) { if (StringUtils.length(octalField.stringValue()) >= 3) { if (StringUtils.isNumeric(octalField.stringValue())) { return new Permission(Integer.valueOf(octalField.stringValue()).intValue()); } } } log.warn(String.format("Invalid octal field input %s", octalField.stringValue())); return null; } @Action public void recursiveButtonClicked(final NSButton sender) { final Permission permission = this.getPermissionFromOctalField(); if (null == permission) { AppKitFunctionsLibrary.beep(); this.initPermissions(); } else { this.changePermissions(permission, true); } } @Action public void permissionSelectionChanged(final NSButton sender) { if (sender.state() == NSCell.NSMixedState) { sender.setState(NSCell.NSOnState); } final Permission p = this.getPermissionFromCheckboxes(); this.setPermissions(Collections.singletonList(p)); this.changePermissions(p, false); } /** * Permission selection from checkboxes. * * @return Never null. */ private Permission getPermissionFromCheckboxes() { Permission.Action u = Permission.Action.none; if (ownerr.state() == NSCell.NSOnState) { u = u.or(Permission.Action.read); } if (ownerw.state() == NSCell.NSOnState) { u = u.or(Permission.Action.write); } if (ownerx.state() == NSCell.NSOnState) { u = u.or(Permission.Action.execute); } Permission.Action g = Permission.Action.none; if (groupr.state() == NSCell.NSOnState) { g = g.or(Permission.Action.read); } if (groupw.state() == NSCell.NSOnState) { g = g.or(Permission.Action.write); } if (groupx.state() == NSCell.NSOnState) { g = g.or(Permission.Action.execute); } Permission.Action o = Permission.Action.none; if (otherr.state() == NSCell.NSOnState) { o = o.or(Permission.Action.read); } if (otherw.state() == NSCell.NSOnState) { o = o.or(Permission.Action.write); } if (otherx.state() == NSCell.NSOnState) { o = o.or(Permission.Action.execute); } return new Permission(u, g, o); } /** * @param permission UNIX permissions to apply to files * @param recursive Recursively apply to child of directories */ private void changePermissions(final Permission permission, final boolean recursive) { if (this.togglePermissionSettings(false)) { controller.background( new WorkerBackgroundAction<Boolean>(controller, controller.getSession(), controller.getCache(), new WritePermissionWorker(files, permission, recursive ? new AlertRecursiveCallback<Permission>(this) : new BooleanRecursiveCallback<Permission>(false), controller) { @Override public void cleanup(final Boolean v) { togglePermissionSettings(true); } })); } } /** * Toggle settings before and after update * * @param stop Enable controls and stop progress spinner * @return True if controls are enabled for the given protocol in idle state */ private boolean togglePermissionSettings(final boolean stop) { this.window().endEditingFor(null); final Session session = controller.getSession(); final Credentials credentials = session.getHost().getCredentials(); boolean enable = !credentials.isAnonymousLogin() && session.getFeature(UnixPermission.class) != null; recursiveButton.setEnabled(stop && enable); for (Path next : files) { if (next.isFile()) { recursiveButton.setEnabled(false); break; } } octalField.setEnabled(stop && enable); ownerr.setEnabled(stop && enable); ownerw.setEnabled(stop && enable); ownerx.setEnabled(stop && enable); groupr.setEnabled(stop && enable); groupw.setEnabled(stop && enable); groupx.setEnabled(stop && enable); otherr.setEnabled(stop && enable); otherw.setEnabled(stop && enable); otherx.setEnabled(stop && enable); if (stop) { permissionProgress.stopAnimation(null); } else if (enable) { permissionProgress.startAnimation(null); } return enable; } /** * Toggle settings before and after update * * @param stop Enable controls and stop progress spinner * @return True if controls are enabled for the given protocol in idle state */ private boolean toggleDistributionSettings(final boolean stop) { this.window().endEditingFor(null); final Session<?> session = controller.getSession(); final Credentials credentials = session.getHost().getCredentials(); final DistributionConfiguration cdn = session.getFeature(DistributionConfiguration.class); boolean enable = !credentials.isAnonymousLogin() && cdn != null; final Path container = containerService.getContainer(getSelected()); if (enable) { // Not enabled if multiple files selected with not same parent container for (Path next : files) { if (containerService.getContainer(next).equals(container)) { continue; } enable = false; break; } } Distribution.Method method = Distribution.Method .forName(distributionDeliveryPopup.selectedItem().representedObject()); distributionEnableButton.setEnabled(stop && enable); distributionDeliveryPopup.setEnabled(stop && enable); distributionLoggingButton .setEnabled(stop && enable && cdn.getFeature(DistributionLogging.class, method) != null); if (enable) { final AnalyticsProvider analyticsFeature = cdn.getFeature(AnalyticsProvider.class, method); final IdentityConfiguration identityFeature = cdn.getFeature(IdentityConfiguration.class, method); if (null == analyticsFeature || null == identityFeature) { distributionAnalyticsButton.setEnabled(false); } else { if (Objects.equals(identityFeature.getCredentials(analyticsFeature.getName()), credentials)) { // No need to create new IAM credentials when same as session credentials distributionAnalyticsButton.setEnabled(false); } else { distributionAnalyticsButton.setEnabled(stop); } } } else { distributionAnalyticsButton.setEnabled(false); } distributionLoggingPopup .setEnabled(stop && enable && cdn.getFeature(DistributionLogging.class, method) != null); distributionCnameField.setEnabled(stop && enable && cdn.getFeature(Cname.class, method) != null); distributionInvalidateObjectsButton .setEnabled(stop && enable && cdn.getFeature(Purge.class, method) != null); distributionDefaultRootPopup.setEnabled(stop && enable && cdn.getFeature(Index.class, method) != null); if (stop) { distributionProgress.stopAnimation(null); } else if (enable) { distributionProgress.startAnimation(null); } return enable; } @Action public void distributionInvalidateObjectsButtonClicked(final ID sender) { if (this.toggleDistributionSettings(false)) { controller.background(new BrowserControllerBackgroundAction<Void>(controller) { @Override public Void run() throws BackgroundException { final Session<?> session = controller.getSession(); Distribution.Method method = Distribution.Method .forName(distributionDeliveryPopup.selectedItem().representedObject()); final Path container = containerService.getContainer(getSelected()); final DistributionConfiguration cdn = session.getFeature(DistributionConfiguration.class); final Purge feature = cdn.getFeature(Purge.class, method); feature.invalidate(container, method, files, prompt); return null; } @Override public void cleanup() { super.cleanup(); // Refresh the current distribution status distributionStatusButtonClicked(sender); } @Override public String getActivity() { return MessageFormat.format( LocaleFactory.localizedString("Writing CDN configuration of {0}", "Status"), containerService.getContainer(getSelected()).getName()); } }); } } @Action public void distributionLoggingPopupClicked(final ID sender) { if (distributionLoggingButton.state() == NSCell.NSOnState) { // Only write change if logging is already enabled this.distributionApplyButtonClicked(sender); } } @Action public void distributionApplyButtonClicked(final ID sender) { if (this.toggleDistributionSettings(false)) { controller.background(new BrowserControllerBackgroundAction<Void>(controller) { @Override public Void run() throws BackgroundException { final Session<?> session = controller.getSession(); Distribution.Method method = Distribution.Method .forName(distributionDeliveryPopup.selectedItem().representedObject()); final Path container = containerService.getContainer(getSelected()); final DistributionConfiguration cdn = session.getFeature(DistributionConfiguration.class); final Distribution configuration = new Distribution(method, distributionEnableButton.state() == NSCell.NSOnState); configuration.setIndexDocument(distributionDefaultRootPopup.selectedItem().representedObject()); configuration.setLogging(distributionLoggingButton.state() == NSCell.NSOnState); configuration.setLoggingContainer(distributionLoggingPopup.selectedItem().representedObject()); configuration.setCNAMEs(StringUtils.split(distributionCnameField.stringValue())); cdn.write(container, configuration, prompt); return null; } @Override public void cleanup() { super.cleanup(); // Refresh the current distribution status distributionStatusButtonClicked(sender); } @Override public String getActivity() { return MessageFormat.format( LocaleFactory.localizedString("Writing CDN configuration of {0}", "Status"), getSelected().getName()); } }); } } @Action public void distributionStatusButtonClicked(final ID sender) { if (this.toggleDistributionSettings(false)) { final Path container = containerService.getContainer(getSelected()); final Distribution.Method method = Distribution.Method .forName(distributionDeliveryPopup.selectedItem().representedObject()); final List<Path> rootDocuments = new ArrayList<Path>(); final Session<?> session = controller.getSession(); controller.background(new BrowserControllerBackgroundAction<Distribution>(controller) { private Distribution distribution = new Distribution(method, false); @Override public Distribution run() throws BackgroundException { final DistributionConfiguration cdn = session.getFeature(DistributionConfiguration.class); distribution = cdn.read(container, method, prompt); if (cdn.getFeature(Index.class, distribution.getMethod()) != null) { // Make sure container items are cached for default root object. rootDocuments.addAll(session.list(container, new DisabledListProgressListener())); } return distribution; } @Override public void cleanup() { super.cleanup(); final Path file = getSelected(); final Path container = containerService.getContainer(file); final DistributionConfiguration cdn = session.getFeature(DistributionConfiguration.class); distributionEnableButton.setTitle( MessageFormat.format(LocaleFactory.localizedString("Enable {0} Distribution", "Status"), cdn.getName(distribution.getMethod()))); distributionEnableButton .setState(distribution.isEnabled() ? NSCell.NSOnState : NSCell.NSOffState); distributionStatusField.setAttributedStringValue( NSMutableAttributedString.create(distribution.getStatus(), TRUNCATE_MIDDLE_ATTRIBUTES)); distributionLoggingButton .setState(distribution.isLogging() ? NSCell.NSOnState : NSCell.NSOffState); final List<Path> containers = distribution.getContainers(); if (!containers.isEmpty()) { distributionLoggingPopup.removeAllItems(); } for (Path c : containers) { // Populate with list of available logging targets distributionLoggingPopup.addItemWithTitle(c.getName()); distributionLoggingPopup.lastItem().setRepresentedObject(c.getName()); } if (StringUtils.isNotBlank(distribution.getLoggingContainer())) { // Select configured logging container if any distributionLoggingPopup.selectItemWithTitle(distribution.getLoggingContainer()); } else { if (distributionLoggingPopup.itemWithTitle(container.getName()) != null) { distributionLoggingPopup.selectItemWithTitle(container.getName()); } } if (null == distributionLoggingPopup.selectedItem()) { distributionLoggingPopup.selectItemWithTitle(LocaleFactory.localizedString("None")); } final AnalyticsProvider analyticsFeature = cdn.getFeature(AnalyticsProvider.class, method); final IdentityConfiguration identityFeature = cdn.getFeature(IdentityConfiguration.class, method); if (analyticsFeature != null && identityFeature != null) { final Credentials credentials = identityFeature.getCredentials(analyticsFeature.getName()); distributionAnalyticsButton .setState(credentials != null ? NSCell.NSOnState : NSCell.NSOffState); if (credentials != null) { distributionAnalyticsSetupUrlField.setAttributedStringValue( HyperlinkAttributedStringFactory.create(analyticsFeature.getSetup( cdn.getHostname(), distribution.getMethod().getScheme(), container.getName(), credentials))); } } final DescriptiveUrl origin = cdn.toUrl(file).find(DescriptiveUrl.Type.origin); if (!origin.equals(DescriptiveUrl.EMPTY)) { distributionOriginField .setAttributedStringValue(HyperlinkAttributedStringFactory.create(origin)); } // Concatenate URLs if (numberOfFiles() > 1) { distributionUrlField.setStringValue( String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); distributionUrlField.setToolTip(StringUtils.EMPTY); distributionCnameUrlField.setStringValue( String.format("(%s)", LocaleFactory.localizedString("Multiple files"))); } else { final DescriptiveUrl url = cdn.toUrl(file).find(DescriptiveUrl.Type.cdn); if (!url.equals(DescriptiveUrl.EMPTY)) { distributionUrlField .setAttributedStringValue(HyperlinkAttributedStringFactory.create(url)); distributionUrlField.setToolTip(LocaleFactory.localizedString("CDN URL")); } else { distributionUrlField.setStringValue(LocaleFactory.localizedString("None")); distributionUrlField.setToolTip(StringUtils.EMPTY); } } final String[] cnames = distribution.getCNAMEs(); if (0 == cnames.length) { distributionCnameField.setStringValue(StringUtils.EMPTY); distributionCnameUrlField.setStringValue(StringUtils.EMPTY); distributionCnameUrlField.setToolTip(StringUtils.EMPTY); } else { distributionCnameField.setStringValue(StringUtils.join(cnames, ' ')); final DescriptiveUrl url = cdn.toUrl(file).find(DescriptiveUrl.Type.cname); if (!url.equals(DescriptiveUrl.EMPTY)) { // We only support one CNAME URL to be displayed distributionCnameUrlField .setAttributedStringValue(HyperlinkAttributedStringFactory.create(url)); distributionCnameUrlField.setToolTip(LocaleFactory.localizedString("CDN URL")); } } if (cdn.getFeature(Index.class, distribution.getMethod()) != null) { for (Path next : rootDocuments) { if (next.isFile()) { distributionDefaultRootPopup.addItemWithTitle(next.getName()); distributionDefaultRootPopup.lastItem().setRepresentedObject(next.getName()); } } } if (StringUtils.isNotBlank(distribution.getIndexDocument())) { if (null == distributionDefaultRootPopup.itemWithTitle(distribution.getIndexDocument())) { distributionDefaultRootPopup.addItemWithTitle(distribution.getIndexDocument()); } distributionDefaultRootPopup.selectItemWithTitle(distribution.getIndexDocument()); } else { distributionDefaultRootPopup.selectItemWithTitle(LocaleFactory.localizedString("None")); } StringBuilder tooltip = new StringBuilder(); for (Iterator<Path> iter = files.iterator(); iter.hasNext();) { Path f = iter.next(); tooltip.append(f.getAbsolute()); if (iter.hasNext()) { tooltip.append("\n"); } } distributionInvalidateObjectsButton.setToolTip(tooltip.toString()); distributionInvalidationStatusField.setStringValue(distribution.getInvalidationStatus()); toggleDistributionSettings(true); } @Override public String getActivity() { return MessageFormat.format( LocaleFactory.localizedString("Reading CDN configuration of {0}", "Status"), getSelected().getName()); } }); } } @Outlet private NSButton distributionAnalyticsButton; public void setDistributionAnalyticsButton(NSButton b) { this.distributionAnalyticsButton = b; this.distributionAnalyticsButton.setAction(Foundation.selector("distributionAnalyticsButtonClicked:")); } @Action public void distributionAnalyticsButtonClicked(final NSButton sender) { if (this.toggleDistributionSettings(false)) { controller.background(new BrowserControllerBackgroundAction<Void>(controller) { @Override public Void run() throws BackgroundException { final Session<?> session = controller.getSession(); if (distributionAnalyticsButton.state() == NSCell.NSOnState) { final String document = preferences.getProperty("analytics.provider.qloudstat.iam.policy"); session.getFeature(IdentityConfiguration.class) .create(session.getFeature(AnalyticsProvider.class).getName(), document, prompt); } else { session.getFeature(IdentityConfiguration.class) .delete(session.getFeature(AnalyticsProvider.class).getName(), prompt); } return null; } @Override public void cleanup() { super.cleanup(); toggleDistributionSettings(true); initDistribution(); } }); } } @Outlet private NSTextField distributionAnalyticsSetupUrlField; public void setDistributionAnalyticsSetupUrlField(NSTextField f) { this.distributionAnalyticsSetupUrlField = f; this.distributionAnalyticsSetupUrlField.setAllowsEditingTextAttributes(true); this.distributionAnalyticsSetupUrlField.setSelectable(true); } @Action public void calculateSizeButtonClicked(final ID sender) { if (this.toggleSizeSettings(false)) { controller.background(new WorkerBackgroundAction<Long>(controller, controller.getSession(), controller.getCache(), new CalculateSizeWorker(files, controller) { @Override public void cleanup(final Long size) { updateSize(size); toggleSizeSettings(true); } @Override protected void update(final long size) { invoke(new WindowMainAction(InfoController.this) { @Override public void run() { updateSize(size); } }); } })); } } /** * @param stop Enable controls and stop progress spinner * @return True if progress animation has started and settings are toggled */ private boolean toggleSizeSettings(final boolean stop) { this.window().endEditingFor(null); sizeButton.setEnabled(false); for (Path next : files) { if (next.isDirectory()) { sizeButton.setEnabled(stop); break; } } if (stop) { sizeProgress.stopAnimation(null); } else { sizeProgress.startAnimation(null); } return true; } @Override @Action public void helpButtonClicked(final NSButton sender) { final StringBuilder site = new StringBuilder(preferences.getProperty("website.help")); switch (InfoToolbarItem.valueOf(this.getSelectedTab())) { case info: site.append("/howto/info"); BrowserLauncherFactory.get().open(site.toString()); break; case permissions: site.append("/howto/permissions"); BrowserLauncherFactory.get().open(site.toString()); break; case acl: site.append("/howto/acl"); BrowserLauncherFactory.get().open(site.toString()); break; case distribution: site.append("/howto/cdn"); BrowserLauncherFactory.get().open(site.toString()); break; default: new DefaultProviderHelpService().help(controller.getSession().getHost().getProtocol()); } } }