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