Merge pull request #87 from ultrasonic/api-versions-check

Api versions check
This commit is contained in:
Yahor Berdnikau 2017-12-10 10:54:12 +01:00 committed by GitHub
commit 0516ebcc20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 385 additions and 85 deletions

View File

@ -16,7 +16,7 @@ import java.util.TimeZone
const val USERNAME = "some-user"
const val PASSWORD = "some-password"
val CLIENT_VERSION = SubsonicAPIVersions.V1_13_0
val CLIENT_VERSION = SubsonicAPIVersions.V1_16_0
const val CLIENT_ID = "test-client"
val dateFormat by lazy(LazyThreadSafetyMode.NONE, {

View File

@ -0,0 +1,11 @@
package org.moire.ultrasonic.api.subsonic
import java.io.IOException
/**
* Special [IOException] to indicate that called api is not yet supported
* by current server api version.
*/
class ApiNotSupportedException(
serverApiVersion: SubsonicAPIVersions)
: IOException("Server api $serverApiVersion does not support this call")

View File

@ -0,0 +1,303 @@
package org.moire.ultrasonic.api.subsonic
import okhttp3.ResponseBody
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_11_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_12_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_14_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_2_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_3_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_4_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_5_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_6_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_7_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_8_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_9_0
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
import org.moire.ultrasonic.api.subsonic.response.GetAlbumListResponse
import org.moire.ultrasonic.api.subsonic.response.GetAlbumResponse
import org.moire.ultrasonic.api.subsonic.response.GetArtistResponse
import org.moire.ultrasonic.api.subsonic.response.GetArtistsResponse
import org.moire.ultrasonic.api.subsonic.response.GetLyricsResponse
import org.moire.ultrasonic.api.subsonic.response.GetPlaylistsResponse
import org.moire.ultrasonic.api.subsonic.response.GetPodcastsResponse
import org.moire.ultrasonic.api.subsonic.response.GetRandomSongsResponse
import org.moire.ultrasonic.api.subsonic.response.GetSongsByGenreResponse
import org.moire.ultrasonic.api.subsonic.response.GetStarredResponse
import org.moire.ultrasonic.api.subsonic.response.GetStarredTwoResponse
import org.moire.ultrasonic.api.subsonic.response.GetUserResponse
import org.moire.ultrasonic.api.subsonic.response.JukeboxResponse
import org.moire.ultrasonic.api.subsonic.response.SearchThreeResponse
import org.moire.ultrasonic.api.subsonic.response.SearchTwoResponse
import org.moire.ultrasonic.api.subsonic.response.SharesResponse
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
import org.moire.ultrasonic.api.subsonic.response.VideosResponse
import retrofit2.Call
/**
* Special wrapper for [SubsonicAPIDefinition] that checks if [currentApiVersion] is suitable
* for this call.
*/
internal class ApiVersionCheckWrapper(
val api: SubsonicAPIDefinition,
var currentApiVersion: SubsonicAPIVersions) : SubsonicAPIDefinition by api {
override fun getArtists(musicFolderId: Long?): Call<GetArtistsResponse> {
checkVersion(V1_8_0)
return api.getArtists(musicFolderId)
}
override fun star(id: Long?, albumId: Long?, artistId: Long?): Call<SubsonicResponse> {
checkVersion(V1_8_0)
return api.star(id, albumId, artistId)
}
override fun unstar(id: Long?, albumId: Long?, artistId: Long?): Call<SubsonicResponse> {
checkVersion(V1_8_0)
return api.unstar(id, albumId, artistId)
}
override fun getArtist(id: Long): Call<GetArtistResponse> {
checkVersion(V1_8_0)
return api.getArtist(id)
}
override fun getAlbum(id: Long): Call<GetAlbumResponse> {
checkVersion(V1_8_0)
return api.getAlbum(id)
}
override fun search2(query: String,
artistCount: Int?,
artistOffset: Int?,
albumCount: Int?,
albumOffset: Int?,
songCount: Int?,
musicFolderId: Long?): Call<SearchTwoResponse> {
checkVersion(V1_4_0)
checkParamVersion(musicFolderId, V1_12_0)
return api.search2(query, artistCount, artistOffset, albumCount, albumOffset, songCount,
musicFolderId)
}
override fun search3(query: String,
artistCount: Int?,
artistOffset: Int?,
albumCount: Int?,
albumOffset: Int?,
songCount: Int?,
musicFolderId: Long?): Call<SearchThreeResponse> {
checkVersion(V1_8_0)
checkParamVersion(musicFolderId, V1_12_0)
return api.search3(query, artistCount, artistOffset, albumCount, albumOffset,
songCount, musicFolderId)
}
override fun getPlaylists(username: String?): Call<GetPlaylistsResponse> {
checkParamVersion(username, V1_8_0)
return api.getPlaylists(username)
}
override fun createPlaylist(id: Long?,
name: String?,
songIds: List<Long>?): Call<SubsonicResponse> {
checkVersion(V1_2_0)
return api.createPlaylist(id, name, songIds)
}
override fun deletePlaylist(id: Long): Call<SubsonicResponse> {
checkVersion(V1_2_0)
return api.deletePlaylist(id)
}
override fun updatePlaylist(id: Long,
name: String?,
comment: String?,
public: Boolean?,
songIdsToAdd: List<Long>?,
songIndexesToRemove: List<Int>?): Call<SubsonicResponse> {
checkVersion(V1_8_0)
return api.updatePlaylist(id, name, comment, public, songIdsToAdd, songIndexesToRemove)
}
override fun getPodcasts(includeEpisodes: Boolean?, id: Long?): Call<GetPodcastsResponse> {
checkVersion(V1_6_0)
checkParamVersion(includeEpisodes, V1_9_0)
checkParamVersion(id, V1_9_0)
return api.getPodcasts(includeEpisodes, id)
}
override fun getLyrics(artist: String?, title: String?): Call<GetLyricsResponse> {
checkVersion(V1_2_0)
return api.getLyrics(artist, title)
}
override fun scrobble(id: String, time: Long?, submission: Boolean?): Call<SubsonicResponse> {
checkVersion(V1_5_0)
checkParamVersion(time, V1_8_0)
return api.scrobble(id, time, submission)
}
override fun getAlbumList(type: AlbumListType,
size: Int?,
offset: Int?,
fromYear: Int?,
toYear: Int?,
genre: String?,
musicFolderId: Long?): Call<GetAlbumListResponse> {
checkVersion(V1_2_0)
checkParamVersion(musicFolderId, V1_11_0)
return api.getAlbumList(type, size, offset, fromYear, toYear, genre, musicFolderId)
}
override fun getAlbumList2(type: AlbumListType,
size: Int?,
offset: Int?,
fromYear: Int?,
toYear: Int?,
genre: String?,
musicFolderId: Long?): Call<GetAlbumList2Response> {
checkVersion(V1_8_0)
checkParamVersion(musicFolderId, V1_12_0)
return api.getAlbumList2(type, size, offset, fromYear, toYear, genre, musicFolderId)
}
override fun getRandomSongs(size: Int?,
genre: String?,
fromYear: Int?,
toYear: Int?,
musicFolderId: Long?): Call<GetRandomSongsResponse> {
checkVersion(V1_2_0)
return api.getRandomSongs(size, genre, fromYear, toYear, musicFolderId)
}
override fun getStarred(musicFolderId: Long?): Call<GetStarredResponse> {
checkVersion(V1_8_0)
checkParamVersion(musicFolderId, V1_12_0)
return api.getStarred(musicFolderId)
}
override fun getStarred2(musicFolderId: Long?): Call<GetStarredTwoResponse> {
checkVersion(V1_8_0)
checkParamVersion(musicFolderId, V1_12_0)
return api.getStarred2(musicFolderId)
}
override fun stream(id: String,
maxBitRate: Int?,
format: String?,
timeOffset: Int?,
videoSize: String?,
estimateContentLength: Boolean?,
converted: Boolean?,
offset: Long?): Call<ResponseBody> {
checkParamVersion(maxBitRate, V1_2_0)
checkParamVersion(format, V1_6_0)
checkParamVersion(videoSize, V1_6_0)
checkParamVersion(estimateContentLength, V1_8_0)
checkParamVersion(converted, V1_14_0)
return api.stream(id, maxBitRate, format, timeOffset, videoSize,
estimateContentLength, converted)
}
override fun jukeboxControl(action: JukeboxAction,
index: Int?,
offset: Int?,
ids: List<String>?,
gain: Float?): Call<JukeboxResponse> {
checkVersion(V1_2_0)
checkParamVersion(offset, V1_7_0)
return api.jukeboxControl(action, index, offset, ids, gain)
}
override fun getShares(): Call<SharesResponse> {
checkVersion(V1_6_0)
return api.getShares()
}
override fun createShare(idsToShare: List<String>,
description: String?,
expires: Long?): Call<SharesResponse> {
checkVersion(V1_6_0)
return api.createShare(idsToShare, description, expires)
}
override fun deleteShare(id: Long): Call<SubsonicResponse> {
checkVersion(V1_6_0)
return api.deleteShare(id)
}
override fun updateShare(id: Long,
description: String?,
expires: Long?): Call<SubsonicResponse> {
checkVersion(V1_6_0)
return api.updateShare(id, description, expires)
}
override fun getGenres(): Call<GenresResponse> {
checkVersion(V1_9_0)
return api.getGenres()
}
override fun getSongsByGenre(genre: String,
count: Int,
offset: Int,
musicFolderId: Long?): Call<GetSongsByGenreResponse> {
checkVersion(V1_9_0)
checkParamVersion(musicFolderId, V1_12_0)
return api.getSongsByGenre(genre, count, offset, musicFolderId)
}
override fun getUser(username: String): Call<GetUserResponse> {
checkVersion(V1_3_0)
return api.getUser(username)
}
override fun getChatMessages(since: Long?): Call<ChatMessagesResponse> {
checkVersion(V1_2_0)
return api.getChatMessages(since)
}
override fun addChatMessage(message: String): Call<SubsonicResponse> {
checkVersion(V1_2_0)
return api.addChatMessage(message)
}
override fun getBookmarks(): Call<BookmarksResponse> {
checkVersion(V1_9_0)
return api.getBookmarks()
}
override fun createBookmark(id: Int, position: Long, comment: String?): Call<SubsonicResponse> {
checkVersion(V1_9_0)
return api.createBookmark(id, position, comment)
}
override fun deleteBookmark(id: Int): Call<SubsonicResponse> {
checkVersion(V1_9_0)
return api.deleteBookmark(id)
}
override fun getVideos(): Call<VideosResponse> {
checkVersion(V1_8_0)
return api.getVideos()
}
override fun getAvatar(username: String): Call<ResponseBody> {
checkVersion(V1_8_0)
return api.getAvatar(username)
}
private fun checkVersion(expectedVersion: SubsonicAPIVersions) {
if (currentApiVersion < expectedVersion) throw ApiNotSupportedException(currentApiVersion)
}
private fun checkParamVersion(param: Any?, expectedVersion: SubsonicAPIVersions) {
if (param != null) {
checkVersion(expectedVersion)
}
}
}

View File

@ -26,6 +26,9 @@ private const val READ_TIMEOUT = 60_000L
*
* For supported API calls see [SubsonicAPIDefinition].
*
* Client will automatically adjust [protocolVersion] to the current server version on
* doing successful requests.
*
* @author Yahor Berdnikau
*/
class SubsonicAPIClient(baseUrl: String,
@ -37,6 +40,7 @@ class SubsonicAPIClient(baseUrl: String,
private val versionInterceptor = VersionInterceptor(minimalProtocolVersion) {
protocolVersion = it
}
private val proxyPasswordInterceptor = ProxyPasswordInterceptor(minimalProtocolVersion,
PasswordHexInterceptor(password), PasswordMD5Interceptor(password))
@ -47,6 +51,7 @@ class SubsonicAPIClient(baseUrl: String,
private set(value) {
field = value
proxyPasswordInterceptor.apiVersion = field
wrappedApi.currentApiVersion = field
}
private val okHttpClient = OkHttpClient.Builder()
@ -78,7 +83,11 @@ class SubsonicAPIClient(baseUrl: String,
.addConverterFactory(JacksonConverterFactory.create(jacksonMapper))
.build()
val api: SubsonicAPIDefinition = retrofit.create(SubsonicAPIDefinition::class.java)
private val wrappedApi = ApiVersionCheckWrapper(
retrofit.create(SubsonicAPIDefinition::class.java),
minimalProtocolVersion)
val api: SubsonicAPIDefinition get() = wrappedApi
/**
* Convenient method to get cover art from api using item [id] and optional maximum [size].

View File

@ -0,0 +1,46 @@
package org.moire.ultrasonic.api.subsonic
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.never
import com.nhaarman.mockito_kotlin.verify
import org.amshove.kluent.`should throw`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_1_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_2_0
import org.moire.ultrasonic.api.subsonic.models.AlbumListType.BY_GENRE
/**
* Unit test for [ApiVersionCheckWrapper].
*/
class ApiVersionCheckWrapperTest {
private val apiMock = mock<SubsonicAPIDefinition>()
private val wrapper = ApiVersionCheckWrapper(apiMock, V1_1_0)
@Test
fun `Should just call real api for ping`() {
wrapper.ping()
verify(apiMock).ping()
}
@Test
fun `Should throw ApiNotSupportedException when current api level is too low for call`() {
val throwCall = { wrapper.getBookmarks() }
throwCall `should throw` ApiNotSupportedException::class
verify(apiMock, never()).getBookmarks()
}
@Test
fun `Should throw ApiNotSupportedException when call param is not supported by current api`() {
wrapper.currentApiVersion = V1_2_0
wrapper.getAlbumList(BY_GENRE)
val throwCall = { wrapper.getAlbumList(BY_GENRE, musicFolderId = 12L) }
throwCall `should throw` ApiNotSupportedException::class
verify(apiMock).getAlbumList(BY_GENRE)
verify(apiMock, never()).getAlbumList(BY_GENRE, musicFolderId = 12L)
}
}

View File

@ -19,47 +19,20 @@
package org.moire.ultrasonic.activity;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.domain.Playlist;
import org.moire.ultrasonic.domain.PodcastsChannel;
import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory;
import org.moire.ultrasonic.service.OfflineException;
import org.moire.ultrasonic.service.ServerTooOldException;
import org.moire.ultrasonic.util.BackgroundTask;
import org.moire.ultrasonic.util.CacheCleaner;
import org.moire.ultrasonic.util.Constants;
import org.moire.ultrasonic.util.LoadingTask;
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
import org.moire.ultrasonic.util.Util;
import org.moire.ultrasonic.view.PlaylistAdapter;
import org.moire.ultrasonic.view.PodcastsChannelsAdapter;
import java.util.List;

View File

@ -43,12 +43,13 @@ import android.widget.TextView;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
import org.moire.ultrasonic.domain.Playlist;
import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory;
import org.moire.ultrasonic.service.OfflineException;
import org.moire.ultrasonic.service.ServerTooOldException;
import org.moire.ultrasonic.util.BackgroundTask;
import org.moire.ultrasonic.util.CacheCleaner;
import org.moire.ultrasonic.util.Constants;
@ -271,7 +272,7 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
protected void error(Throwable error)
{
String msg;
msg = error instanceof OfflineException || error instanceof ServerTooOldException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_playlist_error, playlist.getName()), getErrorMessage(error));
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_playlist_error, playlist.getName()), getErrorMessage(error));
Util.toast(SelectPlaylistActivity.this, msg, false);
}
@ -360,7 +361,7 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
protected void error(Throwable error)
{
String msg;
msg = error instanceof OfflineException || error instanceof ServerTooOldException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, playlist.getName()), getErrorMessage(error));
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, playlist.getName()), getErrorMessage(error));
Util.toast(SelectPlaylistActivity.this, msg, false);
}

