mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-13 10:10:54 +01:00
Merge pull request #46 from ultrasonic/add-get-podcasts-episodes
Add get podcasts episodes
This commit is contained in:
commit
59ed898b23
@ -0,0 +1,82 @@
|
||||
package org.moire.ultrasonic.api.subsonic
|
||||
|
||||
import org.amshove.kluent.`should equal to`
|
||||
import org.amshove.kluent.`should equal`
|
||||
import org.amshove.kluent.`should not be`
|
||||
import org.junit.Test
|
||||
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
|
||||
|
||||
/**
|
||||
* Integration test for [SubsonicAPIClient] for getPodcasts call.
|
||||
*/
|
||||
class SubsonicApiGetPodcastsTest : SubsonicAPIClientTest() {
|
||||
@Test
|
||||
fun `Should handle error response`() {
|
||||
val response = checkErrorCallParsed(mockWebServerRule) {
|
||||
client.api.getPodcasts().execute()
|
||||
}
|
||||
|
||||
response.podcastChannels `should not be` null
|
||||
response.podcastChannels `should equal` emptyList()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should handle ok response`() {
|
||||
mockWebServerRule.enqueueResponse("get_podcasts_ok.json")
|
||||
|
||||
val response = client.api.getPodcasts().execute()
|
||||
|
||||
assertResponseSuccessful(response)
|
||||
val podcastChannelsList = response.body().podcastChannels
|
||||
podcastChannelsList.size `should equal to` 1
|
||||
with(podcastChannelsList[0]) {
|
||||
id `should equal to` 2
|
||||
url `should equal to` "http://feeds.codenewbie.org/cnpodcast.xml"
|
||||
title `should equal to` "CodeNewbie"
|
||||
description `should equal to` "Stories and interviews from people on their coding journey."
|
||||
coverArt `should equal to` "pod-2"
|
||||
originalImageUrl `should equal to` "http://codenewbie.blubrry.com/wp-content/uploads/powerpress/220808.jpg"
|
||||
status `should equal to` "completed"
|
||||
errorMessage `should equal to` ""
|
||||
episodeList.size `should equal to` 10
|
||||
episodeList[0] `should equal` MusicDirectoryChild(id = 148, parent = 9959, isDir = false,
|
||||
title = "S1:EP3 – How to teach yourself computer science (Vaidehi Joshi)",
|
||||
album = "CodeNewbie", artist = "podcasts", coverArt = "9959",
|
||||
size = 38274221, contentType = "audio/mpeg", suffix = "mp3",
|
||||
duration = 2397, bitRate = 128, isVideo = false, playCount = 0,
|
||||
created = parseDate("2017-08-30T09:33:39.000Z"), type = "podcast",
|
||||
streamId = 9982, channelId = 2,
|
||||
description = "Vaidehi decided to take on a year-long challenge. " +
|
||||
"She'd pick a computer science topic every week, do tons of research " +
|
||||
"and write a technical blog post explaining it in simple terms and " +
|
||||
"beautiful illustrations. And then she actually did it. She tells us " +
|
||||
"about her project, basecs, how it's changed her as a developer, and " +
|
||||
"how she handles the trolls and negativity from people who don't " +
|
||||
"appreciate her work. Show Notes Technical Writer position at " +
|
||||
"CodeNewbie basecs 100 Days of Code Conway's Game of Life Hexes and " +
|
||||
"Other Magical Numbers (Vaidehi's blog post) Bits, Bytes, Building " +
|
||||
"With Binary (Vaidehi's blog post) Rust",
|
||||
status = "completed", publishDate = parseDate("2017-08-29T00:01:01.000Z"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should pass include episodes in request`() {
|
||||
val includeEpisodes = true
|
||||
|
||||
mockWebServerRule.assertRequestParam(responseResourceName = "get_podcasts_ok.json",
|
||||
expectedParam = "includeEpisodes=$includeEpisodes") {
|
||||
client.api.getPodcasts(includeEpisodes = includeEpisodes).execute()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should pass id in request param`() {
|
||||
val id = 249L
|
||||
|
||||
mockWebServerRule.assertRequestParam(responseResourceName = "get_podcasts_ok.json",
|
||||
expectedParam = "id=$id") {
|
||||
client.api.getPodcasts(id = id).execute()
|
||||
}
|
||||
}
|
||||
}
|
154
subsonic-api/src/integrationTest/resources/get_podcasts_ok.json
Normal file
154
subsonic-api/src/integrationTest/resources/get_podcasts_ok.json
Normal file
@ -0,0 +1,154 @@
|
||||
{
|
||||
"subsonic-response" : {
|
||||
"status" : "ok",
|
||||
"version" : "1.15.0",
|
||||
"podcasts" : {
|
||||
"channel" : [ {
|
||||
"id" : "2",
|
||||
"url" : "http://feeds.codenewbie.org/cnpodcast.xml",
|
||||
"title" : "CodeNewbie",
|
||||
"description" : "Stories and interviews from people on their coding journey.",
|
||||
"coverArt" : "pod-2",
|
||||
"originalImageUrl" : "http://codenewbie.blubrry.com/wp-content/uploads/powerpress/220808.jpg",
|
||||
"status" : "completed",
|
||||
"episode" : [ {
|
||||
"id" : "148",
|
||||
"parent" : "9959",
|
||||
"isDir" : false,
|
||||
"title" : "S1:EP3 – How to teach yourself computer science (Vaidehi Joshi)",
|
||||
"album" : "CodeNewbie",
|
||||
"artist" : "podcasts",
|
||||
"coverArt" : "9959",
|
||||
"size" : 38274221,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 2397,
|
||||
"bitRate" : 128,
|
||||
"isVideo" : false,
|
||||
"playCount" : 0,
|
||||
"created" : "2017-08-30T09:33:39.000Z",
|
||||
"type" : "podcast",
|
||||
"streamId" : "9982",
|
||||
"channelId" : "2",
|
||||
"description" : "Vaidehi decided to take on a year-long challenge. She'd pick a computer science topic every week, do tons of research and write a technical blog post explaining it in simple terms and beautiful illustrations. And then she actually did it. She tells us about her project, basecs, how it's changed her as a developer, and how she handles the trolls and negativity from people who don't appreciate her work. Show Notes Technical Writer position at CodeNewbie basecs 100 Days of Code Conway's Game of Life Hexes and Other Magical Numbers (Vaidehi's blog post) Bits, Bytes, Building With Binary (Vaidehi's blog post) Rust",
|
||||
"status" : "completed",
|
||||
"publishDate" : "2017-08-29T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "147",
|
||||
"parent" : "9959",
|
||||
"isDir" : false,
|
||||
"title" : "S1:EP2 – Building community in a virtual world: Moderation tools in VR (Cameron Brown)",
|
||||
"album" : "CodeNewbie",
|
||||
"artist" : "podcasts",
|
||||
"coverArt" : "9959",
|
||||
"size" : 52657014,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 3298,
|
||||
"bitRate" : 128,
|
||||
"isVideo" : false,
|
||||
"playCount" : 0,
|
||||
"created" : "2017-08-25T19:32:53.000Z",
|
||||
"type" : "podcast",
|
||||
"streamId" : "9963",
|
||||
"channelId" : "2",
|
||||
"description" : "Rec Room is the most popular virtual reality game. It's a social space where you can play dodgeball, ping pong, darts and more with people from all over the world. But when you're inviting everyone to play, how do you make sure that everyone is safe? What happens when a player attacks someone? What does an attack even look like in a virtual world? Cameron Brown, Chief Creative Officer at Against Gravity, the creators of Rec Room, takes us through the world of social virtual reality and shows how they've designed a system to make their game a welcoming place for all. Show Links Incapsula (sponsor) Dice (sponsor) Technical Writer position at CodeNewbie HTC Vive Rec Room Rec Room's Code of Conduct",
|
||||
"status" : "completed",
|
||||
"publishDate" : "2017-08-24T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "146",
|
||||
"parent" : "9959",
|
||||
"isDir" : false,
|
||||
"title" : "S1:EP1 – Intro to Accessibility (Stephanie Slattery)",
|
||||
"album" : "CodeNewbie",
|
||||
"artist" : "podcasts",
|
||||
"coverArt" : "9959",
|
||||
"size" : 50708305,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 3176,
|
||||
"bitRate" : 128,
|
||||
"isVideo" : false,
|
||||
"playCount" : 0,
|
||||
"created" : "2017-08-16T19:32:53.000Z",
|
||||
"type" : "podcast",
|
||||
"streamId" : "9962",
|
||||
"channelId" : "2",
|
||||
"description" : "We kick off the first episode of our official first season with Stephanie Slattery, a front-end engineer who specializes in accessibility. She breaks down the world of accessibility, giving you the perfect introduction to this topic. She explains the five categories of disabilities, shows us how to implement suggestions from the Web Content Accessibility Guidelines, and shares why she’s so passionate about helping more people experience tech. Show Links Incapsula (sponsor) Hover (sponsor) An Introduction to Web Accessibility (Stephanie's Blog Post) Codeland, CodeNewbie's conference - April 21 & 22 in NYC NeoPets Dev Bootcamp Illinois Institute of Technology W3C WCAG (Web Content Accessibility Guidelines) ADA 1990 Rehabilitation Act of 1973",
|
||||
"status" : "completed",
|
||||
"publishDate" : "2017-08-16T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "0",
|
||||
"parent" : "9959",
|
||||
"isDir" : false,
|
||||
"title" : "Ep. 146 – Codeland - Mentorship, Technical Blogging, and Open Source Talks from Katrina Owen, Quincy Larson, and Nell Shamrell-Harrington",
|
||||
"album" : "CodeNewbie",
|
||||
"artist" : "podcasts",
|
||||
"coverArt" : "9959",
|
||||
"size" : 47779050,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 2993,
|
||||
"bitRate" : 128,
|
||||
"isVideo" : false,
|
||||
"playCount" : 0,
|
||||
"created" : "2017-08-12T18:40:06.000Z",
|
||||
"type" : "podcast",
|
||||
"streamId" : "9960",
|
||||
"channelId" : "2",
|
||||
"description" : "In our final episode of our Codeland mini-series, Katrina Owen shares what it really takes to get that mentor you've always wanted, Quincy Larson gives us his best practices for writing technical blog posts people will actually read, and Nell Shamrell-Harrington explores what it really takes for an open source project to be successful and what you should know as a future contributor. Show Links Flatiron School (sponsor) Incapsula (sponsor) Hover (sponsor) CodeNewbie YouTube channel Codeland, CodeNewbie's conference - April 21 & 22 in NYC Be Lucky—it’s an easy skill to learn by Richard Wiseman How to read Medium articles people will actually read Sample Contribution Guide Sample Testing Guide Travis CI Sample Code of Conduct Open Source Governance Continuous Integration (CI) System",
|
||||
"status" : "completed",
|
||||
"publishDate" : "2017-08-01T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "1",
|
||||
"isDir" : false,
|
||||
"title" : "Ep. 145 – Codeland - NYPL and Khan Academy talks from Courteney Ervin and Celia La",
|
||||
"channelId" : "2",
|
||||
"description" : "Courteney Ervin shares the ups and downs of building a product for one of the largest library systems in the world. Celia La walks us through the technical challenges (and solutions) of bringing Khan Academy's high quality content to people all over the world. Show Links Flatiron School (sponsor) Hover (sponsor) Incapsula (sponsor) CodeNewbie YouTube channel Codeland, CodeNewbie's conference - April 21 & 22 in NYC Khan Academy Memcached Git Version Control System New York Public Library Integrated Library System",
|
||||
"status" : "skipped",
|
||||
"publishDate" : "2017-07-25T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "2",
|
||||
"isDir" : false,
|
||||
"title" : "Ep. 144 – Codeland - Accessibility and Education talks from Sterling Walker and Maurice Rogers",
|
||||
"channelId" : "2",
|
||||
"description" : "We wrap up our community talks with Sterling’s story of her very first project at her first dev job: making the app accessible to two blind students. Maurice kicks off our education talks with the story of Abacus, his side project that became the learning system used by thousands of students in his country of Belize. Show Links Flatiron School (sponsor) Hover (sponsor)Incapsula (sponsor) CodeNewbie YouTube channel Codeland, CodeNewbie's conference - April 21 & 22 in NYC An Alphabet of Accessibility Issues Web Content Accessibility Guidelines (WCAG) Abacus Grails Java xkcd comic \"Standards\"",
|
||||
"status" : "skipped",
|
||||
"publishDate" : "2017-07-18T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "3",
|
||||
"isDir" : false,
|
||||
"title" : "Ep. 143 – Codeland - Community Talks from Valerie Woolard Srinivasan and Rapi Castillo",
|
||||
"channelId" : "2",
|
||||
"description" : "Valerie explores the importance of security in creating powerful and engaged communities, and breaks down three ways your code might be vulnerable. Rapi shares his story of creating a toy coding project in D3.js that sparked a movement and helped thousands of people become more politically engaged. Checkout the videos of these talks on the CodeNewbie YouTube channel. Show Links Flatiron School (sponsor) Hover (sponsor) Incapsula (sponsor) CodeNewbie YouTube channel Codeland, CodeNewbie's conference - April 21 & 22 in NYC Rapi's Talk [VIDEO] Valerie's Talk [VIDEO] Progressive Coders Network D3.js SQL mass assignment man in the middle SQL injection validating inputs sanitizing inputs strong parameters",
|
||||
"status" : "skipped",
|
||||
"publishDate" : "2017-07-06T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "4",
|
||||
"isDir" : false,
|
||||
"title" : "Ep. 142 – Codeland - Codeland - Mental Health talks from Michelle Morales and Greg Baugues",
|
||||
"channelId" : "2",
|
||||
"description" : "This episode features two talks on mental health that explore two very different sides of this important topic. Michelle’s talk is a technical showcase of how her research project uses open source tools to better diagnose depression. Greg shares his personal struggles with ADHD and bipolar disorder, and how important it is for us to openly talk about mental health. Show Links Flatiron School (sponsor) Hover (sponsor) Incapsula (sponsor) CodeNewbie YouTube channel Codeland, CodeNewbie's conference - April 21 & 22 in NYC CUNY Graduate Center Tom Insel's TED talk on depression Audio/Visual Emotion and Depression Recognition dataset DAIC-WOZ Database Covarep OpenFace OpenMM IBM Watson Speech to Text Natural Language Processing Machine Learning Feature Extraction Automatic Speech Recognition ZocDoc 718-312-8335 (Greg's mental health resource number)",
|
||||
"status" : "skipped",
|
||||
"publishDate" : "2017-06-28T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "5",
|
||||
"isDir" : false,
|
||||
"title" : "Ep. 141 – Codeland - Interview with NYC's first CTO (Minerva Tantoco)",
|
||||
"channelId" : "2",
|
||||
"description" : "When Minerva Tantoco was first offered the CTO position for New York City, she thought it was a prank. But in 2014, she became the city’s first Chief Technology Officer. She sits down with Codeland’s emcee, Nikhil Paul, to talk about how she started her long, impressive tech career, what programming looked like back her coding days, and how she hopes tech will transform cities for the better. Show Links Flatiron School (sponsor) Hover (sponsor) Incapsula (sponsor) CodeNewbie YouTube channel Mayor de Blasio’s announcement Codeland, CodeNewbie's conference - April 21 & 22 in NYC",
|
||||
"status" : "skipped",
|
||||
"publishDate" : "2017-06-21T00:01:01.000Z"
|
||||
}, {
|
||||
"id" : "6",
|
||||
"isDir" : false,
|
||||
"title" : "Ep. 140 – Codeland - Gaming and City Talks from Chris Algoo, Kate Rabinowitz, Eric Brelsford",
|
||||
"channelId" : "2",
|
||||
"description" : "Chris Algoo shares how he co-created “Breakup Squad,” the game where you have to keep two exes from getting back together. Kate Rabinowitz shows us how open data can help build powerful, insightful tools to better understand and improve your city. Eric Brelsford shares how he used mapping tools to help community members turn vacant lots into beautiful neighborhood spaces. Show Links Flatiron School (sponsor) Hover (sponsor) Incapsula (sponsor) Breakup Squad (trailer) Codeland, CodeNewbie's conference - April 21 & 22 in NYC CodeNewbie YouTube channel Unity Twine FMOD Open Game Art Freesound.org The Big List of Game Making Tools An Introduction to Statistical Learning Interactive Data Visualization for the Web Code for America Brigades DataKind Maptime NYC Urban Reviewer 596 Acres Living Lots NYC NYCommons Open Data R Python Tableau Carto D3 API",
|
||||
"status" : "skipped",
|
||||
"publishDate" : "2017-06-13T00:01:01.000Z"
|
||||
} ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,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
|
||||
@ -108,4 +109,8 @@ interface SubsonicAPIDefinition {
|
||||
@Query("public") public: Boolean? = null,
|
||||
@Query("songIdToAdd") songIdsToAdd: List<Long>? = null,
|
||||
@Query("songIndexToRemove") songIndexesToRemove: List<Int>? = null) : Call<SubsonicResponse>
|
||||
|
||||
@GET("getPodcasts.view")
|
||||
fun getPodcasts(@Query("includeEpisodes") includeEpisodes: Boolean? = null,
|
||||
@Query("id") id: Long? = null) : Call<GetPodcastsResponse>
|
||||
}
|
||||
|
@ -27,4 +27,9 @@ data class MusicDirectoryChild(val id: Long = -1L,
|
||||
val albumId: Long = -1,
|
||||
val artistId: Long = -1,
|
||||
val type: String = "",
|
||||
val starred: Calendar? = null)
|
||||
val starred: Calendar? = null,
|
||||
val streamId: Long = -1,
|
||||
val channelId: Long = -1,
|
||||
val description: String = "",
|
||||
val status: String = "",
|
||||
val publishDate: Calendar? = null)
|
||||
|
@ -0,0 +1,14 @@
|
||||
package org.moire.ultrasonic.api.subsonic.models
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
data class PodcastChannel(
|
||||
val id: Long = -1,
|
||||
val url: String = "",
|
||||
val title: String = "",
|
||||
val description: String = "",
|
||||
val coverArt: String = "",
|
||||
val originalImageUrl: String = "",
|
||||
val status: String = "",
|
||||
val errorMessage: String = "",
|
||||
@JsonProperty("episode") val episodeList: List<MusicDirectoryChild> = emptyList())
|
@ -0,0 +1,19 @@
|
||||
package org.moire.ultrasonic.api.subsonic.response
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||
import org.moire.ultrasonic.api.subsonic.SubsonicError
|
||||
import org.moire.ultrasonic.api.subsonic.models.PodcastChannel
|
||||
|
||||
class GetPodcastsResponse(
|
||||
status: Status,
|
||||
version: SubsonicAPIVersions,
|
||||
error: SubsonicError?) : SubsonicResponse(status, version, error) {
|
||||
@JsonProperty("podcasts") private val channelsWrapper = PodcastChannelWrapper()
|
||||
|
||||
val podcastChannels: List<PodcastChannel>
|
||||
get() = channelsWrapper.channelsList
|
||||
}
|
||||
|
||||
private class PodcastChannelWrapper(
|
||||
@JsonProperty("channel") val channelsList: List<PodcastChannel> = emptyList())
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
@ -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() }
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user