PDA

View Full Version : Thoughts regarding a generic CLI browse command



erland
2007-05-07, 12:30
Fred asked me to give some suggestion on a more generic CLI browse command and also how it can be done to make it possible to also integrate the Custom Browse menus in it.

First of all some information about the Custom Browse plugin.
What it basically does is defines a number of custom browse menu. Each menu is defined as a XML file containing a specific XML structure.
The menu can be based on the standard SlimServer objects such as albums, artists, tracks, but it can also be based on other information in the database such as decades, ratings, libraries(Multi Library plugin), custom tags(Custom Scan plugin) or basically anything else that is stored in the database. Today Custom Browse register its menus using the Slim::Buttons::Home::addSubMenu function for the player interface and the Slim::Web::Pages->addPageLinks function for the web interface.

I have also just started implementation of a CLI interface for Custom Browse that works as below, this is not part of the released version yet but will be in a few days or weeks:



Command:
xx:xx:xx:xx:xx:xx custombrowse browse

Result:
xx:xx:xx:xx:xx:xx custombrowse browse count:2 level:albums itemid:albums itemname:Albums itemtype:custom level:artists itemid:artists itemname:Artists itemtype:custom

The execution above shows that the top menus available is:
Albums
Artists

The next step is if the client wants to step down into the Albums menu, this is done by usinng the following command:


Command:
xx:xx:xx:xx:xx:xx custombrowse browse albums albums=albums

Result:
xx:xx:xx:xx:xx:xx custombrowse browse count:2 level:album itemid:1 itemname:Album1 itemtype:album level:album itemid:2 itemname:Album2 itemtype:album


The execution above shows that there is two sub menus below the Albums menu:
Album1
Album2

Now lets step down one more step into Album2:


Command:
xx:xx:xx:xx:xx:xx custombrowse browse albums,album albums=albums album=2

Result:
xx:xx:xx:xx:xx:xx custombrowse browse count:3 level:track itemid:1 itemname:FirstTrack itemtype:track level:track itemid:2 itemname:SecondTrack itemtype:track level:track itemid:3 itemname:ThirdTrack itemtype:track


Now this shows us that below Albums/Album2 the following elements exist:
FirstTrack
SecondTrack
ThirdTrack

The method for this CLI interface is pretty similar to how the HTTP request parameters are sent from the normal SlimServer web interface.

The command syntax is basically:
<client> custombrowse browse <hierarchy> <additional parameters>

And in the response you will first get a count that shows the number of elements returned and then an array with an item for each menu element that consist of:
level: The name of the level in the menu structure
itemid: The identifier of the element
itemtype: The type of object the element represents, might be standard SlimServer objects(album,artist,track,year,genre,playlist) but also custom objects.
itemname: The name of the menu element that shall be shown to the user

The <hierarchy> parameter in the command is a concatinated list of levels for the sub menu to browse into. The <additional parameters> parameter in the command is a number of additional parameters, one for each menu level where each parameter is created as "<level>=<itemid>" for the part of the menu you like to browse into.

Now, the important stuff here isn't the exact syntax, its the ideas:
- A menu element can be represented by anything, but it always has an identifier(itemid) and a user friendly name(itemname) to display.
- Each menu element has an identifier(level)
- A menu element does not have to represent a SlimServer database object, the only one that needs to know the thing it represents is the menu implementation that has been registered, so in Custom Browse it might be a custom tag, a rating, decade or something else.
- Plugins must be able to register new top level menus
- SlimServer can pretty easy register its menus and Custom Browse can register its menus and the client can handle them all with the same browse command.
- A similar command is also needed for applying an action to a menu element, for example playing it. This is important to make it possible to also play custom items which doesn't represent a standard SlimServer object.
- The server decides how the menu should look on the client. The client can obviously choose to hide some menus, but the probably client will probably display all available menus. SlimServer can register all menu tree combinations for its menu making it possible to browse to "artist,album,track" but also to "artist,track" or just "track".
- To make the Custom Browse plugin really usable it is also a good idea if it can override one of the standard SlimServer menu, or as an alternative make it possible for the user to choose which standard SlimServer menus that shall be available.
- The information returned from the CLI command shall probably not be restricted to the standard items(itemid,level,itemtype,itemname), it is a good idea to allow custom items. This way more information could be provided for the standard SlimServer objects. But if Custom Browse shall work it is important that the clients work even though only the standard items are provided.

