Use new subsonic api getPodcasts call in RestMusicService.

Signed-off-by: Yahor Berdnikau <egorr.berd@gmail.com>
This commit is contained in:
Yahor Berdnikau 2017-09-03 19:49:03 +02:00
parent 636968f03c
commit ba0152bbca
8 changed files with 152 additions and 262 deletions

View File

@ -91,4 +91,29 @@ public class PodcastsChannel implements Serializable
public String toString() {
return getTitle();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PodcastsChannel that = (PodcastsChannel) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (title != null ? !title.equals(that.title) : that.title != null) return false;
if (url != null ? !url.equals(that.url) : that.url != null) return false;
if (description != null ? !description.equals(that.description) : that.description != null)
return false;
return status != null ? status.equals(that.status) : that.status == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (title != null ? title.hashCode() : 0);
result = 31 * result + (url != null ? url.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + (status != null ? status.hashCode() : 0);
return result;
}
}

View File

@ -56,6 +56,7 @@ import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild;
import org.moire.ultrasonic.api.subsonic.response.GetAlbumResponse;
import org.moire.ultrasonic.api.subsonic.response.GetArtistResponse;
import org.moire.ultrasonic.api.subsonic.response.GetArtistsResponse;
@ -63,6 +64,7 @@ import org.moire.ultrasonic.api.subsonic.response.GetIndexesResponse;
import org.moire.ultrasonic.api.subsonic.response.GetMusicDirectoryResponse;
import org.moire.ultrasonic.api.subsonic.response.GetPlaylistResponse;
import org.moire.ultrasonic.api.subsonic.response.GetPlaylistsResponse;
import org.moire.ultrasonic.api.subsonic.response.GetPodcastsResponse;
import org.moire.ultrasonic.api.subsonic.response.LicenseResponse;
import org.moire.ultrasonic.api.subsonic.response.MusicFoldersResponse;
import org.moire.ultrasonic.api.subsonic.response.SearchResponse;
@ -75,6 +77,7 @@ import org.moire.ultrasonic.data.APIIndexesConverter;
import org.moire.ultrasonic.data.APIMusicDirectoryConverter;
import org.moire.ultrasonic.data.APIMusicFolderConverter;
import org.moire.ultrasonic.data.APIPlaylistConverter;
import org.moire.ultrasonic.data.APIPodcastConverter;
import org.moire.ultrasonic.data.APISearchConverter;
import org.moire.ultrasonic.domain.Bookmark;
import org.moire.ultrasonic.domain.ChatMessage;
@ -99,8 +102,6 @@ import org.moire.ultrasonic.service.parser.GenreParser;
import org.moire.ultrasonic.service.parser.JukeboxStatusParser;
import org.moire.ultrasonic.service.parser.LyricsParser;
import org.moire.ultrasonic.service.parser.MusicDirectoryParser;
import org.moire.ultrasonic.service.parser.PodcastEpisodeParser;
import org.moire.ultrasonic.service.parser.PodcastsChannelsParser;
import org.moire.ultrasonic.service.parser.RandomSongsParser;
import org.moire.ultrasonic.service.parser.SearchResult2Parser;
import org.moire.ultrasonic.service.parser.ShareParser;
@ -526,41 +527,6 @@ public class RESTMusicService implements MusicService
}
}
@Override
public List<PodcastsChannel> getPodcastsChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception
{
Reader reader = getReader(context, progressListener, "getPodcasts", null,"includeEpisodes", "false");
try {
return new PodcastsChannelsParser(context).parse(reader, progressListener);
}
finally
{
Util.close(reader);
}
}
@Override
public MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context, ProgressListener progressListener) throws Exception {
List<String> names = new ArrayList<String>();
names.add("id");
names.add("includeEpisodes");
List<Object> values = new ArrayList<Object>();
values.add(podcastChannelId);
values.add("true");
// TODO
Reader reader = getReader(context, progressListener, "getPodcasts", null, names,values);
//Reader reader = GetPodcastEpisodesTestReaderProvider.getReader();
try {
return new PodcastEpisodeParser(context).parse(reader, progressListener);
}
finally
{
Util.close(reader);
}
}
@Override
public List<Playlist> getPlaylists(boolean refresh,
Context context,
@ -618,7 +584,47 @@ public class RESTMusicService implements MusicService
checkResponseSuccessful(response);
}
@Override
@Override
public List<PodcastsChannel> getPodcastsChannels(boolean refresh,
Context context,
ProgressListener progressListener)
throws Exception {
updateProgressListener(progressListener, R.string.parser_reading);
Response<GetPodcastsResponse> response = subsonicAPIClient.getApi()
.getPodcasts(false, null).execute();
checkResponseSuccessful(response);
return APIPodcastConverter.toDomainEntitiesList(response.body().getPodcastChannels());
}
@Override
public MusicDirectory getPodcastEpisodes(String podcastChannelId,
Context context,
ProgressListener progressListener) throws Exception {
if (podcastChannelId == null) {
throw new IllegalArgumentException("Podcast channel id is null!");
}
updateProgressListener(progressListener, R.string.parser_reading);
Response<GetPodcastsResponse> response = subsonicAPIClient.getApi()
.getPodcasts(true, Long.valueOf(podcastChannelId)).execute();
checkResponseSuccessful(response);
List<MusicDirectoryChild> podcastEntries = response.body().getPodcastChannels().get(0)
.getEpisodeList();
MusicDirectory musicDirectory = new MusicDirectory();
for (MusicDirectoryChild podcastEntry : podcastEntries) {
if (!"skipped".equals(podcastEntry.getStatus()) &&
!"error".equals(podcastEntry.getStatus())) {
MusicDirectory.Entry entry = APIMusicDirectoryConverter.toDomainEntity(podcastEntry);
entry.setTrack(null);
musicDirectory.addChild(entry);
}
}
return musicDirectory;
}
@Override
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception
{
checkServerVersion(context, "1.2", "Lyrics not supported.");

View File

@ -1,143 +0,0 @@
/*
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 org.moire.ultrasonic.service.parser;
import android.content.Context;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.domain.PodcastEpisode;
import org.moire.ultrasonic.domain.PodcastsChannel;
import org.moire.ultrasonic.util.ProgressListener;
import org.xmlpull.v1.XmlPullParser;
import java.io.Reader;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @author Sindre Mehus
*/
public class PodcastEpisodeParser extends AbstractParser
{
public PodcastEpisodeParser(Context context)
{
super(context);
}
public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception
{
MusicDirectory musicDirectory = new MusicDirectory();
SortedMap<Date,MusicDirectory.Entry> sortedEntries = new TreeMap<Date, MusicDirectory.Entry>();
Locale currentLocale = getContext().getResources().getConfiguration().locale;
DateFormat shortDateFormat = DateFormat.getDateTimeInstance(
DateFormat.SHORT,
DateFormat.SHORT, currentLocale);
DateFormat parseDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
updateProgress(progressListener, R.string.parser_reading);
init(reader);
int eventType;
do
{
eventType = nextParseEvent();
if (eventType == XmlPullParser.START_TAG)
{
String tag = getElementName();
if ("episode".equals(tag))
{
String status = get("status");
if (!"skipped".equals(status) && !"error".equals(status)) {
MusicDirectory.Entry entry = new MusicDirectory.Entry();
String streamId = get("streamId");
entry.setId(streamId);
entry.setIsDirectory(Boolean.parseBoolean(get("isDir")));
entry.setIsVideo(Boolean.parseBoolean(get("isVideo")));
entry.setType(get("type"));
entry.setPath(get("path"));
entry.setSuffix(get("suffix"));
String size = get("size");
if (size != null) {
entry.setSize(Long.parseLong(size));
}
entry.setCoverArt(get("coverArt"));
entry.setAlbum(get("album"));
entry.setTitle(get("title"));
entry.setAlbumId(get("albumId"));
entry.setArtist(get("artist"));
entry.setArtistId(get("artistId"));
String bitRate = get("bitRate");
if (bitRate != null) {
entry.setBitRate(Integer.parseInt(get("bitRate")));
}
entry.setContentType(get("contentType"));
String duration = get("duration");
if (duration != null) {
entry.setDuration(Long.parseLong(duration));
}
entry.setGenre(get("genre"));
entry.setParent(get("parent"));
entry.setCreated("created");
String publishDate = get("publishDate");
if (publishDate != null) {
try {
Date publishDateDate = parseDateFormat.parse(publishDate);
entry.setArtist(shortDateFormat.format(publishDateDate));
sortedEntries.put(publishDateDate, entry);
} catch (Exception e) {
// nothing to do
}
}
}
}
else if ("error".equals(tag))
{
handleError();
}
}
} while (eventType != XmlPullParser.END_DOCUMENT);
validate();
updateProgress(progressListener, R.string.parser_reading_done);
for (Date pubDate : sortedEntries.keySet()) {
musicDirectory.addFirst(sortedEntries.get(pubDate));
}
return musicDirectory;
}
}

View File

@ -1,81 +0,0 @@
/*
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 org.moire.ultrasonic.service.parser;
import android.content.Context;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.domain.Playlist;
import org.moire.ultrasonic.domain.PodcastsChannel;
import org.moire.ultrasonic.util.ProgressListener;
import org.moire.ultrasonic.view.PlaylistAdapter;
import org.xmlpull.v1.XmlPullParser;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
/**
* @author Sindre Mehus
*/
public class PodcastsChannelsParser extends AbstractParser
{
public PodcastsChannelsParser(Context context)
{
super(context);
}
public List<PodcastsChannel> parse(Reader reader, ProgressListener progressListener) throws Exception
{
updateProgress(progressListener, R.string.parser_reading);
init(reader);
List<PodcastsChannel> result = new ArrayList<PodcastsChannel>();
int eventType;
do
{
eventType = nextParseEvent();
if (eventType == XmlPullParser.START_TAG)
{
String tag = getElementName();
if ("channel".equals(tag))
{
String id = get("id");
String title = get("title");
String url = get("url");
String description = get("description");
String status = get("status");
result.add(new PodcastsChannel(id,title, url,description,status));
}
else if ("error".equals(tag))
{
handleError();
}
}
} while (eventType != XmlPullParser.END_DOCUMENT);
validate();
updateProgress(progressListener, R.string.parser_reading_done);
return result;
}
}

View File

@ -5,8 +5,15 @@ package org.moire.ultrasonic.data
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
import org.moire.ultrasonic.domain.MusicDirectory
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Locale
import org.moire.ultrasonic.api.subsonic.models.MusicDirectory as APIMusicDirectory
internal val dateFormat: DateFormat by lazy {
SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
}
fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry = MusicDirectory.Entry().apply {
id = this@toDomainEntity.id.toString()
parent = this@toDomainEntity.parent.toString()
@ -33,6 +40,12 @@ fun MusicDirectoryChild.toDomainEntity(): MusicDirectory.Entry = MusicDirectory.
starred = this@toDomainEntity.starred != null
discNumber = this@toDomainEntity.discNumber
type = this@toDomainEntity.type
if (this@toDomainEntity.streamId >= 0) {
id = this@toDomainEntity.streamId.toString()
}
if (this@toDomainEntity.publishDate != null) {
artist = dateFormat.format(this@toDomainEntity.publishDate!!.time)
}
}
fun APIMusicDirectory.toDomainEntity(): MusicDirectory = MusicDirectory().apply {

View File

@ -0,0 +1,13 @@
// Converts podcasts entities from [org.moire.ultrasonic.api.subsonic.SubsonicAPIClient]
// to app domain entities.
@file:JvmName("APIPodcastConverter")
package org.moire.ultrasonic.data
import org.moire.ultrasonic.api.subsonic.models.PodcastChannel
import org.moire.ultrasonic.domain.PodcastsChannel
fun PodcastChannel.toDomainEntity(): PodcastsChannel = PodcastsChannel(
this.id.toString(), this.title, this.url, this.description, this.status)
fun List<PodcastChannel>.toDomainEntitiesList(): List<PodcastsChannel> = this
.map { it.toDomainEntity() }

View File

@ -69,4 +69,17 @@ class APIMusicDirectoryConverterTest {
type `should equal to` entity.type
}
}
@Test
fun `Should convert MusicDirectoryChild podact entity`() {
val entity = MusicDirectoryChild(id = 584, streamId = 394,
artist = "some-artist", publishDate = Calendar.getInstance())
val convertedEntity = entity.toDomainEntity()
with(convertedEntity) {
id `should equal to` entity.streamId.toString()
artist `should equal to` dateFormat.format(entity.publishDate?.time)
}
}
}

View File

@ -0,0 +1,44 @@
@file:Suppress("IllegalIdentifier")
package org.moire.ultrasonic.data
import org.amshove.kluent.`should equal to`
import org.amshove.kluent.`should equal`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.models.PodcastChannel
/**
* Unit test for extension functions in [APIPodcastConverter.kt] file.
*/
class APIPodcastConverterTest {
@Test
fun `Should convert podcast channel entity to domain entity`() {
val entity = PodcastChannel(id = 452L, url = "some-url", title = "some-title",
description = "some-description", coverArt = "cA", originalImageUrl = "image-url",
status = "podcast-status", errorMessage = "some-error-message")
val converterEntity = entity.toDomainEntity()
with(converterEntity) {
id = entity.id.toString()
description = entity.description
status = entity.status
title = entity.title
url = entity.url
}
}
@Test
fun `Should convert list of podcasts channels to domain entites list`() {
val entitiesList = listOf(
PodcastChannel(id = 932L, title = "title1"),
PodcastChannel(id = 12L, title = "title2"))
val converted = entitiesList.toDomainEntitiesList()
with(converted) {
size `should equal to` entitiesList.size
this[0] `should equal` entitiesList[0].toDomainEntity()
}
}
}