/*
* Copyright 2015-present Facebook, Inc.
*
* 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.facebook.buck.util;
import static com.facebook.buck.zip.Unzip.ExistingFileMode.OVERWRITE;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.zip.Unzip;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.io.ByteStreams;
import com.google.common.io.Resources;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Represents a zip that has been packaged as a resource with Buck, but which should be expanded at
* most once during Buck's execution (not per-build).
*/
public class PackagedResource implements Supplier<Path> {
private final ProjectFilesystem filesystem;
private final String name;
private final Class<?> relativeTo;
private final Path filename;
private final Supplier<Path> supplier;
public PackagedResource(
ProjectFilesystem filesystem, Class<?> relativeTo, String pathRelativeToClass) {
this.filesystem = filesystem;
this.relativeTo = relativeTo;
// We could magically detect the class we're relative to by examining the stacktrace but that
// would be incredibly fragile. So we won't.
this.name = pathRelativeToClass;
this.filename = Paths.get(pathRelativeToClass).getFileName();
this.supplier = Suppliers.memoize(this::unpack);
}
@Override
public Path get() {
return supplier.get();
}
/**
* Use this as unique ID for resource when hashing is not enabled
*
* @return Class name followed by relative file path. E.g.
* com.facebook.buck.MyClass#some_resource_file.abc
*/
public String getResourceIdentifier() {
return relativeTo.getName() + "#" + name;
}
/**
* Use this combined with file hash as unique ID when hashing is enabled.
*
* @return {@link Path} representing filename of packaged resource
*/
public Path getFilenamePath() {
return filename;
}
private Path unpack() {
try (InputStream inner =
Preconditions.checkNotNull(
Resources.getResource(relativeTo, name), "Unable to find: %s", name)
.openStream();
BufferedInputStream stream = new BufferedInputStream(inner)) {
Path outputPath =
filesystem
.getBuckPaths()
.getResDir()
.resolve(relativeTo.getCanonicalName())
.resolve(filename);
// If the path already exists, delete it.
if (filesystem.exists(outputPath)) {
filesystem.deleteRecursivelyIfExists(outputPath);
}
String extension = com.google.common.io.Files.getFileExtension(filename.toString());
if (extension.equals("zip")) {
filesystem.mkdirs(outputPath);
// Copy the zip to a temporary file, and mark that for deletion once the VM exits.
Path zip = Files.createTempFile(filename.toString(), ".zip");
// Ensure we tidy up
Files.copy(stream, zip, REPLACE_EXISTING);
Unzip.extractZipFile(zip, filesystem, outputPath, OVERWRITE);
Files.delete(zip);
} else {
filesystem.createParentDirs(outputPath);
Path tempFilePath =
filesystem.createTempFile(
outputPath.getParent(), outputPath.getFileName().toString() + ".", ".tmp");
try (OutputStream outputStream = filesystem.newFileOutputStream(tempFilePath)) {
ByteStreams.copy(stream, outputStream);
}
filesystem.move(tempFilePath, outputPath);
}
return outputPath;
} catch (IOException ioe) {
throw new RuntimeException("Unable to unpack " + name, ioe);
}
}
}