1+ package fr .free .nrw .commons ;
2+
3+ import android .net .Uri ;
4+ import android .os .Looper ;
5+ import android .os .SystemClock ;
6+ import com .facebook .imagepipeline .common .BytesRange ;
7+ import com .facebook .imagepipeline .image .EncodedImage ;
8+ import com .facebook .imagepipeline .producers .BaseNetworkFetcher ;
9+ import com .facebook .imagepipeline .producers .BaseProducerContextCallbacks ;
10+ import com .facebook .imagepipeline .producers .Consumer ;
11+ import com .facebook .imagepipeline .producers .FetchState ;
12+ import com .facebook .imagepipeline .producers .NetworkFetcher ;
13+ import com .facebook .imagepipeline .producers .ProducerContext ;
14+ import java .io .IOException ;
15+ import java .util .HashMap ;
16+ import java .util .Map ;
17+ import java .util .concurrent .Executor ;
18+ import okhttp3 .CacheControl ;
19+ import okhttp3 .Call ;
20+ import okhttp3 .OkHttpClient ;
21+ import okhttp3 .Request ;
22+ import okhttp3 .Response ;
23+ import okhttp3 .ResponseBody ;
24+
25+ /** Network fetcher that uses OkHttp 3 as a backend. */
26+ public class CustomNetworkFetcher
27+ extends BaseNetworkFetcher <CustomNetworkFetcher .OkHttpNetworkFetchState > {
28+
29+ public static class OkHttpNetworkFetchState extends FetchState {
30+
31+ public long submitTime ;
32+ public long responseTime ;
33+ public long fetchCompleteTime ;
34+
35+ public OkHttpNetworkFetchState (
36+ Consumer <EncodedImage > consumer , ProducerContext producerContext ) {
37+ super (consumer , producerContext );
38+ }
39+ }
40+
41+ private static final String QUEUE_TIME = "queue_time" ;
42+ private static final String FETCH_TIME = "fetch_time" ;
43+ private static final String TOTAL_TIME = "total_time" ;
44+ private static final String IMAGE_SIZE = "image_size" ;
45+
46+ private final Call .Factory mCallFactory ;
47+ private final CacheControl mCacheControl ;
48+
49+ private Executor mCancellationExecutor ;
50+
51+ /** @param okHttpClient client to use */
52+ public CustomNetworkFetcher (OkHttpClient okHttpClient ) {
53+ this (okHttpClient , okHttpClient .dispatcher ().executorService ());
54+ }
55+
56+ /**
57+ * @param callFactory custom {@link Call.Factory} for fetching image from the network
58+ * @param cancellationExecutor executor on which fetching cancellation is performed if
59+ * cancellation is requested from the UI Thread
60+ */
61+ public CustomNetworkFetcher (Call .Factory callFactory , Executor cancellationExecutor ) {
62+ this (callFactory , cancellationExecutor , true );
63+ }
64+
65+ /**
66+ * @param callFactory custom {@link Call.Factory} for fetching image from the network
67+ * @param cancellationExecutor executor on which fetching cancellation is performed if
68+ * cancellation is requested from the UI Thread
69+ * @param disableOkHttpCache true if network requests should not be cached by OkHttp
70+ */
71+ public CustomNetworkFetcher (
72+ Call .Factory callFactory , Executor cancellationExecutor , boolean disableOkHttpCache ) {
73+ mCallFactory = callFactory ;
74+ mCancellationExecutor = cancellationExecutor ;
75+ mCacheControl = disableOkHttpCache ? new CacheControl .Builder ().noStore ().build () : null ;
76+ }
77+
78+ @ Override
79+ public OkHttpNetworkFetchState createFetchState (
80+ Consumer <EncodedImage > consumer , ProducerContext context ) {
81+ return new OkHttpNetworkFetchState (consumer , context );
82+ }
83+
84+ @ Override
85+ public void fetch (
86+ final OkHttpNetworkFetchState fetchState , final NetworkFetcher .Callback callback ) {
87+ fetchState .submitTime = SystemClock .elapsedRealtime ();
88+ final Uri uri = fetchState .getUri ();
89+
90+ try {
91+ final Request .Builder requestBuilder = new Request .Builder ().url (uri .toString ()).get ();
92+
93+ if (mCacheControl != null ) {
94+ requestBuilder .cacheControl (mCacheControl );
95+ }
96+
97+ final BytesRange bytesRange = fetchState .getContext ().getImageRequest ().getBytesRange ();
98+ if (bytesRange != null ) {
99+ requestBuilder .addHeader ("Range" , bytesRange .toHttpRangeHeaderValue ());
100+ }
101+
102+ fetchWithRequest (fetchState , callback , requestBuilder .build ());
103+ } catch (Exception e ) {
104+ // handle error while creating the request
105+ callback .onFailure (e );
106+ }
107+ }
108+
109+ @ Override
110+ public void onFetchCompletion (OkHttpNetworkFetchState fetchState , int byteSize ) {
111+ fetchState .fetchCompleteTime = SystemClock .elapsedRealtime ();
112+ }
113+
114+ @ Override
115+ public Map <String , String > getExtraMap (OkHttpNetworkFetchState fetchState , int byteSize ) {
116+ Map <String , String > extraMap = new HashMap <>(4 );
117+ extraMap .put (QUEUE_TIME , Long .toString (fetchState .responseTime - fetchState .submitTime ));
118+ extraMap .put (FETCH_TIME , Long .toString (fetchState .fetchCompleteTime - fetchState .responseTime ));
119+ extraMap .put (TOTAL_TIME , Long .toString (fetchState .fetchCompleteTime - fetchState .submitTime ));
120+ extraMap .put (IMAGE_SIZE , Integer .toString (byteSize ));
121+ return extraMap ;
122+ }
123+
124+ protected void fetchWithRequest (
125+ final OkHttpNetworkFetchState fetchState ,
126+ final NetworkFetcher .Callback callback ,
127+ final Request request ) {
128+ final Call call = mCallFactory .newCall (request );
129+
130+ fetchState
131+ .getContext ()
132+ .addCallbacks (
133+ new BaseProducerContextCallbacks () {
134+ @ Override
135+ public void onCancellationRequested () {
136+ if (Looper .myLooper () != Looper .getMainLooper ()) {
137+ call .cancel ();
138+ } else {
139+ mCancellationExecutor .execute (
140+ new Runnable () {
141+ @ Override
142+ public void run () {
143+ call .cancel ();
144+ }
145+ });
146+ }
147+ }
148+ });
149+
150+ call .enqueue (
151+ new okhttp3 .Callback () {
152+ @ Override
153+ public void onResponse (Call call , Response response ) throws IOException {
154+ fetchState .responseTime = SystemClock .elapsedRealtime ();
155+ final ResponseBody body = response .body ();
156+ try {
157+ if (!response .isSuccessful ()) {
158+ handleException (
159+ call , new IOException ("Unexpected HTTP code " + response ), callback );
160+ return ;
161+ }
162+
163+ BytesRange responseRange =
164+ BytesRange .fromContentRangeHeader (response .header ("Content-Range" ));
165+ if (responseRange != null
166+ && !(responseRange .from == 0
167+ && responseRange .to == BytesRange .TO_END_OF_CONTENT )) {
168+ // Only treat as a partial image if the range is not all of the content
169+ fetchState .setResponseBytesRange (responseRange );
170+ fetchState .setOnNewResultStatusFlags (Consumer .IS_PARTIAL_RESULT );
171+ }
172+
173+ long contentLength = body .contentLength ();
174+ if (contentLength < 0 ) {
175+ contentLength = 0 ;
176+ }
177+ callback .onResponse (body .byteStream (), (int ) contentLength );
178+ } catch (Exception e ) {
179+ handleException (call , e , callback );
180+ } finally {
181+ body .close ();
182+ }
183+ }
184+
185+ @ Override
186+ public void onFailure (Call call , IOException e ) {
187+ handleException (call , e , callback );
188+ }
189+ });
190+ }
191+
192+ /**
193+ * Handles exceptions.
194+ *
195+ * <p>OkHttp notifies callers of cancellations via an IOException. If IOException is caught after
196+ * request cancellation, then the exception is interpreted as successful cancellation and
197+ * onCancellation is called. Otherwise onFailure is called.
198+ */
199+ private void handleException (final Call call , final Exception e , final Callback callback ) {
200+ if (call .isCanceled ()) {
201+ callback .onCancellation ();
202+ } else {
203+ callback .onFailure (e );
204+ }
205+ }
206+ }
0 commit comments