So this was my thoughts regarding this.

Fred
2007-05-08, 10:43
Erland,

Thanks for sharing this, it's quite interesting.

When you say it's close to the web interface, does it mean you see a way to do all three with the same code? (CLI, Jive, Web)

Otherwise yes, something like that is what I was thinking of. The general idea is not to keep state in SS but to "export it" to the client. In you example, the client shall remember and send as params all previous traversed levels. (In the web interface, the URL does actually contain everything).

You have added a level: element to every item. In my view it applies to the whole list, no ? (i.e. all items in a menu belong necessarily to the same level).

You're right, there need to be a command for play but also probably a way to identify for each item if they can be played (to enable some local icon or feedback or whatever).

I will give it some more thoughts...

Fred

erland
2007-05-08, 11:33
Erland,

Thanks for sharing this, it's quite interesting.

When you say it's close to the web interface, does it mean you see a way to do all three with the same code? (CLI, Jive, Web)

In CLI and Web it should be possible to use pretty much the same base code. This is what I do in my current Custom Browse implementation. The CLI interface contains some parsing code to retreive the different input commands and then calls a common method that retreives the menu items for the requested level. The web interface in Custom Browse retrieves the same parameters from the request parameter array posted from the web interface and then calls the same common method. In the web interface the result is sent to the html template and in the CLI interface it is put into the CLI response. Besides this the web interface also calls another pretty small method that just creates the data needed for the breadcrumb, but this is pretty much the same thing as you mentioned with the client state. Another addition to the web interface is also to handle the page navigation stuff, that logic is prepared in the common method but it is not part of the CLI response.

The player interface uses the same code in the core but it is a bit different in some ways. As an example the player interface always retrieves all items for a menu level, while the web interface and CLI takes a "start" and "noOfItems" parameter that describes how many menu elements to retrieve. The "start" and "noOfItems" parameters was not part of my previous post, but I realized that they might be a good idea.

I'm not sure how the Jive stuff will work, but if I have understand it correctly it uses the CLI interface over HTTP. This should mean that to only important stuff is to make sure enough information is returned for each item. But basically if you return an id and a text string is should be possible to render it on the Jive display. For certain itemtype additional result parameters might be returned, such as a url for the album artwork if itemtype=album.

I'm also guessing that if the Web interface would be using Ajax it might be possible to use the CLI interface also directly from the web interface. This way only a single interface will be needed to cover both Web, Jive and CLI. However, to make the Jive and Web interface work correctly I think you will have to also add some additional result elements that represents the "page" returned, such as the "start" value to use to get to the next and previous "page". This "page" stuff might also be needed to the Jive interface, depending how it looks like.


Otherwise yes, something like that is what I was thinking of. The general idea is not to keep state in SS but to "export it" to the client. In you example, the client shall remember and send as params all previous traversed levels. (In the web interface, the URL does actually contain everything).

Now when you say it I think my examples is a bit incorrect. The CLI response always seems to begin with all the request parameters and then the response parameters come starting with the "count" item. At least this is the case when I use the CLI interface from a standard telnet window. So this should take care of the client state problem you mentioned.


You have added a level: element to every item. In my view it applies to the whole list, no ? (i.e. all items in a menu belong necessarily to the same level).

Well it can actually differ between the menu elements. For example lets say you like to have a genres and sub genres menus like this:


Genres
GenreA
ArtistA
GenreB
GenreC
ArtistBC
ArtistB
ArtistBC

ArtistA: Belongs only to GenreA
ArtistBC: Belongs to both GenreB and GenreC (and is shown at two menu levels as you see)
ArtistB: Belongs only to GenreB

The elements below GenreB will have level set to something like:
GenreC: level=subgenre
ArtistB: level=artist
ArtistBC: level=artist


You're right, there need to be a command for play but also probably a way to identify for each item if they can be played (to enable some local icon or feedback or whatever).There might be some other stuff to consider to include:
- playable (as you suggested, every level in Custom Browse is not playable)
- a list of available mixers for the element (could maybe also be implemented as a separate command).

It might also be a good idea to consider "add" besides "play", and maybe also "insert". As I just mentioned a "mix" command is probably also a good idea, to be able to execute one of the mixers available for an item.

Fred
2007-05-10, 07:37
Another addition to the web interface is also to handle the page navigation stuff, that logic is prepared in the common method but it is not part of the CLI response.

