Improve Playlist Handling
- Use external library chamelon (lizzy) - Adds the ability to specify playlist export format - Fixes some deficiences with playlist handling Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
This commit is contained in:
+2
-2
@@ -77,7 +77,8 @@ public class ImportPlaylistController {
|
||||
String fileName = FilenameUtils.getName(item.getName());
|
||||
String format = StringUtils.lowerCase(FilenameUtils.getExtension(item.getName()));
|
||||
String username = securityService.getCurrentUsername(request);
|
||||
Playlist playlist = playlistService.importPlaylist(username, playlistName, fileName, format, item.getInputStream(), null);
|
||||
Playlist playlist = playlistService.importPlaylist(username, playlistName, fileName,
|
||||
item.getInputStream(), null);
|
||||
map.put("playlist", playlist);
|
||||
}
|
||||
}
|
||||
@@ -95,5 +96,4 @@ public class ImportPlaylistController {
|
||||
return "importPlaylist";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -19,31 +19,29 @@
|
||||
*/
|
||||
package org.libresonic.player.service;
|
||||
|
||||
import chameleon.playlist.SpecificPlaylist;
|
||||
import chameleon.playlist.SpecificPlaylistFactory;
|
||||
import chameleon.playlist.SpecificPlaylistProvider;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
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 org.libresonic.player.dao.MediaFileDao;
|
||||
import org.libresonic.player.dao.PlaylistDao;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.libresonic.player.domain.MusicFolder;
|
||||
import org.libresonic.player.domain.Playlist;
|
||||
import org.libresonic.player.domain.User;
|
||||
import org.libresonic.player.service.playlist.PlaylistExportHandler;
|
||||
import org.libresonic.player.service.playlist.PlaylistImportHandler;
|
||||
import org.libresonic.player.util.Pair;
|
||||
import org.libresonic.player.util.StringUtil;
|
||||
import org.libresonic.player.util.Util;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Provides services for loading and saving playlists to and from persistent storage.
|
||||
@@ -54,11 +52,34 @@ import java.util.regex.Pattern;
|
||||
public class PlaylistService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PlaylistService.class);
|
||||
private MediaFileService mediaFileService;
|
||||
private MediaFileDao mediaFileDao;
|
||||
private PlaylistDao playlistDao;
|
||||
private SecurityService securityService;
|
||||
private SettingsService settingsService;
|
||||
private List<PlaylistExportHandler> exportHandlers;
|
||||
private List<PlaylistImportHandler> importHandlers;
|
||||
|
||||
public PlaylistService(
|
||||
MediaFileDao mediaFileDao,
|
||||
PlaylistDao playlistDao,
|
||||
SecurityService securityService,
|
||||
SettingsService settingsService,
|
||||
List<PlaylistExportHandler> exportHandlers,
|
||||
List<PlaylistImportHandler> importHandlers
|
||||
) {
|
||||
Assert.notNull(mediaFileDao);
|
||||
Assert.notNull(playlistDao);
|
||||
Assert.notNull(securityService);
|
||||
Assert.notNull(settingsService);
|
||||
Assert.notNull(exportHandlers);
|
||||
Assert.notNull(importHandlers);
|
||||
this.mediaFileDao = mediaFileDao;
|
||||
this.playlistDao = playlistDao;
|
||||
this.securityService = securityService;
|
||||
this.settingsService = settingsService;
|
||||
this.exportHandlers = exportHandlers;
|
||||
this.importHandlers = importHandlers;
|
||||
}
|
||||
|
||||
public List<Playlist> getAllPlaylists() {
|
||||
return sort(playlistDao.getAllPlaylists());
|
||||
@@ -147,14 +168,20 @@ public class PlaylistService {
|
||||
playlistDao.updatePlaylist(playlist);
|
||||
}
|
||||
|
||||
public Playlist importPlaylist(String username, String playlistName, String fileName, String format,
|
||||
InputStream inputStream, Playlist existingPlaylist) throws Exception {
|
||||
PlaylistFormat playlistFormat = getPlaylistFormat(format);
|
||||
if (playlistFormat == null) {
|
||||
throw new Exception("Unsupported playlist format: " + format);
|
||||
public Playlist importPlaylist(
|
||||
String username, String playlistName, String fileName, InputStream inputStream, Playlist existingPlaylist
|
||||
) throws Exception {
|
||||
|
||||
// TODO: handle other encodings
|
||||
final SpecificPlaylist inputSpecificPlaylist = SpecificPlaylistFactory.getInstance().readFrom(inputStream, "UTF-8");
|
||||
if (inputSpecificPlaylist == null) {
|
||||
throw new Exception("Unsupported playlist " + fileName);
|
||||
}
|
||||
PlaylistImportHandler importHandler = getImportHandler(inputSpecificPlaylist);
|
||||
LOG.debug("Using "+importHandler.getClass().getSimpleName()+" playlist import handler");
|
||||
|
||||
Pair<List<MediaFile>, List<String>> result = importHandler.handle(inputSpecificPlaylist);
|
||||
|
||||
Pair<List<MediaFile>, List<String>> result = parseFiles(IOUtils.toByteArray(inputStream), playlistFormat);
|
||||
if (result.getFirst().isEmpty() && !result.getSecond().isEmpty()) {
|
||||
throw new Exception("No songs in the playlist were found.");
|
||||
}
|
||||
@@ -183,28 +210,35 @@ public class PlaylistService {
|
||||
return playlist;
|
||||
}
|
||||
|
||||
private Pair<List<MediaFile>, List<String>> parseFiles(byte[] playlist, PlaylistFormat playlistFormat) throws IOException {
|
||||
Pair<List<MediaFile>, List<String>> result = null;
|
||||
|
||||
// Try with multiple encodings; use the one that finds the most files.
|
||||
String[] encodings = {StringUtil.ENCODING_LATIN, StringUtil.ENCODING_UTF8, Charset.defaultCharset().name()};
|
||||
for (String encoding : encodings) {
|
||||
Pair<List<MediaFile>, List<String>> files = parseFilesWithEncoding(playlist, playlistFormat, encoding);
|
||||
if (result == null || result.getFirst().size() < files.getFirst().size()) {
|
||||
result = files;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Pair<List<MediaFile>, List<String>> parseFilesWithEncoding(byte[] playlist, PlaylistFormat playlistFormat, String encoding) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(playlist), encoding));
|
||||
return playlistFormat.parse(reader, mediaFileService);
|
||||
public String getExportPlaylistExtension() {
|
||||
String format = settingsService.getPlaylistExportFormat();
|
||||
SpecificPlaylistProvider provider = SpecificPlaylistFactory.getInstance().findProviderById(format);
|
||||
return provider.getContentTypes()[0].getExtensions()[0];
|
||||
}
|
||||
|
||||
public void exportPlaylist(int id, OutputStream out) throws Exception {
|
||||
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, StringUtil.ENCODING_UTF8));
|
||||
new M3UFormat().format(getFilesInPlaylist(id, true), writer);
|
||||
String format = settingsService.getPlaylistExportFormat();
|
||||
SpecificPlaylistProvider provider = SpecificPlaylistFactory.getInstance().findProviderById(format);
|
||||
PlaylistExportHandler handler = getExportHandler(provider);
|
||||
SpecificPlaylist specificPlaylist = handler.handle(id, provider);
|
||||
specificPlaylist.writeTo(out, StringUtil.ENCODING_UTF8);
|
||||
}
|
||||
|
||||
private PlaylistImportHandler getImportHandler(SpecificPlaylist playlist) {
|
||||
return importHandlers.stream()
|
||||
.filter(handler -> handler.canHandle(playlist.getClass()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("No import handler for " + playlist.getClass()
|
||||
.getName()));
|
||||
|
||||
}
|
||||
|
||||
private PlaylistExportHandler getExportHandler(SpecificPlaylistProvider provider) {
|
||||
return exportHandlers.stream()
|
||||
.filter(handler -> handler.canHandle(provider.getClass()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("No export handler for " + provider.getClass()
|
||||
.getName()));
|
||||
}
|
||||
|
||||
public void importPlaylists() {
|
||||
@@ -238,10 +272,6 @@ public class PlaylistService {
|
||||
}
|
||||
|
||||
private void importPlaylistIfUpdated(File file, List<Playlist> allPlaylists) throws Exception {
|
||||
String format = FilenameUtils.getExtension(file.getPath());
|
||||
if (getPlaylistFormat(format) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String fileName = file.getName();
|
||||
Playlist existingPlaylist = null;
|
||||
@@ -256,241 +286,13 @@ public class PlaylistService {
|
||||
}
|
||||
InputStream in = new FileInputStream(file);
|
||||
try {
|
||||
importPlaylist(User.USERNAME_ADMIN, FilenameUtils.getBaseName(fileName), fileName, format, in, existingPlaylist);
|
||||
importPlaylist(User.USERNAME_ADMIN, FilenameUtils.getBaseName(fileName), fileName, in, existingPlaylist);
|
||||
LOG.info("Auto-imported playlist " + file);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
|
||||
private PlaylistFormat getPlaylistFormat(String format) {
|
||||
if (format == null) {
|
||||
return null;
|
||||
}
|
||||
if (format.equalsIgnoreCase("m3u") || format.equalsIgnoreCase("m3u8")) {
|
||||
return new M3UFormat();
|
||||
}
|
||||
if (format.equalsIgnoreCase("pls")) {
|
||||
return new PLSFormat();
|
||||
}
|
||||
if (format.equalsIgnoreCase("xspf")) {
|
||||
return new XSPFFormat();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setPlaylistDao(PlaylistDao playlistDao) {
|
||||
this.playlistDao = playlistDao;
|
||||
}
|
||||
|
||||
public void setMediaFileDao(MediaFileDao mediaFileDao) {
|
||||
this.mediaFileDao = mediaFileDao;
|
||||
}
|
||||
|
||||
public void setMediaFileService(MediaFileService mediaFileService) {
|
||||
this.mediaFileService = mediaFileService;
|
||||
}
|
||||
|
||||
public void setSecurityService(SecurityService securityService) {
|
||||
this.securityService = securityService;
|
||||
}
|
||||
|
||||
public void setSettingsService(SettingsService settingsService) {
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract superclass for playlist formats.
|
||||
*/
|
||||
private abstract class PlaylistFormat {
|
||||
public abstract Pair<List<MediaFile>, List<String>> parse(BufferedReader reader, MediaFileService mediaFileService) throws IOException;
|
||||
|
||||
public abstract void format(List<MediaFile> files, PrintWriter writer) throws IOException;
|
||||
|
||||
|
||||
protected MediaFile getMediaFile(String path) {
|
||||
try {
|
||||
File file = new File(path);
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
file = normalizePath(file);
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
MediaFile mediaFile = mediaFileService.getMediaFile(file);
|
||||
if (mediaFile != null && mediaFile.exists()) {
|
||||
return mediaFile;
|
||||
}
|
||||
} catch (SecurityException x) {
|
||||
// Ignored
|
||||
} catch (IOException x) {
|
||||
// Ignored
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paths in an external playlist may not have the same upper/lower case as in the (case sensitive) media_file table.
|
||||
* This methods attempts to normalize the external path to match the one stored in the table.
|
||||
*/
|
||||
private File normalizePath(File file) throws IOException {
|
||||
|
||||
// Only relevant for Windows where paths are case insensitive.
|
||||
if (!Util.isWindows()) {
|
||||
return file;
|
||||
}
|
||||
|
||||
// Find the most specific music folder.
|
||||
String canonicalPath = file.getCanonicalPath();
|
||||
MusicFolder containingMusicFolder = null;
|
||||
for (MusicFolder musicFolder : settingsService.getAllMusicFolders()) {
|
||||
String musicFolderPath = musicFolder.getPath().getPath();
|
||||
if (canonicalPath.toLowerCase().startsWith(musicFolderPath.toLowerCase())) {
|
||||
if (containingMusicFolder == null || containingMusicFolder.getPath().length() < musicFolderPath.length()) {
|
||||
containingMusicFolder = musicFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (containingMusicFolder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new File(containingMusicFolder.getPath().getPath() + canonicalPath.substring(containingMusicFolder.getPath().getPath().length()));
|
||||
// TODO: Consider slashes.
|
||||
}
|
||||
}
|
||||
|
||||
private class M3UFormat extends PlaylistFormat {
|
||||
public Pair<List<MediaFile>, List<String>> parse(BufferedReader reader, MediaFileService mediaFileService) throws IOException {
|
||||
List<MediaFile> ok = new ArrayList<MediaFile>();
|
||||
List<String> error = new ArrayList<String>();
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
if (!line.startsWith("#")) {
|
||||
MediaFile file = getMediaFile(line);
|
||||
if (file != null) {
|
||||
ok.add(file);
|
||||
} else {
|
||||
error.add(line);
|
||||
}
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
return new Pair<List<MediaFile>, List<String>>(ok, error);
|
||||
}
|
||||
|
||||
public void format(List<MediaFile> files, PrintWriter writer) throws IOException {
|
||||
writer.println("#EXTM3U");
|
||||
for (MediaFile file : files) {
|
||||
writer.println(file.getPath());
|
||||
}
|
||||
if (writer.checkError()) {
|
||||
throw new IOException("Error when writing playlist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of PLS playlist format.
|
||||
*/
|
||||
private class PLSFormat extends PlaylistFormat {
|
||||
public Pair<List<MediaFile>, List<String>> parse(BufferedReader reader, MediaFileService mediaFileService) throws IOException {
|
||||
List<MediaFile> ok = new ArrayList<MediaFile>();
|
||||
List<String> error = new ArrayList<String>();
|
||||
|
||||
Pattern pattern = Pattern.compile("^File\\d+=(.*)$");
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.find()) {
|
||||
String path = matcher.group(1);
|
||||
MediaFile file = getMediaFile(path);
|
||||
if (file != null) {
|
||||
ok.add(file);
|
||||
} else {
|
||||
error.add(path);
|
||||
}
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
return new Pair<List<MediaFile>, List<String>>(ok, error);
|
||||
}
|
||||
|
||||
public void format(List<MediaFile> files, PrintWriter writer) throws IOException {
|
||||
writer.println("[playlist]");
|
||||
int counter = 0;
|
||||
|
||||
for (MediaFile file : files) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of XSPF (http://www.xspf.org/) playlist format.
|
||||
*/
|
||||
private class XSPFFormat extends PlaylistFormat {
|
||||
public Pair<List<MediaFile>, List<String>> parse(BufferedReader reader, MediaFileService mediaFileService) throws IOException {
|
||||
List<MediaFile> ok = new ArrayList<MediaFile>();
|
||||
List<String> error = new ArrayList<String>();
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
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) {
|
||||
MediaFile file = getMediaFile(location);
|
||||
if (file != null) {
|
||||
ok.add(file);
|
||||
} else {
|
||||
error.add(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Pair<List<MediaFile>, List<String>>(ok, error);
|
||||
}
|
||||
|
||||
public void format(List<MediaFile> files, 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 : files) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PlaylistComparator implements Comparator<Playlist> {
|
||||
@Override
|
||||
public int compare(Playlist p1, Playlist p2) {
|
||||
|
||||
@@ -107,6 +107,7 @@ public class SettingsService {
|
||||
private static final String KEY_SMTP_USER = "SmtpUser";
|
||||
private static final String KEY_SMTP_PASSWORD = "SmtpPassword";
|
||||
private static final String KEY_SMTP_FROM = "SmtpFrom";
|
||||
private static final String KEY_EXPORT_PLAYLIST_FORMAT = "PlaylistExportFormat";
|
||||
|
||||
// Database Settings
|
||||
private static final String KEY_DATABASE_CONFIG_TYPE = "DatabaseConfigType";
|
||||
@@ -174,6 +175,7 @@ public class SettingsService {
|
||||
private static final boolean DEFAULT_SONOS_ENABLED = false;
|
||||
private static final String DEFAULT_SONOS_SERVICE_NAME = "Libresonic";
|
||||
private static final int DEFAULT_SONOS_SERVICE_ID = 242;
|
||||
private static final String DEFAULT_EXPORT_PLAYLIST_FORMAT = "m3u";
|
||||
|
||||
private static final String DEFAULT_SMTP_SERVER = null;
|
||||
private static final String DEFAULT_SMTP_ENCRYPTION = "None";
|
||||
@@ -1363,4 +1365,8 @@ public class SettingsService {
|
||||
setDatabaseUsertableQuote(DEFAULT_DATABASE_USERTABLE_QUOTE);
|
||||
setDatabaseConfigType(DEFAULT_DATABASE_CONFIG_TYPE);
|
||||
}
|
||||
|
||||
public String getPlaylistExportFormat() {
|
||||
return getProperty(KEY_EXPORT_PLAYLIST_FORMAT, DEFAULT_EXPORT_PLAYLIST_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package org.libresonic.player.service.playlist;
|
||||
|
||||
import chameleon.content.Content;
|
||||
import chameleon.playlist.Media;
|
||||
import chameleon.playlist.Playlist;
|
||||
import chameleon.playlist.SpecificPlaylist;
|
||||
import chameleon.playlist.SpecificPlaylistProvider;
|
||||
import org.libresonic.player.dao.MediaFileDao;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class DefaultPlaylistExportHandler implements PlaylistExportHandler {
|
||||
|
||||
@Autowired
|
||||
MediaFileDao mediaFileDao;
|
||||
|
||||
@Override
|
||||
public boolean canHandle(Class<? extends SpecificPlaylistProvider> providerClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpecificPlaylist handle(int id, SpecificPlaylistProvider provider) throws Exception {
|
||||
chameleon.playlist.Playlist playlist = createChameleonGenericPlaylistFromDBId(id);
|
||||
return provider.toSpecificPlaylist(playlist);
|
||||
}
|
||||
|
||||
Playlist createChameleonGenericPlaylistFromDBId(int id) {
|
||||
Playlist newPlaylist = new Playlist();
|
||||
List<MediaFile> files = mediaFileDao.getFilesInPlaylist(id);
|
||||
files.forEach(file -> {
|
||||
Media component = new Media();
|
||||
Content content = new Content(file.getPath());
|
||||
component.setSource(content);
|
||||
newPlaylist.getRootSequence().addComponent(component);
|
||||
});
|
||||
return newPlaylist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package org.libresonic.player.service.playlist;
|
||||
|
||||
import chameleon.playlist.*;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.libresonic.player.service.MediaFileService;
|
||||
import org.libresonic.player.util.Pair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class DefaultPlaylistImportHandler implements PlaylistImportHandler {
|
||||
|
||||
@Autowired
|
||||
MediaFileService mediaFileService;
|
||||
|
||||
@Override
|
||||
public boolean canHandle(Class<? extends SpecificPlaylist> playlistClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<List<MediaFile>, List<String>> handle(
|
||||
SpecificPlaylist inputSpecificPlaylist
|
||||
) {
|
||||
List<MediaFile> mediaFiles = new ArrayList<>();
|
||||
List<String> errors = new ArrayList<>();
|
||||
try {
|
||||
inputSpecificPlaylist.toPlaylist().acceptDown(new PlaylistVisitor() {
|
||||
@Override
|
||||
public void beginVisitPlaylist(Playlist playlist) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisitPlaylist(Playlist playlist) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginVisitParallel(Parallel parallel) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisitParallel(Parallel parallel) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginVisitSequence(Sequence sequence) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisitSequence(Sequence sequence) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginVisitMedia(Media media) throws Exception {
|
||||
try {
|
||||
URI uri = media.getSource().getURI();
|
||||
File file = new File(uri);
|
||||
MediaFile mediaFile = mediaFileService.getMediaFile(file);
|
||||
if(mediaFile != null) {
|
||||
mediaFiles.add(mediaFile);
|
||||
} else {
|
||||
errors.add("Cannot find media file " + file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors.add(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisitMedia(Media media) throws Exception {
|
||||
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
errors.add(e.getMessage());
|
||||
}
|
||||
|
||||
return Pair.create(mediaFiles, errors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.libresonic.player.service.playlist;
|
||||
|
||||
import chameleon.playlist.SpecificPlaylist;
|
||||
import chameleon.playlist.SpecificPlaylistProvider;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
public interface PlaylistExportHandler extends Ordered {
|
||||
boolean canHandle(Class<? extends SpecificPlaylistProvider> providerClass);
|
||||
|
||||
SpecificPlaylist handle(int id, SpecificPlaylistProvider provider) throws Exception;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.libresonic.player.service.playlist;
|
||||
|
||||
import chameleon.playlist.SpecificPlaylist;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.libresonic.player.util.Pair;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PlaylistImportHandler extends Ordered {
|
||||
boolean canHandle(Class<? extends SpecificPlaylist> playlistClass);
|
||||
|
||||
Pair<List<MediaFile>,List<String>> handle(SpecificPlaylist inputSpecificPlaylist);
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package org.libresonic.player.service.playlist;
|
||||
|
||||
import chameleon.playlist.SpecificPlaylist;
|
||||
import chameleon.playlist.SpecificPlaylistProvider;
|
||||
import chameleon.playlist.xspf.Location;
|
||||
import chameleon.playlist.xspf.Track;
|
||||
import chameleon.playlist.xspf.XspfProvider;
|
||||
import org.libresonic.player.dao.MediaFileDao;
|
||||
import org.libresonic.player.dao.PlaylistDao;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class XspfPlaylistExportHandler implements PlaylistExportHandler {
|
||||
|
||||
@Autowired
|
||||
MediaFileDao mediaFileDao;
|
||||
|
||||
@Autowired
|
||||
PlaylistDao playlistDao;
|
||||
|
||||
@Override
|
||||
public boolean canHandle(Class<? extends SpecificPlaylistProvider> providerClass) {
|
||||
return XspfProvider.class.equals(providerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpecificPlaylist handle(int id, SpecificPlaylistProvider provider) throws Exception {
|
||||
chameleon.playlist.xspf.Playlist playlist = createXsfpPlaylistFromDBId(id);
|
||||
return playlist;
|
||||
}
|
||||
|
||||
chameleon.playlist.xspf.Playlist createXsfpPlaylistFromDBId(int id) {
|
||||
chameleon.playlist.xspf.Playlist newPlaylist = new chameleon.playlist.xspf.Playlist();
|
||||
org.libresonic.player.domain.Playlist playlist = playlistDao.getPlaylist(id);
|
||||
newPlaylist.setTitle(playlist.getName());
|
||||
newPlaylist.setCreator("Libresonic user " + playlist.getUsername());
|
||||
newPlaylist.setDate(new Date());
|
||||
List<MediaFile> files = mediaFileDao.getFilesInPlaylist(id);
|
||||
|
||||
files.stream().map(mediaFile -> {
|
||||
Track track = new Track();
|
||||
track.setTrackNumber(mediaFile.getTrackNumber());
|
||||
track.setCreator(mediaFile.getArtist());
|
||||
track.setTitle(mediaFile.getTitle());
|
||||
track.setAlbum(mediaFile.getAlbumName());
|
||||
track.setDuration(mediaFile.getDurationSeconds());
|
||||
track.setImage(mediaFile.getCoverArtPath());
|
||||
Location location = new Location();
|
||||
location.setText(mediaFile.getPath());
|
||||
track.getStringContainers().add(location);
|
||||
return track;
|
||||
}).forEach(newPlaylist::addTrack);
|
||||
|
||||
return newPlaylist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package org.libresonic.player.service.playlist;
|
||||
|
||||
import chameleon.playlist.SpecificPlaylist;
|
||||
import chameleon.playlist.xspf.Location;
|
||||
import chameleon.playlist.xspf.Playlist;
|
||||
import chameleon.playlist.xspf.StringContainer;
|
||||
import org.libresonic.player.Logger;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.libresonic.player.service.MediaFileService;
|
||||
import org.libresonic.player.util.Pair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class XspfPlaylistImportHandler implements PlaylistImportHandler {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(XspfPlaylistImportHandler.class);
|
||||
|
||||
@Autowired
|
||||
MediaFileService mediaFileService;
|
||||
|
||||
@Override
|
||||
public boolean canHandle(Class<? extends SpecificPlaylist> playlistClass) {
|
||||
return Playlist.class.equals(playlistClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<List<MediaFile>, List<String>> handle(SpecificPlaylist inputSpecificPlaylist) {
|
||||
List<MediaFile> mediaFiles = new ArrayList<>();
|
||||
List<String> errors = new ArrayList<>();
|
||||
Playlist xspfPlaylist = (Playlist) inputSpecificPlaylist;
|
||||
xspfPlaylist.getTracks().forEach(track -> {
|
||||
MediaFile mediaFile = null;
|
||||
for(StringContainer sc : track.getStringContainers()) {
|
||||
if(sc instanceof Location) {
|
||||
Location location = (Location) sc;
|
||||
try {
|
||||
File file = new File(new URI(location.getText()));
|
||||
mediaFile = mediaFileService.getMediaFile(file);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
if(mediaFile == null) {
|
||||
try {
|
||||
File file = new File(sc.getText());
|
||||
mediaFile = mediaFileService.getMediaFile(file);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(mediaFile != null) {
|
||||
mediaFiles.add(mediaFile);
|
||||
} else {
|
||||
String errorMsg = "Could not find media file matching ";
|
||||
try {
|
||||
errorMsg += track.getStringContainers().stream().map(StringContainer::getText).collect(Collectors.joining(","));
|
||||
} catch (Exception ignored) {}
|
||||
errors.add(errorMsg);
|
||||
}
|
||||
});
|
||||
return Pair.create(mediaFiles, errors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 40;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# First one, as it is a binary format that can easily be recognized.
|
||||
chameleon.playlist.pla.PLAProvider
|
||||
chameleon.playlist.asx.AsxProvider
|
||||
chameleon.playlist.b4s.B4sProvider
|
||||
# BEFORE SMIL (same root element).
|
||||
chameleon.playlist.wpl.WplProvider
|
||||
chameleon.playlist.smil.SmilProvider
|
||||
chameleon.playlist.rss.RSSProvider
|
||||
chameleon.playlist.atom.AtomProvider
|
||||
# Before XSPF, because this format is very close to XSPF,
|
||||
# but its XML format is strictly checked (and XSPF's format is not)
|
||||
chameleon.playlist.hypetape.HypetapeProvider
|
||||
chameleon.playlist.xspf.XspfProvider
|
||||
chameleon.playlist.rmp.RmpProvider
|
||||
chameleon.playlist.plist.PlistProvider
|
||||
chameleon.playlist.kpl.KplProvider
|
||||
chameleon.playlist.pls.PLSProvider
|
||||
chameleon.playlist.mpcpl.MPCPLProvider
|
||||
chameleon.playlist.plp.PLPProvider
|
||||
# Shall be last, as the M3U format can match almost everything.
|
||||
chameleon.playlist.m3u.M3UProvider
|
||||
@@ -129,13 +129,10 @@
|
||||
<property name="transcodingService" ref="transcodingService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="playlistService" class="org.libresonic.player.service.PlaylistService">
|
||||
<property name="mediaFileService" ref="mediaFileService"/>
|
||||
<property name="securityService" ref="securityService"/>
|
||||
<property name="settingsService" ref="settingsService"/>
|
||||
<property name="mediaFileDao" ref="mediaFileDao"/>
|
||||
<property name="playlistDao" ref="playlistDao"/>
|
||||
</bean>
|
||||
<context:component-scan base-package="org.libresonic.player.service.playlist" />
|
||||
|
||||
<!--suppress SpringBeanConstructorArgInspection, AutowiredDependenciesInspection -->
|
||||
<bean id="playlistService" class="org.libresonic.player.service.PlaylistService" autowire="constructor"/>
|
||||
|
||||
<bean id="versionService" class="org.libresonic.player.service.VersionService" />
|
||||
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package org.libresonic.player.service;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.libresonic.player.dao.MediaFileDao;
|
||||
import org.libresonic.player.dao.PlaylistDao;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.libresonic.player.domain.Playlist;
|
||||
import org.libresonic.player.service.playlist.DefaultPlaylistExportHandler;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PlaylistServiceTestExport {
|
||||
|
||||
PlaylistService playlistService;
|
||||
|
||||
@InjectMocks
|
||||
DefaultPlaylistExportHandler defaultPlaylistExportHandler;
|
||||
|
||||
@Mock
|
||||
MediaFileDao mediaFileDao;
|
||||
|
||||
@Mock
|
||||
PlaylistDao playlistDao;
|
||||
|
||||
@Mock
|
||||
MediaFileService mediaFileService;
|
||||
|
||||
@Mock
|
||||
SettingsService settingsService;
|
||||
|
||||
@Mock
|
||||
SecurityService securityService;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<Playlist> actual;
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<List<MediaFile>> medias;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
playlistService = new PlaylistService(mediaFileDao,
|
||||
playlistDao,
|
||||
securityService,
|
||||
settingsService,
|
||||
Lists.newArrayList(
|
||||
defaultPlaylistExportHandler),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportToM3U() throws Exception {
|
||||
|
||||
when(mediaFileDao.getFilesInPlaylist(eq(23))).thenReturn(getPlaylistFiles());
|
||||
when(settingsService.getPlaylistExportFormat()).thenReturn("m3u");
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
playlistService.exportPlaylist(23, outputStream);
|
||||
String actual = outputStream.toString();
|
||||
Assert.assertEquals(IOUtils.toString(getClass().getResourceAsStream("/PLAYLISTS/23.m3u")), actual);
|
||||
}
|
||||
|
||||
private List<MediaFile> getPlaylistFiles() {
|
||||
List<MediaFile> mediaFiles = new ArrayList<>();
|
||||
|
||||
MediaFile mf1 = new MediaFile();
|
||||
mf1.setId(142);
|
||||
mf1.setPath("/some/path/to_album/to_artist/name - of - song.mp3");
|
||||
mf1.setPresent(true);
|
||||
mediaFiles.add(mf1);
|
||||
|
||||
MediaFile mf2 = new MediaFile();
|
||||
mf2.setId(1235);
|
||||
mf2.setPath("/some/path/to_album2/to_artist/another song.mp3");
|
||||
mf2.setPresent(true);
|
||||
mediaFiles.add(mf2);
|
||||
|
||||
MediaFile mf3 = new MediaFile();
|
||||
mf3.setId(198403);
|
||||
mf3.setPath("/some/path/to_album2/to_artist/another song2.mp3");
|
||||
mf3.setPresent(false);
|
||||
mediaFiles.add(mf3);
|
||||
|
||||
return mediaFiles;
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
package org.libresonic.player.service;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.libresonic.player.dao.MediaFileDao;
|
||||
import org.libresonic.player.dao.PlaylistDao;
|
||||
import org.libresonic.player.domain.MediaFile;
|
||||
import org.libresonic.player.domain.Playlist;
|
||||
import org.libresonic.player.service.playlist.DefaultPlaylistExportHandler;
|
||||
import org.libresonic.player.service.playlist.DefaultPlaylistImportHandler;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PlaylistServiceTestImport {
|
||||
|
||||
PlaylistService playlistService;
|
||||
|
||||
@InjectMocks
|
||||
DefaultPlaylistImportHandler defaultPlaylistImportHandler;
|
||||
|
||||
@Mock
|
||||
MediaFileDao mediaFileDao;
|
||||
|
||||
@Mock
|
||||
PlaylistDao playlistDao;
|
||||
|
||||
@Mock
|
||||
MediaFileService mediaFileService;
|
||||
|
||||
@Mock
|
||||
SettingsService settingsService;
|
||||
|
||||
@Mock
|
||||
SecurityService securityService;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<Playlist> actual;
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<List<MediaFile>> medias;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
playlistService = new PlaylistService(
|
||||
mediaFileDao,
|
||||
playlistDao,
|
||||
securityService,
|
||||
settingsService,
|
||||
Collections.emptyList(),
|
||||
Lists.newArrayList(defaultPlaylistImportHandler));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportFromM3U() throws Exception {
|
||||
String username = "testUser";
|
||||
String playlistName = "test-playlist";
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("#EXTM3U\n");
|
||||
File mf1 = folder.newFile();
|
||||
FileUtils.touch(mf1);
|
||||
File mf2 = folder.newFile();
|
||||
FileUtils.touch(mf2);
|
||||
File mf3 = folder.newFile();
|
||||
FileUtils.touch(mf3);
|
||||
builder.append(mf1.getAbsolutePath() + "\n");
|
||||
builder.append(mf2.getAbsolutePath() + "\n");
|
||||
builder.append(mf3.getAbsolutePath() + "\n");
|
||||
doAnswer(new PersistPlayList(23)).when(playlistDao).createPlaylist(any());
|
||||
doAnswer(new MediaFileHasEverything()).when(mediaFileService).getMediaFile(any(File.class));
|
||||
InputStream inputStream = new ByteArrayInputStream(builder.toString().getBytes("UTF-8"));
|
||||
String path = "/path/to/"+playlistName+".m3u";
|
||||
playlistService.importPlaylist(username, playlistName, path, inputStream, null);
|
||||
verify(playlistDao).createPlaylist(actual.capture());
|
||||
verify(playlistDao).setFilesInPlaylist(eq(23), medias.capture());
|
||||
Playlist expected = new Playlist();
|
||||
expected.setUsername(username);
|
||||
expected.setName(playlistName);
|
||||
expected.setComment("Auto-imported from " + path);
|
||||
expected.setImportedFrom(path);
|
||||
expected.setShared(true);
|
||||
expected.setId(23);
|
||||
assertTrue("\n" + ToStringBuilder.reflectionToString(actual.getValue()) + "\n\n did not equal \n\n" + ToStringBuilder.reflectionToString(expected), EqualsBuilder.reflectionEquals(actual.getValue(), expected, "created", "changed"));
|
||||
List<MediaFile> mediaFiles = medias.getValue();
|
||||
assertEquals(3, mediaFiles.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportFromPLS() throws Exception {
|
||||
String username = "testUser";
|
||||
String playlistName = "test-playlist";
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("[playlist]\n");
|
||||
File mf1 = folder.newFile();
|
||||
FileUtils.touch(mf1);
|
||||
File mf2 = folder.newFile();
|
||||
FileUtils.touch(mf2);
|
||||
File mf3 = folder.newFile();
|
||||
FileUtils.touch(mf3);
|
||||
builder.append("File1=" + mf1.getAbsolutePath() + "\n");
|
||||
builder.append("File2=" + mf2.getAbsolutePath() + "\n");
|
||||
builder.append("File3=" + mf3.getAbsolutePath() + "\n");
|
||||
doAnswer(new PersistPlayList(23)).when(playlistDao).createPlaylist(any());
|
||||
doAnswer(new MediaFileHasEverything()).when(mediaFileService).getMediaFile(any(File.class));
|
||||
InputStream inputStream = new ByteArrayInputStream(builder.toString().getBytes("UTF-8"));
|
||||
String path = "/path/to/"+playlistName+".pls";
|
||||
playlistService.importPlaylist(username, playlistName, path, inputStream, null);
|
||||
verify(playlistDao).createPlaylist(actual.capture());
|
||||
verify(playlistDao).setFilesInPlaylist(eq(23), medias.capture());
|
||||
Playlist expected = new Playlist();
|
||||
expected.setUsername(username);
|
||||
expected.setName(playlistName);
|
||||
expected.setComment("Auto-imported from " + path);
|
||||
expected.setImportedFrom(path);
|
||||
expected.setShared(true);
|
||||
expected.setId(23);
|
||||
assertTrue("\n" + ToStringBuilder.reflectionToString(actual.getValue()) + "\n\n did not equal \n\n" + ToStringBuilder.reflectionToString(expected), EqualsBuilder.reflectionEquals(actual.getValue(), expected, "created", "changed"));
|
||||
List<MediaFile> mediaFiles = medias.getValue();
|
||||
assertEquals(3, mediaFiles.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportFromXSPF() throws Exception {
|
||||
String username = "testUser";
|
||||
String playlistName = "test-playlist";
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
|
||||
+ " <trackList>\n");
|
||||
File mf1 = folder.newFile();
|
||||
FileUtils.touch(mf1);
|
||||
File mf2 = folder.newFile();
|
||||
FileUtils.touch(mf2);
|
||||
File mf3 = folder.newFile();
|
||||
FileUtils.touch(mf3);
|
||||
builder.append("<track><location>file://" + mf1.getAbsolutePath() + "</location></track>\n");
|
||||
builder.append("<track><location>file://" + mf2.getAbsolutePath() + "</location></track>\n");
|
||||
builder.append("<track><location>file://" + mf3.getAbsolutePath() + "</location></track>\n");
|
||||
builder.append(" </trackList>\n" + "</playlist>\n");
|
||||
doAnswer(new PersistPlayList(23)).when(playlistDao).createPlaylist(any());
|
||||
doAnswer(new MediaFileHasEverything()).when(mediaFileService).getMediaFile(any(File.class));
|
||||
InputStream inputStream = new ByteArrayInputStream(builder.toString().getBytes("UTF-8"));
|
||||
String path = "/path/to/"+playlistName+".xspf";
|
||||
playlistService.importPlaylist(username, playlistName, path, inputStream, null);
|
||||
verify(playlistDao).createPlaylist(actual.capture());
|
||||
verify(playlistDao).setFilesInPlaylist(eq(23), medias.capture());
|
||||
Playlist expected = new Playlist();
|
||||
expected.setUsername(username);
|
||||
expected.setName(playlistName);
|
||||
expected.setComment("Auto-imported from " + path);
|
||||
expected.setImportedFrom(path);
|
||||
expected.setShared(true);
|
||||
expected.setId(23);
|
||||
assertTrue("\n" + ToStringBuilder.reflectionToString(actual.getValue()) + "\n\n did not equal \n\n" + ToStringBuilder.reflectionToString(expected), EqualsBuilder.reflectionEquals(actual.getValue(), expected, "created", "changed"));
|
||||
List<MediaFile> mediaFiles = medias.getValue();
|
||||
assertEquals(3, mediaFiles.size());
|
||||
}
|
||||
|
||||
private class PersistPlayList implements Answer {
|
||||
private final int id;
|
||||
public PersistPlayList(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
Playlist playlist = invocationOnMock.getArgument(0);
|
||||
playlist.setId(id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class MediaFileHasEverything implements Answer {
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
File file = invocationOnMock.getArgument(0);
|
||||
MediaFile mediaFile = new MediaFile();
|
||||
mediaFile.setPath(file.getPath());
|
||||
return mediaFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
/some/path/to_album/to_artist/name - of - song.mp3
|
||||
/some/path/to_album2/to_artist/another song.mp3
|
||||
/some/path/to_album2/to_artist/another song2.mp3
|
||||
@@ -0,0 +1,6 @@
|
||||
[playlist]
|
||||
File1=/some/path/to_album/to_artist/name - of - song.mp3
|
||||
File2=/some/path/to_album2/to_artist/another song.mp3
|
||||
File3=/some/path/to_album2/to_artist/another song2.mp3
|
||||
NumberOfEntries=3
|
||||
Version=2
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<playlist version="1" xmlns="http://xspf.org/ns/0/">
|
||||
<trackList>
|
||||
<track><location>file:///some/path/to_album/to_artist/name - of - song.mp3</location></track>
|
||||
<track><location>file:///some/path/to_album2/to_artist/another song.mp3</location></track>
|
||||
<track><location>file:///some/path/to_album2/to_artist/another song2.mp3</location></track>
|
||||
</trackList>
|
||||
</playlist>
|
||||
Reference in New Issue
Block a user