package com.apollographql.apollo.internal.cache.http;
import com.apollographql.apollo.cache.http.HttpCacheRecord;
import com.apollographql.apollo.cache.http.HttpCacheRecordEditor;
import com.apollographql.apollo.cache.http.HttpCacheStore;
import com.apollographql.apollo.internal.util.ApolloLogger;
import java.io.IOException;
import javax.annotation.Nonnull;
import okhttp3.Interceptor;
import okhttp3.Response;
import okio.ForwardingSource;
import okio.Sink;
import okio.Source;
import static com.apollographql.apollo.api.internal.Utils.checkNotNull;
import static com.apollographql.apollo.internal.cache.http.Utils.copyResponseBody;
@SuppressWarnings("WeakerAccess") public final class HttpCache {
public static final String CACHE_KEY_HEADER = "X-APOLLO-CACHE-KEY";
public static final String CACHE_FETCH_STRATEGY_HEADER = "X-APOLLO-CACHE-FETCH-STRATEGY";
public static final String CACHE_SERVED_DATE_HEADER = "X-APOLLO-SERVED-DATE";
public static final String CACHE_PREFETCH_HEADER = "X-APOLLO-PREFETCH";
public static final String CACHE_EXPIRE_TIMEOUT_HEADER = "X-APOLLO-EXPIRE-TIMEOUT";
public static final String CACHE_EXPIRE_AFTER_READ_HEADER = "X-APOLLO-EXPIRE-AFTER-READ";
private final HttpCacheStore cacheStore;
private final ApolloLogger logger;
public HttpCache(@Nonnull HttpCacheStore cacheStore, @Nonnull ApolloLogger logger) {
this.cacheStore = checkNotNull(cacheStore, "cacheStore can't be null");
this.logger = checkNotNull(logger, "logger can't be null");
}
public void clear() {
try {
cacheStore.delete();
} catch (IOException e) {
logger.e(e, "Failed to clear http cache");
}
}
public void remove(@Nonnull String cacheKey) throws IOException {
cacheStore.remove(cacheKey);
}
public void removeQuietly(@Nonnull String cacheKey) {
try {
remove(cacheKey);
} catch (Exception ignore) {
logger.w(ignore, "Failed to remove cached record for key: %s", cacheKey);
}
}
public Response read(@Nonnull final String cacheKey) {
return read(cacheKey, false);
}
public Response read(@Nonnull final String cacheKey, final boolean expireAfterRead) {
HttpCacheRecord responseCacheRecord = null;
try {
responseCacheRecord = cacheStore.cacheRecord(cacheKey);
if (responseCacheRecord == null) {
return null;
}
final HttpCacheRecord cacheRecord = responseCacheRecord;
Source cacheResponseSource = new ForwardingSource(responseCacheRecord.bodySource()) {
@Override public void close() throws IOException {
super.close();
closeQuietly(cacheRecord);
if (expireAfterRead) {
removeQuietly(cacheKey);
}
}
};
Response response = new ResponseHeaderRecord(responseCacheRecord.headerSource()).response();
String contentType = response.header("Content-Type");
String contentLength = response.header("Content-Length");
return response.newBuilder()
.body(new CacheResponseBody(cacheResponseSource, contentType, contentLength))
.build();
} catch (Exception e) {
closeQuietly(responseCacheRecord);
logger.e(e, "Failed to read http cache entry for key: %s", cacheKey);
return null;
}
}
public Interceptor interceptor() {
return new HttpCacheInterceptor(this, logger);
}
Response cacheProxy(@Nonnull Response response, @Nonnull String cacheKey) {
HttpCacheRecordEditor cacheRecordEditor = null;
try {
cacheRecordEditor = cacheStore.cacheRecordEditor(cacheKey);
if (cacheRecordEditor != null) {
Sink headerSink = cacheRecordEditor.headerSink();
try {
new ResponseHeaderRecord(response).writeTo(headerSink);
} finally {
closeQuietly(headerSink);
}
return response.newBuilder()
.body(new ResponseBodyProxy(cacheRecordEditor, response, logger))
.build();
}
} catch (Exception e) {
abortQuietly(cacheRecordEditor);
logger.e(e, "Failed to proxy http response for key: %s", cacheKey);
}
return response;
}
void write(@Nonnull Response response, @Nonnull String cacheKey) {
HttpCacheRecordEditor cacheRecordEditor = null;
try {
cacheRecordEditor = cacheStore.cacheRecordEditor(cacheKey);
if (cacheRecordEditor != null) {
Sink headerSink = cacheRecordEditor.headerSink();
try {
new ResponseHeaderRecord(response).writeTo(headerSink);
} finally {
closeQuietly(headerSink);
}
Sink bodySink = cacheRecordEditor.bodySink();
try {
copyResponseBody(response, bodySink);
} finally {
closeQuietly(bodySink);
}
cacheRecordEditor.commit();
}
} catch (Exception e) {
abortQuietly(cacheRecordEditor);
logger.e(e, "Failed to cache http response for key: %s", cacheKey);
}
}
private void closeQuietly(HttpCacheRecord cacheRecord) {
try {
if (cacheRecord != null) {
cacheRecord.close();
}
} catch (Exception ignore) {
logger.w(ignore, "Failed to close cache record");
}
}
private void abortQuietly(HttpCacheRecordEditor cacheRecordEditor) {
try {
if (cacheRecordEditor != null) {
cacheRecordEditor.abort();
}
} catch (Exception ignore) {
logger.w(ignore, "Failed to abort cache record edit");
}
}
private void closeQuietly(Sink sink) {
try {
sink.close();
} catch (Exception ignore) {
logger.w(ignore, "Failed to close sink");
}
}
}