001package net.filebot.format;
002
003import static java.util.Collections.*;
004import static net.filebot.media.XattrMetaInfo.*;
005import static net.filebot.util.FileUtilities.*;
006import static net.filebot.util.RegularExpressions.*;
007
008import java.io.File;
009import java.util.AbstractList;
010import java.util.Collection;
011import java.util.HashSet;
012import java.util.LinkedHashMap;
013import java.util.List;
014import java.util.Map;
015import java.util.RandomAccess;
016import java.util.Set;
017import java.util.regex.Pattern;
018
019import groovy.json.JsonSlurper;
020import groovy.xml.XmlSlurper;
021
022import net.filebot.MemoryCache;
023import net.filebot.Resource;
024
025public class DataFile {
026
027        private final File file;
028        private final boolean directory;
029        private final long lastModified;
030
031        public DataFile(File file) {
032                this.file = file;
033                this.directory = file.isDirectory();
034                this.lastModified = file.lastModified();
035        }
036
037        public boolean isStale() {
038                return this.lastModified != file.lastModified();
039        }
040
041        private Map<Object, Object> csv() throws Exception {
042                Map<Object, Object> value = new LinkedHashMap<Object, Object>();
043
044                if (directory) {
045                        // read xattr file structure
046                        for (File f : listFiles(file, NOT_HIDDEN)) {
047                                Object m = xattr.getMetaInfo(f);
048                                if (m != null) {
049                                        value.put(f, m);
050                                }
051                        }
052                } else {
053                        // read CSV file
054                        Pattern[] delimiter = { TAB, SEMICOLON };
055                        for (String line : readLines(file)) {
056                                for (Pattern d : delimiter) {
057                                        String[] field = d.split(line, 2);
058                                        if (field.length >= 2) {
059                                                value.put(field[0].trim(), field[1].trim());
060                                                break;
061                                        }
062                                }
063                        }
064                }
065                return unmodifiableMap(value);
066        }
067
068        private List<String> lines() throws Exception {
069                if (directory) {
070                        return new LookupList(file.list());
071                }
072                return new LookupList(readLines(file));
073        }
074
075        private Object xml() throws Exception {
076                return new XmlSlurper().parse(file);
077        }
078
079        private Object json() throws Exception {
080                return new JsonSlurper().parse(file);
081        }
082
083        public final Resource<Map<Object, Object>> csv = Resource.lazy(this::csv);
084        public final Resource<List<String>> lines = Resource.lazy(this::lines);
085        public final Resource<Object> xml = Resource.lazy(this::xml);
086        public final Resource<Object> json = Resource.lazy(this::json);
087
088        public static synchronized DataFile getDataFile(File file) throws Exception {
089                DataFile data = cache.getIfPresent(file);
090                if (data == null || data.isStale()) {
091                        data = new DataFile(file);
092                        cache.put(file, data);
093                }
094                return data;
095        }
096
097        private static final MemoryCache<File, DataFile> cache = MemoryCache.forMinutes();
098
099}
100
101class LookupList extends AbstractList<String> implements RandomAccess {
102
103        private final String[] values;
104
105        public LookupList(Collection<String> values) {
106                this.values = values.toArray(new String[0]);
107        }
108
109        public LookupList(String[] values) {
110                this.values = values;
111        }
112
113        @Override
114        public String get(int index) {
115                return values[index];
116        }
117
118        @Override
119        public int size() {
120                return values.length;
121        }
122
123        private Set<String> lookup;
124
125        private Set<String> getLookup() {
126                if (lookup == null) {
127                        lookup = new HashSet<String>(this);
128                }
129                return lookup;
130        }
131
132        @Override
133        public boolean contains(Object object) {
134                if (object == null) {
135                        return false;
136                }
137                return getLookup().contains(object.toString());
138        }
139}