/
par_socket_server.R
executable file
·195 lines (193 loc) · 8.84 KB
/
par_socket_server.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#' Get or set parameters specific to SciViews socket clients
#'
#' This function manage to persistently store sensible parameters for
#' configuring communication between the server and the client, as well as, any
#' other persistent data you may need. Parameters remain set even if the client
#' disconnects and then reconnects to R, as long R was not restarted.
#'
#' @param client the client identification. By default, it is the socket
#' identifier as it appears in [get_socket_clients()]. Since no attempt is made
#' to check if the client really exists and is connected, you can create fake
#' ones, outside of the socket server, to test your code for instance.
#' @param server_port the port on which the server is running, 8888 by default.
#' Not important for fake socket client configurations.
#' @param client_socket the Tcl name of the socket where the client is connected.
#' By default, it is the same as `client` name, but in case it was modified, do
#' provide a correct `client_socket` string if you want to be able to activate a
#' redirection to it (see [socket_client_connection()]).
#' @param ... the parameters you want to change as named arguments. Non named
#' arguments are ignored with a warning. If you specify `arg = NULL`, the
#' corresponding variable is deleted from the environment.
#'
#' @return
#' Returns the environment where parameters and data for the client are stored.
#' To access those data, see examples below.
#'
#' @details
#' You can assign the environment to a variable, and then, access its content
#' like if it was a list (`e$var` or `e$var <- "new value"`). To get a list of
#' the content, use `ls(par_socket_server(client, port))`, or
#' `ls(par_socket_server(client, port), all.names = TRUE)`, but not
#' `names(par_socket_server(client, port))`. As long as you keep a variable
#' pointing on that environment alive, you have access to last values (i.e.,
#' changes done elsewhere are taken into account). If you want a frozen snapshot
#' of the parameters, you should use
#' `myvar <- as.list(par_socket_server(client, port)`.
#'
#' There is a convenient placeholder for code send by the client to insert
#' automatically the right socket and server_port in
#' `par_socket_server()`: `<<<s>>>`.
#' Hence, code that the client send to access or change its environment is just
#' `par_socket_server(<<<s>>>, bare = FALSE)` or
#' `par_socket_server(<<<s>>>)$bare` to set or get one parameter. Note that you
#' can set or change many parameters at once.
#'
#' Currently, parameters are:
#' - `bare = TRUE|FALSE` for "bare" mode (no prompt, no echo, no multiline; by
#' default, `bare = TRUE`),
#' - `multiline = TRUE|FALSE`: does the server accept code spread on multiple
#' lines and send in several steps (by default, yes, but works only if
#' `bare = FALSE`.
#' - `echo = TRUE|FALSE` is the command echoed to the regular R console (by
#' default `echo = FALSE`).
#' - `last = ""` string to append to each output (for instance to indicate that
#' processing is done),
#' - `prompt = "> "`, the prompt to use (if not in bare mode) and
#' - `continue = "+ "` the continuation prompt to use, when multiline mode is
#' active. You can only cancel a multiline mode by completing the R code you are
#' sending to the server, but you can break it too by sending `<<<esc>>>` before
#' the next instruction. You can indicate `<<<q>>>` or `<<<Q>>>` at the very
#' beginning of an instruction to tell R to disconnect the connection after the
#' command is processed and result is returned (with `<<<q>>>`), or when the
#' instructions are received but before they are processed (with `<<<Q>>>`).
#' This is useful for "one shot" clients (clients that connect, send code and
#' want to disconnect immediately after that). The code send by the server to
#' the client to tell him to disconnect gracefully (and do some housekeeping) is
#' `\\f` send at the beginning of one line. So, clients should detect this and
#' perform the necessary actions to gracefully disconnect from the server as
#' soon as possible, and he cannot send further instructions from this moment
#' on.
#'
#' For clients that repeatedly connect and disconnect, but want persistent data,
#' the default client identifier (the socket name) cannot be used, because that
#' socket name would change from connection to connection. The client must then
#' provide its own identifier. This is done by sending `<<<id=myID>>>` at the
#' very beginning of a command. This must be done for all commands! `myID` must
#' use only characters or digits. This code could be followed by `<<<e>>>`,
#' `<<<h>>>` or `<<<H>>>`. These commands are intended for R editors/IDE. The
#' first code `<<<e>>>` sets the server into a mode that is suitable to
#' evaluate R code (including in a multi-line way). The other code temporarily
#' configure the server to run the command (in single line mode only) in a
#' hidden way. They can be used to execute R code without displaying it in the
#' console (for instance, to start context help, to get a calltip, or a
#' completion list, etc.). The differences between `<<<h>>>` and `<<<H>>>` is
#' that the former waits for command completion and returns results of the
#' command to the client before disconnecting, while the latter disconnects from
#' the client before executing the command.
#'
#' There is a simple client (written in Tcl) available in the /etc subdirectory
#' of this package installation. Please, read the 'ReadMe.txt' file in the same
#' directory to learn how to use it. You can use this simple client to
#' experiment with the communication using these sockets, but it does not
#' provide advanced command line edition, no command history, and avoid pasting
#' more than one line of code into it.
#'
#' @export
#' @seealso [start_socket_server()], [send_socket_clients()], [get_socket_clients()],
#' [socket_client_connection()]
#' @keywords IO utilities
#' @concept stateful socket server interprocess communication
#'
#' @examples
#' # We use a fake socket client configuration environment
#' e <- par_socket_server("fake")
#' # Look at what it contains
#' ls(e)
#' # Get one data
#' e$bare
#' # ... or
#' par_socket_server("fake")$bare
#'
#' # Change it
#' par_socket_server("fake", bare = FALSE)$bare
#' # Note it is changed too for e
#' e$bare
#'
#' # You can change it too with
#' e$bare <- TRUE
#' e$bare
#' par_socket_server("fake")$bare
#'
#' # Create a new entry
#' e$foo <- "test"
#' ls(e)
#' par_socket_server("fake")$foo
#' # Now delete it
#' par_socket_server("fake", foo = NULL)
#' ls(e)
#'
#' # Our fake socket config is in SciViews:TempEnv environment
#' s <- search()
#' l <- length(s)
#' pos <- (1:l)[s == "SciViews:TempEnv"]
#' ls(pos = pos) # It is named 'socket_client_fake'
#' # Delete it
#' rm(socket_client_fake, pos = pos)
#' # Do some house keeping
#' rm(list = c("s", "l", "pos"))
par_socket_server <- function(client, server_port = 8888,
client_socket = client, ...) {
# Set or get parameters for a given socket client
# No attempt is made to make sure this client exists
sc <- paste("socket_client", client, sep = "_")
if (!exists(sc, envir = temp_env(), inherits = FALSE, mode = "environment")) {
# Create a new environment with default values
e <- new.env(parent = temp_env())
e$client <- client
e$client_socket <- client_socket
e$server_port <- server_port
e$prompt <- ":> " # Default prompt
e$continue <- ":+ " # Default continuation prompt
e$code <- "" # Current partial code for multiline mode
e$last <- "" # String to add at the end of evaluations
e$echo <- FALSE # Don't echo commands to the console
e$flag <- FALSE # Do not flag pieces of code (not used yet!)
e$multiline <- TRUE # Allow for multiline code
e$bare <- TRUE # Always start in "bare" mode
# Note: in bare mode, all other parameters are inactive!
# Assign it to SciViews:TempEnv
assign(sc, e, envir = temp_env())
} else {
e <- get(sc, envir = temp_env(), mode = "environment")
}
# Change or add parameters if they are provided
# There is no reason that server_port changes
# but if a client disconnects and reconnects, the client_socket may be
# different! But only change if it is sockXXX
if (grepl("^sock[0-9]+$", client_socket))
e$client_socket <- client_socket
args <- list(...)
if (l <- length(args)) {
change.par <- function(x, val, env) {
if (is.null(x))
return(FALSE) # Do nothing without a valid name
if (is.null(val)) {
suppressWarnings(rm(list = x, envir = env)) # Remove it
return(TRUE)
}
env[[x]] <- val # Add or change this variable in the environment
return(TRUE)
}
n <- names(args)
res <- rep(TRUE, l)
for (i in seq_len(l))
res[i] <- change.par(n[i], args[[i]], e)
if (any(!res))
warning("Non named arguments are ignored")
}
invisible(e)
}
# Old name of the function
#' @export
#' @rdname par_socket_server
parSocket <- par_socket_server