package dnb.analyze.filetree;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import dnb.analyze.MatchResult;
import dnb.analyze.SimilarScanResult;
import dnb.analyze.MatchResult.Source;
import dnb.analyze.MatchResult.Status;
import dnb.analyze.SimilarScanResult.Match;
import dnb.analyze.SimilarScanResult.MatchType;
import dnb.analyze.nfo.NfoDataFactory;
import dnb.analyze.nfo.NfoField;
import dnb.analyze.nfo.NfoLyzeResult;
import dnb.analyze.nfo.NfoLyzer;
import dnb.data.Artist;
import dnb.data.CatalogNumber;
import dnb.data.Genre;
import dnb.data.Label;
import dnb.data.Labelcode;
import dnb.data.ReleaseData;
import dnb.data.Repository;
import dnb.data.RepositoryObject;
import dnb.data.filetree.Component;
import dnb.data.filetree.Folder;
import dnb.data.filetree.NfoFile;
import dnb.data.impl.CatalogNumberHibernateImpl;
public class Mp3FileTreeAnalyzer {
public enum ConfirmResult {
YES,
NO,
IGNORE
}
public interface LabelcodeConfirmInteraction {
ConfirmResult confirmLabelcode(Folder f, CatalogNumber code);
}
public interface AlbumDataConfirmInteraction {
boolean confirm(NfoFile file, NfoLyzeResult result);
}
private static final Pattern NUMBER_AT_END_PATTERN = Pattern.compile(".*?(\\d+)$");
private static final Pattern NFO_LABELCODE_PATTERN = // "Catalog Nr" "CAT.NUMBER" "Catalog"
Pattern.compile("^.*?cat(?:.*?)(?:nr|no|number)?:?\\s*(.*?)\\s*$",
Pattern.MULTILINE | Pattern.UNIX_LINES | Pattern.CASE_INSENSITIVE);
// XXX clear out & warn about folders ONLY containing nfos!
private static boolean addLabelCode(LabelcodeConfirmInteraction interaction, Set<String> labelCodes, Set<String> ignored,
Folder cur, CatalogNumberHibernateImpl lc) {
if(ignored.contains(lc.getCode())) {
System.out.println("Already ignored:" + lc.getCode());
return false;
}
if (!labelCodes.contains(lc.getCode())) { // new code
ConfirmResult confirmed = interaction.confirmLabelcode(cur, lc);
if(confirmed == ConfirmResult.YES) {
labelCodes.add(lc.getCode());
cur.setLabelcode(lc); // assign label code to this folder or a parent in the hierarchy
return true;
} else if (confirmed == ConfirmResult.IGNORE) {
ignored.add(lc.getCode());
return false;
} else {
return false;
}
} else { // already contained => as good as confirmed
cur.setLabelcode(lc); // assign label code to this folder or a parent in the hierarchy
return true;
}
}
private static void assignMatchStatus(final MatchResult mr,
final SimilarScanResult<? extends RepositoryObject> f) {
if (f == null) { // not found
mr.setStatus(MatchResult.Status.UNKNOWN); // better check !
} else { // one or more matches found...
switch(f.getType()) { // check best avail. type
case LEVENSHTEIN:
case PART:
// IMPORTANT: correct to existing entry NOT with part, but mark both yellow!
// XXX 4th status for part matches: lookup !!!
mr.setStatus(MatchResult.Status.SIMILAR); // could be, but better check
break;
case EXACT:
mr.setStatus(MatchResult.Status.KNOWN); // green 2 go ;-)
if (!mr.getMatch().equals(f.get(0).getFoundObject().getName())) {
System.out.println("Correcting !");
mr.setMatch(f.get(0).getFoundObject().getName());
}
break;
default:
throw new AssertionError();
}
}
}
// XXX return if part status has been assigned
private static void assignMatchStati(NfoLyzeResult result, Repository repository) {
Map<NfoField, MatchResult> m = result.getMatches();
MatchResult mr;
if((mr = m.get(NfoField.ARTIST)) != null) { // get the artist's match result, if set, proceed!
SimilarScanResult<Artist> f = repository.findSimilarArtist(mr.getMatch());
mr.setSimilarScanResult(f); // ... attach similar matches to the match itself
assignMatchStatus(mr, f); // ... assign stati
if (f != null) { // XXX compose alternatives & show on UI !
System.out.println("*********" + f);
}
}
if((mr = m.get(NfoField.GENRE)) != null) { // get the genre's match result, if set, proceed!
SimilarScanResult<Genre> f = repository.findSimilarGenre(mr.getMatch());
mr.setSimilarScanResult(f); // ... attach similar matches to the match itself
assignMatchStatus(mr, f);
if (f != null) { // XXX compose alternatives & show on UI !
System.out.println("GENRE: *********" + f);
}
}
if((mr = m.get(NfoField.RECORD_LABEL)) != null) { // get the label's match result, if set, proceed!
SimilarScanResult<Label> f = repository.findSimilarLabel(mr.getMatch());
mr.setSimilarScanResult(f); // ... attach similar matches to the match itself
assignMatchStatus(mr, f);
if (f != null) { // XXX compose alternatives & show on UI !
System.out.println("LABEL: *********" + f);
}
}
// most important of all, the cat.no a.k.a. labelcode ;-)
assignLabelFieldMatchStati(repository, m);
}
@SuppressWarnings("unchecked")
private static boolean checkLabelcodeAgainstLabelAlternatives(MatchResult labelCodeMatch, MatchResult recordLabelMatch) {
SimilarScanResult<Label> sr = (SimilarScanResult<Label>) recordLabelMatch.getSimilarScanResult();
if (sr != null) { // 1 - n exact or similar labels found
final int ms = sr.size();
final String lc = labelCodeMatch.getMatch();
for (int i = 0; i < ms; i++) { // => check if any of these label objects contains the found code
if (sr.get(i).getFoundObject().findLabelcode(lc) != null) {
// ... if so, move found label to top (only if it is not no. 1 :-)
if (i != 0) { sr.setTopMatch(i); }
// => x-reference total success => set both field stati to green
labelCodeMatch.setStatus(MatchResult.Status.KNOWN);
recordLabelMatch.setStatus(MatchResult.Status.KNOWN); // changes the similar status to known!!!
return true; // .. quit & signal success
}
}
// none of the matching labels contains the code => leave label yellow & set lc to UNKNOWN
// => nevertheless, x-reference successful
labelCodeMatch.setStatus(MatchResult.Status.UNKNOWN);
return true;
} else { // no label matches => x-reference failed!
// no matches for the found label name => set labelcode match to UNKNOWN
labelCodeMatch.setStatus(MatchResult.Status.UNKNOWN);
return false;
}
}
private static void assignLabelFieldMatchStati(Repository repository,
Map<NfoField, MatchResult> matches) {
MatchResult lcmatch;
MatchResult labelmatch = matches.get(NfoField.RECORD_LABEL);
if((lcmatch = matches.get(NfoField.LABEL_CODE)) != null) { // get the lc's match result, if set, proceed!
// we found a label code, if a label name was also found, cross-reference!
if (labelmatch != null) { // label name parsed successfully
// try find matching label from label object alternatives, if any
checkLabelcodeAgainstLabelAlternatives(lcmatch, labelmatch);
} else { // no label name could be parsed => global-lookup label name from code!
findLabelByLabelcode(repository, lcmatch, matches);
}
} else { // mr is null => label-code parsing failed - but if...
if (labelmatch != null) { // the label name was parsed successfully, get code alternatives from label objects
createLabelcodeMatchFromLabel(matches, labelmatch);
} // else {} mr = mlbl = null => neither label nor code successfully parsed => no status 2 set => folder analyzer & ID3TagAnalyzer have 2 kick in 2 find it out!
}
}
@SuppressWarnings("unchecked")
private static void createLabelcodeMatchFromLabel(
Map<NfoField, MatchResult> matches, MatchResult labelNameMatch) {
SimilarScanResult<Label> sr = (SimilarScanResult<Label>) labelNameMatch.getSimilarScanResult();
if (sr != null) { // at least one found label
Label l = null; // holds the found label
Labelcode lc;
SimilarScanResult<Labelcode> srlc = null; // holds the label code alternatives
Match<Labelcode> mt = null; // holds the virtual match
// => create virtual label code match from best label match having codes
final int c = sr.size();
MatchResult mr = null;
for (int i = 0; i < c; i++) { // iterate label matches
l = sr.get(i).getFoundObject(); // get label object from match
if (l.size() == 0) { continue; } // label has no codes => skip
// label has codes => create matches
int ci = 0;
if (mr == null) { // no match result yet => create from best entry & store
mr = new MatchResult(l.get(ci++).getName(), Source.LOOKUP, Status.SIMILAR); // XXX derived, (not similar) entry !
matches.put(NfoField.LABEL_CODE, mr);
}
for (int y = ci; y < l.size(); y++) { // => add other codes as alternatives
lc = l.get(y);
// XXX for now, use fake leven, but create virtual match type l8a on
mt = new Match<Labelcode>(MatchType.LEVENSHTEIN, lc.getName(), lc);
if (srlc == null) {
srlc = new SimilarScanResult<Labelcode>(mt);
mr.setSimilarScanResult(srlc);
} else { srlc.add(mt); }
}
}
} // else {} // no label object matches found, but labelname matched => leave unknown
}
private static void findLabelByLabelcode(Repository labelLookup,
MatchResult labelCodeMatch, Map<NfoField, MatchResult> matchStore) {
String lc = labelCodeMatch.getMatch();
MatchResult mlbl;
Label other = labelLookup.findByLabelcode(lc);
if (other != null) { // we got a label name for our code
// => create virtual label name match!
mlbl = new MatchResult(other.getName(), Source.LOOKUP, Status.KNOWN);
mlbl.setSimilarScanResult(new SimilarScanResult<Label>(
new Match<Label>(MatchType.EXACT, other.getName(), other)));
matchStore.put(NfoField.RECORD_LABEL, mlbl);
// set label code match to green & create EXACT scan result
labelCodeMatch.setStatus(Status.KNOWN);
Labelcode found = other.findLabelcode(lc); // refind to get the lc object
labelCodeMatch.setSimilarScanResult(new SimilarScanResult<Labelcode>(
new Match<Labelcode>(MatchType.EXACT, found.getName(), found)));
} else { // no label name could be parsed AND no label found for our code
labelCodeMatch.setStatus(MatchResult.Status.UNKNOWN); // => set to unknown, too
}
}
public static void analyzeFolders(Repository repository, List<Folder> list, AlbumDataConfirmInteraction confirmInteraction) {
int s;
Component c;
NfoLyzer nfoLyzer = new NfoLyzer();
for (Folder f : list) { // for all folders,
s = f.size();
NfoLyzeResult result = new NfoLyzeResult(f); // one nfo result per folder...
for (int i = 0; i < s; i++) { // iterate files,
c = f.get(i);
if (c instanceof NfoFile) { // find nfos
try {
if (!nfoLyzer.parseNfo(result, (NfoFile) c)) {
System.out.println("No parser for " + c.pathString() + ", trying next nfo.");
continue;
} else {
// parse success => validate, create & save album data
// set repository object candidates and the status of all index fields
assignMatchStati(result, repository); // find known or similar entries & set stati accordingly
// some post-processing
postProcess(result);
if(confirmInteraction.confirm((NfoFile) c, result)) {
// confirmed => convert to Album data & persist!
ReleaseData d = NfoDataFactory.fromNfo(result, repository);
System.out.println(d);
} else {
System.out.println("DECLINED!");
}
break; // cancel nfo discovery
}
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch(RuntimeException e) {
System.out.println(e.getMessage());
}
}
}
if (result.getMatches().isEmpty()) {
System.out.println("Folder parser required for " + f.pathString());
}
}
}
// XXX: => to postprocessor!
private static void postProcess(NfoLyzeResult result) {
// rule 1: known label code = title => set title known
MatchResult lc = result.getMatches().get(NfoField.LABEL_CODE);
MatchResult t = result.getMatches().get(NfoField.ALBUM_TITLE);
if(lc != null && t != null // got both title & a known lc
&& lc.getStatus() == Status.KNOWN
&& lc.getMatch().equalsIgnoreCase(t.getMatch())) {
t.setStatus(Status.KNOWN);
}
// rule 2: label name contains a numeric that NfoField.LABEL_CODE_NO starts with => parsing probably failed!
MatchResult ln = result.getMatches().get(NfoField.RECORD_LABEL);
MatchResult lcn = result.getMatches().get(NfoField.LABEL_CODE_NO);
if (ln != null && lcn != null) {
Matcher m = NUMBER_AT_END_PATTERN.matcher(ln.getMatch());
String no = lcn.getMatch();
if (m.find()) {
String mt = m.group(1);
if (no.startsWith(mt)) {
lcn.setMatch(no.substring(mt.length()));
lcn.setStart(lcn.getStart() + 2);
lc.setMatch(lc.getMatch() + mt);
lc.setEnd(lc.getEnd() + 2);
System.err.println("!!!!!!!ERROR!!!!!!!!!!!!!!!!");
// XXX: => visualize this critital error & its correction on the ui
}
}
}
}
public static void assignLabelcodes(LabelcodeConfirmInteraction interaction, List<Folder> list, Set<String> labelCodes,
Set<String> ignored, boolean folderNameIsLabelcode, boolean lookupLabelcodeInNfo) {
for (Folder f : list) {
if (f.findLabelcode() == null) { // no label code for this folder yet => assign
CatalogNumberHibernateImpl lc = null;
// walk bottom up and try to find label code
Folder cur = f;
if (folderNameIsLabelcode) {
while(cur.getParent().getName() != null) { // walk until root
lc = LabelcodeMatcher.findLabelcode(cur.getName());
if (lc == null) {
lc = LabelcodeMatcher.findLabelcode2(cur.getName());
}
if (lc != null) {
break;
}
cur = (Folder) cur.getParent();
}
} else {
while(cur.getParent().getName() != null // walk until root
&& (lc = LabelcodeMatcher.findLabelcode(cur.getName())) == null) {
cur = (Folder) cur.getParent();
}
}
if (lc != null) { // got label code from folder => confirm
if(!addLabelCode(interaction, labelCodes, ignored, cur, lc)) {
// not confirmed => try get from nfo if indicated
if (lookupLabelcodeInNfo) {
lc = fromNfo(f);
if (lc != null) {
// try 2 get this label code confirmed
addLabelCode(interaction, labelCodes, ignored, f, lc);
}
}
}
} else if (lookupLabelcodeInNfo) {
lc = fromNfo(f);
if (lc != null) {
// try 2 get this label code confirmed
addLabelCode(interaction, labelCodes, ignored, f, lc);
}
}
}
}
}
private static CatalogNumberHibernateImpl fromNfo(Folder f) {
final int s = f.size();
Component c;
CatalogNumberHibernateImpl lc = null;
String lm = null;
for (int i = 0; i < s; i++) {
c = f.get(i);
if (c instanceof NfoFile) {
String pth = c.pathString();
try {
System.out.println("Processing " + pth);
String nfo = IOUtils.toString(new FileReader(pth));
Matcher m = NFO_LABELCODE_PATTERN.matcher(nfo);
while (m.find()) {
lc = LabelcodeMatcher.findLabelcode(m.group(1));
lm = m.group();
if (lc == null) { // try less strict
lc = LabelcodeMatcher.findLabelcode2(m.group(1));
}
if (lc != null) { // found => quit looking @ current nfo
break;
}
}
if (lc != null) { // found => quit looking @ other nfos
break;
}
} catch (IOException e) {
System.err.println("Unable to open " + pth);
}
}
}
if (lm != null) {
System.out.println(lm);
}
return lc;
}
}