mirror of
https://github.com/samsonjs/media.git
synced 2026-04-14 12:45:47 +00:00
Migrate ExoCache CacheSpan filenames from v1 to v2
V2 supports encoding special characters while on disk. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117228319
This commit is contained in:
parent
1ca32cced8
commit
0135aaa122
4 changed files with 142 additions and 8 deletions
|
|
@ -161,4 +161,20 @@ public class UtilTest extends TestCase {
|
|||
assertEquals(value, reconstructedValue);
|
||||
}
|
||||
|
||||
public void testUnescapeInvalidFileName() {
|
||||
assertNull(Util.unescapeFileName("%a"));
|
||||
assertNull(Util.unescapeFileName("%xyz"));
|
||||
}
|
||||
|
||||
public void testEscapeUnescapeFileName() {
|
||||
assertEscapeUnescapeFileName("just+a regular+fileName", "just+a regular+fileName");
|
||||
assertEscapeUnescapeFileName("key:value", "key%3avalue");
|
||||
assertEscapeUnescapeFileName("<>:\"/\\|?*%", "%3c%3e%3a%22%2f%5c%7c%3f%2a%25");
|
||||
}
|
||||
|
||||
private static void assertEscapeUnescapeFileName(String fileName, String escapedFileName) {
|
||||
assertEquals(escapedFileName, Util.escapeFileName(fileName));
|
||||
assertEquals(fileName, Util.unescapeFileName(escapedFileName));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.upstream.cache;
|
||||
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
|
|
@ -25,10 +26,11 @@ import java.util.regex.Pattern;
|
|||
*/
|
||||
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 + ")$");
|
||||
private static final String SUFFIX = ".v2.exo";
|
||||
private static final Pattern CACHE_FILE_PATTERN_V1 =
|
||||
Pattern.compile("^(.+)\\.(\\d+)\\.(\\d+)\\.v1\\.exo$");
|
||||
private static final Pattern CACHE_FILE_PATTERN_V2 =
|
||||
Pattern.compile("^(.+)\\.(\\d+)\\.(\\d+)\\.v2\\.exo$");
|
||||
|
||||
/**
|
||||
* The cache key that uniquely identifies the original stream.
|
||||
|
|
@ -57,7 +59,8 @@ public final class CacheSpan implements Comparable<CacheSpan> {
|
|||
|
||||
public static File getCacheFileName(File cacheDir, String key, long offset,
|
||||
long lastAccessTimestamp) {
|
||||
return new File(cacheDir, key + "." + offset + "." + lastAccessTimestamp + SUFFIX);
|
||||
return new File(cacheDir,
|
||||
Util.escapeFileName(key) + "." + offset + "." + lastAccessTimestamp + SUFFIX);
|
||||
}
|
||||
|
||||
public static CacheSpan createLookup(String key, long position) {
|
||||
|
|
@ -79,12 +82,25 @@ public final class CacheSpan implements Comparable<CacheSpan> {
|
|||
* @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());
|
||||
Matcher matcher = CACHE_FILE_PATTERN_V2.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);
|
||||
String key = Util.unescapeFileName(matcher.group(1));
|
||||
return key == null ? null : createCacheEntry(
|
||||
key, Long.parseLong(matcher.group(2)), Long.parseLong(matcher.group(3)), file);
|
||||
}
|
||||
|
||||
static File upgradeIfNeeded(File file) {
|
||||
Matcher matcher = CACHE_FILE_PATTERN_V1.matcher(file.getName());
|
||||
if (!matcher.matches()) {
|
||||
return file;
|
||||
}
|
||||
String key = matcher.group(1); // Keys were not escaped in version 1.
|
||||
File newCacheFile = getCacheFileName(file.getParentFile(), key,
|
||||
Long.parseLong(matcher.group(2)), Long.parseLong(matcher.group(3)));
|
||||
file.renameTo(newCacheFile);
|
||||
return newCacheFile;
|
||||
}
|
||||
|
||||
private static CacheSpan createCacheEntry(String key, long position, long lastAccessTimestamp,
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ public final class SimpleCache implements Cache {
|
|||
if (file.length() == 0) {
|
||||
file.delete();
|
||||
} else {
|
||||
file = CacheSpan.upgradeIfNeeded(file);
|
||||
CacheSpan span = CacheSpan.createCacheEntry(file);
|
||||
if (span == null) {
|
||||
file.delete();
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ public final class Util {
|
|||
Pattern.compile("^(-)?P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?"
|
||||
+ "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$");
|
||||
|
||||
private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})");
|
||||
|
||||
private static final long MAX_BYTES_TO_DRAIN = 2048;
|
||||
|
||||
private Util() {}
|
||||
|
|
@ -786,4 +788,103 @@ public final class Util {
|
|||
return TYPE_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string so that it's safe for use as a file or directory name on at least FAT32
|
||||
* filesystems. FAT32 is the most restrictive of all filesystems still commonly used today.
|
||||
*
|
||||
* <p>For simplicity, this only handles common characters known to be illegal on FAT32:
|
||||
* <, >, :, ", /, \, |, ?, and *. % is also escaped since it is used as the escape character.
|
||||
* Escaping is performed in a consistent way so that no collisions occur and
|
||||
* {@link #unescapeFileName(String)} can be used to retrieve the original file name.
|
||||
*
|
||||
* @param fileName File name to be escaped.
|
||||
* @return An escaped file name which will be safe for use on at least FAT32 filesystems.
|
||||
*/
|
||||
public static String escapeFileName(String fileName) {
|
||||
int length = fileName.length();
|
||||
int charactersToEscapeCount = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (shouldEscapeCharacter(fileName.charAt(i))) {
|
||||
charactersToEscapeCount++;
|
||||
}
|
||||
}
|
||||
if (charactersToEscapeCount == 0) {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
StringBuilder builder = new StringBuilder(length + charactersToEscapeCount * 2);
|
||||
while (charactersToEscapeCount > 0) {
|
||||
char c = fileName.charAt(i++);
|
||||
if (shouldEscapeCharacter(c)) {
|
||||
builder.append('%').append(Integer.toHexString(c));
|
||||
charactersToEscapeCount--;
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
if (i < length) {
|
||||
builder.append(fileName, i, length);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static boolean shouldEscapeCharacter(char c) {
|
||||
switch (c) {
|
||||
case '<':
|
||||
case '>':
|
||||
case ':':
|
||||
case '"':
|
||||
case '/':
|
||||
case '\\':
|
||||
case '|':
|
||||
case '?':
|
||||
case '*':
|
||||
case '%':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes an escaped file or directory name back to its original value.
|
||||
*
|
||||
* <p>See {@link #escapeFileName(String)} for more information.
|
||||
*
|
||||
* @param fileName File name to be unescaped.
|
||||
* @return The original value of the file name before it was escaped,
|
||||
* or null if the escaped fileName seems invalid.
|
||||
*/
|
||||
public static String unescapeFileName(String fileName) {
|
||||
int length = fileName.length();
|
||||
int percentCharacterCount = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (fileName.charAt(i) == '%') {
|
||||
percentCharacterCount++;
|
||||
}
|
||||
}
|
||||
if (percentCharacterCount == 0) {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
int expectedLength = length - percentCharacterCount * 2;
|
||||
StringBuilder builder = new StringBuilder(expectedLength);
|
||||
Matcher matcher = ESCAPED_CHARACTER_PATTERN.matcher(fileName);
|
||||
int endOfLastMatch = 0;
|
||||
while (percentCharacterCount > 0 && matcher.find()) {
|
||||
char unescapedCharacter = (char) Integer.parseInt(matcher.group(1), 16);
|
||||
builder.append(fileName, endOfLastMatch, matcher.start()).append(unescapedCharacter);
|
||||
endOfLastMatch = matcher.end();
|
||||
percentCharacterCount--;
|
||||
}
|
||||
if (endOfLastMatch < length) {
|
||||
builder.append(fileName, endOfLastMatch, length);
|
||||
}
|
||||
if (builder.length() != expectedLength) {
|
||||
return null;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue