Files
soldr-modules/file_reader/1.0.0/cmodule/module.lua
T
2022-11-22 02:21:28 +03:00

450 lines
17 KiB
Lua

require("yaci")
require("strict")
local ffi = require("ffi")
ffi.cdef [[
// Сигнатура колбэка для возврата ошибок
typedef void(*Module__Error_Ptr)(const char* descr, void* userData);
// Структура для возврата ошибок
typedef struct _Module__ErrorHandler
{
Module__Error_Ptr OnError; // колбэк
void* UserData; // произвольные данные, юзер может их использовать для проброса в колбэк доп. инфы
} Module__ErrorHandler;
// Результат модуля
typedef struct _Module__ResultInfo
{
int Type; // тип результата
int Format; // формат результата
int Encoding; // кодировка
} Module__ResultInfo;
// Результат завершения модуля
typedef struct _Module__FinishResult
{
int FinishCode; // код завершения
bool RestartMePlease; // сообщить агенту, что модуль надо рестартануть
} Module__FinishResult;
typedef struct Module_I Module_I;
typedef void(*Module__Init_Ptr)(Module_I* module, const void* profile, size_t profileSize, const void* savepoint, size_t savepointSize, const Module__ErrorHandler* eh);
typedef Module__FinishResult(*Module__Run_Ptr)(Module_I* module, const Module__ErrorHandler* eh);
typedef void(*Module__Stop_Ptr)(Module_I* module, const Module__ErrorHandler* eh);
typedef void(*Module__Pause_Ptr)(Module_I* module, const Module__ErrorHandler* eh);
typedef void(*Module__Resume_Ptr)(Module_I* module, const Module__ErrorHandler* eh);
]]
if ffi.arch == 'x86' then
ffi.cdef [[
// Параметр info оригинальной сигнатуры раскладывается на стек по элементно
typedef bool(*Module__OnResult_Ptr)(Module_I* module, const char* jobId, int type, int format, int encoding, const void* data, size_t size, const Module__ErrorHandler* eh);
]]
end
if ffi.arch == 'x64' then
ffi.cdef [[
// Параметр info оригинальной сигнатуры передаётся через указатель, чтобы не кастовать каждый элемент в отдельности, заранее определяем его как указатель на int
typedef bool(*Module__OnResult_Ptr)(Module_I* module, const char* jobId, int info, const void* data, size_t size, const Module__ErrorHandler* eh);
]]
end
ffi.cdef [[
typedef const char*(*Module__GetName_Ptr)(Module_I* module, const Module__ErrorHandler* eh);
// Интерфейс модуля
struct Module_I
{
// Указатель на функцию, которая вызывается при получении пакета "Старт" от агента
Module__Init_Ptr Init;
/** Указатель на функцию, реализующую логику модуля.
Вызов этой функции происходит в отдельном потоке после вызова функции "Init"
*/
Module__Run_Ptr Run;
// Указатель на функцию, реализающую остановку модуля
Module__Stop_Ptr Stop;
// Указатель на функцию, реализающую паузу модуля
Module__Pause_Ptr Pause;
// Указатель на функцию, реализающую возобновление работы модуля
Module__Resume_Ptr Resume;
// Указатель на функцию, которая будет вызвана для результатов, пришедших от других модулей
Module__OnResult_Ptr OnResult;
// Указатель на функцию, возвращающую уникальный идентификатор модуля
Module__GetName_Ptr GetName;
};
typedef struct ModuleTransport_I ModuleTransport_I;
typedef void(*ModuleTransport__SendKeepAlive_Ptr)(ModuleTransport_I* transport, const Module__ErrorHandler* eh);
typedef void(*ModuleTransport__SendProgress_Ptr)(ModuleTransport_I* transport, uint32_t progress, const Module__ErrorHandler* eh);
]]
if ffi.os == "Windows" then
if ffi.arch == 'x86' then
ffi.cdef [[
// Параметр info оригинальной сигнатуры раскладывается на стек по элементно
typedef void(*ModuleTransport__SendResult_Ptr)(ModuleTransport_I* transport, int type, int format, int encoding, const void* data, size_t size, const Module__ErrorHandler* eh);
]]
end
if ffi.arch == 'x64' then
ffi.cdef [[
// Параметр info оригинальной сигнатуры передаётся через указатель, чтобы не кастовать каждый элемент в отдельности, заранее определяем его как указатель на int
typedef void(*ModuleTransport__SendResult_Ptr)(ModuleTransport_I* transport, int *info, const void* data, size_t size, const Module__ErrorHandler* eh);
]]
end
end
if ffi.os == "Linux" then
if ffi.arch == 'x86' then
ffi.cdef [[
// Параметр info оригинальной сигнатуры раскладывается на стек по элементно
typedef void(*ModuleTransport__SendResult_Ptr)(ModuleTransport_I* transport, int type, int format, int encoding, const void* data, size_t size, const Module__ErrorHandler* eh);
]]
end
if ffi.arch == 'x64' then
ffi.cdef [[
// Параметр info оригинальной сигнатуры передаётся через указатель, чтобы не кастовать каждый элемент в отдельности, заранее определяем его как указатель на int
typedef void(*ModuleTransport__SendResult_Ptr)(ModuleTransport_I* transport, int *info, int i, const void* data, size_t size, const Module__ErrorHandler* eh);
]]
end
end
ffi.cdef [[
typedef void(*ModuleTransport__SendSavePoint_Ptr)(ModuleTransport_I* transport, const void* data, size_t size, const Module__ErrorHandler* eh);
typedef void(*ModuleTransport__SendState_Ptr)(ModuleTransport_I* transport, int state, const Module__ErrorHandler* eh);
typedef void(*ModuleTransport__SendError_Ptr)(ModuleTransport_I* transport, const void* data, size_t size, const Module__ErrorHandler* eh);
// Интерфейс транспорта модуля
struct ModuleTransport_I
{
ModuleTransport__SendKeepAlive_Ptr SendKeepAlive;
ModuleTransport__SendProgress_Ptr SendProgress;
ModuleTransport__SendResult_Ptr SendResult;
ModuleTransport__SendSavePoint_Ptr SendSavePoint;
ModuleTransport__SendState_Ptr SendState;
ModuleTransport__SendError_Ptr SendError;
};
// Экспортируемые функции из dll-ки модуля
/** Создать модуль.
** @param transport
** Указатель на транспорт, который модуль должен использовать для отправки сообщений.
** Модуль не должен удалять этот указатель!
** @param argc, argv
** Аргументы, с которыми был запущен агентом модуль.
** @param eh
** Структура для возврата ошибок.
*/
Module_I* Module__Create(ModuleTransport_I* transport, int argc, const char** argv, const Module__ErrorHandler* eh);
// Удалить ранее созданный модуль
void Module__Destroy(Module_I* module);
// Вернуть версию модуля
int Module__Version();
// Вернуть версию API, которую реализует модуль
int Module__API_Version();
]]
CModule = newclass("CModule")
function CModule:init(moduleName, libdir, fprint)
assert(type(moduleName) == "string", "module name is invalid")
assert(type(libdir) == "string", "library directory path is invalid")
self.print = type(print) == "function" and fprint or print
self:unregister()
self.libdir = libdir
if ffi.os == "Linux" then self:load_depends() end
self:wrap_load(function()
if ffi.os == "Linux" then
self.module = ffi.load(moduleName .. ".so")
else
self.module = ffi.load(moduleName .. ".dll")
end
end)
self.api = {
create = self.module.Module__Create,
destroy = self.module.Module__Destroy,
version = self.module.Module__Version,
api_version = self.module.Module__API_Version,
}
self.sd = ""
end
function CModule:free()
self.print("call CModule:free")
self.deps = nil
self:unregister()
end
function CModule:unregister()
self.print("call CModule:unregister")
if self.module_i then
self:destroy()
self.print("destroy successful")
end
if self.functions then
self.functions["SendKeepalive"] = nil
self.functions["SendProgress"] = nil
self.functions["SendResult"] = nil
self.functions["SendSavepoint"] = nil
self.functions["SendState"] = nil
self.functions["SendError"] = nil
self.functions["HandleError"] = nil
end
self.functions = nil
self.error = nil
if self.transport then
self.transport.SendKeepAlive = nil
self.transport.SendProgress = nil
self.transport.SendResult = nil
self.transport.SendSavePoint = nil
self.transport.SendState = nil
self.transport.SendError = nil
end
self.transport = nil
if self.api then
self.api.create = nil
self.api.destroy = nil
self.api.version = nil
self.api.api_version = nil
end
self.api = nil
self.module_i = nil
self.module = nil
self.profile = nil
self.argv_type = nil
self.argc = nil
self.argv = nil
self.sp_filename = nil
self.sd = nil
collectgarbage("collect")
end
function CModule:register(profile, callbacks, sp_filename)
self.print("call CModule:register")
assert(type(profile) == "string", "module profile is invalid")
assert(type(callbacks) == "table", "callbacks is invalid")
assert(type(sp_filename) == "string", "savepoint file path is invalid")
if self.module == nil then
return false, "module not loaded"
end
local function get_argv(...)
local nargs = select("#", ...)
local argv = {...}
for i = 1, nargs do
local v = tostring(argv[i])
argv[i] = v
end
return nargs, self.argv_type(nargs, argv)
end
self.functions = {}
self.functions["SendKeepalive"] = function(transport, _)
if callbacks and transport == self.transport and callbacks["keep_alive"] then
callbacks["keep_alive"]()
end
end
self.functions["SendProgress"] = function(transport, progress, _)
if callbacks and transport == self.transport and callbacks["progress"] then
callbacks["progress"](progress)
end
end
if ffi.os == "Windows" then
if ffi.arch == 'x86' then
self.functions["SendResult"] = function(transport, _, _, _, data, size, _)
if callbacks and transport == self.transport and callbacks["result"] and data then
callbacks["result"](ffi.string(data, size))
end
end
end
if ffi.arch == 'x64' then
self.functions["SendResult"] = function(transport, _, data, size, _)
if callbacks and transport == self.transport and callbacks["result"] and data then
callbacks["result"](ffi.string(data, size))
end
end
end
end
if ffi.os == "Linux" then
if ffi.arch == 'x86' then
self.functions["SendResult"] = function(transport, _, _, _, data, size, _)
if callbacks and transport == self.transport and callbacks["result"] and data then
callbacks["result"](ffi.string(data, size))
end
end
end
if ffi.arch == 'x64' then
self.functions["SendResult"] = function(transport, _, _, data, size, _)
if callbacks and transport == self.transport and callbacks["result"] and size and data then
callbacks["result"](ffi.string(data, size))
end
end
end
end
self.functions["SendSavepoint"] = function(transport, data, size, _)
if data and size ~= 0 then
local sd = ffi.string(data, size)
if callbacks and transport == self.transport and callbacks["save_point"] and size and data then
callbacks["save_point"](sd)
end
if self.sp_filename and sd and (#self.sd == 0 or self.sd ~= sd) then
self.sd = sd
local file = io.open(self.sp_filename, "wb+")
if file then
file:write(sd)
file:flush()
file:close()
end
return
end
end
end
self.functions["SendState"] = function(transport, state, _)
if callbacks and transport == self.transport and callbacks["state"] then
callbacks["state"](state)
end
end
self.functions["SendError"] = function(transport, data, size, _)
if callbacks and transport == self.transport and callbacks["error"] then
callbacks["error"](ffi.string(data, size))
end
end
self.functions["HandleError"] = function(descr, _)
self.print("file reader library handled error: ", ffi.string(descr))
end
self.error = ffi.new("Module__ErrorHandler", {ffi.cast("Module__Error_Ptr", self.functions["HandleError"]), nil})
self.transport = ffi.new("ModuleTransport_I", {
ffi.cast("ModuleTransport__SendKeepAlive_Ptr", self.functions["SendKeepalive"]),
ffi.cast("ModuleTransport__SendProgress_Ptr", self.functions["SendProgress"]),
ffi.cast("ModuleTransport__SendResult_Ptr", self.functions["SendResult"]),
ffi.cast("ModuleTransport__SendSavePoint_Ptr", self.functions["SendSavepoint"]),
ffi.cast("ModuleTransport__SendState_Ptr", self.functions["SendState"]),
ffi.cast("ModuleTransport__SendError_Ptr", self.functions["SendError"])
})
self.argv_type = ffi.typeof("const char* [?]")
self.argc, self.argv = get_argv("--id", "692db8c2-9d54-11eb-a8b3-0242ac130003")
self.module_i = self.api.create(self.transport, self.argc, self.argv, self.error)
self.sp_filename = sp_filename
-- SAVEPOINT--
local svp = ""
if sp_filename then
local file = io.open(self.sp_filename, "rb")
if file then
svp = file:read("*a")
file:close()
end
end
self.module_i.Init(self.module_i,
ffi.cast("const void*", profile),
#profile, svp, #svp, self.error)
return true, ""
end
function CModule:destroy()
self.print("call CModule:destroy")
assert(self.module, "module library is not exist")
assert(self.module_i, "instance module is not exist")
self.api.destroy(self.module_i)
end
function CModule:registered()
self.print("call CModule:registered")
return self.transport ~= nil
end
function CModule:run()
self.print("call CModule:run")
assert(self.module_i, "instance module is not exist")
return self.module_i.Run(self.module_i, self.error)
end
function CModule:stop()
self.print("call CModule:stop")
assert(self.module_i, "instance module is not exist")
return self.module_i.Stop(self.module_i, self.error)
end
function CModule:pause()
self.print("call CModule:pause")
assert(self.module_i, "instance module is not exist")
return self.module_i.Pause(self.module_i, self.error)
end
function CModule:resume()
self.print("call CModule:resume")
assert(self.module_i, "instance module is not exist")
return self.module_i.Resume(self.module_i, self.error)
end
function CModule:version()
self.print("call CModule:version")
assert(self.module_i, "instance module is not exist")
return self.module_i.Module__Version()
end
function CModule:version_api()
self.print("call CModule:version_api")
assert(self.module_i, "instance module is not exist")
return self.module_i.Module__API_Version()
end
if ffi.os == "Linux" then
function CModule:load_depends()
local lfs = require("lfs")
local lpath = require("path")
self.deps = {}
for file in lfs.dir(self.libdir) do
if file ~= "." and file ~= ".." then
table.insert(self.deps, ffi.load(lpath.combine(self.libdir, file)))
end
end
end
function CModule:wrap_load(callback)
local _ = self
callback()
end
end
if ffi.os == "Windows" then
function CModule:wrap_load(callback)
local lk32 = require("waffi.windows.kernel32")
local ctmpdir = ffi.new("char[?]", 256)
local stmpdir = ffi.new("char[?]", #self.libdir + 1)
ffi.copy(stmpdir, self.libdir)
lk32.GetDllDirectoryA(256, ctmpdir)
lk32.SetDllDirectoryA(stmpdir)
callback()
lk32.SetDllDirectoryA(ctmpdir)
end
end