[Spotify Binding] Reload Playlists and update Sitemap

Hey there,
I would like to have dynamic sitemaps. Since this is currently not possible, i’ve tried to manipulate a file through a rule. (Later i would like to manipulate a sitemap file …)

I currently cannot write into the file /openhab/conf/test.txt, due to the following error:


My File, My Choice
2020-03-29 20:09:20.245 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update Sitemap': An error occurred during the script execution: Could not invoke method: java.lang.String.getBytes(java.lang.String) on instance: My File, My Choice

I’ve already checked the permissions with Files.isWritable.

Here’s my rule:

    import java.io.IOException
    import java.nio.file.Path
    import java.nio.file.Paths
    import java.nio.file.Files
    import java.nio.charset.StandardCharsets

    rule "Update Sitemap"
      Item spotifyPlaylists_update changed to ON
      val Path path = Paths.get("/openhab/conf/test.txt")
      val Charset charset = StandardCharsets.UTF_8
      var content = new String(Files.readAllBytes(path), charset)
      content = content.replaceAll("foo", "bar")

      // Here I get the error ...
      Files.write(path, content.getBytes(charset))


Depending on what you want to make dynamic this is very much possible using the color and visibility tags. I can’t imagine too many scenarios that can’t be handled with just those two.

I’m not certain this is required, but when referencing static methods in Rules DSL, you should use :: instead of ..

Rules DSL has no ability to save to files on it’s own. Therefore any file writing code you create will be 100% Java.

Looking at the Files JavaDocs I see that readAllBytes only takes one argument. And if you think about it, it makes no sense to supply a character set when you are reading the raw binary into bytes. Maybe you want readAllLines?

And looking at the Files.write JavaDocs the write methods all take three or more arguments and you only supply two.

Thanks Rich for your help.

Currently I can select my spotify playlists from a selection item:

Selection item=Volumio1_Spotify_TrackToPlay label="Playlist Spotify" icon="spotify" mappings=[
                "spotify:user:spotify:playlist:37i9dQZF1DX2JKi7oFC6Jv"  =     "UK Funky House",
                "spotify:user:spotify:playlist:37i9dQZF1DWUraJYejk11q"  =     "Genre Glitch",
                "spotify:user:spotify:playlist:37i9dQZF1DX6VdMW310YC7"  =     "Chill Tracks",
                "spotify:user:spotify:playlist:37i9dQZF1DX1tyCD9QhIWF"  =     "Walk Like A Badass",
                "spotify:user:spotify:playlist:37i9dQZF1DX82pCGH5USnM"  =     "Lounge - Soft House"

This works pretty well.

Now I’m trying to achieve a dynamic update of this part in my .sitemap file.

What I’ve done so far:

Code for loading the playlist from the Spotify API:

// Imports
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.Files
import java.nio.file.StandardOpenOption
import java.util.List
import java.util.Collections
import java.util.Arrays

// Rule

  logInfo("Spotify", "Updating Playlists")
  var String token = spotifyAPIaccessToken.state.toString
  var String api = "https://api.spotify.com/v1/users/%%USERNAME%%/playlists"
  var int limit = 10
  var int offset = 0
  var String command = 'curl@@-s@@-w@@%{http_code}@@-X@@GET@@' + api + '?limit='+ limit.toString +'&offset=' + offset + '@@-H@@Content-Type:application/json@@-H@@Accept:application/json@@-H@@Authorization: Bearer ' + token + "@@"
  var String result = executeCommandLine(command, 5000)
  var int results_total = Integer::parseInt(transform("JSONPATH", "$.total", result))
  logInfo("Spotify", results_total.toString + ' Playlists found.')

  while (offset <= results_total) {
      logInfo("Spotify", offset.toString)
      command = 'curl@@-s@@-w@@%{http_code}@@-X@@GET@@' + api + '?limit='+ limit.toString +'&offset=' + offset + '@@-H@@Content-Type:application/json@@-H@@Accept:application/json@@-H@@Authorization: Bearer ' + token + "@@"
      result = executeCommandLine(command, 5000)
      result_uri = transform("JSONPATH", "$.items[*].uri", result)
      result_name = transform("JSONPATH", "$.items[*].name", result)

logInfo("Spotify", result_uri.toString)

I get the following Output as an example for the URI:

2020-04-13 18:23:22.936 [INFO ] [lipse.smarthome.model.script.Spotify] - ["spotify:playlist:0o6avg8A392phKcm6R5oMK", "spotify:playlist:37i9dQZF1DWTyiBJ6yEqeu", "spotify:playlist:37i9dQZF1DWYUYYlhkTuEn", "spotify:playlist:5VUb9j8U58G36KL4ojb6rn", "spotify:playlist:0EC1TFMmN26VbVvXao8iMc"]

I’m struggling to put the uri and the name back together in the shown format from my sitemap and to write it into my existing sitemap file.

I think it’s a very complicated way to do this, but i’ve no idea if there are any alternative ways to do this …

What I’ve tried so far:
(It’s not yet ready)

val Path path = Paths.get("/openhab/conf/sitemaps/home_copy.sitemap")
  logInfo("execTest", Files.isWritable(path).toString)
  var List<String> content = Files.readAllLines(path)
  logInfo("execTest", content.indexOf("// <---").toString)
  logInfo("execTest", content.indexOf("// --->").toString)
  // Removal
  for (var int i = 0; i <= (content.indexOf("// --->")-content.indexOf("// <---")) ; i++) {
      logInfo("execTest", i.toString + " : " + content.get(content.indexOf("// <---")+i))
  // Add
  for (var int i = 0; i <= uri.size; i++) {
      //logInfo("execTest", i.toString + " : " + uri.get(i))

 //Collections.replaceAll(content,"foo", "bar")
  Files.write(path, content, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)

I don’t use Spotify nor do I try to modify text files from a Rule so I won’t be of much help. I’ve no idea where the name comes from but ultimately, this is a very complicated problem so I would not be surprised if the code is very complicated too.