diff --git a/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/LastFMToSpotify.java b/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/LastFMToSpotify.java index d13787b..aa23918 100644 --- a/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/LastFMToSpotify.java +++ b/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/LastFMToSpotify.java @@ -7,6 +7,7 @@ import de.b00tload.tools.lastfmtospotifyplaylist.arguments.Arguments; import de.b00tload.tools.lastfmtospotifyplaylist.util.BrowserHelper; import de.b00tload.tools.lastfmtospotifyplaylist.util.PeriodHelper; +import de.b00tload.tools.lastfmtospotifyplaylist.util.SpotifyCredentials; import de.b00tload.tools.lastfmtospotifyplaylist.util.TokenHelper; import de.umass.lastfm.Caller; import de.umass.lastfm.Track; @@ -15,7 +16,6 @@ import io.javalin.Javalin; import io.javalin.http.ContentType; import io.javalin.http.HttpStatus; import se.michaelthelin.spotify.SpotifyApi; -import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials; import se.michaelthelin.spotify.model_objects.specification.Playlist; import java.net.URI; @@ -83,11 +83,16 @@ public class LastFMToSpotify { AtomicBoolean waiting = new AtomicBoolean(true); if (configuration.containsKey("cache.crypto") && TokenHelper.existsTokens()) { logLn("Cached credentials have been found.", 2); - logLn("Fetching old credentials, refreshing them and saving to cache", 2); - AuthorizationCodeCredentials oldcred = TokenHelper.fetchTokens(); - AuthorizationCodeCredentials newcred = api.authorizationCodeRefresh(api.getClientId(), api.getClientSecret(), oldcred.getRefreshToken()).setHeader("User-Agent", configuration.get("requests.useragent")).build().execute(); - TokenHelper.saveTokens(newcred); - configuration.put("spotify.access", newcred.getAccessToken()); + logLn("Fetching credentials from cache.", 2); + SpotifyCredentials cred = TokenHelper.fetchTokens(); + api.setRefreshToken(cred.getRefreshToken()); + + if(!cred.isValid()){ + logLn("Cached credentials are invalid due to age. Refreshing and saving to cache", 2); + cred.refreshCredentials(api.authorizationCodeRefresh().build().execute()); + TokenHelper.saveTokens(cred); + } + configuration.put("spotify.access", cred.getAccessToken()); } else { try (Javalin webserver = Javalin.create().start(9876)) { if (configuration.containsKey("cache.crypto")) logLn("No cached credentials have been found.", 2); @@ -99,7 +104,7 @@ public class LastFMToSpotify { webserver.get("/callback/spotify", ctx -> { if(ctx.queryParamMap().containsKey("code")) { logLn("Received spotify authentication code. Requesting credentials.", 2); - AuthorizationCodeCredentials cred = api.authorizationCode(ctx.queryParam("code")).setHeader("User-Agent", configuration.get("requests.useragent")).build().execute(); + SpotifyCredentials cred = new SpotifyCredentials(api.authorizationCode(ctx.queryParam("code")).setHeader("User-Agent", configuration.get("requests.useragent")).build().execute()); configuration.put("spotify.access", cred.getAccessToken()); if(configuration.containsKey("cache.crypto")) { logLn("Saving credentials to cache.", 2); diff --git a/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/util/SpotifyCredentials.java b/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/util/SpotifyCredentials.java new file mode 100644 index 0000000..5c5f4d2 --- /dev/null +++ b/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/util/SpotifyCredentials.java @@ -0,0 +1,70 @@ +package de.b00tload.tools.lastfmtospotifyplaylist.util; +import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials; + +import java.io.Serializable; +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.Objects; + +/** + * A wrapper class for se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials. Implements checking validity of access token. + */ +public class SpotifyCredentials implements Serializable { + + private String accessToken; + private String refreshToken; + private LocalDateTime validUntil; + + /** + * Initializes the class + * @param cred The AuthorizationCodeCredentials to be saved. Recommended for use with recently (last few seconds) generated Credentials + */ + public SpotifyCredentials(AuthorizationCodeCredentials cred){ + this.accessToken = cred.getAccessToken(); + this.refreshToken = cred.getRefreshToken(); + this.validUntil = LocalDateTime.now(Clock.systemDefaultZone()).plusSeconds(cred.getExpiresIn()); + } + + /** + * Get the access token. It becomes invalid after a certain period of time. Check validity with isValid(). + * @return An access token that can be provided in subsequent calls, for example to Spotify Web API services. + */ + public String getAccessToken(){ + return accessToken; + } + + /** + * Get the refresh token. This token can be sent to the Spotify Accounts service in place of an authorization code to retrieve a new access token. + * @return A token that can be sent to the Spotify Accounts service in place of an access token. + */ + public String getRefreshToken(){ + return refreshToken; + } + + /** + * Returns a LocalDateTime which represents the latest point in time when the access token is still valid. + * @return A LocalDateTime representing the latest point in time of access token validity + */ + public LocalDateTime getValidUntil(){ + return validUntil; + } + + /** + * Checks whether the saved access token is still valid for use in calls, for example to the Spotify Web API services. + * @return true if the access token is still valid for use, false if not. + */ + public boolean isValid(){ + return LocalDateTime.now(Clock.systemDefaultZone()).isBefore(getValidUntil()); + } + + /** + * Refreshes the access token. If a new refresh token is provided it will be saved as well. + * @param cred The AuthorizationCodeCredentials to be saved. Recommended for use with recently (last few seconds) generated Credentials + */ + public void refreshCredentials(AuthorizationCodeCredentials cred){ + this.accessToken = cred.getAccessToken(); + if(Objects.nonNull(cred.getRefreshToken())) this.refreshToken = cred.getRefreshToken(); + this.validUntil = LocalDateTime.now(Clock.systemDefaultZone()).plusSeconds(cred.getExpiresIn()); + } + +} \ No newline at end of file diff --git a/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/util/TokenHelper.java b/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/util/TokenHelper.java index b9e434a..ffe40c6 100644 --- a/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/util/TokenHelper.java +++ b/src/main/java/de/b00tload/tools/lastfmtospotifyplaylist/util/TokenHelper.java @@ -1,7 +1,5 @@ package de.b00tload.tools.lastfmtospotifyplaylist.util; -import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials; - import java.nio.file.Path; import static de.b00tload.tools.lastfmtospotifyplaylist.LastFMToSpotify.USER_HOME; @@ -10,23 +8,23 @@ import static de.b00tload.tools.lastfmtospotifyplaylist.LastFMToSpotify.configur public class TokenHelper { /** - * Manages saving a se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials into "~/.lfm2s/spotify.lfm2scred" using de.b00tload.tools.lastfmtospotifyplaylist.util.CryptoHelper.serializeEncrypted(...) - * @param cred The se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials to be saved + * Manages saving a de.b00tload.tools.lastfmtospotifyplaylist.util.SpotifyCredentials into "~/.lfm2s/spotify.lfm2scred" using de.b00tload.tools.lastfmtospotifyplaylist.util.CryptoHelper.serializeEncrypted(...) + * @param cred The de.b00tload.tools.lastfmtospotifyplaylist.util.SpotifyCredentials to be saved */ - public static void saveTokens(AuthorizationCodeCredentials cred) { + public static void saveTokens(SpotifyCredentials cred) { CryptoHelper.serializeEncrypted(cred, Path.of(USER_HOME, "/.lfm2s/spotify.lfm2scred"), CryptoHelper.createKeyFromPassword(configuration.get("cache.crypto"))); } /** - * Manages retrieving a se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials from "~/.lfm2s/spotify.lfm2scred" using de.b00tload.tools.lastfmtospotifyplaylist.util.CryptoHelper.deserializeEncrypted(...) - * @return The retrieved se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials + * Manages retrieving a de.b00tload.tools.lastfmtospotifyplaylist.util.SpotifyCredentials from "~/.lfm2s/spotify.lfm2scred" using de.b00tload.tools.lastfmtospotifyplaylist.util.CryptoHelper.deserializeEncrypted(...) + * @return The retrieved de.b00tload.tools.lastfmtospotifyplaylist.util.SpotifyCredentials */ - public static AuthorizationCodeCredentials fetchTokens() { - return (AuthorizationCodeCredentials) CryptoHelper.deserializeEncrypted(Path.of(USER_HOME, "/.lfm2s/spotify.lfm2scred"), CryptoHelper.createKeyFromPassword(configuration.get("cache.crypto"))); + public static SpotifyCredentials fetchTokens() { + return (SpotifyCredentials) CryptoHelper.deserializeEncrypted(Path.of(USER_HOME, "/.lfm2s/spotify.lfm2scred"), CryptoHelper.createKeyFromPassword(configuration.get("cache.crypto"))); } /** - * Checks whether the saved spotify AuthorizationCodeCredentials at "~/.lfm2s/spotify.lfm2scred" exist + * Checks whether the saved SpotifyCredentials at "~/.lfm2s/spotify.lfm2scred" exist * @return true if file exists, false if not */ public static boolean existsTokens(){