(WIP) try to handle vanished/replaced file; unify partial update functions
This commit is contained in:
parent
22ea8b4e99
commit
09a5d127a1
|
@ -275,7 +275,25 @@ Most changes, whether made through user-editing or by Chronometrist commands, ha
|
|||
|
||||
When @samp{chronometrist-refresh-file} is run by the file system watcher, it uses @samp{chronometrist-file-hash} to assign indices and a hash to @samp{chronometrist--file-state}. The next time the file changes, @samp{chronometrist-file-change-type} compares this state to the current state of the file to determine the type of change made.
|
||||
|
||||
A particular concern is when a change results in the addition of a new task, or removal of the last event of a task - i.e. @samp{chronometrist-task-list} should be updated.
|
||||
Challenges -
|
||||
@enumerate
|
||||
@item
|
||||
Correctly detecting the type of change
|
||||
@item
|
||||
Updating @samp{chronometrist-task-list} and the Chronometrist buffer, when a new task is added or the last interval for a task is removed (v0.6.4)
|
||||
@item
|
||||
Handling changes made to an active interval after midnight
|
||||
@itemize
|
||||
@item
|
||||
use the date from the plist's @samp{:start} timestamp instead of the date today
|
||||
@item
|
||||
:append - normally, add to table; for spanning intervals, invalid operation
|
||||
@item
|
||||
:modify - normally, replace in table; for spanning intervals, split and replace
|
||||
@item
|
||||
:remove - normally, remove from table; for spanning intervals, split and remove
|
||||
@end itemize
|
||||
@end enumerate
|
||||
@end enumerate
|
||||
|
||||
@node Midnight-spanning events
|
||||
|
@ -731,9 +749,7 @@ Function - chronometrist-events-maybe-split (event)
|
|||
@item
|
||||
Function - chronometrist-events-populate ()
|
||||
@item
|
||||
Function - chronometrist-events-add (plist)
|
||||
@item
|
||||
Function - chronometrist-events-replace-last (plist)
|
||||
Function - chronometrist-events-update (plist &optional replace)
|
||||
@item
|
||||
Function - chronometrist-events-subset (start end)
|
||||
@itemize
|
||||
|
|
|
@ -103,6 +103,15 @@ Most changes, whether made through user-editing or by Chronometrist commands, ha
|
|||
|
||||
When =chronometrist-refresh-file= is run by the file system watcher, it uses =chronometrist-file-hash= to assign indices and a hash to =chronometrist--file-state=. The next time the file changes, =chronometrist-file-change-type= compares this state to the current state of the file to determine the type of change made.
|
||||
|
||||
Challenges -
|
||||
1. Correctly detecting the type of change
|
||||
2. Updating =chronometrist-task-list= and the Chronometrist buffer, when a new task is added or the last interval for a task is removed (v0.6.4)
|
||||
3. Handling changes made to an active interval after midnight
|
||||
* use the date from the plist's =:start= timestamp instead of the date today
|
||||
* :append - normally, add to table; for spanning intervals, invalid operation
|
||||
* :modify - normally, replace in table; for spanning intervals, split and replace
|
||||
* :remove - normally, remove from table; for spanning intervals, split and remove
|
||||
|
||||
** Midnight-spanning events
|
||||
:PROPERTIES:
|
||||
:DESCRIPTION: Events starting on one day and ending on another
|
||||
|
@ -302,9 +311,8 @@ Each of these has a corresponding function to clear it and fill it with values -
|
|||
* commented out, unused
|
||||
4. Function - chronometrist-events-maybe-split (event)
|
||||
5. Function - chronometrist-events-populate ()
|
||||
6. Function - chronometrist-events-add (plist)
|
||||
7. Function - chronometrist-events-replace-last (plist)
|
||||
8. Function - chronometrist-events-subset (start end)
|
||||
6. Function - chronometrist-events-update (plist &optional replace)
|
||||
7. Function - chronometrist-events-subset (start end)
|
||||
* ts ts -> hash-table
|
||||
|
||||
** chronometrist-migrate.el
|
||||
|
|
|
@ -28,14 +28,12 @@
|
|||
|
||||
(defvar chronometrist-events (make-hash-table :test #'equal)
|
||||
"Each key is a date in the form (YEAR MONTH DAY).
|
||||
|
||||
Values are lists containing events, where each event is a list in
|
||||
the form (:name \"NAME\" :tags (TAGS) <key value pairs> ...
|
||||
:start TIME :stop TIME).")
|
||||
|
||||
(defun chronometrist-day-start (timestamp)
|
||||
"Get start of day (according to `chronometrist-day-start-time') for TIMESTAMP.
|
||||
|
||||
TIMESTAMP must be a time string in the ISO-8601 format.
|
||||
|
||||
Return value is a time value (see `current-time')."
|
||||
|
@ -51,45 +49,6 @@ Return value is a time value (see `current-time')."
|
|||
(append it timestamp-date-list)
|
||||
(apply #'encode-time it))))
|
||||
|
||||
;; (defun chronometrist-file-clean ()
|
||||
;; "Clean `chronometrist-file' so that events can be processed accurately.
|
||||
;; NOTE - currently unused.
|
||||
|
||||
;; This function splits midnight-spanning intervals into two. It
|
||||
;; must be called before running `chronometrist-populate'.
|
||||
|
||||
;; It returns t if the table was modified, else nil."
|
||||
;; (let ((buffer (find-file-noselect chronometrist-file))
|
||||
;; modified
|
||||
;; expr)
|
||||
;; (with-current-buffer buffer
|
||||
;; (save-excursion
|
||||
;; (goto-char (point-min))
|
||||
;; (while (setq expr (ignore-errors (read (current-buffer))))
|
||||
;; (when (plist-get expr :stop)
|
||||
;; (let ((split-time (chronometrist-midnight-spanning-p (plist-get expr :start)
|
||||
;; (plist-get expr :stop))))
|
||||
;; (when split-time
|
||||
;; (let ((first-start (plist-get (cl-first split-time) :start))
|
||||
;; (first-stop (plist-get (cl-first split-time) :stop))
|
||||
;; (second-start (plist-get (cl-second split-time) :start))
|
||||
;; (second-stop (plist-get (cl-second split-time) :stop)))
|
||||
;; (backward-list 1)
|
||||
;; (chronometrist-sexp-delete-list)
|
||||
;; (-> expr
|
||||
;; (plist-put :start first-start)
|
||||
;; (plist-put :stop first-stop)
|
||||
;; (chronometrist-plist-pp buffer))
|
||||
;; (when (looking-at-p "\n\n")
|
||||
;; (delete-char 2))
|
||||
;; (-> expr
|
||||
;; (plist-put :start second-start)
|
||||
;; (plist-put :stop second-stop)
|
||||
;; (chronometrist-plist-pp buffer))
|
||||
;; (setq modified t))))))
|
||||
;; (save-buffer)))
|
||||
;; modified))
|
||||
|
||||
(defun chronometrist-events-maybe-split (event)
|
||||
"Split EVENT if it spans midnight.
|
||||
|
||||
|
@ -127,25 +86,17 @@ were none."
|
|||
(clrhash chronometrist-events)
|
||||
(chronometrist-sexp-events-populate))
|
||||
|
||||
(defun chronometrist-events-add (plist)
|
||||
"Add new PLIST at the end of `chronometrist-events'."
|
||||
(let* ((date-today (format-time-string "%Y-%m-%d"))
|
||||
(events-today (gethash date-today chronometrist-events)))
|
||||
(--> (list plist)
|
||||
(append events-today it)
|
||||
(puthash date-today it chronometrist-events))))
|
||||
(defun chronometrist-events-update (plist &optional replace)
|
||||
"Add PLIST to the end of `chronometrist-events'.
|
||||
If REPLACE is non-nil, replace the last event with PLIST."
|
||||
(let* ((date (->> (plist-get plist :start)
|
||||
(chronometrist-iso-timestamp->ts )
|
||||
(ts-format "%F" )))
|
||||
(events-today (gethash date chronometrist-events)))
|
||||
(--> (if replace (-drop-last 1 events-today) events-today)
|
||||
(append it (list plist))
|
||||
(puthash date it chronometrist-events))))
|
||||
|
||||
(defun chronometrist-events-replace-last (plist)
|
||||
"Replace the last plist in `chronometrist-events' with PLIST."
|
||||
(let* ((date-today (format-time-string "%Y-%m-%d"))
|
||||
(events-today (gethash date-today chronometrist-events)))
|
||||
(--> (reverse events-today)
|
||||
(cdr it)
|
||||
(append (list plist) it)
|
||||
(reverse it)
|
||||
(puthash date-today it chronometrist-events))))
|
||||
|
||||
;; to be replaced by plist-query
|
||||
(defun chronometrist-events-subset (start end)
|
||||
"Return a subset of `chronometrist-events'.
|
||||
|
||||
|
|
|
@ -395,50 +395,55 @@ Return
|
|||
;; The only interval for TASK is the last expression
|
||||
(setq chronometrist-task-list (remove task chronometrist-task-list)))))
|
||||
|
||||
(defun chronometrist-refresh-file (_fs-event)
|
||||
(defun chronometrist-refresh-file (fs-event)
|
||||
"Re-read `chronometrist-file' and refresh the `chronometrist' buffer.
|
||||
Argument _FS-EVENT is ignored."
|
||||
(run-hooks 'chronometrist-file-change-hook)
|
||||
;; (message "chronometrist - file %s" fs-event)
|
||||
;; `chronometrist-file-change-type' must be run /before/ we update `chronometrist--file-state'
|
||||
;; (the latter represents the old state of the file, which
|
||||
;; `chronometrist-file-change-type' compares with the new one)
|
||||
(aif chronometrist--file-state
|
||||
(let ((change (chronometrist-file-change-type it))
|
||||
(task (plist-get (chronometrist-last) :name)))
|
||||
;; (message "chronometrist - file change type is %s" change)
|
||||
(pcase change
|
||||
(:append
|
||||
(chronometrist-events-add (chronometrist-sexp-last))
|
||||
(chronometrist-add-to-task-list task))
|
||||
(:modify
|
||||
(chronometrist-events-replace-last (chronometrist-sexp-last))
|
||||
(chronometrist-remove-from-task-list task)
|
||||
(chronometrist-add-to-task-list task))
|
||||
(:remove
|
||||
(let* ((date (--> (hash-table-keys chronometrist-events)
|
||||
(last it)
|
||||
(car it)))
|
||||
(old-task (--> (gethash date chronometrist-events)
|
||||
(last it)
|
||||
(car it)
|
||||
(plist-get it :name))))
|
||||
(chronometrist-remove-from-task-list old-task)
|
||||
(--> (gethash date chronometrist-events)
|
||||
(-drop-last 1 it)
|
||||
(puthash date it chronometrist-events))))
|
||||
((pred null) nil)
|
||||
(_ (chronometrist-events-populate))))
|
||||
(chronometrist-events-populate)
|
||||
;; re-create task list
|
||||
(--> (chronometrist-loop-file for plist in chronometrist-file collect (plist-get plist :name))
|
||||
(cl-remove-duplicates it :test #'equal)
|
||||
(sort it #'string-lessp)
|
||||
(setq chronometrist-task-list it)))
|
||||
(setq chronometrist--file-state
|
||||
(list :last (chronometrist-file-hash :before-last nil)
|
||||
:rest (chronometrist-file-hash nil :before-last t)))
|
||||
;; REVIEW - can we move most/all of this to the `chronometrist-file-change-hook'?
|
||||
(chronometrist-refresh))
|
||||
(-let* (((descriptor action file ...) fs-event)
|
||||
(change (chronometrist-file-change-type chronometrist--file-state))
|
||||
(reset-watch (or (eq action 'deleted) (eq action 'renamed))))
|
||||
;; (message "chronometrist - file change type is %s" change)
|
||||
(cond ((or reset-watch (not chronometrist--file-state) (eq change t))
|
||||
(when reset-watch
|
||||
(setq chronometrist--fs-watch nil chronometrist--file-state nil))
|
||||
(chronometrist-events-populate)
|
||||
;; re-create task list
|
||||
(--> (chronometrist-loop-file for plist in chronometrist-file collect (plist-get plist :name))
|
||||
(cl-remove-duplicates it :test #'equal)
|
||||
(sort it #'string-lessp)
|
||||
(setq chronometrist-task-list it)))
|
||||
(chronometrist--file-state
|
||||
(let ((task (plist-get (chronometrist-last) :name)))
|
||||
(pcase change
|
||||
(:append
|
||||
(chronometrist-events-update (chronometrist-sexp-last))
|
||||
(chronometrist-add-to-task-list task))
|
||||
(:modify
|
||||
(chronometrist-events-update (chronometrist-sexp-last) t)
|
||||
(chronometrist-remove-from-task-list task)
|
||||
(chronometrist-add-to-task-list task))
|
||||
(:remove
|
||||
(let* ((date (--> (hash-table-keys chronometrist-events)
|
||||
(last it)
|
||||
(car it)))
|
||||
(old-task (--> (gethash date chronometrist-events)
|
||||
(last it)
|
||||
(car it)
|
||||
(plist-get it :name))))
|
||||
(chronometrist-remove-from-task-list old-task)
|
||||
(--> (gethash date chronometrist-events)
|
||||
(-drop-last 1 it)
|
||||
(puthash date it chronometrist-events))))
|
||||
((pred null) nil)))))
|
||||
(setq chronometrist--file-state
|
||||
(list :last (chronometrist-file-hash :before-last nil)
|
||||
:rest (chronometrist-file-hash nil :before-last t)))
|
||||
;; REVIEW - can we move most/all of this to the `chronometrist-file-change-hook'?
|
||||
(chronometrist-refresh)))
|
||||
|
||||
(defun chronometrist-query-stop ()
|
||||
"Ask the user if they would like to clock out."
|
||||
|
|
Reference in New Issue