/* * Copyright 2011-2012 Amazon Technologies, Inc. * * 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://aws.amazon.com/apache2.0 * * This file 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 com.amazonaws.eclipse.explorer.s3; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.layout.TreeColumnLayout; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.LocalSelectionTransfer; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ILazyTreePathContentProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DragSourceAdapter; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.FileTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.IFormColors; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.part.PluginTransfer; import org.eclipse.ui.part.PluginTransferData; import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.core.regions.Region; import com.amazonaws.eclipse.core.regions.RegionUtils; import com.amazonaws.eclipse.core.regions.ServiceAbbreviations; import com.amazonaws.eclipse.explorer.s3.actions.DeleteObjectAction; import com.amazonaws.eclipse.explorer.s3.actions.EditObjectPermissionsAction; import com.amazonaws.eclipse.explorer.s3.actions.GeneratePresignedUrlAction; import com.amazonaws.eclipse.explorer.s3.dnd.KeySelectionDialog; import com.amazonaws.eclipse.explorer.s3.dnd.S3ObjectSummaryDropAction; import com.amazonaws.eclipse.explorer.s3.dnd.UploadDropAssistant; import com.amazonaws.eclipse.explorer.s3.dnd.UploadFileJob; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.ListObjectsRequest; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.S3ObjectSummary; import com.amazonaws.services.s3.transfer.TransferManager; /** * S3 object listing with virtual directory support. */ public class S3ObjectSummaryTable extends Composite { private final static Object LOADING = new Object(); private final static Object LOADING_DONE = new Object(); private final static String DEFAULT_DELIMITER = "/"; private static final int KEY_COL = 0; private static final int ETAG_COL = 1; private static final int OWNER_COL = 2; private static final int SIZE_COL = 3; private static final int STORAGE_CLASS_COL = 4; private static final int LAST_MODIFIED_COL = 5; private final String bucketName; private final String accountId; private final String s3Endpoint; private final Map<TreePath, Object[]> children; private final TreeViewer viewer; private final Map<ImageDescriptor, Image> imageCache = new HashMap<ImageDescriptor, Image>(); private final class S3ObjectSummaryContentProvider implements // ITreePathContentProvider, ILazyTreePathContentProvider { private TreeViewer viewer; public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { this.viewer = (TreeViewer) viewer; } public void dispose() { for ( Image img : imageCache.values() ) { img.dispose(); } } /* * (non-Javadoc) * * @see * org.eclipse.jface.viewers.ITreePathContentProvider#getParents(java * .lang.Object) */ public TreePath[] getParents(Object element) { return null; } /* * (non-Javadoc) * * @see * org.eclipse.jface.viewers.ILazyTreePathContentProvider#updateElement * (org.eclipse.jface.viewers.TreePath, int) */ public void updateElement(TreePath parentPath, int index) { cacheChildren(parentPath); Object[] childNodes = children.get(parentPath); if ( index >= childNodes.length ) return; viewer.replace(parentPath, index, childNodes[index]); updateHasChildren(parentPath.createChildPath(childNodes[index])); } /* * (non-Javadoc) * * @see * org.eclipse.jface.viewers.ILazyTreePathContentProvider#updateChildCount * (org.eclipse.jface.viewers.TreePath, int) */ public void updateChildCount(TreePath treePath, int currentChildCount) { cacheChildren(treePath); Object[] objects = children.get(treePath); viewer.setChildCount(treePath, objects.length); for ( int i = 0; i < objects.length; i++ ) { if ( objects[i] instanceof IPath ) { viewer.setHasChildren(treePath.createChildPath(objects[i]), true); } } return; } /* * (non-Javadoc) * * @see * org.eclipse.jface.viewers.ILazyTreePathContentProvider#updateHasChildren * (org.eclipse.jface.viewers.TreePath) */ public void updateHasChildren(TreePath path) { viewer.setHasChildren(path, path.getSegmentCount() > 0 && path.getLastSegment() instanceof IPath); } /* * Non-virtual tree content provider implementation below */ /* * (non-Javadoc) * * @see * org.eclipse.jface.viewers.ITreePathContentProvider#getElements(java * .lang.Object) */ @SuppressWarnings("unused") public Object[] getElements(Object inputElement) { TreePath treePath = new TreePath(new Object[0]); cacheChildren(treePath); return children.get(treePath); } /* * (non-Javadoc) * * @see * org.eclipse.jface.viewers.ITreePathContentProvider#getChildren(org * .eclipse.jface.viewers.TreePath) */ @SuppressWarnings("unused") public Object[] getChildren(TreePath parentPath) { cacheChildren(parentPath); return children.get(parentPath); } /* * (non-Javadoc) * * @see * org.eclipse.jface.viewers.ITreePathContentProvider#hasChildren(org * .eclipse.jface.viewers.TreePath) */ @SuppressWarnings("unused") public boolean hasChildren(TreePath path) { return path.getLastSegment() instanceof IPath; } } private final class S3ObjectSummaryLabelProvider implements ITableLabelProvider { public void removeListener(ILabelProviderListener listener) { } public boolean isLabelProperty(Object element, String property) { return true; } public void dispose() { } public void addListener(ILabelProviderListener listener) { } public String getColumnText(Object element, int columnIndex) { if ( element == LOADING ) { return "Loading..."; } else if ( element instanceof IPath ) { if ( columnIndex == 0 ) return ((IPath) element).lastSegment(); else return ""; } S3ObjectSummary s = (S3ObjectSummary) element; switch (columnIndex) { case KEY_COL: int index = s.getKey().lastIndexOf(DEFAULT_DELIMITER); if ( index > 0 ) { return s.getKey().substring(index + 1); } else { return s.getKey(); } case ETAG_COL: return s.getETag(); case OWNER_COL: return s.getOwner().getDisplayName(); case SIZE_COL: return "" + s.getSize(); case STORAGE_CLASS_COL: return s.getStorageClass(); case LAST_MODIFIED_COL: return s.getLastModified().toString(); } return ""; } public Image getColumnImage(Object element, int columnIndex) { if ( columnIndex == 0 && element != LOADING ) { if ( element instanceof IPath ) { return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); } S3ObjectSummary s = (S3ObjectSummary) element; return getCachedImage(PlatformUI.getWorkbench().getEditorRegistry().getImageDescriptor(s.getKey())); } return null; } public Image getCachedImage(ImageDescriptor img) { if ( !imageCache.containsKey(img) ) { imageCache.put(img, img.createImage()); } return imageCache.get(img); } } public S3ObjectSummaryTable(String accountId, String bucketName, String s3Endpoint, Composite composite, FormToolkit toolkit, int style) { super(composite, style); this.accountId = accountId; this.bucketName = bucketName; this.s3Endpoint = s3Endpoint; this.children = Collections.synchronizedMap(new HashMap<TreePath, Object[]>()); GridLayout gridLayout = new GridLayout(1, false); gridLayout.marginWidth = 0; gridLayout.marginHeight = 0; setLayout(gridLayout); Composite sectionComp = toolkit.createComposite(this, SWT.None); sectionComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); sectionComp.setLayout(new GridLayout(1, false)); Composite headingComp = toolkit.createComposite(sectionComp, SWT.None); headingComp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); headingComp.setLayout(new GridLayout()); Label label = toolkit.createLabel(headingComp, "Object listing"); label.setFont(JFaceResources.getHeaderFont()); label.setForeground(toolkit.getColors().getColor(IFormColors.TITLE)); Composite tableHolder = toolkit.createComposite(sectionComp, SWT.None); tableHolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); FillLayout layout = new FillLayout(); layout.marginHeight = 0; layout.marginWidth = 10; layout.type = SWT.VERTICAL; tableHolder.setLayout(layout); Composite tableComp = toolkit.createComposite(tableHolder, SWT.None); TreeColumnLayout tableColumnLayout = new TreeColumnLayout(); tableComp.setLayout(tableColumnLayout); viewer = new TreeViewer(tableComp, SWT.BORDER | SWT.VIRTUAL | SWT.MULTI); viewer.getTree().setLinesVisible(true); viewer.getTree().setHeaderVisible(true); viewer.setUseHashlookup(true); viewer.setLabelProvider(new S3ObjectSummaryLabelProvider()); viewer.setContentProvider(new S3ObjectSummaryContentProvider()); Tree tree = viewer.getTree(); createColumns(tableColumnLayout, tree); initializeDragAndDrop(); final TreePath rootPath = new TreePath(new Object[0]); final Thread t = cacheChildren(rootPath); viewer.setInput(LOADING); new Thread() { @Override public void run() { try { t.join(); } catch ( InterruptedException e1 ) { return; } Display.getDefault().syncExec(new Runnable() { public void run() { // Preserve the current column widths int[] colWidth = new int[viewer.getTree().getColumns().length]; int i = 0; for ( TreeColumn col : viewer.getTree().getColumns() ) { colWidth[i++] = col.getWidth(); } viewer.setInput(LOADING_DONE); i = 0; for ( TreeColumn col : viewer.getTree().getColumns() ) { col.setWidth(colWidth[i++]); } } }); } }.start(); hookContextMenu(); } protected void initializeDragAndDrop() { DragSource dragSource = new DragSource(viewer.getTree(), DND.DROP_COPY | DND.DROP_DEFAULT | DND.DROP_MOVE); dragSource.setTransfer(new Transfer[] { PluginTransfer.getInstance() }); dragSource.addDragListener(new DragSourceAdapter() { @Override public void dragStart(DragSourceEvent event) { event.doit = false; ISelection selection = viewer.getSelection(); if ( selection instanceof IStructuredSelection ) { if ( ((IStructuredSelection) selection).size() == 1 && ((IStructuredSelection) selection).getFirstElement() instanceof S3ObjectSummary ) { event.doit = true; } } } @Override public void dragSetData(DragSourceEvent event) { Object o = ((ITreeSelection) viewer.getSelection()).getFirstElement(); if ( o instanceof S3ObjectSummary ) { S3ObjectSummary s = (S3ObjectSummary) o; event.data = new PluginTransferData(S3ObjectSummaryDropAction.ID, S3ObjectSummaryDropAction .encode(s)); } else { event.doit = false; } } }); DropTarget dropTarget = new DropTarget(viewer.getTree(), DND.DROP_COPY | DND.DROP_DEFAULT | DND.DROP_MOVE); dropTarget.setTransfer(new Transfer[] { LocalSelectionTransfer.getTransfer(), FileTransfer.getInstance() }); dropTarget.addDropListener(new DropTargetAdapter() { @Override public void drop(DropTargetEvent event) { File f = UploadDropAssistant.getFileToDrop(event.currentDataType); String prefix = ""; if ( event.item instanceof TreeItem ) { TreeItem item = (TreeItem) event.item; if ( item.getData() instanceof IPath ) { IPath path = (IPath) item.getData(); prefix = path.toString(); } } KeySelectionDialog dialog = new KeySelectionDialog(viewer.getTree().getDisplay().getActiveShell(), prefix, f); if ( dialog.open() != 0 ) { return; } final String keyName = dialog.getKeyName(); final TransferManager transferManager = new TransferManager(getS3Client()); UploadFileJob uploadFileJob = new UploadFileJob("Uploading " + f.getAbsolutePath().toString(), bucketName, f, keyName, transferManager); uploadFileJob.setRefreshRunnable(new Runnable() { public void run() { refresh(null); } }); uploadFileJob.schedule(); } }); } public synchronized AmazonS3 getS3Client() { return AwsToolkitCore.getClientFactory(accountId).getS3ClientByEndpoint(s3Endpoint); } protected void createColumns(TreeColumnLayout tableColumnLayout, Tree tree) { TreeColumn column = new TreeColumn(tree, SWT.NONE); column.setText("Key"); ColumnWeightData cwd_column = new ColumnWeightData(30); cwd_column.minimumWidth = 1; tableColumnLayout.setColumnData(column, cwd_column); TreeColumn column_1 = new TreeColumn(tree, SWT.NONE); column_1.setMoveable(true); column_1.setText("E-tag"); tableColumnLayout.setColumnData(column_1, new ColumnWeightData(15)); TreeColumn column_2 = new TreeColumn(tree, SWT.NONE); column_2.setMoveable(true); column_2.setText("Owner"); tableColumnLayout.setColumnData(column_2, new ColumnWeightData(15)); TreeColumn column_3 = new TreeColumn(tree, SWT.NONE); column_3.setMoveable(true); column_3.setText("Size"); tableColumnLayout.setColumnData(column_3, new ColumnWeightData(15)); TreeColumn column_4 = new TreeColumn(tree, SWT.NONE); column_4.setMoveable(true); column_4.setText("Storage class"); tableColumnLayout.setColumnData(column_4, new ColumnWeightData(10)); TreeColumn column_5 = new TreeColumn(tree, SWT.NONE); column_5.setMoveable(true); column_5.setText("Last modified"); tableColumnLayout.setColumnData(column_5, new ColumnWeightData(15)); } /** * Fills in the children for the tree path given, which must either be empty * or end in a File object. */ protected Thread cacheChildren(final TreePath treePath) { if ( children.containsKey(treePath) ) return null; children.put(treePath, new Object[] { LOADING }); Thread thread = new Thread() { @Override public void run() { String prefix; if ( treePath.getSegmentCount() == 0 ) { prefix = ""; } else { prefix = ((IPath) treePath.getLastSegment()).toString(); } List<S3ObjectSummary> filteredObjectSummaries = new LinkedList<S3ObjectSummary>(); List<String> filteredCommonPrefixes = new LinkedList<String>(); ObjectListing listObjectsResponse = null; AmazonS3 s3 = getS3Client(); do { if (listObjectsResponse == null) { ListObjectsRequest listObjectsRequest = new ListObjectsRequest() .withBucketName(S3ObjectSummaryTable.this.bucketName) .withDelimiter(DEFAULT_DELIMITER) .withPrefix(prefix); listObjectsResponse = s3.listObjects(listObjectsRequest); } else { listObjectsResponse = s3.listNextBatchOfObjects(listObjectsResponse); } for ( S3ObjectSummary s : listObjectsResponse.getObjectSummaries() ) { if ( !s.getKey().equals(prefix) ) { filteredObjectSummaries.add(s); } } filteredCommonPrefixes.addAll(listObjectsResponse.getCommonPrefixes()); } while (listObjectsResponse.isTruncated()); final Object[] objects = new Object[filteredObjectSummaries.size() + filteredCommonPrefixes.size()]; filteredObjectSummaries.toArray(objects); int i = filteredObjectSummaries.size(); for ( String commonPrefix : filteredCommonPrefixes ) { objects[i++] = new Path(commonPrefix); } children.put(treePath, objects); viewer.getTree().getDisplay().syncExec(new Runnable() { public void run() { if ( treePath.getSegmentCount() == 0 ) viewer.setChildCount(treePath, objects.length); viewer.refresh(); } }); } }; thread.start(); return thread; } /** * Hooks a context menu for the table control. */ private void hookContextMenu() { MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { manager.add(new DeleteObjectAction(S3ObjectSummaryTable.this)); manager.add(new Separator()); manager.add(new EditObjectPermissionsAction(S3ObjectSummaryTable.this)); manager.add(new Separator()); manager.add(new GeneratePresignedUrlAction(S3ObjectSummaryTable.this)); } }); Menu menu = menuMgr.createContextMenu(viewer.getControl()); viewer.getControl().setMenu(menu); menuMgr.createContextMenu(this); } /** * Returns all selected S3 summaries in the table. */ public Collection<S3ObjectSummary> getSelectedObjects() { IStructuredSelection s = (IStructuredSelection) viewer.getSelection(); List<S3ObjectSummary> summaries = new LinkedList<S3ObjectSummary>(); Iterator<?> iter = s.iterator(); while ( iter.hasNext() ) { Object next = iter.next(); if ( next instanceof S3ObjectSummary ) summaries.add((S3ObjectSummary) next); } return summaries; } /** * Refreshes the table, optionally at at given root. */ public void refresh(String prefix) { if ( prefix == null ) { children.clear(); viewer.refresh(); } else { List<IPath> paths = new LinkedList<IPath>(); IPath p = new Path(""); for ( String dir : prefix.split("/") ) { p = p.append(dir + "/"); paths.add(p); } TreePath treePath = new TreePath(paths.toArray()); children.remove(treePath); viewer.refresh(new Path(prefix)); } } }