[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/crdt 3883736730 17/44: many changes
From: |
ELPA Syncer |
Subject: |
[elpa] externals/crdt 3883736730 17/44: many changes |
Date: |
Sat, 2 Jul 2022 22:57:33 -0400 (EDT) |
branch: externals/crdt
commit 388373673060130b36f2dd47767073d4766969eb
Author: Qiantan Hong <qhong@mit.edu>
Commit: Qiantan Hong <qhong@mit.edu>
many changes
---
HACKING.org | 66 ++++-----
crdt.el | 437 ++++++++++++++++++++++++++++++++++--------------------------
2 files changed, 279 insertions(+), 224 deletions(-)
diff --git a/HACKING.org b/HACKING.org
index 4f19ff9ed0..3bd19c85a3 100644
--- a/HACKING.org
+++ b/HACKING.org
@@ -18,19 +18,6 @@ For base IDs, last two bytes are always representing Site ID.
Stored strings are BASE-ID:OFFSETs. So the last two bytes represent offset,
and second last two bytes represent Site ID.
-* Access Control
-
-~crdt.el~ implements a capability based access control system.
-
-Each capability is a list of the form =(type transferable-p nonce . body)=
-
- - read :: body takes the form =(buffer-name)=
- - write :: body takes the form =(buffer-name)=
- - command :: body takes the form =(buffer-name command-symbol)=
- + =buffer-name= can be =t=, which means that =command-symbol= is not
- bound to be invoked in any specific buffer.
- - process :: body takes the form =(buffer-name)=
-
* Protocol
Text-based version
@@ -42,6 +29,8 @@ Site IDs are /buffer local/ and temporarily assigned to users
with writable acce
Every message takes the form =(type . body)=
- Text Editing
+ A peer must obtain a =site-id= before performing the following operations,
+ by remote calling =crdt-get-write-access=. See [[Remote Command]].
+ insert ::
body takes the form =(buffer-name user-id crdt-id position-hint content)=
- =position-hint= is the buffer position where the operation happens at
the site
@@ -50,7 +39,7 @@ Every message takes the form =(type . body)=
- =content= is the string to be inserted
+ delete ::
- body takes the form =(buffer-name position-hint . crdt-id-list)=
+ body takes the form =(buffer-name user-id position-hint . crdt-id-list)=
- =crdt-id-list= is generated from =CRDT--DUMP-IDS= from the deleted text
- Peer State
@@ -58,8 +47,8 @@ Every message takes the form =(type . body)=
body takes the form
=(buffer-name user-id point-position-hint point-crdt-id
mark-position-hint mark-crdt-id)=
=*-crdt-id= can be either a CRDT ID, or
- - =nil=, which means clear the point/mark
- - =""=, which means =(point-max)=
+ - =nil=, which means clear the point/mark
+ - =""=, which means =(point-max)=
+ contact ::
body takes the form
@@ -113,6 +102,9 @@ Every message takes the form =(type . body)=
This message is sent from server to client to notice that some messages
from the
client is not processed due to error =(error-symbol . error-datum)=.
Normally client should follow initial synchronization procedure to
reinitialize the buffer.
+ - =buffer-name= can also be =nil=, which signifies that it's a session
error.
+ The only reasonable thing to do is to disconnect in this scenario.
+ Currently, this happens when client/server protocol version doesn't
match.
- Buffer Service
+ add ::
@@ -151,26 +143,36 @@ Every message takes the form =(type . body)=
+ overlay-remove ::
body takes the form =(buffer-name user-id logical-clock)=
- - Remote Command
+ - <<Remote Command>>
+ + fcap ::
+ body takes the form =(buffer-name command-symbol nonce in-states
out-states)=
+ This grants a "functional capability" to a peer.
+ Nonce is a random number to prevent forging capability.
+ - =buffer-name= can also be =nil=, which means this is a session-scoped
command,
+ not bound to any specific buffer.
+ - =in-states= is a list of state symbols that the command depends on.
+ =out-states= is a list of state symbols that the command modifies and
should be synchronized
+ to the caller.
+ See [[Allowed state symbols]].
+
+ command ::
body takes the form
#+BEGIN_SRC
- (buffer-name spawn-user-id
- user-id logical-clock state-list
- command-symbol . args)
+ (buffer-name user-id logical-clock
+ spawn-user-id state-list nonce command-symbol . args)
#+END_SRC
- - =spawn-user-id= represents the site where the interactive command is
originally invoked
- + It can be different from =user-id= because a remote command can
call a remote command!
- This is especially useful when client makes a remote call,
- but the call on the server request some interactive input,
- and such interactive call are remote-called back into the client.
- - =state-list= is an alist of bindings.
- (except that we use 1 element list for the CDRs, to save a dot in the
serialized string)
- (CDRs can also be 2 element list of the form =(crdt-id pos-hint)=)
- Allowed symbols are
- #+BEGIN_SRC
- buffer point mark mark-active transient-mark-mode last-command-event
- #+END_SRC
+ - =spawn-user-id= represents the site where the interactive command is
originally invoked
+ + It can be different from =user-id= because a remote command can call
a remote command!
+ This is especially useful when client makes a remote call,
+ but the call on the server request some interactive input,
+ and such interactive call are remote-called back into the client.
+ - =state-list= is an alist of bindings.
+ (except that we use 1 element list for the CDRs, to save a dot in the
serialized string)
+ (CDRs can also be 2 element list of the form =(crdt-id pos-hint)=)
+ <<Allowed state symbols>> are
+ #+BEGIN_SRC
+ buffer point mark mark-active transient-mark-mode last-command-event
+ #+END_SRC
+ return ::
body takes the form =(user-id logical-clock state-list success-p .
return-values)=
diff --git a/crdt.el b/crdt.el
index 556e035daf..a43bbde8a2 100644
--- a/crdt.el
+++ b/crdt.el
@@ -76,6 +76,13 @@
"Start tuntox proxy for CRDT servers."
:type '(choice boolean (const confirm)))
+(defcustom crdt-default-session-command-functions
+ '((crdt-get-write-access)
+ crdt-xref-command-function)
+ "A list that describes default policies for public session-scoped commands.
+See `crdt-new-session'.'"
+ :type '(list (or function (list function))))
+
;;; Pseudo cursor/region utils
(defvar crdt-cursor-region-colors
@@ -290,7 +297,7 @@ Must be used inside CRDT--WITH-INSERTION-INFORMATION."
(cl-defstruct (crdt--session (:constructor crdt--make-session))
local-id ; Local user-id
local-clock ; Local logical clock
- contact-table ; A hash table that maps USER-ID to CRDT--CONTACT-METADATAs
+ (contact-table (make-hash-table)) ; A hash table that maps USER-ID to
CRDT--CONTACT-METADATAs
local-name
name
focused-buffer-name
@@ -298,10 +305,12 @@ Must be used inside CRDT--WITH-INSERTION-INFORMATION."
buffer-menu-buffer
network-process
network-clients
- next-client-id
- buffer-table ; maps buffer network name to buffer
+ next-user-id
+ (buffer-table (make-hash-table :test 'equal)); maps buffer network name to
buffer
follow-user-id
- user-command-functions)
+ command-functions
+ (fcap-in-table (make-hash-table :test 'eq))
+ (fcap-out-table (make-hash-table :test 'eq)))
(defvar crdt--inhibit-update nil "When set, don't call CRDT--LOCAL-* on change.
This is useful for functions that apply remote change to local buffer,
@@ -362,14 +371,20 @@ adding/removing actively tracked overlays.")
(defvar-local crdt--enabled-text-properties nil
"A list of text properties that are tracked and synchronized.")
-(defvar-local crdt-user-command-functions nil
+(defvar-local crdt-buffer-command-functions nil
"A list that describes policies for public buffer-local commands.
Each element should be one of
-- a symbol, which should name a command.
- The command is be made accessible to every user.
- a function, which should return a list of commands when
called with a single argument USER-ID.
- The returned list of commands is made accessible to the user with USER-ID.")
+ The returned list of commands is made accessible to the user with USER-ID.
+- a list of commands.
+ These commands are made accessible to every user.")
+
+(defvar-local crdt-buffer-fcap-in-table nil
+ "A hash table that maps local command symbol to a nonce.")
+
+(defvar-local crdt-buffer-fcap-out-table nil
+ "A hash table that maps remote command symbol to a function.")
;;; Global variables
@@ -378,8 +393,8 @@ Each element should be one of
(defvar crdt--process nil
"Temporarily bound to the current network process when processing messages
inside CRDT--NETWORK-FILTER.")
-(defvar crdt--user-id nil
- "Temporarily bound to the User ID who requests the current remote command
call.")
+(defsubst crdt--client-id ()
+ (process-get crdt--process 'client-id))
;;; crdt-mode
@@ -433,10 +448,18 @@ Also set CRDT--PSEUDO-CURSOR-TABLE to NIL."
(setq crdt--pseudo-cursor-table (make-hash-table)))
(unless crdt--overlay-table
(setq crdt--overlay-table (make-hash-table :test 'equal)))
+ (unless crdt-buffer-fcap-in-table
+ (setq crdt-buffer-fcap-in-table (make-hash-table)))
+ (unless crdt-buffer-fcap-out-table
+ (setq crdt-buffer-fcap-out-table (make-hash-table)))
+ (setq crdt--site-id-list (list 0 crdt--max-value))
(crdt--install-hooks))
(crdt--uninstall-hooks)
(crdt--clear-pseudo-cursor-table)
- (setq crdt--overlay-table nil)))
+ (setq crdt--overlay-table nil
+ crdt-buffer-fcap-in-table nil
+ crdt-buffer-fcap-out-table nil
+ crdt--site-id-list nil)))
(defun crdt--clone-buffer-hook ()
(crdt-mode -1))
@@ -528,6 +551,19 @@ If we are the server, ERR is the error we shall report to
client."
;;; Shared buffer utils
+(defun crdt--call-with-buffer-name (name function)
+ "Find CRDT shared buffer associated with NAME and call FUNCTION in it.
+See `crdt--with-buffer-name'."
+ (let (crdt-buffer)
+ (setq crdt-buffer (gethash name (crdt--session-buffer-table
crdt--session)))
+ (when (and crdt-buffer (buffer-live-p crdt-buffer))
+ (with-current-buffer crdt-buffer
+ (save-restriction
+ (widen)
+ (condition-case err
+ (funcall function)
+ (crdt-sync-error (crdt--recover err))))))))
+
(defmacro crdt--with-buffer-name (name &rest body)
"Find CRDT shared buffer associated with NAME and evaluate BODY in it.
Any narrowing is temporarily disabled during evaluation of BODY.
@@ -535,15 +571,7 @@ Also, try to recover from synchronization error if any
error happens in BODY.
Must be called when CURRENT-BUFFER is a CRDT status buffer.
If such buffer doesn't exist yet, do nothing."
(declare (indent 1) (debug (sexp def-body)))
- `(let (crdt-buffer)
- (setq crdt-buffer (gethash ,name (crdt--session-buffer-table
crdt--session)))
- (when (and crdt-buffer (buffer-live-p crdt-buffer))
- (with-current-buffer crdt-buffer
- (save-restriction
- (widen)
- (condition-case err
- ,(cons 'progn body)
- (crdt-sync-error (crdt--recover err))))))))
+ `(crdt--call-with-buffer-name ,name (lambda () ,@body)))
(defmacro crdt--with-buffer-name-pull (name &rest body)
"Find CRDT shared buffer associated with NAME and evaluate BODY in it.
@@ -1112,7 +1140,7 @@ The deletion happens between BEG and END, and have
LENGTH."
(crdt--with-insertion-information ((length crdt--changed-string) outer-end
crdt--changed-string nil 0 nil)
(crdt--split-maybe))
;; (crdt--verify-buffer)
- `(delete ,crdt--buffer-network-name
+ `(delete ,crdt--buffer-network-name ,(crdt--session-local-id crdt--session)
,beg ,@ (crdt--dump-ids 0 (length crdt--changed-string)
crdt--changed-string t))))
(defun crdt--remote-delete (position-hint id-items)
@@ -1144,12 +1172,20 @@ Start the search for those ID-ITEMs around
POSITION-HINT."
(defun crdt--before-change (beg end)
"Before change hook used by CRDT-MODE.
-It saves the content to be changed (between BEG and END) into
CRDT--CHANGED-STRING."
+Save the content to be changed (between BEG and END) into CRDT--CHANGED-STRING.
+Request a Site ID if we don't have it yet."
(unless crdt--inhibit-update
(setq crdt--changed-string (crdt--buffer-substring beg end))
(crdt--text-property-assimilate nil beg end 0
'crdt-id crdt--changed-string)
- (setq crdt--changed-start beg)))
+ (setq crdt--changed-start beg)
+ (unless crdt--site-id
+ (condition-case nil
+ (setq crdt--site-id (crdt-remote-call 'crdt-get-write-access))
+ (crdt-access-denied
+ (read-only-mode)
+ (warn "Write access revoked in %s" crdt--buffer-network-name)
+ (signal 'quit nil))))))
(defsubst crdt--crdt-id-assimilate (template beg &optional object)
"Make the CRDT-ID property after BEG in OBJECT the same as TEMPLATE.
@@ -1273,6 +1309,7 @@ Always return a message otherwise."
Check if focused buffer and cursor/mark position are changed.
Send message to other peers about any changes."
(crdt--with-should-not-error crdt--post-command
+ (add-to-list 'before-change-functions 'crdt--before-change)
(unless (eq crdt--buffer-network-name (crdt--session-focused-buffer-name
crdt--session))
(crdt--broadcast-maybe
(crdt--format-message `(focus ,(crdt--session-local-id crdt--session)
,crdt--buffer-network-name)))
@@ -1479,7 +1516,7 @@ CRDT--PROCESS should be bound to the network process for
the client connection."
(crdt--format-message
`(cursor ,crdt--buffer-network-name
,user-id
,point ,(crdt--get-id
point)
- ,mark ,(crdt--get-id
mark)))))))
+ ,mark ,(when mark
(crdt--get-id mark))))))))
crdt--pseudo-cursor-table)
(process-send-string crdt--process (crdt--format-message
(crdt--local-cursor nil)))
@@ -1503,7 +1540,17 @@ CRDT--PROCESS should be bound to the network process for
the client connection."
crdt--overlay-table)
(crdt--send-process-mark-maybe nil)
- (crdt--send-variables-maybe nil))))
+ (crdt--send-variables-maybe nil)
+
+ ;; send fcaps
+ (dolist (command (crdt--compute-user-commands
crdt-buffer-command-functions (crdt--client-id)))
+ (let ((nonce (or (gethash command crdt-buffer-fcap-in-table)
+ (puthash command (crdt--generate-nonce)
crdt-buffer-fcap-in-table))))
+ (process-send-string crdt--process
+ (crdt--format-message
+ `(fcap ,crdt--buffer-network-name ,command
,nonce
+ ,(get command 'crdt-out-states)
+ ,(get command 'crdt-in-states)))))))))
(defun crdt--greet-client ()
"Send initial information when a client connects.
@@ -1514,14 +1561,12 @@ CRDT--PROCESS should be bound to The network process
for the client connection."
(cl-pushnew crdt--process (crdt--session-network-clients crdt--session))
(let ((client-id (process-get crdt--process 'client-id)))
(unless client-id
- (unless (< (crdt--session-next-client-id crdt--session)
crdt--max-value)
- (error "Used up client IDs. Need to implement allocation
algorithm"))
- (process-put crdt--process 'client-id (crdt--session-next-client-id
crdt--session))
- (setq client-id (crdt--session-next-client-id crdt--session))
+ (process-put crdt--process 'client-id (crdt--session-next-user-id
crdt--session))
+ (setq client-id (crdt--session-next-user-id crdt--session))
(process-send-string crdt--process (crdt--format-message
`(login ,client-id
,(crdt--session-name
crdt--session))))
- (cl-incf (crdt--session-next-client-id crdt--session)))
+ (cl-incf (crdt--session-next-user-id crdt--session)))
(process-send-string crdt--process (crdt--format-message
(cons 'add (hash-table-keys
(crdt--session-buffer-table crdt--session)))))
;; synchronize contact
@@ -1546,20 +1591,30 @@ CRDT--PROCESS should be bound to The network process
for the client connection."
(let ((contact-message `(contact ,client-id ,(process-get crdt--process
'client-name)
,(process-contact crdt--process :host)
,(process-contact crdt--process
:service))))
- (crdt-process-message-1 contact-message)))))
+ (crdt-process-message-1 contact-message))
+ ;; send fcaps
+ (dolist (command (crdt--compute-user-commands
(crdt--session-command-functions crdt--session) client-id))
+ (let ((nonce (or (gethash command (crdt--session-fcap-in-table
crdt--session))
+ (puthash command (crdt--generate-nonce)
+ (crdt--session-fcap-in-table
crdt--session)))))
+ (process-send-string crdt--process
+ (crdt--format-message
+ `(fcap nil ,command ,nonce
+ ,(get command 'crdt-out-states)
+ ,(get command 'crdt-in-states)))))))))
(define-crdt-message-handler insert (buffer-name user-id crdt-id position-hint
content)
(crdt--with-buffer-name buffer-name
(crdt--with-recover
(crdt--remote-insert crdt-id user-id position-hint content)))
- (crdt--broadcast-maybe crdt--message-string (process-get crdt--process
'client-id)))
+ (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
-(define-crdt-message-handler delete (buffer-name position-hint . id-pairs)
+(define-crdt-message-handler delete (buffer-name _user-id position-hint .
id-pairs)
(mapc (lambda (p) (rplaca (cdr p) (cadr p))) id-pairs)
(crdt--with-buffer-name buffer-name
(crdt--with-recover
(crdt--remote-delete position-hint id-pairs)))
- (crdt--broadcast-maybe crdt--message-string (process-get crdt--process
'client-id)))
+ (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
(define-crdt-message-handler cursor
(buffer-name user-id point-position-hint point-crdt-id mark-position-hint
mark-crdt-id)
@@ -1567,7 +1622,7 @@ CRDT--PROCESS should be bound to The network process for
the client connection."
(crdt--with-recover
(crdt--remote-cursor user-id point-position-hint point-crdt-id
mark-position-hint mark-crdt-id)))
- (crdt--broadcast-maybe crdt--message-string (process-get crdt--process
'client-id)))
+ (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
(define-crdt-message-handler get (buffer-name)
(let ((buffer (gethash buffer-name (crdt--session-buffer-table
crdt--session))))
@@ -1578,8 +1633,8 @@ CRDT--PROCESS should be bound to The network process for
the client connection."
(define-crdt-message-handler sync (buffer-name . ids)
(unless (crdt--server-p) ; server shouldn't receive this
(crdt--with-buffer-name buffer-name
- (read-only-mode -1)
- (let ((crdt--inhibit-update t))
+ (let ((crdt--inhibit-update t)
+ (inhibit-read-only t))
(unless crdt--buffer-sync-callback
;; try to get to the same position after sync,
;; if crdt--buffer-sync-callback is not set yet
@@ -1603,7 +1658,9 @@ CRDT--PROCESS should be bound to The network process for
the client connection."
(unless (eq major-mode mode)
(funcall mode) ; trust your server...
(crdt-mode))
- (message "Server uses %s, but not available locally." mode))
+ (warn "Server uses %s, but not available locally." mode))
+ (when (crdt-get-fcap 'crdt-get-write-access)
+ (read-only-mode -1))
(when crdt--buffer-sync-callback
(funcall crdt--buffer-sync-callback)
(setq crdt--buffer-sync-callback nil)))))
@@ -1649,9 +1706,7 @@ CRDT--PROCESS should be bound to The network process for
the client connection."
(warn "Server stopped sharing %s."
(mapconcat #'identity buffer-names ", "))))
(let ((crdt--session saved-session))
- (crdt--broadcast-maybe crdt--message-string
- (when crdt--process
- (process-get crdt--process 'client-id)))
+ (crdt--broadcast-maybe crdt--message-string (when crdt--process
(crdt--client-id)))
(crdt--refresh-buffers-maybe))))
(define-crdt-message-handler login (id session-name)
@@ -1690,7 +1745,7 @@ CRDT--PROCESS should be bound to The network process for
the client connection."
(crdt-stop-follow))
(remhash user-id (crdt--session-contact-table crdt--session))))
(crdt--refresh-users-maybe)
- (crdt--broadcast-maybe crdt--message-string (process-get crdt--process
'client-id)))
+ (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
(define-crdt-message-handler focus (user-id buffer-name)
(let ((existing-item (gethash user-id (crdt--session-contact-table
crdt--session))))
@@ -1704,7 +1759,7 @@ CRDT--PROCESS should be bound to The network process for
the client connection."
(let ((ov-pair (gethash user-id crdt--pseudo-cursor-table)))
(when ov-pair (goto-char (overlay-start (car ov-pair)))))))
(crdt--refresh-users-maybe)
- (crdt--broadcast-maybe crdt--message-string (process-get crdt--process
'client-id)))
+ (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
(defun crdt--network-filter (process string)
"Network filter function for CRDT network processes.
@@ -1788,7 +1843,26 @@ Handle received STRING from PROCESS."
;;; Capabilities
-(defun crdt-request-site-id ()
+(define-error 'crdt-access-denied "CRDT access denied")
+
+(defun crdt--generate-nonce ()
+ (with-temp-buffer
+ (toggle-enable-multibyte-characters 0)
+ (let ((err (call-process-shell-command "head -c 8 /dev/urandom" nil t)))
+ (unless (= err 0)
+ (error "Failed to read /dev/urandom (code %s)" err)))
+ (buffer-string)))
+
+(defun crdt--compute-user-commands (command-function user-id)
+ (cl-loop for f in command-function
+ if (functionp f)
+ append (funcall f user-id)
+ else
+ append f))
+
+(defun crdt-get-write-access ()
+ "Allocate a Site ID for current user to access current buffer.
+Current user means the user corresponding to CRDT--PROCESS."
(let (new-site-id)
(cl-loop
for i in crdt--site-id-list
@@ -1806,7 +1880,7 @@ Handle received STRING from PROCESS."
(rplacd cons nil)
(setq new-site-id victim-id)))
(push new-site-id crdt--site-id-use-list)
- (puthash crdt--user-id new-site-id crdt--site-id-table)
+ (puthash (crdt--client-id) new-site-id crdt--site-id-table)
new-site-id))
;;; UI commands
@@ -1899,16 +1973,14 @@ Handle received STRING from PROCESS."
(setq crdt--session session)
(puthash (buffer-name buffer) buffer (crdt--session-buffer-table
crdt--session))
(setq crdt--buffer-network-name (buffer-name buffer)
- crdt--site-id 0 crdt--site-id-table (make-hash-table)
- crdt--site-id-free-list (cl-loop for i from 1 below
crdt--max-value collect i))
+ crdt--site-id 0 crdt--site-id-table (make-hash-table))
(crdt-mode)
(save-excursion
(save-restriction
(widen)
(let ((crdt--inhibit-update t))
(with-silent-modifications
- (crdt--local-insert (point-min) (point-max))))
- (run-hooks (crdt--session-add-buffer-hook crdt--session))))
+ (crdt--local-insert (point-min) (point-max))))))
(crdt--refresh-buffers-maybe)
(crdt--refresh-sessions-maybe))
(error "Only server can add new buffer")))
@@ -1926,12 +1998,9 @@ Handle received STRING from PROCESS."
string default)))
;;;###autoload
-(defun crdt-share-buffer (session-name &optional port)
+(defun crdt-share-buffer (session-name)
"Share the current buffer in the CRDT session with name SESSION-NAME.
-Create a new one if such a CRDT session doesn't exist. When PORT
-is non-NIL use when creating a new session, otherwise prompt
-from minibuffer. If SESSION-NAME is empty, use the buffer name
-of the current buffer."
+Create a new one if such a CRDT session doesn't exist."
(interactive
(progn
(when (and crdt-mode crdt--session)
@@ -1955,10 +2024,13 @@ of the current buffer."
(apply #'crdt-new-session
(crdt--read-settings
(format "*Settings for %s*" session-name)
- `(("Port: " (number-to-string ,port)
,(crdt--settings-make-ensure-type 'numberp))
+ `(("Port: " "6530" ,(crdt--settings-make-ensure-type
'numberp))
("Session Name: " ,session-name
,(crdt--settings-make-ensure-nonempty session-name))
("Password: " "")
- ("Display Name: " ,crdt-default-name))))))))
+ ("Display Name: " ,crdt-default-name)
+ ("Command Functions: "
+ ,(prin1-to-string crdt-default-session-command-functions)
+ ,(crdt--settings-make-ensure-type 'listp)))))))))
(cl-defun crdt-stop-share-buffer (&optional (session crdt--session)
(network-name crdt--buffer-network-name))
@@ -1974,18 +2046,18 @@ of the current buffer."
(message "Not a CRDT shared buffer.")))
(defun crdt-new-session
- (port session-name password display-name user-command-functions)
+ (port session-name password display-name command-functions)
"Start a new CRDT session on PORT with SESSION-NAME.
Setup up the server with PASSWORD and assign this Emacs DISPLAY-NAME.
-USER-COMMAND-FUNCTIONS is a list that describes policies
+COMMAND-FUNCTIONS is a list that describes policies
for public session-scoped commands.
Each element should be one of
-- a symbol, which should name a command.
- The command is be made accessible to every user, in every buffer.
- a function, which should return a list of commands when
- called with two arguments USER-ID and BUFFER.
+ called with a single argument USER-ID..
The returned list of commands is made accessible
- to the user with USER-ID in BUFFER."
+ to the user with USER-ID in every buffer.
+- a list of commands.
+ These commands are made accessible to every user, in every buffer."
(let* ((network-process (make-network-process
:name "CRDT Server"
:server t
@@ -1996,13 +2068,11 @@ Each element should be one of
(new-session
(crdt--make-session :local-id 0
:local-clock 0
- :next-client-id 1
+ :next-user-id 1
:local-name display-name
- :contact-table (make-hash-table :test 'equal)
- :buffer-table (make-hash-table :test 'equal)
:name session-name
:network-process network-process
- :user-command-functions user-command-functions))
+ :command-functions command-functions))
(tuntox-p (or (eq crdt-use-tuntox t)
(and (eq crdt-use-tuntox 'confirm)
(yes-or-no-p "Start a tuntox proxy for this
session? ")))))
@@ -2139,8 +2209,6 @@ Join with DISPLAY-NAME."
(new-session
(crdt--make-session :local-clock 0
:local-name display-name
- :contact-table
(make-hash-table :test 'equal)
- :buffer-table
(make-hash-table :test 'equal)
:name name-placeholder
:network-process
network-process)))
(process-put network-process 'crdt-session new-session)
@@ -2257,7 +2325,7 @@ Join with DISPLAY-NAME."
(let ((crdt--inhibit-overlay-advices t)
(crdt--modifying-overlay-metadata t))
(overlay-put new-overlay 'crdt-meta meta)))))
- (crdt--broadcast-maybe crdt--message-string (process-get crdt--process
'client-id)))
+ (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
(defun crdt--move-overlay-advice (orig-fun ov beg end &rest args)
(when crdt-mode
@@ -2315,7 +2383,7 @@ Join with DISPLAY-NAME."
(remhash key crdt--overlay-table)
(let ((crdt--inhibit-overlay-advices t))
(delete-overlay ov))))))
- (crdt--broadcast-maybe crdt--message-string (process-get crdt--process
'client-id)))
+ (crdt--broadcast-maybe crdt--message-string (crdt--client-id)))
(defun crdt--overlay-put-advice (orig-fun ov prop value)
(unless (and (eq prop 'crdt-meta)
@@ -2397,33 +2465,57 @@ Join with DISPLAY-NAME."
(push (crdt--readable-decode (cadr entry)) vals))))
(cons vars vals)))
-(defvar crdt--remote-call-spawn-site nil
- "The site where current remote call (if any) is orignally called.")
+(defvar crdt--remote-call-spawn-user-id nil
+ "The User ID where current remote call (if any) is orignally called.")
+
+(define-crdt-message-handler fcap
+ (buffer-name command-symbol nonce in-states _out-states)
+ (cl-flet* ((body ()
+ (puthash command-symbol
+ (lambda (&rest args)
+ (crdt--remote-call (crdt--session-local-id
crdt--session)
+ command-symbol nonce
in-states args))
+ (if buffer-name crdt-buffer-fcap-out-table
+ (crdt--session-fcap-out-table crdt--session)))
+ (cl-case command-symbol
+ ((crdt-get-write-access)
+ (if buffer-name
+ (read-only-mode -1)
+ (dolist (buffer (hash-table-values
(crdt--session-buffer-table crdt--session)))
+ (when buffer
+ (with-current-buffer buffer (read-only-mode
-1)))))))))
+ (if buffer-name (crdt--call-with-buffer-name buffer-name #'body) (funcall
#'body))))
(define-crdt-message-handler command
- (buffer-name spawn-user-id user-id logical-clock
- state-list command-symbol &rest args)
- (crdt--with-buffer-name buffer-name
- (save-mark-and-excursion
- (save-window-excursion
- (let ((bindings (crdt--apply-state-list state-list)))
- (cl-progv (car bindings) (cdr bindings)
- (let* ((crdt--inhibit-update nil)
- (crdt--remote-call-spawn-site spawn-user-id)
- (session crdt--session)
- (return-message
- (if (get command-symbol 'crdt-allow-remote-call)
- (condition-case err
- (list t (apply command-symbol (mapcar
#'crdt--readable-decode args)))
- (error (list nil (car err) (crdt--readable-encode
(cdr err)))))
- (list nil 'crdt-access-denied))))
- (setq crdt--session session) ;; workaround, somehow
CRDT--SESSION becomes NIL after some command
- (let ((msg (crdt--format-message
- `(return ,user-id ,logical-clock
- ,(crdt--assemble-state-list (get
command-symbol 'crdt-command-out-states))
- ,@return-message))))
- (crdt--log-send-network-traffic msg)
- (process-send-string crdt--process msg)))))))))
+ (buffer-name user-id logical-clock spawn-user-id
+ state-list nonce command-symbol &rest args)
+ (cl-flet*
+ ((check ()
+ (or (equal nonce
+ (gethash command-symbol (crdt--session-fcap-in-table
crdt--session)))
+ (and buffer-name
+ (equal nonce
+ (gethash command-symbol
crdt-buffer-fcap-in-table)))))
+ (body ()
+ (let ((bindings (crdt--apply-state-list state-list)))
+ (cl-progv (car bindings) (cdr bindings)
+ (let* ((crdt--inhibit-update nil)
+ (crdt--remote-call-spawn-user-id spawn-user-id)
+ (return-message
+ (if (check)
+ (save-mark-and-excursion
+ (save-window-excursion
+ (condition-case err
+ (list t (apply command-symbol (mapcar
#'crdt--readable-decode args)))
+ (error (list nil (car err)
(crdt--readable-encode (cdr err)))))))
+ (list nil 'crdt-access-denied))))
+ (let ((msg (crdt--format-message
+ `(return ,user-id ,logical-clock
+ ,(crdt--assemble-state-list (get
command-symbol 'crdt-command-out-states))
+ ,@return-message))))
+ (crdt--log-send-network-traffic msg)
+ (process-send-string crdt--process msg)))))))
+ (if buffer-name (crdt--call-with-buffer-name buffer-name #'body) (funcall
#'body))))
(defvar crdt--return-message-table (make-hash-table))
@@ -2432,25 +2524,23 @@ Join with DISPLAY-NAME."
(puthash logical-clock (cl-list* state-list success-p
(crdt--readable-decode return-values))
crdt--return-message-table)))
-(defun crdt--make-remote-call (spawn-user-id function-symbol in-states args)
+(defun crdt--remote-call (spawn-user-id function-symbol nonce in-states args)
"Send remote call request (a command type message) for FUNCTION-SYMBOL.
-SPAWN-USER-ID is the site where
-the series (if any) of remote calls originally started.
-Assemble state list for items in IN-STATES.
-Request for calling FUNCTION-SYMBOL with ARGS."
+SPAWN-USER-ID is the site where the series (if any) of remote calls originally
started.
+NONCE should be acquired from some fcap message for fcap verification.
+Assemble state list for items in IN-STATES. Request for calling
FUNCTION-SYMBOL with ARGS."
(let* ((user-id (crdt--session-local-id crdt--session))
(logical-clock (crdt--session-local-clock crdt--session))
(msg (crdt--format-message
- `(command ,crdt--buffer-network-name ,spawn-user-id
- ,user-id ,logical-clock
- ,(crdt--assemble-state-list in-states)
+ `(command ,crdt--buffer-network-name ,user-id
+ ,logical-clock ,spawn-user-id
+ ,(crdt--assemble-state-list in-states) ,nonce
,function-symbol ,@(mapcar #'crdt--readable-encode
args)))))
(crdt--log-send-network-traffic msg)
(process-send-string (crdt--session-network-process crdt--session) msg)
(cl-incf (crdt--session-local-clock crdt--session))
(while (not (gethash logical-clock crdt--return-message-table))
- (sleep-for 0.1)
- (thread-yield))
+ (accept-process-output (crdt--session-network-process crdt--session)))
(let ((return-message (gethash logical-clock crdt--return-message-table)))
(remhash logical-clock crdt--return-message-table)
(cl-destructuring-bind (state-list success-p &rest return-values)
return-message
@@ -2459,72 +2549,29 @@ Request for calling FUNCTION-SYMBOL with ARGS."
(car return-values)
(apply #'signal return-values))))))
-(defun crdt--make-remote-command-advice (function-symbol in-states)
- (lambda (orig-fun &rest args)
- (if (and crdt--session (not (crdt--server-p)))
- (crdt--make-remote-call (crdt--session-local-id crdt--session)
- function-symbol in-states args)
- (apply orig-fun args))))
-
-(defun crdt-register-remote-command (command-symbol &optional in-states
out-states)
- "Register COMMAND-SYMBOL as a remote command.
-Allow remote calls to COMMAND-SYMBOL.
-Delegate calls to COMMAND-SYMBOL at client side to the server.
-Assume that COMMAND-SYMBOL, when invoked,
-make use of no more states other than those in IN-STATES.
-After executing the command on the server,
-OUT-STATES are sent back to the client."
- (put command-symbol 'crdt-allow-remote-call t)
- (put command-symbol 'crdt-command-out-states out-states)
- (advice-add command-symbol :around (crdt--make-remote-command-advice
command-symbol in-states)
- '((name . crdt-remote-command-advice))))
-
-(defun crdt-unregister-remote-command (command-symbol)
- "Unregister COMMAND-SYMBOL as a remote command.
-Stop allowing remote calls to COMMAND-SYMBOL."
- (cl-remprop command-symbol 'crdt-allow-remote-call)
- (advice-remove command-symbol 'crdt-remote-command-advice))
+(defsubst crdt-get-fcap (command-symbol)
+ "Find buffer or session fcap with name COMMAND-SYMBOL."
+ (or (gethash command-symbol crdt-buffer-fcap-out-table)
+ (gethash command-symbol (crdt--session-fcap-out-table crdt--session))))
+
+(defun crdt-remote-call (command-symbol &rest args)
+ "Remote call COMMAND-SYMBOL with ARGS.
+Find and use buffer or session fcap with name COMMAND-SYMBOL."
+ (let ((fcap (crdt-get-fcap command-symbol)))
+ (if fcap (apply fcap args)
+ (signal 'crdt-access-denied command-symbol))))
(defun crdt-register-remote-commands (command-entries)
"Register a list of remote commands according to COMMAND-ENTRIES.
Each item in COMMAND-ENTRIES should have the form (COMMAND-SYMBOL &optional
IN-STATES OUT-STATES)."
(dolist (entry command-entries)
- (apply #'crdt-register-remote-command entry)))
+ (cl-destructuring-bind (command-symbol &optional in-states out-states)
entry
+ (put command-symbol 'crdt-in-states in-states)
+ (put command-symbol 'crdt-out-states out-states))))
-(defun crdt-unregister-remote-commands (command-entries)
- "Unregister a list of remote commands according to COMMAND-ENTRIES.
-Required form of COMMAND-ENTRIES is the same as that of
CRDT-REGISTER-REMOTE-COMMANDS."
- (dolist (entry command-entries)
- (crdt-unregister-remote-command (car entry))))
-
-(defun crdt--make-remote-interaction-advice (function-symbol)
- (lambda (orig-fun &rest args)
- (if (and crdt--process
- (not (eq crdt--remote-call-spawn-site (crdt--session-local-id
crdt--session))))
- ;; Is the above condition correct?
- ;; We must make sure we don't bind crdt--process AND call interaction
command
- ;; in any circumstances except inside a remote command call
- (crdt--make-remote-call crdt--remote-call-spawn-site function-symbol
nil args)
- (apply orig-fun args))))
-
-(defun crdt-register-interaction-function (function-symbol &rest states)
- "Register FUNCTION-SYMBOL as a remote interaction function.
-Allow remote calls to FUNCTION-SYMBOL.
-Delegate calls to FUNCTION-SYMBOL inside some remote command call
-back to the site where the remote command is originally invoked.
-Assume that COMMAND-SYMBOL, when invoked,
-make use of no more states other than those in STATES."
- (put function-symbol 'crdt-allow-remote-call t)
- (advice-add function-symbol :around (apply
#'crdt--make-remote-interaction-advice function-symbol states)
- '((name . crdt-remote-interaction-advice))))
-
-(defun crdt-unregister-interaction-function (function-symbol)
- "Unregister FUNCTION-SYMBOL as a remote interaction function.
-Stop allowing remote calls to FUNCTION-SYMBOL."
- (cl-remprop function-symbol 'crdt-allow-remote-call)
- (advice-remove function-symbol 'crdt-remote-interaction-advice))
-
-(crdt-register-interaction-function 'read-from-minibuffer)
+(defun crdt-make-publish-command-hook (command-entries)
+ "Return a function that publish commands in COMMAND-ENTRIES for current
buffer."
+ (push (mapcar #'car command-entries) crdt-buffer-command-functions))
;;; Buffer local variables
@@ -2727,8 +2774,7 @@ The result DIFF can be used in (CRDT--NAPPLY-DIFF OLD
DIFF) to reproduce NEW."
(if buffer-process
(progn (set-marker (process-mark buffer-process) (point))
(setq crdt--last-process-mark-id crdt-id)
- (crdt--broadcast-maybe crdt--message-string
- (process-get crdt--process
'client-id)))
+ (crdt--broadcast-maybe crdt--message-string
(crdt--client-id)))
(unless (crdt--server-p)
(setq crdt--buffer-pseudo-process
(crdt--make-pseudo-process :buffer (current-buffer)
:mark (point-marker)))
@@ -2848,25 +2894,27 @@ Use CRDT--UNINSTALL-PROCESS-ADVICES to disable those
advices for the rescue."
do (advice-add command :around #'crdt--org-overlay-advice))
;;;; xscheme
-(crdt-register-remote-commands
- '((xscheme-send-region (region))
- (xscheme-send-definition (point))
- (xscheme-send-previous-expression (point))
- (xscheme-send-next-expression (point))
- (xscheme-send-current-line (point))
- (xscheme-send-buffer)
- (xscheme-send-char)
- (xscheme-delete-output)
- (xscheme-send-breakpoint-interrupt)
- (xscheme-send-proceed)
- (xscheme-send-control-g-interrupt)
- (xscheme-send-control-u-interrupt)
- (xscheme-send-control-x-interrupt)
- (scheme-debugger-self-insert (last-command-event))))
+(defvar crdt-xscheme-commands
+ '((xscheme-send-region (region))
+ (xscheme-send-definition (point))
+ (xscheme-send-previous-expression (point))
+ (xscheme-send-next-expression (point))
+ (xscheme-send-current-line (point))
+ (xscheme-send-buffer)
+ (xscheme-send-char)
+ (xscheme-delete-output)
+ (xscheme-send-breakpoint-interrupt)
+ (xscheme-send-proceed)
+ (xscheme-send-control-g-interrupt)
+ (xscheme-send-control-u-interrupt)
+ (xscheme-send-control-x-interrupt)
+ (scheme-debugger-self-insert (last-command-event))))
+(crdt-register-remote-commands crdt-xscheme-commands)
;; xscheme doesn't use standard DEFINE-*-MODE facility
;; and doesn't call after-change-major-mode-hook.
;; Therefore we have to hack.
(advice-add 'scheme-interaction-mode-initialize :after
'crdt--after-change-major-mode)
+(advice-add 'scheme-interaction-mode-initialize :after
(crdt-make-publish-command-hook crdt-xscheme-commands))
(advice-add 'scheme-debugger-mode-initialize :after
(lambda () ;; haxxxx!!!!
(let ((major-mode 'scheme-debugger-mode-initialize))
@@ -2882,10 +2930,12 @@ Use CRDT--UNINSTALL-PROCESS-ADVICES to disable those
advices for the rescue."
(defvar comint-input-ring-size)
(defvar comint-input-ring-file-name)
-(crdt-register-remote-commands
- '((comint-send-input (point) (point))
- (comint-send-region (region) (region))
- (comint-send-eof (point))))
+(defvar crdt-comint-commands
+ '((comint-send-input (point) (point))
+ (comint-send-region (region) (region))
+ (comint-send-eof (point))))
+(crdt-register-remote-commands crdt-comint-commands)
+(add-hook 'comint-mode-hook (crdt-make-publish-command-hook
crdt-comint-commands))
(crdt-register-autoload 'shell-mode 'shell)
(crdt-register-autoload 'inferior-scheme-mode 'cmuscheme)
@@ -2954,12 +3004,15 @@ This procedure is non-destructive."
(add-hook 'crdt-mode-hook #'crdt--comint-mode-hook)
;;;; xref
-(crdt-register-remote-commands
- '((xref-find-definitions (point) (point))
- (xref-find-references (point) (point))
- (xref-show-location-at-point (point) (point))
- (xref-pop-marker-stack () (point))
- (xref-goto-xref (point) (point))))
+(defvar crdt-xref-commands
+ '((xref-find-definitions (point) (point))
+ (xref-find-references (point) (point))
+ (xref-show-location-at-point (point) (point))
+ (xref-pop-marker-stack () (point))
+ (xref-goto-xref (point) (point))))
+(crdt-register-remote-commands crdt-xref-commands)
+(defun crdt-xref-command-function (_user-id)
+ (mapcar #'car crdt-xref-commands))
(defun crdt--xref-buffer-mode-hook ()
(add-to-list 'crdt--enabled-text-properties 'xref-item)
- [elpa] externals/crdt 28c5f6bd0e 27/44: Also `crdt--refresh-users-maybe' when `crdt-stop-follow', (continued)
- [elpa] externals/crdt 28c5f6bd0e 27/44: Also `crdt--refresh-users-maybe' when `crdt-stop-follow', ELPA Syncer, 2022/07/02
- [elpa] externals/crdt f81f5297fa 28/44: Improve naming for user and buffer list, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 0743dd15f0 29/44: change `crdt--session-name' to `crdt--session-urlstr', ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 9f83928758 32/44: Merge branch 'master' into development, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 7548a9420e 33/44: :name->:urlstr, and fix TLS downgrade, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 01551d66c9 34/44: Fix default theme bug and other bugs., ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 949f4a1afd 37/44: reflect key binding addition in README.org, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 780478a090 39/44: Fix crdt--cycle-user bug with different buffer/window, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt df8954b9c3 40/44: feat: Option to put tuntox password in copied URL, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 72e2b8be12 42/44: Merge branch 'master' into development, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 3883736730 17/44: many changes,
ELPA Syncer <=
- [elpa] externals/crdt e2b6c9ebf6 22/44: get sharing buffer from clients working, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 932566653e 23/44: TLS support by stunnel., ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 282c48c47c 44/44: Revise README, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 709059ff5e 13/44: fix disconnect warn condition, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 2bc5389ba9 26/44: add `crdt-author' to default tracked text properties, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 89e3a2699d 31/44: Capitalise “Session” buffer to follow new convention, ELPA Syncer, 2022/07/02
- [elpa] externals/crdt 1f1e309cd7 41/44: Merge branch 'tuntox-password-in-url' into 'master', ELPA Syncer, 2022/07/02