/**
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.commons.file.protocol.s3;
import com.mucommander.commons.file.*;
import com.mucommander.commons.file.protocol.ProtocolFile;
import com.mucommander.commons.io.RandomAccessOutputStream;
import com.mucommander.commons.runtime.JavaVersion;
import org.jets3t.service.Constants;
import org.jets3t.service.S3ObjectsChunk;
import org.jets3t.service.S3Service;
import org.jets3t.service.S3ServiceException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
/**
* Super class of {@link S3Root}, {@link S3Bucket} and {@link S3Object}.
*
* @author Maxence Bernard
*/
public abstract class S3File extends ProtocolFile {
protected org.jets3t.service.S3Service service;
protected AbstractFile parent;
protected boolean parentSet;
protected S3File(FileURL url, S3Service service) {
super(url);
this.service = service;
}
protected IOException getIOException(S3ServiceException e) throws IOException {
return getIOException(e, fileURL);
}
protected static IOException getIOException(S3ServiceException e, FileURL fileURL) throws IOException {
handleAuthException(e, fileURL);
Throwable cause = e.getCause();
if(cause instanceof IOException)
return (IOException)cause;
if(JavaVersion.JAVA_1_6.isCurrentOrHigher())
return new IOException(e);
return new IOException(e.getMessage());
}
protected static void handleAuthException(S3ServiceException e, FileURL fileURL) throws AuthException {
int code = e.getResponseCode();
if(code==401 || code==403)
throw new AuthException(fileURL);
}
protected AbstractFile[] listObjects(String bucketName, String prefix, S3File parent) throws IOException {
try {
S3ObjectsChunk chunk = service.listObjectsChunked(bucketName, prefix, "/", Constants.DEFAULT_OBJECT_LIST_CHUNK_SIZE, null, true);
org.jets3t.service.model.S3Object objects[] = chunk.getObjects();
String[] commonPrefixes = chunk.getCommonPrefixes();
if(objects.length==0 && !prefix.equals("")) {
// This happens only when the directory does not exist
throw new IOException();
}
AbstractFile[] children = new AbstractFile[objects.length+commonPrefixes.length];
FileURL childURL;
int i=0;
String objectKey;
for(org.jets3t.service.model.S3Object object : objects) {
// Discard the object corresponding to the prefix itself
objectKey = object.getKey();
if(objectKey.equals(prefix))
continue;
childURL = (FileURL)fileURL.clone();
childURL.setPath(bucketName + "/" + objectKey);
children[i] = FileFactory.getFile(childURL, parent, service, object);
i++;
}
org.jets3t.service.model.S3Object directoryObject;
for(String commonPrefix : commonPrefixes) {
childURL = (FileURL)fileURL.clone();
childURL.setPath(bucketName + "/" + commonPrefix);
directoryObject = new org.jets3t.service.model.S3Object(commonPrefix);
// Common prefixes are not objects per se, and therefore do not have a date, content-length nor owner.
directoryObject.setLastModifiedDate(new Date(System.currentTimeMillis()));
directoryObject.setContentLength(0);
children[i] = FileFactory.getFile(childURL, parent, service, directoryObject);
i++;
}
// Trim the array if an object was discarded.
// Note: Having to recreate an array sucks (puts pressure on the GC), but I haven't found a reliable way
// to know in advance whether the prefix will appear in the results or not.
if(i<children.length) {
AbstractFile[] childrenTrimmed = new AbstractFile[i];
System.arraycopy(children, 0, childrenTrimmed, 0, i);
return childrenTrimmed;
}
return children;
}
catch(S3ServiceException e) {
throw getIOException(e);
}
}
//////////////////////
// Abstract methods //
//////////////////////
public abstract FileAttributes getFileAttributes();
/////////////////////////////////
// ProtocolFile implementation //
/////////////////////////////////
@Override
public AbstractFile getParent() {
if(!parentSet) {
FileURL parentFileURL = this.fileURL.getParent();
if(parentFileURL!=null) {
try {
parent = FileFactory.getFile(parentFileURL, null, service);
}
catch(IOException e) {
// No parent
}
}
parentSet = true;
}
return parent;
}
@Override
public void setParent(AbstractFile parent) {
this.parent = parent;
this.parentSet = true;
}
// Delegates to FileAttributes
@Override
public long getDate() {
return getFileAttributes().getDate();
}
@Override
public long getSize() {
return getFileAttributes().getSize();
}
@Override
public boolean exists() {
return getFileAttributes().exists();
}
@Override
public boolean isDirectory() {
return getFileAttributes().isDirectory();
}
@Override
public FilePermissions getPermissions() {
return getFileAttributes().getPermissions();
}
@Override
public Object getUnderlyingFileObject() {
return getFileAttributes();
}
// Unsupported operations, no matter the kind of resource (object, bucket, service)
@Override
public boolean isSymlink() {
return false;
}
@Override
public boolean isSystem() {
return false;
}
@Override
public PermissionBits getChangeablePermissions() {
return PermissionBits.EMPTY_PERMISSION_BITS;
}
@Override
@UnsupportedFileOperation
public void changePermission(PermissionAccess access, PermissionType permission, boolean enabled) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_PERMISSION);
}
@Override
public String getGroup() {
return null;
}
@Override
public boolean canGetGroup() {
return false;
}
@Override
@UnsupportedFileOperation
public OutputStream getAppendOutputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.APPEND_FILE);
}
@Override
@UnsupportedFileOperation
public RandomAccessOutputStream getRandomAccessOutputStream() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.RANDOM_WRITE_FILE);
}
@Override
@UnsupportedFileOperation
public long getFreeSpace() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_FREE_SPACE);
}
@Override
@UnsupportedFileOperation
public long getTotalSpace() throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.GET_TOTAL_SPACE);
}
@Override
@UnsupportedFileOperation
public void changeDate(long lastModified) throws UnsupportedFileOperationException {
throw new UnsupportedFileOperationException(FileOperation.CHANGE_DATE);
}
}