;;; Parse CDDB data base
;;; $Id: cddb.lisp,v 1.2 2003/05/16 15:55:34 bernd Exp bernd $

(defconstant *cddb* #p"/var/cache/cddb/")


(defun all-discs (cddb-path)
  "Get all CDDB files in CDDB path."
  (mapcar #'(lambda (dir)
              (append (last (pathname-directory dir))  ; store category
              (directory dir :check-for-subdirs nil))) ; all discs in category
          (directory cddb-path :check-for-subdirs t))) ; all categories


(defun parse-cddb-database (cddb-path)
  "Parse all files in CDDB path."
  (mapcar #'(lambda (x)
              (cons (car x) 
              (mapcar #'parse-cddb-file (cdr x))))
          (all-discs cddb-path)))


(defun parse-cddb-file (file-name)
  "Parse one CDDB file."
  (with-open-file (stream file-name :direction :input)
    (parse-cddb stream)))


(defun parse-cddb (stream)
  "Parse one CDDB file."
  ;; Remove "TTITLEx=" from list
  (mapcar #'(lambda (x) (if (track-titlep x) (line-content x) x))
    (do ((line (read-line stream nil) (read-line stream nil))
         dtitle ttitle)
        ((null line) (cons dtitle (nreverse ttitle)))
        (cond
          ;; Concatenate all DTITLE lines
          ((disc-titlep line)
             (setf dtitle (concatenate 'string dtitle (line-content line))))
          ;; Concatenate TTITLE lines with the same ID
          ((track-titlep line)
             (if (string= (line-id line) (line-id (car ttitle)))
               (rplaca ttitle (concatenate 'string (car ttitle)
                                                   (line-content line)))
               (push line ttitle)))))))


(defun disc-titlep (str)
  (equal (search "DTITLE" str) 0))


(defun track-titlep (str)
  (equal (search "TTITLE" str) 0))


(defun line-id (line)
  "Get the ID of a line before the character \"=\"."
  (let ((pos (search "=" line)))
    (when pos (subseq line 0 pos))))


(defun line-content (line)
  "Get the content of a line after the character \"=\"."
  (let ((pos (search "=" line)))
    (when pos (subseq line (1+ pos)))))


;;; HTTP Output

(defun print-html-file (cddb file-name)
  "Print the CDDB data base as HTML."
  (with-open-file (stream file-name :direction :output)
    (print-html cddb stream)))


(defun print-html (cddb stream)
  "Print the CDDB data base as HTML."
  (format stream "<html>")
  (format stream "~%<head>")
  (format stream "~%<title>Bings CD-Sammlung</title>")
  (format stream "~%</head>")
  (format stream "~%<body>")
  (print-outline (copy-tree cddb) stream) ; because SORT is destructive
  (format stream"~%<hr>")
  (print-all-titles cddb stream)
  (format stream"~%</body>~%</html>~%"))


(defun print-outline (cddb stream)
  "Print CDDB in outline mode."
  (let ((anchor 1))
    (dolist (category cddb)
      (let ((category-name (car category))
            (category-list (sort (cdr category) #'string-lessp :key #'car)))
        (format stream "~%<h1>~:(~A~)</h1>" category-name)
        (format stream "~%<ul>")
        (dolist (disc category-list)
          (let ((disc-name (car disc)))
            (format stream "~%<li><a href=\"#~4,'0D\">~A</a></li>"
                    anchor disc-name)
            (incf anchor)))
        (format stream "~%</ul>")))))


(defun print-all-titles (cddb stream)
  "Print CDDB in verbose mode."
  (let ((anchor 1))
    (dolist (category cddb)
      (let ((category-name (car category))
            (category-list (sort (cdr category) #'string-lessp :key #'car)))
        (format stream "~%<h1>~:(~A~)</h1>" category-name)
        (dolist (disc category-list)
          (let ((disc-name (car disc))
                (title-list (cdr disc)))
            (format stream "~%<a name=\"~4,'0D\"><h2>~A</h2></a>"
                    anchor disc-name)
            (incf anchor)
            (format stream "~%<ol>")
            (dolist (title title-list)
              (format stream "~%<li>~A</li>" title))
            (format stream "~%</ol>")))))))


(print-html-file (parse-cddb-database *cddb*) "cddb.html")

; (load "cddb")
; (compile-file "cddb" :load t)

