values. Thus, to add the right songs, you need to first build a table object containing the desired values, which you can then use with an in query against the song database. So, add-songs looks like this:

(defun add-songs (playlist column-name values)

(let ((table (make-instance

'table

:schema (extract-schema (list column-name) (schema *mp3s*)))))

(dolist (v values) (insert-row (list column-name v) table))

(do-rows (row (select :from *mp3s* :where (in column-name table)))

(insert-row row (songs-table playlist))))

(update-current-if-necessary playlist))

Deleting songs is a bit simpler; you just need to be able to delete songs from the songs-table that match particular criteria—either a particular song or all songs in a particular genre, by a particular artist, or from a particular album. So, you can provide a delete-songs function that takes keyword/value pairs, which are used to construct a matching :where clause you can pass to the delete- rows database function.

Another complication that arises when deleting songs is that current-idx may need to change. Assuming the current song isn't one of the ones just deleted, you'd like it to remain the current song. But if songs before it in songs-table are deleted, it'll be in a different position in the table after the delete. So after a call to delete-rows, you need to look for the row containing the current song and reset current-idx. If the current song has itself been deleted, then, for lack of anything better to do, you can reset current-idx to zero. After updating current-idx, calling update- current-if-necessary will take care of updating current-song. And if current- idx changed but still points at the same song, current-song will be left alone.

(defun delete-songs (playlist &rest names-and-values)

(delete-rows

:from (songs-table playlist)

:where (apply #'matching (songs-table playlist) names-and-values))

(setf (current-idx playlist) (or (position-of-current playlist) 0))

(update-current-if-necessary playlist))

(defun position-of-current (playlist)

(let* ((table (songs-table playlist))

(matcher (matching table :file (file (current-song playlist))))

(pos 0))

(do-rows (row table)

(when (funcall matcher row)

(return-from position-of-current pos))

(incf pos))))

You can also provide a function to completely clear the playlist, which uses delete-all-rows and doesn't have to worry about finding the current song since it has obviously been deleted. The call to update-current-if-necessary will take care of setting current-song to NIL.

(defun clear-playlist (playlist)

(delete-all-rows (songs-table playlist))

(setf (current-idx playlist) 0)

(update-current-if-necessary playlist))

Sorting and shuffling the playlist are related in that the playlist is always either sorted or shuffled. The shuffle slot says whether the playlist should be shuffled and if so how. If it's set to :none, then the playlist is ordered according to the value in the ordering slot. When shuffle is :song, the playlist will be randomly permuted. And when it's set to :album, the list of albums is randomly permuted, but the songs within each album are listed in track order. Thus, the sort-playlist function, which will be called by the Web interface code whenever the user selects a new ordering, needs to set ordering to the desired ordering and set shuffle to :none before calling order- playlist, which actually does the sort. As in delete-songs, you need to use position-of-current to reset current-idx to the new location of the current song. However, this time you don't need to call update-current-if-necessary since you know the current song is still in the table.

(defun sort-playlist (playlist ordering)

(setf (ordering playlist) ordering)

(setf (shuffle playlist) :none)

(order-playlist playlist)

(setf (current-idx playlist) (position-of-current playlist)))

In order-playlist, you can use the database function sort-rows to actually perform the sort, passing a list of columns to sort by based on the value of ordering.

(defun order-playlist (playlist)

(apply #'sort-rows (songs-table playlist)

(case (ordering playlist)

(:genre '(:genre :album :track))

(:artist '(:artist :album :track))

(:album '(:album :track))

(:song '(:song)))))

The function shuffle-playlist, called by the Web interface code when the user selects a new shuffle mode, works in a similar fashion except it doesn't need to change the value of ordering. Thus, when shuffle-playlist is called with a shuffle of :none, the playlist goes back to being sorted according to the most recent ordering. Shuffling by songs is simple—just call shuffle-table on songs-table. Shuffling by albums is a bit more involved but still not rocket science.

(defun shuffle-playlist (playlist shuffle)

(setf (shuffle playlist) shuffle)

(case shuffle

(:none (order-playlist playlist))

(:song (shuffle-by-song playlist))

(:album (shuffle-by-album playlist)))

(setf (current-idx playlist) (position-of-current playlist)))

(defun shuffle-by-song (playlist)

(shuffle-table (songs-table playlist)))

(defun shuffle-by-album (playlist)

(let ((new-table (make-playlist-table)))

(do-rows (album-row (shuffled-album-names playlist))

(do-rows (song (songs-for-album playlist (column-value album-row :album)))

(insert-row song new-table)))

(setf (songs-table playlist) new-table)))

(defun shuffled-album-names (playlist)

(shuffle-table

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

0

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

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