Выбрать главу

The Playlist

This brings me to the next URL function, playlist. This is the most complex page of the three—it's responsible for displaying the current contents of the user's playlist as well as for providing the interface to manipulate the playlist. But with most of the tedious bookkeeping handled by define-url-function, it's not too hard to see how playlist works. Here's the beginning of the definition, with just the parameter list:

(define-url-function playlist

(request

(playlist-id string (playlist-id request) :package)

(action keyword) ; Playlist manipulation action

(what keyword :file) ; for :add-songs action

(values base-64-list) ; "

file ; for :add-songs and :delete-songs actions

genre ; for :delete-songs action

artist ; "

album ; "

(order-by keyword) ; for :sort action

(shuffle keyword) ; for :shuffle action

(repeat keyword)) ; for :set-repeat action

In addition to the obligatory request parameter, playlist takes a number of query parameters. The most important in some ways is playlist-id, which identifies which playlist object the page should display and manipulate. For this parameter, you can take advantage of define-url-function's "sticky parameter" feature. Normally, the playlist-id won't be supplied explicitly, defaulting to the value returned by the playlist-id function, namely, the IP address of the client machine on which the browser is running. However, users can also manipulate their playlists from different machines than the ones running their MP3 clients by allowing this value to be explicitly specified. And if it's specified once, define-url-function will arrange for it to "stick" by setting a cookie in the browser. Later you'll define a URL function that generates a list of all existing playlists, which users can use to pick a playlist other than the one for the machines they're browsing from.

The action parameter specifies some action to take on the user's playlist object. The value of this parameter, which will be converted to a keyword symbol for you, can be :add-songs, :delete-songs, :clear, :sort, :shuffle, or :set-repeat. The :add-songs action is used by the "Add all" button in the browse page and also by the links used to add individual songs. The other actions are used by the links on the playlist page itself.

The file, what, and values parameters are used with the :add-songs action. By declaring values to be of type base-64-list, the define-url-function infrastructure will take care of decoding the value submitted by the "Add all" form. The other parameters are used with other actions as noted in the comments.

Now let's look at the body of playlist. The first thing you need to do is use the playlist-id to look up the queue object and then acquire the playlist's lock with the following two lines:

(let ((playlist (lookup-playlist playlist-id)))

(with-playlist-locked (playlist)

Since lookup-playlist will create a new playlist if necessary, this will always return a playlist object. Then you take care of any necessary queue manipulation, dispatching on the value of the action parameter in order to call one of the playlist functions.

(case action

(:add-songs (add-songs playlist what (or values (list file))))

(:delete-songs (delete-songs

playlist

:file file :genre genre

:artist artist :album album))

(:clear (clear-playlist playlist))

(:sort (sort-playlist playlist order-by))

(:shuffle (shuffle-playlist playlist shuffle))

(:set-repeat (setf (repeat playlist) repeat)))

All that's left of the playlist function is the actual HTML generation. Again, you can use the :mp3-browser-page HTML macro to make sure the basic form of the page matches the other pages in the application, though this time you pass NIL to the :header argument in order to leave out the H1 header. Here's the rest of the function:

(html

(:mp3-browser-page

(:title (:format "Playlist - ~a" (id playlist)) :header nil)

(playlist-toolbar playlist)

(if (empty-p playlist)

(html (:p (:i "Empty.")))

(html

((:table :class "playlist")

(:table-row "#" "Song" "Album" "Artist" "Genre")

(let ((idx 0)

(current-idx (current-idx playlist)))

(do-rows (row (songs-table playlist))

(with-column-values (track file song album artist genre) row

(let ((row-style (if (= idx current-idx) "now-playing" "normal")))

(html

((:table-row :class row-style)

track

(:progn song (delete-songs-link :file file))

(:progn album (delete-songs-link :album album))

(:progn artist (delete-songs-link :artist artist))

(:progn genre (delete-songs-link :genre genre)))))

(incf idx))))))))))))

The function playlist-toolbar generates a toolbar containing links to playlist to perform the various :action manipulations. And delete-songs-link generates a link to playlist with the :action parameter set to :delete-songs and the appropriate arguments to delete an individual file, or all files on an album, by a particular artist or in a specific genre.

(defun playlist-toolbar (playlist)

(let ((current-repeat (repeat playlist))

(current-sort (ordering playlist))

(current-shuffle (shuffle playlist)))

(html

(:p :class "playlist-toolbar"

(:i "Sort by:")

" [ "

(sort-playlist-button "genre" current-sort) " | "

(sort-playlist-button "artist" current-sort) " | "

(sort-playlist-button "album" current-sort) " | "

(sort-playlist-button "song" current-sort) " ] "

(:i "Shuffle by:")

" [ "

(playlist-shuffle-button "none" current-shuffle) " | "

(playlist-shuffle-button "song" current-shuffle) " | "

(playlist-shuffle-button "album" current-shuffle) " ] "