package floobits.common;
import floobits.common.interfaces.IContext;
import floobits.common.interfaces.IFile;
import floobits.common.jgit.ignore.IgnoreNode;
import floobits.common.jgit.ignore.IgnoreRule;
import floobits.utilities.Flog;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class Ignore implements Comparable<Ignore>{
static final HashSet<String> IGNORE_FILES = new HashSet<String>(Arrays.asList(".gitignore", ".hgignore", ".flignore", ".flooignore"));
static final ArrayList<String> DEFAULT_IGNORES = new ArrayList<String>(Arrays.asList("extern", "node_modules", "tmp", "vendor", ".idea/workspace.xml", ".idea/misc.xml"));
public static final int MAX_FILE_SIZE = 1024 * 1024 * 5;
public final IFile file;
public final String stringPath;
public final HashMap<String, Ignore> children = new HashMap<String, Ignore>();
public final ArrayList<IFile> files = new ArrayList<IFile>();
public int size = 0;
private final IgnoreNode ignoreNode = new IgnoreNode();
public class UploadData {
public HashMap<String, Integer> bigStuff;
public HashSet<String> paths;
public UploadData(HashMap<String, Integer> bigStuff, HashSet<String> paths) {
this.bigStuff = bigStuff;
this.paths = paths;
}
}
public static Ignore BuildIgnore(IFile virtualFile) {
Ignore ig = new Ignore(virtualFile);
// TODO: add more hard-coded ignores
ig.ignoreNode.addRule(new IgnoreRule(".idea/workspace.xml"));
ig.ignoreNode.addRule(new IgnoreRule(".idea/misc.xml"));
ig.ignoreNode.addRule(new IgnoreRule(".git"));
ig.ignoreNode.addRule(new IgnoreRule(".svn"));
ig.ignoreNode.addRule(new IgnoreRule(".hg"));
ig.recurse(ig, virtualFile.getPath());
return ig;
}
public static void writeDefaultIgnores(IContext context) {
Flog.log("Creating default ignores.");
String path = FilenameUtils.concat(context.colabDir, ".flooignore");
try {
File f = new File(path);
if (f.exists()) {
return;
}
FileUtils.writeLines(f, DEFAULT_IGNORES);
} catch (IOException e) {
Flog.error(e);
}
}
public static boolean isIgnoreFile(IFile virtualFile) {
return virtualFile != null && IGNORE_FILES.contains(virtualFile.getName()) && virtualFile.isValid();
}
public Boolean isIgnored(IFile f, String relPath) {
if (isFlooIgnored(f, f.getPath())) {
Flog.log("Ignoring %s just because.", f.getPath());
return true;
}
relPath = FilenameUtils.separatorsToUnix(relPath);
return !relPath.equals(stringPath) && isGitIgnored(relPath, f.isDirectory());
}
private Ignore(IFile virtualFile) {
file = virtualFile;
stringPath = FilenameUtils.separatorsToUnix(virtualFile.getPath());
Flog.debug("Initializing ignores for %s", file);
for (IFile vf : file.getChildren()) {
addRules(vf);
}
}
protected void addRules(IFile virtualFile) {
if (!isIgnoreFile(virtualFile)) {
return;
}
InputStream inputStream = virtualFile.getInputStream();
if (inputStream != null) {
try {
ignoreNode.parse(inputStream);
} catch (IOException e) {
Flog.error(e);
}
}
}
@SuppressWarnings("UnsafeVfsRecursion")
public void recurse(Ignore root, String rootPath) {
@SuppressWarnings("UnsafeVfsRecursion") IFile[] fileChildren = file.getChildren();
for (IFile file : fileChildren) {
String absPath = file.getPath();
if (isFlooIgnored(file, absPath)) {
continue;
}
String relPath = FilenameUtils.separatorsToUnix(Utils.toProjectRelPath(absPath, rootPath));
Boolean isDir = file.isDirectory();
if (root.isGitIgnored(relPath, isDir)) {
continue;
}
if (isDir) {
Ignore child = new Ignore(file);
children.put(file.getName(), child);
child.recurse(root, rootPath);
continue;
}
files.add(file);
size += file.getLength();
}
}
/**
* @param path
* the rel path to test. The path must be relative to this ignore
* node's own repository path, and in repository path format
* (uses '/' and not '\').
**/
private boolean isGitIgnored(String path, boolean isDir) {
IgnoreNode.MatchResult ignored = ignoreNode.isIgnored(path, isDir);
switch (ignored) {
case IGNORED:
Flog.log("Ignoring %s because it is ignored by git.", path);
return true;
case NOT_IGNORED:
return false;
case CHECK_PARENT:
break;
}
String[] split = path.split("/", 2);
if (split.length != 2) {
return false;
}
String nextName = split[0];
path = split[1];
Ignore ignore = children.get(nextName);
return ignore != null && ignore.isGitIgnored(path, isDir);
}
public boolean isFlooIgnored(IFile virtualFile, String absPath) {
if (!file.isValid()) {
return true;
}
if (virtualFile.isSpecial() || virtualFile.isSymLink()) {
Flog.log("Ignoring %s because it is special or a symlink.", absPath);
return true;
}
if (!virtualFile.isDirectory() && virtualFile.getLength() > MAX_FILE_SIZE) {
Flog.log("Ignoring %s because it is too big (%s)", absPath, virtualFile.getLength());
return true;
}
return false;
}
List<Ignore> buildIgnores(Ignore startIgnore, HashSet<Ignore> ignoredIgnores) {
List<Ignore> ignores = new ArrayList<Ignore>();
LinkedList<Ignore> tempIgnores = new LinkedList<Ignore>();
tempIgnores.add(startIgnore);
Ignore ignore;
while (tempIgnores.size() > 0) {
ignore = tempIgnores.removeLast();
if (ignoredIgnores != null && ignoredIgnores.contains(ignore)) {
continue;
}
ignores.add(ignore);
for(Ignore ig: ignore.children.values()) {
tempIgnores.add(ig);
}
}
Collections.sort(ignores);
return ignores;
}
public UploadData getUploadData(Integer maxSize, Utils.FileProcessor<String> fileProcessor) {
HashMap<String, Integer> bigStuff = new HashMap<String, Integer>();
HashSet<String> paths = new HashSet<String>();
HashSet<Ignore> ignoredIgnores = new HashSet<Ignore>();
List<Ignore> allIgnores = buildIgnores(this, null);
int totalSize = 0;
for (Ignore ignore : allIgnores) {
totalSize += ignore.size;
}
while (totalSize > maxSize) {
Ignore ig = allIgnores.remove(0);
ignoredIgnores.add(ig);
totalSize -= ig.size;
bigStuff.put(ig.file.getPath(), ig.size);
}
if (ignoredIgnores.size() > 0) {
allIgnores = buildIgnores(this, ignoredIgnores);
}
for (Ignore ig : allIgnores) {
for (IFile virtualFile : ig.files)
paths.add(fileProcessor.call(virtualFile));
}
return new UploadData(bigStuff, paths);
}
@Override
public int compareTo(@NotNull Ignore ignore) {
return ignore.size - size;
}
}