diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastsChannel.java b/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastsChannel.java index b525c7d6..a575c8e5 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastsChannel.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/domain/PodcastsChannel.java @@ -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; + } } \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java index 8265695e..155ed03f 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -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 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 names = new ArrayList(); - names.add("id"); - names.add("includeEpisodes"); - List values = new ArrayList(); - 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 getPlaylists(boolean refresh, Context context, @@ -618,7 +584,47 @@ public class RESTMusicService implements MusicService checkResponseSuccessful(response); } - @Override + @Override + public List getPodcastsChannels(boolean refresh, + Context context, + ProgressListener progressListener) + throws Exception { + updateProgressListener(progressListener, R.string.parser_reading); + Response 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 response = subsonicAPIClient.getApi() + .getPodcasts(true, Long.valueOf(podcastChannelId)).execute(); + checkResponseSuccessful(response); + + List 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."); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java deleted file mode 100644 index 5b4c56c3..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastEpisodeParser.java +++ /dev/null @@ -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 . - - 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 sortedEntries = new TreeMap(); - - 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; - } -} - diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastsChannelsParser.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastsChannelsParser.java deleted file mode 100644 index 8c10a0cb..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/PodcastsChannelsParser.java +++ /dev/null @@ -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 . - - 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 parse(Reader reader, ProgressListener progressListener) throws Exception - { - - updateProgress(progressListener, R.string.parser_reading); - init(reader); - - List result = new ArrayList(); - 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; - } - -} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIMusicDirectoryConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIMusicDirectoryConverter.kt index ec702d08..b5e98322 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIMusicDirectoryConverter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIMusicDirectoryConverter.kt @@ -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 { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIPodcastConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIPodcastConverter.kt new file mode 100644 index 00000000..3b79dccd --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIPodcastConverter.kt @@ -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.toDomainEntitiesList(): List = this + .map { it.toDomainEntity() } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIMusicDirectoryConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIMusicDirectoryConverterTest.kt index 52d53810..dbbd6673 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIMusicDirectoryConverterTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIMusicDirectoryConverterTest.kt @@ -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) + } + } } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIPodcastConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIPodcastConverterTest.kt new file mode 100644 index 00000000..7a0a71f6 --- /dev/null +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIPodcastConverterTest.kt @@ -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() + } + } +}