Skip to content

Commit 8e86619

Browse files
committed
kotasync: add command line script + binary launcher
1 parent 6948b15 commit 8e86619

File tree

4 files changed

+367
-0
lines changed

4 files changed

+367
-0
lines changed

kotasync/kotasync.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include <lauxlib.h>
2+
#include <lualib.h>
3+
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
7+
static const char *bootstrap_script = (R""""(
8+
local redirects = { ["libs/libkoreader-lfs"] = "lfs" }
9+
for modulename in (')"""" LUA_MODULES R""""('):gmatch("[^ ]+") do
10+
local redir = modulename:gsub("/", "_")
11+
if redir ~= modulename then
12+
redirects[modulename] = redir
13+
end
14+
end
15+
table.insert(package.loaders, function (modulename)
16+
local redir = redirects[modulename]
17+
if redir then
18+
return function() return require(redir) end
19+
end
20+
end)
21+
require "kotasync"
22+
)"""");
23+
24+
25+
int main(int argc, char *argv[]) {
26+
lua_State *L;
27+
if (setenv("KOTASYNC_USE_XZ_LIB", "1", 0)) {
28+
perror("setenv");
29+
return 1;
30+
}
31+
L = lua_open();
32+
if (!L)
33+
return 1;
34+
// Setup `arg` table.
35+
lua_createtable(L, argc, 0);
36+
for (int i = 0; i < argc; ++i) {
37+
lua_pushstring(L, argv[i]);
38+
lua_rawseti(L, -2, i);
39+
}
40+
lua_setglobal(L, "arg");
41+
// Load standard library.
42+
luaL_openlibs(L);
43+
// And run bootstrap script.
44+
if (luaL_dostring(L, bootstrap_script)) {
45+
const char *msg = lua_tostring(L, -1);
46+
fprintf(stderr, "%s\n", msg);
47+
return EXIT_FAILURE;
48+
}
49+
return EXIT_SUCCESS;
50+
}

kotasync/kotasync.lua

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#!./luajit
2+
3+
local lfs
4+
local util
5+
local kotasync
6+
7+
local function naturalsize(size)
8+
local chunk, unit = 1, 'B '
9+
if size >= 1000*1000*1000 then
10+
chunk, unit = 1000*1000*1000, 'GB'
11+
elseif size >= 1000*1000 then
12+
chunk, unit = 1000*1000, 'MB'
13+
elseif size >= 1000 then
14+
chunk, unit = 1000, 'KB'
15+
end
16+
local fmt = chunk > 1 and "%.1f" or "%u"
17+
return string.format(fmt.." %s", size / chunk, unit)
18+
end
19+
20+
local function make(tar_xz_path, kotasync_path, tar_xz_manifest, older_tar_xz_or_kotasync_path)
21+
local tar_xz = kotasync.TarXz:new():open(tar_xz_path, tar_xz_manifest)
22+
if older_tar_xz_or_kotasync_path then
23+
tar_xz:reorder(older_tar_xz_or_kotasync_path)
24+
end
25+
local files = {}
26+
local manifest_by_path = tar_xz.by_path
27+
if tar_xz_manifest then
28+
manifest_by_path = {}
29+
for __, f in ipairs(tar_xz.manifest) do
30+
assert(not manifest_by_path[f])
31+
manifest_by_path[f] = true
32+
end
33+
end
34+
for e in tar_xz:each() do
35+
-- Ignore directories.
36+
if e.size ~= 0 and manifest_by_path[e.path] then
37+
table.insert(files, e)
38+
end
39+
end
40+
if tar_xz_manifest and #files ~= #tar_xz.manifest then
41+
error("mismatched manifest / archive contents")
42+
end
43+
local manifest = {
44+
filename = tar_xz_path:match("([^/]+)$"),
45+
files = files,
46+
xz_check = tonumber(tar_xz.header_stream_flags.check),
47+
}
48+
tar_xz:close()
49+
if not kotasync_path then
50+
assert(tar_xz_path:match("[.]tar.xz$"))
51+
kotasync_path = tar_xz_path:sub(1, -7).."kotasync"
52+
end
53+
kotasync.save_manifest(kotasync_path, manifest)
54+
end
55+
56+
local function sync(state_dir, manifest_url, seed)
57+
local updater = kotasync.Updater:new(state_dir)
58+
if seed and lfs.attributes(seed, "mode") == "file" then
59+
-- If the seed is a kotasync file, we need to load it
60+
-- now, as it may get overwritten by `fetch_manifest`.
61+
local by_path = {}
62+
for i, e in ipairs(kotasync.load_manifest(seed).files) do
63+
by_path[e.path] = e
64+
end
65+
seed = by_path
66+
end
67+
updater:fetch_manifest(manifest_url)
68+
local total_files = #updater.manifest.files
69+
local last_update = 0
70+
local delay = false --190000
71+
local update_frequency = 0.2
72+
local stats = updater:prepare_update(seed, function(count)
73+
local new_update = util.getTimestamp()
74+
if count ~= total_files and new_update - last_update < update_frequency then
75+
return true
76+
end
77+
last_update = new_update
78+
io.stderr:write(string.format("\ranalyzing: %4u/%4u", count, total_files))
79+
if delay then
80+
util.usleep(delay)
81+
end
82+
return true
83+
end)
84+
io.stderr:write(string.format("\r%99s\r", ""))
85+
assert(total_files == stats.total_files)
86+
if stats.missing_files == 0 then
87+
print('nothing to update!')
88+
return
89+
end
90+
print(string.format("missing : %u/%u files", stats.missing_files, total_files))
91+
print(string.format("reusing : %7s (%10u)", naturalsize(stats.reused_size), stats.reused_size))
92+
print(string.format("fetching: %7s (%10u)", naturalsize(stats.download_size), stats.download_size))
93+
io.stdout:flush()
94+
local pbar_indicators = {" ", "", "", "", "", "", "", "", ""}
95+
local pbar_size = 16
96+
local pbar_chunk = (stats.download_size + pbar_size - 1) / pbar_size
97+
local prev_path = ""
98+
local old_progress
99+
last_update = 0
100+
local ok, err = pcall(updater.download_update, updater, function(size, count, path)
101+
local new_update = util.getTimestamp()
102+
if size ~= stats.download_size and new_update - last_update < update_frequency then
103+
return true
104+
end
105+
last_update = new_update
106+
local padding = math.max(#prev_path, #path)
107+
local progress = math.floor(size / pbar_chunk)
108+
local pbar = pbar_indicators[#pbar_indicators]:rep(progress)..pbar_indicators[1 + math.floor(size % pbar_chunk * #pbar_indicators / pbar_chunk)]..(" "):rep(pbar_size - progress - 1)
109+
local new_progress = string.format("\rdownloading: %8s %4u/%4u %s %-"..padding.."s", size, count, stats.missing_files, pbar, path)
110+
if new_progress ~= old_progress then
111+
old_progress = new_progress
112+
io.stderr:write(new_progress)
113+
end
114+
prev_path = path
115+
if delay then
116+
util.usleep(delay)
117+
end
118+
return true
119+
end)
120+
io.stderr:write(string.format("\r%99s\r", ""))
121+
if not ok then
122+
io.stderr:write(string.format("ERROR: %s", err))
123+
return 1
124+
end
125+
end
126+
127+
local help = [[
128+
USAGE: kotasync make [-h] [--manifest TAR_XZ_MANIFEST] [--reorder OLDER_TAR_XZ_OR_KOTASYNC_FILE] TAR_XZ_FILE [KOTASYNC_FILE]
129+
kotasync sync [-h] STATE_DIR KOTASYNC_URL [SEED_DIR_OR_KOTASYNC_FILE]
130+
131+
options:
132+
-h, --help show this help message and exit
133+
134+
MAKE:
135+
136+
TAR_XZ_FILE source tar.xz file
137+
KOTASYNC_FILE destination kotasync file
138+
139+
-m, --manifest TAR_XZ_MANIFEST
140+
archive entry to use as base for manifest
141+
142+
-r, --reorder OLDER_TAR_XZ_OR_KOTASYNC_FILE
143+
will repack the new tar.xz with this order:
144+
┌─────────────┬──────────────────┬────────────────────┐
145+
│ folders │ unmodified files │ new/modified files │
146+
│ (new order) │ (old order) │ (new order) │
147+
└─────────────┴──────────────────┴────────────────────┘
148+
SYNC:
149+
150+
STATE_DIR destination for the kotasync and update files
151+
KOTASYNC_URL URL of kotasync file
152+
SEED_DIR_OR_KOTASYNC_FILE
153+
optional seed directory / kotasync file
154+
]]
155+
156+
local function main()
157+
local command
158+
local options = {}
159+
local arguments = {}
160+
while #arg > 0 do
161+
local a = table.remove(arg, 1)
162+
-- print(i, a)
163+
if a:match("^-(.+)$") then
164+
-- print('option', a)
165+
if a == "-h" or a == "--help" then
166+
io.stdout:write(help)
167+
return
168+
elseif command == "make" and (a == "-m" or a == "--manifest") then
169+
if #arg == 0 then
170+
io.stderr:write(string.format("ERROR: option --manifest: expected one argument\n"))
171+
return 2
172+
end
173+
options.manifest = table.remove(arg, 1)
174+
elseif command == "make" and (a == "-r" or a == "--reorder") then
175+
if #arg == 0 then
176+
io.stderr:write(string.format("ERROR: option --reorder: expected one argument\n"))
177+
return 2
178+
end
179+
options.reorder = table.remove(arg, 1)
180+
else
181+
io.stderr:write(string.format("ERROR: unrecognized option: %s\n", a))
182+
return 2
183+
end
184+
elseif command then
185+
table.insert(arguments, a)
186+
else
187+
command = a
188+
end
189+
end
190+
local fn
191+
if command == "make" then
192+
if #arguments < 1 then
193+
io.stderr:write("ERROR: not enough arguments\n")
194+
return 2
195+
end
196+
if #arguments > 2 then
197+
io.stderr:write("ERROR: too many arguments\n")
198+
return 2
199+
end
200+
fn = function() make(arguments[1], arguments[2], options.manifest, options.reorder) end
201+
elseif command == "sync" then
202+
if #arguments < 2 then
203+
io.stderr:write("ERROR: not enough arguments\n")
204+
return 2
205+
end
206+
if #arguments > 3 then
207+
io.stderr:write("ERROR: too many arguments\n")
208+
return 2
209+
end
210+
fn = function() sync(arguments[1], arguments[2], arguments[3]) end
211+
elseif not command then
212+
io.stderr:write(help)
213+
return 2
214+
else
215+
io.stderr:write(string.format("ERROR: unrecognized command: %s\n", command))
216+
return 2
217+
end
218+
require("ffi/loadlib")
219+
lfs = require("libs/libkoreader-lfs")
220+
util = require("ffi/util")
221+
kotasync = require("ffi/kotasync")
222+
local ok, err = xpcall(fn, debug.traceback)
223+
if not ok then
224+
io.stderr:write(string.format("ERROR: %s\n", err))
225+
return 3
226+
end
227+
end
228+
229+
os.exit(main())

thirdparty/cmake_modules/koreader_targets.cmake

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,93 @@ function(setup_osx_loader)
245245
set_target_properties(osx_loader PROPERTIES OUTPUT_NAME koreader)
246246
endfunction()
247247

248+
# kotasync
249+
declare_koreader_target(
250+
kotasync TYPE executable
251+
DEPENDS luajit::luajit_static
252+
EXCLUDE_FROM_ALL
253+
SOURCES kotasync/kotasync.c
254+
)
255+
function(setup_kotasync)
256+
if(EMULATE_READER)
257+
set(LUAJIT_EXE ${STAGING_DIR}/bin/luajit)
258+
set(LUAJIT_JIT_OPTS)
259+
set(LUA_FFI_POSIX_TYPES posix_types_x64_h)
260+
else()
261+
find_program(
262+
LUAJIT_EXE luajit REQUIRED
263+
PATHS /usr/local/bin /usr/bin
264+
NO_DEFAULT_PATH NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH
265+
NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH
266+
NO_CMAKE_SYSTEM_PATH NO_CMAKE_INSTALL_PREFIX NO_CMAKE_FIND_ROOT_PATH
267+
)
268+
endif()
269+
set(LUAJIT_JIT_OPTS -d -g -o Linux)
270+
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
271+
list(APPEND LUAJIT_JIT_OPTS -X -a arm64)
272+
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm")
273+
list(APPEND LUAJIT_JIT_OPTS -W -a arm)
274+
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "i686")
275+
list(APPEND LUAJIT_JIT_OPTS -W -a x86)
276+
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
277+
list(APPEND LUAJIT_JIT_OPTS -X -a x64)
278+
endif()
279+
set(LUA_MODULES_NAMES)
280+
set(LUA_MODULES_OBJS)
281+
foreach(NAME
282+
# cdecls
283+
ffi/posix_types_64b_h
284+
ffi/posix_types_def_h
285+
ffi/posix_types_x64_h
286+
ffi/posix_types_x86_h
287+
ffi/posix_h
288+
ffi/xxhash_h
289+
ffi/xz_h
290+
ffi/zstd_h
291+
# others
292+
ffi/loadlib
293+
ffi/downloader
294+
ffi/hashoir
295+
ffi/util
296+
ffi/zstd
297+
ffi/kotasync
298+
kotasync
299+
)
300+
string(REPLACE "/" "_" ID ${NAME})
301+
set(OBJ ${CMAKE_CURRENT_BINARY_DIR}/lua_${ID}.o)
302+
set(SRC ${NAME}.lua)
303+
if(NAME STREQUAL "kotasync")
304+
set(SRC ${CMAKE_CURRENT_SOURCE_DIR}/kotasync/${SRC})
305+
endif()
306+
add_custom_command(
307+
COMMAND ${LUAJIT_EXE} -b ${LUAJIT_JIT_OPTS} -n ${ID} ${SRC} ${OBJ}
308+
DEPENDS ${SRC}
309+
OUTPUT ${OBJ}
310+
WORKING_DIRECTORY ${OUTPUT_DIR}
311+
VERBATIM
312+
)
313+
list(APPEND LUA_MODULES_NAMES ${NAME})
314+
list(APPEND LUA_MODULES_OBJS ${OBJ})
315+
endforeach()
316+
list(JOIN LUA_MODULES_NAMES " " LUA_MODULES_NAMES)
317+
set_target_properties(kotasync PROPERTIES ENABLE_EXPORTS TRUE)
318+
target_compile_definitions(kotasync PRIVATE "LUA_MODULES=\"${LUA_MODULES_NAMES}\"")
319+
target_compile_options(kotasync BEFORE PRIVATE -std=gnu11)
320+
target_sources(kotasync PRIVATE ${LUA_MODULES_OBJS})
321+
set(KOTASYNC_SCRIPT ${OUTPUT_DIR}/kotasync.lua)
322+
add_custom_command(
323+
COMMAND ln -snfr ${CMAKE_CURRENT_SOURCE_DIR}/kotasync/kotasync.lua ${KOTASYNC_SCRIPT}
324+
OUTPUT ${KOTASYNC_SCRIPT}
325+
VERBATIM
326+
)
327+
if(ANDROID OR APPLE OR EMULATE_READER)
328+
set(ALL)
329+
else()
330+
set(ALL ALL)
331+
endif()
332+
add_custom_target(kotasync-script ${ALL} DEPENDS ${KOTASYNC_SCRIPT})
333+
endfunction()
334+
248335
# inkview-compat
249336
if(POCKETBOOK)
250337
set(EXCLUDE_FROM_ALL)

thirdparty/cmake_modules/koreader_thirdparty_libs.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ else()
164164
set(LUAJIT_LIB)
165165
endif()
166166
get_target_property(LUAJIT_INC luajit::luajit INTERFACE_INCLUDE_DIRECTORIES)
167+
declare_dependency(luajit::luajit_static INCLUDES luajit-2.1 STATIC luajit-5.1 LIBRARIES dl m)
167168

168169
# luasec
169170
if(MONOLIBTIC)

0 commit comments

Comments
 (0)