git.fiddlerwoaroof.com
sqlite.lisp
257b51a8
 (defpackage :aion.sqlite
   (:use :cl )
   (:import-from :aion.parser
                 #:handle-begin #:handle-end
                 #:handle-property #:process-ics)
   (:export
    #:ics->sqlite))
 (in-package :aion.sqlite)
 
 (fw.lu:defclass+ emit-sql ()
   ((%lines :accessor lines :initform ())
    (%tzid :accessor tzid :initform nil)
    (%db :reader db :initarg :db)))
 (defmethod handle-begin ((client emit-sql) block)
   (values))
 (defmethod handle-end :after ((client emit-sql) block)
   (setf (lines client) nil))
 (defmethod handle-end ((client emit-sql) block)
   )
 (defmethod handle-end :before ((client emit-sql) block)
   (values))
 (defmethod handle-end ((client emit-sql) (block (eql :vevent)))
   (macrolet ((get-setter ()
                `(sxql:set= ,@(mapcan (lambda (it)
                                        (list it `(serapeum:assocadr
                                                   ,(alexandria:make-keyword
                                                     (substitute #\- #\_
                                                                 (string it)))
                                                   (lines client))))
4923642f
                                      '(:GEO :UID :URL :TZID :CLASS
                                        :DTEND :RRULE :ACTION :ATTACH
                                        :EXDATE :METHOD :PRODID :STATUS
                                        :TRANSP :TZNAME :VALARM :VEVENT
                                        :CREATED :DTSTAMP :DTSTART
                                        :SUMMARY :TRIGGER :VERSION
                                        :ATTENDEE :CALSCALE :LOCATION
                                        :SEQUENCE :ORGANIZER
                                        :CATEGORIES :TZOFFSETTO
                                        :X_ALT_DESC :DESCRIPTION
                                        :ACKNOWLEDGED :TZOFFSETFROM
                                        :X_WR_CALDESC :X_WR_CALNAME
                                        :LAST_MODIFIED :RECURRENCE_ID
                                        :X_WR_ALARMUID :X_WR_TIMEZONE
                                        :X_LIC_LOCATION
                                        :X_APPLE_DEFAULT_ALARM
                                        :X_APPLE_TRAVEL_ADVISORY_BEHAVIOR)))))
