545 lines
21 KiB
Java
545 lines
21 KiB
Java
/*
|
|
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.nullsum.audinaut.service;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Bitmap;
|
|
import android.util.Log;
|
|
|
|
import net.nullsum.audinaut.domain.Artist;
|
|
import net.nullsum.audinaut.domain.Genre;
|
|
import net.nullsum.audinaut.domain.Indexes;
|
|
import net.nullsum.audinaut.domain.MusicDirectory;
|
|
import net.nullsum.audinaut.domain.MusicDirectory.Entry;
|
|
import net.nullsum.audinaut.domain.MusicFolder;
|
|
import net.nullsum.audinaut.domain.Playlist;
|
|
import net.nullsum.audinaut.domain.SearchCritera;
|
|
import net.nullsum.audinaut.domain.SearchResult;
|
|
import net.nullsum.audinaut.domain.User;
|
|
import net.nullsum.audinaut.util.Constants;
|
|
import net.nullsum.audinaut.util.FileUtil;
|
|
import net.nullsum.audinaut.util.ProgressListener;
|
|
import net.nullsum.audinaut.util.SilentBackgroundTask;
|
|
import net.nullsum.audinaut.util.Util;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileReader;
|
|
import java.io.Reader;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.SortedSet;
|
|
|
|
import okhttp3.Response;
|
|
|
|
/**
|
|
* @author Sindre Mehus
|
|
*/
|
|
public class OfflineMusicService implements MusicService {
|
|
public static final String ERRORMSG = "Not available in offline mode";
|
|
private static final String TAG = OfflineMusicService.class.getSimpleName();
|
|
private static final Random random = new Random();
|
|
|
|
@Override
|
|
public void ping(Context context, ProgressListener progressListener) throws Exception {
|
|
|
|
}
|
|
|
|
@Override
|
|
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
List<Artist> artists = new ArrayList<>();
|
|
List<Entry> entries = new ArrayList<>();
|
|
File root = FileUtil.getMusicDirectory(context);
|
|
for (File file : FileUtil.listFiles(root)) {
|
|
if (file.isDirectory()) {
|
|
Artist artist = new Artist();
|
|
artist.setId(file.getPath());
|
|
artist.setIndex(file.getName().substring(0, 1));
|
|
artist.setName(file.getName());
|
|
artists.add(artist);
|
|
} else if (!file.getName().equals("albumart.jpg") && !file.getName().equals(".nomedia")) {
|
|
entries.add(createEntry(context, file));
|
|
}
|
|
}
|
|
|
|
return new Indexes(Collections.emptyList(), artists, entries);
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
return getMusicDirectory(id, context);
|
|
}
|
|
|
|
private MusicDirectory getMusicDirectory(String id, Context context) throws Exception {
|
|
File dir = new File(id);
|
|
MusicDirectory result = new MusicDirectory();
|
|
result.setName(dir.getName());
|
|
|
|
Set<String> names = new HashSet<>();
|
|
|
|
for (File file : FileUtil.listMediaFiles(dir)) {
|
|
String name = getName(file);
|
|
if (name != null & !names.contains(name)) {
|
|
names.add(name);
|
|
result.addChild(createEntry(context, file, name, true));
|
|
}
|
|
}
|
|
result.sortChildren(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_CUSTOM_SORT_ENABLED, true));
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
private String getName(File file) {
|
|
String name = file.getName();
|
|
if (file.isDirectory()) {
|
|
return name;
|
|
}
|
|
|
|
if (name.endsWith(".partial") || name.contains(".partial.") || name.equals(Constants.ALBUM_ART_FILE)) {
|
|
return null;
|
|
}
|
|
|
|
name = name.replace(".complete", "");
|
|
return FileUtil.getBaseName(name);
|
|
}
|
|
|
|
private Entry createEntry(Context context, File file) {
|
|
return createEntry(context, file, getName(file));
|
|
}
|
|
|
|
private Entry createEntry(Context context, File file, String name) {
|
|
return createEntry(context, file, name, true);
|
|
}
|
|
|
|
private Entry createEntry(Context context, File file, String name, boolean load) {
|
|
Entry entry;
|
|
entry = new Entry();
|
|
entry.setDirectory(file.isDirectory());
|
|
entry.setId(file.getPath());
|
|
entry.setParent(file.getParent());
|
|
String root = FileUtil.getMusicDirectory(context).getPath();
|
|
entry.setPath(file.getPath().replaceFirst("^" + root + "/", ""));
|
|
String title = name;
|
|
if (file.isFile()) {
|
|
File artistFolder = file.getParentFile().getParentFile();
|
|
File albumFolder = file.getParentFile();
|
|
if (artistFolder.getPath().equals(root)) {
|
|
entry.setArtist(albumFolder.getName());
|
|
} else {
|
|
entry.setArtist(artistFolder.getName());
|
|
}
|
|
entry.setAlbum(albumFolder.getName());
|
|
|
|
int index = name.indexOf('-');
|
|
if (index != -1) {
|
|
try {
|
|
entry.setTrack(Integer.parseInt(name.substring(0, index)));
|
|
title = title.substring(index + 1);
|
|
} catch (Exception e) {
|
|
// Failed parseInt, just means track filled out
|
|
}
|
|
}
|
|
|
|
if (load) {
|
|
entry.loadMetadata(file);
|
|
}
|
|
}
|
|
|
|
entry.setTitle(title);
|
|
entry.setSuffix(FileUtil.getExtension(file.getName().replace(".complete", "")));
|
|
|
|
File albumArt = FileUtil.getAlbumArtFile(context, entry);
|
|
if (albumArt.exists()) {
|
|
entry.setCoverArt(albumArt.getPath());
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
@Override
|
|
public Bitmap getCoverArt(Context context, Entry entry, int size, ProgressListener progressListener, SilentBackgroundTask task) throws Exception {
|
|
try {
|
|
return FileUtil.getAlbumArtBitmap(context, entry, size);
|
|
} catch (Exception e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Response getDownloadInputStream(Context context, Entry song, long offset, int maxBitrate, SilentBackgroundTask task) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception {
|
|
List<Artist> artists = new ArrayList<>();
|
|
List<Entry> albums = new ArrayList<>();
|
|
List<Entry> songs = new ArrayList<>();
|
|
File root = FileUtil.getMusicDirectory(context);
|
|
int closeness;
|
|
for (File artistFile : FileUtil.listFiles(root)) {
|
|
String artistName = artistFile.getName();
|
|
if (artistFile.isDirectory()) {
|
|
if ((closeness = matchCriteria(criteria, artistName)) > 0) {
|
|
Artist artist = new Artist();
|
|
artist.setId(artistFile.getPath());
|
|
artist.setIndex(artistFile.getName().substring(0, 1));
|
|
artist.setName(artistName);
|
|
artist.setCloseness(closeness);
|
|
artists.add(artist);
|
|
}
|
|
|
|
recursiveAlbumSearch(artistName, artistFile, criteria, context, albums, songs);
|
|
}
|
|
}
|
|
|
|
Collections.sort(artists, (lhs, rhs) -> {
|
|
if (lhs.getCloseness() == rhs.getCloseness()) {
|
|
return 0;
|
|
} else if (lhs.getCloseness() > rhs.getCloseness()) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
});
|
|
Collections.sort(albums, (lhs, rhs) -> {
|
|
if (lhs.getCloseness() == rhs.getCloseness()) {
|
|
return 0;
|
|
} else if (lhs.getCloseness() > rhs.getCloseness()) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
});
|
|
Collections.sort(songs, (lhs, rhs) -> {
|
|
if (lhs.getCloseness() == rhs.getCloseness()) {
|
|
return 0;
|
|
} else if (lhs.getCloseness() > rhs.getCloseness()) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
});
|
|
|
|
// Respect counts in search criteria
|
|
int artistCount = Math.min(artists.size(), criteria.getArtistCount());
|
|
int albumCount = Math.min(albums.size(), criteria.getAlbumCount());
|
|
int songCount = Math.min(songs.size(), criteria.getSongCount());
|
|
artists = artists.subList(0, artistCount);
|
|
albums = albums.subList(0, albumCount);
|
|
songs = songs.subList(0, songCount);
|
|
|
|
return new SearchResult(artists, albums, songs);
|
|
}
|
|
|
|
private void recursiveAlbumSearch(String artistName, File file, SearchCritera criteria, Context context, List<Entry> albums, List<Entry> songs) {
|
|
int closeness;
|
|
for (File albumFile : FileUtil.listMediaFiles(file)) {
|
|
if (albumFile.isDirectory()) {
|
|
String albumName = getName(albumFile);
|
|
if ((closeness = matchCriteria(criteria, albumName)) > 0) {
|
|
Entry album = createEntry(context, albumFile, albumName);
|
|
album.setArtist(artistName);
|
|
album.setCloseness(closeness);
|
|
albums.add(album);
|
|
}
|
|
|
|
for (File songFile : FileUtil.listMediaFiles(albumFile)) {
|
|
String songName = getName(songFile);
|
|
if (songName == null) {
|
|
continue;
|
|
}
|
|
|
|
if (songFile.isDirectory()) {
|
|
recursiveAlbumSearch(artistName, songFile, criteria, context, albums, songs);
|
|
} else if ((closeness = matchCriteria(criteria, songName)) > 0) {
|
|
Entry song = createEntry(context, albumFile, songName);
|
|
song.setArtist(artistName);
|
|
song.setAlbum(albumName);
|
|
song.setCloseness(closeness);
|
|
songs.add(song);
|
|
}
|
|
}
|
|
} else {
|
|
String songName = getName(albumFile);
|
|
if ((closeness = matchCriteria(criteria, songName)) > 0) {
|
|
Entry song = createEntry(context, albumFile, songName);
|
|
song.setArtist(artistName);
|
|
song.setAlbum(songName);
|
|
song.setCloseness(closeness);
|
|
songs.add(song);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private int matchCriteria(SearchCritera criteria, String name) {
|
|
if (criteria.getPattern().matcher(name).matches()) {
|
|
return Util.getStringDistance(
|
|
criteria.getQuery().toLowerCase(),
|
|
name.toLowerCase());
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
List<Playlist> playlists = new ArrayList<>();
|
|
File root = FileUtil.getPlaylistDirectory(context);
|
|
String lastServer = null;
|
|
boolean removeServer = true;
|
|
for (File folder : FileUtil.listFiles(root)) {
|
|
if (folder.isDirectory()) {
|
|
String server = folder.getName();
|
|
SortedSet<File> fileList = FileUtil.listFiles(folder);
|
|
for (File file : fileList) {
|
|
if (FileUtil.isPlaylistFile(file)) {
|
|
String id = file.getName();
|
|
String filename = FileUtil.getBaseName(id);
|
|
String name = server + ": " + filename;
|
|
Playlist playlist = new Playlist(server, name);
|
|
playlist.setComment(filename);
|
|
|
|
Reader reader = null;
|
|
BufferedReader buffer = null;
|
|
int songCount = 0;
|
|
try {
|
|
reader = new FileReader(file);
|
|
buffer = new BufferedReader(reader);
|
|
|
|
String line = buffer.readLine();
|
|
while ((line = buffer.readLine()) != null) {
|
|
// No matter what, end file can't have .complete in it
|
|
line = line.replace(".complete", "");
|
|
File entryFile = new File(line);
|
|
|
|
// Don't add file to playlist if it doesn't exist as cached or pinned!
|
|
File checkFile = entryFile;
|
|
if (!checkFile.exists()) {
|
|
// If normal file doens't exist, check if .complete version does
|
|
checkFile = new File(entryFile.getParent(), FileUtil.getBaseName(entryFile.getName())
|
|
+ ".complete." + FileUtil.getExtension(entryFile.getName()));
|
|
}
|
|
|
|
String entryName = getName(entryFile);
|
|
if (checkFile.exists() && entryName != null) {
|
|
songCount++;
|
|
}
|
|
}
|
|
|
|
playlist.setSongCount(Integer.toString(songCount));
|
|
} catch (Exception e) {
|
|
Log.w(TAG, "Failed to count songs in playlist", e);
|
|
} finally {
|
|
Util.close(buffer);
|
|
Util.close(reader);
|
|
}
|
|
|
|
if (songCount > 0) {
|
|
playlists.add(playlist);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!server.equals(lastServer) && fileList.size() > 0) {
|
|
if (lastServer != null) {
|
|
removeServer = false;
|
|
}
|
|
lastServer = server;
|
|
}
|
|
} else {
|
|
// Delete legacy playlist files
|
|
try {
|
|
folder.delete();
|
|
} catch (Exception e) {
|
|
Log.w(TAG, "Failed to delete old playlist file: " + folder.getName());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (removeServer) {
|
|
for (Playlist playlist : playlists) {
|
|
playlist.setName(playlist.getName().substring(playlist.getId().length() + 2));
|
|
}
|
|
}
|
|
return playlists;
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getPlaylist(boolean refresh, String id, String name, Context context, ProgressListener progressListener) throws Exception {
|
|
DownloadService downloadService = DownloadService.getInstance();
|
|
if (downloadService == null) {
|
|
return new MusicDirectory();
|
|
}
|
|
|
|
Reader reader = null;
|
|
BufferedReader buffer = null;
|
|
try {
|
|
int firstIndex = name.indexOf(id);
|
|
if (firstIndex != -1) {
|
|
name = name.substring(id.length() + 2);
|
|
}
|
|
|
|
File playlistFile = FileUtil.getPlaylistFile(context, id, name);
|
|
reader = new FileReader(playlistFile);
|
|
buffer = new BufferedReader(reader);
|
|
|
|
MusicDirectory playlist = new MusicDirectory();
|
|
String line = buffer.readLine();
|
|
if (!"#EXTM3U".equals(line)) return playlist;
|
|
|
|
while ((line = buffer.readLine()) != null) {
|
|
// No matter what, end file can't have .complete in it
|
|
line = line.replace(".complete", "");
|
|
File entryFile = new File(line);
|
|
|
|
// Don't add file to playlist if it doesn't exist as cached or pinned!
|
|
File checkFile = entryFile;
|
|
if (!checkFile.exists()) {
|
|
// If normal file doens't exist, check if .complete version does
|
|
checkFile = new File(entryFile.getParent(), FileUtil.getBaseName(entryFile.getName())
|
|
+ ".complete." + FileUtil.getExtension(entryFile.getName()));
|
|
}
|
|
|
|
String entryName = getName(entryFile);
|
|
if (checkFile.exists() && entryName != null) {
|
|
playlist.addChild(createEntry(context, entryFile, entryName, false));
|
|
}
|
|
}
|
|
|
|
return playlist;
|
|
} finally {
|
|
Util.close(buffer);
|
|
Util.close(reader);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void createPlaylist(String id, String name, List<Entry> entries, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public void addToPlaylist(String id, List<Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public void removeFromPlaylist(String id, List<Integer> toRemove, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public void overwritePlaylist(String id, String name, int toRemove, List<Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getAlbumList(String type, int size, int offset, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getAlbumList(String type, String extra, int size, int offset, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getSongList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public MusicDirectory getRandomSongs(int size, String folder, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception {
|
|
File root = FileUtil.getMusicDirectory(context);
|
|
List<File> children = new LinkedList<>();
|
|
listFilesRecursively(root, children);
|
|
MusicDirectory result = new MusicDirectory();
|
|
|
|
if (children.isEmpty()) {
|
|
return result;
|
|
}
|
|
for (int i = 0; i < size; i++) {
|
|
File file = children.get(random.nextInt(children.size()));
|
|
result.addChild(createEntry(context, file, getName(file)));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
@Override
|
|
public void setInstance(Integer instance) throws Exception {
|
|
throw new OfflineException();
|
|
}
|
|
|
|
private void listFilesRecursively(File parent, List<File> children) {
|
|
for (File file : FileUtil.listMediaFiles(parent)) {
|
|
if (file.isFile()) {
|
|
children.add(file);
|
|
} else {
|
|
listFilesRecursively(file, children);
|
|
}
|
|
}
|
|
}
|
|
}
|