package org.rr.commons.utils.compression.rar;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.io.FileUtils;
import org.rr.commons.log.LoggerFactory;
import org.rr.commons.mufs.IResourceHandler;
import org.rr.commons.utils.ProcessExecutor;
import org.rr.commons.utils.ProcessExecutorHandler;
import org.rr.commons.utils.ReflectionUtils;
import org.rr.commons.utils.StringUtil;
import org.rr.commons.utils.compression.CompressedDataEntry;
import org.rr.commons.utils.compression.CompressionUtils;
import org.rr.commons.utils.compression.EmptyFileEntryFilter;
import org.rr.commons.utils.compression.FileEntryFilter;
public class RarUtils {
private static String rarExecFolder;
/**
* Set the folder where the rar executables could be found.
*/
public static void setRarExecFolder(String rarExecF) {
rarExecFolder = rarExecF;
}
/**
* Extracts all entries from a rar file that are accepted by the given {@link FileEntryFilter}.
* @param rarFileHandler The rar file in the file system.
* @param path qualified rar path of the entry to be extracted.
* @return The desired extracted entries.
*/
public static List<CompressedDataEntry> extract(IResourceHandler rarFileHandler, FileEntryFilter rarFileFilter) {
ArrayList<CompressedDataEntry> result = new ArrayList<>();
List<String> list = list(rarFileHandler, rarFileFilter);
for(String entry : list) {
result.add(extract(rarFileHandler, entry));
}
return result;
}
/**
* Extracts one entry from a rar file.
* @param rarFileHandler The rar file in the file system.
* @param name qualified rar path of the entry to be extracted.
* @return The desired extracted entry. Never returns <code>null</code> but the
* result {@link TrueZipDataEntry} would return no bytes if the rar entry did not exists.
*/
public static CompressedDataEntry extract(IResourceHandler rarFileHandler, String name) {
return new LazyRarDataEntry(rarFileHandler, name);
}
/**
* List all entries of the rar file.
*/
public static List<String> list(IResourceHandler rarFileHandler) {
List<String> result = list(rarFileHandler, new EmptyFileEntryFilter());
return result;
}
private static void removeDirectoryEntries(List<String> files) {
//need to sort previous because the dir path is always directly before
//the file entry.
Collections.sort(files);
ArrayList<String> toRemove = new ArrayList<>();
for(int i = 0; i < files.size(); i++) {
String current = files.get(i);
if(files.size() > i + 1) {
String next = files.get(i + 1);
if(next.startsWith(current + "/")) {
toRemove.add(current);
}
}
}
files.removeAll(toRemove);
}
private static List<String> processFileEntryFilter(List<String> files, FileEntryFilter filter) {
ArrayList<String> result = new ArrayList<>(files.size());
for(String file: files) {
if(filter == null || filter.accept(file, file.getBytes())) {
result.add(file);
}
}
return result;
}
/**
* List all entries of the rar file allowed by the given {@link ZipFileFilter} instance.
*/
public static List<String> list(final IResourceHandler rarFileHandler, final FileEntryFilter rarFileFilter) {
final List<String> result = new ArrayList<>();
final CommandLine cl = new CommandLine(getUnRarExecutable());
cl.addArgument("vb");
cl.addArgument("\"" + rarFileHandler.toFile().getPath() + "\"", false);
try {
ProcessExecutor.runProcessAsScript(cl, new ProcessExecutorHandler() {
@Override
public void onStandardOutput(String msg) {
if(msg.trim().isEmpty()) {
return;
} else if(msg.indexOf(cl.toString()) != -1) {
return;
} else {
msg = StringUtil.replace(msg, "\\", "/");
result.add(msg);
}
}
@Override
public void onStandardError(String msg) {
}
}, 100000);
} catch (Exception e) {
LoggerFactory.getLogger().log(Level.SEVERE, "To list files in rar " + rarFileHandler, e);
}
//remove dirs
removeDirectoryEntries(result);
//apply filter
return processFileEntryFilter(result, rarFileFilter);
}
/**
* Add / Replace the an entry with the given name and given data
*/
public static boolean add(IResourceHandler rarFileHandler, String name, InputStream data) {
final CommandLine cl = new CommandLine(getRarExecutable());
File in = null;
try {
cl.addArgument("a"); //add
cl.addArgument("-o+"); //overwrite existing
//rar archive path
if(name.indexOf('/') != -1) {
String path = name.substring(0, name.lastIndexOf('/'));
cl.addArgument("-ap\"" + path + "\"", false);
name = name.substring(name.lastIndexOf('/') + 1);
} else {
cl.addArgument("-ap");
}
//store only
if(CompressionUtils.isStoreOnlyFile(name)) {
cl.addArgument("-m0");
}
cl.addArgument("-ep"); //do not use the fs path
String rarFilePath = rarFileHandler.toFile().getAbsolutePath();
cl.addArgument(StringUtil.quote(rarFilePath, '"'), false); //rar archive
//create a copy of the entry that should be added to the rar.
in = new File(FileUtils.getTempDirectoryPath() + File.separator + UUID.randomUUID().toString() + File.separator + name);
FileUtils.copyInputStreamToFile(data, in);
cl.addArgument(StringUtil.quote(in.getAbsolutePath(), '"'), false); //entry to add
ProcessExecutor.runProcessAsScript(cl, new ProcessExecutorHandler() {
@Override
public void onStandardOutput(String msg) {
LoggerFactory.getLogger(this).log(Level.INFO, msg);
}
@Override
public void onStandardError(String msg) {
LoggerFactory.getLogger(this).log(Level.WARNING, msg);
}
}, ExecuteWatchdog.INFINITE_TIMEOUT);
return true;
} catch (Exception e) {
LoggerFactory.getLogger().log(Level.SEVERE, "Faild to add " + name + " to "+ rarFileHandler, e);
} finally {
if(in != null) {
FileUtils.deleteQuietly(in.getParentFile());
}
}
return false;
}
/**
* Get the rar executable.
*/
private static String getRarExecutable() {
if(ReflectionUtils.getOS() == ReflectionUtils.OS_WINDOWS) {
return rarExecFolder + File.separator + "Rar.exe";
} else if(ReflectionUtils.getOS() == ReflectionUtils.OS_LINUX) {
return rarExecFolder + File.separator + "rar";
}
throw new RuntimeException("No rar executable!");
}
/**
* Get the rar executable.
*/
static String getUnRarExecutable() {
if(ReflectionUtils.getOS() == ReflectionUtils.OS_WINDOWS) {
return rarExecFolder + File.separator + "UnRAR.exe";
} else if(ReflectionUtils.getOS() == ReflectionUtils.OS_LINUX) {
return rarExecFolder + File.separator + "unrar";
}
throw new RuntimeException("No rar executable!");
}
}