257b51a8
     (multiple-value-bind (query params)
         (sxql:yield
          (sxql:insert-into :vevent
            (get-setter)))
       (apply 'sqlite:execute-single
              (db client)
              query
              params))))
 (defmethod handle-property ((client emit-sql) tag params content)
   (push (list tag content)
         (lines client)))
 
 (defparameter +datetime-scanner+
   (cl-ppcre:create-scanner
    '(:SEQUENCE
      :START-ANCHOR
      ;; date yyyy-mm-dd
      (:REGISTER (:GREEDY-REPETITION 4 4 :DIGIT-CLASS))
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      #\T
      ;; time hh-mm-ss
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      ;; tz
      (:GREEDY-REPETITION 0 1 (:REGISTER #\Z))
      :END-ANCHOR)))
 (defparameter +date-scanner+
   (cl-ppcre:create-scanner
    '(:SEQUENCE
      :START-ANCHOR
      ;; date yyyy-mm-dd
      (:REGISTER (:GREEDY-REPETITION 4 4 :DIGIT-CLASS))
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      :END-ANCHOR)))
 (defparameter +time-scanner+
   (cl-ppcre:create-scanner
    '(:SEQUENCE
      :START-ANCHOR
      ;; time hh-mm-ss
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
      ;; tz
      (:GREEDY-REPETITION 0 1 (:REGISTER #\Z))
      :END-ANCHOR)))
 
 (defparameter +sqlite-format+
   '((:YEAR 4) #\- (:MONTH 2) #\- (:DAY 2)
     #\space (:HOUR 2) #\: (:MIN 2) #\: (:SEC 2)
     :GMT-OFFSET-OR-Z))
 
 (defun parse-datetime (time timezone)
   (trivia:ematch (nth-value 1 (cl-ppcre:scan-to-strings +datetime-scanner+ time))
     (#(ye mo da ho mi se tz)
       (local-time:encode-timestamp
        0
        (fw.lu:if-let* ((se (parse-integer se))
                        (_ (= 60 se)))
          59
          se)
        (parse-integer mi) (parse-integer ho)
        (parse-integer da) (parse-integer mo) (parse-integer ye)
        :timezone (if (equal tz "Z")
                      local-time:+utc-zone+
                      timezone)))))
 
 (defun parse-date (time)
   (trivia:ematch (nth-value 1 (cl-ppcre:scan-to-strings +date-scanner+ time))
     (#(ye mo da)
       (local-time:encode-timestamp
        0 0 0 0
        (parse-integer da) (parse-integer mo) (parse-integer ye)))))
 
 ;;what do I do for the date here???
 #+(or)
 (defun parse-time (time)
   (trivia::match (nth-value 1 (cl-ppcre:scan-to-strings +time-scanner+ time))
     (#(ho mi se)
       (local-time:make-timestamp
        0 0 0 0
        (parse-integer da) (parse-integer mo) (parse-integer ye)))))
 
 (defun handle-ical-date (client tag params content)
   (push (list tag
               (local-time:format-timestring
                nil
                (case (alexandria:make-keyword
                       (string-upcase
                        (serapeum:assocadr :value params)))
                  (:date (parse-date content))
                  (t (parse-datetime content
                                     (or (local-time:find-timezone-by-location-name
                                          (serapeum:assocadr :tzid params))
                                         (tzid client)))))
                :format +sqlite-format+))
         (lines client)))
 
 (defmethod handle-property ((client emit-sql) (tag (eql :dtstart)) params content)
   (handle-ical-date client tag params content))
 (defmethod handle-property ((client emit-sql) (tag (eql :dtend)) params content)
   (handle-ical-date client tag params content))
 (defmethod handle-property ((client emit-sql) (tag (eql :created)) params content)
   (handle-ical-date client tag params content))
 (defmethod handle-property ((client emit-sql) (tag (eql :dtstamp)) params content)
   (handle-ical-date client tag params content))
 
 (defun setup-sql ()
   (sxql:yield
4923642f
    (sxql:make-statement
     :create-table '(:vevent :if-not-exists t)
     (mapcar (lambda (it)
               (sxql.clause:make-column-definition-clause it :type 'text))
             '(:GEO :UID :URL :TZID :CLASS :DTEND :RRULE :ACTION :ATTACH :EXDATE :METHOD
               :PRODID :STATUS :TRANSP :TZNAME :VALARM :VEVENT :CREATED :DTSTAMP :DTSTART
               :SUMMARY :TRIGGER :VERSION :ATTENDEE :CALSCALE :LOCATION :SEQUENCE :ORGANIZER
               :CATEGORIES :TZOFFSETTO :X_ALT_DESC :DESCRIPTION :ACKNOWLEDGED :TZOFFSETFROM
               :X_WR_CALDESC :X_WR_CALNAME :LAST_MODIFIED :RECURRENCE_ID :X_WR_ALARMUID
               :X_WR_TIMEZONE :X_LIC_LOCATION :X_APPLE_DEFAULT_ALARM
               :X_APPLE_TRAVEL_ADVISORY_BEHAVIOR))
     (sxql:primary-key '(:sequence :uid :recurrence_id)))
    #+(or)
257b51a8
    (sxql:create-table (:vevent :if-not-exists t)
        ((:ATTACH :type 'text)
         (:ATTENDEE :type 'text)
         (:CATEGORIES :type 'text)
         (:CREATED :type 'text)
         (:DESCRIPTION :type 'text)
         (:DTEND :type 'text)
         (:DTSTAMP :type 'text)
         (:DTSTART :type 'text)
         (:EXDATE :type 'text)
         (:GEO :type 'text)
         (:LAST_MODIFIED :type 'text)
         (:LOCATION :type 'text)
         (:ORGANIZER :type 'text)
         (:RECURRENCE_ID :type 'text)
         (:RRULE :type 'text)
         (:SEQUENCE :type 'text)
         (:STATUS :type 'text)
         (:SUMMARY :type 'text)
         (:TRANSP :type 'text)
         (:UID :type 'text)
         (:URL :type 'text)
         (:VALARM :type 'text)
         (:VEVENT :type 'text)
         (:X_ALT_DESC :type 'text)
         (:X_APPLE_TRAVEL_ADVISORY_BEHAVIOR :type 'text))
      (sxql:primary-key '(:sequence :uid :recurrence_id)))))
 
 (defun ics->sqlite (fn data)
   (sqlite:with-open-database (db fn)
     (sqlite:execute-non-query
      db (sxql:yield (sxql:drop-table :vevent :if-exists t)))
     (sqlite:execute-non-query
      db (setup-sql))
     (sqlite:with-transaction db
       (process-ics (emit-sql db) data))))