Package net.pms.dlna

Source Code of net.pms.dlna.RootFolder

/*
* PS3 Media Server, for streaming any medias to your PS3.
* Copyright (C) 2008  A.Brochard
*
* This program is 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.dlna;

import com.sun.jna.Platform;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.text.Collator;
import java.text.Normalizer;
import java.util.*;
import net.pms.Messages;
import net.pms.PMS;
import net.pms.configuration.DownloadPlugins;
import net.pms.configuration.MapFileConfiguration;
import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.dlna.virtual.VirtualFolder;
import net.pms.dlna.virtual.VirtualVideoAction;
import net.pms.external.AdditionalFolderAtRoot;
import net.pms.external.AdditionalFoldersAtRoot;
import net.pms.external.ExternalFactory;
import net.pms.external.ExternalListener;
import net.pms.formats.Format;
import net.pms.newgui.IFrame;
import net.pms.util.FileUtil;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xmlwise.Plist;
import xmlwise.XmlParseException;

public class RootFolder extends DLNAResource {
  private static final Logger LOGGER = LoggerFactory.getLogger(RootFolder.class);
  private static final PmsConfiguration configuration = PMS.getConfiguration();
  private boolean running;
  private FolderLimit lim;
  private MediaMonitor mon;
  private RecentlyPlayed last;
  private ArrayList<String> tags;

  public RootFolder(ArrayList<String> tags) {
    setIndexId(0);
    this.tags = tags;
  }

  public RootFolder() {
    this(null);
  }

  @Override
  public InputStream getInputStream() {
    return null;
  }

  @Override
  public String getName() {
    return "root";
  }

  @Override
  public boolean isFolder() {
    return true;
  }

  @Override
  public long length() {
    return 0;
  }

  @Override
  public String getSystemName() {
    return getName();
  }

  @Override
  public boolean isValid() {
    return true;
  }

  @Override
  public void discoverChildren() {
    if (isDiscovered()) {
      return;
    }

    if (!configuration.isHideRecentlyPlayedFolder()) {
      last = new RecentlyPlayed();
      addChild(last);
    }

    if (!configuration.isHideNewMediaFolder()) {
      String m = (String) configuration.getFoldersMonitored();
      if (!StringUtils.isEmpty(m)) {
        String[] tmp = m.split(",");
        File[] dirs = new File[tmp.length];
        for (int i = 0; i < tmp.length; i++) {
          dirs[i] = new File(tmp[i]);
        }
        mon = new MediaMonitor(dirs);
        addChild(mon);
      }
    }

    if (configuration.getFolderLimit() && getDefaultRenderer().isLimitFolders()) {
      lim = new FolderLimit();
      addChild(lim);
    }

    for (DLNAResource r : getConfiguredFolders(tags)) {
      addChild(r);
    }

    for (DLNAResource r : getVirtualFolders(tags)) {
      addChild(r);
    }

    String webConfPath = configuration.getWebConfPath();
    File webConf = new File(webConfPath);
    if (webConf.exists() && configuration.getExternalNetwork() && !configuration.isHideWebFolder(tags)) {
      addWebFolder(webConf);
    }

    if (Platform.isMac() && configuration.isShowIphotoLibrary()) {
      DLNAResource iPhotoRes = getiPhotoFolder();
      if (iPhotoRes != null) {
        addChild(iPhotoRes);
      }
    }

    if (Platform.isMac() && configuration.isShowApertureLibrary()) {
      DLNAResource apertureRes = getApertureFolder();
      if (apertureRes != null) {
        addChild(apertureRes);
      }
    }

    if ((Platform.isMac() || Platform.isWindows()) && configuration.isShowItunesLibrary()) {
      DLNAResource iTunesRes = getiTunesFolder();
      if (iTunesRes != null) {
        addChild(iTunesRes);
      }
    }

    if (!configuration.isHideMediaLibraryFolder()) {
      DLNAResource libraryRes = PMS.get().getLibrary();
      if (libraryRes != null) {
        addChild(libraryRes);
      }
    }

    for (DLNAResource r : getAdditionalFoldersAtRoot()) {
      addChild(r);
    }

    if (!configuration.getHideVideoSettings()) {
      addAdminFolder();
    }

    setDiscovered(true);
  }

  public void setFolderLim(DLNAResource r) {
    if (lim != null) {
      lim.setStart(r);
    }
  }

  public void scan() {
    running = true;

    if (!isDiscovered()) {
      discoverChildren();
    }

    setDefaultRenderer(RendererConfiguration.getDefaultConf());
    scan(this);
    IFrame frame = PMS.get().getFrame();
    frame.setScanLibraryEnabled(true);
    PMS.get().getDatabase().cleanup();
    frame.setStatusLine(null);
  }

  /*
   * @deprecated Use {@link #stopScan()} instead.
   */
  @Deprecated
  public void stopscan() {
    stopScan();
  }

  public void stopScan() {
    running = false;
  }

  private void scan(DLNAResource resource) {
    if (running) {
      for (DLNAResource child : resource.getChildren()) {
        if (running && child.allowScan()) {
          child.setDefaultRenderer(resource.getDefaultRenderer());
          String trace = null;

          if (child instanceof RealFile) {
            trace = Messages.getString("DLNAMediaDatabase.4") + " " + child.getName();
          }

          if (trace != null) {
            LOGGER.debug(trace);
            PMS.get().getFrame().setStatusLine(trace);
          }

          if (child.isDiscovered()) {
            child.refreshChildren();
          } else {
            if (child instanceof DVDISOFile || child instanceof DVDISOTitle) { // ugly hack
              child.resolve();
            }
            child.discoverChildren();
            child.analyzeChildren(-1);
            child.setDiscovered(true);
          }

          int count = child.getChildren().size();

          if (count == 0) {
            continue;
          }

          scan(child);
          child.getChildren().clear();
        }
      }
    }
  }

  private List<RealFile> getConfiguredFolders(ArrayList<String> tags) {
    List<RealFile> res = new ArrayList<>();
    File[] files = PMS.get().getSharedFoldersArray(false, tags);
    String s = PMS.getConfiguration().getFoldersIgnored(tags);
    String[] skips = null;

    if (s != null) {
      skips = s.split(",");
    }

    if (files == null || files.length == 0) {
      files = File.listRoots();
    }

    for (File f : files) {
      if (skipPath(skips, f.getAbsolutePath().toLowerCase())) {
        continue;
      }
      res.add(new RealFile(f));
    }

    if (configuration.getSearchFolder()) {
      SearchFolder sf = new SearchFolder(Messages.getString("PMS.143"), new FileSearch(res));
      addChild(sf);
    }

    return res;
  }

  private boolean skipPath(String[] skips, String path) {
    for (String s : skips) {
      if (StringUtils.isBlank(s)) {
        continue;
      }

      if (path.contains(s.toLowerCase())) {
        return true;
      }
    }

    return false;
  }

  private List<DLNAResource> getVirtualFolders(ArrayList<String> tags) {
    List<DLNAResource> res = new ArrayList<>();
    List<MapFileConfiguration> mapFileConfs = MapFileConfiguration.parseVirtualFolders(tags);

    if (mapFileConfs != null) {
      for (MapFileConfiguration f : mapFileConfs) {
        res.add(new MapFile(f));
      }
    }

    return res;
  }

  private void addWebFolder(File webConf) {
    if (webConf.exists()) {
      try {
        try (LineNumberReader br = new LineNumberReader(new InputStreamReader(new FileInputStream(webConf), "UTF-8"))) {
          String line;
          while ((line = br.readLine()) != null) {
            line = line.trim();

            if (line.length() > 0 && !line.startsWith("#") && line.indexOf('=') > -1) {
              String key = line.substring(0, line.indexOf('='));
              String value = line.substring(line.indexOf('=') + 1);
              String[] keys = parseFeedKey(key);

              try {
                if (
                  keys[0].equals("imagefeed") ||
                  keys[0].equals("audiofeed") ||
                  keys[0].equals("videofeed") ||
                  keys[0].equals("audiostream") ||
                  keys[0].equals("videostream")
                ) {
                  String[] values = parseFeedValue(value);
                  DLNAResource parent = null;

                  if (keys[1] != null) {
                    StringTokenizer st = new StringTokenizer(keys[1], ",");
                    DLNAResource currentRoot = this;

                    while (st.hasMoreTokens()) {
                      String folder = st.nextToken();
                      parent = currentRoot.searchByName(folder);

                      if (parent == null) {
                        parent = new VirtualFolder(folder, "");
                        currentRoot.addChild(parent);
                      }

                      currentRoot = parent;
                    }
                  }

                  if (parent == null) {
                    parent = this;
                  }
                  if (keys[0].endsWith("stream")) {
                    int type = keys[0].startsWith("audio") ? Format.AUDIO : Format.VIDEO;
                    DLNAResource playlist = PlaylistFolder.getPlaylist(values[0], values[1], type);
                    if (playlist != null) {
                      parent.addChild(playlist);
                      continue;
                    }
                  }
                  switch (keys[0]) {
                    case "imagefeed":
                      parent.addChild(new ImagesFeed(values[0]));
                      break;
                    case "videofeed":
                      parent.addChild(new VideosFeed(values[0]));
                      break;
                    case "audiofeed":
                      parent.addChild(new AudiosFeed(values[0]));
                      break;
                    case "audiostream":
                      parent.addChild(new WebAudioStream(values[0], values[1], values[2]));
                      break;
                    case "videostream":
                      parent.addChild(new WebVideoStream(values[0], values[1], values[2]));
                      break;
                    default:
                      break;
                  }
                }
              } catch (ArrayIndexOutOfBoundsException e) {
                // catch exception here and go with parsing
                LOGGER.info("Error at line " + br.getLineNumber() + " of WEB.conf: " + e.getMessage());
                LOGGER.debug(null, e);
              }
            }
          }
        }
      } catch (IOException e) {
        LOGGER.info("Unexpected error in WEB.conf" + e.getMessage());
        LOGGER.debug(null, e);
      }
    }
  }

  /**
   * Splits the first part of a WEB.conf spec into a pair of Strings
   * representing the resource type and its DLNA folder.
   *
   * @param spec (String) to be split
   * @return Array of (String) that represents the tokenized entry.
   */
  private String[] parseFeedKey(String spec) {
    String[] pair = StringUtils.split(spec, ".", 2);

    if (pair == null || pair.length < 2) {
      pair = new String[2];
    }

    if (pair[0] == null) {
      pair[0] = "";
    }

    return pair;
  }

  /**
   * Splits the second part of a WEB.conf spec into a triple of Strings
   * representing the DLNA path, resource URI and optional thumbnail URI.
   *
   * @param spec (String) to be split
   * @return Array of (String) that represents the tokenized entry.
   */
  private String[] parseFeedValue(String spec) {
    StringTokenizer st = new StringTokenizer(spec, ",");
    String[] triple = new String[3];
    int i = 0;

    while (st.hasMoreTokens()) {
      triple[i++] = st.nextToken();
    }

    return triple;
  }

  /**
   * Creates, populates and returns a virtual folder mirroring the
   * contents of the system's iPhoto folder.
   * Mac OS X only.
   *
   * @return iPhotoVirtualFolder the populated <code>VirtualFolder</code>, or null if one couldn't be created.
   */
  private DLNAResource getiPhotoFolder() {
    VirtualFolder iPhotoVirtualFolder = null;

    if (Platform.isMac()) {
      LOGGER.debug("Adding iPhoto folder");
      InputStream inputStream = null;

      try {
        // This command will show the XML files for recently opened iPhoto databases
        Process process = Runtime.getRuntime().exec("defaults read com.apple.iApps iPhotoRecentDatabases");
        inputStream = process.getInputStream();
        List<String> lines = IOUtils.readLines(inputStream);
        LOGGER.debug("iPhotoRecentDatabases: {}", lines);

        if (lines.size() >= 2) {
          // we want the 2nd line
          String line = lines.get(1);

          // Remove extra spaces
          line = line.trim();

          // Remove quotes
          line = line.substring(1, line.length() - 1);

          URI uri = new URI(line);
          URL url = uri.toURL();
          File file = FileUtils.toFile(url);
          LOGGER.debug("Resolved URL to file: {} -> {}", url, file.getAbsolutePath());

          // Load the properties XML file.
          Map<String, Object> iPhotoLib = Plist.load(file);

          // The list of all photos
          Map<?, ?> photoList = (Map<?, ?>) iPhotoLib.get("Master Image List");

          // The list of events (rolls)
          List<Map<?, ?>> listOfRolls = (List<Map<?, ?>>) iPhotoLib.get("List of Rolls");

          iPhotoVirtualFolder = new VirtualFolder("iPhoto Library", null);

          for (Map<?, ?> roll : listOfRolls) {
            Object rollName = roll.get("RollName");

            if (rollName != null) {
              VirtualFolder virtualFolder = new VirtualFolder(rollName.toString(), null);

              // List of photos in an event (roll)
              List<?> rollPhotos = (List<?>) roll.get("KeyList");

              for (Object photo : rollPhotos) {
                Map<?, ?> photoProperties = (Map<?, ?>) photoList.get(photo);

                if (photoProperties != null) {
                  Object imagePath = photoProperties.get("ImagePath");

                  if (imagePath != null) {
                    RealFile realFile = new RealFile(new File(imagePath.toString()));
                    virtualFolder.addChild(realFile);
                  }
                }
              }

              iPhotoVirtualFolder.addChild(virtualFolder);
            }
          }
        } else {
          LOGGER.info("iPhoto folder not found");
        }
      } catch (XmlParseException | URISyntaxException | IOException e) {
        LOGGER.error("Something went wrong with the iPhoto Library scan: ", e);
      } finally {
        IOUtils.closeQuietly(inputStream);
      }
    }

    return iPhotoVirtualFolder;
  }

  /**
   * Returns Aperture folder. Used by manageRoot, so it is usually used as
   * a folder at the root folder. Only works when PMS is run on Mac OS X.
   * TODO: Requirements for Aperture.
   */
  private DLNAResource getApertureFolder() {
    VirtualFolder res = null;

    if (Platform.isMac()) {
      Process process = null;

      try {
        process = Runtime.getRuntime().exec("defaults read com.apple.iApps ApertureLibraries");
        try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
          // Every line entry is one aperture library. We want all of them as a dlna folder.
          String line;
          res = new VirtualFolder("Aperture libraries", null);

          while ((line = in.readLine()) != null) {
            if (line.startsWith("(") || line.startsWith(")")) {
              continue;
            }

            line = line.trim(); // remove extra spaces
            line = line.substring(1, line.lastIndexOf('"')); // remove quotes and spaces
            VirtualFolder apertureLibrary = createApertureDlnaLibrary(line);

            if (apertureLibrary != null) {
              res.addChild(apertureLibrary);
            }
          }
        }
      } catch (IOException | XmlParseException | URISyntaxException e) {
        LOGGER.error("Something went wrong with the aperture library scan: ", e);
      } finally {
        // Avoid zombie processes, or open stream failures
        if (process != null) {
          try {
            // The process seems to always finish, so we can wait for it.
            // If the result code is not read by parent. The process might turn into a zombie (they are real!)
            process.waitFor();
          } catch (InterruptedException e) {
            // Can this thread be interrupted? Don't think so, or, and even when, what will happen?
            LOGGER.warn("Interrupted while waiting for stream for process" + e.getMessage());
          }

          try {
            process.getErrorStream().close();
          } catch (Exception e) {
            LOGGER.warn("Could not close stream for output process", e);
          }

          try {
            process.getInputStream().close();
          } catch (Exception e) {
            LOGGER.warn("Could not close stream for output process", e);
          }

          try {
            process.getOutputStream().close();
          } catch (Exception e) {
            LOGGER.warn("Could not close stream for output process", e);
          }
        }
      }
    }

    return res;
  }

  private VirtualFolder createApertureDlnaLibrary(String url) throws UnsupportedEncodingException, MalformedURLException, XmlParseException, IOException, URISyntaxException {
    VirtualFolder res = null;

    if (url != null) {
      Map<String, Object> iPhotoLib;
      // every project is a album, too
      List<?> listOfAlbums;
      Map<?, ?> album;
      Map<?, ?> photoList;

      URI tURI = new URI(url);
      iPhotoLib = Plist.load(URLDecoder.decode(tURI.toURL().getFile(), System.getProperty("file.encoding"))); // loads the (nested) properties.
      photoList = (Map<?, ?>) iPhotoLib.get("Master Image List"); // the list of photos
      final Object mediaPath = iPhotoLib.get("Archive Path");
      String mediaName;

      if (mediaPath != null) {
        mediaName = mediaPath.toString();

        if (mediaName != null && mediaName.lastIndexOf('/') != -1 && mediaName.lastIndexOf(".aplibrary") != -1) {
          mediaName = mediaName.substring(mediaName.lastIndexOf('/'), mediaName.lastIndexOf(".aplibrary"));
        } else {
          mediaName = "unknown library";
        }
      } else {
        mediaName = "unknown library";
      }

      LOGGER.info("Going to parse aperture library: " + mediaName);
      res = new VirtualFolder(mediaName, null);
      listOfAlbums = (List<?>) iPhotoLib.get("List of Albums"); // the list of events (rolls)

      for (Object item : listOfAlbums) {
        album = (Map<?, ?>) item;

        if (album.get("Parent") == null) {
          VirtualFolder vAlbum = createApertureAlbum(photoList, album, listOfAlbums);
          res.addChild(vAlbum);
        }
      }
    } else {
      LOGGER.info("No Aperture library found.");
    }
    return res;
  }

  private VirtualFolder createApertureAlbum(
    Map<?, ?> photoList,
    Map<?, ?> album, List<?> listOfAlbums
  ) {

    List<?> albumPhotos;
    int albumId = (Integer) album.get("AlbumId");
    VirtualFolder vAlbum = new VirtualFolder(album.get("AlbumName").toString(), null);

    for (Object item : listOfAlbums) {
      Map<?, ?> sub = (Map<?, ?>) item;

      if (sub.get("Parent") != null) {
        // recursive album creation
        int parent = (Integer) sub.get("Parent");

        if (parent == albumId) {
          VirtualFolder subAlbum = createApertureAlbum(photoList, sub, listOfAlbums);
          vAlbum.addChild(subAlbum);
        }
      }
    }

    albumPhotos = (List<?>) album.get("KeyList");

    if (albumPhotos == null) {
      return vAlbum;
    }

    boolean firstPhoto = true;

    for (Object photoKey : albumPhotos) {
      Map<?, ?> photo = (Map<?, ?>) photoList.get(photoKey);

      if (firstPhoto) {
        Object x = photoList.get("ThumbPath");

        if (x != null) {
          vAlbum.setThumbnail(x.toString());
        }

        firstPhoto = false;
      }

      RealFile file = new RealFile(new File(photo.get("ImagePath").toString()));
      vAlbum.addChild(file);
    }

    return vAlbum;
  }

  /**
   * Returns the iTunes XML file. This file has all the information of the
   * iTunes database. The methods used in this function depends on whether
   * UMS runs on Mac OS X or Windows.
   *
   * @return (String) Absolute path to the iTunes XML file.
   * @throws Exception
   */
  private String getiTunesFile() throws Exception {
    String line;
    String iTunesFile = null;
    String customUserPath = configuration.getItunesLibraryPath();

    if (!"".equals(customUserPath)) {
      return customUserPath;
    }

    if (Platform.isMac()) {
      // the second line should contain a quoted file URL e.g.:
      // "file://localhost/Users/MyUser/Music/iTunes/iTunes%20Music%20Library.xml"
      Process process = Runtime.getRuntime().exec("defaults read com.apple.iApps iTunesRecentDatabases");
      try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
        // we want the 2nd line
        if ((line = in.readLine()) != null && (line = in.readLine()) != null) {
          line = line.trim(); // remove extra spaces
          line = line.substring(1, line.length() - 1); // remove quotes and spaces
          URI tURI = new URI(line);
          iTunesFile = URLDecoder.decode(tURI.toURL().getFile(), "UTF8");
        }
      }
    } else if (Platform.isWindows()) {
      Process process = Runtime.getRuntime().exec("reg query \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\" /v \"My Music\"");
      String location;
      try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
        location = null;
        while ((line = in.readLine()) != null) {
          final String LOOK_FOR = "REG_SZ";
          if (line.contains(LOOK_FOR)) {
            location = line.substring(line.indexOf(LOOK_FOR) + LOOK_FOR.length()).trim();
          }
        }
      }

      if (location != null) {
        // Add the iTunes folder to the end
        location += "\\iTunes\\iTunes Music Library.xml";
        iTunesFile = location;
      } else {
        LOGGER.info("Could not find the My Music folder");
      }
    }

    return iTunesFile;
  }

  private static boolean areNamesEqual(String aThis, String aThat) {
    Collator collator = Collator.getInstance(Locale.getDefault());
    collator.setStrength(Collator.PRIMARY);
    int comparison = collator.compare(aThis, aThat);

    return (comparison == 0);
  }

  /**
   * Returns iTunes folder. Used by manageRoot, so it is usually used as a
   * folder at the root folder. Only works on Mac OS X or Windows.
   *
   * The iTunes XML is parsed fully when this method is called, so it can
   * take some time for larger (+1000 albums) databases.
   *
   * This method does not support genius playlists and does not provide a
   * media library.
   *
   * @see RootFolder#getiTunesFile()
   */
  private DLNAResource getiTunesFolder() {
    DLNAResource res = null;

    if (Platform.isMac() || Platform.isWindows()) {
      Map<String, Object> iTunesLib;
      List<?> Playlists;
      Map<?, ?> Playlist;
      Map<?, ?> Tracks;
      Map<?, ?> track;
      List<?> PlaylistTracks;

      try {
        String iTunesFile = getiTunesFile();

        if (iTunesFile != null && (new File(iTunesFile)).exists()) {
          iTunesLib = Plist.load(URLDecoder.decode(iTunesFile, System.getProperty("file.encoding"))); // loads the (nested) properties.
          Tracks = (Map<?, ?>) iTunesLib.get("Tracks"); // the list of tracks
          Playlists = (List<?>) iTunesLib.get("Playlists"); // the list of Playlists
          res = new VirtualFolder("iTunes Library", null);

          VirtualFolder playlistsFolder = null;

          for (Object item : Playlists) {
            Playlist = (Map<?, ?>) item;

            if (Playlist.containsKey("Visible") && Playlist.get("Visible").equals(Boolean.FALSE)) {
              continue;
            }

            if (Playlist.containsKey("Music") && Playlist.get("Music").equals(Boolean.TRUE)) {
              // Create virtual folders for artists, albums and genres

              VirtualFolder musicFolder = new VirtualFolder(Playlist.get("Name").toString(), null);
              res.addChild(musicFolder);

              VirtualFolder virtualFolderArtists = new VirtualFolder(Messages.getString("FoldTab.50"), null);
              VirtualFolder virtualFolderAlbums = new VirtualFolder(Messages.getString("FoldTab.51"), null);
              VirtualFolder virtualFolderGenres = new VirtualFolder(Messages.getString("FoldTab.52"), null);
              VirtualFolder virtualFolderAllTracks = new VirtualFolder(Messages.getString("PMS.11"), null);
              PlaylistTracks = (List<?>) Playlist.get("Playlist Items"); // list of tracks in a playlist

              String artistName;
              String albumName;
              String genreName;

              if (PlaylistTracks != null) {
                for (Object t : PlaylistTracks) {
                  Map<?, ?> td = (Map<?, ?>) t;
                  track = (Map<?, ?>) Tracks.get(td.get("Track ID").toString());

                  if (
                    track != null &&
                    track.get("Location") != null &&
                    track.get("Location").toString().startsWith("file://")
                  ) {
                    String name = Normalizer.normalize((String) track.get("Name"), Normalizer.Form.NFC);
                    // remove dots from name to prevent media renderer from trimming
                    name = name.replace('.', '-');

                    if (track.containsKey("Protected") && track.get("Protected").equals(Boolean.TRUE)) {
                      name = String.format(Messages.getString("RootFolder.1"), name);
                    }

                    boolean isCompilation = (track.containsKey("Compilation") && track.get("Compilation").equals(Boolean.TRUE));

                    artistName = (String) track.get("Artist");
                    if (isCompilation) {
                      artistName = "Compilation";
                    } else if (track.containsKey("Album Artist")) {
                      artistName = (String) track.get("Album Artist");
                    }
                    albumName = (String) track.get("Album");
                    genreName = (String) track.get("Genre");

                    if (artistName == null) {
                      artistName = "Unknown Artist";
                    } else {
                      artistName = Normalizer.normalize(artistName, Normalizer.Form.NFC);
                    }

                    if (albumName == null) {
                      albumName = "Unknown Album";
                    } else {
                      albumName = Normalizer.normalize(albumName, Normalizer.Form.NFC);
                    }

                    if (genreName == null || "".equals(genreName.replaceAll("[^a-zA-Z]", ""))) {
                      // This prevents us from adding blank or numerical genres
                      genreName = "Unknown Genre";
                    } else {
                      genreName = Normalizer.normalize(genreName, Normalizer.Form.NFC);
                    }

                    // Replace &nbsp with space and then trim
                    artistName = artistName.replace('\u0160', ' ').trim();
                    albumName  = albumName.replace('\u0160', ' ').trim();
                    genreName  = genreName.replace('\u0160', ' ').trim();

                    URI tURI2 = new URI(track.get("Location").toString());
                    File refFile = new File(URLDecoder.decode(tURI2.toURL().getFile(), "UTF-8"));
                    RealFile file = new RealFile(refFile, name);

                    // Put the track into the artist's album folder and the artist's "All tracks" folder
                    {
                      VirtualFolder individualArtistFolder = null;
                      VirtualFolder individualArtistAllTracksFolder;
                      VirtualFolder individualArtistAlbumFolder = null;

                      for (DLNAResource artist : virtualFolderArtists.getChildren()) {
                        if (areNamesEqual(artist.getName(), artistName)) {
                          individualArtistFolder = (VirtualFolder) artist;
                          for (DLNAResource album : individualArtistFolder.getChildren()) {
                            if (areNamesEqual(album.getName(), albumName)) {
                              individualArtistAlbumFolder = (VirtualFolder) album;
                            }
                          }
                          break;
                        }
                      }

                      if (individualArtistFolder == null) {
                        individualArtistFolder = new VirtualFolder(artistName, null);
                        virtualFolderArtists.addChild(individualArtistFolder);
                        individualArtistAllTracksFolder = new VirtualFolder(Messages.getString("PMS.11"), null);
                        individualArtistFolder.addChild(individualArtistAllTracksFolder);
                      } else {
                        individualArtistAllTracksFolder = (VirtualFolder) individualArtistFolder.getChildren().get(0);
                      }

                      if (individualArtistAlbumFolder == null) {
                        individualArtistAlbumFolder = new VirtualFolder(albumName, null);
                        individualArtistFolder.addChild(individualArtistAlbumFolder);
                      }

                      individualArtistAlbumFolder.addChild(file.clone());
                      individualArtistAllTracksFolder.addChild(file);
                    }

                    // Put the track into its album folder
                    {
                      if (!isCompilation) {
                        albumName += " - " + artistName;
                      }

                      VirtualFolder individualAlbumFolder = null;
                      for (DLNAResource album : virtualFolderAlbums.getChildren()) {
                        if (areNamesEqual(album.getName(), albumName)) {
                          individualAlbumFolder = (VirtualFolder) album;
                          break;
                        }
                      }
                      if (individualAlbumFolder == null) {
                        individualAlbumFolder = new VirtualFolder(albumName, null);
                        virtualFolderAlbums.addChild(individualAlbumFolder);
                      }
                      individualAlbumFolder.addChild(file.clone());
                    }

                    // Put the track into its genre folder
                    {
                      VirtualFolder individualGenreFolder = null;
                      for (DLNAResource genre : virtualFolderGenres.getChildren()) {
                        if (areNamesEqual(genre.getName(), genreName)) {
                          individualGenreFolder = (VirtualFolder) genre;
                          break;
                        }
                      }
                      if (individualGenreFolder == null) {
                        individualGenreFolder = new VirtualFolder(genreName, null);
                        virtualFolderGenres.addChild(individualGenreFolder);
                      }
                      individualGenreFolder.addChild(file.clone());
                    }

                    // Put the track into the global "All tracks" folder
                    virtualFolderAllTracks.addChild(file.clone());
                  }
                }
              }

              musicFolder.addChild(virtualFolderArtists);
              musicFolder.addChild(virtualFolderAlbums);
              musicFolder.addChild(virtualFolderGenres);
              musicFolder.addChild(virtualFolderAllTracks);

              // Sort the virtual folders alphabetically
              Collections.sort(virtualFolderArtists.getChildren(), new Comparator<DLNAResource>() {
                @Override
                public int compare(DLNAResource o1, DLNAResource o2) {
                  VirtualFolder a = (VirtualFolder) o1;
                  VirtualFolder b = (VirtualFolder) o2;
                  return a.getName().compareToIgnoreCase(b.getName());
                }
              });

              Collections.sort(virtualFolderAlbums.getChildren(), new Comparator<DLNAResource>() {
                @Override
                public int compare(DLNAResource o1, DLNAResource o2) {
                  VirtualFolder a = (VirtualFolder) o1;
                  VirtualFolder b = (VirtualFolder) o2;
                  return a.getName().compareToIgnoreCase(b.getName());
                }
              });

              Collections.sort(virtualFolderGenres.getChildren(), new Comparator<DLNAResource>() {
                @Override
                public int compare(DLNAResource o1, DLNAResource o2) {
                  VirtualFolder a = (VirtualFolder) o1;
                  VirtualFolder b = (VirtualFolder) o2;
                  return a.getName().compareToIgnoreCase(b.getName());
                }
              });
            } else {
              // Add all playlists
              VirtualFolder pf = new VirtualFolder(Playlist.get("Name").toString(), null);
              PlaylistTracks = (List<?>) Playlist.get("Playlist Items"); // list of tracks in a playlist

              if (PlaylistTracks != null) {
                for (Object t : PlaylistTracks) {
                  Map<?, ?> td = (Map<?, ?>) t;
                  track = (Map<?, ?>) Tracks.get(td.get("Track ID").toString());

                  if (
                    track != null &&
                    track.get("Location") != null &&
                    track.get("Location").toString().startsWith("file://")
                  ) {
                    String name = Normalizer.normalize(track.get("Name").toString(), Normalizer.Form.NFC);
                    // remove dots from name to prevent media renderer from trimming
                    name = name.replace('.', '-');

                    if (track.containsKey("Protected") && track.get("Protected").equals(Boolean.TRUE)) {
                      name = String.format(Messages.getString("RootFolder.1"), name);
                    }

                    URI tURI2 = new URI(track.get("Location").toString());
                    RealFile file = new RealFile(new File(URLDecoder.decode(tURI2.toURL().getFile(), "UTF-8")), name);
                    pf.addChild(file);
                  }
                }
              }

              int kind = Playlist.containsKey("Distinguished Kind") ? ((Number) Playlist.get("Distinguished Kind")).intValue() : -1;
              if (kind >= 0 && kind != 17 && kind != 19 && kind != 20) {
                // System folder, but not voice memos (17) and purchased items (19 & 20)
                res.addChild(pf);
              } else {
                // User playlist or playlist folder
                if (playlistsFolder == null) {
                  playlistsFolder = new VirtualFolder("Playlists", null);
                  res.addChild(playlistsFolder);
                }
                playlistsFolder.addChild(pf);
              }
            }
          }
        } else {
          LOGGER.info("Could not find the iTunes file");
        }
      } catch (Exception e) {
        LOGGER.error("Something went wrong with the iTunes Library scan: ", e);
      }
    }

    return res;
  }

  private void addAdminFolder() {
    DLNAResource res = new VirtualFolder(Messages.getString("PMS.131"), null);
    DLNAResource vsf = getVideoSettingsFolder();

    if (vsf != null) {
      res.addChild(vsf);
    }

    res.addChild(new VirtualFolder(Messages.getString("NetworkTab.39"), null) {
      @Override
      public void discoverChildren() {
        final ArrayList<DownloadPlugins> plugins = DownloadPlugins.downloadList();
        for (final DownloadPlugins plugin : plugins) {
          addChild(new VirtualVideoAction(plugin.getName(), true) {
            @Override
            public boolean enable() {
              try {
                plugin.install(null);
              } catch (Exception e) {
              }

              return true;
            }
          });
        }
      }
    });

    if (configuration.getScriptDir() != null) {
      final File scriptDir = new File(configuration.getScriptDir());

      if (scriptDir.exists()) {
        res.addChild(new VirtualFolder(Messages.getString("PMS.132"), null) {
          @Override
          public void discoverChildren() {
            File[] files = scriptDir.listFiles();
            for (File file : files) {
              String name = file.getName().replaceAll("_", " ");
              int pos = name.lastIndexOf('.');

              if (pos != -1) {
                name = name.substring(0, pos);
              }

              final File f = file;

              addChild(new VirtualVideoAction(name, true) {
                @Override
                public boolean enable() {
                  try {
                    ProcessBuilder pb = new ProcessBuilder(f.getAbsolutePath());
                    Process pid = pb.start();
                    InputStream is = pid.getInputStream();
                    BufferedReader br;
                    try (InputStreamReader isr = new InputStreamReader(is)) {
                      br = new BufferedReader(isr);
                      while (br.readLine() != null) {
                      }
                    }
                    br.close();
                    pid.waitFor();
                  } catch (IOException | InterruptedException e) {
                  }

                  return true;
                }
              });
            }
          }
        });
      }
    }

    // Resume file management
    if (configuration.isResumeEnabled()) {
      res.addChild(new VirtualFolder(Messages.getString("PMS.135"), null) {
        @Override
        public void discoverChildren() {
          final File[] files = ResumeObj.resumeFiles();
          addChild(new VirtualVideoAction(Messages.getString("PMS.136"), true) {
            @Override
            public boolean enable() {
              for (File f : files) {
                f.delete();
              }
              getParent().getChildren().remove(this);
              return false;
            }
          });
          for (final File f : files) {
            String name = FileUtil.getFileNameWithoutExtension(f.getName());
            name = name.replaceAll(ResumeObj.CLEAN_REG, "");
            addChild(new VirtualVideoAction(name, false) {
              @Override
              public boolean enable() {
                f.delete();
                getParent().getChildren().remove(this);
                return false;
              }
            });
          }
        }
      });
    }

    // recently played mgmt
    if (last != null) {
      final List<DLNAResource> l = last.getList();
      res.addChild(new VirtualFolder(Messages.getString("PMS.137"), null) {
        @Override
        public void discoverChildren() {
          addChild(new VirtualVideoAction(Messages.getString("PMS.136"), true) {
            @Override
            public boolean enable() {
              getParent().getChildren().clear();
              l.clear();
              last.update();
              return true;
            }
          });
          for (final DLNAResource r : l) {
            addChild(new VirtualVideoAction(r.getName(), false) {
              @Override
              public boolean enable() {
                getParent().getChildren().remove(this);
                l.remove(r);
                last.update();
                return false;
              }
            });
          }
        }
      });
    }

    addChild(res);
  }

  /**
   * Returns Video Settings folder. Used by manageRoot, so it is usually
   * used as a folder at the root folder. Child objects are created when
   * this folder is created.
   */
  private DLNAResource getVideoSettingsFolder() {
    DLNAResource res = null;

    if (!configuration.getHideVideoSettings()) {
      res = new VirtualFolder(Messages.getString("PMS.37"), null);
      VirtualFolder vfSub = new VirtualFolder(Messages.getString("PMS.8"), null);
      res.addChild(vfSub);

      res.addChild(new VirtualVideoAction(Messages.getString("PMS.3"), configuration.isMencoderNoOutOfSync()) {
        @Override
        public boolean enable() {
          configuration.setMencoderNoOutOfSync(!configuration.isMencoderNoOutOfSync());
          return configuration.isMencoderNoOutOfSync();
        }
      });

      res.addChild(new VirtualVideoAction(Messages.getString("PMS.14"), configuration.isMencoderMuxWhenCompatible()) {
        @Override
        public boolean enable() {
          configuration.setMencoderMuxWhenCompatible(!configuration.isMencoderMuxWhenCompatible());

          return configuration.isMencoderMuxWhenCompatible();
        }
      });

      res.addChild(new VirtualVideoAction("  !!-- Fix 23.976/25fps A/V Mismatch --!!", configuration.isFix25FPSAvMismatch()) {
        @Override
        public boolean enable() {
          configuration.setMencoderForceFps(!configuration.isFix25FPSAvMismatch());
          configuration.setFix25FPSAvMismatch(!configuration.isFix25FPSAvMismatch());
          return configuration.isFix25FPSAvMismatch();
        }
      });

      res.addChild(new VirtualVideoAction(Messages.getString("PMS.4"), configuration.isMencoderYadif()) {
        @Override
        public boolean enable() {
          configuration.setMencoderYadif(!configuration.isMencoderYadif());

          return configuration.isMencoderYadif();
        }
      });

      vfSub.addChild(new VirtualVideoAction(Messages.getString("TrTab2.51"), configuration.isDisableSubtitles()) {
        @Override
        public boolean enable() {
          boolean oldValue = configuration.isDisableSubtitles();
          boolean newValue = !oldValue;
          configuration.setDisableSubtitles(newValue);
          return newValue;
        }
      });

      vfSub.addChild(new VirtualVideoAction(Messages.getString("MEncoderVideo.22"), configuration.isAutoloadExternalSubtitles()) {
        @Override
        public boolean enable() {
          boolean oldValue = configuration.isAutoloadExternalSubtitles();
          boolean newValue = !oldValue;
          configuration.setAutoloadExternalSubtitles(newValue);
          return newValue;
        }
      });

      vfSub.addChild(new VirtualVideoAction(Messages.getString("MEncoderVideo.36"), configuration.isUseEmbeddedSubtitlesStyle()) {
        @Override
        public boolean enable() {
          boolean oldValue = configuration.isUseEmbeddedSubtitlesStyle();
          boolean newValue = !oldValue;
          configuration.setUseEmbeddedSubtitlesStyle(newValue);
          return newValue;
        }
      });

      res.addChild(new VirtualVideoAction(Messages.getString("MEncoderVideo.0"), configuration.getSkipLoopFilterEnabled()) {
        @Override
        public boolean enable() {
          configuration.setSkipLoopFilterEnabled(!configuration.getSkipLoopFilterEnabled());
          return configuration.getSkipLoopFilterEnabled();
        }
      });

      res.addChild(new VirtualVideoAction(Messages.getString("TrTab2.28"), configuration.isAudioEmbedDtsInPcm()) {
        @Override
        public boolean enable() {
          configuration.setAudioEmbedDtsInPcm(!configuration.isAudioEmbedDtsInPcm());
          return configuration.isAudioEmbedDtsInPcm();
        }
      });

      res.addChild(new VirtualVideoAction(Messages.getString("PMS.27"), true) {
        @Override
        public boolean enable() {
          try {
            configuration.save();
          } catch (ConfigurationException e) {
            LOGGER.debug("Caught exception", e);
          }
          return true;
        }
      });

      res.addChild(new VirtualVideoAction(Messages.getString("LooksFrame.12"), true) {
        @Override
        public boolean enable() {
          PMS.get().reset();
          return true;
        }
      });
      res.addChild(new VirtualVideoAction(Messages.getString("FoldTab.42"), configuration.isHideLiveSubtitlesFolder()) {
        @Override
        public boolean enable() {
          configuration.setHideLiveSubtitlesFolder(!configuration.isHideLiveSubtitlesFolder());
          return configuration.isHideLiveSubtitlesFolder();
        }
      });
    }

    return res;
  }

  /**
   * Returns as many folders as plugins providing root folders are loaded
   * into memory (need to implement AdditionalFolder(s)AtRoot)
   */
  private List<DLNAResource> getAdditionalFoldersAtRoot() {
    List<DLNAResource> res = new ArrayList<>();
    String[] legalPlugs = null;
    String tmp = configuration.getPlugins(tags);
    if (StringUtils.isNotBlank(tmp)) {
      legalPlugs = tmp.split(",");
    }

    for (ExternalListener listener : ExternalFactory.getExternalListeners()) {
      if (illegalPlugin(legalPlugs, listener.name())) {
        LOGGER.debug("plugin " + listener.name() + " is not legal for render");
        continue;
      }
      if (listener instanceof AdditionalFolderAtRoot) {
        AdditionalFolderAtRoot afar = (AdditionalFolderAtRoot) listener;

        try {
          DLNAResource resource = afar.getChild();
          LOGGER.debug("add ext list " + listener);
          if (resource == null) {
            continue;
          }
          resource.setMasterParent(listener);
          for (DLNAResource r : resource.getChildren()) {
            r.setMasterParent(listener);
          }
          res.add(resource);
        } catch (Throwable t) {
          LOGGER.error(String.format("Failed to append AdditionalFolderAtRoot with name=%s, class=%s", afar.name(), afar.getClass()), t);
        }
      } else if (listener instanceof AdditionalFoldersAtRoot) {
        Iterator<DLNAResource> folders = ((AdditionalFoldersAtRoot) listener).getChildren();

        while (folders.hasNext()) {
          DLNAResource resource = folders.next();
          resource.setMasterParent(listener);
          for (DLNAResource r : resource.getChildren()) {
            r.setMasterParent(listener);
          }
          try {
            res.add(resource);
          } catch (Throwable t) {
            LOGGER.error(String.format("Failed to append AdditionalFolderAtRoots with class=%s for DLNAResource=%s", listener.getClass(), resource.getClass()), t);
          }
        }
      }
    }

    return res;
  }

  @Override
  public String toString() {
    return "RootFolder[" + getChildren() + "]";
  }

  public void reset() {
    setDiscovered(false);
  }

  public void stopPlaying(DLNAResource res) {
    if (mon != null) {
      mon.stopped(res);
    }
    if (last != null) {
      last.add(res);
    }
  }

  private boolean illegalPlugin(String[] plugs, String name) {
    if (StringUtils.isBlank(name)) {
      if (plugs == null || plugs.length == 0) {
        // only allowed without plugins filter
        return false;
      }
      return true;
    }
    if (plugs == null || plugs.length == 0) {
      return false;
    }
    for (String p : plugs) {
      if (name.equals(p)) {
        return false;
      }
    }
    return true;
  }

  public ArrayList<String> getTags() {
    return tags;
  }
}
TOP

Related Classes of net.pms.dlna.RootFolder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.