There's an enhancement request to support pagebar in the CLI, so this may be in one day...


Now when you say it I think my examples is a bit incorrect. The CLI response always seems to begin with all the request parameters and then the response parameters come starting with the "count" item. At least this is the case when I use the CLI interface from a standard telnet window. So this should take care of the client state problem you mentioned.

It's not really a problem. I was just pointing out the mode stuff for the player is keeping the state (SS knows what the player is showing), whereas for the web and CLI, the state is exported to the client (in a URL for the web).



Well [the level] can actually differ between the menu elements.

OK. I was thinking that if you want 2 different ways of browsing the same data (two hierarchies, f.e. genres->albums->artists and genres->artists->albums), you'd need two different levels that show the same stuff (f.e. genreAlbum and genreArtist).




There might be some other stuff to consider to include:
- playable (as you suggested, every level in Custom Browse is not playable)
- a list of available mixers for the element (could maybe also be implemented as a separate command).
It might also be a good idea to consider "add" besides "play", and maybe also "insert". As I just mentioned a "mix" command is probably also a good idea, to be able to execute one of the mixers available for an item.

Right. My issue is that every item, however trivial, now becomes huge. F.e. artwork URLs are all the same save for the id which is different. Same for the play link, the mix links, etc. So you end up with a album list:



{ level:album,
itemname:An Album,
itemtype: album,
itemid: 33,
play: playlistctrl cmd:play album_id:33,
mix1: whatever1 album_id:33,
artwork: blablabla/blablabla/33/cover.jpg
},
{ level:album,
itemname:Another Album,
itemtype: album,
itemid: 34,
play: playlistctrl cmd:play album_id:34,
mix1: whatever1 album_id:34,
artwork: blablabla/blablabla/34/cover.jpg
},
{ level:album,
itemname:A Third Album,
itemtype: album,
itemid: 35,
play: playlistctrl cmd:play album_id:35,
mix1: whatever1 album_id:35,
artwork: blablabla/blablabla/35/cover.jpg
},


All this redundant text will take time to generate, transmit, parse and interpret. And this does not cover (yet) other stuff like search. And it cannot cover status (now playing).

So I am trying to find some middle-ground...

Fred

erland
2007-05-10, 10:16
OK. I was thinking that if you want 2 different ways of browsing the same data (two hierarchies, f.e. genres->albums->artists and genres->artists->albums), you'd need two different levels that show the same stuff (f.e. genreAlbum and genreArtist).Just to make sure we understand each other. Let's say you would like a menu like this ("level" values between parenteses)


Genres(genres)
----Albums(albums)
--------Artists(artists)
----Artists(artists)
--------Albums(albums)

If you like to browse down into the genres->albums->artists menu you would issue a command like:
custombrowse browse genres,albums,artists genres=genres albums=albums artists=artists

In the same way if you like to browse down into the genres->artists->albums you would issue a command like:
custombrowse browse genres,artists,albums genres=genres artists=artists albums=albums



Right. My issue is that every item, however trivial, now becomes huge. F.e. artwork URLs are all the same save for the id which is different. Same for the play link, the mix links, etc. So you end up with a album list:
...

All this redundant text will take time to generate, transmit, parse and interpret. And this does not cover (yet) other stuff like search. And it cannot cover status (now playing).

I have worked a bit more on my custom CLI interface for the Custom Browse plugin, at the moment it looks as follows:


BROWSE command (syntax + sample command):
custombrowse browse <start> <noOfItems> <hierarchy> <additional parameters>
custombrowse browse 0 10 artists,artist artists=artists artist=1

BROWSE response (syntax + sample command)
custombrowse browse <start> <noOfItems> <hierarchy> <additional parameters> count:<noOfResults> level:<level> itemid:<id> itemname:<name> itemplayable:<playable> itemmixable:<mixable>
custombrowse browse 0 10 artists,artist artists=artists artist=1 count:2 level:album itemid:1 itemname:Album1 itemplayable:1 itemmixable:0 level:album itemid:2 itemname:Album2 itemplayable:1 itemmixable:1

PLAY command (syntax + sample command):
custombrowse play <hierarchy> <additional parameters>
custombrowse play artists,artist,album artists=artists artist=1 album=2

ADD command (syntax + sample command):
custombrowse add <hierarchy> <additional parameters>
custombrowse add artists,artist,album artists=artists artist=1 album=2

