package edu.washington.cs.oneswarm.watchdir;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import org.gudy.azureus2.core3.config.StringList;
import edu.washington.cs.oneswarm.ui.gwt.client.newui.FileTypeFilter;
import edu.washington.cs.oneswarm.ui.gwt.client.newui.settings.MagicWatchType;
import edu.washington.cs.oneswarm.ui.gwt.rpc.OneSwarmConstants.InOrderType;
public class MagicDecider {
private static Logger logger = Logger.getLogger(MagicDecider.class.getName());
// /**
// * placeholder
// */
// public static enum FileTypeFilter {
// Other,
// Audio,
// Videos
// };
//
// /**
// * taken from
// edu.washington.cs.oneswarm.ui.gwt.rpc.OneSwarmConstants.InOrderType
// * remember to keep in sync
// */
// public static enum InOrderType {
// JPG("jpg", false, "image/jpeg", "image", FileTypeFilter.Other),
//
// JPEG("jpeg", false, "image/jpeg", "image", FileTypeFilter.Other),
//
// GIF("gif", false, "image/gif", "image", FileTypeFilter.Other),
//
// PNG("png", false, "image/png", "image", FileTypeFilter.Other),
//
// MP3("mp3", false, "audio/mpeg", "audio", FileTypeFilter.Audio),
// M4A("m4a", false, "audio/mpeg", "audio", FileTypeFilter.Audio),
// // MP3("mp3", true, "video/x-FLV", "audio"),
//
// FLV("flv", false, "video/x-FLV", "video", FileTypeFilter.Audio),
//
// FL_MP4("flash_ready.mp4", false, "video/mp4", "video",
// FileTypeFilter.Videos),
//
// AAC("aac", false, "audio/x-aac", "audio", FileTypeFilter.Audio),
//
// AVI("avi", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// MPEG("mpeg", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// MPG("mpg", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// XVID("xvid", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// DIVX("divx", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// MOV("mov", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// WMV("wmv", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// MKV("mkv", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// MP4("mp4", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// M4V("m4v", true, "video/x-FLV", "video", FileTypeFilter.Videos),
//
// WMA("wma", true, "video/x-FLV", "video", FileTypeFilter.Audio), ;
//
// public final boolean convertNeeded;
//
// public final String suffix;
//
// public final String convertedMime;
//
// public final String jwPlayerType;
// public final FileTypeFilter type;
//
// InOrderType(String suffix, boolean convertNeeded, String convertedMime,
// String jwPlayerType, FileTypeFilter type) {
// this.suffix = suffix;
// this.convertNeeded = convertNeeded;
// this.convertedMime = convertedMime;
// this.jwPlayerType = jwPlayerType;
// this.type = type;
// }
//
// public FileTypeFilter getFileTypeFilter()
// {
// return type;
// }
//
// public static InOrderType getType(String filename) {
// if (filename != null) {
// for (InOrderType type : values()) {
// if (filename.toLowerCase().endsWith(type.suffix)) {
// return type;
// }
// }
// }
// return null;
// }
// }
public static List<File> decideTorrents(UpdatingFileTree root, MagicWatchType type,
List<String> exclusions, StringList watchdirs) {
List<File> out = new ArrayList<File>();
boolean inspectKids = true;
/**
* Simple file?
*/
if (root.isDirectory() == false) {
if ((acceptSingleFile(root) || type.equals(MagicWatchType.Everything))
&& !isExcluded(exclusions, root)) // at least 1 MB in size
{
out.add(root.thisFile);
}
return out;
}
boolean excludeThis = false;
if (watchdirs != null) {
for (int i = 0; i < watchdirs.size(); i++) {
if (watchdirs.get(i) == null) {
continue;
}
if ((new File(watchdirs.get(i).substring(1))).equals(root.getThisFile())) {
excludeThis = true;
}
}
}
/**
* movie directory?
*/
if (!excludeThis && checkMovieDirectory(root) && !isExcluded(exclusions, root)) {
out.add(root.thisFile);
logger.finer("movie dir: " + root.thisFile.getName());
inspectKids = false;
}
/**
* ordered directory?
*/
else if (!excludeThis && checkOrderedDirectory(root) && !isExcluded(exclusions, root)) {
out.add(root.thisFile);
logger.finer("ordered dir: " + root.thisFile.getName());
inspectKids = false;
}
/**
* audio directory?
*/
else if (!excludeThis && checkAudioDirectory(root) && !isExcluded(exclusions, root)) {
out.add(root.thisFile);
logger.finer("audio dir: " + root.thisFile.getName());
inspectKids = false;
}
if (inspectKids == true) {
logger.finer("not grouped dir: " + root.thisFile.getName());
UpdatingFileTree[] kids = root.getChildren().toArray(new UpdatingFileTree[0]);
for (UpdatingFileTree c : kids) {
out.addAll(decideTorrents(c, type, exclusions, watchdirs));
}
}
return out;
}
enum Tag {
X("x"), OF("of"), E("e");
private String mKey;
private Tag(String key) {
mKey = key.toLowerCase();
}
public boolean match(String fnameOrig) {
String fname = fnameOrig.toLowerCase();
int offset = 0;
int candidate = fname.indexOf(mKey, offset);
while (candidate != -1) {
if (candidate > 0 && candidate < fname.length() - 1) {
if (Character.isDigit(fname.charAt(candidate - 1))
&& Character.isDigit(fname.charAt(candidate + 1))) {
return true;
}
}
offset = candidate + 1;
candidate = fname.indexOf(mKey, offset);
}
return false;
}
}
private static boolean checkOrderedDirectory(UpdatingFileTree root) {
if (root.mHasDirectoryChildren == true)
return false;
/**
* just like movie dir...
*/
List<String> fnames = flatMovieFilenameList(root);
if (fnames.size() == 0)
return false;
String ext = null;
Tag tag = null;
for (String f : fnames) {
if (f.length() < 5) // "<something>.{avi, mp4, mkv, etc}"
return false;
InOrderType t = InOrderType.getType(f);
if (t == null) {
return false;
} else if (t.getFileTypeFilter().equals(FileTypeFilter.Videos) == false) {
return false;
} else {
// System.out.println("file is video: " + f);
}
// if( ext == null )
// {
// ext = f.substring(f.length()-4); // we may or may not miss the
// dot depending on whether its .divx or .avi, but this is no big
// deal
// }
// else
// {
// if( f.substring(f.length()-4).equals(ext) == false )
// {
// return false;
// }
// }
//
for (Tag candidate : Tag.values()) {
if (candidate.match(f) && tag == null)
tag = candidate;
}
}
if (tag == null) {
return false;
}
/**
* except instead of edit distance, check that each file has a
* consistent tag
*/
int nonconforming = 0, conforming = 0;
for (String f : fnames) {
if (tag.match(f) == false) {
nonconforming++;
} else {
conforming++;
}
}
logger.finer(root.getThisFile().getName() + " conf: " + conforming + " nonconf: "
+ nonconforming);
return ((double) nonconforming <= Math.max(((double) conforming * 0.1), 3));
}
static boolean isExcluded(List<String> exclusions, UpdatingFileTree root) {
for (String s : exclusions.toArray(new String[0])) {
if (root.thisFile.getAbsolutePath().startsWith(s)) {
// System.out.println(root + " excluded by " + s);
return true;
}
// if( s.startsWith(root.thisFile.getAbsolutePath()) )
// {
// System.out.println(root + " excluded by " + s);
// return true;
// }
}
return false;
}
private static boolean acceptSingleFile(UpdatingFileTree root) {
/**
* Special case user request: PDF files
*/
if (root.thisFile.getName().endsWith(".pdf")) {
return true;
}
if (root.thisFile.length() < 1 * 1048576) // at least 1 MB
{
return false;
}
InOrderType t = InOrderType.getType(root.thisFile.getName());
if (t == null) {
return false;
} else if (t.getFileTypeFilter().equals(FileTypeFilter.Other)) {
return false;
} else if (t.getFileTypeFilter().equals(FileTypeFilter.Audio)) {
/**
* don't create single-file audio torrents automatically. good
* chance these are not intended
*/
return false;
}
/**
* Don't create if a torrent file already exists in the same dir
*/
if ((new File(root.thisFile.getAbsolutePath() + ".torrent")).exists()) {
return false;
}
return true;
}
private static List<String> flatMovieFilenameList(UpdatingFileTree root) {
List<String> out = new LinkedList<String>();
for (UpdatingFileTree f : root.getChildren().toArray(new UpdatingFileTree[0])) {
String fname = f.thisFile.getName().toLowerCase();
if (f.isDirectory() == false) {
if (fname.endsWith(".info") || fname.endsWith(".nfo") || fname.endsWith(".srt")
|| fname.equals("desktop.ini") || fname.equals("thumbs.db")) {
continue;
} else {
out.add(f.thisFile.getName());
}
} else {
// skip standard dirs
if (fname.equals("sample") || fname.equals("subs")) {
continue;
} else {
out.addAll(flatMovieFilenameList(f));
}
}
}
return out;
}
public static boolean checkAudioDirectory(UpdatingFileTree root) {
if (root.getDirectoryChildren().size() > 0)
return false;
List<UpdatingFileTree> kids = root.getChildren();
if (kids.size() == 0)
return false;
int covers = 0;
for (UpdatingFileTree f : kids) {
InOrderType t = InOrderType.getType(f.thisFile.getName());
boolean bad = false;
if (t == null) {
bad = true;
} else if (t.getFileTypeFilter() != FileTypeFilter.Audio) {
bad = true;
}
if (bad) {
String n = f.thisFile.getName().toLowerCase();
if (!(n.endsWith(".jpg") || n.endsWith(".png") || n.endsWith(".bmp")
|| n.equals("desktop.ini") || n.equals("thumbs.db")
|| n.equals(".ds_store") || n.endsWith(".sfv") || n.endsWith(".m3u") || n
.endsWith(".nfo"))) // covers
{
return false;
}
}
}
return true;
}
private static boolean checkMovieDirectory(UpdatingFileTree root) {
/**
* Get a flat list of all the files in all subdirectories and check to
* see if they are recognized types
*/
List<String> fnames = flatMovieFilenameList(root);
if (fnames.size() == 0)
return false;
String ext = null;
for (String f : fnames) {
if (f.length() < 5) // "<something>.{avi, mp4, mkv, etc}"
return false;
InOrderType t = InOrderType.getType(f);
if (t == null) {
return false;
} else if (t.getFileTypeFilter().equals(FileTypeFilter.Videos) == false) {
return false;
} else {
logger.finest("file is video: " + f);
}
if (ext == null) {
ext = f.substring(f.length() - 4); // we may or may not miss the
// dot depending on whether
// its .divx or .avi, but
// this is no big deal
} else {
if (f.substring(f.length() - 4).equals(ext) == false) {
return false;
}
}
}
/**
* Finally check edit distance. most files should conform to: abc1.avi,
* abc2.avi, etc if multipart
*/
String tester = null;
for (String f : fnames) {
if (tester == null)
tester = f;
else {
if (edit_distance(tester, f) > 3)
return false;
}
}
return true;
}
// from: http://www.merriampark static .com/ld.htm
public static int edit_distance(String s, String t) {
int d[][]; // matrix
int n; // length of s
int m; // length of t
int i; // iterates through s
int j; // iterates through t
char s_i; // ith character of s
char t_j; // jth character of t
int cost; // cost
// Step 1
n = s.length();
m = t.length();
if (n == 0) {
return m;
}
if (m == 0) {
return n;
}
d = new int[n + 1][m + 1];
// Step 2
for (i = 0; i <= n; i++) {
d[i][0] = i;
}
for (j = 0; j <= m; j++) {
d[0][j] = j;
}
// Step 3
for (i = 1; i <= n; i++) {
s_i = s.charAt(i - 1);
// Step 4
for (j = 1; j <= m; j++) {
t_j = t.charAt(j - 1);
// Step 5
if (s_i == t_j) {
cost = 0;
} else {
cost = 1;
}
// Step 6
d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), d[i - 1][j - 1]
+ cost);
}
}
// Step 7
return d[n][m];
}
}