Created
January 3, 2020 15:10
-
-
Save winder/73a46de22ccb89a34ab85b46830848e2 to your computer and use it in GitHub Desktop.
AWS S3 JFileChooser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public final class Example { | |
final static String access = "access-id-here"; | |
final static String secret = "secret-key-here"; | |
public static void main(String[] args) throws Exception { | |
S3FileSystemView viewer = new S3FileSystemView(access, secret); | |
JFileChooser chooser = new JFileChooser("s3:/", viewer); | |
int returnVal = chooser.showOpenDialog(new JFrame()); | |
if (returnVal == JFileChooser.APPROVE_OPTION) { | |
File f = chooser.getSelectedFile(); | |
System.out.println("Found a file! It is " + f); | |
} else { | |
System.out.println("No file selected..."); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
MIT License | |
Copyright (c) 2020 Will Winder | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
*/ | |
import io.minio.MinioClient; | |
import io.minio.Result; | |
import io.minio.messages.Bucket; | |
import io.minio.messages.Item; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import javax.swing.Icon; | |
import javax.swing.filechooser.FileSystemView; | |
import org.openide.util.Exceptions; | |
/** | |
* | |
* @author Will Winder | |
*/ | |
public class S3FileSystemView extends FileSystemView { | |
// Must match "s3://<bucket>" or "s3://<bucket>/" | |
// Must match "s3:/<bucket>" or "s3:/<bucket>/" | |
final static Pattern parseURI = Pattern.compile("^s3://?(?<bucket>[^/]+)/?(?<path>.*)$"); | |
private static boolean hasBucket(File f) { | |
return parseURI(f).matches(); | |
} | |
private static Matcher parseURI(File f) { | |
String path = f.toString(); | |
return parseURI.matcher(path); | |
} | |
final String id; | |
final String secret; | |
final MinioClient minioClient; | |
public S3FileSystemView(final String id, final String secret) { | |
this.id = id; | |
this.secret = secret; | |
try { | |
minioClient = new MinioClient("https://s3.amazonaws.com", id, secret); | |
} catch (Exception ex) { | |
throw new RuntimeException("Unable to create S3 Client."); | |
} | |
} | |
@Override | |
protected File createFileSystemRoot(File f) { | |
return new VirtualFile(f, 1); | |
} | |
@Override | |
public boolean isComputerNode(File dir) { | |
return false; | |
} | |
@Override | |
public boolean isFloppyDrive(File dir) { | |
return false; | |
} | |
@Override | |
public boolean isDrive(File dir) { | |
return false; | |
} | |
@Override | |
public Icon getSystemIcon(File f) { | |
return null; | |
} | |
@Override | |
public String getSystemTypeDescription(File f) { | |
return f.toPath().toString(); | |
} | |
@Override | |
public String getSystemDisplayName(File f) { | |
return f.getName(); | |
} | |
@Override | |
public File getParentDirectory(final File dir) { | |
return dir.getParentFile(); | |
} | |
@Override | |
public File[] getFiles(final File dir, boolean useFileHiding) { | |
// If there is no bucket (i.e. just "s3:/"), add the buckets. | |
if (!hasBucket(dir)) { | |
try { | |
final List<File> files = new ArrayList<>(1); | |
List<Bucket> bucketList = minioClient.listBuckets(); | |
for (Bucket bucket : bucketList) { | |
System.out.println(bucket.creationDate() + ", " + bucket.name()); | |
files.add(new VirtualFile("s3:/" + bucket.name() + "/", 0)); | |
} | |
return files.toArray(new File[files.size()]); | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
} | |
return new VirtualFile[0]; | |
} | |
ArrayList<VirtualFile> ret = new ArrayList<>(); | |
Matcher m = parseURI(dir); | |
try { | |
if (m.matches()) { | |
String bucket = m.group("bucket"); | |
// Path should start with a slash unless it's empty | |
String path = m.group("path"); | |
if (! "".equals(path)) { | |
path += "/"; | |
} | |
Iterable<Result<Item>> objects = minioClient.listObjects(bucket, path, false); | |
String prefix = "s3:/" + bucket + "/"; | |
String dirMatch = dir.toString() + "/"; | |
for (Result<Item> res : objects) { | |
Item i = res.get(); | |
String name = prefix + i.objectName(); | |
// listObjects matches the current directory, filter it out. | |
if (name.equals(dirMatch)) { | |
continue; | |
} | |
VirtualFile f = new VirtualFile(name, i.objectSize()); | |
if (!f.isDir) { | |
f.setLastModified(i.lastModified().getTime()); | |
} | |
ret.add(f); | |
} | |
} | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
} | |
return ret.toArray(new VirtualFile[0]); | |
} | |
@Override | |
public File createFileObject(final String path) { | |
return new VirtualFile(path, 1); | |
} | |
@Override | |
public File createFileObject(final File dir, final String filename) { | |
throw new UnsupportedOperationException("Sorry, no support for creating files in S3."); | |
} | |
@Override | |
public File getDefaultDirectory() { | |
return new VirtualFile("s3:/", 1); | |
} | |
@Override | |
public File getHomeDirectory() { | |
return getDefaultDirectory(); | |
} | |
@Override | |
public File[] getRoots() { | |
final List<File> files = new ArrayList<>(1); | |
files.add(new VirtualFile("s3:/", 1)); | |
return files.toArray(new VirtualFile[0]); | |
} | |
@Override | |
public boolean isFileSystemRoot(final File dir) { | |
return !hasBucket(dir); | |
} | |
@Override | |
public boolean isHiddenFile(final File f) { | |
return false; | |
} | |
@Override | |
public boolean isFileSystem(final File f) { | |
return !isFileSystemRoot(f); | |
} | |
@Override | |
public File getChild(final File parent, final String fileName) { | |
throw new UnsupportedOperationException("Not sure when this would make sense. Call getFiles instead."); | |
} | |
@Override | |
public boolean isParent(final File folder, final File file) { | |
return file.toPath().getParent().equals(folder.toPath()); | |
} | |
@Override | |
public Boolean isTraversable(final File f) { | |
return f.isDirectory(); | |
} | |
@Override | |
public boolean isRoot(final File f) { | |
// Root should just be "s3:/" or "s3:", so check that there is no bucket. | |
boolean hasBucket = hasBucket(f); | |
return hasBucket == false; | |
} | |
@Override | |
public File createNewFolder(final File containingDir) throws IOException { | |
throw new UnsupportedOperationException("Sorry, no support for editing S3."); | |
} | |
private class VirtualFile extends File { | |
private static final long serialVersionUID = -1752685357864733168L; | |
private final boolean isDir; | |
private final long length; | |
private long lastModified = 0; | |
private VirtualFile(final File file, long length) { | |
this(file.toString(), length); | |
} | |
private VirtualFile(String pathname, long length) { | |
super(pathname); | |
isDir = pathname.endsWith("/"); | |
this.length = length; | |
} | |
private VirtualFile(String parent, String child, long length) { | |
super(parent, child); | |
isDir = child.endsWith("/"); | |
this.length = length; | |
} | |
private VirtualFile(File parent, String child, long length) { | |
super(parent, child); | |
isDir = child.endsWith("/"); | |
this.length = length; | |
} | |
@Override | |
public boolean setLastModified(long t) { | |
this.lastModified = t; | |
return true; | |
} | |
@Override | |
public long lastModified() { | |
return lastModified; | |
} | |
@Override | |
public long length() { | |
return length; | |
} | |
@Override | |
public boolean exists() { | |
return true; | |
} | |
@Override | |
public boolean isDirectory() { | |
return isDir; | |
} | |
@Override | |
public File getCanonicalFile() throws IOException { | |
return this; | |
} | |
@Override | |
public File getAbsoluteFile() { | |
return this; | |
} | |
@Override | |
public File getParentFile() { | |
final int lastIndex = this.toString().lastIndexOf('/'); | |
if (lastIndex == -1) return null; | |
String parent = this.toString().substring(0, lastIndex + 1); | |
return new VirtualFile(parent, 1); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A short blog post about this code is here: https://blog.willwinder.com/2020/01/using-jfilechooser-to-browse-aws-s3.html