passing the right arguments depending on the value of what. This is also where you select a random subset of the matching rows if necessary.

(defun values-for-page (what genre artist album random)

(let ((values

(select

:from *mp3s*

:columns (if (eql what :song) t what)

:where (matching *mp3s* :genre genre :artist artist :album album)

:distinct (not (eql what :song))

:order-by (if (eql what :song) '(:album :track) what))))

(if random (random-selection values random) values)))

To generate the title for the browse page, you pass the browsing criteria to the following function, browse-page-title:

(defun browse-page-title (what random genre artist album)

(with-output-to-string (s)

(when random (format s '~:(~r~) Random ' random))

(format s '~:(~a~p~)' what random)

(when (or genre artist album)

(when (not (eql what :song)) (princ ' with songs' s))

(when genre (format s ' in genre ~a' genre))

(when artist (format s ' by artist ~a ' artist))

(when album (format s ' on album ~a' album)))))

Once you have the values you want to present, you need to do two things with them. The main task, of course, is to present them, which happens in the do-rows loop, leaving the rendering of each row to the function list-item-for-page. That function renders :song rows one way and all other kinds another way.

(defun list-item-for-page (what row)

(if (eql what :song)

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

(html

(:li

(:a :href (link 'playlist' :file file :action 'add-songs') (:b song)) ' from '

(:a :href (link 'browse' :what :song :album album) album) ' by '

(:a :href (link 'browse' :what :song :artist artist) artist) ' in genre '

(:a :href (link 'browse' :what :song :genre genre) genre))))

(let ((value (column-value row what)))

(html

(:li value ' - '

(browse-link :genre what value)

(browse-link :artist what value)

(browse-link :album what value)

(browse-link :song what value))))))

(defun browse-link (new-what what value)

(unless (eql new-what what)

(html

'['

(:a :href (link 'browse' :what new-what what value) (:format '~(~as~)' new-what))

'] ')))

The other thing on the browse page is a form with several hidden INPUT fields and an 'Add all' submit button. You need to use an HTML form instead of a regular link to keep the application stateless—to make sure all the information needed to respond to a request comes in the request itself. Because the browse page results can be partially random, you need to submit a fair bit of data for the server to be able to reconstitute the list of songs to add to the playlist. If you didn't allow the browse page to return randomly generated results, you wouldn't need much data—you could just submit a request to add songs with whatever search criteria the browse page used. But if you added songs that way, with criteria that included a random argument, then you'd end up adding a different set of random songs than the user was looking at on the page when they hit the 'Add all' button.

The solution you'll use is to send back a form that has enough information stashed away in a hidden INPUT element to allow the server to reconstitute the list of songs matching the browse page criteria. That information is the list of values returned by values-for-page and the value of the what parameter. This is where you use the base64-list parameter type; the function values->base64 extracts the values of a specified column from the table returned by values-for-page into a list and then makes a base 64-encoded string out of that list to embed in the form.

(defun values->base-64 (column values-table)

(flet ((value (r) (column-value r column)))

(obj->base64 (map-rows #'value values-table))))

When that parameter comes back as the value of the values query parameter to a URL function that declares values to be of type base-64-list, it'll be automatically converted back to a list. As you'll see in a moment, that list can then be used to construct a query that'll return the correct list of songs.[310] When you're browsing by :song, you use the values from the :file column since they uniquely identify the actual songs while the song names may not.

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

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату