Generate per-cache unique identifier.

PiperOrigin-RevId: 233030337
This commit is contained in:
olly 2019-02-08 10:53:39 +00:00 committed by Andrew Lewis
parent dec00997e3
commit 42e691519a

View File

@ -22,6 +22,8 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -46,6 +48,8 @@ public final class SimpleCache implements Cache {
*/ */
private static final int SUBDIRECTORY_COUNT = 10; private static final int SUBDIRECTORY_COUNT = 10;
private static final String UID_FILE_SUFFIX = ".uid";
private static final HashSet<File> lockedCacheDirs = new HashSet<>(); private static final HashSet<File> lockedCacheDirs = new HashSet<>();
private static boolean cacheFolderLockingDisabled; private static boolean cacheFolderLockingDisabled;
@ -411,16 +415,25 @@ public final class SimpleCache implements Cache {
return; return;
} }
File[] files = cacheDir.listFiles();
long uid = 0;
try {
uid = loadUid(cacheDir, files);
} catch (IOException e) {
// TODO: Decide how to handle this.
}
// TODO: Pass the UID to the index, and use it.
contentIndex.load(); contentIndex.load();
if (fileIndex != null) { if (fileIndex != null) {
Map<String, CacheFileMetadata> fileMetadata = fileIndex.getAll(); Map<String, CacheFileMetadata> fileMetadata = fileIndex.getAll();
loadDirectory(cacheDir, /* isRoot= */ true, fileMetadata); loadDirectory(cacheDir, /* isRoot= */ true, files, fileMetadata);
fileIndex.removeAll(fileMetadata.keySet()); fileIndex.removeAll(fileMetadata.keySet());
} else { } else {
loadDirectory(cacheDir, /* isRoot= */ true, /* fileMetadata= */ null); loadDirectory(cacheDir, /* isRoot= */ true, files, /* fileMetadata= */ null);
} }
contentIndex.removeEmpty(); contentIndex.removeEmpty();
try { try {
contentIndex.store(); contentIndex.store();
} catch (CacheException e) { } catch (CacheException e) {
@ -431,37 +444,40 @@ public final class SimpleCache implements Cache {
/** /**
* Loads a cache directory. If the root directory is passed, also loads any subdirectories. * Loads a cache directory. If the root directory is passed, also loads any subdirectories.
* *
* @param directory The directory to load. * @param directory The directory.
* @param isRoot Whether the directory is the root directory. * @param isRoot Whether the directory is the root directory.
* @param files The files belonging to the directory.
* @param fileMetadata A mutable map containing cache file metadata, keyed by file name. The map * @param fileMetadata A mutable map containing cache file metadata, keyed by file name. The map
* is modified by removing entries for all loaded files. When the method call returns, the map * is modified by removing entries for all loaded files. When the method call returns, the map
* will contain only metadata that was unused. May be null if no file metadata is available. * will contain only metadata that was unused. May be null if no file metadata is available.
*/ */
private void loadDirectory( private void loadDirectory(
File directory, boolean isRoot, @Nullable Map<String, CacheFileMetadata> fileMetadata) { File directory,
File[] files = directory.listFiles(); boolean isRoot,
if (files == null) { File[] files,
// Not a directory. @Nullable Map<String, CacheFileMetadata> fileMetadata) {
return; if (files == null || files.length == 0) {
} // Either (a) directory isn't really a directory (b) it's empty, or (c) listing files failed.
if (!isRoot && files.length == 0) { if (!isRoot) {
// Empty non-root directory. // For (a) and (b) deletion is the desired result. For (c) it will be a no-op if the
directory.delete(); // directory is non-empty, so there's no harm in trying.
directory.delete();
}
return; return;
} }
for (File file : files) { for (File file : files) {
String fileName = file.getName(); String fileName = file.getName();
if (isRoot && fileName.indexOf('.') == -1) { if (isRoot && fileName.indexOf('.') == -1) {
loadDirectory(file, /* isRoot= */ false, fileMetadata); loadDirectory(file, /* isRoot= */ false, file.listFiles(), fileMetadata);
} else { } else {
if (isRoot && CachedContentIndex.isIndexFile(fileName)) { if (isRoot
// Skip the (expected) index files in the root directory. && (CachedContentIndex.isIndexFile(fileName) || fileName.endsWith(UID_FILE_SUFFIX))) {
// Skip expected UID and index files in the root directory.
continue; continue;
} }
CacheFileMetadata metadata =
fileMetadata != null ? fileMetadata.remove(file.getName()) : null;
long length = C.LENGTH_UNSET; long length = C.LENGTH_UNSET;
long lastAccessTimestamp = C.TIME_UNSET; long lastAccessTimestamp = C.TIME_UNSET;
CacheFileMetadata metadata = fileMetadata != null ? fileMetadata.remove(fileName) : null;
if (metadata != null) { if (metadata != null) {
length = metadata.length; length = metadata.length;
lastAccessTimestamp = metadata.lastAccessTimestamp; lastAccessTimestamp = metadata.lastAccessTimestamp;
@ -549,6 +565,49 @@ public final class SimpleCache implements Cache {
evictor.onSpanTouched(this, oldSpan, newSpan); evictor.onSpanTouched(this, oldSpan, newSpan);
} }
/**
* Loads the cache UID from the files belonging to the root directory, generating one if needed.
*
* @param directory The root directory.
* @param files The files belonging to the rood directory.
* @return The cache loaded UID.
* @throws IOException If there is an error loading or generating the UID.
*/
private static long loadUid(File directory, File[] files) throws IOException {
for (File file : files) {
String fileName = file.getName();
if (fileName.endsWith(UID_FILE_SUFFIX)) {
try {
return parseUid(fileName);
} catch (NumberFormatException e) {
// This should never happen, but if it does delete the malformed UID file and continue.
Log.e(TAG, "Malformed UID file: " + file);
file.delete();
}
}
}
return createUid(directory);
}
private static long createUid(File directory) throws IOException {
SecureRandom random = new SecureRandom();
// Generate a non-negative UID.
long uid = random.nextLong();
uid = uid == Long.MIN_VALUE ? 0 : Math.abs(uid);
// Persist it as a file.
String hexUid = Long.toString(uid, /* radix= */ 16);
File hexUidFile = new File(directory, hexUid + UID_FILE_SUFFIX);
if (!hexUidFile.createNewFile()) {
// False means that the file already exists, so this should never happen.
throw new IOException("Failed to create UID file: " + hexUidFile);
}
return uid;
}
private static long parseUid(String fileName) {
return Long.parseLong(fileName.substring(0, fileName.indexOf('.')), /* radix= */ 16);
}
private static synchronized boolean lockFolder(File cacheDir) { private static synchronized boolean lockFolder(File cacheDir) {
if (cacheFolderLockingDisabled) { if (cacheFolderLockingDisabled) {
return true; return true;