Commit bdc87a4f by olly Committed by Oliver Woodman

API and plumbing for indexing file metadata (length + timestamp)

When SimpleCache uses a CacheFileMetadataIndex, it will be able to avoid
querying file.length() and renaming files, both of which are expensive
operations on some file systems.

PiperOrigin-RevId: 232664255
parent 3845304e
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream.cache;
/** Metadata associated with a cache file. */
/* package */ final class CacheFileMetadata {
public final long length;
public final long lastAccessTimestamp;
public CacheFileMetadata(long length, long lastAccessTimestamp) {
this.length = length;
this.lastAccessTimestamp = lastAccessTimestamp;
}
}
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream.cache;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/** Maintains an index of cache file metadata. */
/* package */ class CacheFileMetadataIndex {
/**
* Returns all file metadata keyed by file name. The returned map is mutable and may be modified
* by the caller.
*/
public Map<String, CacheFileMetadata> getAll() {
return Collections.emptyMap();
}
/**
* Sets metadata for a given file.
*
* @param name The name of the file.
* @param length The file length.
* @param lastAccessTimestamp The file last access timestamp.
* @return Whether the index was updated successfully.
*/
public boolean set(String name, long length, long lastAccessTimestamp) {
// TODO.
return false;
}
/**
* Removes metadata.
*
* @param name The name of the file whose metadata is to be removed.
*/
public void remove(String name) {
// TODO.
}
/**
* Removes metadata.
*
* @param names The names of the files whose metadata is to be removed.
*/
public void removeAll(Set<String> names) {
// TODO.
}
}
......@@ -16,13 +16,16 @@
package com.google.android.exoplayer2.upstream.cache;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import java.io.File;
import java.util.TreeSet;
/** Defines the cached content for a single stream. */
/* package */ final class CachedContent {
private static final String TAG = "CachedContent";
/** The cache file id that uniquely identifies the original stream. */
public final int id;
/** The cache key that uniquely identifies the original stream. */
......@@ -138,21 +141,30 @@ import java.util.TreeSet;
}
/**
* Copies the given span with an updated last access time. Passed span becomes invalid after this
* call.
* Sets the given span's last access timestamp. The passed span becomes invalid after this call.
*
* @param cacheSpan Span to be copied and updated.
* @return a span with the updated last access time.
* @throws CacheException If renaming of the underlying span file failed.
* @param lastAccessTimestamp The new last access timestamp.
* @param updateFile Whether the span file should be renamed to have its timestamp match the new
* last access time.
* @return A span with the updated last access timestamp.
*/
public SimpleCacheSpan touch(SimpleCacheSpan cacheSpan) throws CacheException {
SimpleCacheSpan newCacheSpan = cacheSpan.copyWithUpdatedLastAccessTime(id);
if (!cacheSpan.file.renameTo(newCacheSpan.file)) {
throw new CacheException("Renaming of " + cacheSpan.file + " to " + newCacheSpan.file
+ " failed.");
}
// Replace the in-memory representation of the span.
public SimpleCacheSpan setLastAccessTimestamp(
SimpleCacheSpan cacheSpan, long lastAccessTimestamp, boolean updateFile) {
Assertions.checkState(cachedSpans.remove(cacheSpan));
File file = cacheSpan.file;
if (updateFile) {
File directory = file.getParentFile();
long position = cacheSpan.position;
File newFile = SimpleCacheSpan.getCacheFile(directory, id, position, lastAccessTimestamp);
if (file.renameTo(newFile)) {
file = newFile;
} else {
Log.w(TAG, "Failed to rename " + file + " to " + newFile + ".");
}
}
SimpleCacheSpan newCacheSpan =
cacheSpan.copyWithFileAndLastAccessTimestamp(file, lastAccessTimestamp);
cachedSpans.add(newCacheSpan);
return newCacheSpan;
}
......
......@@ -38,16 +38,16 @@ import java.util.regex.Pattern;
/**
* Returns a new {@link File} instance from {@code cacheDir}, {@code id}, {@code position}, {@code
* lastAccessTimestamp}.
* timestamp}.
*
* @param cacheDir The parent abstract pathname.
* @param id The cache file id.
* @param position The position of the stored data in the original stream.
* @param lastAccessTimestamp The last access timestamp.
* @param timestamp The file timestamp.
* @return The cache file.
*/
public static File getCacheFile(File cacheDir, int id, long position, long lastAccessTimestamp) {
return new File(cacheDir, id + "." + position + "." + lastAccessTimestamp + SUFFIX);
public static File getCacheFile(File cacheDir, int id, long position, long timestamp) {
return new File(cacheDir, id + "." + position + "." + timestamp + SUFFIX);
}
/**
......@@ -84,22 +84,36 @@ import java.util.regex.Pattern;
return new SimpleCacheSpan(key, position, length, C.TIME_UNSET, null);
}
/*
* Note: {@code fileLength} is equivalent to {@code file.length()}, but passing it as an explicit
* argument can reduce the number of calls to this method if the calling code already knows the
* file length. This is preferable because calling {@code file.length()} can be expensive. See:
* https://github.com/google/ExoPlayer/issues/4253#issuecomment-451593889.
*/
/**
* Creates a cache span from an underlying cache file. Upgrades the file if necessary.
*
* @param file The cache file.
* @param length The length of the cache file in bytes.
* @param length The length of the cache file in bytes, or {@link C#LENGTH_UNSET} to query the
* underlying file system. Querying the underlying file system can be expensive, so callers
* that already know the length of the file should pass it explicitly.
* @return The span, or null if the file name is not correctly formatted, or if the id is not
* present in the content index.
* present in the content index, or if the length is 0.
*/
@Nullable
public static SimpleCacheSpan createCacheEntry(File file, long length, CachedContentIndex index) {
return createCacheEntry(file, length, /* lastAccessTimestamp= */ C.TIME_UNSET, index);
}
/**
* Creates a cache span from an underlying cache file. Upgrades the file if necessary.
*
* @param file The cache file.
* @param length The length of the cache file in bytes, or {@link C#LENGTH_UNSET} to query the
* underlying file system. Querying the underlying file system can be expensive, so callers
* that already know the length of the file should pass it explicitly.
* @param lastAccessTimestamp The last access timestamp, or {@link C#TIME_UNSET} to use the file
* timestamp.
* @return The span, or null if the file name is not correctly formatted, or if the id is not
* present in the content index, or if the length is 0.
*/
@Nullable
public static SimpleCacheSpan createCacheEntry(
File file, long length, long lastAccessTimestamp, CachedContentIndex index) {
String name = file.getName();
if (!name.endsWith(SUFFIX)) {
file = upgradeFile(file, index);
......@@ -120,9 +134,18 @@ import java.util.regex.Pattern;
return null;
}
if (length == C.LENGTH_UNSET) {
length = file.length();
}
if (length == 0) {
return null;
}
long position = Long.parseLong(matcher.group(2));
long lastAccessTime = Long.parseLong(matcher.group(3));
return new SimpleCacheSpan(key, position, length, lastAccessTime, file);
if (lastAccessTimestamp == C.TIME_UNSET) {
lastAccessTimestamp = Long.parseLong(matcher.group(3));
}
return new SimpleCacheSpan(key, position, length, lastAccessTimestamp, file);
}
/**
......@@ -174,18 +197,16 @@ import java.util.regex.Pattern;
}
/**
* Returns a copy of this CacheSpan whose last access time stamp is set to current time. This
* doesn't copy or change the underlying cache file.
* Returns a copy of this CacheSpan with a new file and last access timestamp.
*
* @param id The cache file id.
* @return A {@link SimpleCacheSpan} with updated last access time stamp.
* @param file The new file.
* @param lastAccessTimestamp The new last access time.
* @return A copy with the new file and last access timestamp.
* @throws IllegalStateException If called on a non-cached span (i.e. {@link #isCached} is false).
*/
public SimpleCacheSpan copyWithUpdatedLastAccessTime(int id) {
public SimpleCacheSpan copyWithFileAndLastAccessTimestamp(File file, long lastAccessTimestamp) {
Assertions.checkState(isCached);
long now = System.currentTimeMillis();
File newCacheFile = getCacheFile(file.getParentFile(), id, position, now);
return new SimpleCacheSpan(key, position, length, now, newCacheFile);
return new SimpleCacheSpan(key, position, length, lastAccessTimestamp, file);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment