add files API and provders

This commit is contained in:
Jörg Prante 2021-11-29 18:54:29 +01:00
parent 6eca3c6d2b
commit 2a724f1b37
22 changed files with 1374 additions and 2 deletions

View file

@ -0,0 +1,8 @@
import org.xbib.files.DefaultFilesProvider;
import org.xbib.files.Provider;
module org.xbib.files {
exports org.xbib.files;
uses Provider;
provides Provider with DefaultFilesProvider;
}

View file

@ -0,0 +1,332 @@
package org.xbib.files;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.nio.file.Files;
public class DefaultFiles implements org.xbib.files.Files {
private static final int READ_BUFFER_SIZE = 128 * 1024;
private static final int WRITE_BUFFER_SIZE = 128 * 1024;
private static final Set<PosixFilePermission> DEFAULT_DIR_PERMISSIONS =
PosixFilePermissions.fromString("rwxr-xr-x");
private static final Set<PosixFilePermission> DEFAULT_FILE_PERMISSIONS =
PosixFilePermissions.fromString("rw-r--r--");
private final URI uri;
private final Map<String, ?> env;
public DefaultFiles(URI uri, Map<String, ?> env) {
this.uri = uri;
this.env = env;
}
@Override
public Boolean exists(String path) {
return Files.exists(Paths.get(path));
}
@Override
public Boolean isExecutable(String path) {
return Files.isExecutable(Paths.get(path));
}
@Override
public Boolean isDirectory(String path) {
return Files.isDirectory(Paths.get(path));
}
@Override
public Boolean isRegularFile(String path) {
return Files.isRegularFile(Paths.get(path));
}
@Override
public Boolean isHidden(String path) throws IOException {
return Files.isHidden(Paths.get(path));
}
@Override
public Boolean isSameFile(String path1, String path2) throws IOException {
return Files.isSameFile(Paths.get(path1), Paths.get(path2));
}
@Override
public Boolean isSymbolicLink(String path) {
return Files.isSymbolicLink(Paths.get(path));
}
@Override
public Boolean isReadable(String path) {
return Files.isReadable(Paths.get(path));
}
@Override
public Boolean isWritable(String path) {
return Files.isWritable(Paths.get(path));
}
@Override
public void setAttribute(String path, String attribute, Object value) throws IOException {
Files.setAttribute(Paths.get(path), attribute, value);
}
@Override
public Object getAttribute(String path, String attribute) throws IOException {
return Files.getAttribute(Paths.get(path), attribute);
}
@Override
public void setPermissions(String path, Set<PosixFilePermission> permissions) throws IOException {
Files.setPosixFilePermissions(Paths.get(path), permissions);
}
@Override
public Set<PosixFilePermission> getPermissions(String path) throws IOException {
return Files.getPosixFilePermissions(Paths.get(path));
}
@Override
public void setOwner(String path, UserPrincipal userPrincipal) throws IOException {
Files.setOwner(Paths.get(path), userPrincipal);
}
@Override
public UserPrincipal getOwner(String path) throws IOException {
return Files.getOwner(Paths.get(path));
}
@Override
public void setLastModifiedTime(String path, FileTime fileTime) throws IOException {
Files.setLastModifiedTime(Paths.get(path), fileTime);
}
@Override
public FileTime getLastModifiedTime(String path) throws IOException {
return Files.getLastModifiedTime(Paths.get(path));
}
@Override
public void createFile(String path, FileAttribute<?>... attributes) throws IOException {
Files.createFile(Paths.get(path), attributes);
}
@Override
public void createDirectory(String path, FileAttribute<?>... attributes) throws IOException {
Files.createDirectory(Paths.get(path), attributes);
}
@Override
public void createDirectories(String path, FileAttribute<?>... attributes) throws IOException {
Files.createDirectories(Paths.get(path), attributes);
}
@Override
public void upload(Path source, Path target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
@Override
public void upload(Path source, String target, CopyOption... copyOptions) throws IOException {
upload(source, Paths.get(target), DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
@Override
public void upload(InputStream source, Path target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
@Override
public void upload(InputStream source, String target, CopyOption... copyOptions) throws IOException {
upload(source, Paths.get(target), DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
@Override
public void download(Path source, Path target, CopyOption... copyOptions) throws IOException {
download(source, target, READ_BUFFER_SIZE, copyOptions);
}
@Override
public void download(String source, Path target, CopyOption... copyOptions) throws IOException {
download(Paths.get(source), target, READ_BUFFER_SIZE, copyOptions);
}
@Override
public void download(Path source, OutputStream target) throws IOException {
download(source, target, READ_BUFFER_SIZE);
}
@Override
public void download(String source, OutputStream target) throws IOException {
download(Paths.get(source), target, READ_BUFFER_SIZE);
}
@Override
public void rename(String source, String target, CopyOption... copyOptions) throws IOException {
Files.move(Paths.get(source), Paths.get(target), copyOptions);
}
@Override
public void remove(String source) throws IOException {
Files.deleteIfExists(Paths.get(source));
}
@Override
public void copy(String source, String target, CopyOption... copyOptions) throws IOException {
Files.copy(Paths.get(source), Paths.get(target), copyOptions);
}
@Override
public DirectoryStream<Path> stream(String path, String glob) throws IOException {
return Files.newDirectoryStream(Paths.get(path), glob);
}
@Override
public DirectoryStream<Path> filter(String path, DirectoryStream.Filter<Path> filter) throws IOException {
return Files.newDirectoryStream(Paths.get(path), filter);
}
private void upload(Path source, Path target,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
upload(Files.newByteChannel(source), target, WRITE_BUFFER_SIZE, dirPerms, filePerms, copyOptions);
}
private void upload(InputStream source, Path target,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
upload(Channels.newChannel(source), target, WRITE_BUFFER_SIZE, dirPerms, filePerms, copyOptions);
}
private void upload(ReadableByteChannel source,
Path target,
int bufferSize,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
prepareForWrite(target, dirPerms, filePerms);
transfer(source, Files.newByteChannel(target, prepareWriteOptions(copyOptions)), bufferSize);
}
private void download(Path source,
OutputStream outputStream,
int bufferSize) throws IOException {
download(source, Channels.newChannel(outputStream), bufferSize);
}
private void download(Path source,
WritableByteChannel writableByteChannel,
int bufferSize) throws IOException {
transfer(Files.newByteChannel(source, prepareReadOptions()), writableByteChannel,
bufferSize);
}
private void download(Path source,
Path target,
int bufferSize,
CopyOption... copyOptions) throws IOException {
prepareForWrite(target);
transfer(Files.newByteChannel(source, prepareReadOptions(copyOptions)),
Files.newByteChannel(target, prepareWriteOptions(copyOptions)), bufferSize);
}
private void prepareForWrite(Path path) throws IOException {
if (path == null) {
return;
}
Path parent = path.getParent();
if (parent != null) {
if (!Files.exists(parent)) {
Files.createDirectories(parent);
}
}
if (!Files.exists(path)) {
Files.createFile(path);
}
}
private void prepareForWrite(Path path,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms) throws IOException {
if (path == null) {
return;
}
Path parent = path.getParent();
if (parent != null) {
if (!Files.exists(parent)) {
Files.createDirectories(parent);
}
PosixFileAttributeView posixFileAttributeView =
Files.getFileAttributeView(parent, PosixFileAttributeView.class);
posixFileAttributeView.setPermissions(dirPerms);
}
if (!Files.exists(path)) {
Files.createFile(path);
}
PosixFileAttributeView posixFileAttributeView =
Files.getFileAttributeView(path, PosixFileAttributeView.class);
posixFileAttributeView.setPermissions(filePerms);
}
private Set<? extends OpenOption> prepareReadOptions(CopyOption... copyOptions) {
// ignore user copy options
return EnumSet.of(StandardOpenOption.READ);
}
private Set<? extends OpenOption> prepareWriteOptions(CopyOption... copyOptions) {
Set<? extends OpenOption> options = null;
for (CopyOption copyOption : copyOptions) {
if (copyOption == StandardCopyOption.REPLACE_EXISTING) {
options = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
}
if (options == null) {
// we can not use CREATE_NEW, file is already there because of prepareForWrite() -> Files.createFile()
options = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE);
}
return options;
}
private void transfer(ReadableByteChannel readableByteChannel,
WritableByteChannel writableByteChannel,
int bufferSize) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
int read;
while ((read = readableByteChannel.read(buffer)) > 0) {
buffer.flip();
while (read > 0) {
read -= writableByteChannel.write(buffer);
}
buffer.clear();
}
}
}

View file

@ -0,0 +1,11 @@
package org.xbib.files;
import java.net.URI;
import java.util.Map;
public class DefaultFilesProvider implements Provider {
@Override
public Files provide(URI uri, Map<String, ?> env) {
return !uri.isAbsolute() || uri.getScheme().equals("file") ? new DefaultFiles(uri, env) : null;
}
}

View file

@ -0,0 +1,105 @@
package org.xbib.files;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
public interface Files {
class Holder {
private static Files createFiles(URI uri, Map<String, ?> env) {
ServiceLoader<Provider> serviceLoader = ServiceLoader.load(Provider.class);
Optional<Files> first = serviceLoader.stream()
.map(ServiceLoader.Provider::get)
.filter(Objects::nonNull)
.map(p -> p.provide(uri, env))
.filter(Objects::nonNull)
.findFirst();
return first.orElse(null);
}
}
default Files newInstance(URI uri, Map<String, ?> env) {
return Holder.createFiles(uri, env);
}
Boolean exists(String path) throws IOException;
Boolean isExecutable(String path) throws IOException;
Boolean isDirectory(String path) throws IOException;
Boolean isRegularFile(String path) throws IOException;
Boolean isHidden(String path) throws IOException;
Boolean isSameFile(String path1, String path2) throws IOException;
Boolean isSymbolicLink(String path) throws IOException;
Boolean isReadable(String path) throws IOException;
Boolean isWritable(String path) throws IOException;
void setAttribute(String path, String attribute, Object value) throws IOException;
Object getAttribute(String path, String attribute) throws IOException;
void setPermissions(String path, Set<PosixFilePermission> permissions) throws IOException;
Set<PosixFilePermission> getPermissions(String path) throws IOException;
void setOwner(String path, UserPrincipal userPrincipal) throws IOException;
UserPrincipal getOwner(String path) throws IOException;
void setLastModifiedTime(String path, FileTime fileTime) throws IOException;
FileTime getLastModifiedTime(String path) throws IOException;
void createFile(String path, FileAttribute<?>... attributes) throws IOException;
void createDirectory(String path, FileAttribute<?>... attributes) throws IOException;
void createDirectories(String path, FileAttribute<?>... attributes) throws IOException;
void upload(Path source, Path target, CopyOption... copyOptions) throws IOException;
void upload(Path source, String target, CopyOption... copyOptions) throws Exception;
void upload(InputStream source, Path target, CopyOption... copyOptions) throws IOException;
void upload(InputStream source, String target, CopyOption... copyOptions) throws IOException;
void download(Path source, Path target, CopyOption... copyOptions) throws IOException;
void download(String source, Path target, CopyOption... copyOptions) throws IOException;
void download(Path source, OutputStream target) throws IOException;
void download(String source, OutputStream target) throws IOException;
void rename(String source, String target, CopyOption... copyOptions) throws IOException;
void remove(String source) throws IOException;
void copy(String source, String target, CopyOption... copyOptions) throws IOException;
DirectoryStream<Path> stream(String path, String glob) throws IOException;
DirectoryStream<Path> filter(String path, DirectoryStream.Filter<Path> filter) throws IOException;
}

View file

@ -0,0 +1,9 @@
package org.xbib.files;
import java.net.URI;
import java.util.Map;
public interface Provider {
Files provide(URI uri, Map<String, ?> env);
}

View file

@ -0,0 +1 @@
org.xbib.files.DefaultFilesProvider

View file

@ -1,4 +1,5 @@
dependencies {
api project(':files-api')
api project(':files-ftp')
testImplementation "org.mockftpserver:MockFtpServer:${project.property('mockftpserver.version')}"
testImplementation "org.junit.jupiter:junit-jupiter-params:${project.property('junit.version')}"

View file

@ -1,5 +1,11 @@
import java.nio.file.spi.FileSystemProvider;
import org.xbib.files.Provider;
import org.xbib.io.ftp.fs.FTPFileSystemProvider;
import org.xbib.io.ftp.fs.spi.FTPFilesProvider;
module org.xbib.files.ftp.fs {
requires org.xbib.files;
requires org.xbib.files.ftp;
provides java.nio.file.spi.FileSystemProvider
with org.xbib.io.ftp.fs.FTPFileSystemProvider;
provides FileSystemProvider with FTPFileSystemProvider;
provides Provider with FTPFilesProvider;
}

View file

@ -0,0 +1,25 @@
package org.xbib.io.ftp.fs.spi;
import org.xbib.io.ftp.fs.FTPEnvironment;
import org.xbib.io.ftp.fs.FTPFileSystemProvider;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.util.Map;
class FTPContext {
final FTPFileSystemProvider provider;
final FileSystem fileSystem;
FTPContext(URI uri, Map<String, ?> env) throws IOException {
this.provider = new FTPFileSystemProvider();
this.fileSystem = provider.newFileSystem(uri, env != null ? env : new FTPEnvironment());
}
void close() throws IOException {
fileSystem.close();
}
}

View file

@ -0,0 +1,391 @@
package org.xbib.io.ftp.fs.spi;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
public class FTPFiles implements org.xbib.files.Files {
private static final int READ_BUFFER_SIZE = 128 * 1024;
private static final int WRITE_BUFFER_SIZE = 128 * 1024;
private static final Set<PosixFilePermission> DEFAULT_DIR_PERMISSIONS =
PosixFilePermissions.fromString("rwxr-xr-x");
private static final Set<PosixFilePermission> DEFAULT_FILE_PERMISSIONS =
PosixFilePermissions.fromString("rw-r--r--");
private final URI uri;
private final Map<String, ?> env;
public FTPFiles(URI uri, Map<String, ?> env) {
this.uri = uri;
this.env = env;
}
@Override
public Boolean exists(String path) throws IOException {
return performWithContext(ctx -> Files.exists(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isExecutable(String path) throws IOException {
return performWithContext(ctx -> Files.isExecutable(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isDirectory(String path) throws IOException {
return performWithContext(ctx -> Files.isDirectory(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isRegularFile(String path) throws IOException {
return performWithContext(ctx -> Files.isRegularFile(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isHidden(String path) throws IOException {
return performWithContext(ctx -> Files.isHidden(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isSameFile(String path1, String path2) throws IOException {
return performWithContext(ctx -> Files.isSameFile(ctx.fileSystem.getPath(path1), ctx.fileSystem.getPath(path2)));
}
@Override
public Boolean isSymbolicLink(String path) throws IOException {
return performWithContext(ctx -> Files.isSymbolicLink(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isReadable(String path) throws IOException {
return performWithContext(ctx -> Files.isReadable(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isWritable(String path) throws IOException {
return performWithContext(ctx -> Files.isWritable(ctx.fileSystem.getPath(path)));
}
@Override
public void createFile(String path, FileAttribute<?>... attributes) throws IOException {
performWithContext(ctx -> Files.createFile(ctx.fileSystem.getPath(path), attributes));
}
@Override
public void createDirectory(String path, FileAttribute<?>... attributes) throws IOException {
performWithContext(ctx -> Files.createDirectory(ctx.fileSystem.getPath(path), attributes));
}
@Override
public void createDirectories(String path, FileAttribute<?>... attributes) throws IOException {
performWithContext(ctx -> Files.createDirectories(ctx.fileSystem.getPath(path), attributes));
}
@Override
public void setAttribute(String path, String attribute, Object value) throws IOException {
performWithContext(ctx -> Files.setAttribute(ctx.fileSystem.getPath(path), attribute, value));
}
@Override
public Object getAttribute(String path, String attribute) throws IOException {
return performWithContext(ctx -> Files.getAttribute(ctx.fileSystem.getPath(path), attribute));
}
@Override
public void setPermissions(String path, Set<PosixFilePermission> permissions) throws IOException {
performWithContext(ctx -> Files.setPosixFilePermissions(ctx.fileSystem.getPath(path), permissions));
}
@Override
public Set<PosixFilePermission> getPermissions(String path) throws IOException {
return performWithContext(ctx -> Files.getPosixFilePermissions(ctx.fileSystem.getPath(path)));
}
@Override
public void setLastModifiedTime(String path, FileTime fileTime) throws IOException {
performWithContext(ctx -> Files.setLastModifiedTime(ctx.fileSystem.getPath(path), fileTime));
}
@Override
public FileTime getLastModifiedTime(String path) throws IOException{
return performWithContext(ctx -> Files.getLastModifiedTime(ctx.fileSystem.getPath(path)));
}
@Override
public void setOwner(String path, UserPrincipal userPrincipal) throws IOException {
performWithContext(ctx -> Files.setOwner(ctx.fileSystem.getPath(path), userPrincipal));
}
@Override
public UserPrincipal getOwner(String path) throws IOException {
return performWithContext(ctx -> Files.getOwner(ctx.fileSystem.getPath(path)));
}
@Override
public DirectoryStream<Path> stream(String path, String glob) throws IOException {
return performWithContext(ctx -> Files.newDirectoryStream(ctx.fileSystem.getPath(path), glob));
}
@Override
public DirectoryStream<Path> filter(String path, DirectoryStream.Filter<Path> filter) throws IOException {
return performWithContext(ctx -> Files.newDirectoryStream(ctx.fileSystem.getPath(path), filter));
}
@Override
public void upload(Path source, Path target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
public void upload(Path source, Path target,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
upload(ctx, Files.newByteChannel(source), target, WRITE_BUFFER_SIZE,
dirPerms, filePerms, copyOptions);
return null;
});
}
public void upload(Path source, String target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
public void upload(Path source, String target,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
upload(ctx, Files.newByteChannel(source), ctx.fileSystem.getPath(target), WRITE_BUFFER_SIZE,
dirPerms, filePerms, copyOptions);
return null;
});
}
public void upload(InputStream source, Path target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
public void upload(InputStream source, Path target,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
upload(ctx, Channels.newChannel(source), target, WRITE_BUFFER_SIZE,
dirPerms, filePerms, copyOptions);
return null;
});
}
public void upload(InputStream source, String target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
public void upload(InputStream source, String target,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
upload(ctx, Channels.newChannel(source), ctx.fileSystem.getPath(target), WRITE_BUFFER_SIZE,
dirPerms, filePerms, copyOptions);
return null;
});
}
public void download(Path source, Path target, CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
download(ctx, source, target, READ_BUFFER_SIZE, copyOptions);
return null;
});
}
public void download(String source, Path target, CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
download(ctx, ctx.fileSystem.getPath(source), target, READ_BUFFER_SIZE, copyOptions);
return null;
});
}
public void download(Path source, OutputStream target) throws IOException {
performWithContext(ctx -> {
download(ctx, source, target, READ_BUFFER_SIZE);
return null;
});
}
public void download(String source, OutputStream target) throws IOException {
performWithContext(ctx -> {
Files.copy(ctx.fileSystem.getPath(source), target);
return null;
});
}
public void copy(String source, String target, CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
Files.copy(ctx.fileSystem.getPath(source), ctx.fileSystem.getPath(target), copyOptions);
return null;
});
}
public void rename(String source, String target, CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
Files.move(ctx.fileSystem.getPath(source), ctx.fileSystem.getPath(target), copyOptions);
return null;
});
}
public void remove(String source) throws IOException {
performWithContext(ctx -> {
Files.deleteIfExists(ctx.fileSystem.getPath(source));
return null;
});
}
private void upload(FTPContext ctx,
ReadableByteChannel source,
Path target,
int bufferSize,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
prepareForWrite(target, dirPerms, filePerms);
transfer(source, ctx.provider.newByteChannel(target, prepareWriteOptions(copyOptions)), bufferSize);
}
private void download(FTPContext ctx,
Path source,
OutputStream outputStream,
int bufferSize) throws IOException {
download(ctx, source, Channels.newChannel(outputStream), bufferSize);
}
private void download(FTPContext ctx,
Path source,
WritableByteChannel writableByteChannel,
int bufferSize) throws IOException {
transfer(ctx.provider.newByteChannel(source, prepareReadOptions()), writableByteChannel,
bufferSize);
}
private void download(FTPContext ctx,
Path source,
Path target,
int bufferSize,
CopyOption... copyOptions) throws IOException {
prepareForWrite(target);
transfer(ctx.provider.newByteChannel(source, prepareReadOptions(copyOptions)),
Files.newByteChannel(target, prepareWriteOptions(copyOptions)), bufferSize);
}
private void prepareForWrite(Path path) throws IOException {
if (path == null) {
return;
}
Path parent = path.getParent();
if (parent != null) {
if (!Files.exists(parent)) {
Files.createDirectories(parent);
}
}
if (!Files.exists(path)) {
Files.createFile(path);
}
}
private void prepareForWrite(Path path,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms) throws IOException {
if (path == null) {
return;
}
Path parent = path.getParent();
if (parent != null) {
if (!Files.exists(parent)) {
Files.createDirectories(parent);
}
PosixFileAttributeView posixFileAttributeView =
Files.getFileAttributeView(parent, PosixFileAttributeView.class);
posixFileAttributeView.setPermissions(dirPerms);
}
if (!Files.exists(path)) {
Files.createFile(path);
}
PosixFileAttributeView posixFileAttributeView =
Files.getFileAttributeView(path, PosixFileAttributeView.class);
posixFileAttributeView.setPermissions(filePerms);
}
private Set<? extends OpenOption> prepareReadOptions(CopyOption... copyOptions) {
// ignore user copy options
return EnumSet.of(StandardOpenOption.READ);
}
private Set<? extends OpenOption> prepareWriteOptions(CopyOption... copyOptions) {
Set<? extends OpenOption> options = null;
for (CopyOption copyOption : copyOptions) {
if (copyOption == StandardCopyOption.REPLACE_EXISTING) {
options = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
}
if (options == null) {
// we can not use CREATE_NEW, file is already there because of prepareForWrite() -> Files.createFile()
options = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE);
}
return options;
}
private void transfer(ReadableByteChannel readableByteChannel,
WritableByteChannel writableByteChannel,
int bufferSize) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
int read;
while ((read = readableByteChannel.read(buffer)) > 0) {
buffer.flip();
while (read > 0) {
read -= writableByteChannel.write(buffer);
}
buffer.clear();
}
}
private <T> T performWithContext(WithContext<T> action) throws IOException {
FTPContext ctx = null;
try {
if (uri != null) {
ctx = new FTPContext(uri, env);
return action.perform(ctx);
} else {
return null;
}
} finally {
if (ctx != null) {
ctx.close();
}
}
}
}

View file

@ -0,0 +1,14 @@
package org.xbib.io.ftp.fs.spi;
import org.xbib.files.Files;
import org.xbib.files.Provider;
import java.net.URI;
import java.util.Map;
public class FTPFilesProvider implements Provider {
@Override
public Files provide(URI uri, Map<String, ?> env) {
return !uri.isAbsolute() || uri.getScheme().equals("file") ? new FTPFiles(uri, env) : null;
}
}

View file

@ -0,0 +1,7 @@
package org.xbib.io.ftp.fs.spi;
import java.io.IOException;
interface WithContext<T> {
T perform(FTPContext ctx) throws IOException;
}

View file

@ -0,0 +1 @@
org.xbib.io.ftp.fs.spi.FTPFilesProvider

0
files-ftp/build.gradle Normal file
View file

View file

@ -1,4 +1,5 @@
dependencies {
api project(':files-api')
api project(':files-sftp')
testImplementation "org.bouncycastle:bcpkix-jdk15on:${project.property('bouncycastle.version')}"
}

View file

@ -1,8 +1,13 @@
import org.apache.sshd.fs.SftpFileSystemProvider;
import java.nio.file.spi.FileSystemProvider;
import org.xbib.files.Provider;
import org.apache.sshd.fs.spi.SFTPFilesProvider;
module org.xbib.files.sftp.fs {
exports org.apache.sshd.fs;
exports org.apache.sshd.fs.spi;
requires org.xbib.files;
requires transitive org.xbib.files.sftp;
provides FileSystemProvider with SftpFileSystemProvider;
provides Provider with SFTPFilesProvider;
}

View file

@ -0,0 +1,40 @@
package org.apache.sshd.fs.spi;
import org.apache.sshd.client.ClientBuilder;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.fs.SftpFileSystem;
import org.apache.sshd.fs.SftpFileSystemProvider;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
class SFTPContext {
private final SshClient sshClient;
final SftpFileSystemProvider provider;
final SftpFileSystem fileSystem;
SFTPContext(URI uri, Map<String, ?> env) throws IOException {
this.sshClient = ClientBuilder.builder().build();
Object object = env.get("workers");
if (object instanceof Integer) {
sshClient.setNioWorkers((Integer) object);
} else if (object instanceof String) {
sshClient.setNioWorkers(Integer.parseInt((String) object));
} else {
// we do not require a vast pool of threads
sshClient.setNioWorkers(1);
}
sshClient.start();
this.provider = new SftpFileSystemProvider(sshClient);
this.fileSystem = provider.newFileSystem(uri, env);
}
void close() throws IOException {
sshClient.stop();
fileSystem.close();
}
}

View file

@ -0,0 +1,392 @@
package org.apache.sshd.fs.spi;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
public class SFTPFiles implements org.xbib.files.Files {
private static final int READ_BUFFER_SIZE = 128 * 1024;
private static final int WRITE_BUFFER_SIZE = 128 * 1024;
private static final Set<PosixFilePermission> DEFAULT_DIR_PERMISSIONS =
PosixFilePermissions.fromString("rwxr-xr-x");
private static final Set<PosixFilePermission> DEFAULT_FILE_PERMISSIONS =
PosixFilePermissions.fromString("rw-r--r--");
private final URI uri;
private final Map<String, ?> env;
public SFTPFiles(URI uri, Map<String, ?> env) {
this.uri = uri;
this.env = env;
}
@Override
public Boolean exists(String path) throws IOException {
return performWithContext(ctx -> Files.exists(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isExecutable(String path) throws IOException {
return performWithContext(ctx -> Files.isExecutable(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isDirectory(String path) throws IOException {
return performWithContext(ctx -> Files.isDirectory(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isRegularFile(String path) throws IOException {
return performWithContext(ctx -> Files.isRegularFile(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isHidden(String path) throws IOException {
return performWithContext(ctx -> Files.isHidden(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isSameFile(String path1, String path2) throws IOException {
return performWithContext(ctx -> Files.isSameFile(ctx.fileSystem.getPath(path1), ctx.fileSystem.getPath(path2)));
}
@Override
public Boolean isSymbolicLink(String path) throws IOException {
return performWithContext(ctx -> Files.isSymbolicLink(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isReadable(String path) throws IOException {
return performWithContext(ctx -> Files.isReadable(ctx.fileSystem.getPath(path)));
}
@Override
public Boolean isWritable(String path) throws IOException {
return performWithContext(ctx -> Files.isWritable(ctx.fileSystem.getPath(path)));
}
@Override
public void createFile(String path, FileAttribute<?>... attributes) throws IOException {
performWithContext(ctx -> Files.createFile(ctx.fileSystem.getPath(path), attributes));
}
@Override
public void createDirectory(String path, FileAttribute<?>... attributes) throws IOException {
performWithContext(ctx -> Files.createDirectory(ctx.fileSystem.getPath(path), attributes));
}
@Override
public void createDirectories(String path, FileAttribute<?>... attributes) throws IOException {
performWithContext(ctx -> Files.createDirectories(ctx.fileSystem.getPath(path), attributes));
}
@Override
public void setAttribute(String path, String attribute, Object value) throws IOException {
performWithContext(ctx -> Files.setAttribute(ctx.fileSystem.getPath(path), attribute, value));
}
@Override
public Object getAttribute(String path, String attribute) throws IOException {
return performWithContext(ctx -> Files.getAttribute(ctx.fileSystem.getPath(path), attribute));
}
@Override
public void setPermissions(String path, Set<PosixFilePermission> permissions) throws IOException {
performWithContext(ctx -> Files.setPosixFilePermissions(ctx.fileSystem.getPath(path), permissions));
}
@Override
public Set<PosixFilePermission> getPermissions(String path) throws IOException {
return performWithContext(ctx -> Files.getPosixFilePermissions(ctx.fileSystem.getPath(path)));
}
@Override
public void setLastModifiedTime(String path, FileTime fileTime) throws IOException {
performWithContext(ctx -> Files.setLastModifiedTime(ctx.fileSystem.getPath(path), fileTime));
}
@Override
public FileTime getLastModifiedTime(String path) throws IOException{
return performWithContext(ctx -> Files.getLastModifiedTime(ctx.fileSystem.getPath(path)));
}
@Override
public void setOwner(String path, UserPrincipal userPrincipal) throws IOException {
performWithContext(ctx -> Files.setOwner(ctx.fileSystem.getPath(path), userPrincipal));
}
@Override
public UserPrincipal getOwner(String path) throws IOException {
return performWithContext(ctx -> Files.getOwner(ctx.fileSystem.getPath(path)));
}
@Override
public DirectoryStream<Path> stream(String path, String glob) throws IOException {
return performWithContext(ctx -> Files.newDirectoryStream(ctx.fileSystem.getPath(path), glob));
}
@Override
public DirectoryStream<Path> filter(String path, DirectoryStream.Filter<Path> filter) throws IOException {
return performWithContext(ctx -> Files.newDirectoryStream(ctx.fileSystem.getPath(path), filter));
}
@Override
public void upload(Path source, Path target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
@Override
public void upload(Path source, String target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
@Override
public void upload(InputStream source, Path target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
@Override
public void upload(InputStream source, String target, CopyOption... copyOptions) throws IOException {
upload(source, target, DEFAULT_DIR_PERMISSIONS, DEFAULT_FILE_PERMISSIONS, copyOptions);
}
@Override
public void download(Path source, Path target, CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
download(ctx, source, target, READ_BUFFER_SIZE, copyOptions);
return null;
});
}
@Override
public void download(String source, Path target, CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
download(ctx, ctx.fileSystem.getPath(source), target, READ_BUFFER_SIZE, copyOptions);
return null;
});
}
@Override
public void download(Path source, OutputStream target) throws IOException {
performWithContext(ctx -> {
download(ctx, source, target, READ_BUFFER_SIZE);
return null;
});
}
@Override
public void download(String source, OutputStream target) throws IOException {
performWithContext(ctx -> {
download(ctx, ctx.fileSystem.getPath(source), target, READ_BUFFER_SIZE);
return null;
});
}
@Override
public void copy(String source, String target, CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> Files.copy(ctx.fileSystem.getPath(source), ctx.fileSystem.getPath(target), copyOptions));
}
@Override
public void rename(String source, String target, CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> Files.move(ctx.fileSystem.getPath(source), ctx.fileSystem.getPath(target), copyOptions));
}
@Override
public void remove(String source) throws IOException {
performWithContext(ctx -> Files.deleteIfExists(ctx.fileSystem.getPath(source)));
}
public void upload(Path source, Path target,
Set<PosixFilePermission> dirPermissions,
Set<PosixFilePermission> filePermissions,
CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
upload(ctx, Files.newByteChannel(source), target, WRITE_BUFFER_SIZE,
dirPermissions, filePermissions, copyOptions);
return null;
});
}
public void upload(Path source, String target,
Set<PosixFilePermission> dirPermissions,
Set<PosixFilePermission> filePermissions,
CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
upload(ctx, Files.newByteChannel(source), ctx.fileSystem.getPath(target), WRITE_BUFFER_SIZE,
dirPermissions, filePermissions, copyOptions);
return null;
});
}
public void upload(InputStream source, Path target,
Set<PosixFilePermission> dirPermissions,
Set<PosixFilePermission> filePermissions,
CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
upload(ctx, Channels.newChannel(source), target, WRITE_BUFFER_SIZE,
dirPermissions, filePermissions, copyOptions);
return null;
});
}
public void upload(InputStream source, String target,
Set<PosixFilePermission> dirPermissions,
Set<PosixFilePermission> filePermissions,
CopyOption... copyOptions) throws IOException {
performWithContext(ctx -> {
upload(ctx, Channels.newChannel(source), ctx.fileSystem.getPath(target), WRITE_BUFFER_SIZE,
dirPermissions, filePermissions, copyOptions);
return null;
});
}
private void upload(SFTPContext ctx,
ReadableByteChannel source,
Path target,
int bufferSize,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms,
CopyOption... copyOptions) throws IOException {
prepareForWrite(target, dirPerms, filePerms);
transfer(source, ctx.provider.newByteChannel(target, prepareWriteOptions(copyOptions)), bufferSize);
}
private void download(SFTPContext ctx,
Path source,
OutputStream outputStream,
int bufferSize) throws IOException {
download(ctx, source, Channels.newChannel(outputStream), bufferSize);
}
private void download(SFTPContext ctx,
Path source,
WritableByteChannel writableByteChannel,
int bufferSize) throws IOException {
transfer(ctx.provider.newByteChannel(source, prepareReadOptions()), writableByteChannel,
bufferSize);
}
private void download(SFTPContext ctx,
Path source,
Path target,
int bufferSize,
CopyOption... copyOptions) throws IOException {
prepareForRead(target);
transfer(ctx.provider.newByteChannel(source, prepareReadOptions(copyOptions)),
Files.newByteChannel(target, prepareWriteOptions(copyOptions)), bufferSize);
}
private void prepareForRead(Path path) throws IOException {
if (path == null) {
return;
}
Path parent = path.getParent();
if (parent != null) {
if (!Files.exists(parent)) {
Files.createDirectories(parent);
}
}
if (!Files.exists(path)) {
Files.createFile(path);
}
}
private void prepareForWrite(Path path,
Set<PosixFilePermission> dirPerms,
Set<PosixFilePermission> filePerms) throws IOException {
if (path == null) {
return;
}
Path parent = path.getParent();
if (parent != null) {
if (!Files.exists(parent)) {
Files.createDirectories(parent);
}
PosixFileAttributeView posixFileAttributeView =
Files.getFileAttributeView(parent, PosixFileAttributeView.class);
posixFileAttributeView.setPermissions(dirPerms);
}
if (!Files.exists(path)) {
Files.createFile(path);
}
PosixFileAttributeView posixFileAttributeView =
Files.getFileAttributeView(path, PosixFileAttributeView.class);
posixFileAttributeView.setPermissions(filePerms);
}
private Set<? extends OpenOption> prepareReadOptions(CopyOption... copyOptions) {
// ignore user copy options
return EnumSet.of(StandardOpenOption.READ);
}
private Set<? extends OpenOption> prepareWriteOptions(CopyOption... copyOptions) {
Set<? extends OpenOption> options = null;
for (CopyOption copyOption : copyOptions) {
if (copyOption == StandardCopyOption.REPLACE_EXISTING) {
options = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
}
if (options == null) {
// we can not use CREATE_NEW, file is already there because of prepareForWrite() -> Files.createFile()
options = EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE);
}
return options;
}
private void transfer(ReadableByteChannel readableByteChannel,
WritableByteChannel writableByteChannel,
int bufferSize) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
int read;
while ((read = readableByteChannel.read(buffer)) > 0) {
buffer.flip();
while (read > 0) {
read -= writableByteChannel.write(buffer);
}
buffer.clear();
}
}
private <T> T performWithContext(WithContext<T> action) throws IOException {
SFTPContext ctx = null;
try {
if (uri != null) {
ctx = new SFTPContext(uri, env);
return action.perform(ctx);
} else {
return null;
}
} finally {
if (ctx != null) {
ctx.close();
}
}
}
}

View file

@ -0,0 +1,14 @@
package org.apache.sshd.fs.spi;
import org.xbib.files.Files;
import org.xbib.files.Provider;
import java.net.URI;
import java.util.Map;
public class SFTPFilesProvider implements Provider {
@Override
public Files provide(URI uri, Map<String, ?> env) {
return !uri.isAbsolute() || uri.getScheme().equals("file") ? new SFTPFiles(uri, env) : null;
}
}

View file

@ -0,0 +1,7 @@
package org.apache.sshd.fs.spi;
import java.io.IOException;
interface WithContext<T> {
T perform(SFTPContext ctx) throws IOException;
}

View file

@ -0,0 +1 @@
org.apache.sshd.fs.spi.SFTPFilesProvider

View file

@ -1,3 +1,4 @@
include 'files-api'
include 'files-eddsa'
include 'files-zlib'
include 'files-ftp'