@@ -50,25 +50,47 @@ public void setPreserveAttributes(boolean preserveAttributes) {
5050 @ Override
5151 public void upload (String source , String dest )
5252 throws IOException {
53- upload (new FileSystemFile (source ), dest );
53+ upload (source , dest , 0 );
54+ }
55+
56+ @ Override
57+ public void upload (String source , String dest , long byteOffset )
58+ throws IOException {
59+ upload (new FileSystemFile (source ), dest , byteOffset );
5460 }
5561
5662 @ Override
5763 public void download (String source , String dest )
5864 throws IOException {
59- download (source , new FileSystemFile (dest ));
65+ download (source , dest , 0 );
66+ }
67+
68+ @ Override
69+ public void download (String source , String dest , long byteOffset )
70+ throws IOException {
71+ download (source , new FileSystemFile (dest ), byteOffset );
6072 }
6173
6274 @ Override
6375 public void upload (LocalSourceFile localFile , String remotePath ) throws IOException {
64- new Uploader (localFile , remotePath ).upload (getTransferListener ());
76+ upload (localFile , remotePath , 0 );
77+ }
78+
79+ @ Override
80+ public void upload (LocalSourceFile localFile , String remotePath , long byteOffset ) throws IOException {
81+ new Uploader (localFile , remotePath ).upload (getTransferListener (), byteOffset );
6582 }
6683
6784 @ Override
6885 public void download (String source , LocalDestFile dest ) throws IOException {
86+ download (source , dest , 0 );
87+ }
88+
89+ @ Override
90+ public void download (String source , LocalDestFile dest , long byteOffset ) throws IOException {
6991 final PathComponents pathComponents = engine .getPathHelper ().getComponents (source );
7092 final FileAttributes attributes = engine .stat (source );
71- new Downloader ().download (getTransferListener (), new RemoteResourceInfo (pathComponents , attributes ), dest );
93+ new Downloader ().download (getTransferListener (), new RemoteResourceInfo (pathComponents , attributes ), dest , byteOffset );
7294 }
7395
7496 public void setUploadFilter (LocalFileFilter uploadFilter ) {
@@ -92,7 +114,8 @@ private class Downloader {
92114 @ SuppressWarnings ("PMD.MissingBreakInSwitch" )
93115 private void download (final TransferListener listener ,
94116 final RemoteResourceInfo remote ,
95- final LocalDestFile local ) throws IOException {
117+ final LocalDestFile local ,
118+ final long byteOffset ) throws IOException {
96119 final LocalDestFile adjustedFile ;
97120 switch (remote .getAttributes ().getType ()) {
98121 case DIRECTORY :
@@ -101,8 +124,9 @@ private void download(final TransferListener listener,
101124 case UNKNOWN :
102125 log .warn ("Server did not supply information about the type of file at `{}` " +
103126 "-- assuming it is a regular file!" , remote .getPath ());
127+ // fall through
104128 case REGULAR :
105- adjustedFile = downloadFile (listener .file (remote .getName (), remote .getAttributes ().getSize ()), remote , local );
129+ adjustedFile = downloadFile (listener .file (remote .getName (), remote .getAttributes ().getSize ()), remote , local , byteOffset );
106130 break ;
107131 default :
108132 throw new IOException (remote + " is not a regular file or directory" );
@@ -119,7 +143,7 @@ private LocalDestFile downloadDir(final TransferListener listener,
119143 final RemoteDirectory rd = engine .openDir (remote .getPath ());
120144 try {
121145 for (RemoteResourceInfo rri : rd .scan (getDownloadFilter ()))
122- download (listener , rri , adjusted .getChild (rri .getName ()));
146+ download (listener , rri , adjusted .getChild (rri .getName ()), 0 ); // not supporting individual byte offsets for these files
123147 } finally {
124148 rd .close ();
125149 }
@@ -128,13 +152,15 @@ private LocalDestFile downloadDir(final TransferListener listener,
128152
129153 private LocalDestFile downloadFile (final StreamCopier .Listener listener ,
130154 final RemoteResourceInfo remote ,
131- final LocalDestFile local )
155+ final LocalDestFile local ,
156+ final long byteOffset )
132157 throws IOException {
133158 final LocalDestFile adjusted = local .getTargetFile (remote .getName ());
134159 final RemoteFile rf = engine .open (remote .getPath ());
135160 try {
136- final RemoteFile .ReadAheadRemoteFileInputStream rfis = rf .new ReadAheadRemoteFileInputStream (16 );
137- final OutputStream os = adjusted .getOutputStream ();
161+ log .debug ("Attempting to download {} with offset={}" , remote .getPath (), byteOffset );
162+ final RemoteFile .ReadAheadRemoteFileInputStream rfis = rf .new ReadAheadRemoteFileInputStream (16 , byteOffset );
163+ final OutputStream os = adjusted .getOutputStream (byteOffset != 0 );
138164 try {
139165 new StreamCopier (rfis , os , engine .getLoggerFactory ())
140166 .bufSize (engine .getSubsystem ().getLocalMaxPacketSize ())
@@ -173,17 +199,17 @@ private Uploader(final LocalSourceFile source, final String remote) {
173199 this .remote = remote ;
174200 }
175201
176- private void upload (final TransferListener listener ) throws IOException {
202+ private void upload (final TransferListener listener , long byteOffset ) throws IOException {
177203 if (source .isDirectory ()) {
178204 makeDirIfNotExists (remote ); // Ensure that the directory exists
179205 uploadDir (listener .directory (source .getName ()), source , remote );
180206 setAttributes (source , remote );
181207 } else if (source .isFile () && isDirectory (remote )) {
182208 String adjustedRemote = engine .getPathHelper ().adjustForParent (this .remote , source .getName ());
183- uploadFile (listener .file (source .getName (), source .getLength ()), source , adjustedRemote );
209+ uploadFile (listener .file (source .getName (), source .getLength ()), source , adjustedRemote , byteOffset );
184210 setAttributes (source , adjustedRemote );
185211 } else if (source .isFile ()) {
186- uploadFile (listener .file (source .getName (), source .getLength ()), source , remote );
212+ uploadFile (listener .file (source .getName (), source .getLength ()), source , remote , byteOffset );
187213 setAttributes (source , remote );
188214 } else {
189215 throw new IOException (source + " is not a file or directory" );
@@ -192,13 +218,14 @@ private void upload(final TransferListener listener) throws IOException {
192218
193219 private void upload (final TransferListener listener ,
194220 final LocalSourceFile local ,
195- final String remote )
221+ final String remote ,
222+ final long byteOffset )
196223 throws IOException {
197224 final String adjustedPath ;
198225 if (local .isDirectory ()) {
199226 adjustedPath = uploadDir (listener .directory (local .getName ()), local , remote );
200227 } else if (local .isFile ()) {
201- adjustedPath = uploadFile (listener .file (local .getName (), local .getLength ()), local , remote );
228+ adjustedPath = uploadFile (listener .file (local .getName (), local .getLength ()), local , remote , byteOffset );
202229 } else {
203230 throw new IOException (local + " is not a file or directory" );
204231 }
@@ -217,22 +244,34 @@ private String uploadDir(final TransferListener listener,
217244 throws IOException {
218245 makeDirIfNotExists (remote );
219246 for (LocalSourceFile f : local .getChildren (getUploadFilter ()))
220- upload (listener , f , engine .getPathHelper ().adjustForParent (remote , f .getName ()));
247+ upload (listener , f , engine .getPathHelper ().adjustForParent (remote , f .getName ()), 0 ); // not supporting individual byte offsets for these files
221248 return remote ;
222249 }
223250
224251 private String uploadFile (final StreamCopier .Listener listener ,
225252 final LocalSourceFile local ,
226- final String remote )
253+ final String remote ,
254+ final long byteOffset )
227255 throws IOException {
228- final String adjusted = prepareFile (local , remote );
256+ final String adjusted = prepareFile (local , remote , byteOffset );
229257 RemoteFile rf = null ;
230258 InputStream fis = null ;
231259 RemoteFile .RemoteFileOutputStream rfos = null ;
260+ EnumSet <OpenMode > modes ;
232261 try {
233- rf = engine .open (adjusted , EnumSet .of (OpenMode .WRITE , OpenMode .CREAT , OpenMode .TRUNC ));
262+ if (byteOffset == 0 ) {
263+ // Starting at the beginning, overwrite/create
264+ modes = EnumSet .of (OpenMode .WRITE , OpenMode .CREAT , OpenMode .TRUNC );
265+ } else {
266+ // Starting at some offset, append
267+ modes = EnumSet .of (OpenMode .WRITE , OpenMode .APPEND );
268+ }
269+
270+ log .debug ("Attempting to upload {} with offset={}" , local .getName (), byteOffset );
271+ rf = engine .open (adjusted , modes );
234272 fis = local .getInputStream ();
235- rfos = rf .new RemoteFileOutputStream (0 , 16 );
273+ fis .skip (byteOffset );
274+ rfos = rf .new RemoteFileOutputStream (byteOffset , 16 );
236275 new StreamCopier (fis , rfos , engine .getLoggerFactory ())
237276 .bufSize (engine .getSubsystem ().getRemoteMaxPacketSize () - rf .getOutgoingPacketOverhead ())
238277 .keepFlushing (false )
@@ -294,7 +333,7 @@ private boolean isDirectory(final String remote) throws IOException {
294333 }
295334 }
296335
297- private String prepareFile (final LocalSourceFile local , final String remote )
336+ private String prepareFile (final LocalSourceFile local , final String remote , final long byteOffset )
298337 throws IOException {
299338 final FileAttributes attrs ;
300339 try {
@@ -309,7 +348,7 @@ private String prepareFile(final LocalSourceFile local, final String remote)
309348 if (attrs .getMode ().getType () == FileMode .Type .DIRECTORY ) {
310349 throw new IOException ("Trying to upload file " + local .getName () + " to path " + remote + " but that is a directory" );
311350 } else {
312- log .debug ("probeFile: {} is a {} file that will be replaced " , remote , attrs .getMode ().getType ());
351+ log .debug ("probeFile: {} is a {} file that will be {} " , remote , attrs .getMode ().getType (), byteOffset > 0 ? "resumed" : "replaced" );
313352 return remote ;
314353 }
315354 }
0 commit comments