emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/triples 5d70bb0c0d 3/7: Add `triples-backups' module.


From: ELPA Syncer
Subject: [elpa] externals/triples 5d70bb0c0d 3/7: Add `triples-backups' module.
Date: Sun, 1 Jan 2023 14:58:17 -0500 (EST)

branch: externals/triples
commit 5d70bb0c0dc1a1b3906d0364dba11de32f34d830
Author: Andrew Hyatt <ahyatt@gmail.com>
Commit: Andrew Hyatt <ahyatt@gmail.com>

    Add `triples-backups' module.
    
    This moves most of the new backup logic to its own file, and tests it.
    
    Also, document the new functionality in the README.
---
 README.org              | 21 ++++++++++++
 triples-backups-test.el | 65 ++++++++++++++++++++++++++++++++++++
 triples-backups.el      | 87 +++++++++++++++++++++++++++++++++++++++++++++++++
 triples-test.el         |  9 -----
 triples.el              | 24 +++-----------
 5 files changed, 177 insertions(+), 29 deletions(-)

diff --git a/README.org b/README.org
index b7c2386ca1..06a4c8ee53 100644
--- a/README.org
+++ b/README.org
@@ -98,6 +98,27 @@ Sometimes clients of this library need to do something with 
the database, and th
 - =triples-db-select=: Select triples matching any of the parts of the triple. 
 Like =triples-db-delete=, empty arguments match everything.  You can specify 
exactly what to return with a selector.
 
 Sometimes this still doesn't cover what you might want to do.  In that case, 
you should write your own direct database access.  However, please follow the 
coding patterns for the functions above in writing it, so that the code works 
with both Emacs 29's builtin sqlite, and =emacsql=.
+** Backups
+If your application wants to back up your database, the function 
=triples-backup= provides the capability to do so safely.  It can be called 
like:
+#+begin_src emacs-lisp
+(triples-backup db db-file 3)
+#+end_src
+Where =db= is the database, =db-file= is the filename where that database is 
stored, and =3= is the number of most recent backup files to keep.  All older 
backup files will be deleted.  The backup is stored where other emacs file 
backups are kept, defined by =backup-directory-alist=.
+
+The =triples-backups= module provides a way to backup a database in a way 
defined in the database itself (so multiple clients of the same database can 
work in a sane way together).  The number of backups to be kept, along with the 
"strategy" of when we want backups to happen is defined once per database.
+#+begin_src emacs-lisp
+;; Set up a backup configuration if none exists.
+(require 'triples-backups)
+(unless (triples-backups-configuration db)
+  (triples-backups-setup db 3 'daily))
+#+end_src
+
+Once this is set up, whenever a change happens, simply call 
=triples-backups-maybe-backup= with the database and the filename where the 
database was opened from, which will back up the database if appropriate.  This 
should be done after any important database write, once the action, at the 
application level, is finished.  The triples module doesn't know when an 
appropriate point would be, so this is up to the client to run.
+#+begin_src emacs-lisp
+(defun my-package-add-data (data)
+  (my-package-write-new-data package-db data)
+  (triples-backups-maybe-backup db db-filename))
+#+end_src
 * Using =triples= to develop apps with shared data
 One possibility that arises from a design with entities (in triples terms,
 subjects) having multiple decomposable types like is done in the =triples= 
library
diff --git a/triples-backups-test.el b/triples-backups-test.el
new file mode 100644
index 0000000000..c1f9dd2491
--- /dev/null
+++ b/triples-backups-test.el
@@ -0,0 +1,65 @@
+;;; triples-backups-test.el --- Tests for the triples-backup module.  -*- 
lexical-binding: t; -*-
+
+;; Copyright (c) 2022  Free Software Foundation, Inc.
+
+;; This program 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 2 of the
+;; License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Note: It's important to test this on emacs 29, with emacsql installed, so we
+;; can make both types of sqlite backend work.
+
+;;; Code:
+(require 'ert)
+(require 'triples-backups)
+
+(ert-deftest triples-backups-strategy-daily ()
+  (cl-letf (((symbol-function 'current-time)
+             (lambda ()
+               (encode-time (iso8601-parse "2023-01-15T12:00Z")))))
+    (should (triples-backups-strategy-daily (encode-time (iso8601-parse 
"2023-01-14T12:00Z"))))
+    (should (triples-backups-strategy-daily (encode-time (iso8601-parse 
"2022-01-01T12:00Z"))))
+    (should-not (triples-backups-strategy-daily (encode-time (iso8601-parse 
"2023-01-15T12:00Z"))))
+    (should-not (triples-backups-strategy-daily (encode-time (iso8601-parse 
"2023-02-01T12:00Z"))))))
+
+(ert-deftest triples-backups-strategy-weekly ()
+  (cl-letf (((symbol-function 'current-time)
+             (lambda ()
+               (encode-time (iso8601-parse "2023-01-15T12:00Z")))))
+    (should (triples-backups-strategy-daily (encode-time (iso8601-parse 
"2023-01-01T12:00Z"))))
+    (should (triples-backups-strategy-daily (encode-time (iso8601-parse 
"2022-01-01T12:00Z"))))
+    (should-not (triples-backups-strategy-daily (encode-time (iso8601-parse 
"2023-01-15T12:00Z"))))
+    (should-not (triples-backups-strategy-daily (encode-time (iso8601-parse 
"2023-02-01T12:00Z"))))))
+
+(ert-deftest triples-backups-maybe-backup ()
+  (let* ((filename (make-temp-file "triples-test"))
+         (db (triples-connect filename))
+         (backup-called))
+    (cl-letf (((symbol-function 'triples-backup)
+               (lambda (_ _ num-to-keep)
+                 (should (= num-to-keep 3))
+                 (setq backup-called t)))
+              ((symbol-function 'triples-backups-strategy-always)
+               (lambda (_) t))
+              ((symbol-function 'triples-backups-strategy-never)
+               (lambda (_) nil)))
+      (should-error (triples-backups-maybe-backup db filename))
+      (triples-backups-setup db 3 'never)
+      (triples-backups-maybe-backup db filename)
+      (should-not backup-called)
+      (triples-backups-setup db 3 'always)
+      (triples-backups-maybe-backup db filename)
+      (should backup-called))))
+
+(provide 'triples-backups-test)
diff --git a/triples-backups.el b/triples-backups.el
new file mode 100644
index 0000000000..b60a2af3dc
--- /dev/null
+++ b/triples-backups.el
@@ -0,0 +1,87 @@
+;;; triples-backups --- Functions to add backup functionality to triple 
databases.  -*- lexical-binding: t; -*-
+
+;; Copyright (c) 2022  Free Software Foundation, Inc.
+
+;; Author: Andrew Hyatt <ahyatt@gmail.com>
+;; Homepage: https://github.com/ahyatt/triples
+;;
+;; This program 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 2 of the
+;; License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;; This provides backup functionality. The information about how and when to do
+;; backups lives in the database itself, on a special entity `database'.
+
+(require 'triples)
+
+(defun triples-backups-setup (db num-to-keep strategy)
+  "Set DB's backup strategy.
+NUM-TO-KEEP is the number of backup files to keep. Older ones are
+removed. STRATEGY is a symbol that corresponds to a function
+`triples-backups-strategy-STRATEGY'. This function must always be
+loaded before any client of this db calls
+`triples-backups-maybe-backup', so adding your own may not always
+be appropriate."
+  (triples-with-transaction db
+    (triples-add-schema db 'backup '(num-to-keep :base/unique t :base/type 
integer)
+                        '(strategy :base/unique t :base/type symbol)
+                        '(last-update-time :base/unique t :base/type integer))
+    (triples-set-type db 'database 'backup :num-to-keep num-to-keep
+                      :strategy strategy :last-update-time (time-convert 
(current-time) 'integer))))
+
+(defun triples-backups-configuration (db)
+  "Returns the backup configuration set by `triples-backups-setup'.
+If no one has ever run that on this database, `nil' is returned."
+  (triples-get-type db 'database 'backup))
+
+(defun triples-backups-last-update-time (db)
+  "Get the last time DB has been updated."
+  (plist-get (triples-get-type db 'database 'backup) :last-update-time))
+
+(defun triples-backups-maybe-backup (db filename)
+  "If it is time for DB to be backed up, then back it up.
+FILENAME is also necessary for the backup operation."
+  (let* ((backup-info (triples-backups-configuration db))
+         (strategy-func (intern (format "triples-backups-strategy-%s"
+                                        (plist-get backup-info :strategy)))))
+    (unless backup-info
+      (error "`triples-backups-setup' needs to be called on this database 
before trying to back up."))
+    (unless (fboundp strategy-func)
+      (display-warning
+       'triples
+       (format "Triples backup strategy %s not found, defaulting to 
`triples-backups-strategy-daily'" 
+               strategy-func)
+       :error))
+    (when (funcall (or (symbol-function strategy-func) 
triples-backups-strategy-daily)
+                 (time-convert (plist-get backup-info :last-update-time) t))
+        (triples-backup db filename (plist-get backup-info :num-to-keep)))))
+
+(defun triples-backups-strategy-every-change (_)
+  "Backup strategy to do a backup on each change."
+  t)
+
+(defun triples-backups-strategy-daily (last-update)
+  "Backup strategy to create a change daily at most.
+LAST-UPDATE is the time of the last update."
+  (>= (/ (time-convert (time-subtract (current-time) last-update) 'integer) 
86400)
+      1))
+
+(defun triples-backups-strategy-weekly (last-update)
+  "Backup strategy to create a change daily at most.
+LAST-UPDATE is the time of the last update."
+  (>= (/ (time-convert (time-subtract (current-time) last-update) 'integer) 
86400)
+      7))
+
+(provide 'triples-backups)
+;;; Code:
+
diff --git a/triples-test.el b/triples-test.el
index 7bc9587414..44d92ab2da 100644
--- a/triples-test.el
+++ b/triples-test.el
@@ -345,15 +345,6 @@ easily debug into it.")
                    (sqlite-select db "SELECT COUNT(*) FROM triples WHERE 
subject = ? AND predicate = 'base/type' AND object = 'marker'"
                                   (list (triples-standardize-val "foo")))))))
 
-(ert-deftest triples-backup-strategy-daily ()
-  (cl-letf (((symbol-function 'current-time)
-             (lambda ()
-               (encode-time (iso8601-parse "2023-01-15T12:00Z")))))
-    (should (triples-backup-strategy-daily (encode-time (iso8601-parse 
"2023-01-14T12:00Z"))))
-    (should (triples-backup-strategy-daily (encode-time (iso8601-parse 
"2022-01-01T12:00Z"))))
-    (should-not (triples-backup-strategy-daily (encode-time (iso8601-parse 
"2023-01-15T12:00Z"))))
-    (should-not (triples-backup-strategy-daily (encode-time (iso8601-parse 
"2023-02-01T12:00Z"))))))
-
 (ert-deftest triples-readme ()
   (triples-test-with-temp-db
    (triples-add-schema db 'person
diff --git a/triples.el b/triples.el
index a91a579dd3..ebc59982f0 100644
--- a/triples.el
+++ b/triples.el
@@ -54,16 +54,6 @@ It is invoked to make backups.")
   "The default filename triples database. If no database is
 specified, this file is used.")
 
-(defconst triples-backup-files-to-keep 3
-  "How many backups of database to keep.
-Backups are stored in the default location, or the location
-specified in `backup-directory-alist'")
-
-(defconst triples-backup-strategy 'triples-backup-strategy-daily
-  "Function that controls when backups are created.
-Each function is called when a change happens, with no arguments,
-and returns a non-nil value if a backup should be created.")
-
 (defun triples-connect (&optional file)
   "Connect to the database FILE and make sure it is populated.
 If FILE is nil, use `triples-default-database-filename'."
@@ -105,7 +95,7 @@ If FILE is nil, use `triples-default-database-filename'."
     ('builtin (sqlite-close db))
     ('emacsql (emacsql-close db))))
 
-(defun triples-backup (db filename)
+(defun triples-backup (db filename num-to-keep)
   "Perform a backup of DB, located at path FILENAME.
 This uses the same backup location and names as configured in
 variables such as `backup-directory-alist'. Due to the fact that
@@ -116,7 +106,8 @@ Th DB argument is currently unused, but may be used in the 
future
 if emacs's native sqlite gains a backup feature.
 
 This also will clear excess backup files, according to
-`triples-backup-files-to-keep'."
+NUM-TO-KEEP, which specifies how many backup files at max should
+exist at any time. Older backups are the ones that are deleted."
   (call-process (pcase triples-sqlite-interface
                   ('builtin triples-sqlite-executable)
                   ('emacsql emacsql-sqlite-executable))
@@ -127,16 +118,9 @@ This also will clear excess backup files, according to
   (let ((backup-files (file-backup-file-names (expand-file-name filename))))
     (cl-loop for backup-file in (cl-subseq
                                  backup-files
-                                 (min triples-backup-files-to-keep
-                                      (length backup-files)))
+                                 (min num-to-keep (length backup-files)))
              do (delete-file backup-file))))
 
-(defun triples-backup-strategy-daily (last-update)
-  "Backup strategy to create a change daily at most.
-LAST-UPDATE is the time of the last update."
-  (>= (/ (time-convert (time-subtract (current-time) last-update) 'integer) 
86400)
-      1))
-
 (defun triples--decolon (sym)
   "Remove colon from SYM."
   (intern (string-replace ":" "" (format "%s" sym))))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]