Ftp Resume Transfer
Resumable transfer significantly improves the efficiency of file transfers,It is very efficient and convenient.Let’s try it in java
Dependencies
Using commons-net 3.11.1 under spring-boot-starter-parent 3.2.6,otherwise fatal error might been occur.
10 <parent>
11 <groupId>org.springframework.boot</groupId>
12 <artifactId>spring-boot-starter-parent</artifactId>
13 <version>3.2.6</version>
14 <relativePath/> <!-- lookup parent from repository -->
15 </parent>
16
17 <dependency>
18 <groupId>commons-net</groupId>
19 <artifactId>commons-net</artifactId>
20 <version>3.11.1</version>
21 </dependency>
FTP Client
You will see the class org.apache.commons.net.ftp.FTPClient from commons-net.
10 package com.neoemacs.video.upload.tool.server.service;
11
12 import java.io.IOException;
13 import org.apache.commons.net.ftp.FTPClient;
14 import org.springframework.beans.factory.annotation.Value;
15 import org.springframework.context.annotation.Bean;
16 import org.springframework.context.annotation.Configuration;
17
18 @Configuration
19 public class FtpClientConfiguration {
20
21 @Value("${ftp.server}")
22 private String ftpServer;
23
24 @Value("${ftp.account}")
25 private String ftpAccount;
26
27 @Value("${ftp.password}")
28 private String ftpPassword;
29
30 @Bean
31 public FTPClient ftpClient() {
32 FTPClient ftpClient = new FTPClient();
33 try {
34 ftpClient.connect(ftpServer);
35 ftpClient.login(ftpAccount, ftpPassword);
36 ftpClient.enterLocalPassiveMode();
37 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
38 } catch (IOException e) {
39 throw new RuntimeException("Failed to create FTP client", e);
40 }
41 return ftpClient;
42 }
43 }
Define Interface
Every ftp account have privilege to access a particular director which indeed is your client’s root. Obviously workdir is a sub director under the ftp server’s root. Here use two paramter to make the function’s purpose more clear.
10 /**
11 * Program wil upload file on by one via breanpoint resume.
12 *
13 * e.g. uploadFile("C:/Desktop/tmp", "/record_rtp/test/20240309")
14 * Files located at C:/Desktop/tmp will upload to ${workdir}/record_rtp/test/20240309/
15 *
16 */
17 public void uploadFile(String localFileDir, String ftpDir) throws IOException;
File Offset
The principle of it is query list files info from ftp server,use the size of ftp file as local file’s offset.
10 /**
11 * get file uploaded offset from ftp server
12 */
13 private long checkFileOffsetOnFtp(String fileName) throws IOException {
14 String remoteFile = fileName;
15 FTPFile[] files = ftpClient.listFiles(remoteFile);
16 if (files.length == 1 && files[0].isFile()) {
17 return files[0].getSize();
18 }
19 return 0;
20 }
Start Update
To start update,we need constitute a inputstream and skip it’s offset,and use a remote addr to recive stream.
10
11 @Autowired
12 FTPClient ftpClient;
13
14 File localFile = new File(localFileDir);
15 InputStream inputStream = new FileInputStream(localFile);
16
17 String ftpFilePath = " ";
18 long offset = checkFileOffsetOnFtp(ftpFilePath);
19 inputStream.skip(offset);
20
21 ftpClient.setRestartOffset(offset);
22 boolean done = ftpClient.storeFile(ftpFilePath, inputStream);
Monitor Progress
The CopyStreamListener#bytesTransferred could obtains the progress of uploading.You need override the bytesTransferred , totalBytesTransferred gives the uploaded bytes but do not contains offset bytes from previously process.You could use CopyStreamListener divide by fileSize to got the uploading progress when both of their unit are bytes.
10 ftpClient.setCopyStreamListener(new CopyStreamListener() {
11 private long megsTransferred = 0;
12
13 @Override
14 public void bytesTransferred(long totalBytesTransferred,
15 int bytesTransferred, long streamSize) {
16 long megs = totalBytesTransferred / (1024 * 1024);
17 if (megs > megsTransferred) {
18 megsTransferred = megs;
19 log.info("==== [progress]: {}",
20 (double) (totalBytesTransferred + offset) / fileSize * 100);
21 }
22 }
23
24 @Override
25 public void bytesTransferred(CopyStreamEvent event) {
26 // 此方法在复制流事件时调用
27 }
28 });
Don’t drive bytesTransferred frequency to high.Case It’s might slow down the uploading speed.
Full Code
1 package com.minxiot.video.upload.tool.server.service;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import org.apache.commons.net.ftp.FTPClient;
8 import org.apache.commons.net.ftp.FTPFile;
9 import org.apache.commons.net.io.CopyStreamEvent;
10 import org.apache.commons.net.io.CopyStreamListener;
11 import org.springframework.beans.factory.annotation.Autowired;
12 import org.springframework.stereotype.Service;
13 import lombok.extern.slf4j.Slf4j;
14
15 @Slf4j
16 @Service
17 public class FileUploadService {
18
19 @Autowired
20 FTPClient ftpClient;
21
22 public void uploadFile(String localFileDir, String ftpDir) throws IOException {
23 File localDir = new File(localFileDir);
24 if (!localDir.isDirectory()) {
25 throw new IllegalArgumentException("The provided path is not a directory");
26 }
27
28 for (File eachLocalFile : localDir.listFiles()) {
29 if (eachLocalFile.isFile()) {
30 boolean isConnected = ftpClient.isConnected();
31 log.info("==== [ftp.isConnected]: {} ", isConnected);
32 log.info("==== [local.fileName]: {} ", eachLocalFile.getAbsolutePath());
33 String ftpFileName = ftpDir + eachLocalFile.getName();
34 log.info("==== [ftp.fileName]: {}", ftpFileName);
35 if (!ftpClient.changeWorkingDirectory(ftpDir)) {
36 log.error("==== [ftp.error]: {}, Cannot change to working Dir:{}",
37 ftpClient.getReplyString(), ftpDir);
38 continue;
39 }
40 long offset = checkFileOffsetOnFtp(ftpFileName);
41 log.info("==== [ftp.fileName.offset]: {}:{}", ftpFileName, offset);
42 try (InputStream inputStream = new FileInputStream(eachLocalFile)) {
43 if (offset > 0) {
44 inputStream.skip(offset);
45 ftpClient.setRestartOffset(offset);
46 }
47 long fileSize = eachLocalFile.length();
48 ftpClient.setCopyStreamListener(new CopyStreamListener() {
49 private long megsTransferred = 0;
50
51 @Override
52 public void bytesTransferred(long totalBytesTransferred,
53 int bytesTransferred, long streamSize) {
54 long megs = totalBytesTransferred / (1024 * 1024);
55 if (megs > megsTransferred) {
56 megsTransferred = megs;
57 log.info("==== [progress]: {}",
58 (double) (totalBytesTransferred + offset) / fileSize * 100);
59 }
60 }
61
62 @Override
63 public void bytesTransferred(CopyStreamEvent event) {
64 // 此方法在复制流事件时调用
65 }
66 });
67
68 boolean done = ftpClient.storeFile(ftpFileName, inputStream);
69 if (!done) {
70 log.error("==== [ftp.error]: ReplyCode:{},ReplyString:{}",
71 ftpClient.getReplyCode(), ftpClient.getReplyString());
72 }
73 } catch (IOException e) {
74 log.error("==== [ftp.error]: {}", e);
75 continue;
76 }
77 }
78 }
79 }
80
81 /**
82 * get file uploaded offset from ftp server
83 */
84 private long checkFileOffsetOnFtp(String fileName) throws IOException {
85 String remoteFile = fileName;
86 FTPFile[] files = ftpClient.listFiles(remoteFile);
87 if (files.length == 1 && files[0].isFile()) {
88 return files[0].getSize();
89 }
90 return 0;
91 }
92
93 }