package net.pms.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.pms.PMS; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FileDb { private static final Logger LOGGER = LoggerFactory.getLogger(FileDb.class); private static final String NULLOBJ_STR = "@@@NULLOBJ@@@"; private Map<String, Object> db; private int minCnt; private String separator; private String encodedSeparator; private File file; private DbHandler handler; private boolean autoSync; private boolean overwrite; private boolean useNullObj; private Object nullObj; private boolean hasNulls; public FileDb(DbHandler h) { this(PMS.getConfiguration().getDataFile(h.name()), h); } public FileDb(String f, DbHandler h) { if (StringUtils.isEmpty(f)) { f = "UMS.db"; } file = new File(f); handler = h; minCnt = 2; separator = ","; encodedSeparator = ","; autoSync = true; overwrite = false; useNullObj = false; db = new HashMap<>(); nullObj = new Object(); hasNulls = false; } public void setSep(String separator, String encodedSeparator) { if (separator == null || encodedSeparator == null) { throw new IllegalArgumentException("Neither argument can be null"); } this.separator = separator; this.encodedSeparator = encodedSeparator; } public void setMinCnt(int c) { minCnt = c; } public void setAutoSync(boolean b) { autoSync = b; } public void setOverwrite(boolean b) { overwrite = b; } public void setUseNullObj(boolean b) { useNullObj = b; } public Object nullObj() { return nullObj; } public boolean isNull(Object obj) { return ((obj == null) || (obj == nullObj)); } public boolean hasNulls() { return hasNulls; } public Set<String> keys() { return db.keySet(); } public Iterator iterator() { return db.entrySet().iterator(); } private String recode(String str) { return Pattern.compile(encodedSeparator, Pattern.LITERAL | Pattern.CASE_INSENSITIVE). matcher(str).replaceAll(Matcher.quoteReplacement(separator)); } public void init() { if (!file.exists()) { return; } hasNulls = false; try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { String line; while ((line = in.readLine()) != null) { line = line.trim(); if (StringUtils.isEmpty(line) || line.startsWith("#")) { continue; } if (useNullObj) { String re = ".*" + separator + NULLOBJ_STR + "$"; if (line.matches(re)) { // we got a line which is key, NULL // translate to nullobj hasNulls = true; String[] key = Pattern.compile(separator, Pattern.LITERAL).split(line); db.put(recode(key[0]), nullObj); continue; } } String[] entry = Pattern.compile(separator, Pattern.LITERAL).split(line); if (entry.length < minCnt) { continue; } // Substitute the encoded separator with the separator for (int i = 0; i < entry.length; i++) { if (entry[i] != null) { entry[i] = recode(entry[i]); } } db.put(entry[0], handler.create(entry)); } } catch (IOException e) { LOGGER.warn("Could not read file database file \"{}\": {}", file.getAbsolutePath(), e.getMessage()); LOGGER.trace("", e); } } public void addNoSync(String key, Object obj) { if (!overwrite) { if (get(key) != null) { return; } } db.put(key, obj); hasNulls |= isNull(obj); } public void removeNoSync(String key) { db.remove(key); } public void add(String key, Object obj) { addNoSync(key, obj); if (autoSync) { sync(); } } public void remove(String key) { db.remove(key); if (autoSync) { sync(); } } public Object get(String key) { return db.get(key); } public void sync() { try (FileOutputStream out = new FileOutputStream(file)) { // Write a dummy line to make sure the file exists Date now = new Date(); StringBuilder data = new StringBuilder(); data.append("#########################\n#### Db file generated ").append(now.toString()).append("\n") .append("#### Edit with care\n#########################\n"); out.write(data.toString().getBytes(StandardCharsets.UTF_8)); hasNulls = false; for (Entry<String, Object> entry : db.entrySet()) { Object obj = entry.getValue(); data = new StringBuilder(Pattern.compile(separator, Pattern.LITERAL). matcher(entry.getKey()). replaceAll(Matcher.quoteReplacement(encodedSeparator))); if (isNull(obj)) { hasNulls = true; if (useNullObj) { data.append(separator); data.append(NULLOBJ_STR); } else { for (int i = 1; i < minCnt; i++) { data.append(separator); } } data.append("\n"); } else { String[] data1 = handler.format(obj); // Substitute the separator with the encoded separator for (int i = 0; i < data1.length; i++) { data1[i] = Pattern.compile(separator, Pattern.LITERAL).matcher(data1[i]).replaceAll(Matcher.quoteReplacement(encodedSeparator)); } data.append(separator).append(StringUtils.join(data1, separator)).append("\n"); } out.write(data.toString().getBytes(StandardCharsets.UTF_8)); } out.flush(); } catch (IOException e) { LOGGER.warn("Could not write file database file \"{}\": {}", file.getAbsolutePath(), e.getMessage()); LOGGER.trace("", e); } } public static String safeGetArg(String[] args, int i) { return (i >= args.length ? "" : args[i]); } }