package xapi.file.impl;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import xapi.annotation.inject.SingletonDefault;
import xapi.file.api.FileService;
import xapi.io.X_IO;
import xapi.log.X_Log;
import xapi.source.X_Source;
import xapi.util.X_Debug;
@SingletonDefault(implFor=FileService.class)
public class FileServiceImpl implements FileService {
private final static class Cleanup extends Thread {
private final List<String> toKill = new ArrayList<String>();
@Override
public void run() {
for (String kill : toKill) {
rm(kill, Boolean.getBoolean("xapi.file.autorm"));
}
}
}
private static final Cleanup GC = new Cleanup();
static {
Runtime.getRuntime().addShutdownHook(GC);
}
@Override
public File chmod(int chmod, File file) {
assertValidChmod(chmod);
if (file.exists()) {
{
final boolean isExecutable = (chmod & 0x111) > 0;
try {
file.setExecutable(isExecutable, isExecutable ? (chmod & 0x011) == 0 : (chmod & 0x11) > 0);
} catch (SecurityException e) {
file.setExecutable(isExecutable);
}
}
{
final boolean isWritable = (chmod & 0x222) > 0;
try {
file.setWritable(isWritable, isWritable ? (chmod & 0x022) == 0 : (chmod & 0x2) > 0);
} catch (SecurityException e) {
file.setWritable(isWritable);
}
}
{
final boolean isReadable = (chmod & 0x444) > 0;
try {
file.setReadable(isReadable, isReadable? (chmod & 0x044) == 0 : (chmod & 0x44) > 0);
} catch (SecurityException e) {
file.setReadable(isReadable);
}
}
}
return file;
}
@Override
public void delete(String kill, boolean recursive) {
rm(kill, recursive);
}
private static void rm(String kill, boolean recursive) {
File f = new File(kill);
if (recursive) {
HashSet<File> cycle = new HashSet<File>();
rmRecursive(f, cycle);
}
if (f.exists() && !f.delete()) {
X_Log.warn(FileServiceImpl.class, "Unable to delete file ",f);
}
}
private static void rmRecursive(File f, HashSet<File> cycle) {
if (cycle.add(f)) {
if (f.isDirectory()) {
for (File child : f.listFiles()) {
rmRecursive(child, cycle); // Prevent symlink cycle recursion
}
if (!f.delete()) {
X_Log.warn(FileServiceImpl.class,"Unable to delete",f);
}
} else if (f.isFile()) {
if (!f.delete()) {
X_Log.warn(FileServiceImpl.class,"Unable to delete",f);
}
}
}
}
@Override
public File createTempDir(String prefix, boolean deleteOnExit) {
File f = null;
try {
f = File.createTempFile(prefix, "");
try {
f.delete();
} catch (Exception e) {
chmod(0x444, f);
f.delete();
}
f.mkdirs();
if (deleteOnExit) {
GC.toKill.add(f.getCanonicalPath());
}
chmod(0x777, f);
} catch (IOException e) {
X_Log.warn("Unable to create temporary directory for ", prefix, e);
X_Debug.maybeRethrow(e);
}
return f;
}
@Override
public String getPath(String path) {
try {
return new File(path).getCanonicalPath();
} catch (IOException e) {
return new File(path).getAbsolutePath();
}
}
@Override
public String getFileMaybeUnzip(String file, int chmod) {
File f = new File(file);
if (f.getAbsolutePath().contains("jar!")) {
try {
return unzip(file, new JarFile(f), chmod);
} catch (IOException e) {
X_Log.error(getClass(), "Unable to unzip", f, "from file", file,"with chmod",Integer.toHexString(chmod), e);
}
}
return getPath(file);
}
@Override
public String getResourceMaybeUnzip(String resource, ClassLoader cl, int chmod) {
if (cl == null) {
cl = Thread.currentThread().getContextClassLoader();
}
if (cl == null) {
cl = getClass().getClassLoader();
}
URL url = cl.getResource(resource);
try {
if (url == null)
throw new RuntimeException("Resource "+resource +" not available on classpath.");
if (url.getProtocol().equals("file")) {
String loc = url.toExternalForm();
if (loc.contains("jar!")) {
return unzip(resource, new JarFile(X_Source.stripJarName(loc)), chmod);
} else {
String file = X_Source.stripFileName(loc);
chmod(chmod, new File(file));
return file;
}
} else if (url.getProtocol().equals("jar")) {
return unzip(resource, ((JarURLConnection)(url.openConnection())).getJarFile(), chmod);
} else {
X_Log.warn("Unknown get resource protocol "+url.getProtocol());
}
} catch (Throwable e) {
X_Log.error("Error trying to load / unzip resouce "+resource+" using file "+url, e);
X_Debug.maybeRethrow(e);
}
return null;
}
@Override
public boolean saveFile(String path, String fileName, String contents) {
return saveFile(path, fileName, contents, "UTF-8");
}
@Override
public boolean saveFile(String path, String fileName, String contents, String charset) {
File f = new File(path);
if (!f.exists()) {
if (!f.mkdirs()) {
X_Log.warn("Unable to create parent directory", path,"in", f, new Throwable());
return false;
}
}
f = new File(f, fileName);
if (!f.exists()) {
try {
f.createNewFile();
} catch (IOException e) {
X_Log.warn("Unable to create new file", fileName,"in", f, e);
return false;
}
}
try {
X_IO.drain(new FileOutputStream(f), new ByteArrayInputStream(contents.getBytes()));
} catch (IOException e) {
X_Log.warn("Unable to save contents to file", f, e);
return false;
}
return true;
}
@Override
public String unzip(String resource, JarFile jarFile, int chmod) {
final ZipEntry entry = jarFile.getEntry(resource);
String fileName = null;
final File file;
try {
// Don't close this jar; it's in use by the classloader
InputStream is = jarFile.getInputStream(entry);
file = File.createTempFile(resource.replace('/', '_'), "");
fileName = file.getCanonicalPath();
try (FileOutputStream fOut = new FileOutputStream(file)) {
X_IO.drain(fOut, is);
}
chmod(chmod, file);
file.deleteOnExit();
return fileName;
} catch (Throwable e) {
X_Log.error("Error encountered unzipping jar entry",resource,"from",jarFile," Entry:", entry, e);
}
return fileName;
}
protected void assertValidChmod(int chmod) {
assert isHexadecimalChmod(chmod) : "Do not send "
+ (chmod > 0x777 ? "values greated than 0x777" : "decimal values")
+ " to X_File.chmod; you sent "+chmod+
" when you really meant to send 0x"+chmod+"="+Integer.parseInt(Integer.toString(chmod), 16);
}
/**
* This test is an APPROXIMATION of whether a given integer is hexadecimal,
* and is used only in an assertion statement warding off malformed permissions.
*
* Any value > 777 and < 0x777 is automatically deemed valid;
* after that, all we can do is check each number's 4th bit, (chmod & 888 == 0)
*
* @param chmod - The chmod value to check
* @return true is this value COULD be a valid hexadecimal chmod.
* returns chmod < 0x778 && (chmod & 888) == 0;
*
*/
protected static boolean isHexadecimalChmod(int chmod) {
return chmod < 0x778 && ((chmod & 0x888) == 0);
}
@Override
public void mkdirsTransient(File dest) {
if (!dest.exists()) {
File parent = dest.getParentFile();
List<File> parents = new ArrayList<File>();
while (parent != null) {
if (parent.exists()) {
break;
} else {
parents.add(parent);
}
parent = parent.getParentFile();
}
if (!parents.isEmpty()) {
dest.getParentFile().mkdirs();
for (
ListIterator<File> iter = parents.listIterator(parents.size());
iter.hasNext();) {
File prev = iter.next();
prev.deleteOnExit();
}
}
}
}
}