net.sourceforge.subsonic.service.PlaylistService.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.subsonic.service.PlaylistService.java

Source

/*
 This file is part of Subsonic.
    
 Subsonic 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, either version 3 of the License, or
 (at your option) any later version.
    
 Subsonic 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 Subsonic.  If not, see <http://www.gnu.org/licenses/>.
    
 Copyright 2009 (C) Sindre Mehus
 */
package net.sourceforge.subsonic.service;

import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.domain.MediaFile;
import net.sourceforge.subsonic.domain.Playlist;
import net.sourceforge.subsonic.util.FileUtil;
import net.sourceforge.subsonic.util.StringUtil;
import org.apache.commons.lang.StringEscapeUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Provides services for loading and saving playlists to and from persistent storage.
 *
 * @author Sindre Mehus
 * @see Playlist
 */
public class PlaylistService {

    private static final Logger LOG = Logger.getLogger(PlaylistService.class);
    private SettingsService settingsService;
    private SecurityService securityService;
    private MediaFileService mediaFileService;

    /**
     * Saves the given playlist to persistent storage.
     *
     * @param playlist The playlist to save.
     * @throws IOException If an I/O error occurs.
     */
    public void savePlaylist(Playlist playlist) throws IOException {
        String name = playlist.getName();

        // Add m3u suffix if no other suitable suffix is given.
        if (!new PlaylistFilenameFilter().accept(getPlaylistDirectory(), name)) {
            name += ".m3u";
            playlist.setName(name);
        }

        File playlistFile = new File(getPlaylistDirectory(), name);
        checkAccess(playlistFile);

        PrintWriter writer = new PrintWriter(playlistFile, StringUtil.ENCODING_UTF8);

        try {
            PlaylistFormat format = PlaylistFormat.getPlaylistFormat(playlistFile);
            format.savePlaylist(playlist, writer);
        } finally {
            writer.close();
        }
    }

