diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetBookmarksTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetBookmarksTest.kt new file mode 100644 index 00000000..ea66eef9 --- /dev/null +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetBookmarksTest.kt @@ -0,0 +1,46 @@ +package org.moire.ultrasonic.api.subsonic + +import org.amshove.kluent.`should equal to` +import org.amshove.kluent.`should equal` +import org.junit.Test +import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild + +/** + * Integration test for [SubsonicAPIDefinition.getBookmarks] call. + */ +class SubsonicApiGetBookmarksTest : SubsonicAPIClientTest() { + @Test + fun `Should handle error response`() { + val response = checkErrorCallParsed(mockWebServerRule) { + client.api.getBookmarks().execute() + } + + response.bookmarkList `should equal` emptyList() + } + + @Test + fun `Should handle ok response`() { + mockWebServerRule.enqueueResponse("get_bookmarks_ok.json") + + val response = client.api.getBookmarks().execute() + + assertResponseSuccessful(response) + response.body().bookmarkList.size `should equal to` 1 + with(response.body().bookmarkList[0]) { + position `should equal to` 107914 + username `should equal to` "CaptainEurope" + comment `should equal to` "Look at this" + created `should equal` parseDate("2017-11-18T15:22:22.144Z") + changed `should equal` parseDate("2017-11-18T15:22:22.144Z") + entry `should equal` MusicDirectoryChild(id = 10349, parent = 10342, + isDir = false, title = "Amerika", album = "Home of the Strange", + artist = "Young the Giant", track = 1, year = 2016, genre = "Indie Rock", + coverArt = "10342", size = 9628673, contentType = "audio/mpeg", + suffix = "mp3", duration = 240, bitRate = 320, + path = "Young the Giant/Home of the Strange/01 Amerika.mp3", + isVideo = false, playCount = 2, discNumber = 1, + created = parseDate("2017-11-01T17:46:52.000Z"), + albumId = 984, artistId = 571, type = "music") + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/get_bookmarks_ok.json b/subsonic-api/src/integrationTest/resources/get_bookmarks_ok.json new file mode 100644 index 00000000..deb9dec6 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/get_bookmarks_ok.json @@ -0,0 +1,40 @@ +{ + "subsonic-response" : { + "status" : "ok", + "version" : "1.15.0", + "bookmarks" : { + "bookmark" : [ { + "position" : 107914, + "username" : "CaptainEurope", + "comment" : "Look at this", + "created" : "2017-11-18T15:22:22.144Z", + "changed" : "2017-11-18T15:22:22.144Z", + "entry" : { + "id" : "10349", + "parent" : "10342", + "isDir" : false, + "title" : "Amerika", + "album" : "Home of the Strange", + "artist" : "Young the Giant", + "track" : 1, + "year" : 2016, + "genre" : "Indie Rock", + "coverArt" : "10342", + "size" : 9628673, + "contentType" : "audio/mpeg", + "suffix" : "mp3", + "duration" : 240, + "bitRate" : 320, + "path" : "Young the Giant/Home of the Strange/01 Amerika.mp3", + "isVideo" : false, + "playCount" : 2, + "discNumber" : 1, + "created" : "2017-11-01T17:46:52.000Z", + "albumId" : "984", + "artistId" : "571", + "type" : "music" + } + } ] + } + } +} \ No newline at end of file diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt index ae06b3ab..04bb260a 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIDefinition.kt @@ -3,6 +3,7 @@ package org.moire.ultrasonic.api.subsonic import okhttp3.ResponseBody import org.moire.ultrasonic.api.subsonic.models.AlbumListType import org.moire.ultrasonic.api.subsonic.models.JukeboxAction +import org.moire.ultrasonic.api.subsonic.response.BookmarksResponse import org.moire.ultrasonic.api.subsonic.response.ChatMessagesResponse import org.moire.ultrasonic.api.subsonic.response.GenresResponse import org.moire.ultrasonic.api.subsonic.response.GetAlbumList2Response @@ -220,4 +221,7 @@ interface SubsonicAPIDefinition { @GET("addChatMessage.view") fun addChatMessage(@Query("message") message: String): Call + + @GET("getBookmarks.view") + fun getBookmarks(): Call } diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/Bookmark.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/Bookmark.kt new file mode 100644 index 00000000..f527b068 --- /dev/null +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/Bookmark.kt @@ -0,0 +1,11 @@ +package org.moire.ultrasonic.api.subsonic.models + +import java.util.Calendar + +data class Bookmark( + val position: Long = 0, + val username: String = "", + val comment: String = "", + val created: Calendar? = null, + val changed: Calendar? = null, + val entry: MusicDirectoryChild = MusicDirectoryChild()) diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/BookmarksResponse.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/BookmarksResponse.kt new file mode 100644 index 00000000..4eaae226 --- /dev/null +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/BookmarksResponse.kt @@ -0,0 +1,18 @@ +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.Bookmark + +class BookmarksResponse( + status: Status, + version: SubsonicAPIVersions, + error: SubsonicError?) : SubsonicResponse(status, version, error) { + @JsonProperty("bookmarks") private val bookmarksWrapper = BookmarkWrapper() + + val bookmarkList: List get() = bookmarksWrapper.bookmarkList +} + +internal class BookmarkWrapper( + @JsonProperty("bookmark") val bookmarkList: List = emptyList()) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/domain/Bookmark.java b/ultrasonic/src/main/java/org/moire/ultrasonic/domain/Bookmark.java index f3a4c84b..043e0ec5 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/domain/Bookmark.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/domain/Bookmark.java @@ -3,10 +3,7 @@ package org.moire.ultrasonic.domain; import org.moire.ultrasonic.domain.MusicDirectory.Entry; import java.io.Serializable; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Date; -import java.util.Locale; public class Bookmark implements Serializable { @@ -51,53 +48,21 @@ public class Bookmark implements Serializable this.comment = comment; } - public Date getCreated() - { - return created; - } + public Date getCreated() { + return created; + } - public void setCreated(String created) - { - if (created != null) - { - try - { - this.created = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).parse(created); - } - catch (ParseException e) - { - this.created = null; - } - } - else - { - this.created = null; - } - } + public void setCreated(Date created) { + this.created = created; + } - public Date getChanged() - { - return changed; - } + public Date getChanged() { + return changed; + } - public void setChanged(String changed) - { - if (changed != null) - { - try - { - this.changed = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).parse(changed); - } - catch (ParseException e) - { - this.changed = null; - } - } - else - { - this.changed = null; - } - } + public void setChanged(Date changed) { + this.changed = changed; + } public Entry getEntry() { @@ -108,4 +73,34 @@ public class Bookmark implements Serializable { this.entry = entry; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Bookmark bookmark = (Bookmark) o; + + if (position != bookmark.position) return false; + if (username != null ? !username.equals(bookmark.username) : bookmark.username != null) + return false; + if (comment != null ? !comment.equals(bookmark.comment) : bookmark.comment != null) + return false; + if (created != null ? !created.equals(bookmark.created) : bookmark.created != null) + return false; + if (changed != null ? !changed.equals(bookmark.changed) : bookmark.changed != null) + return false; + return entry != null ? entry.equals(bookmark.entry) : bookmark.entry == null; + } + + @Override + public int hashCode() { + int result = position; + result = 31 * result + (username != null ? username.hashCode() : 0); + result = 31 * result + (comment != null ? comment.hashCode() : 0); + result = 31 * result + (created != null ? created.hashCode() : 0); + result = 31 * result + (changed != null ? changed.hashCode() : 0); + result = 31 * result + (entry != null ? entry.hashCode() : 0); + return result; + } } 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 17fc91c8..31c4afb9 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -58,6 +58,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient; import org.moire.ultrasonic.api.subsonic.models.AlbumListType; import org.moire.ultrasonic.api.subsonic.models.JukeboxAction; import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild; +import org.moire.ultrasonic.api.subsonic.response.BookmarksResponse; import org.moire.ultrasonic.api.subsonic.response.ChatMessagesResponse; import org.moire.ultrasonic.api.subsonic.response.GenresResponse; import org.moire.ultrasonic.api.subsonic.response.GetAlbumList2Response; @@ -87,6 +88,7 @@ import org.moire.ultrasonic.api.subsonic.response.StreamResponse; import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse; import org.moire.ultrasonic.data.APIAlbumConverter; import org.moire.ultrasonic.data.APIArtistConverter; +import org.moire.ultrasonic.data.APIBookmarkConverter; import org.moire.ultrasonic.data.APIChatMessageConverter; import org.moire.ultrasonic.data.APIIndexesConverter; import org.moire.ultrasonic.data.APIJukeboxConverter; @@ -114,7 +116,6 @@ import org.moire.ultrasonic.domain.SearchResult; import org.moire.ultrasonic.domain.Share; import org.moire.ultrasonic.domain.UserInfo; import org.moire.ultrasonic.domain.Version; -import org.moire.ultrasonic.service.parser.BookmarkParser; import org.moire.ultrasonic.service.parser.ErrorParser; import org.moire.ultrasonic.service.parser.MusicDirectoryParser; import org.moire.ultrasonic.service.parser.SubsonicRESTException; @@ -1272,22 +1273,16 @@ public class RESTMusicService implements MusicService checkResponseSuccessful(response); } - @Override - public List getBookmarks(Context context, ProgressListener progressListener) throws Exception - { - checkServerVersion(context, "1.9", "Bookmarks not supported."); + @Override + public List getBookmarks(Context context, + ProgressListener progressListener) throws Exception { + updateProgressListener(progressListener, R.string.parser_reading); + Response response = subsonicAPIClient.getApi() + .getBookmarks().execute(); + checkResponseSuccessful(response); - Reader reader = getReader(context, progressListener, "getBookmarks", null); - - try - { - return new BookmarkParser(context).parse(reader, progressListener); - } - finally - { - Util.close(reader); - } - } + return APIBookmarkConverter.toDomainEntitiesList(response.body().getBookmarkList()); + } @Override public void createBookmark(String id, int position, Context context, ProgressListener progressListener) throws Exception diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/BookmarkParser.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/BookmarkParser.java deleted file mode 100644 index 2c51d9ad..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/parser/BookmarkParser.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.moire.ultrasonic.service.parser; - -import android.content.Context; - -import org.moire.ultrasonic.R; -import org.moire.ultrasonic.domain.Bookmark; -import org.moire.ultrasonic.util.ProgressListener; - -import org.xmlpull.v1.XmlPullParser; - -import java.io.Reader; -import java.util.ArrayList; -import java.util.List; - -/** - * @author Joshua Bahnsen - */ -public class BookmarkParser extends MusicDirectoryEntryParser -{ - - public BookmarkParser(Context context) - { - super(context); - } - - public List parse(Reader reader, ProgressListener progressListener) throws Exception - { - - updateProgress(progressListener, R.string.parser_reading); - init(reader); - - List dir = new ArrayList(); - Bookmark bookmark = null; - int eventType; - - do - { - eventType = nextParseEvent(); - - if (eventType == XmlPullParser.START_TAG) - { - String name = getElementName(); - - if ("bookmark".equals(name)) - { - bookmark = new Bookmark(); - bookmark.setChanged(get("changed")); - bookmark.setCreated(get("created")); - bookmark.setComment(get("comment")); - bookmark.setPosition(getInteger("position")); - bookmark.setUsername(get("username")); - } - else if ("entry".equals(name)) - { - if (bookmark != null) - { - bookmark.setEntry(parseEntry(null, false, bookmark.getPosition())); - dir.add(bookmark); - } - } - else if ("error".equals(name)) - { - handleError(); - } - } - } while (eventType != XmlPullParser.END_DOCUMENT); - - validate(); - updateProgress(progressListener, R.string.parser_reading_done); - - return dir; - } -} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIBookmarkConverter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIBookmarkConverter.kt new file mode 100644 index 00000000..df9a127e --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/APIBookmarkConverter.kt @@ -0,0 +1,17 @@ +// Contains helper functions to convert api Bookmark entity to domain entity +@file:JvmName("APIBookmarkConverter") +package org.moire.ultrasonic.data + +import org.moire.ultrasonic.domain.Bookmark +import org.moire.ultrasonic.api.subsonic.models.Bookmark as ApiBookmark + +fun ApiBookmark.toDomainEntity(): Bookmark = Bookmark().apply { + position = this@toDomainEntity.position.toInt() + username = this@toDomainEntity.username + comment = this@toDomainEntity.comment + created = this@toDomainEntity.created?.time + changed = this@toDomainEntity.changed?.time + entry = this@toDomainEntity.entry.toDomainEntity() +} + +fun List.toDomainEntitiesList(): List = map { it.toDomainEntity() } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIBookmarkConverterTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIBookmarkConverterTest.kt new file mode 100644 index 00000000..3e2527b3 --- /dev/null +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/data/APIBookmarkConverterTest.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.Bookmark +import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild +import java.util.Calendar + +/** + * Unit test for function that converts [Bookmark] api entity to domain. + */ +class APIBookmarkConverterTest { + @Test + fun `Should convert to domain entity`() { + val entity = Bookmark(412313L, "Awesemo", "Nice", Calendar.getInstance(), + Calendar.getInstance(), MusicDirectoryChild(id = 12333)) + + val domainEntity = entity.toDomainEntity() + + with(domainEntity) { + position `should equal to` entity.position.toInt() + username `should equal to` entity.username + comment `should equal to` entity.comment + created `should equal` entity.created?.time + changed `should equal` entity.changed?.time + entry `should equal` entity.entry.toDomainEntity() + } + } + + @Test + fun `Should convert list of entities to domain entities`() { + val entitiesList = listOf(Bookmark(443L), Bookmark(444L)) + + val domainEntitiesList = entitiesList.toDomainEntitiesList() + + domainEntitiesList.size `should equal to` entitiesList.size + domainEntitiesList.forEachIndexed({ index, bookmark -> + bookmark `should equal` entitiesList[index].toDomainEntity() + }) + } +}