1 package com.terradue.jcatalogue.client.download;
2
3 import static java.nio.charset.Charset.forName;
4 import static java.lang.String.format;
5 import static java.security.MessageDigest.getInstance;
6
7 import java.io.Closeable;
8 import java.io.File;
9 import java.io.FileNotFoundException;
10 import java.io.FileOutputStream;
11 import java.io.IOException;
12 import java.nio.ByteBuffer;
13 import java.nio.channels.FileChannel;
14 import java.nio.charset.Charset;
15 import java.security.MessageDigest;
16 import java.security.NoSuchAlgorithmException;
17 import java.util.List;
18
19 import com.ning.http.client.AsyncHandler;
20 import com.ning.http.client.HttpResponseBodyPart;
21 import com.ning.http.client.HttpResponseHeaders;
22 import com.ning.http.client.HttpResponseStatus;
23
24
25
26
27 final class HttpDownloadHandler<T>
28 implements AsyncHandler<T>
29 {
30
31
32
33
34 private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
35
36 private static final Charset UTF_8 = forName( "UTF-8" );
37
38 private static final String CONTENT_LENGTH = "Content-Length";
39
40 private static final String MD5 = "MD5";
41
42 private final File targetFile;
43
44 private final FileOutputStream output;
45
46 private final DownloadHandler<T> downloadHandler;
47
48 private final MessageDigest md5Digest;
49
50 private long contentLength = -1;
51
52 private long downloadCounter = 0;
53
54 public HttpDownloadHandler( File targetFile, DownloadHandler<T> downloadHandler )
55 throws FileNotFoundException
56 {
57 this.targetFile = targetFile;
58 output = new FileOutputStream( targetFile );
59 this.downloadHandler = downloadHandler;
60
61 try
62 {
63 md5Digest = getInstance( MD5 );
64 }
65 catch ( NoSuchAlgorithmException e )
66 {
67
68 throw new RuntimeException( e.getMessage() );
69 }
70 }
71
72 @Override
73 public void onThrowable( Throwable t )
74 {
75 downloadHandler.onError( t );
76 }
77
78 @Override
79 public STATE onBodyPartReceived( HttpResponseBodyPart bodyPart )
80 throws Exception
81 {
82 bodyPart.writeTo( output );
83 md5Digest.update( bodyPart.getBodyPartBytes() );
84
85
86 if ( contentLength > 0 )
87 {
88 downloadCounter += bodyPart.getBodyPartBytes().length;
89
90 downloadHandler.onContentDownloadProgress( downloadCounter, contentLength );
91 }
92
93 return STATE.CONTINUE;
94 }
95
96 @Override
97 public STATE onStatusReceived( HttpResponseStatus responseStatus )
98 throws Exception
99 {
100 return STATE.CONTINUE;
101 }
102
103 @Override
104 public STATE onHeadersReceived( HttpResponseHeaders headers )
105 throws Exception
106 {
107 List<String> contentLength = headers.getHeaders().get( CONTENT_LENGTH );
108
109 if ( !contentLength.isEmpty() )
110 {
111 this.contentLength = Long.valueOf( contentLength.iterator().next() ).longValue();
112 }
113
114 return STATE.CONTINUE;
115 }
116
117 @Override
118 public T onCompleted()
119 throws Exception
120 {
121 output.flush();
122 closeQuietly( output );
123
124 writeMd5ChecksumFile();
125
126 return downloadHandler.onCompleted( targetFile );
127 }
128
129 private void writeMd5ChecksumFile()
130 {
131 File checksumFile = new File( targetFile.getParent(), format( "%.md5", targetFile.getName() ) );
132 try
133 {
134 FileChannel digestChannel = new FileOutputStream( checksumFile ).getChannel();
135
136 byte[] checksumData = md5Digest.digest();
137 char[] hexData = new char[checksumData.length << 1];
138
139 for ( int i = 0, j = 0; i < checksumData.length; i++ )
140 {
141 hexData[j++] = HEX_DIGITS[( 0xF0 & hexData[i] ) >>> 4];
142 hexData[j++] = HEX_DIGITS[0x0F & hexData[i]];
143 }
144
145 digestChannel.write( ByteBuffer.wrap( new String( hexData ).getBytes( UTF_8 ) ) );
146
147 digestChannel.close();
148 }
149 catch ( Exception e )
150 {
151
152 }
153 }
154
155 private static void closeQuietly( Closeable closeable )
156 {
157 if ( closeable != null )
158 {
159 try
160 {
161 closeable.close();
162 }
163 catch ( IOException e )
164 {
165
166 }
167 }
168 }
169
170 }