    /**
     * Loads a named playlist from persistent storage and into the provided playlist instance.
     *
     * @param playlist The playlist to populate. Any existing entries in the playlist will
     *                 be removed.
     * @param name     The name of a previously persisted playlist.
     * @throws IOException If an I/O error occurs.
     */
    public void loadPlaylist(Playlist playlist, String name) throws IOException {
        File playlistFile = new File(getPlaylistDirectory(), name);
        checkAccess(playlistFile);

        playlist.setName(name);

        BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(playlistFile), StringUtil.ENCODING_UTF8));
        try {
            PlaylistFormat format = PlaylistFormat.getPlaylistFormat(playlistFile);
            format.loadPlaylist(playlist, reader, mediaFileService);
        } finally {
            reader.close();
        }
    }

    /**
     * Returns a list of all previously saved playlists.
     *
     * @return A list of all previously saved playlists.
     */
    public File[] getSavedPlaylists() {
        return FileUtil.listFiles(getPlaylistDirectory(), new PlaylistFilenameFilter(), true);
    }

    /**
     * Returns the saved playlist with the given name.
     *
     * @param name The name of the playlist.
     * @return The playlist, or <code>null</code> if not found.
     */
    public File getSavedPlaylist(String name) {
        for (File file : getSavedPlaylists()) {
            if (name.equals(file.getName())) {
                return file;
            }
        }
        return null;
    }

    /**
     * Deletes the named playlist from persistent storage.
     *
     * @param name The name of the playlist to delete.
     * @throws IOException If an I/O error occurs.
     */
    public void deletePlaylist(String name) throws IOException {
        File file = new File(getPlaylistDirectory(), name);
        checkAccess(file);
        file.delete();
    }

    /**
     * Returns the directory where playlists are stored.
     *
     * @return The directory where playlists are stored.
     */
    public File getPlaylistDirectory() {
        return new File(settingsService.getPlaylistFolder());
    }

    private void checkAccess(File file) {
        if (!securityService.isWriteAllowed(file)) {
            throw new SecurityException("Access denied to file " + file);
        }
    }

    public void setSettingsService(SettingsService settingsService) {
        this.settingsService = settingsService;
    }

    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    public void setMediaFileService(MediaFileService mediaFileService) {
        this.mediaFileService = mediaFileService;
    }

    private static class PlaylistFilenameFilter implements FilenameFilter {
        public boolean accept(File dir, String name) {
            name = name.toLowerCase();
            return name.endsWith(".m3u") || name.endsWith(".pls") || name.endsWith(".xspf");
        }
    }

    /**
     * Abstract superclass for playlist formats.
     */
    private abstract static class PlaylistFormat {
        public abstract void loadPlaylist(Playlist playlist, BufferedReader reader,
                MediaFileService mediaFileService) throws IOException;

        public abstract void savePlaylist(Playlist playlist, PrintWriter writer) throws IOException;

        public static PlaylistFormat getPlaylistFormat(File file) {
            String name = file.getName().toLowerCase();
            if (name.endsWith(".m3u")) {
                return new M3UFormat();
            }
            if (name.endsWith(".pls")) {
                return new PLSFormat();
            }
            if (name.endsWith(".xspf")) {
                return new XSPFFormat();
            }
            return null;
        }
    }

    /**
     * Implementation of M3U playlist format.
     */
    private static class M3UFormat extends PlaylistFormat {
        public void loadPlaylist(Playlist playlist, BufferedReader reader, MediaFileService mediaFileService)
                throws IOException {
            playlist.clear();
            String line = reader.readLine();
            while (line != null) {
                if (!line.startsWith("#")) {
                    try {
                        MediaFile file = mediaFileService.getMediaFile(line);
                        if (file.getFile().exists()) {
                            playlist.addFiles(true, file);
                        }
                    } catch (SecurityException x) {
                        LOG.warn(x.getMessage(), x);
                    }
                }
                line = reader.readLine();
            }
        }

        public void savePlaylist(Playlist playlist, PrintWriter writer) throws IOException {
            writer.println("#EXTM3U");
            for (MediaFile file : playlist.getFiles()) {
                writer.println(file.getPath());
            }
            if (writer.checkError()) {
                throw new IOException("Error when writing playlist " + playlist.getName());
            }
        }
    }

    /**
     * Implementation of PLS playlist format.
     */
    private static class PLSFormat extends PlaylistFormat {
        public void loadPlaylist(Playlist playlist, BufferedReader reader, MediaFileService mediaFileService)
                throws IOException {
            playlist.clear();

            Pattern pattern = Pattern.compile("^File\\d+=(.*)$");
            String line = reader.readLine();
            while (line != null) {

                Matcher matcher = pattern.matcher(line);
                if (matcher.find()) {
                    try {
                        MediaFile file = mediaFileService.getMediaFile(matcher.group(1));
                        if (file.getFile().exists()) {
                            playlist.addFiles(true, file);
                        }
                    } catch (SecurityException x) {
                        LOG.warn(x.getMessage(), x);
                    }
                }
                line = reader.readLine();
            }
        }

        public void savePlaylist(Playlist playlist, PrintWriter writer) throws IOException {
            writer.println("[playlist]");
            int counter = 0;

            for (MediaFile file : playlist.getFiles()) {
                counter++;
                writer.println("File" + counter + '=' + file.getPath());
            }
            writer.println("NumberOfEntries=" + counter);
            writer.println("Version=2");

            if (writer.checkError()) {
                throw new IOException("Error when writing playlist " + playlist.getName());
            }
        }
    }

    /**
     * Implementation of XSPF (http://www.xspf.org/) playlist format.
     */
    private static class XSPFFormat extends PlaylistFormat {
        public void loadPlaylist(Playlist playlist, BufferedReader reader, MediaFileService mediaFileService)
                throws IOException {
            playlist.clear();

            SAXBuilder builder = new SAXBuilder();
            Document document;
            try {
                document = builder.build(reader);
            } catch (JDOMException x) {
                LOG.warn("Failed to parse XSPF playlist.", x);
                throw new IOException("Failed to parse XSPF playlist " + playlist.getName());
            }

            Element root = document.getRootElement();
            Namespace ns = root.getNamespace();
            Element trackList = root.getChild("trackList", ns);
            List<?> tracks = trackList.getChildren("track", ns);

            for (Object obj : tracks) {
                Element track = (Element) obj;
                String location = track.getChildText("location", ns);
                if (location != null && location.startsWith("file://")) {
                    location = location.replaceFirst("file://", "");
                    try {
                        MediaFile file = mediaFileService.getMediaFile(location);
                        if (file.getFile().exists()) {
                            playlist.addFiles(true, file);
                        }
                    } catch (SecurityException x) {
                        LOG.warn(x.getMessage(), x);
                    }
                }
            }
        }

        public void savePlaylist(Playlist playlist, PrintWriter writer) throws IOException {
            writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            writer.println("<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">");
            writer.println("    <trackList>");

            for (MediaFile file : playlist.getFiles()) {
                writer.println("        <track><location>file://" + StringEscapeUtils.escapeXml(file.getPath())
                        + "</location></track>");
            }
            writer.println("    </trackList>");
            writer.println("</playlist>");

            if (writer.checkError()) {
                throw new IOException("Error when writing playlist " + playlist.getName());
            }
        }
    }
}