1 package org.eclipse.aether.connector.basic;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import java.io.Closeable;
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.RandomAccessFile;
29 import java.nio.channels.Channel;
30 import java.nio.channels.FileLock;
31 import java.nio.channels.OverlappingFileLockException;
32 import java.util.UUID;
33 import java.util.concurrent.atomic.AtomicBoolean;
34
35
36
37
38
39
40 final class PartialFile
41 implements Closeable
42 {
43
44 static final String EXT_PART = ".part";
45
46 static final String EXT_LOCK = ".lock";
47
48 interface RemoteAccessChecker
49 {
50
51 void checkRemoteAccess()
52 throws Exception;
53
54 }
55
56 static class LockFile
57 {
58
59 private final File lockFile;
60
61 private final FileLock lock;
62
63 private final AtomicBoolean concurrent;
64
65 LockFile( File partFile, int requestTimeout, RemoteAccessChecker checker )
66 throws Exception
67 {
68 lockFile = new File( partFile.getPath() + EXT_LOCK );
69 concurrent = new AtomicBoolean( false );
70 lock = lock( lockFile, partFile, requestTimeout, checker, concurrent );
71 }
72
73 private static FileLock lock( File lockFile, File partFile, int requestTimeout, RemoteAccessChecker checker,
74 AtomicBoolean concurrent )
75 throws Exception
76 {
77 boolean interrupted = false;
78 try
79 {
80 for ( long lastLength = -1L, lastTime = 0L;; )
81 {
82 FileLock lock = tryLock( lockFile );
83 if ( lock != null )
84 {
85 return lock;
86 }
87
88 long currentLength = partFile.length();
89 long currentTime = System.currentTimeMillis();
90 if ( currentLength != lastLength )
91 {
92 if ( lastLength < 0L )
93 {
94 concurrent.set( true );
95
96
97
98
99
100 checker.checkRemoteAccess();
101 LOGGER.debug( "Concurrent download of {} in progress, awaiting completion", partFile );
102 }
103 lastLength = currentLength;
104 lastTime = currentTime;
105 }
106 else if ( requestTimeout > 0 && currentTime - lastTime > Math.max( requestTimeout, 3 * 1000 ) )
107 {
108 throw new IOException( "Timeout while waiting for concurrent download of " + partFile
109 + " to progress" );
110 }
111
112 try
113 {
114 Thread.sleep( 100 );
115 }
116 catch ( InterruptedException e )
117 {
118 interrupted = true;
119 }
120 }
121 }
122 finally
123 {
124 if ( interrupted )
125 {
126 Thread.currentThread().interrupt();
127 }
128 }
129 }
130
131 private static FileLock tryLock( File lockFile )
132 throws IOException
133 {
134 RandomAccessFile raf = null;
135 FileLock lock = null;
136 try
137 {
138 raf = new RandomAccessFile( lockFile, "rw" );
139 lock = raf.getChannel().tryLock( 0, 1, false );
140
141 if ( lock == null )
142 {
143 raf.close();
144 raf = null;
145 }
146 }
147 catch ( OverlappingFileLockException e )
148 {
149 close( raf );
150 raf = null;
151 lock = null;
152 }
153 catch ( RuntimeException e )
154 {
155 close( raf );
156 raf = null;
157 if ( !lockFile.delete() )
158 {
159 lockFile.deleteOnExit();
160 }
161 throw e;
162 }
163 catch ( IOException e )
164 {
165 close( raf );
166 raf = null;
167 if ( !lockFile.delete() )
168 {
169 lockFile.deleteOnExit();
170 }
171 throw e;
172 }
173 finally
174 {
175 try
176 {
177 if ( lock == null && raf != null )
178 {
179 raf.close();
180 }
181 }
182 catch ( final IOException e )
183 {
184
185 }
186 }
187
188 return lock;
189 }
190
191 private static void close( Closeable file )
192 {
193 try
194 {
195 if ( file != null )
196 {
197 file.close();
198 }
199 }
200 catch ( IOException e )
201 {
202
203 }
204 }
205
206 public boolean isConcurrent()
207 {
208 return concurrent.get();
209 }
210
211 public void close() throws IOException
212 {
213 Channel channel = null;
214 try
215 {
216 channel = lock.channel();
217 lock.release();
218 channel.close();
219 channel = null;
220 }
221 finally
222 {
223 try
224 {
225 if ( channel != null )
226 {
227 channel.close();
228 }
229 }
230 catch ( final IOException e )
231 {
232
233 }
234 finally
235 {
236 if ( !lockFile.delete() )
237 {
238 lockFile.deleteOnExit();
239 }
240 }
241 }
242 }
243
244 @Override
245 public String toString()
246 {
247 return lockFile + " - " + lock.isValid();
248 }
249
250 }
251
252 static class Factory
253 {
254
255 private final boolean resume;
256
257 private final long resumeThreshold;
258
259 private final int requestTimeout;
260
261 private static final Logger LOGGER = LoggerFactory.getLogger( Factory.class );
262
263 Factory( boolean resume, long resumeThreshold, int requestTimeout )
264 {
265 this.resume = resume;
266 this.resumeThreshold = resumeThreshold;
267 this.requestTimeout = requestTimeout;
268 }
269
270 public PartialFile newInstance( File dstFile, RemoteAccessChecker checker )
271 throws Exception
272 {
273 if ( resume )
274 {
275 File partFile = new File( dstFile.getPath() + EXT_PART );
276
277 long reqTimestamp = System.currentTimeMillis();
278 LockFile lockFile = new LockFile( partFile, requestTimeout, checker );
279 if ( lockFile.isConcurrent() && dstFile.lastModified() >= reqTimestamp - 100L )
280 {
281 lockFile.close();
282 return null;
283 }
284 try
285 {
286 if ( !partFile.createNewFile() && !partFile.isFile() )
287 {
288 throw new IOException( partFile.exists() ? "Path exists but is not a file" : "Unknown error" );
289 }
290 return new PartialFile( partFile, lockFile, resumeThreshold );
291 }
292 catch ( IOException e )
293 {
294 lockFile.close();
295 LOGGER.debug( "Cannot create resumable file {}: {}", partFile.getAbsolutePath(), e.getMessage(), e );
296
297 }
298 }
299
300 File tempFile =
301 File.createTempFile( dstFile.getName() + '-' + UUID.randomUUID().toString().replace( "-", "" ), ".tmp",
302 dstFile.getParentFile() );
303 return new PartialFile( tempFile );
304 }
305
306 }
307
308 private final File partFile;
309
310 private final LockFile lockFile;
311
312 private final long threshold;
313
314 private static final Logger LOGGER = LoggerFactory.getLogger( PartialFile.class );
315
316 private PartialFile( File partFile )
317 {
318 this( partFile, null, 0L );
319 }
320
321 private PartialFile( File partFile, LockFile lockFile, long threshold )
322 {
323 this.partFile = partFile;
324 this.lockFile = lockFile;
325 this.threshold = threshold;
326 }
327
328 public File getFile()
329 {
330 return partFile;
331 }
332
333 public boolean isResume()
334 {
335 return lockFile != null && partFile.length() >= threshold;
336 }
337
338 public void close() throws IOException
339 {
340 if ( partFile.exists() && !isResume() )
341 {
342 if ( !partFile.delete() && partFile.exists() )
343 {
344 LOGGER.debug( "Could not delete temporary file {}", partFile );
345 }
346 }
347 if ( lockFile != null )
348 {
349 lockFile.close();
350 }
351 }
352
353 @Override
354 public String toString()
355 {
356 return String.valueOf( getFile() );
357 }
358
359 }