MIXES command (syntax + sample command):
custombrowse mixes <hierarchy> <additional parameters>
custombrowse mixes artists,artist,album artists=artists artist=1 album=2

MIXES response (syntax + sample command):
custombrowse mixes <hierarchy> <additional parameters> count:<noOfResults> mixid:<id> mixname:<name>
custombrowse mixes artists,artist,album artists=artists artist=1 album=2 count:2 mixid:musicmagic mixname:MusicMagic mixid:random mixname:RandomAlbum

MIX command (syntax + sample command):
custombrowse mix <mixid> <hierarchy> <additional parameters>
custombrowse mix 2 artists,artist,album artists=artists artist=1 album=2

I'm not sure if the above will give you any ideas, but I thought I show it to you in case it does.

The mixes command will not be optimal for performance since it must be called for each item which has itemmixable=1, but I haven't figured you how to return an array inside an array over the CLI from a plugin using the Request class in 6.5. I'm using the Slim::Control::Request->addResultLoop method to fill in the item result, but I don't think I can use that for a nested "mixes" array within each "item" returned. So I choosed to implement a separate "mixes" command instead.

Fred
2007-05-11, 11:50
Re. the menu, I'd like



Albums by genre (genres)
-Rock(genre)
--A Rock Album(album)
Artists by genre(genres)
-Rock(genre)
--A Rock Artist(artist)


i.e. not something that displays "Albums" and/or "Artists" under "Genres". The labels for that cannot be both "genres" as what is the label returned by the command:
custombrowse browse genres,genre genres=genres genre=Rock
?

Re. the commands, yes it gives me some ideas. I'd just like for the sake of coherence to have all parameters after paging be tagged and use the : syntax instead of =....

custombrowse browse 0 10 hierarchy:artists,artist artists:artists artist:1

or even

custombrowse browse 0 10 hierarchy:artists,artist ids:artists,1


You're right, at the moment there is no way to have a loop inside a loop. The problem is that it starts to become complicated to parse when it's serialized on a text line. Not everybody uses Perl as a language to control SS!
Your solution works, but it's inefficient if mixes info is needed. Need to think about that!

Thanks

Fred

erland
2007-05-11, 13:31
Re. the menu, I'd like



Albums by genre (genres)
-Rock(genre)
--A Rock Album(album)
Artists by genre(genres)
-Rock(genre)
--A Rock Artist(artist)


i.e. not something that displays "Albums" and/or "Artists" under "Genres". The labels for that cannot be both "genres" as what is the label returned by the command:
custombrowse browse genres,genre genres=genres genre=Rock
?

In the current Custom Browse configuration a menu like that would be defined as:


Albums by genre (genrealbums)
-Rock(genre)
--A Rock Album(album)
Artists by genre(genreartists)
-Rock(genre)
--A Rock Artist(artist)

Which would make a command like:
custombrowse browse genrealbums,genre genrealbums=genrealbums genre=Rock

To return:
A Rock Album

And a command like:
custombrowse browse genreartists,genre genreartists=genreartists genre=Rock

Would return:
A Rock Artists

Of course if you like the top elements to have same level you could also make sure that they have different id's, so the first command based on your menus example would look like:
custombrowse browse genres,genre genres=1 genre=Rock

And the second based on your structure:
custombrowse browse genres,genre genres=2 genre=Rock


Re. the commands, yes it gives me some ideas. I'd just like for the sake of coherence to have all parameters after paging be tagged and use the : syntax instead of =....

custombrowse browse 0 10 hierarchy:artists,artist artists:artists artist:1

or even

custombrowse browse 0 10 hierarchy:artists,artist ids:artists,1

At the moment I register my CLI callback with:
Slim::Control::Request::addDispatch(['custombrowse','browse','_start','_itemsPerRespons e','_hierarchy'], [1, 1, 0, \&cliHandler]);

Which means that I inside the callback to get the hierarchy parameter I do:
$request->getParam('_hierarchy');

And to get the additional parameters after the hierarchy parameter I do:
$request->getParam('_p5');
$request->getParam('_p6');
$request->getParam('_p7');

Should I do the same when using your tagged parameter syntax but parse the name:value part inside my callback or is this parsing already handled in some SlimServer code ?

I'm just asking in case you see this before I have figured it out myself.


Your solution works, but it's inefficient if mixes info is needed. Need to think about that!I agree.