diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/Subsonic.java b/app/src/main/java/com/cappielloantonio/play/subsonic/Subsonic.java new file mode 100644 index 00000000..46b3d03c --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/Subsonic.java @@ -0,0 +1,51 @@ +package com.cappielloantonio.play.subsonic; + +import com.cappielloantonio.play.subsonic.base.Version; + +import java.util.List; +import java.util.Map; + +public class Subsonic { + private final SubsonicPreferences preferences; + + private static final Version API_MIN_VERSION = Version.of("1.13.0"); + private static final Version API_MAX_VERSION = Version.of("1.15.0"); + + private Version apiVersion = API_MAX_VERSION; + + public Subsonic(SubsonicPreferences preferences) { + this.preferences = preferences; + } + + public SubsonicPreferences getPreferences() { + return preferences; + } + + public static Version getApiMinVersion() { + return API_MIN_VERSION; + } + + public static Version getApiMaxVersion() { + return API_MAX_VERSION; + } + + public Version getApiVersion() { + return apiVersion; + } + + public String createUrl(String path, Map> params) { + final StringBuilder sb = new StringBuilder(preferences.getServerUrl()); + + sb.append("/rest/").append(path); + sb.append("?u=").append(preferences.getUsername()); + sb.append("&s=").append(preferences.getAuthentication().getSalt()); + sb.append("&t=").append(preferences.getAuthentication().getToken()); + sb.append("&v=").append(getApiVersion().getVersionString()); + sb.append("&c=").append(preferences.getClientName()); + sb.append("&f=").append("xml"); + + params.keySet().forEach(k -> params.get(k).forEach(v -> sb.append("&").append(k).append("=").append(v))); + + return sb.toString().replace("//rest", "/rest"); + } +} diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/SubsonicPreferences.java b/app/src/main/java/com/cappielloantonio/play/subsonic/SubsonicPreferences.java new file mode 100644 index 00000000..bbf1688f --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/SubsonicPreferences.java @@ -0,0 +1,73 @@ +package com.cappielloantonio.play.subsonic; + +import com.cappielloantonio.play.subsonic.utils.StringUtil; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; + +public class SubsonicPreferences { + private String serverUrl; + private String username; + private String clientName = "SubsonicJavaClient"; + private int streamBitRate = 192; + private String streamFormat = "mp3"; + + private final SubsonicAuthentication authentication; + + public SubsonicPreferences(String serverUrl, String username, String password) { + this.serverUrl = serverUrl; + this.username = username; + this.authentication = new SubsonicAuthentication(password); + } + + public String getServerUrl() { + return serverUrl; + } + + public String getUsername() { + return username; + } + + public String getClientName() { + return clientName; + } + + public int getStreamBitRate() { + return streamBitRate; + } + + public String getStreamFormat() { + return streamFormat; + } + + public SubsonicAuthentication getAuthentication() { + return authentication; + } + + public void setPassword(String password) { + authentication.update(password); + } + + public static class SubsonicAuthentication { + private String salt; + private String token; + + public SubsonicAuthentication(String password) { + update(password); + } + + public String getSalt() { + return salt; + } + + public String getToken() { + return token; + } + + void update(String password) { + this.salt = UUID.randomUUID().toString(); + this.token = StringUtil.tokenize(password + salt); + } + } +} diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/base/SubsonicIncompatibilityException.java b/app/src/main/java/com/cappielloantonio/play/subsonic/base/SubsonicIncompatibilityException.java new file mode 100644 index 00000000..8fd35e9d --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/base/SubsonicIncompatibilityException.java @@ -0,0 +1,13 @@ +package com.cappielloantonio.play.subsonic.base; + +public class SubsonicIncompatibilityException extends RuntimeException{ + private final Version serverApiVersion; + private final Version minClientApiVersion; + + public SubsonicIncompatibilityException(Version serverApiVersion, Version minClientApiVersion) { + super(String.format("Server API version %s is lower than minimal supported API version %s.", serverApiVersion, minClientApiVersion)); + + this.serverApiVersion = serverApiVersion; + this.minClientApiVersion = minClientApiVersion; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/base/Version.java b/app/src/main/java/com/cappielloantonio/play/subsonic/base/Version.java new file mode 100644 index 00000000..45798a55 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/base/Version.java @@ -0,0 +1,56 @@ +package com.cappielloantonio.play.subsonic.base; + +public class Version implements Comparable { + + private static final String VERSION_PATTERN = "[0-9]+(\\.[0-9]+)*"; + private final String versionString; + + public static Version of(String versionString) { + return new Version(versionString); + } + + private Version(String versionString) { + if (versionString == null || !versionString.matches(VERSION_PATTERN)) { + throw new IllegalArgumentException("Invalid version format"); + } + this.versionString = versionString; + } + + public String getVersionString() { + return versionString; + } + + public boolean isLowerThan(Version version) { + return compareTo(version) < 0; + } + + @Override + public int compareTo(Version that) { + if (that == null) { + return 1; + } + + String[] thisParts = this.getVersionString().split("\\."); + String[] thatParts = that.getVersionString().split("\\."); + + int length = Math.max(thisParts.length, thatParts.length); + + for (int i = 0; i < length; i++) { + int thisPart = i < thisParts.length ? Integer.parseInt(thisParts[i]) : 0; + int thatPart = i < thatParts.length ? Integer.parseInt(thatParts[i]) : 0; + + if (thisPart < thatPart) { + return -1; + } + if (thisPart > thatPart) { + return 1; + } + } + return 0; + } + + @Override + public String toString() { + return versionString; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/utils/StringUtil.java b/app/src/main/java/com/cappielloantonio/play/subsonic/utils/StringUtil.java new file mode 100644 index 00000000..0cd2f236 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/utils/StringUtil.java @@ -0,0 +1,28 @@ +package com.cappielloantonio.play.subsonic.utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class StringUtil { + public static String tokenize(String s) { + final String MD5 = "MD5"; + try { + MessageDigest digest = java.security.MessageDigest.getInstance(MD5); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + StringBuilder hexString = new StringBuilder(); + for (byte aMessageDigest : messageDigest) { + String h = Integer.toHexString(0xFF & aMessageDigest); + while (h.length() < 2) { + h = "0" + h; + } + hexString.append(h); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return ""; + } +}