Skip to content

Commit 91a1ade

Browse files
committed
kotasync: add command line script + rules to build an AppImage
1 parent f721040 commit 91a1ade

File tree

9 files changed

+370
-2
lines changed

9 files changed

+370
-2
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ $(CMAKE_DIR)/meson_%.ini: $(KOR_BASE)/Makefile.defs | $(CMAKE_DIR)/
8080
$(call write_file,$@,$(meson_$*))
8181

8282
# Forward unknown targets to the CMake build system.
83-
LEFTOVERS = $(filter-out $(PHONY) $(SOUND),$(MAKECMDGOALS))
83+
LEFTOVERS = $(filter-out $(PHONY) $(SOUND),$(MAKECMDGOALS) $(NINJA_GOALS))
8484
.PHONY: $(LEFTOVERS)
8585
$(BASE_PREFIX)all $(LEFTOVERS): skeleton $(BUILD_ENTRYPOINT)
8686
$(if $(wildcard $(BUILD_ENTRYPOINT)),+)cd $(CMAKE_DIR) && $(strip $(NINJA) $(NINJAFLAGS) $(patsubst $(BASE_PREFIX)all,all,$@))

Makefile.defs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,7 @@ set(MESON_TOOLCHAINS --native-file=$(abspath $(MESON_HOST_TOOLCHAIN)) $(if $(fil
973973
set(MESON_FILE_ARG $(if $(filter-out $(MESON_HOST_TOOLCHAIN),$(MESON_CROSS_TOOLCHAIN)),--cross-file,--native-file))
974974

975975
# Miscellaneous.
976+
set(KOTASYNC $(KOTASYNC))
976977
set(MONOLIBTIC $(MONOLIBTIC))
977978
set(USE_LJ_WPACLIENT $(USE_LJ_WPACLIENT))
978979
set(USE_SDL $(SDL))

kotasync/AppRun

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/sh
2+
3+
if [ -z "${APPDIR}" ]; then
4+
APPDIR="$(dirname "$(realpath "$0")")"
5+
fi
6+
7+
export LUA_PATH="${APPDIR}/usr/share/lua/?.lua;${APPDIR}/usr/share/lua/?/init.lua"
8+
export LUA_CPATH="${APPDIR}/usr/share/lua/?.so"
9+
export KOTASYNC_USE_XZ_LIB=1
10+
11+
exec "${APPDIR}/usr/bin/luajit" -l kotasync kotasync "$@"
12+
13+
# vim: sw=4

kotasync/Makefile

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
KODEBUG ?=
2+
KOR_BASE ?= ..
3+
4+
APPIMAGE = 1
5+
KOTASYNC = 1
6+
VERSION = 1.0.0
7+
8+
include $(KOR_BASE)/Makefile.defs
9+
10+
APPDIR = $(OUTPUT_DIR)/AppDir
11+
12+
LINUXDEPLOY = linuxdeploy-$(APPIMAGE_ARCH).AppImage
13+
LINUXDEPLOY_URL = https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20251107-1/$(LINUXDEPLOY)
14+
LINUXDEPLOY_SHA1_aarch64 = a2a35e519a99981cf31fcba9835b485c9b9394d3
15+
LINUXDEPLOY_SHA1_armhf = 6f7f59bf25f32e2604dd897e7c38552e19536595
16+
LINUXDEPLOY_SHA1_x86_64 = a79c5f5c27fe1ed98dc19417fce5a79733d46d15
17+
LINUXDEPLOY_SHA1 = $(LINUXDEPLOY_SHA1_$(APPIMAGE_ARCH))
18+
19+
NINJA_GOALS = dkjson\ koreader-lfs\ luasec\ xxhash\ xz\ zstd
20+
21+
all: appimage
22+
23+
clean: base-clean
24+
25+
define LUA_FILES
26+
27+
common/dkjson.lua
28+
common/ltn12.lua
29+
common/mime.lua
30+
common/mime/mcore.so
31+
common/socket.lua
32+
common/socket/ftp.lua
33+
common/socket/headers.lua
34+
common/socket/http.lua
35+
common/socket/score.so
36+
common/socket/smtp.lua
37+
common/socket/tp.lua
38+
common/socket/url.lua
39+
common/ssl.lua
40+
common/ssl.so
41+
common/ssl/https.lua
42+
43+
ffi/downloader.lua
44+
ffi/hashoir.lua
45+
ffi/kotasync.lua
46+
ffi/loadlib.lua
47+
ffi/posix_h.lua
48+
ffi/posix_types_64b_h.lua
49+
ffi/posix_types_def_h.lua
50+
ffi/posix_types_x64_h.lua
51+
ffi/posix_types_x86_h.lua
52+
ffi/util.lua
53+
ffi/xxhash_h.lua
54+
ffi/xz_h.lua
55+
ffi/zstd.lua
56+
ffi/zstd_h.lua
57+
58+
kotasync.lua
59+
60+
libs/libkoreader-lfs.so
61+
62+
endef
63+
64+
define make_appimage
65+
LINUXDEPLOY_OUTPUT_VERSION='$(VERSION)'
66+
./$(LINUXDEPLOY) --appimage-extract-and-run
67+
--appdir $(APPDIR)
68+
--custom-apprun AppRun
69+
--desktop-file kotasync.desktop
70+
--executable $(OUTPUT_DIR)/luajit
71+
--icon-file kotasync.png
72+
--library $(OUTPUT_DIR)/libs/libcrypto.so.[0-9][0-9]
73+
--library $(OUTPUT_DIR)/libs/liblzma.so.[0-9]
74+
--library $(OUTPUT_DIR)/libs/libssl.so.[0-9][0-9]
75+
--library $(OUTPUT_DIR)/libs/libxxhash.so.[0-9]
76+
--library $(OUTPUT_DIR)/libs/libzstd.so.[0-9]
77+
--library $(OUTPUT_DIR)/libs/libzstd.so.[0-9]
78+
--output appimage
79+
endef
80+
81+
define newline
82+
83+
84+
endef
85+
86+
linuxdeploy $(LINUXDEPLOY):
87+
$(call wget_and_validate,$(LINUXDEPLOY).part,$(LINUXDEPLOY_URL),$(LINUXDEPLOY_SHA1))
88+
ifeq (armhf,$(APPIMAGE_ARCH))
89+
# Fix header so binfmt+qemu can be used (e.g. under Docker from another architecture).
90+
test "$$(od $(LINUXDEPLOY).part --address-radix=n --skip-bytes=8 --read-bytes=3 --format=x1)" = ' 41 49 02'
91+
printf '\x0\x0\x0' | dd conv=notrunc obs=1 of=$(LINUXDEPLOY).part seek=8
92+
endif
93+
chmod +x $(LINUXDEPLOY).part
94+
mv $(LINUXDEPLOY).part $(LINUXDEPLOY)
95+
96+
appdir: $(NINJA_GOALS)
97+
rm -rf $(APPDIR) $(APPDIR).part
98+
$(foreach f,$(LUA_FILES), install -Dv $(wildcard $f $(OUTPUT_DIR)/$f) $(APPDIR).part/usr/share/lua/$(f:common/%=%)$(newline))
99+
chrpath -c -r '$$ORIGIN/../../../lib' $(APPDIR).part/usr/share/lua/*.so
100+
chrpath -c -r '$$ORIGIN/../../lib' $(APPDIR).part/usr/share/lua/*.so
101+
mv $(APPDIR).part $(APPDIR)
102+
103+
appimage: appdir $(LINUXDEPLOY)
104+
$(strip $(make_appimage))
105+
106+
PHONY = all appdir appimage clean linuxdeploy
107+
SOUND = $(LINUXDEPLOY)
108+
109+
.NOTPARALLEL:
110+
.PHONY: $(PHONY)
111+
112+
include $(KOR_BASE)/Makefile

kotasync/kotasync.desktop

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Desktop Entry]
2+
Version = 1.5
3+
Type = Application
4+
NoDisplay = true
5+
Name = kotasync
6+
Icon = kotasync
7+
Exec = kotasync
8+
Categories = Development

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+
│ new/modified files │ unmodified files │ folders │
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())

kotasync/kotasync.png

340 Bytes
Loading

thirdparty/cmake_modules/koreader_thirdparty_libs.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ else()
167167
set(LUAJIT_LIB)
168168
endif()
169169
get_target_property(LUAJIT_INC luajit::luajit INTERFACE_INCLUDE_DIRECTORIES)
170+
declare_dependency(luajit::luajit_static INCLUDES luajit-2.1 STATIC luajit-5.1 LIBRARIES dl m)
170171

171172
# luasec
172173
if(MONOLIBTIC)

0 commit comments

Comments
 (0)