2017-12-20 21:42:32 +01:00
|
|
|
;; Copyright (C) 2016-2017 Vincent Ambo <mail@tazj.in>
|
|
|
|
;;
|
|
|
|
;; This file is part of Gemma.
|
|
|
|
;;
|
|
|
|
;; Gemma is free software: you can redistribute it and/or modify it
|
|
|
|
;; under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
2017-12-19 18:24:40 +01:00
|
|
|
(defpackage gemma
|
|
|
|
(:use :cl
|
|
|
|
:local-time
|
|
|
|
:cl-json))
|
|
|
|
(in-package :gemma)
|
|
|
|
|
|
|
|
;; TODO: Store an average of how many days it was between task
|
|
|
|
;; completions. Some of the current numbers are just guesses
|
|
|
|
;; anyways.
|
|
|
|
|
|
|
|
;;
|
|
|
|
;; Define task management system
|
|
|
|
;;
|
|
|
|
(defclass task ()
|
2017-12-21 01:21:11 +01:00
|
|
|
((id :reader id
|
|
|
|
:initarg :id)
|
|
|
|
|
|
|
|
;; (Unique) name of the task
|
2017-12-19 18:24:40 +01:00
|
|
|
(name :type symbol
|
|
|
|
:initarg :name
|
|
|
|
:accessor name-of)
|
|
|
|
|
|
|
|
;; Maximum completion interval
|
|
|
|
(days :type integer
|
|
|
|
:initarg :days
|
|
|
|
:accessor days-of)
|
|
|
|
|
|
|
|
;; Optional description
|
|
|
|
(description :type string
|
|
|
|
:initarg :description
|
|
|
|
:accessor description-of)
|
|
|
|
|
|
|
|
;; Last completion time
|
2017-12-21 13:54:02 +01:00
|
|
|
(done-at :type timestamp
|
2017-12-21 01:21:11 +01:00
|
|
|
:initarg :done-at
|
2017-12-19 18:24:40 +01:00
|
|
|
:accessor last-done-at)))
|
|
|
|
|
2017-12-21 01:21:11 +01:00
|
|
|
(defvar *gemma-data-dir*
|
|
|
|
(pathname (or (sb-posix:getenv "GEMMA_DATA_DIR")
|
|
|
|
(sb-posix:getcwd)))
|
|
|
|
"Directory in which to store Gemma data.")
|
|
|
|
|
|
|
|
(defvar *p-tasks*
|
|
|
|
(cl-prevalence:make-prevalence-system *gemma-data-dir*)
|
|
|
|
"All tasks registered in this Gemma instance.")
|
|
|
|
|
|
|
|
;; Initialise database ID counter
|
|
|
|
(or (> (length (cl-prevalence:find-all-objects *p-tasks* 'task)) 0)
|
|
|
|
(cl-prevalence:tx-create-id-counter *p-tasks*))
|
2017-12-19 18:24:40 +01:00
|
|
|
|
|
|
|
(defmacro deftask (task-name days &optional description)
|
2017-12-21 01:21:11 +01:00
|
|
|
(unless (get-task task-name)
|
|
|
|
`(progn (cl-prevalence:tx-create-object
|
|
|
|
*p-tasks*
|
|
|
|
'task
|
|
|
|
(quote ((name ,task-name)
|
|
|
|
(days ,days)
|
|
|
|
(description ,(or description ""))
|
2017-12-21 13:54:02 +01:00
|
|
|
(done-at ,(now)))))
|
2017-12-21 01:21:11 +01:00
|
|
|
(cl-prevalence:snapshot *p-tasks*))))
|
2017-12-19 18:24:40 +01:00
|
|
|
|
|
|
|
(defun get-task (name)
|
2017-12-21 01:21:11 +01:00
|
|
|
(cl-prevalence:find-object-with-slot *p-tasks* 'task 'name name))
|
2017-12-19 18:24:40 +01:00
|
|
|
|
|
|
|
(defun list-tasks ()
|
2017-12-21 01:21:11 +01:00
|
|
|
(cl-prevalence:find-all-objects *p-tasks* 'task))
|
2017-12-19 18:24:40 +01:00
|
|
|
|
|
|
|
(defun days-remaining (task)
|
|
|
|
"Returns the number of days remaining before the supplied TASK reaches its
|
|
|
|
maximum interval."
|
2017-12-21 13:54:02 +01:00
|
|
|
(let* ((expires-at (timestamp+ (last-done-at task)
|
|
|
|
(days-of task) :day))
|
|
|
|
(secs-until-expiry (timestamp-difference expires-at (now))))
|
2017-12-19 18:24:40 +01:00
|
|
|
(round (/ secs-until-expiry 60 60 24))))
|
|
|
|
|
|
|
|
(defun sort-tasks (tasks)
|
|
|
|
"Sorts TASKS in descending order by number of days remaining."
|
|
|
|
(sort (copy-list tasks)
|
2017-12-20 19:36:05 +01:00
|
|
|
(lambda (t1 t2) (< (days-remaining t1)
|
2017-12-19 18:24:40 +01:00
|
|
|
(days-remaining t2)))))
|
|
|
|
|
|
|
|
(defun complete-task (name &optional at)
|
|
|
|
"Mark the task with NAME as completed, either now or AT specified time."
|
2017-12-21 01:21:11 +01:00
|
|
|
(cl-prevalence:tx-change-object-slots *p-tasks* 'task
|
|
|
|
(id (get-task name))
|
2017-12-21 13:54:02 +01:00
|
|
|
`((done-at ,(or at (now)))))
|
2017-12-21 01:27:06 +01:00
|
|
|
(cl-prevalence:snapshot *p-tasks*))
|
2017-12-19 18:24:40 +01:00
|
|
|
|
|
|
|
;;
|
|
|
|
;; Define web API
|
|
|
|
;;
|
|
|
|
|
2017-12-20 19:36:31 +01:00
|
|
|
(defun response-for (task)
|
|
|
|
"Create a response object to be JSON encoded for TASK."
|
|
|
|
`((:name . ,(name-of task))
|
|
|
|
(:description . ,(description-of task))
|
|
|
|
(:remaining . ,(days-remaining task))))
|
|
|
|
|
2017-12-19 18:24:40 +01:00
|
|
|
(defun start-gemma ()
|
|
|
|
;; Set up web server
|
|
|
|
(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242))
|
|
|
|
|
|
|
|
;; ... and register all handlers.
|
2017-12-20 19:36:31 +01:00
|
|
|
|
|
|
|
;; Task listing handler
|
2017-12-19 18:24:40 +01:00
|
|
|
(hunchentoot:define-easy-handler
|
|
|
|
(get-tasks :uri "/tasks") ()
|
|
|
|
|
|
|
|
(setf (hunchentoot:content-type*) "application/json")
|
|
|
|
(setf (hunchentoot:header-out "Access-Control-Allow-Origin") "*")
|
2017-12-21 13:54:02 +01:00
|
|
|
(encode-json-to-string
|
2017-12-19 18:24:40 +01:00
|
|
|
;; Construct a frontend-friendly representation of the tasks.
|
2017-12-20 19:36:31 +01:00
|
|
|
(mapcar #'response-for (sort-tasks (list-tasks)))))
|
|
|
|
|
|
|
|
;; Task completion handler
|
|
|
|
(hunchentoot:define-easy-handler
|
|
|
|
(complete-task-handler :uri "/complete") (task)
|
|
|
|
(setf (hunchentoot:content-type*) "application/json")
|
2017-12-21 13:54:02 +01:00
|
|
|
(let* ((key (intern (camel-case-to-lisp task) "GEMMA")))
|
2017-12-20 19:36:31 +01:00
|
|
|
(format t "Marking task ~A as completed" key)
|
|
|
|
(complete-task key)
|
2017-12-21 13:54:02 +01:00
|
|
|
(encode-json-to-string (response-for (get-task key))))))
|
2017-12-19 18:24:40 +01:00
|
|
|
|
|
|
|
;; (not-so) example tasks
|
|
|
|
|
|
|
|
;; Bathroom tasks
|
|
|
|
(deftask bathroom/wipe-mirror 7)
|
|
|
|
(deftask bathroom/wipe-counter 7)
|
|
|
|
|
|
|
|
;; Bedroom tasks
|
|
|
|
(deftask bedroom/change-sheets 7)
|
|
|
|
(deftask bedroom/vacuum 10)
|
|
|
|
|
|
|
|
;; Kitchen tasks
|
|
|
|
(deftask kitchen/normal-trash 3)
|
|
|
|
(deftask kitchen/green-trash 5)
|
|
|
|
(deftask kitchen/blue-trash 5)
|
|
|
|
(deftask kitchen/wipe-counters 3)
|
|
|
|
(deftask kitchen/vacuum 5 "Kitchen has more crumbs and such!")
|
|
|
|
|
|
|
|
;; Entire place
|
|
|
|
(deftask clean-windows 60)
|
|
|
|
|
|
|
|
;; Experimentation / testing stuff
|
|
|
|
|
|
|
|
(defun randomise-completion-times ()
|
|
|
|
"Set some random completion timestamps for all tasks"
|
|
|
|
(mapcar
|
2017-12-21 01:21:11 +01:00
|
|
|
(lambda (task)
|
|
|
|
(complete-task (name-of task)
|
2017-12-21 13:54:02 +01:00
|
|
|
(timestamp- (now)
|
|
|
|
(random 14)
|
|
|
|
:day)))
|
2017-12-21 01:21:11 +01:00
|
|
|
(cl-prevalence:find-all-objects *p-tasks* 'task)))
|
|
|
|
|
|
|
|
(defun clear-all-tasks ()
|
|
|
|
(mapcar (lambda (task) (cl-prevalence:tx-delete-object *p-tasks* 'task (id task)))
|
|
|
|
(cl-prevalence:find-all-objects *p-tasks* 'task)))
|
2017-12-19 18:24:40 +01:00
|
|
|
|
|
|
|
;; (randomise-completion-times)
|