;;; rudel-infinote-group-document.el --- Infinote document group -*- lexical-binding:t -*- ;; ;; Copyright (C) 2009, 2010, 2014, 2016 Free Software Foundation, Inc. ;; ;; Author: Jan Moringen ;; Keywords: rudel, infinote, group, communication ;; X-RCS: $Id:$ ;; ;; This file is part of Rudel. ;; ;; Rudel 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. ;; ;; Rudel 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 Rudel. If not, see . ;;; Commentary: ;; ;; This file contains the implementation of the infinote communication ;; group used by document sessions. ;;; History: ;; ;; 0.1 - Initial version ;;; Code: ;; (eval-when-compile (require 'cl-lib)) (require 'warnings) (require 'eieio) (require 'rudel-color) ;; for HSV color functions (require 'rudel-xml) (require 'rudel-infinote-group) ;;; Class rudel-infinote-group-document-state-idle ;; (defclass rudel-infinote-group-document-state-idle (rudel-infinote-group-state) () "") (cl-defmethod rudel-infinote/sync-begin ((_this rudel-infinote-group-document-state-idle) xml) "Handle 'sync-begin' message." (with-tag-attrs ((num-messages num-messages number)) xml ;; Switch to synchronizing state. (list 'synchronizing num-messages))) (cl-defmethod rudel-infinote/user-join ((this rudel-infinote-group-document-state-idle) xml) "Handle 'user-join' message." (with-tag-attrs ((id id number) name status ;; (caret caret number) ;; (selection selection number) (hue hue number)) xml (if (rudel-find-user this id #'= #'rudel-id) ;; If the user is already subscribed to the document, ;; display a warning and ignore the request. (display-warning '(rudel infinote) (format "User with id %d is already subscribed to document `%s'" id (object-name-string (oref this document))) :warning) ;; Otherwise, construct the document user object and add it to ;; the document. (rudel-add-user this (rudel-infinote-document-user name :id id :color (rudel-hsv->string hue 0.3 (rudel-color-background-value)) :status (intern-soft status))))) ;; Stay in this state. nil) (cl-defmethod rudel-infinote/user-rejoin ((this rudel-infinote-group-document-state-idle) xml) "" (with-tag-attrs ((id id number) ;; name status ;; (caret caret number) ;; (selection selection number) (hue hue number)) xml (let ((user (rudel-find-user this id #'= #'rudel-id))) (if (not user) ;; We did not find the user, display a warning and give up. (display-warning '(rudel infinote) (format "Could not find user: %d" id) :warning) ;; If we found the user, update its slots. (rudel-set-color user (rudel-hsv->string hue 0.3 (rudel-color-background-value))) (rudel-set-id user id) (rudel-set-status user (intern-soft status)) (rudel-change-notify user)))) ;; Stay in this state. nil) (cl-defmethod rudel-infinote/user-status-change ((this rudel-infinote-group-document-state-idle) xml) "" (with-tag-attrs ((id id number) status) xml (let ((user (rudel-find-user this id #'= #'rudel-id))) (if (not user) ;; We did not find the user, display a warning and give up. (display-warning '(rudel infinote) (format "Could not find user: %d" id) :warning) ;; If we found the user, update its slots. (rudel-set-status user (intern-soft status)) ;; TODO add type symbol to with-tag-attr? (rudel-change-notify user)))) ;; Stay in this state. nil) (cl-defmethod rudel-infinote/user-color-change ((this rudel-infinote-group-document-state-idle) xml) "" (with-tag-attrs ((id id number) (hue hue number)) xml (let ((user (rudel-find-user this id #'= #'rudel-id))) (if (not user) ;; We did not find the user, display a warning and give up. (display-warning '(rudel infinote) (format "Could not find user: %d" id) :warning) ;; If we found the user, update its slots. (rudel-set-color user (rudel-hsv->string hue 0.3 (rudel-color-background-value))) (rudel-change-notify user)))) ;; Stay in this state. nil) ;; TODO does this belong here or in derived classes? (cl-defmethod rudel-infinote/request ((this rudel-infinote-group-document-state-idle) xml) "" (with-tag-attrs ((user-id user number)) xml (let* ((operation (car (xml-node-children xml))) ;; TODO are multiple operations possible? (type (xml-node-name operation)) (user (rudel-find-user this user-id #'= #'rudel-id))) (if (not user) ;; Warn if we cannot find the user. (display-warning '(rudel infinote) (format "Could not find user: %d'" user-id) :warning) ;; Dispatch to handler. (rudel-dispatch this "rudel-infinote/request/" (symbol-name type) (list user operation))))) ;; Stay in this state. nil) (cl-defmethod rudel-infinote/session-close ((_this rudel-infinote-group-document-state-idle) _xml) "Handle 'session-close' message." ;; Switch to closed state. 'closed) ;; we can receive ;; ;; Human readable ;; ;; or should the base class handle this? ;; TODO we can send ;; ;; and ;; ;;; Class rudel-infinote-state-synchronizing ;; (defclass rudel-infinote-group-document-state-synchronizing (rudel-infinote-group-state) ((all-items :initarg :all-items :type (integer 0) :documentation "") (remaining-items :initarg :num-items :type (integer 0) :documentation "")) "") (cl-defmethod rudel-enter ((this rudel-infinote-group-document-state-synchronizing) num-items) "" (with-slots (document all-items remaining-items) this ;; Remove all subscribed users from the document. The ;; synchronization will add users anew. (rudel-clear-users document) (setq all-items num-items remaining-items num-items)) nil) (cl-defmethod rudel-infinote/sync-user ((this rudel-infinote-group-document-state-synchronizing) xml) "Create a user object and add it to the document." ;; TODO send sync-error if remaining-items is already zero (with-slots (remaining-items document) this (with-tag-attrs ((id id number) name status ;; (caret caret number) ;; (selection selection number) (hue hue number)) xml (let ((user (rudel-infinote-document-user name :color (rudel-hsv->string hue 0.3 (rudel-color-background-value)) :id id :status (intern-soft status)))) ;; Add user to the list of subscribed users of the document. (rudel-add-user document user))) ;; Expect one less synchronization item. (cl-decf remaining-items)) ;; Stay in this state. nil) (cl-defmethod rudel-infinote/sync-request ((this rudel-infinote-group-document-state-synchronizing) _xml) "Handle 'sync-request' message." (with-slots (remaining-items) this ;; (with-tag-attrs (user time) xml ;; ) ;; TODO ;; Expect one less synchronization item. (cl-decf remaining-items)) ;; Stay in this state. nil) (cl-defmethod rudel-infinote/sync-segment ;; TODO text documents only? ((this rudel-infinote-group-document-state-synchronizing) _xml) "Handle 'sync-segment' message." (with-slots (remaining-items) this ;; (with-tag-attrs (author) xml ;; ) ;; TODO ;; Expect one less synchronization item. (cl-decf remaining-items)) ;; Stay in this state. nil) (cl-defmethod rudel-infinote/sync-end ((this rudel-infinote-group-document-state-synchronizing) _xml) "Handle 'sync-end' message." (with-slots (all-items remaining-items) this (if (= remaining-items 0) ;; Everything is fine, we received the expected number of ;; items. (rudel-send this '(sync-ack)) ;; We did not received the correct number of items. Send an ;; error message and display a warning. (rudel-send this `(sync-error ((domain . "INF_SESSION_SYNCHRONIZATION_ERROR") (code . "0")) ,(format "Received less synchronization items (%d) than previously announced (%d)" (- all-items remaining-items) all-items))) (display-warning '(rudel infinote) (format "Received less synchronization items (%d) than previously announced (%d)" (- all-items remaining-items) :warning)))) ;; Stay in this state. 'idle) (cl-defmethod rudel-infinote/sync-cancel ((_this rudel-infinote-group-document-state-synchronizing) _xml) "Handle 'sync-cancel' message." ;; Stay in this state. 'idle) ;; In this state, we can send ;; ;; Human readable ;; ;;; Class rudel-infinote-group-document-state-joining ;; (defclass rudel-infinote-group-document-state-joining (rudel-infinote-group-state) () "This state indicates that we are currently joining the session associated to a document. After sending a 'user-join' message, we expect a 'user-join' or 'user-rejoin' message in response.") (cl-defmethod rudel-enter ((this rudel-infinote-group-document-state-joining)) "" (let ((self (rudel-self (oref this :session)))) (with-slots ((name :object-name) color status) self (let ((hue (car (apply #'rudel-rgb->hsv (color-values color))))) (rudel-send this `(user-join ((name . ,name) (status . "active") (time . "") (caret . ,(format "%d" 0)) ;; selection (hue . ,(format "%f" hue)))))))) ;; Remain in this state and wait for reply. nil) (cl-defmethod rudel-infinote/user-join ((this rudel-infinote-group-document-state-joining) xml) "Handle 'user-join' message." (with-tag-attrs ((id id number) name status ;; (caret caret number) ;; (selection selection number) (hue hue number)) xml ;; In the joining state, the join message has to refer to our own ;; user. Therefore, we obtain the self user object from the ;; session, update its slots and add it to the document. (let ((self (rudel-self (oref this :session)))) ;; When we did not find the self user display a warning. (when (not self) (display-warning '(rudel infinote) "No self user in session" :warning)) (let ((user (rudel-add-user this (apply #'rudel-infinote-document-user name :color (rudel-hsv->string hue 0.3 (rudel-color-background-value)) :id id :status (intern-soft status) (when self (list :session-user self)))))) (rudel-set-self this user)))) ;; Since we expect the join or rejoin message for our own user, we ;; can leave the state and go to idle. 'idle) (cl-defmethod rudel-infinote/user-rejoin ((this rudel-infinote-group-document-state-joining) xml) "" (with-tag-attrs ((id id number) name status ;; (caret caret number) ;; (selection selection number) (hue hue number)) xml (let ((user (rudel-find-user this id #'= #'rudel-id))) ;; When we did not find the self user or the document user or ;; they are not the same object, display a warning. (if (not user) (display-warning '(rudel infinote) (format "Could not find self user in document: %d" id) :warning) ;; Update slots of the user object (object-set-name-string user name) (rudel-set-color user (rudel-hsv->string hue 0.3 (rudel-color-background-value))) (rudel-set-id user id) (rudel-set-status user (intern-soft status)) (rudel-set-self this user) (rudel-change-notify user)))) ;; Since we expect the join or rejoin message for our own user, we ;; can leave the state and go to idle. 'idle) ;;; ;; (defvar rudel-infinote-group-document-states '((idle . rudel-infinote-group-document-state-idle) (synchronizing . rudel-infinote-group-document-state-synchronizing) (joining . rudel-infinote-group-document-state-joining) (closed . rudel-infinote-group-state-closed)) "TODO") ;;; Class rudel-infinote-group-document ;; (defclass rudel-infinote-group-document (rudel-infinote-group rudel-impersonator rudel-delegator) ((document :initarg :document :type rudel-infinote-document-child :documentation "") (impersonation-target-slot :initform document) (delegation-target-slot :initform document)) "") (cl-defmethod initialize-instance ((this rudel-infinote-group-document) _slots) "" ;; Initialize slots of THIS. (cl-call-next-method) ;; Register states. (rudel-register-states this rudel-infinote-group-document-states) ) (provide 'rudel-infinote-group-document) ;;; rudel-infinote-group-document.el ends here