diff options
| author | ruki <[email protected]> | 2022-05-21 00:58:29 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2022-05-21 00:58:29 +0800 |
| commit | c0a8eb13c2f309ee965d75afac6d7eb05c73fa9f (patch) | |
| tree | 8e19ecf85ca7bef3ae4b24eb46066405c85c9260 | |
| parent | 1ebaa37d7fab7fd6a226e1a738709c4fbc020154 (diff) | |
add remote cache stub
3 files changed, 562 insertions, 0 deletions
diff --git a/xmake/modules/private/service/remote_cache/client.lua b/xmake/modules/private/service/remote_cache/client.lua new file mode 100644 index 000000000..78c6945f8 --- /dev/null +++ b/xmake/modules/private/service/remote_cache/client.lua @@ -0,0 +1,292 @@ +--!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, TBOOX Open Source Group. +-- +-- @author ruki +-- @file client.lua +-- + +-- imports +import("core.base.bytes") +import("core.base.base64") +import("core.base.socket") +import("core.base.option") +import("core.base.scheduler") +import("core.project.config", {alias = "project_config"}) +import("lib.detect.find_tool") +import("private.service.client_config", {alias = "config"}) +import("private.service.message") +import("private.service.client") +import("private.service.stream", {alias = "socket_stream"}) + +-- define module +local remote_cache_client = remote_cache_client or client() +local super = remote_cache_client:class() + +-- init client +function remote_cache_client:init() + super.init(self) + + -- init address + local address = assert(config.get("remote_cache.connect"), "config(remote_cache.connect): not found!") + self:address_set(address) + + -- get project directory + local projectdir = os.projectdir() + local projectfile = os.projectfile() + if projectfile and os.isfile(projectfile) and projectdir then + self._PROJECTDIR = projectdir + self._WORKDIR = path.join(project_config.directory(), "remote_cache") + else + raise("we need enter a project directory with xmake.lua first!") + end + + -- init filesync + local filesync = new_filesync(self:projectdir(), path.join(self:workdir(), "manifest.txt")) + filesync:ignorefiles_add(".git/**") + filesync:ignorefiles_add(".xmake/**") + self._FILESYNC = filesync +end + +-- get class +function remote_cache_client:class() + return remote_cache_client +end + +-- connect to the remote server +function remote_cache_client:connect() + if self:is_connected() then + print("%s: has been connected!", self) + return + end + + -- we need user authorization? + local token = config.get("remote_cache.token") + if not token and self:user() then + + -- get user password + cprint("Please input user ${bright}%s${clear} password to connect <%s:%d>:", self:user(), self:addr(), self:port()) + io.flush() + local pass = (io.read() or ""):trim() + assert(pass ~= "", "password is empty!") + + -- compute user authorization + token = base64.encode(self:user() .. ":" .. pass) + token = hash.md5(bytes(token)) + end + + -- do connect + local addr = self:addr() + local port = self:port() + local sock = assert(socket.connect(addr, port), "%s: server unreachable!", self) + local session_id = self:session_id() + local ok = false + local errors + print("%s: connect %s:%d ..", self, addr, port) + if sock then + local stream = socket_stream(sock) + if stream:send_msg(message.new_connect(session_id, {token = token})) and stream:flush() then + local msg = stream:recv_msg() + if msg then + vprint(msg:body()) + if msg:success() then + ok = true + else + errors = msg:errors() + end + end + end + end + if ok then + print("%s: connected!", self) + else + print("%s: connect %s:%d failed, %s", self, addr, port, errors or "unknown") + end + + -- update status + local status = self:status() + status.addr = addr + status.port = port + status.token = token + status.connected = ok + status.session_id = session_id + self:status_save() + + -- sync files + if ok then + self:sync() + end +end + +-- disconnect server +function remote_cache_client:disconnect() + if not self:is_connected() then + print("%s: has been disconnected!", self) + return + end + local addr = self:addr() + local port = self:port() + local sock = socket.connect(addr, port) + local session_id = self:session_id() + local errors + local ok = false + print("%s: disconnect %s:%d ..", self, addr, port) + if sock then + local stream = socket_stream(sock) + if stream:send_msg(message.new_disconnect(session_id, {token = self:token()})) and stream:flush() then + local msg = stream:recv_msg() + if msg then + vprint(msg:body()) + if msg:success() then + ok = true + else + errors = msg:errors() + end + end + end + else + -- server unreachable, but we still disconnect it. + wprint("%s: server unreachable!", self) + ok = true + end + if ok then + print("%s: disconnected!", self) + else + print("%s: disconnect %s:%d failed, %s", self, addr, port, errors or "unknown") + end + + -- update status + local status = self:status() + status.token = nil + status.connected = not ok + self:status_save() +end + +-- is connected? +function remote_cache_client:is_connected() + return self:status().connected +end + +-- get the status +function remote_cache_client: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 remote_cache_client:status_save() + io.save(self:statusfile(), self:status()) +end + +-- get the status file +function remote_cache_client:statusfile() + return path.join(self:workdir(), "status.txt") +end + +-- get the project directory +function remote_cache_client:projectdir() + return self._PROJECTDIR +end + +-- get working directory +function remote_cache_client:workdir() + return self._WORKDIR +end + +-- get user token +function remote_cache_client:token() + return self:status().token +end + +-- get the session id, only for unique project +function remote_cache_client:session_id() + return self:status().session_id or hash.uuid():split("-", {plain = true})[1]:lower() +end + +-- set the given client address +function remote_cache_client:address_set(address) + local addr, port, user = self:address_parse(address) + self._ADDR = addr + self._PORT = port + self._USER = user +end + +-- get user name +function remote_cache_client:user() + return self._USER +end + +-- get the ip address +function remote_cache_client:addr() + return self._ADDR +end + +-- get the address port +function remote_cache_client:port() + return self._PORT +end + +function remote_cache_client:__tostring() + return "<remote_cache_client>" +end + +-- is connected? we cannot depend on client:init when run action +function is_connected() + -- the current process is in service? we cannot enable it + if os.getenv("XMAKE_IN_SERVICE") then + return false + end + local projectdir = os.projectdir() + local projectfile = os.projectfile() + if projectfile and os.isfile(projectfile) and projectdir then + local workdir = path.join(project_config.directory(), "remote_cache") + local statusfile = path.join(workdir, "status.txt") + if os.isfile(statusfile) then + local status = io.load(statusfile) + if status and status.connected then + return true + end + end + end +end + +-- new a client instance +function new() + local instance = remote_cache_client() + instance:init() + return instance +end + +-- get the singleton +function singleton() + local instance = _g.singleton + if not instance then + instance = new() + _g.singleton = instance + end + return instance +end + +function main() + return new() +end diff --git a/xmake/modules/private/service/remote_cache/server.lua b/xmake/modules/private/service/remote_cache/server.lua new file mode 100644 index 000000000..354e9abf8 --- /dev/null +++ b/xmake/modules/private/service/remote_cache/server.lua @@ -0,0 +1,133 @@ +--!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, TBOOX Open Source Group. +-- +-- @author ruki +-- @file server.lua +-- + +-- imports +import("core.base.global") +import("private.service.server_config", {alias = "config"}) +import("private.service.message") +import("private.service.server") +import("private.service.stream", {alias = "socket_stream"}) +import("private.service.remote_cache.server_session") +import("lib.detect.find_tool") + +-- define module +local remote_cache_server = remote_cache_server or server() +local super = remote_cache_server:class() + +-- init server +function remote_cache_server:init(daemon) + super.init(self, daemon) + + -- init address + local address = assert(config.get("remote_cache.listen"), "config(remote_cache.listen): not found!") + super.address_set(self, address) + + -- init handler + super.handler_set(self, self._on_handle) + + -- init sessions + self._SESSIONS = {} +end + +-- get class +function remote_cache_server:class() + return remote_cache_server +end + +-- get work directory +function remote_cache_server:workdir() + local workdir = config.get("remote_cache.workdir") + if not workdir then + workdir = path.join(global.directory(), "service", "server", "remote_cache") + end + return workdir +end + +-- on handle message +function remote_cache_server:_on_handle(stream, msg) + local session_id = msg:session_id() + local session = self:_session(session_id) + vprint("%s: %s: <session %s>: on handle message(%d)", self, stream:sock(), session_id, msg:code()) + vprint(msg:body()) + session:stream_set(stream) + local respmsg = msg:clone() + local session_errs + local session_ok = try + { + function() + if self:need_verfiy() then + local ok, errors = self:verify_user(msg:token(), stream:sock():peeraddr()) + if not ok then + session_errs = errors + return false + end + end + if msg:is_connect() then + session:open() + elseif msg:is_disconnect() then + session:close() + self._SESSIONS[session_id] = nil + else + assert(session:is_connected(), "session has not been connected!") + end + return true + end, + catch + { + function (errors) + if errors then + session_errs = tostring(errors) + vprint(session_errs) + end + end + } + } + respmsg:status_set(session_ok) + if not session_ok and session_errs then + respmsg:errors_set(session_errs) + end + local ok = stream:send_msg(respmsg) and stream:flush() + vprint("%s: %s: <session %s>: send %s", self, stream:sock(), session_id, ok and "ok" or "failed") +end + +-- get session +function remote_cache_server:_session(session_id) + local session = self._SESSIONS[session_id] + if not session then + session = server_session(self, session_id) + self._SESSIONS[session_id] = session + end + return session +end + +-- close session +function remote_cache_server:_session_close(session_id) + self._SESSIONS[session_id] = nil +end + +function remote_cache_server:__tostring() + return "<remote_cache_server>" +end + +function main(daemon) + local instance = remote_cache_server() + instance:init(daemon ~= nil) + return instance +end diff --git a/xmake/modules/private/service/remote_cache/server_session.lua b/xmake/modules/private/service/remote_cache/server_session.lua new file mode 100644 index 000000000..5611f73b4 --- /dev/null +++ b/xmake/modules/private/service/remote_cache/server_session.lua @@ -0,0 +1,137 @@ +--!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, TBOOX Open Source Group. +-- +-- @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") + +-- 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 +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 + +-- get work directory +function server_session:workdir() + return path.join(self:server():workdir(), "sessons", 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 + +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 |
