package com.razorfish.platforms.intellivault.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.razorfish.platforms.intellivault.diff.FileComparator;
import com.razorfish.platforms.intellivault.exceptions.IntelliVaultException;
import com.razorfish.platforms.intellivault.filter.Filter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.razorfish.platforms.intellivault.exceptions.IntelliVaultException;
import com.razorfish.platforms.intellivault.filter.Filter;
/**
* FileUtiles is a set of static utility methods for itneracting with files.
* Both files on the file system before/after a vault import/export operation,
* and virtual files within the intelliJ IDEA Project file system.
*/
public class FileUtils {
private static final String VAULTCLIPSE_TEMP_DIR_NAME = "IntelliVault";
public static final int BUFFER_LENGTH = 1024;
private static final boolean IGNORE_WHITESPACE = false;
/**
* Private default constructor, prevents creating instances
*/
private FileUtils() {
}
/**
* Create a temporary directory on the file system, which will be used as
* the base directory for a vault operation.
*
* @param userTempDir
* the user's temporary directory, where the tempdir will be
* created.
* @return a java.io.File instance representing the newly created directory.
*/
public static File createTempDirectory(String userTempDir) {
File baseDir = new File(userTempDir + File.separator + VAULTCLIPSE_TEMP_DIR_NAME + File.separator
+ System.currentTimeMillis());
baseDir.mkdirs();
return baseDir;
}
/**
* Copy the contents of an import operation from the IDEA project directory
* to the vault temp directory.
*
* @param importBaseDir
* the file system directory, which serves as the root of the
* copy target
* @param importDir
* the PsiDirectory (IDEA virtual directory) containing the
* contents to be copied
* @param path
* the jcr path representing the root of the import
* @param filter
* A list of Filters specifying which files should be ignored
* (not imported).
*
* @throws com.razorfish.platforms.intellivault.exceptions.IntelliVaultException
* if an error occurs during copy
*/
public static void copyImportContents(File importBaseDir, PsiDirectory importDir, String path,
Filter<VirtualFile> filter) throws IntelliVaultException {
File copyRootDir = new File(importBaseDir.getAbsolutePath() + File.separator + IntelliVaultConstants.JCR_ROOT
+ path.replace(IntelliVaultConstants.JCR_PATH_SEPERATOR, File.separator));
copyRootDir.mkdirs();
try {
copyImportContents(copyRootDir, importDir.getVirtualFile(), filter);
} catch (IOException e) {
throw new IntelliVaultException("Failed copying contents.", e);
}
}
/**
* Recursive method called for copying import contents from the IDEA project
* directory to the vault temp directory. For each child of virtualFile, if
* it is a directory, a new directory is created in fsDir, and this method
* is called recursively. If the child is a file, then that file is created
* and the content copied.
*
* @param fsDir
* th file system directory where contents will be copied
* @param virtualFile
* the VirtualFile in the IDEA project being copied to fsDir. In
* practice this is always a directory.
* @param filter
* A list of Filters specifying which files should be ignored
* (not imported).
* @throws IOException
* if an error occurs during copy
*/
private static void copyImportContents(File fsDir, VirtualFile virtualFile, Filter<VirtualFile> filter)
throws IOException {
VirtualFile[] contents = virtualFile.getChildren();
for (int i = 0; i < contents.length; i++) {
VirtualFile file = contents[i];
if (filter.allows(file)) {
if (file.isDirectory()) {
File newDir = new File(fsDir.getAbsolutePath() + File.separator + file.getName());
newDir.mkdir();
copyImportContents(newDir, file, filter);
} else {
InputStream ins = null;
try {
ins = file.getInputStream();
writeFile(file.getInputStream(), fsDir.getAbsolutePath() + File.separator + file.getName());
} finally {
if(ins!=null){
ins.close();;
}
}
}
}
}
}
public static void copyExportContents(final PsiDirectory exportDir, final File exportBaseDir, final String path)
throws IntelliVaultException {
final File copyRootDir = new File(exportBaseDir.getAbsolutePath() + File.separator
+ IntelliVaultConstants.JCR_ROOT
+ path.replace(IntelliVaultConstants.JCR_PATH_SEPERATOR, File.separator));
if (!copyRootDir.exists()) {
throw new IntelliVaultException("Failed copying contents.");
}
try {
copyExportContents(exportDir, copyRootDir);
} catch (IOException e) {
throw new IntelliVaultException("Error Copying Exported contents to IDEA.", e);
}
exportDir.getVirtualFile().refresh(false, true);
}
/**
* Delete a directory and all of it's contents in a depth-first recursive manner.
* @param f the directory to delete
* @throws IOException if an error occurs
*/
public static void deleteDirectoryRecursive(File f) throws IOException {
if (f.isDirectory()) {
for (File c : f.listFiles()) {
deleteDirectoryRecursive(c);
}
}
//once we have deleted all of a directories contents, we delete the directory
delete(f);
}
/**
* Delete a file. This method will retry the delete up to 5 times, sleeping for one second between retries.
* This is because sometimes, especially with smaller imports, the cleanup starts happening before vault has closed
* all of it's file locks. The retry behavior makes a best effort to properly cleanup when that happens.
* @param f the file to delete
* @throws IOException if the file can't be deleted.
*/
private static void delete(File f) throws IOException {
boolean isDeleted = f.delete();
for(int i=0;i<4 && !isDeleted;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new IOException("Failed to delete file: " + f, e);
}
isDeleted = f.delete();
}
if(!isDeleted) {
throw new IOException("Failed to delete file: " + f);
}
}
private static void copyExportContents(final PsiDirectory exportDir, File copyRootDir) throws IOException {
File[] contents = copyRootDir.listFiles();
FileComparator comparator = new FileComparator(IGNORE_WHITESPACE);
for (int i = 0; i < contents.length; i++) {
final File f = contents[i];
if (f.isDirectory()) {
PsiDirectory subdir = exportDir.findSubdirectory(f.getName());
if (subdir == null) {
subdir = ApplicationManager.getApplication().runWriteAction(new Computable<PsiDirectory>() {
@Override
public PsiDirectory compute() {
return exportDir.createSubdirectory(f.getName());
}
});
}
copyExportContents(subdir, f);
} else {
copyFile(exportDir, f, comparator);
}
}
}
private static void copyFile(final PsiDirectory exportDir, final File f, FileComparator comparator) throws IOException {
if (f.getName().equals(".vlt")) {
return;
}
PsiFile file = exportDir.findFile(f.getName());
if (file == null) {
file = ApplicationManager.getApplication().runWriteAction(new Computable<PsiFile>() {
@Override
public PsiFile compute() {
return exportDir.createFile(f.getName());
}
});
}
if(!comparator.areEqual(f,file.getVirtualFile())) {
copyFileContents(file, f);
}
}
private static void copyFileContents(final PsiFile file, final File f) throws IOException {
final VirtualFile vf = file.getVirtualFile();
ApplicationManager.getApplication().runWriteAction(new ThrowableComputable<Void, IOException>() {
@Override
public Void compute() throws IOException {
OutputStream out = null;
InputStream in = null;
try {
out = vf.getOutputStream(ApplicationManager.getApplication());
in = new FileInputStream(f);
write(in, out);
} finally {
if (out != null) {
out.flush();
out.close();
}
if (in != null) {
in.close();
}
}
return null;
}
});
}
public static void writeFile(String inputResourcePath, String filePath) throws IOException {
InputStream ins = null;
try {
ins = FileUtils.class.getClassLoader().getResourceAsStream(inputResourcePath);
writeFile(ins, filePath);
} finally {
if (ins != null) {
ins.close();
}
}
}
public static void writeFile(InputStream inputStream, String filePath) throws IOException {
File f = new File(filePath);
OutputStream os = null;
try {
os = new FileOutputStream(f);
write(inputStream, os);
} finally {
if (os != null) {
os.flush();
os.close();
}
}
}
private static void write(InputStream inputStream, OutputStream os) throws IOException {
byte[] buf = new byte[BUFFER_LENGTH];
int len;
while ((len = inputStream.read(buf)) > 0) {
os.write(buf, 0, len);
}
}
}