Files

179 lines
5.6 KiB
Lua

local fs = require "nixio.fs"
local nixio = require "nixio"
local OPKG_STATUS_FILE = "/usr/lib/opkg/status"
local APK_STATUS_FILE = "/lib/apk/db/installed"
local DEFAULT_VERSION = "v0.0.0"
local M = {}
M.POINTER_KEYS = {
"active_campus_id", "default_campus_id",
"active_hotspot_id", "default_hotspot_id",
}
M.LIST_KEYS = { "campus_accounts", "hotspot_profiles" }
M.SCHOOL_EXTRA_KEY = "school_extra"
function M.normalize_ap_selection(value, bssid)
local policy = tostring(value or ""):match("^%s*(.-)%s*$"):lower()
if policy == "auto" or policy == "strongest" or policy == "fixed" then
return policy
end
return tostring(bssid or ""):match("^%s*(.-)%s*$") ~= "" and "fixed" or "auto"
end
function M.is_valid_bssid(value)
local address = tostring(value or ""):match("^%s*(.-)%s*$"):lower()
if not address:match("^%x%x:%x%x:%x%x:%x%x:%x%x:%x%x$") then return false end
return address ~= "00:00:00:00:00:00" and tonumber(address:sub(1, 2), 16) % 2 == 0
end
function M.ap_selection_label(policy)
local labels = { auto = "系统自动", strongest = "连接时信号优先", fixed = "固定 BSSID" }
return labels[tostring(policy or "")] or "未知"
end
local POINTER_KEY_SET = {}
for _, key in ipairs(M.POINTER_KEYS) do
POINTER_KEY_SET[key] = true
end
local LIST_KEY_SET = {}
for _, key in ipairs(M.LIST_KEYS) do
LIST_KEY_SET[key] = true
end
-- load_defaults prefers the daemon's own schema.
--
-- Spec 02 gives Go the types, defaults, bounds and choices and leaves the page
-- its labels, so that the two cannot disagree about what a valid value is. The
-- page reports a stopped/unavailable service when schema cannot be read; it
-- never falls back to an obsolete 1.x configuration file.
local function load_defaults()
-- One pcall around both the load and the call: on a host without nixio, or
-- with the service stopped, this must fall back rather than take the page
-- down with it.
local ok, parsed = pcall(function()
return require("luci.smart_srun.bridge").defaults()
end)
if not ok or type(parsed) ~= "table" then
parsed = {}
end
if parsed.school == nil then
parsed.school = "default"
end
local defaults = {}
for key, value in pairs(parsed) do
if not POINTER_KEY_SET[key] and not LIST_KEY_SET[key] then
defaults[key] = tostring(value or "")
end
end
return defaults
end
M.SCALAR_DEFAULTS = load_defaults()
M.GLOBAL_SCALAR_KEYS = {}
for key, _ in pairs(M.SCALAR_DEFAULTS) do
M.GLOBAL_SCALAR_KEYS[#M.GLOBAL_SCALAR_KEYS + 1] = key
end
table.sort(M.GLOBAL_SCALAR_KEYS)
function M.global_scalar_key_set()
local key_set = {}
for _, key in ipairs(M.GLOBAL_SCALAR_KEYS) do
key_set[key] = true
end
return key_set
end
function M.with_file_lock(path, callback)
local oflags = nixio.open_flags("wronly", "creat")
local lock, _, msg = nixio.open(tostring(path) .. ".lock", oflags)
if not lock then
error("Open lock failed: " .. tostring(msg or "unknown"))
end
local ok, _, lock_msg = lock:lock("lock")
if not ok then
lock:close()
error("Lock failed: " .. tostring(lock_msg or "unknown"))
end
local call_ok, a, b, c = pcall(callback)
lock:lock("ulock")
lock:close()
if not call_ok then
error(a)
end
return a, b, c
end
local function normalize_version_string(raw)
local value = tostring(raw or "")
-- opkg and APK use different native RC separators; the UI uses the same
-- display version as the Go binary and the release tag.
value = value:gsub("~rc", "rc"):gsub("_rc", "rc")
local version = value:match("^v?([0-9][%w%._%-]*)%-r?%d+$") or value:match("^v?([0-9][%w%._%-]*)$")
if version and version ~= "" then
version = version:gsub("_", "-")
local base, beta = version:match("^(.+)%-beta%.?(%d+)$")
if base and beta then
version = base .. "-b" .. beta
end
return string.format("v%s", version)
end
return DEFAULT_VERSION
end
local function read_package_status()
local text = fs.readfile(OPKG_STATUS_FILE)
if text and text ~= "" then
return text
end
return fs.readfile(APK_STATUS_FILE) or ""
end
local function package_versions()
local versions = {}
local text = read_package_status() .. "\n\n"
for block in text:gmatch("(.-)\n\n") do
local package_name = block:match("\n?Package:%s*([^\r\n]+)")
or block:match("\n?P:%s*([^\r\n]+)")
or block:match("^Package:%s*([^\r\n]+)")
or block:match("^P:%s*([^\r\n]+)")
if package_name then
versions[package_name] =
block:match("\n?Version:%s*([^\r\n]+)")
or block:match("\n?V:%s*([^\r\n]+)")
or block:match("^Version:%s*([^\r\n]+)")
or block:match("^V:%s*([^\r\n]+)")
or ""
end
end
return versions
end
function M.installed_package_name()
local versions = package_versions()
if versions["luci-app-smart-srun-bundle"] then
return "luci-app-smart-srun-bundle"
end
if versions["luci-app-smart-srun"] then
return "luci-app-smart-srun"
end
return "smart-srun"
end
function M.installed_package_display_text()
local versions = package_versions()
local package_name = M.installed_package_name()
local label = package_name == "luci-app-smart-srun-bundle" and "Bundle 版"
or package_name == "luci-app-smart-srun" and "标准版"
or "CLI 版"
local version = normalize_version_string(versions[package_name] or "")
return string.format("%s %s", label, version)
end
return M