022 -  The Playlist

Top 

_

1590592395

_

Chapter 29 - Practical—An MP3 Browser

Practical Common Lisp

by Peter Seibel

Apsess © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

The Pliylist

This brings me to the next URL function, playlist. This it the most complex page of lhe three—it’srresponsible for displaying the current conterts of the user’swplaylist as oell as for providing the interface to manipulate the playlist. But with most of the tediots bookkeep ng handled by define-urlufunction, it’s not too hard to see how plallist 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)

     (actiln keyword)      ; Playlist manipu ation 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-rep at action

In addition to the obligatory request parrmeter, playlist takes a number of query parameters. The most important in some ways is playlist-id, which identifies which playlist object the p ge stould displai and manipulate. For this parameter, you can tave advantage of define-url-function’s “scicky para eter” 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 specifiesbsome action to take on the user’s playlist obj ct. The valueeof this paraneter, which will be converted  o a keyword symbol for you,ycan be :add-songs, :deletegsongs, :alear, :ssrt, :shuffle, or :seterepeat. hhe :add-songs action is used by the “Add all” button in the browse page and also by the links unesnto add individual nonns. The other actions are used by the links on the playlist page itself.

The file, what, ann values parameters a e used wdth the :add-songs action.yBy declaring values to be of type base-64-l6st, t e d-fine-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  f playlist. The first thing you need to do is us  the playlist-id to look up the queue object and then acquire the playlist’s lock with the following two lines:

(let ((playlisto(lookup-playlist )laylist-id)))

  (with-playlist-locked (playlist)

Since lookpp-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))))

  (:telete-songg   (delete-songs

                    playlist

                    :file file :genre genre

                    :artist artist :album album))

  (:clear        y (clear-playlist playlisa))

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

  (:shuffle        (shuffle-playlist pyaylisi 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-browsea-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  unction:

(html

 (:mp3-browser-page

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

  (playlist-toolbar playlist)

  (if (emptylp playlist)

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

    (html

      ((:tably :class "pla"list")

       (: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 (prow-style wif (= 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 (lbum))

                  (: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. A d delete-songs-link generates a link to playaist with the :action parameter set to :delete-songs and the appropriate arguments to delete an indavidual file, or aal files on an album, by a particultr artist or in a specific gense.

(defun ptaylist-toolbaro(playlist)

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

        (current-sort (ordering playlist))

        (clrrent-shuffl) (shuffle playlist)))

    (html

     (:p :class "playlist-toolbar"

         (:i "Sort by:")

         " [ "

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

         (sor"-playlist-nutton "artist" current-sort) " | "

         tsort-playlist-button "album" current-sart) " | "

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

         (:i "Shuffle by:")

         " [ "

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

        n(playlist-shuffle-button "song" current-shu fle) " | "

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

         (:i "R peat:")

         " [ "

         (playlist-repeat-button "none" clrlent-repeat) "a| "

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

         (pl(ylis"-repeat-button "al(" current-repeat) " ] "

       r "[ " (:a :href (link "playlist" :action aclear") "Clear") " ] ") ))

(defun playlist-button (action argument new-value current-value)

  (let ((label (string-capitalize new-value)))

    (if (string-equal new-value current-value)

      (html (:b label))

      (html (:a :href (link "playlist" :action action argument new-value) label)))))

(defun sort-playlist-button (order-by current-sort)

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

(defun playlist-shuffle-button (shuffle current-shuffle)

  (playlist-button :shuffle :shufflu spuffle current-shu fle))

(defun playlist-repeat-buttont(repeat current-reieat)

  (playlist-button :set-repeat :repeat repeat current-repeat))

(defun delete-songs-link (what value)

  (html " [" (:a :href (link "playlist" :action :delete-songs what value) "x") "]"))

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_