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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
|
--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, Xmake Open Source Community.
--
-- @author ruki
-- @file server_session.lua
--
-- imports
import("core.base.pipe")
import("core.base.bytes")
import("core.base.object")
import("core.base.global")
import("core.base.option")
import("core.base.hashset")
import("core.base.scheduler")
import("private.service.server_config", {alias = "config"})
import("private.service.message")
import("private.service.remote_build.filesync", {alias = "new_filesync"})
-- define module
local server_session = server_session or object()
-- init server session
function server_session:init(server, session_id)
self._ID = session_id
self._SERVER = server
local filesync = new_filesync(self:sourcedir(), path.join(self:workdir(), "manifest.txt"))
filesync:ignorefiles_add(".git/**")
filesync:ignorefiles_add(".xmake/**")
self._FILESYNC = filesync
end
-- get server session id
function server_session:id()
return self._ID
end
-- get server
function server_session:server()
return self._SERVER
end
-- open server session
function server_session:open()
if self:is_connected() then
return
end
-- ensure source directory
self:_ensure_sourcedir()
-- update status
local status = self:status()
status.connected = true
status.session_id = self:id()
self:status_save()
end
-- close server session
function server_session:close()
if not self:is_connected() then
return
end
-- update status
local status = self:status()
status.connected = false
status.session_id = self:id()
self:status_save()
end
-- set stream
function server_session:stream_set(stream)
self._STREAM = stream
end
-- get stream
function server_session:stream()
return self._STREAM
end
-- diff files
function server_session:diff(respmsg)
local body = respmsg:body()
-- ensure sourcedir
self:_ensure_sourcedir()
-- do snapshot
local filesync = body.xmakesrc and self:_xmake_filesync() or self:_filesync()
local manifest_server = assert(filesync:snapshot(), "server manifest not found!")
local manifest_client = assert(body.manifest, "client manifest not found!")
vprint("%s: diff files in %s ..", self, filesync:rootdir())
-- get all files
local fileitems = hashset.new()
for fileitem, _ in pairs(manifest_client) do
fileitems:insert(fileitem)
end
for fileitem, _ in pairs(manifest_server) do
fileitems:insert(fileitem)
end
-- do diff
local removed = {}
local modified = {}
local inserted = {}
local changed = false
for _, fileitem in fileitems:keys() do
local manifest_info_client = manifest_client[fileitem]
local manifest_info_server = manifest_server[fileitem]
if manifest_info_client and manifest_info_server
and manifest_info_client.sha256 ~= manifest_info_server.sha256 then
table.insert(modified, fileitem)
changed = true
vprint("[*]: %s", fileitem)
elseif not manifest_info_server and manifest_info_client then
table.insert(inserted, fileitem)
changed = true
vprint("[+]: %s", fileitem)
elseif manifest_info_server and not manifest_info_client then
table.insert(removed, fileitem)
changed = true
vprint("[-]: %s", fileitem)
end
end
body.manifest = {changed = changed, removed = removed, inserted = inserted, modified = modified}
vprint("%s: diff files ok", self)
end
-- sync files
function server_session:sync(respmsg)
local body = respmsg:body()
local stream = self:stream()
local manifest = assert(body.manifest, "manifest not found!")
local filesync = body.xmakesrc and self:_xmake_filesync() or self:_filesync()
local sourcedir = body.xmakesrc and self:xmake_sourcedir() or self:sourcedir()
local archivedir = os.tmpfile() .. ".dir"
vprint("%s: sync files in %s ..", self, sourcedir)
if self:_recv_syncfiles(manifest, archivedir) then
-- do sync
for _, fileitem in ipairs(manifest.inserted) do
vprint("[+]: %s", fileitem)
local filepath_server = path.join(sourcedir, fileitem)
local filepath_client = path.join(archivedir, fileitem)
os.cp(filepath_client, filepath_server)
filesync:update(fileitem, filepath_server)
end
for _, fileitem in ipairs(manifest.modified) do
vprint("[*]: %s", fileitem)
local filepath_server = path.join(sourcedir, fileitem)
local filepath_client = path.join(archivedir, fileitem)
os.cp(filepath_client, filepath_server)
filesync:update(fileitem, filepath_server)
end
for _, fileitem in ipairs(manifest.removed) do
vprint("[-]: %s", fileitem)
local filepath_server = path.join(sourcedir, fileitem)
os.rm(filepath_server)
filesync:remove(fileitem)
end
filesync:manifest_save()
else
raise("receive files failed!")
end
os.tryrm(archivedir)
vprint("%s: sync files ok", self)
end
-- pull file
function server_session:pull(respmsg)
local body = respmsg:body()
local stream = self:stream()
local filepattern = body.filename
vprint("pull %s ..", filepattern)
-- get files
local filepaths = os.files(path.join(self:sourcedir(), filepattern))
local fileitems = {}
for _, filepath in ipairs(filepaths) do
local fileitem = path.relative(filepath, self:sourcedir())
if is_host("windows") then
fileitem = fileitem:gsub("\\", "/")
end
if fileitem:startswith("./") then
fileitem = fileitem:sub(3)
end
table.insert(fileitems, fileitem)
end
vprint(fileitems)
-- send files
body.fileitems = fileitems
respmsg:status_set(true)
if stream:send_msg(respmsg) then
for _, fileitem in ipairs(fileitems) do
local filepath = path.join(self:sourcedir(), fileitem)
vprint("sending %s ..", filepath)
if not stream:send_file(filepath, {compress = os.filesize(filepath) > 4096}) then
raise("send %s failed!", filepath)
end
end
end
end
-- clean files
function server_session:clean(respmsg)
local body = respmsg:body()
vprint("%s: clean files in %s ..", self, self:workdir())
os.tryrm(self:sourcedir())
os.tryrm(path.join(self:workdir(), "manifest.txt"))
if body.all then
for _, sessiondir in ipairs(os.dirs(path.join(self:server():workdir(), "sessions", "*"))) do
os.tryrm(path.join(sessiondir, "source"))
os.tryrm(path.join(sessiondir, "manifest.txt"))
end
os.tryrm(self:xmake_sourcedir())
os.tryrm(path.join(self:server():workdir(), "xmakesrc_manifest.txt"))
end
vprint("%s: clean files ok", self)
end
-- run command
function server_session:runcmd(respmsg)
local body = respmsg:body()
local program = body.program
local argv = body.argv
vprint("%s: run command(%s) ..", self, os.args(table.join(program, argv)))
-- init pipes
local stdin_rpipe, stdin_wpipe = pipe.openpair("BA") -- rpipe (block)
local stdin_wpipeopt = {wpipe = stdin_wpipe, stop = false}
local stdout_rpipe, stdout_wpipe = pipe.openpair()
local stdout_rpipeopt = {rpipe = stdout_rpipe, stop = false}
-- read and write pipe
local group_name = "remote_build/runcmd"
scheduler.co_group_begin(group_name, function (co_group)
scheduler.co_start(self._write_pipe, self, stdin_wpipeopt)
scheduler.co_start(self._read_pipe, self, stdout_rpipeopt)
end)
-- run program
local xmakesrc
if os.isfile(path.join(self:xmake_sourcedir(), "core", "main.lua")) then
xmakesrc = self:xmake_sourcedir()
end
try { function ()
os.execv(program, argv, {curdir = self:sourcedir(),
stdout = stdout_wpipe, stderr = stdout_wpipe, stdin = stdin_rpipe,
envs = {XMAKE_IN_SERVICE = "true", XMAKE_PROGRAM_DIR = xmakesrc}})
end}
stdin_rpipe:close()
stdout_wpipe:close()
-- stop it
stdin_wpipeopt.stop = true
stdout_rpipeopt.stop = true
-- wait pipes exits
scheduler.co_group_wait(group_name)
vprint("%s: run command ok", self)
end
-- get work directory
function server_session:workdir()
return path.join(self:server():workdir(), "sessions", self:id())
end
-- is connected?
function server_session:is_connected()
return self:status().connected
end
-- get the status
function server_session:status()
local status = self._STATUS
local statusfile = self:statusfile()
if not status then
if os.isfile(statusfile) then
status = io.load(statusfile)
end
status = status or {}
self._STATUS = status
end
return status
end
-- save status
function server_session:status_save()
io.save(self:statusfile(), self:status())
end
-- get status file
function server_session:statusfile()
return path.join(self:workdir(), "status.txt")
end
-- get sourcedir directory
function server_session:sourcedir()
return path.join(self:workdir(), "source")
end
-- get xmake sourcedir directory
function server_session:xmake_sourcedir()
return self:server():xmake_sourcedir()
end
-- get filesync
function server_session:_filesync()
return self._FILESYNC
end
-- get xmake filesync
function server_session:_xmake_filesync()
return self:server():_xmake_filesync()
end
-- ensure source directory
function server_session:_ensure_sourcedir()
local sourcedir = self:sourcedir()
if not os.isdir(sourcedir) then
os.mkdir(sourcedir)
end
local xmake_sourcedir = self:xmake_sourcedir()
if not os.isdir(xmake_sourcedir) then
os.mkdir(xmake_sourcedir)
end
end
-- write data to pipe
function server_session:_write_pipe(opt)
local buff = bytes(256)
local wpipe = opt.wpipe
vprint("%s: %s: writing data ..", self, wpipe)
while not opt.stop do
local data = self:_recv_data(buff)
if data then
local real = wpipe:write(data, {block = true})
vprint("%s: %s: write bytes(%d)", self, wpipe, real)
if real < 0 then
break
end
else
break
end
end
wpipe:close()
vprint("%s: %s: write data end", self, wpipe)
end
-- read data from pipe
function server_session:_read_pipe(opt)
local buff = bytes(256)
local rpipe = opt.rpipe
local verbose = option.get("verbose")
vprint("%s: %s: reading data ..", self, rpipe)
local leftstr = ""
while not opt.stop do
local real, data = rpipe:read(buff)
if real > 0 then
if verbose then
leftstr = leftstr .. data:str()
local pos = leftstr:lastof("\n", true)
if pos then
cprint(leftstr:sub(1, pos - 1))
leftstr = leftstr:sub(pos + 1)
end
end
if not self:_send_data(data) then
break
end
elseif real == 0 then
if rpipe:wait(pipe.EV_READ, -1) < 0 then
break
end
else
break
end
end
rpipe:close()
if #leftstr > 0 then
cprint(leftstr)
end
-- say end to client
self:_send_end()
vprint("%s: %s: read data end", self, rpipe)
end
-- recv data from stream
function server_session:_recv_data(buff)
local stream = self:stream()
local msg = stream:recv_msg({timeout = -1})
if msg and msg:is_data() then
return stream:recv(buff, msg:body().size)
end
end
-- send data to stream
function server_session:_send_data(data)
local stream = self:stream()
if stream:send_msg(message.new_data(self:id(), data:size())) then
if stream:send(data) then
return stream:flush()
end
end
end
-- send end to stream
function server_session:_send_end()
local stream = self:stream()
if stream:send_msg(message.new_end(self:id())) then
return stream:flush()
end
end
-- recv syncfiles
function server_session:_recv_syncfiles(manifest, outputdir)
local stream = self:stream()
for _, fileitem in ipairs(manifest.inserted) do
local filepath = path.join(outputdir, fileitem)
if not stream:recv_file(filepath) then
dprint("%s: recv %s failed!", self, filepath)
return false
end
end
for _, fileitem in ipairs(manifest.modified) do
local filepath = path.join(outputdir, fileitem)
if not stream:recv_file(filepath) then
dprint("%s: recv %s failed!", self, filepath)
return false
end
end
return true
end
function server_session:__tostring()
return string.format("<session %s>", self:id())
end
function main(server, session_id)
local instance = server_session()
instance:init(server, session_id)
return instance
end
|