(WIP) try to handle vanished/replaced file; unify partial update functions

This commit is contained in:
contrapunctus 2021-01-30 12:08:57 +05:30
parent 22ea8b4e99
commit 09a5d127a1
4 changed files with 84 additions and 104 deletions

View File

@ -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

View File

@ -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

View File

@ -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'.

View File

@ -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."