View File

@ -44,12 +44,13 @@ import android.widget.TextView;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
import org.moire.ultrasonic.domain.Share;
import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory;
import org.moire.ultrasonic.service.OfflineException;
import org.moire.ultrasonic.service.ServerTooOldException;
import org.moire.ultrasonic.util.BackgroundTask;
import org.moire.ultrasonic.util.Constants;
import org.moire.ultrasonic.util.LoadingTask;
@ -251,7 +252,7 @@ public class ShareActivity extends SubsonicTabActivity implements AdapterView.On
protected void error(Throwable error)
{
String msg;
msg = error instanceof OfflineException || error instanceof ServerTooOldException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_share_error, share.getName()), getErrorMessage(error));
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_share_error, share.getName()), getErrorMessage(error));
Util.toast(ShareActivity.this, msg, false);
}
@ -356,7 +357,7 @@ public class ShareActivity extends SubsonicTabActivity implements AdapterView.On
protected void error(Throwable error)
{
String msg;
msg = error instanceof OfflineException || error instanceof ServerTooOldException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, share.getName()), getErrorMessage(error));
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, share.getName()), getErrorMessage(error));
Util.toast(ShareActivity.this, msg, false);
}

View File

@ -28,6 +28,7 @@ import android.widget.ProgressBar;
import android.widget.Toast;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
import org.moire.ultrasonic.domain.JukeboxStatus;
import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
@ -185,7 +186,7 @@ public class JukeboxService
private void onError(JukeboxTask task, Throwable x)
{
if (x instanceof ServerTooOldException && !(task instanceof Stop))
if (x instanceof ApiNotSupportedException && !(task instanceof Stop))
{
disableJukeboxOnError(x, R.string.download_jukebox_server_too_old);
}

View File

@ -43,6 +43,7 @@ public class MusicServiceFactory {
if (OFFLINE_MUSIC_SERVICE == null) {
synchronized (MusicServiceFactory.class) {
if (OFFLINE_MUSIC_SERVICE == null) {
Log.d(LOG_TAG, "Creating new offline music service");
OFFLINE_MUSIC_SERVICE = new OfflineMusicService(createSubsonicApiClient(context));
}
}
@ -54,6 +55,7 @@ public class MusicServiceFactory {
if (REST_MUSIC_SERVICE == null) {
synchronized (MusicServiceFactory.class) {
if (REST_MUSIC_SERVICE == null) {
Log.d(LOG_TAG, "Creating new rest music service");
REST_MUSIC_SERVICE = new CachedMusicService(new RESTMusicService(
createSubsonicApiClient(context)));
}

View File

@ -26,6 +26,7 @@ import android.support.annotation.StringRes;
import android.util.Log;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
import org.moire.ultrasonic.api.subsonic.models.AlbumListType;
import org.moire.ultrasonic.api.subsonic.models.JukeboxAction;
@ -339,7 +340,7 @@ public class RESTMusicService implements MusicService {
Util.getShouldUseId3Tags(context) ?
search3(criteria, context, progressListener) :
search2(criteria, context, progressListener);
} catch (ServerTooOldException x) {
} catch (ApiNotSupportedException ignored) {
// Ensure backward compatibility with REST 1.3.
return searchOld(criteria, context, progressListener);
}

View File

@ -1,48 +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;
/**
* Thrown if the REST API version implemented by the server is too old.
*
* @author Sindre Mehus
* @version $Id$
*/
public class ServerTooOldException extends Exception
{
private static final long serialVersionUID = -7955245839000220002L;
public ServerTooOldException(String text)
{
super(createMessage(text));
}
private static String createMessage(String text)
{
StringBuilder builder = new StringBuilder(25);
if (text != null)
{
builder.append(text).append(' ');
}
builder.append("Subsonic server version is too old. Please upgrade.");
return builder.toString();
}
}