blob: b33dd64ca5343673d39407044d88975b5e831fce [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.mojo.shell;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Helper methods for file extraction from APK assets and zip archives.
*/
class FileHelper {
public static final String TAG = "MojoFileHelper";
// Size of the buffer used in streaming file operations.
private static final int BUFFER_SIZE = 1024 * 1024;
// Prefix used when naming temporary files.
private static final String TEMP_FILE_PREFIX = "temp-";
// Prefix used when naming timestamp files.
private static final String TIMESTAMP_PREFIX = "asset_timestamp-";
/**
* Looks for a timestamp file on disk that indicates the version of the APK that the resource
* assets were extracted from. Returns null if a timestamp was found and it indicates that the
* resources match the current APK. Otherwise returns a String that represents the filename of a
* timestamp to create.
*/
public static String checkAssetTimestamp(Context context, File outputDir) {
PackageManager pm = context.getPackageManager();
PackageInfo pi = null;
try {
pi = pm.getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
return TIMESTAMP_PREFIX;
}
if (pi == null) {
return TIMESTAMP_PREFIX;
}
String expectedTimestamp = TIMESTAMP_PREFIX + pi.versionCode + "-" + pi.lastUpdateTime;
String[] timestamps = outputDir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(TIMESTAMP_PREFIX);
}
});
if (timestamps.length != 1) {
// If there's no timestamp, nuke to be safe as we can't tell the age of the files.
// If there's multiple timestamps, something's gone wrong so nuke.
return expectedTimestamp;
}
if (!expectedTimestamp.equals(timestamps[0])) {
return expectedTimestamp;
}
// Timestamp file is already up-to date.
return null;
}
public static File extractFromAssets(Context context, String assetName, File outputDirectory,
boolean useTempFile) throws IOException, FileNotFoundException {
String timestampToCreate = null;
if (!useTempFile) {
timestampToCreate = checkAssetTimestamp(context, outputDirectory);
if (timestampToCreate != null) {
for (File child : outputDirectory.listFiles()) {
deleteRecursively(child);
}
}
}
File outputFile;
if (useTempFile) {
// Make the original filename part of the temp file name.
// TODO(ppi): do we need to sanitize the suffix?
String suffix = "-" + assetName;
outputFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, outputDirectory);
} else {
outputFile = new File(outputDirectory, assetName);
if (outputFile.exists()) {
return outputFile;
}
}
BufferedInputStream inputStream = new BufferedInputStream(
context.getAssets().open(assetName));
try {
writeStreamToFile(inputStream, outputFile);
} finally {
inputStream.close();
}
if (timestampToCreate != null) {
try {
new File(outputDirectory, timestampToCreate).createNewFile();
} catch (IOException e) {
// In the worst case we don't write a timestamp, so we'll re-extract the asset next
// time.
Log.w(TAG, "Failed to write asset timestamp!");
}
}
return outputFile;
}
/**
* Extracts the file of the given extension from the archive. Throws FileNotFoundException if no
* matching file is found.
*/
static File extractFromArchive(File archive, String suffixToMatch,
File outputDirectory) throws IOException, FileNotFoundException {
ZipInputStream zip = new ZipInputStream(new BufferedInputStream(new FileInputStream(
archive)));
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
if (entry.getName().endsWith(suffixToMatch)) {
// Make the original filename part of the temp file name.
// TODO(ppi): do we need to sanitize the suffix?
String suffix = "-" + new File(entry.getName()).getName();
File extractedFile = File.createTempFile(TEMP_FILE_PREFIX, suffix,
outputDirectory);
writeStreamToFile(zip, extractedFile);
zip.close();
return extractedFile;
}
}
zip.close();
throw new FileNotFoundException();
}
/**
* Deletes a file or directory. Directory will be deleted even if not empty.
*/
public static void deleteRecursively(File file) {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
deleteRecursively(child);
}
}
if (!file.delete()) {
Log.w(TAG, "Unable to delete file: " + file.getAbsolutePath());
}
}
private static void writeStreamToFile(InputStream inputStream, File outputFile)
throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
try {
int read;
while ((read = inputStream.read(buffer, 0, BUFFER_SIZE)) > 0) {
outputStream.write(buffer, 0, read);
}
} finally {
outputStream.close();
}
}
}