diff --git a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SftpSession.java b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SftpSession.java new file mode 100644 index 0000000..2b24ea3 --- /dev/null +++ b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SftpSession.java @@ -0,0 +1,250 @@ +package net.lamgc.oracle.sentry.oci.compute.ssh; + +import org.apache.sshd.sftp.client.SftpClient; +import org.apache.sshd.sftp.common.SftpConstants; +import org.apache.sshd.sftp.common.SftpException; + +import java.io.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.NoSuchFileException; +import java.util.HashSet; +import java.util.Set; + +/** + * Sftp 会话. + *

可通过会话访问远程服务器的文件. + * @author LamGC + */ +@SuppressWarnings("unused") +public class SftpSession implements Closeable { + + /** + * 排除的文件名. + */ + private final static Set EXCLUDED_FILE_NAMES; + + static { + EXCLUDED_FILE_NAMES = new HashSet<>(); + EXCLUDED_FILE_NAMES.add("."); + EXCLUDED_FILE_NAMES.add(".."); + } + + private final SftpClient sftpClient; + + /** + * 创建 Sftp 会话. + * @param sftpClient Sftp 客户端. + */ + SftpSession(SftpClient sftpClient) { + this.sftpClient = sftpClient; + } + + /** + * 获取指定文件夹内的所有文件. + * @param path 文件夹路径. + * @return 返回该目录下所有文件的文件名, 文件名不带路径. + * @throws IOException 执行失败时抛出异常. + */ + public Set listFiles(String path) throws IOException { + SftpClient.CloseableHandle handle = sftpClient.openDir(path); + Set paths = new HashSet<>(); + for (SftpClient.DirEntry entry : sftpClient.listDir(handle)) { + if (EXCLUDED_FILE_NAMES.contains(entry.getFilename())) { + continue; + } + paths.add(entry.getFilename()); + } + sftpClient.close(handle); + return paths; + } + + /** + * 读取指定路径的问题. + * @param path 文件所在路径. + * @return 返回文件输入流. + * @throws FileNotFoundException 当文件不存在时抛出该异常. + * @throws IOException 当操作执行失败时抛出异常. + */ + public InputStream read(String path) throws IOException { + if (!exists(path)) { + throw new FileNotFoundException(path); + } + return sftpClient.read(path); + } + + /** + * 写入数据到指定目录. + * @param path 待写入的路径. + * @return 返回数据输出流. + * @throws FileNotFoundException 当文件不存在时抛出该异常. + * @throws IOException 如果操作失败则抛出异常. + */ + public OutputStream write(String path) throws IOException { + if (!exists(path)) { + throw new FileNotFoundException(path); + } + return sftpClient.write(path, SftpClient.OpenMode.Write); + } + + /** + * 检查指定路径是否存在. + * @param path 待检查的路径. + * @return 如果存在, 返回 {@code true}, 如果文件不存在, 返回 {@code false}. + * @throws IOException 执行失败时抛出. + */ + public boolean exists(String path) throws IOException { + try { + return getAttributes(path) != null; + } catch (IOException e) { + if (e instanceof SftpException sftpException) { + if (sftpException.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) { + return false; + } + } + throw e; + } + } + + /** + * 是否为一个目录. + * @param path 待检查路径. + * @return 如果是一个目录, 返回 {@code true}. + * @throws FileNotFoundException 当路径不存在时抛出该异常. + * @throws IOException 如果执行失败则抛出异常. + */ + public boolean isDirectory(String path) throws IOException { + if (!exists(path)) { + throw new FileNotFoundException(path); + } + return getAttributes(path).isDirectory(); + } + + /** + * 是否为一个文件. + * @param path 待检查路径. + * @return 如果是一个文件, 返回 {@code true}. + * @throws FileNotFoundException 当路径不存在时抛出该异常. + * @throws IOException 如果执行失败则抛出异常. + */ + public boolean isFile(String path) throws IOException { + if (!exists(path)) { + throw new FileNotFoundException(path); + } + return getAttributes(path).isRegularFile(); + } + + /** + * 获取文件大小. + * @param path 待获取的路径. + * @return 返回文件大小, 单位 b. + * @throws NoSuchFileException 当指定路径不是一个文件(或符号链接)时抛出. + * @throws FileNotFoundException 当指定路径不存在时抛出. + * @throws IOException 当操作执行失败时抛出. + */ + public long getFileSize(String path) throws IOException { + if (!exists(path)) { + throw new FileNotFoundException(path); + } + SftpClient.Attributes attributes = getAttributes(path); + if (!attributes.isRegularFile()) { + if (attributes.isSymbolicLink()) { + return getFileSize(sftpClient.readLink(path)); + } + throw new NoSuchFileException("Not a file: " + path); + } + + return attributes.getSize(); + } + + /** + * 获取指定路径的属性. + * @param path 待获取属性的路径. + * @return 返回路径所属属性. + * @throws IOException 如果执行失败则抛出异常. + */ + public SftpClient.Attributes getAttributes(String path) throws IOException { + return sftpClient.stat(path); + } + + /** + * 创建文件夹. + * @param path 待创建的目录. + * @return 当文件夹已存在时返回 {@code false}, 不存在且创建成功则返回 {@code true}. + * @throws IOException 如果操作执行失败则抛出异常. + */ + public boolean mkdir(String path) throws IOException { + if (exists(path)) { + return false; + } + sftpClient.mkdir(path); + return true; + } + + /** + * 创建新文件. + * @param path 待创建的文件路径. + * @return 当文件已存在时返回 {@code false}, 不存在且创建成功则返回 {@code true}. + * @throws IOException 如果操作失败啧抛出异常. + */ + public boolean createNewFile(String path) throws IOException { + if (exists(path)) { + return false; + } + SftpClient.CloseableHandle handle = sftpClient.open(path, SftpClient.OpenMode.Create); + sftpClient.close(handle); + return true; + } + + /** + * 删除指定路径. + *

该方法内部做了适配, 可兼容文件与文件夹两种类型. + * @param path 待删除的路径. + * @throws IOException 如果删除失败, 抛出异常. + * @throws FileNotFoundException 当指定路径不存在时抛出该异常. + * @throws DirectoryNotEmptyException 当路径为目录且目录不为空时抛出该异常. + */ + public void delete(String path) throws IOException { + if (!exists(path)) { + throw new FileNotFoundException(path); + } + if (isDirectory(path)) { + if (!listFiles(path).isEmpty()) { + throw new DirectoryNotEmptyException(path); + } + sftpClient.rmdir(path); + } else { + sftpClient.remove(path); + } + } + + /** + * 强制删除文件夹. + *

如果删除文件夹, 将会对文件夹执行遍历删除, 文件夹及子文件夹内的所有内容都会被删除. + * @param path 待删除的路径. + * @return 如果目标路径为文件夹且删除成功, 返回 {@code true}, 如果是文件, 则不会执行操作并返回 {@code false}. + * 该行为是防止文件遭到误删. + * @throws IOException 如果操作执行失败, 则抛出异常. + */ + public boolean forceDeleteDir(String path) throws IOException { + if (isDirectory(path)) { + for (String filePath : listFiles(path)) { + String fullFilePath = path + "/" + filePath; + if (isDirectory(fullFilePath)) { + forceDeleteDir(fullFilePath); + } else { + sftpClient.remove(fullFilePath); + } + } + sftpClient.rmdir(path); + return true; + } else { + return false; + } + } + + @Override + public void close() throws IOException { + sftpClient.close(); + } + +} diff --git a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshSession.java b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshSession.java index e43e125..0adf13c 100644 --- a/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshSession.java +++ b/src/main/java/net/lamgc/oracle/sentry/oci/compute/ssh/SshSession.java @@ -1,6 +1,7 @@ package net.lamgc.oracle.sentry.oci.compute.ssh; import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.sftp.client.SftpClientFactory; import java.io.Closeable; import java.io.IOException; @@ -33,6 +34,17 @@ public class SshSession implements Closeable { return new CommandExecSession(clientSession.createExecChannel(command)); } + /** + * 创建 Sftp 会话. + *

可通过会话操作 Sftp. + * @return 返回 Sftp 会话. + * @throws IOException 如果创建失败, 将抛出异常. + */ + public SftpSession createSftpSession() throws IOException { + SftpClientFactory factory = SftpClientFactory.instance(); + return new SftpSession(factory.createSftpClient(clientSession)); + } + /** * 关闭 SSH 连接会话, 该连接会话所属的其他会话将会一同被关闭. * @throws IOException 关闭失败时抛出异常,