/* * Copyright (C) 2014 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.exoplayer.upstream.cache; import java.io.File; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Defines a span of data that may or may not be cached (as indicated by {@link #isCached}). */ public final class CacheSpan implements Comparable<CacheSpan> { private static final String SUFFIX = ".v1.exo"; private static final String SUFFIX_ESCAPED = "\\.v1\\.exo"; private static final Pattern cacheFilePattern = Pattern.compile("^(.+)\\.(\\d+)\\.(\\d+)(" + SUFFIX_ESCAPED + ")$"); /** * The cache key that uniquely identifies the original stream. */ public final String key; /** * The position of the {@link CacheSpan} in the original stream. */ public final long position; /** * The length of the {@link CacheSpan}, or -1 if this is an open-ended hole. */ public final long length; /** * Whether the {@link CacheSpan} is cached. */ public final boolean isCached; /** * The file corresponding to this {@link CacheSpan}, or null if {@link #isCached} is false. */ public final File file; /** * The last access timestamp, or -1 if {@link #isCached} is false. */ public final long lastAccessTimestamp; public static File getCacheFileName(File cacheDir, String key, long offset, long lastAccessTimestamp) { return new File(cacheDir, key + "." + offset + "." + lastAccessTimestamp + SUFFIX); } public static CacheSpan createLookup(String key, long position) { return new CacheSpan(key, position, -1, false, -1, null); } public static CacheSpan createOpenHole(String key, long position) { return new CacheSpan(key, position, -1, false, -1, null); } public static CacheSpan createClosedHole(String key, long position, long length) { return new CacheSpan(key, position, length, false, -1, null); } /** * Creates a cache span from an underlying cache file. * * @param file The cache file. * @return The span, or null if the file name is not correctly formatted. */ public static CacheSpan createCacheEntry(File file) { Matcher matcher = cacheFilePattern.matcher(file.getName()); if (!matcher.matches()) { return null; } return CacheSpan.createCacheEntry(matcher.group(1), Long.parseLong(matcher.group(2)), Long.parseLong(matcher.group(3)), file); } private static CacheSpan createCacheEntry(String key, long position, long lastAccessTimestamp, File file) { return new CacheSpan(key, position, file.length(), true, lastAccessTimestamp, file); } private CacheSpan(String key, long position, long length, boolean isCached, long lastAccessTimestamp, File file) { this.key = key; this.position = position; this.length = length; this.isCached = isCached; this.file = file; this.lastAccessTimestamp = lastAccessTimestamp; } /** * @return True if this is an open-ended {@link CacheSpan}. False otherwise. */ public boolean isOpenEnded() { return length == -1; } /** * Renames the file underlying this cache span to update its last access time. * * @return A {@link CacheSpan} representing the updated cache file. */ public CacheSpan touch() { long now = System.currentTimeMillis(); File newCacheFile = getCacheFileName(file.getParentFile(), key, position, now); file.renameTo(newCacheFile); return CacheSpan.createCacheEntry(key, position, now, newCacheFile); } @Override public int compareTo(CacheSpan another) { if (!key.equals(another.key)) { return key.compareTo(another.key); } long startOffsetDiff = position - another.position; return startOffsetDiff == 0 ? 0 : ((startOffsetDiff < 0) ? -1 : 1); } }