summaryrefslogtreecommitdiff
path: root/xmake/modules/private/service/remote_build/server_session.lua
blob: 45c8e8736256bc23876389f122f9605b6ea28813 (plain)
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