mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-27 14:12:14 +03:00
Merge branch 'nitehu-fix/store-api-version' into develop
This commit is contained in:
commit
5c637a2e9c
@ -81,6 +81,7 @@ class SubsonicAPIClient(
|
|||||||
private val jacksonMapper = ObjectMapper()
|
private val jacksonMapper = ObjectMapper()
|
||||||
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
|
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||||
|
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
|
||||||
.registerModule(KotlinModule())
|
.registerModule(KotlinModule())
|
||||||
|
|
||||||
private val retrofit = Retrofit.Builder()
|
private val retrofit = Retrofit.Builder()
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package org.moire.ultrasonic.api.subsonic.interceptors
|
package org.moire.ultrasonic.api.subsonic.interceptors
|
||||||
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
import java.util.Locale
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Interceptor.Chain
|
import okhttp3.Interceptor.Chain
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@ -15,27 +15,33 @@ import okhttp3.Response
|
|||||||
* and above.
|
* and above.
|
||||||
*/
|
*/
|
||||||
class PasswordMD5Interceptor(private val password: String) : Interceptor {
|
class PasswordMD5Interceptor(private val password: String) : Interceptor {
|
||||||
private val salt: String by lazy {
|
private val secureRandom = SecureRandom()
|
||||||
val secureRandom = SecureRandom()
|
private val saltBytes = ByteArray(16)
|
||||||
BigInteger(130, secureRandom).toString(32)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val passwordMD5Hash: String by lazy {
|
|
||||||
try {
|
|
||||||
val md5Digest = MessageDigest.getInstance("MD5")
|
|
||||||
md5Digest.digest("$password$salt".toByteArray()).toHexBytes().toLowerCase()
|
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
|
||||||
throw IllegalStateException(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun intercept(chain: Chain): Response {
|
override fun intercept(chain: Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
|
val salt = getSalt()
|
||||||
val updatedUrl = originalRequest.url().newBuilder()
|
val updatedUrl = originalRequest.url().newBuilder()
|
||||||
.addQueryParameter("t", passwordMD5Hash)
|
.addQueryParameter("t", getPasswordMD5Hash(salt))
|
||||||
.addQueryParameter("s", salt)
|
.addQueryParameter("s", salt)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build())
|
return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getSalt(): String {
|
||||||
|
secureRandom.nextBytes(saltBytes)
|
||||||
|
return saltBytes.toHexBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPasswordMD5Hash(salt: String): String {
|
||||||
|
try {
|
||||||
|
val md5Digest = MessageDigest.getInstance("MD5")
|
||||||
|
return md5Digest.digest(
|
||||||
|
"$password$salt".toByteArray()
|
||||||
|
).toHexBytes().toLowerCase(Locale.getDefault())
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
throw IllegalStateException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1218,6 +1218,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||||||
@Override
|
@Override
|
||||||
protected void error(final Throwable error)
|
protected void error(final Throwable error)
|
||||||
{
|
{
|
||||||
|
Timber.e(error, "Exception has occurred in savePlaylistInBackground");
|
||||||
final String msg = String.format("%s %s", getResources().getString(R.string.download_playlist_error), getErrorMessage(error));
|
final String msg = String.format("%s %s", getResources().getString(R.string.download_playlist_error), getErrorMessage(error));
|
||||||
Util.toast(DownloadActivity.this, msg);
|
Util.toast(DownloadActivity.this, msg);
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,11 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.moire.ultrasonic.R;
|
import org.moire.ultrasonic.R;
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||||
|
import org.moire.ultrasonic.data.ServerSetting;
|
||||||
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport;
|
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport;
|
||||||
import org.moire.ultrasonic.service.MusicService;
|
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
|
||||||
import org.moire.ultrasonic.util.Constants;
|
import org.moire.ultrasonic.util.Constants;
|
||||||
import org.moire.ultrasonic.util.FileUtil;
|
import org.moire.ultrasonic.util.FileUtil;
|
||||||
import org.moire.ultrasonic.util.MergeAdapter;
|
import org.moire.ultrasonic.util.MergeAdapter;
|
||||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
|
||||||
import org.moire.ultrasonic.util.Util;
|
import org.moire.ultrasonic.util.Util;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -55,7 +53,7 @@ public class MainActivity extends SubsonicTabActivity
|
|||||||
{
|
{
|
||||||
private static boolean infoDialogDisplayed;
|
private static boolean infoDialogDisplayed;
|
||||||
private static boolean shouldUseId3;
|
private static boolean shouldUseId3;
|
||||||
private static int lastActiveServer;
|
private static String lastActiveServerProperties;
|
||||||
|
|
||||||
private Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
|
private Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
|
||||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||||
@ -121,7 +119,7 @@ public class MainActivity extends SubsonicTabActivity
|
|||||||
final View albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist);
|
final View albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist);
|
||||||
final View videosButton = buttons.findViewById(R.id.main_videos);
|
final View videosButton = buttons.findViewById(R.id.main_videos);
|
||||||
|
|
||||||
lastActiveServer = ActiveServerProvider.Companion.getActiveServerId(this);
|
lastActiveServerProperties = getActiveServerProperties();
|
||||||
String name = activeServerProvider.getValue().getActiveServer().getName();
|
String name = activeServerProvider.getValue().getActiveServer().getName();
|
||||||
|
|
||||||
serverTextView.setText(name);
|
serverTextView.setText(name);
|
||||||
@ -152,10 +150,6 @@ public class MainActivity extends SubsonicTabActivity
|
|||||||
|
|
||||||
adapter.addView(videosTitle, false);
|
adapter.addView(videosTitle, false);
|
||||||
adapter.addViews(Collections.singletonList(videosButton), true);
|
adapter.addViews(Collections.singletonList(videosButton), true);
|
||||||
|
|
||||||
if (Util.isNetworkConnected(this)) {
|
|
||||||
new PingTask(this, false).execute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
@ -249,7 +243,7 @@ public class MainActivity extends SubsonicTabActivity
|
|||||||
{
|
{
|
||||||
final SharedPreferences.Editor editor = preferences.edit();
|
final SharedPreferences.Editor editor = preferences.edit();
|
||||||
editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory(this).getPath());
|
editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory(this).getPath());
|
||||||
editor.commit();
|
editor.apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +254,7 @@ public class MainActivity extends SubsonicTabActivity
|
|||||||
boolean shouldRestart = false;
|
boolean shouldRestart = false;
|
||||||
|
|
||||||
boolean id3 = Util.getShouldUseId3Tags(MainActivity.this);
|
boolean id3 = Util.getShouldUseId3Tags(MainActivity.this);
|
||||||
int currentActiveServer = ActiveServerProvider.Companion.getActiveServerId(MainActivity.this);
|
String currentActiveServerProperties = getActiveServerProperties();
|
||||||
|
|
||||||
if (id3 != shouldUseId3)
|
if (id3 != shouldUseId3)
|
||||||
{
|
{
|
||||||
@ -268,9 +262,9 @@ public class MainActivity extends SubsonicTabActivity
|
|||||||
shouldRestart = true;
|
shouldRestart = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentActiveServer != lastActiveServer)
|
if (!currentActiveServerProperties.equals(lastActiveServerProperties))
|
||||||
{
|
{
|
||||||
lastActiveServer = currentActiveServer;
|
lastActiveServerProperties = currentActiveServerProperties;
|
||||||
shouldRestart = true;
|
shouldRestart = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,22 +372,11 @@ public class MainActivity extends SubsonicTabActivity
|
|||||||
startActivityForResult(intent, 0);
|
startActivityForResult(intent, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private String getActiveServerProperties()
|
||||||
* Temporary task to make a ping to server to get it supported api version.
|
{
|
||||||
*/
|
ServerSetting currentSetting = activeServerProvider.getValue().getActiveServer();
|
||||||
private static class PingTask extends TabActivityBackgroundTask<Void> {
|
return String.format("%s;%s;%s;%s;%s;%s", currentSetting.getUrl(), currentSetting.getUserName(),
|
||||||
PingTask(SubsonicTabActivity activity, boolean changeProgress) {
|
currentSetting.getPassword(), currentSetting.getAllowSelfSignedCertificate(),
|
||||||
super(activity, changeProgress);
|
currentSetting.getLdapSupport(), currentSetting.getMinimumApiVersion());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground() throws Throwable {
|
|
||||||
final MusicService service = MusicServiceFactory.getMusicService(getActivity());
|
|
||||||
service.ping(getActivity(), null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done(Void result) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -361,7 +361,16 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
|
|||||||
public synchronized void clear(boolean serialize)
|
public synchronized void clear(boolean serialize)
|
||||||
{
|
{
|
||||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||||
if (mediaPlayerService != null) mediaPlayerService.clear(serialize);
|
if (mediaPlayerService != null) {
|
||||||
|
mediaPlayerService.clear(serialize);
|
||||||
|
} else {
|
||||||
|
// If no MediaPlayerService is available, just empty the playlist
|
||||||
|
downloader.clear();
|
||||||
|
if (serialize) {
|
||||||
|
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList,
|
||||||
|
downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,14 @@ package org.moire.ultrasonic.service;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.media.MediaMetadataRetriever;
|
import android.media.MediaMetadataRetriever;
|
||||||
|
|
||||||
|
import kotlin.Pair;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient;
|
|
||||||
import org.moire.ultrasonic.cache.PermanentFileStorage;
|
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||||
import org.moire.ultrasonic.domain.Artist;
|
import org.moire.ultrasonic.domain.Artist;
|
||||||
|
import org.moire.ultrasonic.domain.Bookmark;
|
||||||
|
import org.moire.ultrasonic.domain.ChatMessage;
|
||||||
import org.moire.ultrasonic.domain.Genre;
|
import org.moire.ultrasonic.domain.Genre;
|
||||||
import org.moire.ultrasonic.domain.Indexes;
|
import org.moire.ultrasonic.domain.Indexes;
|
||||||
import org.moire.ultrasonic.domain.JukeboxStatus;
|
import org.moire.ultrasonic.domain.JukeboxStatus;
|
||||||
@ -34,10 +36,12 @@ import org.moire.ultrasonic.domain.Lyrics;
|
|||||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||||
import org.moire.ultrasonic.domain.MusicFolder;
|
import org.moire.ultrasonic.domain.MusicFolder;
|
||||||
import org.moire.ultrasonic.domain.Playlist;
|
import org.moire.ultrasonic.domain.Playlist;
|
||||||
|
import org.moire.ultrasonic.domain.PodcastsChannel;
|
||||||
import org.moire.ultrasonic.domain.SearchCriteria;
|
import org.moire.ultrasonic.domain.SearchCriteria;
|
||||||
import org.moire.ultrasonic.domain.SearchResult;
|
import org.moire.ultrasonic.domain.SearchResult;
|
||||||
import org.moire.ultrasonic.domain.Share;
|
import org.moire.ultrasonic.domain.Share;
|
||||||
import org.moire.ultrasonic.domain.UserInfo;
|
import org.moire.ultrasonic.domain.UserInfo;
|
||||||
|
import org.moire.ultrasonic.util.CancellableTask;
|
||||||
import org.moire.ultrasonic.util.Constants;
|
import org.moire.ultrasonic.util.Constants;
|
||||||
import org.moire.ultrasonic.util.FileUtil;
|
import org.moire.ultrasonic.util.FileUtil;
|
||||||
import org.moire.ultrasonic.util.ProgressListener;
|
import org.moire.ultrasonic.util.ProgressListener;
|
||||||
@ -48,6 +52,7 @@ import java.io.BufferedWriter;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -68,22 +73,11 @@ import static org.koin.java.KoinJavaComponent.inject;
|
|||||||
/**
|
/**
|
||||||
* @author Sindre Mehus
|
* @author Sindre Mehus
|
||||||
*/
|
*/
|
||||||
public class OfflineMusicService extends RESTMusicService
|
public class OfflineMusicService implements MusicService
|
||||||
{
|
{
|
||||||
private static final Pattern COMPILE = Pattern.compile(" ");
|
private static final Pattern COMPILE = Pattern.compile(" ");
|
||||||
|
|
||||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||||
|
|
||||||
public OfflineMusicService(SubsonicAPIClient subsonicAPIClient, PermanentFileStorage storage) {
|
|
||||||
super(subsonicAPIClient, storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||||
{
|
{
|
||||||
@ -150,7 +144,7 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener)
|
||||||
{
|
{
|
||||||
File dir = new File(id);
|
File dir = new File(id);
|
||||||
MusicDirectory result = new MusicDirectory();
|
MusicDirectory result = new MusicDirectory();
|
||||||
@ -341,7 +335,7 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception
|
public Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -355,7 +349,7 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception
|
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -369,25 +363,7 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void star(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
|
public SearchResult search(SearchCriteria criteria, Context context, ProgressListener progressListener)
|
||||||
{
|
|
||||||
throw new OfflineException("Star not available in offline mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unstar(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
|
|
||||||
{
|
|
||||||
throw new OfflineException("UnStar not available in offline mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
|
||||||
{
|
|
||||||
throw new OfflineException("Music folders not available in offline mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SearchResult search(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception
|
|
||||||
{
|
{
|
||||||
List<Artist> artists = new ArrayList<Artist>();
|
List<Artist> artists = new ArrayList<Artist>();
|
||||||
List<MusicDirectory.Entry> albums = new ArrayList<MusicDirectory.Entry>();
|
List<MusicDirectory.Entry> albums = new ArrayList<MusicDirectory.Entry>();
|
||||||
@ -531,7 +507,7 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener)
|
||||||
{
|
{
|
||||||
List<Playlist> playlists = new ArrayList<Playlist>();
|
List<Playlist> playlists = new ArrayList<Playlist>();
|
||||||
File root = FileUtil.getPlaylistDirectory(context);
|
File root = FileUtil.getPlaylistDirectory(context);
|
||||||
@ -661,6 +637,45 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener)
|
||||||
|
{
|
||||||
|
File root = FileUtil.getMusicDirectory(context);
|
||||||
|
List<File> children = new LinkedList<File>();
|
||||||
|
listFilesRecursively(root, children);
|
||||||
|
MusicDirectory result = new MusicDirectory();
|
||||||
|
|
||||||
|
if (children.isEmpty())
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Random random = new java.security.SecureRandom();
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
File file = children.get(random.nextInt(children.size()));
|
||||||
|
result.addChild(createEntry(context, file, getName(file)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void listFilesRecursively(File parent, List<File> children)
|
||||||
|
{
|
||||||
|
for (File file : FileUtil.listMediaFiles(parent))
|
||||||
|
{
|
||||||
|
if (file.isFile())
|
||||||
|
{
|
||||||
|
children.add(file);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
listFilesRecursively(file, children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception
|
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception
|
||||||
{
|
{
|
||||||
@ -691,12 +706,6 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
throw new OfflineException("Album lists not available in offline mode");
|
throw new OfflineException("Album lists not available in offline mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getVideoUrl(Context context, String id, boolean useFlash)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception
|
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception
|
||||||
{
|
{
|
||||||
@ -739,29 +748,6 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
throw new OfflineException("Starred not available in offline mode");
|
throw new OfflineException("Starred not available in offline mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception
|
|
||||||
{
|
|
||||||
File root = FileUtil.getMusicDirectory(context);
|
|
||||||
List<File> children = new LinkedList<File>();
|
|
||||||
listFilesRecursively(root, children);
|
|
||||||
MusicDirectory result = new MusicDirectory();
|
|
||||||
|
|
||||||
if (children.isEmpty())
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Random random = new java.security.SecureRandom();
|
|
||||||
for (int i = 0; i < size; i++)
|
|
||||||
{
|
|
||||||
File file = children.get(random.nextInt(children.size()));
|
|
||||||
result.addChild(createEntry(context, file, getName(file)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception
|
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception
|
||||||
{
|
{
|
||||||
@ -804,18 +790,121 @@ public class OfflineMusicService extends RESTMusicService
|
|||||||
throw new OfflineException("Updating shares not available in offline mode");
|
throw new OfflineException("Updating shares not available in offline mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void listFilesRecursively(File parent, List<File> children)
|
@Override
|
||||||
|
public void star(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
|
||||||
{
|
{
|
||||||
for (File file : FileUtil.listMediaFiles(parent))
|
throw new OfflineException("Star not available in offline mode");
|
||||||
{
|
|
||||||
if (file.isFile())
|
|
||||||
{
|
|
||||||
children.add(file);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
@Override
|
||||||
|
public void unstar(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
|
||||||
{
|
{
|
||||||
listFilesRecursively(file, children);
|
throw new OfflineException("UnStar not available in offline mode");
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||||
|
{
|
||||||
|
throw new OfflineException("Music folders not available in offline mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MusicDirectory getAlbumList2(String type, int size, int offset, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getAlbumList2 was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVideoUrl(Context context, String id, boolean useFlash) {
|
||||||
|
Timber.w("OfflineMusicService.getVideoUrl was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ChatMessage> getChatMessages(Long since, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getChatMessages was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addChatMessage(String message, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.addChatMessage was called but it isn't available");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Bookmark> getBookmarks(Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getBookmarks was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteBookmark(String id, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.deleteBookmark was called but it isn't available");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createBookmark(String id, int position, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.createBookmark was called but it isn't available");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MusicDirectory getVideos(boolean refresh, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getVideos was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchResult getStarred2(Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getStarred2 was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ping(Context context, ProgressListener progressListener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLicenseValid(Context context, ProgressListener progressListener) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Indexes getArtists(boolean refresh, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getArtists was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getArtist was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getAlbum was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getPodcastEpisodes was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<InputStream, Boolean> getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) {
|
||||||
|
Timber.w("OfflineMusicService.getDownloadInputStream was called but it isn't available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRating(String id, int rating, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.setRating was called but it isn't available");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PodcastsChannel> getPodcastsChannels(boolean refresh, Context context, ProgressListener progressListener) {
|
||||||
|
Timber.w("OfflineMusicService.getPodcastsChannels was called but it isn't available");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,6 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import java.io.IOException
|
|
||||||
import java.net.MalformedURLException
|
import java.net.MalformedURLException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
@ -19,16 +18,14 @@ import org.moire.ultrasonic.R
|
|||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
|
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
|
||||||
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.data.ServerSetting
|
import org.moire.ultrasonic.data.ServerSetting
|
||||||
|
import org.moire.ultrasonic.service.ApiCallResponseChecker.Companion.checkResponseSuccessful
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||||
import org.moire.ultrasonic.service.SubsonicRESTException
|
|
||||||
import org.moire.ultrasonic.util.Constants
|
import org.moire.ultrasonic.util.Constants
|
||||||
import org.moire.ultrasonic.util.ErrorDialog
|
import org.moire.ultrasonic.util.ErrorDialog
|
||||||
import org.moire.ultrasonic.util.ModalBackgroundTask
|
import org.moire.ultrasonic.util.ModalBackgroundTask
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import retrofit2.Response
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,6 +84,17 @@ internal class EditServerActivity : AppCompatActivity() {
|
|||||||
if (t != null) {
|
if (t != null) {
|
||||||
currentServerSetting = t
|
currentServerSetting = t
|
||||||
setFields()
|
setFields()
|
||||||
|
// Remove the minimum API version so it can be detected again
|
||||||
|
if (currentServerSetting?.minimumApiVersion != null) {
|
||||||
|
currentServerSetting!!.minimumApiVersion = null
|
||||||
|
serverSettingsModel.updateItem(currentServerSetting)
|
||||||
|
if (
|
||||||
|
activeServerProvider.getActiveServer().id ==
|
||||||
|
currentServerSetting!!.id
|
||||||
|
) {
|
||||||
|
MusicServiceFactory.resetMusicService()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -260,7 +268,18 @@ internal class EditServerActivity : AppCompatActivity() {
|
|||||||
BuildConfig.DEBUG
|
BuildConfig.DEBUG
|
||||||
)
|
)
|
||||||
val subsonicApiClient = SubsonicAPIClient(configuration)
|
val subsonicApiClient = SubsonicAPIClient(configuration)
|
||||||
val pingResponse = subsonicApiClient.api.ping().execute()
|
|
||||||
|
// Execute a ping to retrieve the API version.
|
||||||
|
// This is accepted to fail if the authentication is incorrect yet.
|
||||||
|
var pingResponse = subsonicApiClient.api.ping().execute()
|
||||||
|
if (pingResponse?.body() != null) {
|
||||||
|
val restApiVersion = pingResponse.body()!!.version.restApiVersion
|
||||||
|
currentServerSetting!!.minimumApiVersion = restApiVersion
|
||||||
|
Timber.i("Server minimum API version set to %s", restApiVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a ping to check the authentication, now using the correct API version.
|
||||||
|
pingResponse = subsonicApiClient.api.ping().execute()
|
||||||
checkResponseSuccessful(pingResponse)
|
checkResponseSuccessful(pingResponse)
|
||||||
|
|
||||||
val licenseResponse = subsonicApiClient.api.getLicense().execute()
|
val licenseResponse = subsonicApiClient.api.getLicense().execute()
|
||||||
@ -292,28 +311,6 @@ internal class EditServerActivity : AppCompatActivity() {
|
|||||||
task.execute()
|
task.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the Subsonic Response for application specific errors
|
|
||||||
*/
|
|
||||||
private fun checkResponseSuccessful(response: Response<out SubsonicResponse?>) {
|
|
||||||
if (
|
|
||||||
response.isSuccessful &&
|
|
||||||
response.body()!!.status === SubsonicResponse.Status.OK
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
throw IOException("Server error, code: " + response.code())
|
|
||||||
} else if (
|
|
||||||
response.body()!!.status === SubsonicResponse.Status.ERROR &&
|
|
||||||
response.body()!!.error != null
|
|
||||||
) {
|
|
||||||
throw SubsonicRESTException(response.body()!!.error!!)
|
|
||||||
} else {
|
|
||||||
throw IOException("Failed to perform request: " + response.code())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finishes the Activity, after confirmation from the user if needed
|
* Finishes the Activity, after confirmation from the user if needed
|
||||||
*/
|
*/
|
||||||
|
@ -209,7 +209,8 @@ class ServerSettingsModel(
|
|||||||
false
|
false
|
||||||
),
|
),
|
||||||
settings.getBoolean(PREFERENCES_KEY_LDAP_SUPPORT + preferenceId, false),
|
settings.getBoolean(PREFERENCES_KEY_LDAP_SUPPORT + preferenceId, false),
|
||||||
settings.getString(PREFERENCES_KEY_MUSIC_FOLDER_ID + preferenceId, null)
|
settings.getString(PREFERENCES_KEY_MUSIC_FOLDER_ID + preferenceId, null),
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,8 @@ class ActiveServerProvider(
|
|||||||
jukeboxByDefault = false,
|
jukeboxByDefault = false,
|
||||||
allowSelfSignedCertificate = false,
|
allowSelfSignedCertificate = false,
|
||||||
ldapSupport = false,
|
ldapSupport = false,
|
||||||
musicFolderId = ""
|
musicFolderId = "",
|
||||||
|
minimumApiVersion = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,18 @@ class ActiveServerProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum Subsonic API version of the current server.
|
||||||
|
*/
|
||||||
|
fun setMinimumApiVersion(apiVersion: String) {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
if (cachedServer != null) {
|
||||||
|
cachedServer!!.minimumApiVersion = apiVersion
|
||||||
|
repository.update(cachedServer!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates the Active Server Setting cache
|
* Invalidates the Active Server Setting cache
|
||||||
* This should be called when the Active Server or one of its properties changes
|
* This should be called when the Active Server or one of its properties changes
|
||||||
|
@ -2,11 +2,13 @@ package org.moire.ultrasonic.data
|
|||||||
|
|
||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Room Database to be used to store data for Ultrasonic
|
* Room Database to be used to store data for Ultrasonic
|
||||||
*/
|
*/
|
||||||
@Database(entities = [ServerSetting::class], version = 1)
|
@Database(entities = [ServerSetting::class], version = 2)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,3 +16,11 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
*/
|
*/
|
||||||
abstract fun serverSettingDao(): ServerSettingDao
|
abstract fun serverSettingDao(): ServerSettingDao
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL(
|
||||||
|
"ALTER TABLE ServerSetting ADD COLUMN minimumApiVersion TEXT"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,12 +28,13 @@ data class ServerSetting(
|
|||||||
@ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean,
|
@ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean,
|
||||||
@ColumnInfo(name = "allowSelfSignedCertificate") var allowSelfSignedCertificate: Boolean,
|
@ColumnInfo(name = "allowSelfSignedCertificate") var allowSelfSignedCertificate: Boolean,
|
||||||
@ColumnInfo(name = "ldapSupport") var ldapSupport: Boolean,
|
@ColumnInfo(name = "ldapSupport") var ldapSupport: Boolean,
|
||||||
@ColumnInfo(name = "musicFolderId") var musicFolderId: String?
|
@ColumnInfo(name = "musicFolderId") var musicFolderId: String?,
|
||||||
|
@ColumnInfo(name = "minimumApiVersion") var minimumApiVersion: String?
|
||||||
) {
|
) {
|
||||||
constructor() : this (
|
constructor() : this (
|
||||||
-1, 0, "", "", "", "", false, false, false, null
|
-1, 0, "", "", "", "", false, false, false, null, null
|
||||||
)
|
)
|
||||||
constructor(name: String, url: String) : this(
|
constructor(name: String, url: String) : this(
|
||||||
-1, 0, name, url, "", "", false, false, false, null
|
-1, 0, name, url, "", "", false, false, false, null, null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import org.koin.dsl.module
|
|||||||
import org.moire.ultrasonic.activity.ServerSettingsModel
|
import org.moire.ultrasonic.activity.ServerSettingsModel
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.data.AppDatabase
|
import org.moire.ultrasonic.data.AppDatabase
|
||||||
|
import org.moire.ultrasonic.data.MIGRATION_1_2
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
|
|
||||||
const val SP_NAME = "Default_SP"
|
const val SP_NAME = "Default_SP"
|
||||||
@ -20,7 +21,10 @@ val appPermanentStorage = module {
|
|||||||
androidContext(),
|
androidContext(),
|
||||||
AppDatabase::class.java,
|
AppDatabase::class.java,
|
||||||
"ultrasonic-database"
|
"ultrasonic-database"
|
||||||
).build()
|
)
|
||||||
|
.addMigrations(MIGRATION_1_2)
|
||||||
|
.fallbackToDestructiveMigrationOnDowngrade()
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
single { get<AppDatabase>().serverSettingDao() }
|
single { get<AppDatabase>().serverSettingDao() }
|
||||||
|
@ -13,6 +13,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
|
|||||||
import org.moire.ultrasonic.cache.PermanentFileStorage
|
import org.moire.ultrasonic.cache.PermanentFileStorage
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.log.TimberOkHttpLogger
|
import org.moire.ultrasonic.log.TimberOkHttpLogger
|
||||||
|
import org.moire.ultrasonic.service.ApiCallResponseChecker
|
||||||
import org.moire.ultrasonic.service.CachedMusicService
|
import org.moire.ultrasonic.service.CachedMusicService
|
||||||
import org.moire.ultrasonic.service.MusicService
|
import org.moire.ultrasonic.service.MusicService
|
||||||
import org.moire.ultrasonic.service.OfflineMusicService
|
import org.moire.ultrasonic.service.OfflineMusicService
|
||||||
@ -46,7 +47,8 @@ val musicServiceModule = module {
|
|||||||
username = get<ActiveServerProvider>().getActiveServer().userName,
|
username = get<ActiveServerProvider>().getActiveServer().userName,
|
||||||
password = get<ActiveServerProvider>().getActiveServer().password,
|
password = get<ActiveServerProvider>().getActiveServer().password,
|
||||||
minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
|
minimalProtocolVersion = SubsonicAPIVersions.getClosestKnownClientApiVersion(
|
||||||
Constants.REST_PROTOCOL_VERSION
|
get<ActiveServerProvider>().getActiveServer().minimumApiVersion
|
||||||
|
?: Constants.REST_PROTOCOL_VERSION
|
||||||
),
|
),
|
||||||
clientID = Constants.REST_CLIENT_ID,
|
clientID = Constants.REST_CLIENT_ID,
|
||||||
allowSelfSignedCertificate = get<ActiveServerProvider>()
|
allowSelfSignedCertificate = get<ActiveServerProvider>()
|
||||||
@ -58,13 +60,14 @@ val musicServiceModule = module {
|
|||||||
|
|
||||||
single<HttpLoggingInterceptor.Logger> { TimberOkHttpLogger() }
|
single<HttpLoggingInterceptor.Logger> { TimberOkHttpLogger() }
|
||||||
single { SubsonicAPIClient(get(), get()) }
|
single { SubsonicAPIClient(get(), get()) }
|
||||||
|
single { ApiCallResponseChecker(get(), get()) }
|
||||||
|
|
||||||
single<MusicService>(named(ONLINE_MUSIC_SERVICE)) {
|
single<MusicService>(named(ONLINE_MUSIC_SERVICE)) {
|
||||||
CachedMusicService(RESTMusicService(get(), get()))
|
CachedMusicService(RESTMusicService(get(), get(), get(), get()))
|
||||||
}
|
}
|
||||||
|
|
||||||
single<MusicService>(named(OFFLINE_MUSIC_SERVICE)) {
|
single<MusicService>(named(OFFLINE_MUSIC_SERVICE)) {
|
||||||
OfflineMusicService(get(), get())
|
OfflineMusicService()
|
||||||
}
|
}
|
||||||
|
|
||||||
single { SubsonicImageLoader(androidContext(), get()) }
|
single { SubsonicImageLoader(androidContext(), get()) }
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
package org.moire.ultrasonic.service
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIDefinition
|
||||||
|
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||||
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
|
import retrofit2.Response
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This call wraps Subsonic API calls so their results can be checked for errors, API version, etc
|
||||||
|
*/
|
||||||
|
class ApiCallResponseChecker(
|
||||||
|
private val subsonicAPIClient: SubsonicAPIClient,
|
||||||
|
private val activeServerProvider: ActiveServerProvider
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Executes a Subsonic API call with response check
|
||||||
|
*/
|
||||||
|
@Throws(SubsonicRESTException::class, IOException::class)
|
||||||
|
fun <T : Response<out SubsonicResponse>> callWithResponseCheck(
|
||||||
|
call: (SubsonicAPIDefinition) -> T
|
||||||
|
): T {
|
||||||
|
// Check for API version when first contacting the server
|
||||||
|
if (activeServerProvider.getActiveServer().minimumApiVersion == null) {
|
||||||
|
try {
|
||||||
|
val response = subsonicAPIClient.api.ping().execute()
|
||||||
|
if (response?.body() != null) {
|
||||||
|
val restApiVersion = response.body()!!.version.restApiVersion
|
||||||
|
Timber.i("Server minimum API version set to %s", restApiVersion)
|
||||||
|
activeServerProvider.setMinimumApiVersion(restApiVersion)
|
||||||
|
}
|
||||||
|
} catch (ignored: Exception) {
|
||||||
|
// This Ping is only used to get the API Version, if it fails, that's no problem.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This call will be now executed with the correct API Version, so it shouldn't fail
|
||||||
|
val result = call.invoke(subsonicAPIClient.api)
|
||||||
|
checkResponseSuccessful(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Exceptions from the results returned by the Subsonic API
|
||||||
|
*/
|
||||||
|
companion object {
|
||||||
|
@Throws(SubsonicRESTException::class, IOException::class)
|
||||||
|
fun checkResponseSuccessful(response: Response<out SubsonicResponse>) {
|
||||||
|
if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
throw IOException("Server error, code: " + response.code())
|
||||||
|
} else if (
|
||||||
|
response.body()!!.status === SubsonicResponse.Status.ERROR &&
|
||||||
|
response.body()!!.error != null
|
||||||
|
) {
|
||||||
|
throw SubsonicRESTException(response.body()!!.error!!)
|
||||||
|
} else {
|
||||||
|
throw IOException("Failed to perform request: " + response.code())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -309,9 +309,9 @@
|
|||||||
<string name="settings.theme_black">Black</string>
|
<string name="settings.theme_black">Black</string>
|
||||||
<string name="settings.theme_title">Theme</string>
|
<string name="settings.theme_title">Theme</string>
|
||||||
<string name="settings.title.allow_self_signed_certificate">Allow self-signed HTTPS certificate</string>
|
<string name="settings.title.allow_self_signed_certificate">Allow self-signed HTTPS certificate</string>
|
||||||
<string name="settings.title.enable_ldap_users_support">Enable support for LDAP users</string>
|
<string name="settings.title.enable_ldap_users_support">Force plain password authentication</string>
|
||||||
<string name="settings.summary.enable_ldap_users_support">This forces app to always send password in old-way,
|
<string name="settings.summary.enable_ldap_users_support">This forces the app to always send the password unencrypted.
|
||||||
because Subsonic api does not support new authorization for LDAP users.</string>
|
Useful if the Subsonic server does not support the new authentication API for the users.</string>
|
||||||
<string name="settings.use_folder_for_album_artist">Use Folders For Artist Name</string>
|
<string name="settings.use_folder_for_album_artist">Use Folders For Artist Name</string>
|
||||||
<string name="settings.use_folder_for_album_artist_summary">Assume top-level folder is the name of the album artist</string>
|
<string name="settings.use_folder_for_album_artist_summary">Assume top-level folder is the name of the album artist</string>
|
||||||
<string name="settings.use_id3">Browse Using ID3 Tags</string>
|
<string name="settings.use_id3">Browse Using ID3 Tags</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user