/* * Universal Media Server, for streaming any media to DLNA * compatible renderers based on the http://www.ps3mediaserver.org. * Copyright (C) 2012 UMS developers. * * This program is a free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License only. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.pms.util; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.Collator; import java.util.*; import java.util.List; import net.pms.PMS; import net.pms.configuration.RendererConfiguration; import net.pms.dlna.*; import net.pms.encoders.Player; import net.pms.encoders.PlayerFactory; import net.pms.external.ExternalFactory; import net.pms.external.ExternalListener; import net.pms.formats.Format; import net.pms.formats.v2.SubtitleType; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class UMSUtils { private static final Collator collator; private static final Logger LOGGER = LoggerFactory.getLogger(UMSUtils.class); static { collator = Collator.getInstance(); collator.setStrength(Collator.PRIMARY); } public static void postSearch(List<DLNAResource> files, String searchCriteria) { if (files == null || searchCriteria == null) { return; } searchCriteria = searchCriteria.toLowerCase(); for (int i = files.size() - 1; i >= 0; i--) { DLNAResource res = files.get(i); if (res.isSearched()) { continue; } boolean keep = res.getName().toLowerCase().contains(searchCriteria); final DLNAMediaInfo media = res.getMedia(); if (!keep && media != null && media.getAudioTracksList() != null) { for (int j = 0; j < media.getAudioTracksList().size(); j++) { DLNAMediaAudio audio = media.getAudioTracksList().get(j); if (audio.getAlbum() != null) { keep |= audio.getAlbum().toLowerCase().contains(searchCriteria); } if (audio.getArtist() != null) { keep |= audio.getArtist().toLowerCase().contains(searchCriteria); } if (audio.getSongname() != null) { keep |= audio.getSongname().toLowerCase().contains(searchCriteria); } } } if (!keep) { // dump it files.remove(i); } } } // Sort constants public static final int SORT_LOC_SENS = 0; public static final int SORT_MOD_NEW = 1; public static final int SORT_MOD_OLD = 2; public static final int SORT_INS_ASCII = 3; public static final int SORT_LOC_NAT = 4; public static final int SORT_RANDOM = 5; public static final int SORT_NO_SORT = 6; public static void sort(List<File> files, int method) { switch (method) { case SORT_NO_SORT: // no sorting break; case SORT_LOC_NAT: // Locale-sensitive natural sort Collections.sort(files, new Comparator<File>() { @Override public int compare(File f1, File f2) { String filename1ToSort = FileUtil.renameForSorting(f1.getName()); String filename2ToSort = FileUtil.renameForSorting(f2.getName()); return NaturalComparator.compareNatural(collator, filename1ToSort, filename2ToSort); } }); break; case SORT_INS_ASCII: // Case-insensitive ASCIIbetical sort Collections.sort(files, new Comparator<File>() { @Override public int compare(File f1, File f2) { String filename1ToSort = FileUtil.renameForSorting(f1.getName()); String filename2ToSort = FileUtil.renameForSorting(f2.getName()); return filename1ToSort.compareToIgnoreCase(filename2ToSort); } }); break; case SORT_MOD_OLD: // Sort by modified date, oldest first Collections.sort(files, new Comparator<File>() { @Override public int compare(File f1, File f2) { return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified()); } }); break; case SORT_MOD_NEW: // Sort by modified date, newest first Collections.sort(files, new Comparator<File>() { @Override public int compare(File f1, File f2) { return Long.valueOf(f2.lastModified()).compareTo(f1.lastModified()); } }); break; case SORT_RANDOM: // Random Collections.shuffle(files, new Random(System.currentTimeMillis())); break; case SORT_LOC_SENS: // Same as default default: // Locale-sensitive A-Z Collections.sort(files, new Comparator<File>() { @Override public int compare(File f1, File f2) { String filename1ToSort = FileUtil.renameForSorting(f1.getName()); String filename2ToSort = FileUtil.renameForSorting(f2.getName()); return collator.compare(filename1ToSort, filename2ToSort); } }); break; } } public static String playedDurationStr(String current, String duration) { String pos = StringUtil.shortTime(current, 4); String dur = StringUtil.shortTime(duration, 4); return pos + (pos.equals("0:00") ? "" : dur.equals("0:00") ? "" : (" / " + dur)); } private static String iso639(String s) { String[] tmp = s.split(","); StringBuilder res = new StringBuilder(); String sep = ""; for (String tmp1 : tmp) { res.append(sep).append(Iso639.getISO639_2Code(tmp1)); sep = ","; } if (StringUtils.isNotEmpty(res)) { return res.toString(); } return s; } public static String getLangList(RendererConfiguration r) { return getLangList(r, false); } public static String getLangList(RendererConfiguration r, boolean three) { String res; if (r != null) { res = r.getSubLanguage(); } else { res = PMS.getConfiguration().getSubtitlesLanguages(); } if (three) { res = iso639(res); } return res; } /** * Bitwise constants relating to playlist management. */ public interface IOListModes { public static final int PERMANENT = 1; public static final int AUTOSAVE = 2; public static final int AUTOREMOVE = 4; } /** * A DLNAResource list with built-in file i/o. */ public static class IOList extends ArrayList<DLNAResource> implements IOListModes { private static final long serialVersionUID = 8042924548275374060L; private File file; private int mode; public IOList(String uri, int mode) { this.mode = mode; if (!StringUtils.isBlank(uri)) { file = new File(uri); load(file); } else { file = null; } } @Override public boolean add(DLNAResource d) { super.add(d); if (isMode(AUTOSAVE)) { save(); } return true; } @Override public DLNAResource remove(int index) { DLNAResource d = super.remove(index); if (isMode(AUTOSAVE)) { save(); } return d; } public boolean isMode(int m) { return (mode & m) == m; } public File getFile() { return file; } public void load(File f) { if (f.exists()) { file = f; clear(); try { read(this, f); } catch (IOException e) { LOGGER.debug("Error loading resource list '{}': {}", f, e); } if (PMS.getConfiguration().getSortMethod(f) == UMSUtils.SORT_RANDOM) { Collections.shuffle(this); } } } public void save() { if (file != null) { save(file); } } public void save(File f) { if (size() > 0) { try { write(this, f); } catch (IOException e) { LOGGER.debug("Error saving resource list to '{}': {}", f, e); } } else if (f != null && f.exists()) { // Save = delete for empty playlists f.delete(); } } public static void write(List<DLNAResource> playlist, File f) throws IOException { Date now = new Date(); try (FileWriter out = new FileWriter(f)) { StringBuilder sb = new StringBuilder(); sb.append("######\n"); sb.append("## __UPS__\n"); sb.append("## NOTE!!!!!\n"); sb.append("## This file is auto generated\n"); sb.append("## Edit with EXTREME care\n"); sb.append("## Generated: "); sb.append(now.toString()); sb.append("\n"); for (DLNAResource r : playlist) { String data = r.write(); if (!org.apache.commons.lang.StringUtils.isEmpty(data) && sb.indexOf(data) == -1) { ExternalListener external = r.getMasterParent(); String id; if (external != null) { id = external.getClass().getName(); } else { id = "internal:" + r.getClass().getName(); } sb.append("master:").append(id).append(';'); if (r.getPlayer() != null) { sb.append("player:").append(r.getPlayer().toString()).append(';'); } if (r.isResume()) { sb.append("resume"); sb.append(r.getResume().getResumeFile().getAbsolutePath()); sb.append(';'); } if (r.getMediaSubtitle() != null) { DLNAMediaSubtitle sub = r.getMediaSubtitle(); if (sub.getLang() != null && sub.getId() != -1) { sb.append("sub"); sb.append(sub.getLang()); sb.append(','); if (sub.isExternal()) { sb.append("file:"); sb.append(sub.getExternalFile().getAbsolutePath()); } else { sb.append("id:"); sb.append("").append(sub.getId()); } sb.append(';'); } } sb.append(data); sb.append("\n"); } } out.write(sb.toString()); out.flush(); } } private static ExternalListener findMasterParent(String className) { for (ExternalListener l : ExternalFactory.getExternalListeners()) { if (className.equals(l.getClass().getName())) { return l; } } return null; } private static Player findPlayer(String playerName) { for (Player p : PlayerFactory.getPlayers()) { if (playerName.equals(p.name())) { return p; } } return null; } private static DLNAResource parse(String clazz, String data) { boolean error = false; if (clazz.contains("RealFile")) { if (data.contains(">")) { String[] tmp = data.split(">"); return new RealFile(new File(tmp[1]), tmp[0]); } error = true; } if (clazz.contains("SevenZipEntry")) { if (data.contains(">")) { String[] tmp = data.split(">"); long len = Long.parseLong(tmp[2]); return new SevenZipEntry(new File(tmp[1]), tmp[0], len); } error = true; } if (clazz.contains("ZippedEntry")) { if (data.contains(">")) { String[] tmp = data.split(">"); long len = Long.parseLong(tmp[2]); return new ZippedEntry(new File(tmp[1]), tmp[0], len); } error = true; } if (clazz.contains("WebStream")) { if (data.contains(">")) { String[] tmp = data.split(">"); int type; try { type = Integer.parseInt(tmp[3]); } catch (NumberFormatException e) { type = Format.UNKNOWN; } return new WebStream(tmp[0], tmp[1], tmp[2], type); } error = true; } if (error) { LOGGER.debug("Error parsing playlist resource:"); LOGGER.debug("clazz: " + clazz); LOGGER.debug("data:" + data); } return null; } public static void read(List<DLNAResource> playlist, File f) throws IOException { if (!f.exists()) { return; } try (BufferedReader in = new BufferedReader(new FileReader(f))) { String str; while ((str = in.readLine()) != null) { if (org.apache.commons.lang.StringUtils.isEmpty(str)) { continue; } if (str.startsWith("#")) { continue; } str = str.trim(); if (!str.startsWith("master:")) { continue; } str = str.substring(7); int pos = str.indexOf(';'); if (pos == -1) { continue; } String master = str.substring(0, pos); str = str.substring(pos + 1); pos = str.indexOf(';'); String subData = null; String resData = null; DLNAResource res = null; Player player = null; while (pos != -1) { if (str.startsWith("player:")) { // find last player player = findPlayer(str.substring(7, pos)); } if (str.startsWith("resume")) { // resume data resData = str.substring(6, pos); } if (str.startsWith("sub")) { // subs data subData = str.substring(3, pos); } str = str.substring(pos + 1); pos = str.indexOf(';'); } LOGGER.debug("master is " + master + " str " + str); ExternalListener external; if (master.startsWith("internal:")) { res = parse(master.substring(9), str); } else { external = findMasterParent(master); if (external != null) { res = resolveCreateMethod(external, str); if (res != null) { LOGGER.debug("set masterparent for " + res + " to " + external); res.setMasterParent(external); } } } if (res != null) { if (resData != null) { ResumeObj r = new ResumeObj(new File(resData)); if (!r.isDone()) { r.read(); res.setResume(r); } } res.setPlayer(player); if (subData != null) { DLNAMediaSubtitle s = res.getMediaSubtitle(); if (s == null) { s = new DLNAMediaSubtitle(); res.setMediaSubtitle(s); } String[] tmp = subData.split(","); s.setLang(tmp[0]); subData = tmp[1]; if (subData.startsWith("file:")) { String sFile = subData.substring(5); s.setExternalFile(new File(sFile), null); s.setId(100); SubtitleType t = SubtitleType.valueOfFileExtension(FileUtil.getExtension(sFile)); s.setType(t); } else if (subData.startsWith("id:")) { s.setId(Integer.parseInt(subData.substring(3))); } } playlist.add(res); } } } } public static DLNAResource resolveCreateMethod(ExternalListener l, String arg) { // FIXME: this effectively imposes an undeclared interface, better to declare it explicitly Method create; try { Class<?> clazz = l.getClass(); create = clazz.getDeclaredMethod("create", String.class); return (DLNAResource) create.invoke(l, arg); // Ignore all errors } catch (SecurityException | NoSuchMethodException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { LOGGER.debug("Unable to recreate {} item: {}", l.name(), arg); } return null; } } }