org.jets3t.apps.cockpit.Cockpit Maven / Gradle / Ivy
/*
* jets3t : Java Extra-Tasty S3 Toolkit (for Amazon S3 online storage service)
* This is a java.net project, see https://jets3t.dev.java.net/
*
* Copyright 2008 James Murty
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jets3t.apps.cockpit;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScheme;
import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
import org.apache.commons.httpclient.auth.CredentialsProvider;
import org.apache.commons.httpclient.auth.NTLMScheme;
import org.apache.commons.httpclient.auth.RFC2617Scheme;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jets3t.apps.cockpit.gui.AccessControlDialog;
import org.jets3t.apps.cockpit.gui.BucketLoggingDialog;
import org.jets3t.apps.cockpit.gui.BucketTableModel;
import org.jets3t.apps.cockpit.gui.CreateBucketDialog;
import org.jets3t.apps.cockpit.gui.ObjectTableModel;
import org.jets3t.apps.cockpit.gui.PreferencesDialog;
import org.jets3t.apps.cockpit.gui.RequesterPaysDialog;
import org.jets3t.apps.cockpit.gui.SignedGetUrlDialog;
import org.jets3t.apps.cockpit.gui.StartupDialog;
import org.jets3t.gui.AuthenticationDialog;
import org.jets3t.gui.CopyObjectsDialog;
import org.jets3t.gui.ErrorDialog;
import org.jets3t.gui.GuiUtils;
import org.jets3t.gui.HyperlinkActivatedListener;
import org.jets3t.gui.ItemPropertiesDialog;
import org.jets3t.gui.JHtmlLabel;
import org.jets3t.gui.ManageDistributionsDialog;
import org.jets3t.gui.ObjectsAttributesDialog;
import org.jets3t.gui.ProgressDialog;
import org.jets3t.gui.TableSorter;
import org.jets3t.gui.skins.SkinsFactory;
import org.jets3t.service.CloudFrontService;
import org.jets3t.service.CloudFrontServiceException;
import org.jets3t.service.Constants;
import org.jets3t.service.Jets3tProperties;
import org.jets3t.service.S3ObjectsChunk;
import org.jets3t.service.S3Service;
import org.jets3t.service.S3ServiceException;
import org.jets3t.service.acl.AccessControlList;
import org.jets3t.service.impl.rest.httpclient.RestS3Service;
import org.jets3t.service.io.BytesProgressWatcher;
import org.jets3t.service.model.S3Bucket;
import org.jets3t.service.model.S3Object;
import org.jets3t.service.model.cloudfront.Distribution;
import org.jets3t.service.multithread.CancelEventTrigger;
import org.jets3t.service.multithread.CopyObjectsEvent;
import org.jets3t.service.multithread.CreateBucketsEvent;
import org.jets3t.service.multithread.CreateObjectsEvent;
import org.jets3t.service.multithread.DeleteObjectsEvent;
import org.jets3t.service.multithread.DownloadObjectsEvent;
import org.jets3t.service.multithread.DownloadPackage;
import org.jets3t.service.multithread.GetObjectHeadsEvent;
import org.jets3t.service.multithread.GetObjectsEvent;
import org.jets3t.service.multithread.ListObjectsEvent;
import org.jets3t.service.multithread.LookupACLEvent;
import org.jets3t.service.multithread.S3ServiceEventListener;
import org.jets3t.service.multithread.S3ServiceMulti;
import org.jets3t.service.multithread.ServiceEvent;
import org.jets3t.service.multithread.ThreadWatcher;
import org.jets3t.service.multithread.UpdateACLEvent;
import org.jets3t.service.security.AWSCredentials;
import org.jets3t.service.security.EncryptionUtil;
import org.jets3t.service.utils.ByteFormatter;
import org.jets3t.service.utils.FileComparer;
import org.jets3t.service.utils.FileComparerResults;
import org.jets3t.service.utils.Mimetypes;
import org.jets3t.service.utils.ObjectUtils;
import org.jets3t.service.utils.TimeFormatter;
import com.centerkey.utils.BareBonesBrowserLaunch;
/**
* Cockpit is a graphical Java application for viewing and managing the contents of an Amazon S3 account.
* For more information and help please see the
* Cockpit Guide.
*
* This is the Cockpit application class; it may be run as a stand-alone application or as an Applet.
*
* @author jmurty
*/
public class Cockpit extends JApplet implements S3ServiceEventListener, ActionListener,
ListSelectionListener, HyperlinkActivatedListener, CredentialsProvider
{
private static final long serialVersionUID = -3982368878320163058L;
private static final Log log = LogFactory.getLog(Cockpit.class);
public static final String JETS3T_COCKPIT_HELP_PAGE = "http://jets3t.s3.amazonaws.com/applications/cockpit.html";
public static final String AMAZON_S3_PAGE = "http://www.amazon.com/s3";
public static final String OS_NAME_MAC = "Mac OS X";
public static final String APPLICATION_DESCRIPTION = "Cockpit/0.7.1";
public static final String APPLICATION_TITLE = "JetS3t Cockpit";
private static final int BUCKET_LIST_CHUNKING_SIZE = 1000;
private File cockpitHomeDirectory = Constants.DEFAULT_PREFERENCES_DIRECTORY;
private CockpitPreferences cockpitPreferences = null;
private final Insets insetsZero = new Insets(0, 0, 0, 0);
private final Insets insetsDefault = new Insets(5, 7, 5, 7);
private final ByteFormatter byteFormatter = new ByteFormatter();
private final TimeFormatter timeFormatter = new TimeFormatter();
private final SimpleDateFormat yearAndTimeSDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private final SimpleDateFormat timeSDF = new SimpleDateFormat("HH:mm:ss");
private final GuiUtils guiUtils = new GuiUtils();
/**
* Multi-threaded S3 service used by the application.
*/
private S3ServiceMulti s3ServiceMulti = null;
private CloudFrontService cloudFrontService = null;
private boolean cloudFrontMembershipChecked = false;
private JFrame ownerFrame = null;
private boolean isStandAloneApplication = false;
// Service main menu items
private JMenuItem loginMenuItem = null;
private JMenuItem logoutMenuItem = null;
private JMenu loginSwitchMenu = null;
// Bucket main menu items
private JPopupMenu bucketActionMenu = null;
private JMenuItem viewBucketPropertiesMenuItem = null;
private JMenuItem refreshBucketMenuItem = null;
private JMenuItem createBucketMenuItem = null;
private JMenuItem manageDistributionsMenuItem = null;
private JMenuItem updateBucketACLMenuItem = null;
private JMenuItem updateBucketRequesterPaysStatusMenuItem = null;
private JMenuItem deleteBucketMenuItem = null;
// Object main menu items
private JPopupMenu objectActionMenu = null;
private JMenuItem refreshObjectMenuItem = null;
private JMenuItem viewOrModifyObjectAttributesMenuItem = null;
private JMenuItem copyObjectsMenuItem = null;
private JMenuItem updateObjectACLMenuItem = null;
private JMenuItem downloadObjectMenuItem = null;
private JMenuItem uploadFilesMenuItem = null;
private JMenuItem generatePublicGetUrls = null;
private JMenuItem generateTorrentUrl = null;
private JMenuItem deleteObjectMenuItem = null;
// Tools menu items.
private JMenuItem bucketLoggingMenuItem = null;
// Preference menu items.
private JMenuItem preferencesDialogMenuItem = null;
// Help menu items.
private JMenuItem cockpitHelpMenuItem = null;
private JMenuItem amazonS3HelpMenuItem = null;
// Tables
private JTable bucketsTable = null;
private JTable objectsTable = null;
private JScrollPane objectsTableSP = null;
private BucketTableModel bucketTableModel = null;
private TableSorter bucketTableModelSorter = null;
private ObjectTableModel objectTableModel = null;
private TableSorter objectTableModelSorter = null;
private JLabel objectsSummaryLabel = null;
private HashMap cachedBuckets = new HashMap();
private ProgressDialog progressDialog = null;
private ObjectsAttributesDialog objectsAttributesDialog = null;
private File downloadDirectory = null;
private File fileChoosersLastUploadDirectory = null;
private JPanel filterObjectsPanel = null;
private JCheckBox filterObjectsCheckBox = null;
private JTextField filterObjectsPrefix = null;
private JComboBox filterObjectsDelimiter = null;
// File comparison options
private static final String UPLOAD_NEW_FILES_ONLY = "Only upload new files";
private static final String UPLOAD_NEW_AND_CHANGED_FILES = "Upload new and changed files";
private static final String UPLOAD_ALL_FILES = "Upload all files";
private static final String DOWNLOAD_NEW_FILES_ONLY = "Only download new files";
private static final String DOWNLOAD_NEW_AND_CHANGED_FILES = "Download new and changed files";
private static final String DOWNLOAD_ALL_FILES = "Download all files";
private EncryptionUtil encryptionUtil = null;
private Jets3tProperties cockpitProperties = null;
private SkinsFactory skinsFactory = null;
private S3Bucket currentSelectedBucket = null;
private HashMap loginAwsCredentialsMap = new HashMap();
/**
* Constructor to run this application as an Applet.
*/
public Cockpit() {
}
/**
* Constructor to run this application in a stand-alone window.
*
* @param ownerFrame the frame the application will be displayed in
* @throws S3ServiceException
*/
public Cockpit(JFrame ownerFrame) throws S3ServiceException {
this.ownerFrame = ownerFrame;
isStandAloneApplication = true;
init();
ownerFrame.getContentPane().add(this);
ownerFrame.setBounds(this.getBounds());
ownerFrame.setVisible(true);
}
/**
* Prepares application to run as a GUI by finding/creating a root owner JFrame, creating an
* un-authenticated {@link RestS3Service} and loading properties files.
*/
public void init() {
super.init();
// Find or create a Frame to own modal dialog boxes.
if (this.ownerFrame == null) {
Component c = this;
while (!(c instanceof Frame) && c.getParent() != null) {
c = c.getParent();
}
if (!(c instanceof JFrame)) {
this.ownerFrame = new JFrame();
} else {
this.ownerFrame = (JFrame) c;
}
}
// Initialise the GUI.
initGui();
// Initialise a non-authenticated service.
try {
// Revert to anonymous service.
s3ServiceMulti = new S3ServiceMulti(
new RestS3Service(null, APPLICATION_DESCRIPTION, this), this);
cloudFrontService = null;
} catch (S3ServiceException e) {
String message = "Unable to start anonymous service";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
// Load Cockpit configuration files from cockpit's home directory.
File mimeTypesFile = new File(cockpitHomeDirectory, "mime.types");
if (mimeTypesFile.exists()) {
try {
Mimetypes.getInstance().loadAndReplaceMimetypes(
new FileInputStream(mimeTypesFile));
} catch (IOException e) {
String message = "Unable to load mime.types file: " + mimeTypesFile;
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
File jets3tPropertiesFile = new File(cockpitHomeDirectory, "jets3t.properties");
if (jets3tPropertiesFile.exists()) {
try {
Jets3tProperties.getInstance(Constants.JETS3T_PROPERTIES_FILENAME)
.loadAndReplaceProperties(new FileInputStream(jets3tPropertiesFile),
"jets3t.properties in Cockpit's home folder " + cockpitHomeDirectory);
} catch (IOException e) {
String message = "Unable to load jets3t.properties file: " + jets3tPropertiesFile;
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
// Initialise the user's preferences.
this.cockpitPreferences = new CockpitPreferences();
File cockpitPreferencesPropertiesFile = new File(cockpitHomeDirectory, Constants.COCKPIT_PROPERTIES_FILENAME);
if (cockpitPreferencesPropertiesFile.exists()) {
try {
Properties properties = new Properties();
properties.load(new FileInputStream(cockpitPreferencesPropertiesFile));
this.cockpitPreferences.fromProperties(properties);
} catch (IOException e) {
String message = "Unable to load your preferences";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
cockpitProperties = Jets3tProperties.getInstance(Constants.JETS3T_PROPERTIES_FILENAME);
skinsFactory = SkinsFactory.getInstance(cockpitProperties.getProperties());
SwingUtilities.invokeLater(new Runnable() {
public void run() {
loginEvent(null);
}
});
}
/**
* Initialises the application's GUI elements.
*/
private void initGui() {
initMenus();
JPanel appContent = new JPanel(new GridBagLayout());
this.getContentPane().add(appContent);
// Buckets panel.
JPanel bucketsPanel = new JPanel(new GridBagLayout());
JButton bucketActionButton = new JButton();
bucketActionButton.setToolTipText("Bucket actions menu");
guiUtils.applyIcon(bucketActionButton, "/images/nuvola/16x16/actions/misc.png");
bucketActionButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButton sourceButton = (JButton) e.getSource();
bucketActionMenu.show(sourceButton, 0, sourceButton.getHeight());
}
});
bucketsPanel.add(new JHtmlLabel("Buckets", this),
new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
bucketsPanel.add(bucketActionButton,
new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
bucketTableModel = new BucketTableModel(false);
bucketTableModelSorter = new TableSorter(bucketTableModel);
bucketsTable = new JTable(bucketTableModelSorter);
bucketTableModelSorter.setTableHeader(bucketsTable.getTableHeader());
bucketsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
bucketsTable.getSelectionModel().addListSelectionListener(this);
bucketsTable.setShowHorizontalLines(true);
bucketsTable.setShowVerticalLines(false);
bucketsTable.addMouseListener(new ContextMenuListener());
bucketsPanel.add(new JScrollPane(bucketsTable),
new GridBagConstraints(0, 1, 2, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, insetsZero, 0, 0));
bucketsPanel.add(new JLabel(" "),
new GridBagConstraints(0, 2, 2, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, insetsDefault, 0, 0));
// Filter panel.
filterObjectsPanel = new JPanel(new GridBagLayout());
filterObjectsPrefix = new JTextField();
filterObjectsPrefix.setToolTipText("Only show objects with this prefix");
filterObjectsPrefix.addActionListener(this);
filterObjectsPrefix.setActionCommand("RefreshObjects");
filterObjectsDelimiter = new JComboBox(new String[] {"", "/", "?", "\\"});
filterObjectsDelimiter.setEditable(true);
filterObjectsDelimiter.setToolTipText("Object name delimiter");
filterObjectsDelimiter.addActionListener(this);
filterObjectsDelimiter.setActionCommand("RefreshObjects");
filterObjectsPanel.add(new JHtmlLabel("Prefix:", this),
new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, insetsZero, 0, 0));
filterObjectsPanel.add(filterObjectsPrefix,
new GridBagConstraints(1, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, insetsDefault, 0, 0));
filterObjectsPanel.add(new JHtmlLabel("Delimiter:", this),
new GridBagConstraints(2, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, insetsDefault, 0, 0));
filterObjectsPanel.add(filterObjectsDelimiter,
new GridBagConstraints(3, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, insetsZero, 0, 0));
filterObjectsPanel.setVisible(false);
// Objects panel.
JPanel objectsPanel = new JPanel(new GridBagLayout());
int row = 0;
filterObjectsCheckBox = new JCheckBox("Filter objects");
filterObjectsCheckBox.addActionListener(this);
filterObjectsCheckBox.setToolTipText("Check this option to filter the objects listed");
objectsPanel.add(new JHtmlLabel("Objects", this),
new GridBagConstraints(0, row, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
objectsPanel.add(filterObjectsCheckBox,
new GridBagConstraints(1, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
JButton objectActionButton = new JButton();
objectActionButton.setToolTipText("Object actions menu");
guiUtils.applyIcon(objectActionButton, "/images/nuvola/16x16/actions/misc.png");
objectActionButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButton sourceButton = (JButton) e.getSource();
objectActionMenu.show(sourceButton, 0, sourceButton.getHeight());
}
});
objectsPanel.add(objectActionButton,
new GridBagConstraints(2, row, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
objectsPanel.add(filterObjectsPanel,
new GridBagConstraints(0, ++row, 3, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, insetsZero, 0, 0));
objectsTable = new JTable();
objectTableModel = new ObjectTableModel();
objectTableModelSorter = new TableSorter(objectTableModel);
objectTableModelSorter.setTableHeader(objectsTable.getTableHeader());
objectsTable.setModel(objectTableModelSorter);
objectsTable.setDefaultRenderer(Long.class, new DefaultTableCellRenderer() {
private static final long serialVersionUID = 301092191828910402L;
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
String formattedSize = byteFormatter.formatByteSize(((Long)value).longValue());
return super.getTableCellRendererComponent(table, formattedSize, isSelected, hasFocus, row, column);
}
});
objectsTable.setDefaultRenderer(Date.class, new DefaultTableCellRenderer() {
private static final long serialVersionUID = 7285511556343895652L;
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Date date = (Date) value;
return super.getTableCellRendererComponent(table, yearAndTimeSDF.format(date), isSelected, hasFocus, row, column);
}
});
objectsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
objectsTable.getSelectionModel().addListSelectionListener(this);
objectsTable.setShowHorizontalLines(true);
objectsTable.setShowVerticalLines(true);
objectsTable.addMouseListener(new ContextMenuListener());
objectsTableSP = new JScrollPane(objectsTable);
objectsPanel.add(objectsTableSP,
new GridBagConstraints(0, ++row, 3, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, insetsZero, 0, 0));
objectsSummaryLabel = new JHtmlLabel("Please select a bucket", this);
objectsSummaryLabel.setHorizontalAlignment(JLabel.CENTER);
objectsSummaryLabel.setFocusable(false);
objectsPanel.add(objectsSummaryLabel,
new GridBagConstraints(0, ++row, 3, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insetsDefault, 0, 0));
// Combine sections.
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
bucketsPanel, objectsPanel);
splitPane.setOneTouchExpandable(true);
splitPane.setContinuousLayout(true);
appContent.add(splitPane,
new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, insetsDefault, 0, 0));
// Set preferred sizes
int preferredWidth = 800;
int preferredHeight = 600;
this.setBounds(new Rectangle(new Dimension(preferredWidth, preferredHeight)));
splitPane.setResizeWeight(0.30);
// Initialize drop target.
initDropTarget(new JComponent[] {objectsTableSP, objectsTable} );
objectsTable.getDropTarget().setActive(false);
objectsTableSP.getDropTarget().setActive(false);
}
/**
* Initialise the application's menu bar.
*/
private void initMenus() {
JMenuBar appMenuBar = new JMenuBar();
if (this.isStandAloneApplication && OS_NAME_MAC.equals(System.getProperty("os.name"))) {
/*
* We need to check we're running on a Mac before adding the menu to the
* owner frame, otherwise the menus will be displayed *behind* the main
* GUI panel on Windows Vista and later [sigh]
*/
ownerFrame.setJMenuBar(appMenuBar);
} else {
this.setJMenuBar(appMenuBar);
}
// Service menu
JMenu serviceMenu = new JMenu("Service");
loginMenuItem = new JMenuItem("Log in...");
loginMenuItem.setActionCommand("LoginEvent");
loginMenuItem.addActionListener(this);
guiUtils.applyIcon(loginMenuItem, "/images/nuvola/16x16/actions/connect_creating.png");
serviceMenu.add(loginMenuItem);
logoutMenuItem = new JMenuItem("Log out");
logoutMenuItem.setActionCommand("LogoutEvent");
logoutMenuItem.addActionListener(this);
guiUtils.applyIcon(logoutMenuItem, "/images/nuvola/16x16/actions/connect_no.png");
serviceMenu.add(logoutMenuItem);
loginSwitchMenu = new JMenu("Switch login");
loginSwitchMenu.addActionListener(this);
serviceMenu.add(new JSeparator());
guiUtils.applyIcon(loginSwitchMenu, "/images/nuvola/16x16/actions/connect_established.png");
serviceMenu.add(loginSwitchMenu);
loginSwitchMenu.setEnabled(false);
if (isStandAloneApplication) {
serviceMenu.add(new JSeparator());
JMenuItem quitMenuItem = new JMenuItem("Quit");
quitMenuItem.setActionCommand("QuitEvent");
quitMenuItem.addActionListener(this);
guiUtils.applyIcon(quitMenuItem, "/images/nuvola/16x16/actions/exit.png");
serviceMenu.add(quitMenuItem);
}
logoutMenuItem.setEnabled(false);
// Bucket action menu.
bucketActionMenu = new JPopupMenu();
refreshBucketMenuItem = new JMenuItem("Refresh bucket listing");
refreshBucketMenuItem.setActionCommand("RefreshBuckets");
refreshBucketMenuItem.addActionListener(this);
guiUtils.applyIcon(refreshBucketMenuItem, "/images/nuvola/16x16/actions/reload.png");
bucketActionMenu.add(refreshBucketMenuItem);
viewBucketPropertiesMenuItem = new JMenuItem("View bucket properties...");
viewBucketPropertiesMenuItem.setActionCommand("ViewBucketProperties");
viewBucketPropertiesMenuItem.addActionListener(this);
guiUtils.applyIcon(viewBucketPropertiesMenuItem, "/images/nuvola/16x16/actions/viewmag.png");
bucketActionMenu.add(viewBucketPropertiesMenuItem);
updateBucketACLMenuItem = new JMenuItem("Update bucket's Access Control List...");
updateBucketACLMenuItem.setActionCommand("UpdateBucketACL");
updateBucketACLMenuItem.addActionListener(this);
guiUtils.applyIcon(updateBucketACLMenuItem, "/images/nuvola/16x16/actions/encrypted.png");
bucketActionMenu.add(updateBucketACLMenuItem);
updateBucketRequesterPaysStatusMenuItem = new JMenuItem("Update bucket's Requester Pays status...");
updateBucketRequesterPaysStatusMenuItem.setActionCommand("UpdateBucketRequesterPaysStatus");
updateBucketRequesterPaysStatusMenuItem.addActionListener(this);
guiUtils.applyIcon(updateBucketRequesterPaysStatusMenuItem, "/images/nuvola/16x16/actions/identity.png");
bucketActionMenu.add(updateBucketRequesterPaysStatusMenuItem);
bucketActionMenu.add(new JSeparator());
createBucketMenuItem = new JMenuItem("Create new bucket...");
createBucketMenuItem.setActionCommand("CreateBucket");
createBucketMenuItem.addActionListener(this);
guiUtils.applyIcon(createBucketMenuItem, "/images/nuvola/16x16/actions/viewmag+.png");
bucketActionMenu.add(createBucketMenuItem);
JMenuItem thirdPartyBucketMenuItem = new JMenuItem("Add third-party bucket...");
thirdPartyBucketMenuItem.setActionCommand("AddThirdPartyBucket");
thirdPartyBucketMenuItem.addActionListener(this);
guiUtils.applyIcon(thirdPartyBucketMenuItem, "/images/nuvola/16x16/actions/viewmagfit.png");
bucketActionMenu.add(thirdPartyBucketMenuItem);
bucketActionMenu.add(new JSeparator());
deleteBucketMenuItem = new JMenuItem("Delete bucket...");
deleteBucketMenuItem.setActionCommand("DeleteBucket");
deleteBucketMenuItem.addActionListener(this);
guiUtils.applyIcon(deleteBucketMenuItem, "/images/nuvola/16x16/actions/cancel.png");
bucketActionMenu.add(deleteBucketMenuItem);
viewBucketPropertiesMenuItem.setEnabled(false);
refreshBucketMenuItem.setEnabled(false);
createBucketMenuItem.setEnabled(false);
updateBucketACLMenuItem.setEnabled(false);
updateBucketRequesterPaysStatusMenuItem.setEnabled(false);
deleteBucketMenuItem.setEnabled(false);
// Object action menu.
objectActionMenu = new JPopupMenu();
refreshObjectMenuItem = new JMenuItem("Refresh object listing");
refreshObjectMenuItem.setActionCommand("RefreshObjects");
refreshObjectMenuItem.addActionListener(this);
guiUtils.applyIcon(refreshObjectMenuItem, "/images/nuvola/16x16/actions/reload.png");
objectActionMenu.add(refreshObjectMenuItem);
viewOrModifyObjectAttributesMenuItem = new JMenuItem("View or Modify object attributes...");
viewOrModifyObjectAttributesMenuItem.setActionCommand("ViewOrModifyObjectAttributes");
viewOrModifyObjectAttributesMenuItem.addActionListener(this);
guiUtils.applyIcon(viewOrModifyObjectAttributesMenuItem, "/images/nuvola/16x16/actions/viewmag.png");
objectActionMenu.add(viewOrModifyObjectAttributesMenuItem);
copyObjectsMenuItem = new JMenuItem("Copy or Move objects...");
copyObjectsMenuItem.setActionCommand("CopyObjects");
copyObjectsMenuItem.addActionListener(this);
guiUtils.applyIcon(copyObjectsMenuItem, "/images/nuvola/16x16/actions/filenew.png");
objectActionMenu.add(copyObjectsMenuItem);
updateObjectACLMenuItem = new JMenuItem("View or Modify Access Control Lists...");
updateObjectACLMenuItem.setActionCommand("UpdateObjectACL");
updateObjectACLMenuItem.addActionListener(this);
guiUtils.applyIcon(updateObjectACLMenuItem, "/images/nuvola/16x16/actions/encrypted.png");
objectActionMenu.add(updateObjectACLMenuItem);
downloadObjectMenuItem = new JMenuItem("Download objects...");
downloadObjectMenuItem.setActionCommand("DownloadObjects");
downloadObjectMenuItem.addActionListener(this);
guiUtils.applyIcon(downloadObjectMenuItem, "/images/nuvola/16x16/actions/1downarrow.png");
objectActionMenu.add(downloadObjectMenuItem);
uploadFilesMenuItem = new JMenuItem("Upload files...");
uploadFilesMenuItem.setActionCommand("UploadFiles");
uploadFilesMenuItem.addActionListener(this);
guiUtils.applyIcon(uploadFilesMenuItem, "/images/nuvola/16x16/actions/1uparrow.png");
objectActionMenu.add(uploadFilesMenuItem);
objectActionMenu.add(new JSeparator());
generatePublicGetUrls = new JMenuItem("Generate Public GET URLs...");
generatePublicGetUrls.setActionCommand("GeneratePublicGetURLs");
generatePublicGetUrls.addActionListener(this);
guiUtils.applyIcon(generatePublicGetUrls, "/images/nuvola/16x16/actions/wizard.png");
objectActionMenu.add(generatePublicGetUrls);
generateTorrentUrl = new JMenuItem("Generate Torrent URL...");
generateTorrentUrl.setActionCommand("GenerateTorrentURL");
generateTorrentUrl.addActionListener(this);
guiUtils.applyIcon(generateTorrentUrl, "/images/nuvola/16x16/actions/wizard.png");
objectActionMenu.add(generateTorrentUrl);
objectActionMenu.add(new JSeparator());
deleteObjectMenuItem = new JMenuItem("Delete objects...");
deleteObjectMenuItem.setActionCommand("DeleteObjects");
deleteObjectMenuItem.addActionListener(this);
guiUtils.applyIcon(deleteObjectMenuItem, "/images/nuvola/16x16/actions/cancel.png");
objectActionMenu.add(deleteObjectMenuItem);
viewOrModifyObjectAttributesMenuItem.setEnabled(false);
copyObjectsMenuItem.setEnabled(false);
refreshObjectMenuItem.setEnabled(false);
updateObjectACLMenuItem.setEnabled(false);
downloadObjectMenuItem.setEnabled(false);
uploadFilesMenuItem.setEnabled(false);
generatePublicGetUrls.setEnabled(false);
generateTorrentUrl.setEnabled(false);
deleteObjectMenuItem.setEnabled(false);
// Tools menu.
JMenu toolsMenu = new JMenu("Tools");
bucketLoggingMenuItem = new JMenuItem("Configure Bucket logging...");
bucketLoggingMenuItem.setActionCommand("BucketLogging");
bucketLoggingMenuItem.addActionListener(this);
bucketLoggingMenuItem.setEnabled(false);
guiUtils.applyIcon(bucketLoggingMenuItem, "/images/nuvola/16x16/actions/toggle_log.png");
toolsMenu.add(bucketLoggingMenuItem);
manageDistributionsMenuItem = new JMenuItem("Manage CloudFront Distributions...");
manageDistributionsMenuItem.setActionCommand("ManageDistributions");
manageDistributionsMenuItem.addActionListener(this);
guiUtils.applyIcon(manageDistributionsMenuItem, "/images/nuvola/16x16/actions/irkick.png");
manageDistributionsMenuItem.setEnabled(false);
toolsMenu.add(manageDistributionsMenuItem);
toolsMenu.add(new JSeparator());
preferencesDialogMenuItem = new JMenuItem("Preferences...");
preferencesDialogMenuItem.setActionCommand("PreferencesDialog");
preferencesDialogMenuItem.addActionListener(this);
guiUtils.applyIcon(preferencesDialogMenuItem, "/images/nuvola/16x16/actions/configure.png");
toolsMenu.add(preferencesDialogMenuItem);
// Help menu.
JMenu helpMenu = new JMenu("Help");
cockpitHelpMenuItem = new JMenuItem("Cockpit Guide");
guiUtils.applyIcon(cockpitHelpMenuItem, "/images/nuvola/16x16/actions/help.png");
cockpitHelpMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
followHyperlink(new URL(JETS3T_COCKPIT_HELP_PAGE), "_blank");
} catch (MalformedURLException ex) {
throw new IllegalStateException("Invalid URL embedded in program: "
+ JETS3T_COCKPIT_HELP_PAGE);
}
}
});
helpMenu.add(cockpitHelpMenuItem);
amazonS3HelpMenuItem = new JMenuItem("Amazon S3");
guiUtils.applyIcon(amazonS3HelpMenuItem, "/images/nuvola/16x16/actions/gohome.png");
amazonS3HelpMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
followHyperlink(new URL(AMAZON_S3_PAGE), "_blank");
} catch (MalformedURLException ex) {
throw new IllegalStateException("Invalid URL embedded in program: "
+ AMAZON_S3_PAGE);
}
}
});
helpMenu.add(amazonS3HelpMenuItem);
// Build application menu bar.
appMenuBar.add(serviceMenu);
appMenuBar.add(toolsMenu);
appMenuBar.add(helpMenu);
}
/**
* Initialise the application's File drop targets for drag and drop copying of local files
* to S3.
*
* @param dropTargetComponents
* the components files can be dropped on to transfer them to S3
*/
private void initDropTarget(JComponent[] dropTargetComponents) {
DropTargetListener dropTargetListener = new DropTargetListener() {
private boolean checkValidDrag(DropTargetDragEvent dtde) {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
&& (DnDConstants.ACTION_COPY == dtde.getDropAction()
|| DnDConstants.ACTION_MOVE == dtde.getDropAction()))
{
dtde.acceptDrag(dtde.getDropAction());
return true;
} else {
dtde.rejectDrag();
return false;
}
}
public void dragEnter(DropTargetDragEvent dtde) {
if (checkValidDrag(dtde)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
objectsTable.requestFocusInWindow();
};
});
}
}
public void dragOver(DropTargetDragEvent dtde) {
checkValidDrag(dtde);
}
public void dropActionChanged(DropTargetDragEvent dtde) {
if (checkValidDrag(dtde)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
objectsTable.requestFocusInWindow();
};
});
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ownerFrame.requestFocusInWindow();
};
});
}
}
public void dragExit(DropTargetEvent dte) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ownerFrame.requestFocusInWindow();
};
});
}
public void drop(DropTargetDropEvent dtde) {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
&& (DnDConstants.ACTION_COPY == dtde.getDropAction()
|| DnDConstants.ACTION_MOVE == dtde.getDropAction()))
{
dtde.acceptDrop(dtde.getDropAction());
try {
final List fileList = (List) dtde.getTransferable().getTransferData(
DataFlavor.javaFileListFlavor);
if (fileList != null && fileList.size() > 0) {
uploadFiles((File[]) fileList.toArray(new File[fileList.size()]));
}
} catch (Exception e) {
String message = "Unable to start accept dropped items";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, null, message, e);
}
} else {
dtde.rejectDrop();
}
}
};
// Attach drop target listener to each target component.
for (int i = 0; i < dropTargetComponents.length; i++) {
new DropTarget(dropTargetComponents[i], DnDConstants.ACTION_COPY, dropTargetListener, true);
}
}
/**
* Run the provided Runnable object in a background thread. This method will
* return as soon as the background thread is started, it does not wait for
* the thread to complete.
*/
private synchronized void runInBackgroundThread(Runnable runnable) {
Thread t = new Thread(runnable);
t.start();
}
/**
* Run the provided runnable in the application's event dispatcher thread,
* and wait for the action to complete before returning.
*
* @param runnable
* @return
*/
private synchronized boolean runInDispatcherThreadImmediately(Runnable runnable) {
try {
SwingUtilities.invokeAndWait(runnable);
return true;
} catch (Exception e) {
log.error("Error displaying graphical elements", e);
return false;
}
}
/**
* Starts a progress display dialog that cannot be cancelled. While the dialog is running the user
* cannot interact with the application.
*
* @param statusText
* describes the status of a task in text meaningful to the user
*/
private void startProgressDialog(String statusText) {
startProgressDialog(statusText, null, 0, 0, null, null);
}
/**
* Starts a progress display dialog. While the dialog is running the user cannot interact
* with the application, except to cancel the task.
*
* @param statusMessage
* describes the status of a task text meaningful to the user, such as "3 files of 7 uploaded"
* @param detailsText
* describes the status of a task in more detail, such as the current transfer rate and Time remaining.
* @param minTaskValue the minimum progress value for a task, generally 0
* @param maxTaskValue
* the maximum progress value for a task, such as the total number of threads or 100 if
* using percentage-complete as a metric.
* @param cancelEventListener
* listener that is responsible for cancelling a long-lived task when the user clicks
* the cancel button. If a task cannot be cancelled this must be null.
* @param cancelButtonText
* text displayed in the cancel button if a task can be cancelled. This is only used if
* a cancel event listener is provided.
*/
private void startProgressDialog(final String statusMessage, final String detailsText,
final int minTaskValue, final int maxTaskValue, final String cancelButtonText,
final CancelEventTrigger cancelEventListener)
{
if (this.progressDialog == null) {
this.progressDialog = new ProgressDialog(this.ownerFrame, "Please wait...", null);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressDialog.startDialog(statusMessage, detailsText, minTaskValue, maxTaskValue,
cancelEventListener, cancelButtonText);
}
});
}
/**
* Updates the status text and value of the progress display dialog.
* @param statusMessage
* describes the status of a task text meaningful to the user, such as "3 files of 7 uploaded"
* @param detailsText
* describes the status of a task in more detail, such as the current transfer rate and time remaining.
* @param progressValue
* value representing how far through the task we are (relative to min and max values)
*/
private void updateProgressDialog(final String statusMessage, final String detailsText, final int progressValue) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressDialog.updateDialog(statusMessage, detailsText, progressValue);
}
});
}
/**
* Stops/halts the progress display dialog and allows the user to interact with the application.
*/
private void stopProgressDialog() {
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
progressDialog.stopDialog();
}
});
}
/**
* Event handler for this application, handles all menu items.
*/
public void actionPerformed(ActionEvent event) {
// Service Menu Events
if ("LoginEvent".equals(event.getActionCommand())) {
loginEvent(null);
} else if ("LogoutEvent".equals(event.getActionCommand())) {
logoutEvent();
} else if (event.getActionCommand() != null && event.getActionCommand().startsWith("LoginSwitch")) {
String loginName = event.getActionCommand().substring("LoginSwitch:".length());
AWSCredentials awsCredentials = (AWSCredentials) loginAwsCredentialsMap.get(loginName);
loginEvent(awsCredentials);
} else if ("QuitEvent".equals(event.getActionCommand())) {
System.exit(0);
}
// Bucket Events.
else if ("ViewBucketProperties".equals(event.getActionCommand())) {
listBucketProperties();
} else if ("RefreshBuckets".equals(event.getActionCommand())) {
listAllBuckets();
} else if ("CreateBucket".equals(event.getActionCommand())) {
createBucketAction();
} else if ("DeleteBucket".equals(event.getActionCommand())) {
deleteSelectedBucket();
} else if ("ManageDistributions".equals(event.getActionCommand())) {
S3Bucket[] buckets = bucketTableModel.getBuckets();
String[] bucketNames = new String[buckets.length];
for (int i = 0; i < buckets.length; i++) {
bucketNames[i] = buckets[i].getName();
}
ManageDistributionsDialog.showDialog(ownerFrame, cloudFrontService, bucketNames, this);
} else if ("AddThirdPartyBucket".equals(event.getActionCommand())) {
addThirdPartyBucket();
} else if ("UpdateBucketACL".equals(event.getActionCommand())) {
updateBucketAccessControlList();
} else if ("UpdateBucketRequesterPaysStatus".equals(event.getActionCommand())) {
updateBucketRequesterPaysSetting();
}
// Object Events
else if ("ViewOrModifyObjectAttributes".equals(event.getActionCommand())) {
displayObjectsAttributesDialog();
} else if ("CopyObjects".equals(event.getActionCommand())) {
copyObjects();
} else if ("RefreshObjects".equals(event.getActionCommand())) {
listObjects();
} else if ("UpdateObjectACL".equals(event.getActionCommand())) {
displayAclModificationDialog();
} else if ("GeneratePublicGetURLs".equals(event.getActionCommand())) {
generatePublicGetUrls();
} else if ("GenerateTorrentURL".equals(event.getActionCommand())) {
generateTorrentUrl();
} else if ("DeleteObjects".equals(event.getActionCommand())) {
deleteSelectedObjects();
} else if ("DownloadObjects".equals(event.getActionCommand())) {
downloadSelectedObjects();
} else if ("UploadFiles".equals(event.getActionCommand())) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setMultiSelectionEnabled(true);
fileChooser.setDialogTitle("Choose files to upload");
fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
fileChooser.setApproveButtonText("Upload files");
fileChooser.setCurrentDirectory(fileChoosersLastUploadDirectory);
int returnVal = fileChooser.showOpenDialog(ownerFrame);
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
final File[] uploadFiles = fileChooser.getSelectedFiles();
if (uploadFiles.length == 0) {
return;
}
// Save the chosen directory location for next time.
fileChoosersLastUploadDirectory = uploadFiles[0].getParentFile();
uploadFiles(uploadFiles);
} else if (event.getSource().equals(filterObjectsCheckBox)) {
if (filterObjectsCheckBox.isSelected()) {
filterObjectsPanel.setVisible(true);
} else {
filterObjectsPanel.setVisible(false);
filterObjectsPrefix.setText("");
if (filterObjectsDelimiter.getSelectedIndex() != 0) {
filterObjectsDelimiter.setSelectedIndex(0);
}
}
}
// Tools events
else if ("BucketLogging".equals(event.getActionCommand())) {
S3Bucket[] buckets = bucketTableModel.getBuckets();
BucketLoggingDialog.showDialog(ownerFrame, s3ServiceMulti.getS3Service(), buckets, this);
}
// Preference Events
else if ("PreferencesDialog".equals(event.getActionCommand())) {
PreferencesDialog.showDialog(cockpitPreferences, ownerFrame, this);
// Save a user's preferences if requested, otherwise wipe any existing preferences file.
File cockpitPreferencesPropertiesFile = new File(cockpitHomeDirectory, Constants.COCKPIT_PROPERTIES_FILENAME);
if (cockpitPreferences.isRememberPreferences()) {
try {
Properties properties = cockpitPreferences.toProperties();
if (!cockpitHomeDirectory.exists()) {
cockpitHomeDirectory.mkdir();
}
properties.list(new PrintStream(
new FileOutputStream(cockpitPreferencesPropertiesFile)));
} catch (IOException e) {
String message = "Unable to save your preferences";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
} else if (cockpitPreferencesPropertiesFile.exists()) {
// User elected not to store preferences, delete the existing preferences file.
cockpitPreferencesPropertiesFile.delete();
}
if (cockpitPreferences.isEncryptionPasswordSet()) {
try {
encryptionUtil = new EncryptionUtil(
cockpitPreferences.getEncryptionPassword(),
cockpitPreferences.getEncryptionAlgorithm(),
EncryptionUtil.DEFAULT_VERSION);
} catch (Exception e) {
String message = "Unable to start encryption utility";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
} else {
encryptionUtil = null;
}
}
// Ooops...
else {
log.debug("Unrecognised ActionEvent command '" + event.getActionCommand() + "' in " + event);
}
}
/**
* Handles list selection events for this application.
*/
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
if (e.getSource().equals(bucketsTable.getSelectionModel())) {
bucketSelectedAction();
} else if (e.getSource().equals(objectsTable.getSelectionModel())) {
objectSelectedAction();
}
}
/**
* Displays the {@link StartupDialog} dialog and, if the user provides login credentials,
* logs into the S3 service using those credentials.
*
* This method should always be run within the event dispatcher thread.
*/
private void loginEvent(AWSCredentials awsCredentials) {
try {
if (awsCredentials == null) {
StartupDialog startupDialog = new StartupDialog(ownerFrame, cockpitProperties, this);
startupDialog.setVisible(true);
awsCredentials = startupDialog.getAWSCredentials();
startupDialog.dispose();
if (awsCredentials == null) {
log.debug("Log in cancelled by user");
return;
}
}
s3ServiceMulti = new S3ServiceMulti(
new RestS3Service(awsCredentials, APPLICATION_DESCRIPTION, this), this);
cloudFrontMembershipChecked = false;
listAllBuckets();
objectsSummaryLabel.setText(" ");
logoutMenuItem.setEnabled(true);
refreshBucketMenuItem.setEnabled(true);
createBucketMenuItem.setEnabled(true);
bucketLoggingMenuItem.setEnabled(true);
String loginName = (awsCredentials.hasFriendlyName()
? awsCredentials.getFriendlyName()
: awsCredentials.getAccessKey());
if (!loginAwsCredentialsMap.containsKey(loginName)) {
loginAwsCredentialsMap.put(loginName, awsCredentials);
JMenuItem menuItem = new JMenuItem(loginName);
menuItem.setActionCommand("LoginSwitch:" + loginName);
menuItem.addActionListener(this);
loginSwitchMenu.add(menuItem);
loginSwitchMenu.setEnabled(true);
}
} catch (Exception e) {
String message = "Unable to log in to S3";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
logoutEvent();
}
}
/**
* Logs out of the S3 service by clearing all listed objects and buckets and resetting
* the s3ServiceMulti member variable.
*
* This method should always be invoked within the event dispatching thread.
*/
private void logoutEvent() {
log.debug("Logging out");
try {
AWSCredentials awsCredentials = s3ServiceMulti.getAWSCredentials();
String loginName = (awsCredentials.hasFriendlyName()
? awsCredentials.getFriendlyName()
: awsCredentials.getAccessKey());
if (loginAwsCredentialsMap.containsKey(loginName)) {
Component[] components = loginSwitchMenu.getMenuComponents();
for (int i = 0; i < components.length; i++) {
JMenuItem menuItem = (JMenuItem)components[i];
if (loginName.equals(menuItem.getText())) {
loginSwitchMenu.remove(components[i]);
break;
}
}
loginAwsCredentialsMap.remove(loginName);
loginSwitchMenu.setEnabled(loginAwsCredentialsMap.size() > 0);
}
// Revert to anonymous service.
s3ServiceMulti = new S3ServiceMulti(
new RestS3Service(null, APPLICATION_DESCRIPTION, this), this);
cloudFrontService = null;
bucketsTable.clearSelection();
bucketTableModel.removeAllBuckets();
objectTableModel.removeAllObjects();
objectsSummaryLabel.setText(" ");
ownerFrame.setTitle(APPLICATION_TITLE);
logoutMenuItem.setEnabled(false);
refreshBucketMenuItem.setEnabled(false);
createBucketMenuItem.setEnabled(false);
bucketLoggingMenuItem.setEnabled(false);
manageDistributionsMenuItem.setEnabled(false);
} catch (Exception e) {
String message = "Unable to log out from S3";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
/**
* Displays the currently selected bucket's properties in the dialog {@link ItemPropertiesDialog}.
*/
private void listBucketProperties() {
final S3Bucket selectedBucket = currentSelectedBucket;
if (selectedBucket.getAcl() == null || !selectedBucket.isLocationKnown()) {
// Retrieve all a bucket's details before displaying the summary.
runInBackgroundThread(new Runnable() {
public void run() {
startProgressDialog("Retrieving details for bucket " + selectedBucket.getName());
try {
try {
if (selectedBucket.getAcl() == null) {
selectedBucket.setAcl(
s3ServiceMulti.getS3Service().getBucketAcl(
selectedBucket));
}
if (!selectedBucket.isLocationKnown()) {
selectedBucket.setLocation(
s3ServiceMulti.getS3Service().getBucketLocation(
selectedBucket.getName()));
}
if (!selectedBucket.isRequesterPaysKnown()) {
selectedBucket.setRequesterPays(
s3ServiceMulti.getS3Service().isRequesterPaysBucket(
selectedBucket.getName()));
}
} catch (S3ServiceException e) {
// Retrieving details for a third-party bucket will
// often fail when ACL or Location is retrieved,
// ignore these failures.
}
stopProgressDialog();
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
ItemPropertiesDialog.showDialog(ownerFrame, selectedBucket, null);
}
});
} catch (final Exception e) {
stopProgressDialog();
String message = "Unable to retrieve details for bucket";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, null, message, e);
}
};
});
} else {
ItemPropertiesDialog.showDialog(ownerFrame, selectedBucket, null);
}
}
/**
* Displays the currently selected object's properties in the dialog {@link ObjectsAttributesDialog}.
*
* As detailed information about the object may not yet be available, this method works
* indirectly via the {@link #retrieveObjectsDetails} method. The retrieveObjectsDetails
* method retrieves all the details for the currently selected objects, and once they are available
* knows to display the dialog as the {@link #isViewingOrModifyingObjectProperties} flag is set.
*/
private void displayObjectsAttributesDialog() {
final S3Bucket selectedBucket = currentSelectedBucket;
runInBackgroundThread(new Runnable() {
public void run() {
if (!retrieveObjectsDetails(getSelectedObjects())) {
return;
}
if (objectsAttributesDialog == null) {
objectsAttributesDialog = new ObjectsAttributesDialog(
ownerFrame, "Object Attributes", skinsFactory);
}
final S3Object[] sourceObjects = getSelectedObjects();
boolean ok = runInDispatcherThreadImmediately(new Runnable() {
public void run() {
objectsAttributesDialog.displayDialog(sourceObjects, true);
}
});
if (!ok) {
return;
}
final String[] sourceObjectKeys = objectsAttributesDialog.getSourceObjectKeys();
final S3Object[] destinationObjects = objectsAttributesDialog.getDestinationObjects();
if (!objectsAttributesDialog.isModifyActionApproved()) {
// Do nothing.
return;
}
// Retain ACL settings from original objects.
if (!s3ServiceMulti.getObjectACLs(selectedBucket, sourceObjects)) {
return;
}
for (int i = 0; i < sourceObjects.length; i++) {
destinationObjects[i].setAcl(
sourceObjects[i].getAcl());
}
// Copy objects in-place, to REPLACE their metadata attributes.
ok = s3ServiceMulti.copyObjects(
selectedBucket.getName(), selectedBucket.getName(),
sourceObjectKeys, destinationObjects, true);
// Refresh details for modified objects
if (ok) {
s3ServiceMulti.getObjectsHeads(
selectedBucket, destinationObjects);
}
}
});
}
/**
* Lists the buckets in the user's S3 account and refreshes the GUI to display
* these buckets. Any buckets or objects already listed in the GUI are cleared first.
*/
private void listAllBuckets() {
// Remove current bucket and object data from models.
cachedBuckets.clear();
bucketsTable.clearSelection();
bucketTableModel.removeAllBuckets();
objectTableModel.removeAllObjects();
final Cockpit myself = this;
// This is all very convoluted. This was necessary so we can display the status dialog box.
runInBackgroundThread(new Runnable() {
public void run() {
if (!cloudFrontMembershipChecked) {
// Check whether the user is signed-up for CloudFront.
startProgressDialog("Checking for CloudFront account membership");
try {
cloudFrontService = new CloudFrontService(
s3ServiceMulti.getAWSCredentials(), APPLICATION_DESCRIPTION, myself, null, null);
cloudFrontService.listDistributions();
} catch (CloudFrontServiceException e) {
stopProgressDialog();
if ("OptInRequired".equals(e.getErrorCode())) {
log.debug("Your AWS account is not subscribed to the Amazon CloudFront service, "
+ "you will not be able to manage distributions");
}
cloudFrontService = null;
} finally {
stopProgressDialog();
cloudFrontMembershipChecked = true;
// Update the bucket table to show, or not show, distributions
bucketTableModel = new BucketTableModel(cloudFrontService != null);
bucketTableModelSorter = new TableSorter(bucketTableModel);
bucketsTable.setModel(bucketTableModelSorter);
bucketTableModelSorter.setTableHeader(bucketsTable.getTableHeader());
if (cloudFrontService != null) {
// Set column width for Cloud Front distributions indicator.
TableColumn distributionFlagColumn = bucketsTable.getColumnModel().getColumn(1);
int distributionFlagColumnWidth = 18;
distributionFlagColumn.setPreferredWidth(distributionFlagColumnWidth);
distributionFlagColumn.setMaxWidth(distributionFlagColumnWidth);
distributionFlagColumn.setMinWidth(0);
}
manageDistributionsMenuItem.setEnabled(cloudFrontService != null);
}
}
startProgressDialog("Listing buckets for " + s3ServiceMulti.getAWSCredentials().getAccessKey());
try {
final S3Bucket[] buckets = s3ServiceMulti.getS3Service().listAllBuckets();
// Lookup user's CloudFront distributions.
Distribution[] distributions = new Distribution[] {};
if (cloudFrontService != null) {
updateProgressDialog("Listing distributions for " + cloudFrontService.getAWSCredentials().getAccessKey(), "", 0);
distributions = cloudFrontService.listDistributions();
}
final Distribution[] finalDistributions = distributions;
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
for (int i = 0; i < buckets.length; i++) {
// Determine whether each bucket has one or more CloudFront distributions.
boolean bucketHasDistribution = false;
for (int j = 0; j < finalDistributions.length; j++) {
if (finalDistributions[j].getOrigin().equals(buckets[i].getName() + ".s3.amazonaws.com")) {
bucketHasDistribution = true;
}
}
bucketTableModel.addBucket(buckets[i], bucketHasDistribution);
if (i == 0) {
ownerFrame.setTitle(APPLICATION_TITLE + " : " +
buckets[i].getOwner().getDisplayName());
}
}
}
});
} catch (final Exception e) {
stopProgressDialog();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
logoutEvent();
String message = "Unable to list your buckets in S3, please log in again";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, null, message, e);
loginEvent(null);
}
});
} finally {
stopProgressDialog();
}
};
});
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a GetObjectsEvent
.
*
* This never happens in this application as downloads are performed by
* {@link S3ServiceMulti#downloadObjects(S3Bucket, DownloadPackage[])} instead.
*
* @param event
*/
public void s3ServiceEventPerformed(GetObjectsEvent event) {
// Not used.
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a ListObjectsEvent
.
*
* This never happens in this application as it does not perform multi-threaded object
* listings.
*
* @param event
*/
public void s3ServiceEventPerformed(ListObjectsEvent event) {
// Not used.
}
/**
* Actions performed when a bucket is selected in the bucket list table.
*/
private void bucketSelectedAction() {
this.currentSelectedBucket = null;
// Find the selected bucket in the buckets table, if any.
if (bucketsTable.getSelectedRows().length != 0) {
this.currentSelectedBucket = bucketTableModel.getBucket(
bucketTableModelSorter.modelIndex(
bucketsTable.getSelectedRows()[0]));
}
if (currentSelectedBucket == null) {
viewBucketPropertiesMenuItem.setEnabled(false);
refreshBucketMenuItem.setEnabled(true);
updateBucketACLMenuItem.setEnabled(false);
updateBucketRequesterPaysStatusMenuItem.setEnabled(false);
deleteBucketMenuItem.setEnabled(false);
refreshObjectMenuItem.setEnabled(false);
uploadFilesMenuItem.setEnabled(false);
objectTableModel.removeAllObjects();
objectsTable.getDropTarget().setActive(false);
objectsTableSP.getDropTarget().setActive(false);
} else {
viewBucketPropertiesMenuItem.setEnabled(true);
refreshBucketMenuItem.setEnabled(true);
updateBucketACLMenuItem.setEnabled(true);
updateBucketRequesterPaysStatusMenuItem.setEnabled(true);
deleteBucketMenuItem.setEnabled(true);
refreshObjectMenuItem.setEnabled(true);
uploadFilesMenuItem.setEnabled(true);
objectsTable.getDropTarget().setActive(true);
objectsTableSP.getDropTarget().setActive(true);
if (cachedBuckets.containsKey(currentSelectedBucket.getName())) {
S3Object[] objects = (S3Object[]) cachedBuckets.get(currentSelectedBucket.getName());
objectTableModel.removeAllObjects();
objectTableModel.addObjects(objects);
updateObjectsSummary(false);
} else {
listObjects();
}
}
}
/**
* Actions performed when an object is selected in the objects list table.
*/
private void objectSelectedAction() {
int count = getSelectedObjects().length;
updateObjectACLMenuItem.setEnabled(count > 0);
downloadObjectMenuItem.setEnabled(count > 0);
deleteObjectMenuItem.setEnabled(count > 0);
viewOrModifyObjectAttributesMenuItem.setEnabled(count > 0);
copyObjectsMenuItem.setEnabled(count > 0);
generatePublicGetUrls.setEnabled(count >= 1 && s3ServiceMulti.getAWSCredentials() != null);
generateTorrentUrl.setEnabled(count == 1);
}
/**
* Starts a thread to run {@link S3ServiceMulti#listObjects}.
*/
private void listObjects() {
if (currentSelectedBucket == null) {
// Oops, better do nothing.
return;
}
final boolean listingCancelled[] = new boolean[1]; // Default to false.
final CancelEventTrigger cancelListener = new CancelEventTrigger() {
private static final long serialVersionUID = 6939193243303189876L;
public void cancelTask(Object eventSource) {
listingCancelled[0] = true;
}
};
objectTableModel.removeAllObjects();
objectsSummaryLabel.setText(" ");
// This is all very convoluted, it was done this way to ensure we can display the dialog box.
runInBackgroundThread(new Runnable() {
public void run() {
try {
startProgressDialog(
"Listing objects in " + currentSelectedBucket.getName(),
"", 0, 0, "Cancel bucket listing", cancelListener);
final String prefix = filterObjectsPrefix.getText();
final String delimiter = (String) filterObjectsDelimiter.getSelectedItem();
final ArrayList allObjects = new ArrayList();
String priorLastKey = null;
do {
S3ObjectsChunk chunk = s3ServiceMulti.getS3Service().listObjectsChunked(
currentSelectedBucket.getName(), prefix, delimiter,
BUCKET_LIST_CHUNKING_SIZE, priorLastKey);
final S3Object[] objects = chunk.getObjects();
for (int i = 0; i < objects.length; i++) {
objects[i].setOwner(currentSelectedBucket.getOwner());
}
priorLastKey = chunk.getPriorLastKey();
allObjects.addAll(Arrays.asList(objects));
updateProgressDialog(
"Listed " + allObjects.size() + " objects in "
+ currentSelectedBucket.getName(), "", 0);
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
objectTableModel.addObjects(objects);
updateObjectsSummary(true);
}
});
} while (!listingCancelled[0] && priorLastKey != null);
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
updateObjectsSummary(listingCancelled[0]);
S3Object[] allObjects = objectTableModel.getObjects();
cachedBuckets.put(currentSelectedBucket.getName(), allObjects);
}
});
} catch (final Exception e) {
stopProgressDialog();
String message = "Unable to list objects";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, null, message, e);
} finally {
stopProgressDialog();
}
};
});
}
/**
* Updates the summary text shown below the listing of objects, which details the
* number and total size of the objects.
*
*/
private void updateObjectsSummary(boolean isIncompleteListing) {
S3Object[] objects = objectTableModel.getObjects();
try {
String summary = "Please select a bucket";
long totalBytes = 0;
if (objects != null) {
summary = "" + objects.length + " item" + (objects.length != 1? "s" : "");
for (int i = 0; i < objects.length; i++) {
totalBytes += objects[i].getContentLength();
}
if (totalBytes > 0) {
summary += ", " + byteFormatter.formatByteSize(totalBytes);
}
summary += " @ " + timeSDF.format(new Date());
if (isObjectFilteringActive()) {
summary += " - Filtered";
}
if (isIncompleteListing) {
summary += " - Incomplete";
}
summary += "";
}
objectsSummaryLabel.setText(summary);
} catch (Throwable t) {
String message = "Unable to update object list summary";
log.error(message, t);
ErrorDialog.showDialog(ownerFrame, this, message, t);
}
}
/**
* Displays bucket-specific actions in a popup menu.
* @param invoker the component near which the popup menu will be displayed
* @param xPos the mouse's horizontal co-ordinate when the popup menu was invoked
* @param yPos the mouse's vertical co-ordinate when the popup menu was invoked
*/
private void showBucketPopupMenu(JComponent invoker, int xPos, int yPos) {
if (s3ServiceMulti == null) {
return;
}
bucketActionMenu.show(invoker, xPos, yPos);
}
/**
* Displays object-specific actions in a popup menu.
* @param invoker the component near which the popup menu will be displayed
* @param xPos the mouse's horizontal co-ordinate when the popup menu was invoked
* @param yPos the mouse's vertical co-ordinate when the popup menu was invoked
*/
private void showObjectPopupMenu(JComponent invoker, int xPos, int yPos) {
if (currentSelectedBucket == null || getSelectedObjects().length == 0) {
return;
}
objectActionMenu.show(invoker, xPos, yPos);
}
/**
* Action to create a new bucket in S3 after prompting the user for a bucket name.
*
*/
private void createBucketAction() {
String proposedNewName =
s3ServiceMulti.getAWSCredentials().getAccessKey().toLowerCase()
+ "." + "bucket-name";
CreateBucketDialog dialog = new CreateBucketDialog(proposedNewName, ownerFrame, this);
dialog.setVisible(true);
if (!dialog.getOkClicked()) {
return;
}
final S3Bucket newBucket = new S3Bucket(dialog.getBucketName(), dialog.getBucketLocation());
dialog.dispose();
runInBackgroundThread(new Runnable() {
public void run() {
if (s3ServiceMulti.createBuckets(new S3Bucket[] { newBucket })) {
int modelIndex = bucketTableModel.getBucketIndexByName(newBucket.getName());
int viewIndex = bucketTableModelSorter.viewIndex(modelIndex);
bucketsTable.setRowSelectionInterval(viewIndex, viewIndex);
}
}
});
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a CreateBucketsEvent
.
*
* When a bucket is successfully created it is added to the listing of buckets.
*
* @param event
*/
public void s3ServiceEventPerformed(final CreateBucketsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
startProgressDialog(
"Creating " + event.getThreadWatcher().getThreadCount() + " buckets",
"", 0, (int) event.getThreadWatcher().getThreadCount(),
"Cancel bucket creation", event.getThreadWatcher().getCancelEventListener());
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
for (int i = 0; i < event.getCreatedBuckets().length; i++) {
bucketTableModel.addBucket(event.getCreatedBuckets()[i], false);
}
}
});
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Created " + progressStatus.getCompletedThreads() + " buckets of " + progressStatus.getThreadCount();
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to create a bucket";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, message, event.getErrorCause());
}
}
/**
* Deletes the bucket currently selected in the gui.
*
*/
private void deleteSelectedBucket() {
if (currentSelectedBucket == null) {
log.warn("Ignoring delete bucket command, no currently selected bucket");
return;
}
int response = JOptionPane.showConfirmDialog(ownerFrame,
"Are you sure you want to delete '" + currentSelectedBucket.getName() + "'?",
"Delete Bucket?", JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.NO_OPTION) {
return;
}
try {
s3ServiceMulti.getS3Service().deleteBucket(currentSelectedBucket.getName());
bucketTableModel.removeBucket(currentSelectedBucket);
currentSelectedBucket = null;
} catch (Exception e) {
String message = "Unable to delete bucket";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
/**
* Adds a bucket not owned by the current S3 user to the bucket listing, after
* prompting the user for the name of the bucket to add.
* To be added in this way, the third-party bucket must be publicly available.
*
*/
private void addThirdPartyBucket() {
try {
String bucketName = (String) JOptionPane.showInputDialog(ownerFrame,
"Name for third-party bucket:",
"Add a third-party bucket", JOptionPane.QUESTION_MESSAGE);
if (bucketName != null) {
if (s3ServiceMulti.getS3Service().isBucketAccessible(bucketName)) {
S3Bucket thirdPartyBucket = new S3Bucket(bucketName);
bucketTableModel.addBucket(thirdPartyBucket, false);
} else {
String message = "Unable to access third-party bucket: " + bucketName;
log.error(message);
ErrorDialog.showDialog(ownerFrame, this, message, null);
}
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
String message = "Unable to access third-party bucket";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
/**
* Updates the ACL settings for the currently selected bucket.
*/
private void updateBucketAccessControlList() {
try {
AccessControlList bucketACL = s3ServiceMulti.getS3Service().getBucketAcl(currentSelectedBucket);
AccessControlList updatedBucketACL = AccessControlDialog.showDialog(
ownerFrame, new S3Bucket[] {currentSelectedBucket}, bucketACL, this);
if (updatedBucketACL != null) {
currentSelectedBucket.setAcl(updatedBucketACL);
s3ServiceMulti.getS3Service().putBucketAcl(currentSelectedBucket);
}
} catch (Exception e) {
String message = "Unable to update bucket's Access Control List";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
/**
* Updates the ACL settings for the currently selected bucket.
*/
private void updateBucketRequesterPaysSetting() {
try {
final S3Bucket selectedBucket = currentSelectedBucket;
if (!selectedBucket.isRequesterPaysKnown()) {
selectedBucket.setRequesterPays(
s3ServiceMulti.getS3Service().isRequesterPaysBucket(
selectedBucket.getName()));
}
boolean originalRequesterPaysFlag = selectedBucket.isRequesterPays();
RequesterPaysDialog dialog = new RequesterPaysDialog(selectedBucket, ownerFrame, this);
dialog.setVisible(true);
if (!dialog.getOkClicked()) {
return;
}
final boolean newRequesterPaysFlag = dialog.isRequesterPaysSelected();
dialog.dispose();
if (newRequesterPaysFlag != originalRequesterPaysFlag) {
runInBackgroundThread(new Runnable() {
public void run() {
try {
s3ServiceMulti.getS3Service().setRequesterPaysBucket(
selectedBucket.getName(), newRequesterPaysFlag);
selectedBucket.setRequesterPays(newRequesterPaysFlag);
} catch (final Exception e) {
String message = "Unable to update Requester Pays status";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, null, message, e);
}
}
});
}
} catch (Exception e) {
String message = "Unable to update bucket's Access Control List";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
/**
* @return the set of objects currently selected in the gui, or an empty array if none are selected.
*/
private S3Object[] getSelectedObjects() {
int viewRows[] = objectsTable.getSelectedRows();
if (viewRows.length == 0) {
return new S3Object[] {};
} else {
S3Object objects[] = new S3Object[viewRows.length];
for (int i = 0; i < viewRows.length; i++) {
int modelRow = objectTableModelSorter.modelIndex(viewRows[i]);
objects[i] = objectTableModel.getObject(modelRow);
}
return objects;
}
}
private void displayAclModificationDialog() {
final HyperlinkActivatedListener hyperlinkListener = this;
final S3Bucket selectedBucket = currentSelectedBucket;
runInBackgroundThread(new Runnable() {
public void run() {
final S3Object[] selectedObjects = getSelectedObjects();
boolean aclLookupSucceeded = s3ServiceMulti.getObjectACLs(
selectedBucket, selectedObjects);
if (!aclLookupSucceeded) {
return;
}
final AccessControlList[] updatedObjectACL = new AccessControlList[] {null};
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
// Build merged ACL containing ALL relevant permissions
AccessControlList mergedACL = new AccessControlList();
for (int i = 0; i < selectedObjects.length; i++) {
AccessControlList objectACL = selectedObjects[i].getAcl();
mergedACL.grantAllPermissions(objectACL.getGrants());
// BEWARE! Here we assume that all the objects have the same owner...
if (mergedACL.getOwner() == null) {
mergedACL.setOwner(objectACL.getOwner());
}
}
// Show ACL dialog box for user to change ACL settings for all objects.
updatedObjectACL[0] = AccessControlDialog.showDialog(
ownerFrame, selectedObjects, mergedACL, hyperlinkListener);
}
});
if (updatedObjectACL[0] != null) {
// Update ACLs for each object.
for (int i = 0; i < selectedObjects.length; i++) {
selectedObjects[i].setAcl(updatedObjectACL[0]);
}
// Perform ACL updates.
s3ServiceMulti.putACLs(selectedBucket, selectedObjects);
}
}
});
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a LookupACLEvent
.
*
* The ACL details are retrieved for the currently selected objects in the gui, then the
* {@link AccessControlDialog} is displayed to allow the user to update the ACL settings
* for these objects.
*
* @param event
*/
public void s3ServiceEventPerformed(LookupACLEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
startProgressDialog(
"Retrieved 0 of " + event.getThreadWatcher().getThreadCount() + " ACLs",
"", 0, (int) event.getThreadWatcher().getThreadCount(), "Cancel Lookup",
event.getThreadWatcher().getCancelEventListener());
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Retrieved " + progressStatus.getCompletedThreads() + " of " + progressStatus.getThreadCount() + " ACLs";
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to lookup Access Control list for objects";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, message, event.getErrorCause());
}
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a UpdateACLEvent
.
*
* This method merely updates the progress dialog as ACLs are updated.
*
* @param event
*/
public void s3ServiceEventPerformed(UpdateACLEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
startProgressDialog(
"Updated 0 of " + event.getThreadWatcher().getThreadCount() + " ACLs",
"", 0, (int) event.getThreadWatcher().getThreadCount(), "Cancel Update",
event.getThreadWatcher().getCancelEventListener());
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Updated " + progressStatus.getCompletedThreads() + " of " + progressStatus.getThreadCount() + " ACLs";
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to update Access Control Lists";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, message, event.getErrorCause());
}
}
/**
* Downloads the objects currently selected in the objects table. The user is
* prompted
* Prepares to perform a download of objects from S3 by prompting the user for a directory
* to store the files in, then performing the download.
*
* @throws IOException
*/
private void downloadSelectedObjects() {
// Prompt user to choose directory location for downloaded files (or cancel download altogether)
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Choose directory to save S3 files in");
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fileChooser.setMultiSelectionEnabled(false);
fileChooser.setSelectedFile(downloadDirectory);
int returnVal = fileChooser.showDialog(ownerFrame, "Choose Directory");
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
downloadDirectory = fileChooser.getSelectedFile();
// Find clashing files
final Map filesAlreadyInDownloadDirectoryMap = new HashMap();
S3Object[] objectsForDownload = getSelectedObjects();
for (int i = 0; i < objectsForDownload.length; i++) {
File file = new File(downloadDirectory,
objectsForDownload[i].getKey());
if (file.exists()) {
filesAlreadyInDownloadDirectoryMap.put(
objectsForDownload[i].getKey(), file);
}
}
// Build map of S3 Objects being downloaded.
final Map s3DownloadObjectsMap = FileComparer.getInstance()
.populateS3ObjectMap("", objectsForDownload);
final HyperlinkActivatedListener hyperlinkListener = this;
runInBackgroundThread(new Runnable() {
public void run() {
// Retrieve details of objects for download
if (!retrieveObjectsDetails(getSelectedObjects())) {
return;
}
try {
final FileComparerResults comparisonResults = compareRemoteAndLocalFiles(
filesAlreadyInDownloadDirectoryMap,
s3DownloadObjectsMap);
DownloadPackage[] downloadPackages =
buildDownloadPackageList(comparisonResults, s3DownloadObjectsMap);
if (downloadPackages == null) {
return;
}
s3ServiceMulti.downloadObjects(currentSelectedBucket, downloadPackages);
} catch (final Exception e) {
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
String message = "Unable to download objects";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame,
hyperlinkListener, message, e);
}
});
}
}
});
}
private void uploadFiles(File[] uploadFiles) {
// Fail if encryption is turned on but no password is available.
if (cockpitPreferences.isUploadEncryptionActive()
&& !cockpitPreferences.isEncryptionPasswordSet())
{
ErrorDialog.showDialog(ownerFrame, this,
"Upload encryption is enabled but you have not yet set a password in the Encryption Preferences.",
null);
return;
}
try {
// Build map of files proposed for upload.
final Map filesForUploadMap = FileComparer.getInstance()
.buildFileMap(uploadFiles, false);
// Build map of objects already existing in target S3 bucket with keys
// matching the proposed upload keys.
List objectsWithExistingKeys = new ArrayList();
S3Object[] existingObjects = objectTableModel.getObjects();
for (int i = 0; i < existingObjects.length; i++) {
if (filesForUploadMap.keySet().contains(existingObjects[i].getKey()))
{
objectsWithExistingKeys.add(existingObjects[i]);
}
}
existingObjects = (S3Object[]) objectsWithExistingKeys
.toArray(new S3Object[objectsWithExistingKeys.size()]);
final Map s3ExistingObjectsMap = FileComparer.getInstance()
.populateS3ObjectMap("", existingObjects);
final HyperlinkActivatedListener hyperlinkListener = this;
final S3Object[] clashingObjects = existingObjects;
runInBackgroundThread(new Runnable() {
public void run() {
if (clashingObjects.length > 0) {
// Retrieve details of potential clashes
if (!retrieveObjectsDetails(clashingObjects)) {
return;
}
}
try {
FileComparerResults comparisonResults =
compareRemoteAndLocalFiles(filesForUploadMap, s3ExistingObjectsMap);
S3Object[] uploadObjects = buildUploadObjectsList(
comparisonResults, filesForUploadMap);
if (uploadObjects == null) {
return;
}
// Upload the files.
s3ServiceMulti.putObjects(currentSelectedBucket, uploadObjects);
} catch (final Exception e) {
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
String message = "Unable to upload objects";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, hyperlinkListener, message, e);
}
});
}
}
});
} catch (Exception e) {
String message = "Unable to upload objects";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
private FileComparerResults compareRemoteAndLocalFiles(final Map localFilesMap, final Map s3ObjectsMap)
throws Exception
{
try {
// Compare objects being downloaded and existing local files.
final String statusText =
"Comparing " + s3ObjectsMap.size() + " object" + (s3ObjectsMap.size() > 1 ? "s" : "") +
" in S3 with " + localFilesMap.size() + " local file" + (localFilesMap.size() > 1 ? "s" : "");
startProgressDialog(statusText, "", 0, 100, null, null);
// Calculate total files size.
File[] files = (File[]) localFilesMap.values().toArray(new File[localFilesMap.size()]);
final long filesSizeTotal[] = new long[1];
for (int i = 0; i < files.length; i++) {
filesSizeTotal[0] += files[i].length();
}
// Monitor generation of MD5 hash, and provide feedback via the progress bar.
BytesProgressWatcher progressWatcher = new BytesProgressWatcher(filesSizeTotal[0]) {
public void updateBytesTransferred(long byteCount) {
super.updateBytesTransferred(byteCount);
String detailsText = formatBytesProgressWatcherDetails(this, true);
int progressValue = (int)((double)getBytesTransferred() * 100 / getBytesToTransfer());
updateProgressDialog(statusText, detailsText, progressValue);
}
};
FileComparerResults comparisonResults = FileComparer.getInstance()
.buildDiscrepancyLists(localFilesMap, s3ObjectsMap, progressWatcher);
stopProgressDialog();
return comparisonResults;
} finally {
stopProgressDialog();
}
}
/**
* Performs the real work of downloading files by comparing the download candidates against
* existing files, prompting the user whether to overwrite any pre-existing file versions,
* and starting {@link S3ServiceMulti#downloadObjects} where the real work is done.
*
*/
private DownloadPackage[] buildDownloadPackageList(FileComparerResults comparisonResults,
Map s3DownloadObjectsMap) throws Exception
{
// Determine which files to download, prompting user whether to over-write existing files
List objectKeysForDownload = new ArrayList();
objectKeysForDownload.addAll(comparisonResults.onlyOnServerKeys);
int newFiles = comparisonResults.onlyOnServerKeys.size();
int unchangedFiles = comparisonResults.alreadySynchronisedKeys.size();
int changedFiles = comparisonResults.updatedOnClientKeys.size()
+ comparisonResults.updatedOnServerKeys.size();
if (unchangedFiles > 0 || changedFiles > 0) {
// Ask user whether to replace existing unchanged and/or existing changed files.
log.debug("Files for download clash with existing local files, prompting user to choose which files to replace");
List options = new ArrayList();
String message = "Of the " + (newFiles + unchangedFiles + changedFiles)
+ " objects being downloaded:\n\n";
if (newFiles > 0) {
message += newFiles + " files are new.\n\n";
options.add(DOWNLOAD_NEW_FILES_ONLY);
}
if (changedFiles > 0) {
message += changedFiles + " files have changed.\n\n";
options.add(DOWNLOAD_NEW_AND_CHANGED_FILES);
}
if (unchangedFiles > 0) {
message += unchangedFiles + " files already exist and are unchanged.\n\n";
options.add(DOWNLOAD_ALL_FILES);
}
message += "Please choose which files you wish to download:";
Object response = JOptionPane.showInputDialog(
ownerFrame, message, "Replace files?", JOptionPane.QUESTION_MESSAGE,
null, options.toArray(), DOWNLOAD_NEW_AND_CHANGED_FILES);
if (response == null) {
return null;
}
if (DOWNLOAD_NEW_FILES_ONLY.equals(response)) {
// No change required to default objectKeysForDownload list.
} else if (DOWNLOAD_ALL_FILES.equals(response)) {
objectKeysForDownload.addAll(comparisonResults.updatedOnClientKeys);
objectKeysForDownload.addAll(comparisonResults.updatedOnServerKeys);
objectKeysForDownload.addAll(comparisonResults.alreadySynchronisedKeys);
} else if (DOWNLOAD_NEW_AND_CHANGED_FILES.equals(response)) {
objectKeysForDownload.addAll(comparisonResults.updatedOnClientKeys);
objectKeysForDownload.addAll(comparisonResults.updatedOnServerKeys);
} else {
// Download cancelled.
return null;
}
}
log.debug("Downloading " + objectKeysForDownload.size() + " objects");
if (objectKeysForDownload.size() == 0) {
return null;
}
// Create array of objects for download.
S3Object[] objects = new S3Object[objectKeysForDownload.size()];
int objectIndex = 0;
for (Iterator iter = objectKeysForDownload.iterator(); iter.hasNext();) {
objects[objectIndex++] = (S3Object) s3DownloadObjectsMap.get(iter.next());
}
Map downloadObjectsToFileMap = new HashMap();
ArrayList downloadPackageList = new ArrayList();
// Setup files to write to, creating parent directories when necessary.
for (int i = 0; i < objects.length; i++) {
File file = new File(downloadDirectory, objects[i].getKey());
// Encryption password must be null if no password is set.
String encryptionPassword = null;
if (cockpitPreferences.isEncryptionPasswordSet()) {
encryptionPassword = cockpitPreferences.getEncryptionPassword();
}
// Create local directories corresponding to objects flagged as dirs.
if (Mimetypes.MIMETYPE_JETS3T_DIRECTORY.equals(objects[i].getContentType())) {
file.mkdirs();
}
DownloadPackage downloadPackage = ObjectUtils
.createPackageForDownload(objects[i], file, true, true, encryptionPassword);
if (downloadPackage != null) {
downloadObjectsToFileMap.put(objects[i].getKey(), file);
downloadPackageList.add(downloadPackage);
}
}
return (DownloadPackage[]) downloadPackageList
.toArray(new DownloadPackage[downloadPackageList.size()]);
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a DownloadObjectsEvent
.
*
* This method merely updates the progress dialog as objects are downloaded.
*
* @param event
*/
public void s3ServiceEventPerformed(DownloadObjectsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
ThreadWatcher watcher = event.getThreadWatcher();
// Show percentage of bytes transferred, if this info is available.
if (watcher.isBytesTransferredInfoAvailable()) {
startProgressDialog("Downloaded " +
watcher.getCompletedThreads() + "/" + watcher.getThreadCount() + " - " +
byteFormatter.formatByteSize(watcher.getBytesTransferred())
+ " of " + byteFormatter.formatByteSize(watcher.getBytesTotal()),
"", 0, 100, "Cancel Download",
watcher.getCancelEventListener());
// ... otherwise just show the number of completed threads.
} else {
startProgressDialog("Downloaded " + watcher.getCompletedThreads()
+ " of " + watcher.getThreadCount() + " objects",
"", 0, (int) watcher.getThreadCount(), "Cancel Download",
watcher.getCancelEventListener());
}
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
ThreadWatcher watcher = event.getThreadWatcher();
// Show percentage of bytes transferred, if this info is available.
if (watcher.isBytesTransferredInfoAvailable()) {
String bytesCompletedStr = byteFormatter.formatByteSize(watcher.getBytesTransferred());
String bytesTotalStr = byteFormatter.formatByteSize(watcher.getBytesTotal());
String statusText = "Downloaded " +
watcher.getCompletedThreads() + "/" + watcher.getThreadCount() + " - " +
bytesCompletedStr + " of " + bytesTotalStr;
String detailsText = formatTransferDetails(watcher);
int percentage = (int)
(((double)watcher.getBytesTransferred() / watcher.getBytesTotal()) * 100);
updateProgressDialog(statusText, detailsText, percentage);
}
// ... otherwise just show the number of completed threads.
else {
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Downloaded " + progressStatus.getCompletedThreads()
+ " of " + progressStatus.getThreadCount() + " objects";
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
} else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to download objects";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, message, event.getErrorCause());
}
}
private S3Object[] buildUploadObjectsList(FileComparerResults comparisonResults,
Map uploadingFilesMap) throws Exception
{
// Determine which files to upload, prompting user whether to over-write existing files
List fileKeysForUpload = new ArrayList();
fileKeysForUpload.addAll(comparisonResults.onlyOnClientKeys);
int newFiles = comparisonResults.onlyOnClientKeys.size();
int unchangedFiles = comparisonResults.alreadySynchronisedKeys.size();
int changedFiles = comparisonResults.updatedOnClientKeys.size()
+ comparisonResults.updatedOnServerKeys.size();
if (unchangedFiles > 0 || changedFiles > 0) {
// Ask user whether to replace existing unchanged and/or existing changed files.
log.debug("Files for upload clash with existing S3 objects, prompting user to choose which files to replace");
List options = new ArrayList();
String message = "Of the " + uploadingFilesMap.size()
+ " files being uploaded:\n\n";
if (newFiles > 0) {
message += newFiles + " files are new.\n\n";
options.add(UPLOAD_NEW_FILES_ONLY);
}
if (changedFiles > 0) {
message += changedFiles + " files have changed.\n\n";
options.add(UPLOAD_NEW_AND_CHANGED_FILES);
}
if (unchangedFiles > 0) {
message += unchangedFiles + " files already exist and are unchanged.\n\n";
options.add(UPLOAD_ALL_FILES);
}
message += "Please choose which files you wish to upload:";
Object response = JOptionPane.showInputDialog(
ownerFrame, message, "Replace files?", JOptionPane.QUESTION_MESSAGE,
null, options.toArray(), UPLOAD_NEW_AND_CHANGED_FILES);
if (response == null) {
return null;
}
if (UPLOAD_NEW_FILES_ONLY.equals(response)) {
// No change required to default fileKeysForUpload list.
} else if (UPLOAD_ALL_FILES.equals(response)) {
fileKeysForUpload.addAll(comparisonResults.updatedOnClientKeys);
fileKeysForUpload.addAll(comparisonResults.updatedOnServerKeys);
fileKeysForUpload.addAll(comparisonResults.alreadySynchronisedKeys);
} else if (UPLOAD_NEW_AND_CHANGED_FILES.equals(response)) {
fileKeysForUpload.addAll(comparisonResults.updatedOnClientKeys);
fileKeysForUpload.addAll(comparisonResults.updatedOnServerKeys);
} else {
// Upload cancelled.
stopProgressDialog();
return null;
}
}
if (fileKeysForUpload.size() == 0) {
return null;
}
final String[] statusText = new String[1];
statusText[0] = "Prepared 0 of " + fileKeysForUpload.size() + " files for upload";
startProgressDialog(statusText[0], "", 0, 100, null, null);
long bytesToProcess = 0;
for (Iterator iter = fileKeysForUpload.iterator(); iter.hasNext();) {
File file = (File) uploadingFilesMap.get(iter.next().toString());
bytesToProcess += file.length() *
(cockpitPreferences.isUploadEncryptionActive() || cockpitPreferences.isUploadCompressionActive() ? 3 : 1);
}
BytesProgressWatcher progressWatcher = new BytesProgressWatcher(bytesToProcess) {
public void updateBytesTransferred(long byteCount) {
super.updateBytesTransferred(byteCount);
String detailsText = formatBytesProgressWatcherDetails(this, false);
int progressValue = (int)((double)getBytesTransferred() * 100 / getBytesToTransfer());
updateProgressDialog(statusText[0], detailsText, progressValue);
}
};
// Populate S3Objects representing upload files with metadata etc.
final S3Object[] objects = new S3Object[fileKeysForUpload.size()];
int objectIndex = 0;
for (Iterator iter = fileKeysForUpload.iterator(); iter.hasNext();) {
String fileKey = iter.next().toString();
File file = (File) uploadingFilesMap.get(fileKey);
S3Object newObject = ObjectUtils
.createObjectForUpload(fileKey, file,
(cockpitPreferences.isUploadEncryptionActive() ? encryptionUtil : null),
cockpitPreferences.isUploadCompressionActive(), progressWatcher);
String aclPreferenceString = cockpitPreferences.getUploadACLPermission();
if (CockpitPreferences.UPLOAD_ACL_PERMISSION_PRIVATE.equals(aclPreferenceString)) {
// Objects are private by default, nothing more to do.
} else if (CockpitPreferences.UPLOAD_ACL_PERMISSION_PUBLIC_READ.equals(aclPreferenceString)) {
newObject.setAcl(AccessControlList.REST_CANNED_PUBLIC_READ);
} else if (CockpitPreferences.UPLOAD_ACL_PERMISSION_PUBLIC_READ_WRITE.equals(aclPreferenceString)) {
newObject.setAcl(AccessControlList.REST_CANNED_PUBLIC_READ_WRITE);
} else {
log.warn("Ignoring unrecognised upload ACL permission setting: " + aclPreferenceString);
}
statusText[0] = "Prepared " + (objectIndex + 1)
+ " of " + fileKeysForUpload.size() + " files for upload";
objects[objectIndex++] = newObject;
}
stopProgressDialog();
return objects;
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a CreateObjectsEvent
.
*
* This method merely updates the progress dialog as files are uploaded.
*
* @param event
*/
public void s3ServiceEventPerformed(final CreateObjectsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
ThreadWatcher watcher = event.getThreadWatcher();
// Show percentage of bytes transferred, if this info is available.
if (watcher.isBytesTransferredInfoAvailable()) {
String bytesTotalStr = byteFormatter.formatByteSize(watcher.getBytesTotal());
String statusText = "Uploaded " +
watcher.getCompletedThreads() + "/" + watcher.getThreadCount() + " - " +
"0 of " + bytesTotalStr;
startProgressDialog(statusText, " ", 0, 100, "Cancel Upload",
event.getThreadWatcher().getCancelEventListener());
}
// ... otherwise show the number of completed threads.
else {
startProgressDialog(
"Uploaded 0 of " + watcher.getThreadCount() + " objects",
"", (int) watcher.getCompletedThreads(), (int) watcher.getThreadCount(),
"Cancel upload", event.getThreadWatcher().getCancelEventListener());
}
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (int i = 0; i < event.getCreatedObjects().length; i++) {
S3Object object = event.getCreatedObjects()[i];
object.setBucketName(currentSelectedBucket.getName());
objectTableModel.addObject(object);
}
if (event.getCreatedObjects().length > 0) {
updateObjectsSummary(true);
}
}
});
ThreadWatcher watcher = event.getThreadWatcher();
// Show percentage of bytes transferred, if this info is available.
if (watcher.isBytesTransferredInfoAvailable()) {
if (watcher.getBytesTransferred() >= watcher.getBytesTotal()) {
// Upload is completed, just waiting on resonse from S3.
String statusText = "Upload completed, awaiting confirmation";
updateProgressDialog(statusText, "", 100);
} else {
String bytesCompletedStr = byteFormatter.formatByteSize(watcher.getBytesTransferred());
String bytesTotalStr = byteFormatter.formatByteSize(watcher.getBytesTotal());
String statusText = "Uploaded " +
watcher.getCompletedThreads() + "/" + watcher.getThreadCount() + " - " +
bytesCompletedStr + " of " + bytesTotalStr;
int percentage = (int)
(((double)watcher.getBytesTransferred() / watcher.getBytesTotal()) * 100);
String detailsText = formatTransferDetails(watcher);
updateProgressDialog(statusText, detailsText, percentage);
}
}
// ... otherwise show the number of completed threads.
else {
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Uploaded " + progressStatus.getCompletedThreads() +
" of " + progressStatus.getThreadCount() + " objects";
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressDialog();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateObjectsSummary(false);
S3Object[] allObjects = objectTableModel.getObjects();
cachedBuckets.put(currentSelectedBucket.getName(), allObjects);
}
});
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateObjectsSummary(false);
}
});
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to upload objects";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, message, event.getErrorCause());
}
}
private void copyObjects() {
try {
final S3Object[] sourceObjects = getSelectedObjects();
CopyObjectsDialog dialog = new CopyObjectsDialog(ownerFrame,
"Copy or Move Objects", skinsFactory,
sourceObjects, bucketTableModel.getBuckets());
dialog.setVisible(true);
if (dialog.isCopyActionApproved()) {
final String currentBucketName = currentSelectedBucket.getName();
final String destinationBucketName = dialog.getDestinationBucketName();
final String[] sourceObjectKeys = dialog.getSourceObjectKeys();
final S3Object[] destinationObjects = dialog.getDestinationObjects();
final boolean isDeleteAfterCopy = dialog.isMoveOptionSelected();
final boolean retainAcls = dialog.isCopyOriginalAccessControlLists();
dialog.dispose();
if (!destinationBucketName.equals(currentBucketName)) {
cachedBuckets.remove(destinationBucketName);
}
runInBackgroundThread(new Runnable() {
public void run() {
if (retainAcls) {
// Retain ACL settings from original objects.
if (!s3ServiceMulti.getObjectACLs(
currentSelectedBucket, sourceObjects))
{
return;
}
for (int i = 0; i < sourceObjects.length; i++) {
destinationObjects[i].setAcl(
sourceObjects[i].getAcl());
}
}
// Copy objects. Metadata is retained, not replaced.
s3ServiceMulti.copyObjects(currentBucketName, destinationBucketName,
sourceObjectKeys, destinationObjects, false);
if (isDeleteAfterCopy) {
final S3Object[] sourceObjects = new S3Object[sourceObjectKeys.length];
for (int i = 0; i < sourceObjectKeys.length; i++) {
sourceObjects[i] = new S3Object(sourceObjectKeys[i]);
}
s3ServiceMulti.deleteObjects(currentSelectedBucket, sourceObjects);
}
if (destinationBucketName.equals(currentBucketName) || isDeleteAfterCopy) {
// Refesh object listing for current bucket if the bucket's contents
// have changed.
listObjects();
}
}
});
} else {
dialog.dispose();
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
stopProgressDialog();
String message = "Unable to modify objects";
log.error(message, e);
ErrorDialog.showDialog(ownerFrame, this, message, e);
}
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a CopyObjectsEvent
.
*
* This method merely updates the progress dialog as objects are copied.
*
* @param event
*/
public void s3ServiceEventPerformed(final CopyObjectsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
ThreadWatcher watcher = event.getThreadWatcher();
startProgressDialog("Copied 0 of " + watcher.getThreadCount() + " objects",
"", 0, (int) watcher.getThreadCount(),
"Cancel Copy", event.getThreadWatcher().getCancelEventListener());
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
ThreadWatcher watcher = event.getThreadWatcher();
String statusText = "Copied " + watcher.getCompletedThreads()
+ " of " + watcher.getThreadCount() + " objects";
updateProgressDialog(statusText, "", (int) watcher.getCompletedThreads());
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()
|| ServiceEvent.EVENT_CANCELLED == event.getEventCode())
{
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to copy objects";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, message, event.getErrorCause());
}
}
private void generatePublicGetUrls() {
final S3Object[] objects = getSelectedObjects();
if (objects.length < 1) {
log.warn("Ignoring Generate Public URLs object command because no objects are selected");
return;
}
SignedGetUrlDialog dialog = new SignedGetUrlDialog(ownerFrame, this,
s3ServiceMulti.getS3Service(), objects);
dialog.setVisible(true);
}
private void generateTorrentUrl() {
final S3Object[] objects = getSelectedObjects();
if (objects.length != 1) {
log.warn("Ignoring Generate Public URL object command, can only operate on a single object");
return;
}
S3Object currentObject = objects[0];
// Generate URL
String torrentUrl = S3Service.createTorrentUrl(
currentSelectedBucket.getName(), currentObject.getKey());
// Display signed URL
JOptionPane.showInputDialog(ownerFrame,
"Torrent URL for '" + currentObject.getKey() + "'.",
"Torrent URL", JOptionPane.INFORMATION_MESSAGE, null, null, torrentUrl);
}
private void deleteSelectedObjects() {
final S3Object[] objects = getSelectedObjects();
if (objects.length == 0) {
log.warn("Ignoring delete objects command, no currently selected objects");
return;
}
int response = JOptionPane.showConfirmDialog(ownerFrame,
(objects.length == 1 ?
"Are you sure you want to delete '" + objects[0].getKey() + "'?" :
"Are you sure you want to delete " + objects.length + " objects"
),
"Delete Objects?", JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.NO_OPTION) {
return;
}
runInBackgroundThread(new Runnable() {
public void run() {
s3ServiceMulti.deleteObjects(currentSelectedBucket, objects);
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
updateObjectsSummary(false);
S3Object[] allObjects = objectTableModel.getObjects();
cachedBuckets.put(currentSelectedBucket.getName(), allObjects);
}
});
}
});
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a DeleteObjectsEvent
.
*
* This method merely updates the progress dialog as objects are deleted.
*
* @param event
*/
public void s3ServiceEventPerformed(final DeleteObjectsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
startProgressDialog(
"Deleted 0 of " + event.getThreadWatcher().getThreadCount() + " objects",
"", 0, (int) event.getThreadWatcher().getThreadCount(), "Cancel Delete Objects",
event.getThreadWatcher().getCancelEventListener());
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (int i = 0; i < event.getDeletedObjects().length; i++) {
objectTableModel.removeObject(
event.getDeletedObjects()[i]);
}
if (event.getDeletedObjects().length > 0) {
updateObjectsSummary(true);
}
}
});
ThreadWatcher progressStatus = event.getThreadWatcher();
String statusText = "Deleted " + progressStatus.getCompletedThreads()
+ " of " + progressStatus.getThreadCount() + " objects";
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to delete objects";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, message, event.getErrorCause());
}
}
/**
* Retrieves details about objects including metadata etc by invoking the method
* {@link S3ServiceMulti#getObjectsHeads}.
*
* This is generally done as a prelude to some further action, such as
* displaying the objects' details or downloading the objects.
* The real action occurs in the method s3ServiceEventPerformed
for handling
* GetObjectHeadsEvent
events.
*
* @param candidateObjects
*
* @return
* true if objects details were successfully retrieved.
*/
private boolean retrieveObjectsDetails(final S3Object[] candidateObjects) {
// Identify which of the candidate objects have incomplete metadata.
ArrayList s3ObjectsIncompleteList = new ArrayList();
for (int i = 0; i < candidateObjects.length; i++) {
if (!candidateObjects[i].isMetadataComplete()) {
s3ObjectsIncompleteList.add(candidateObjects[i]);
}
}
log.debug("Of " + candidateObjects.length + " object candidates for HEAD requests "
+ s3ObjectsIncompleteList.size() + " are incomplete, performing requests for these only");
final S3Object[] incompleteObjects = (S3Object[]) s3ObjectsIncompleteList
.toArray(new S3Object[s3ObjectsIncompleteList.size()]);
return s3ServiceMulti.getObjectsHeads(currentSelectedBucket, incompleteObjects);
}
/**
* This method is an {@link S3ServiceEventListener} action method that is invoked when this
* application's S3ServiceMulti
triggers a GetObjectHeadsEvent
.
*
* This method merely updates the progress dialog as object details (heads) are retrieved.
*
* @param event
*/
public void s3ServiceEventPerformed(final GetObjectHeadsEvent event) {
if (ServiceEvent.EVENT_STARTED == event.getEventCode()) {
if (event.getThreadWatcher().getThreadCount() > 0) {
startProgressDialog("Retrieved details for 0 of "
+ event.getThreadWatcher().getThreadCount() + " objects",
"", 0, (int) event.getThreadWatcher().getThreadCount(), "Cancel Retrieval",
event.getThreadWatcher().getCancelEventListener());
}
}
else if (ServiceEvent.EVENT_IN_PROGRESS == event.getEventCode()) {
final ThreadWatcher progressStatus = event.getThreadWatcher();
// Store detail-complete objects in table.
runInDispatcherThreadImmediately(new Runnable() {
public void run() {
// Update object in table with the retrieved details.
for (int i = 0; i < event.getCompletedObjects().length; i++) {
S3Object objectWithDetails = event.getCompletedObjects()[i];
S3Object originalObject = objectTableModel.getObjectByKey(
objectWithDetails.getKey());
originalObject.replaceAllMetadata(objectWithDetails.getMetadataMap());
originalObject.setMetadataComplete(true);
log.debug("Updated table with " + originalObject.getKey()
+ ", content-type=" + originalObject.getContentType());
}
}
});
// Update progress of GetObject requests.
String statusText = "Retrieved details for " + progressStatus.getCompletedThreads()
+ " of " + progressStatus.getThreadCount() + " objects";
updateProgressDialog(statusText, "", (int) progressStatus.getCompletedThreads());
}
else if (ServiceEvent.EVENT_COMPLETED == event.getEventCode()) {
// Stop GetObjectHead progress display.
stopProgressDialog();
}
else if (ServiceEvent.EVENT_CANCELLED == event.getEventCode()) {
stopProgressDialog();
}
else if (ServiceEvent.EVENT_ERROR == event.getEventCode()) {
stopProgressDialog();
String message = "Unable to retrieve objects details";
log.error(message, event.getErrorCause());
ErrorDialog.showDialog(ownerFrame, this, message, event.getErrorCause());
}
}
private String formatTransferDetails(ThreadWatcher watcher) {
long bytesPerSecond = watcher.getBytesPerSecond();
String detailsText = byteFormatter.formatByteSize(bytesPerSecond) + "/s";
if (watcher.isTimeRemainingAvailable()) {
long secondsRemaining = watcher.getTimeRemaining();
detailsText += " - Time remaining: " + timeFormatter.formatTime(secondsRemaining);
}
return detailsText;
}
private String formatBytesProgressWatcherDetails(BytesProgressWatcher watcher, boolean includeBytes) {
long secondsRemaining = watcher.getRemainingTime();
String detailsText =
(includeBytes
? byteFormatter.formatByteSize(watcher.getBytesTransferred())
+ " of " + byteFormatter.formatByteSize(watcher.getBytesToTransfer())
+ " - "
: "")
+ "Time remaining: " +
timeFormatter.formatTime(secondsRemaining);
return detailsText;
}
/**
* Follows hyperlinks clicked on by a user. This is achieved differently depending on whether
* Cockpit is running as an applet or as a stand-alone application:
*
* - Application: Detects the default browser application for the user's system (using
* BareBonesBrowserLaunch) and opens the link as a new window in that browser
* - Applet: Opens the link in the current browser using the applet's context
*
*
* @param url
* the url to open
* @param target
* the target pane to open the url in, eg "_blank". This may be null.
*/
public void followHyperlink(URL url, String target) {
if (!isStandAloneApplication) {
if (target == null) {
getAppletContext().showDocument(url);
} else {
getAppletContext().showDocument(url, target);
}
} else {
BareBonesBrowserLaunch.openURL(url.toString());
}
}
/**
* Implementation method for the CredentialsProvider interface.
*
* Based on sample code:
* InteractiveAuthenticationExample
*
*/
public Credentials getCredentials(AuthScheme authscheme, String host, int port, boolean proxy) throws CredentialsNotAvailableException {
if (authscheme == null) {
return null;
}
try {
Credentials credentials = null;
if (authscheme instanceof NTLMScheme) {
AuthenticationDialog pwDialog = new AuthenticationDialog(
ownerFrame, "Authentication Required",
"Host " + host + ":" + port + " requires Windows authentication", true);
pwDialog.setVisible(true);
if (pwDialog.getUser().length() > 0) {
credentials = new NTCredentials(pwDialog.getUser(), pwDialog.getPassword(),
host, pwDialog.getDomain());
}
pwDialog.dispose();
} else
if (authscheme instanceof RFC2617Scheme) {
AuthenticationDialog pwDialog = new AuthenticationDialog(
ownerFrame, "Authentication Required",
"
Host " + host + ":" + port + ""
+ " requires authentication for the realm:
" + authscheme.getRealm() + " ", false);
pwDialog.setVisible(true);
if (pwDialog.getUser().length() > 0) {
credentials = new UsernamePasswordCredentials(pwDialog.getUser(), pwDialog.getPassword());
}
pwDialog.dispose();
} else {
throw new CredentialsNotAvailableException("Unsupported authentication scheme: " +
authscheme.getSchemeName());
}
return credentials;
} catch (IOException e) {
throw new CredentialsNotAvailableException(e.getMessage(), e);
}
}
private boolean isObjectFilteringActive() {
if (!filterObjectsCheckBox.isSelected()) {
return false;
} else {
String delimiter = (String) filterObjectsDelimiter.getSelectedItem();
if (filterObjectsPrefix.getText().length() > 0
|| delimiter.length() > 0)
{
return true;
} else {
return false;
}
}
}
private class ContextMenuListener extends MouseAdapter {
public void mousePressed(MouseEvent e) {
showContextMenu(e);
}
public void mouseReleased(MouseEvent e) {
showContextMenu(e);
}
private void showContextMenu(MouseEvent e) {
if (e.isPopupTrigger()) {
// Select item under context-click.
if (e.getSource() instanceof JList) {
JList jList = (JList) e.getSource();
int locIndex = jList.locationToIndex(e.getPoint());
if (locIndex >= 0) {
jList.setSelectedIndex(locIndex);
}
} else if (e.getSource() instanceof JTable) {
JTable jTable = (JTable) e.getSource();
int rowIndex = jTable.rowAtPoint(e.getPoint());
if (rowIndex >= 0) {
jTable.addRowSelectionInterval(rowIndex, rowIndex);
}
}
// Show context popup menu.
if (e.getSource().equals(bucketsTable)) {
showBucketPopupMenu((JComponent)e.getSource(), e.getX(), e.getY());
} else if (e.getSource().equals(objectsTable)) {
showObjectPopupMenu((JComponent)e.getSource(), e.getX(), e.getY());
}
}
}
}
/**
* Runs Cockpit as a stand-alone application.
* @param args
* @throws Exception
*/
public static void main(String args[]) throws Exception {
// When running on OS X, display app menu in the right place (i.e. not the app window)
System.setProperty("apple.laf.useScreenMenuBar", "true");
JFrame ownerFrame = new JFrame("JetS3t Cockpit");
ownerFrame.addWindowListener(new WindowListener() {
public void windowOpened(WindowEvent e) {
}
public void windowClosing(WindowEvent e) {
e.getWindow().dispose();
}
public void windowClosed(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowActivated(WindowEvent e) {
}
public void windowDeactivated(WindowEvent e) {
}
});
new Cockpit(ownerFrame);
}
}