forked from google/volley
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Get Implementation in AsyncCache that takes in a callback function (g…
…oogle#344) Co-authored-by: Scott Phillips <[email protected]>
- Loading branch information
Showing
7 changed files
with
585 additions
and
383 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.android.volley; | ||
|
||
import androidx.annotation.Nullable; | ||
|
||
/** Asynchronous equivalent to the {@link Cache} interface. */ | ||
public abstract class AsyncCache { | ||
|
||
public interface OnGetCompleteCallback { | ||
/** | ||
* Invoked when the read from the cache is complete. | ||
* | ||
* @param entry The entry read from the cache, or null if the read failed or the key did not | ||
* exist in the cache. | ||
*/ | ||
void onGetComplete(@Nullable Cache.Entry entry); | ||
} | ||
|
||
/** | ||
* Retrieves an entry from the cache and sends it back through the {@link | ||
* OnGetCompleteCallback#onGetComplete} function | ||
* | ||
* @param key Cache key | ||
* @param callback Callback that will be notified when the information has been retrieved | ||
*/ | ||
public abstract void get(String key, OnGetCompleteCallback callback); | ||
|
||
// TODO(#181): Implement the rest. | ||
} |
146 changes: 146 additions & 0 deletions
146
src/main/java/com/android/volley/toolbox/CacheHeader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package com.android.volley.toolbox; | ||
|
||
import androidx.annotation.Nullable; | ||
import com.android.volley.Cache; | ||
import com.android.volley.Header; | ||
import com.android.volley.VolleyLog; | ||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
/** Handles holding onto the cache headers for an entry. */ | ||
class CacheHeader { | ||
/** Magic number for current version of cache file format. */ | ||
private static final int CACHE_MAGIC = 0x20150306; | ||
|
||
/** | ||
* The size of the data identified by this CacheHeader on disk (both header and data). | ||
* | ||
* <p>Must be set by the caller after it has been calculated. | ||
* | ||
* <p>This is not serialized to disk. | ||
*/ | ||
long size; | ||
|
||
/** The key that identifies the cache entry. */ | ||
final String key; | ||
|
||
/** ETag for cache coherence. */ | ||
@Nullable final String etag; | ||
|
||
/** Date of this response as reported by the server. */ | ||
final long serverDate; | ||
|
||
/** The last modified date for the requested object. */ | ||
final long lastModified; | ||
|
||
/** TTL for this record. */ | ||
final long ttl; | ||
|
||
/** Soft TTL for this record. */ | ||
final long softTtl; | ||
|
||
/** Headers from the response resulting in this cache entry. */ | ||
final List<Header> allResponseHeaders; | ||
|
||
private CacheHeader( | ||
String key, | ||
String etag, | ||
long serverDate, | ||
long lastModified, | ||
long ttl, | ||
long softTtl, | ||
List<Header> allResponseHeaders) { | ||
this.key = key; | ||
this.etag = "".equals(etag) ? null : etag; | ||
this.serverDate = serverDate; | ||
this.lastModified = lastModified; | ||
this.ttl = ttl; | ||
this.softTtl = softTtl; | ||
this.allResponseHeaders = allResponseHeaders; | ||
} | ||
|
||
/** | ||
* Instantiates a new CacheHeader object. | ||
* | ||
* @param key The key that identifies the cache entry | ||
* @param entry The cache entry. | ||
*/ | ||
CacheHeader(String key, Cache.Entry entry) { | ||
this( | ||
key, | ||
entry.etag, | ||
entry.serverDate, | ||
entry.lastModified, | ||
entry.ttl, | ||
entry.softTtl, | ||
getAllResponseHeaders(entry)); | ||
} | ||
|
||
private static List<Header> getAllResponseHeaders(Cache.Entry entry) { | ||
// If the entry contains all the response headers, use that field directly. | ||
if (entry.allResponseHeaders != null) { | ||
return entry.allResponseHeaders; | ||
} | ||
|
||
// Legacy fallback - copy headers from the map. | ||
return HttpHeaderParser.toAllHeaderList(entry.responseHeaders); | ||
} | ||
|
||
/** | ||
* Reads the header from a CountingInputStream and returns a CacheHeader object. | ||
* | ||
* @param is The InputStream to read from. | ||
* @throws IOException if fails to read header | ||
*/ | ||
static CacheHeader readHeader(DiskBasedCache.CountingInputStream is) throws IOException { | ||
int magic = DiskBasedCacheUtility.readInt(is); | ||
if (magic != CACHE_MAGIC) { | ||
// don't bother deleting, it'll get pruned eventually | ||
throw new IOException(); | ||
} | ||
String key = DiskBasedCacheUtility.readString(is); | ||
String etag = DiskBasedCacheUtility.readString(is); | ||
long serverDate = DiskBasedCacheUtility.readLong(is); | ||
long lastModified = DiskBasedCacheUtility.readLong(is); | ||
long ttl = DiskBasedCacheUtility.readLong(is); | ||
long softTtl = DiskBasedCacheUtility.readLong(is); | ||
List<Header> allResponseHeaders = DiskBasedCacheUtility.readHeaderList(is); | ||
return new CacheHeader( | ||
key, etag, serverDate, lastModified, ttl, softTtl, allResponseHeaders); | ||
} | ||
|
||
/** Creates a cache entry for the specified data. */ | ||
Cache.Entry toCacheEntry(byte[] data) { | ||
Cache.Entry e = new Cache.Entry(); | ||
e.data = data; | ||
e.etag = etag; | ||
e.serverDate = serverDate; | ||
e.lastModified = lastModified; | ||
e.ttl = ttl; | ||
e.softTtl = softTtl; | ||
e.responseHeaders = HttpHeaderParser.toHeaderMap(allResponseHeaders); | ||
e.allResponseHeaders = Collections.unmodifiableList(allResponseHeaders); | ||
return e; | ||
} | ||
|
||
/** Writes the contents of this CacheHeader to the specified OutputStream. */ | ||
boolean writeHeader(OutputStream os) { | ||
try { | ||
DiskBasedCacheUtility.writeInt(os, CACHE_MAGIC); | ||
DiskBasedCacheUtility.writeString(os, key); | ||
DiskBasedCacheUtility.writeString(os, etag == null ? "" : etag); | ||
DiskBasedCacheUtility.writeLong(os, serverDate); | ||
DiskBasedCacheUtility.writeLong(os, lastModified); | ||
DiskBasedCacheUtility.writeLong(os, ttl); | ||
DiskBasedCacheUtility.writeLong(os, softTtl); | ||
DiskBasedCacheUtility.writeHeaderList(allResponseHeaders, os); | ||
os.flush(); | ||
return true; | ||
} catch (IOException e) { | ||
VolleyLog.d("%s", e.toString()); | ||
return false; | ||
} | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
src/main/java/com/android/volley/toolbox/DiskBasedAsyncCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package com.android.volley.toolbox; | ||
|
||
import android.os.Build; | ||
import androidx.annotation.RequiresApi; | ||
import com.android.volley.AsyncCache; | ||
import com.android.volley.VolleyLog; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.ByteBuffer; | ||
import java.nio.channels.AsynchronousFileChannel; | ||
import java.nio.channels.CompletionHandler; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.nio.file.StandardOpenOption; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* AsyncCache implementation that uses Java NIO's AsynchronousFileChannel to perform asynchronous | ||
* disk reads and writes. | ||
*/ | ||
@RequiresApi(Build.VERSION_CODES.O) | ||
public class DiskBasedAsyncCache extends AsyncCache { | ||
|
||
/** Map of the Key, CacheHeader pairs */ | ||
private final Map<String, CacheHeader> mEntries = new LinkedHashMap<>(16, .75f, true); | ||
|
||
/** The supplier for the root directory to use for the cache. */ | ||
private final DiskBasedCacheUtility.FileSupplier mRootDirectorySupplier; | ||
|
||
/** Total amount of space currently used by the cache in bytes. */ | ||
private long mTotalSize = 0; | ||
|
||
/** | ||
* Constructs an instance of the DiskBasedAsyncCache at the specified directory. | ||
* | ||
* @param rootDirectory The root directory of the cache. | ||
*/ | ||
public DiskBasedAsyncCache(final File rootDirectory) { | ||
mRootDirectorySupplier = | ||
new DiskBasedCacheUtility.FileSupplier() { | ||
@Override | ||
public File get() { | ||
return rootDirectory; | ||
} | ||
}; | ||
} | ||
|
||
/** Returns the cache entry with the specified key if it exists, null otherwise. */ | ||
@Override | ||
public void get(String key, final OnGetCompleteCallback callback) { | ||
final CacheHeader entry = mEntries.get(key); | ||
// if the entry does not exist, return null. | ||
if (entry == null) { | ||
callback.onGetComplete(null); | ||
return; | ||
} | ||
final File file = getFileForKey(key); | ||
final int size = (int) file.length(); | ||
Path path = Paths.get(file.getPath()); | ||
try (AsynchronousFileChannel afc = | ||
AsynchronousFileChannel.open(path, StandardOpenOption.READ)) { | ||
final ByteBuffer buffer = ByteBuffer.allocate(size); | ||
afc.read( | ||
/* destination= */ buffer, | ||
/* position= */ 0, | ||
/* attachment= */ null, | ||
new CompletionHandler<Integer, Void>() { | ||
@Override | ||
public void completed(Integer result, Void v) { | ||
// if the file size changes, return null | ||
if (size != result) { | ||
VolleyLog.e( | ||
"File changed while reading: %s", file.getAbsolutePath()); | ||
callback.onGetComplete(null); | ||
return; | ||
} | ||
byte[] data = buffer.array(); | ||
callback.onGetComplete(entry.toCacheEntry(data)); | ||
} | ||
|
||
@Override | ||
public void failed(Throwable exc, Void v) { | ||
VolleyLog.e(exc, "Failed to read file %s", file.getAbsolutePath()); | ||
callback.onGetComplete(null); | ||
} | ||
}); | ||
} catch (IOException e) { | ||
VolleyLog.e(e, "Failed to read file %s", file.getAbsolutePath()); | ||
callback.onGetComplete(null); | ||
} | ||
} | ||
|
||
/** Returns a file object for the given cache key. */ | ||
File getFileForKey(String key) { | ||
return new File(mRootDirectorySupplier.get(), DiskBasedCacheUtility.getFilenameForKey(key)); | ||
} | ||
} |
Oops, something went wrong.