diff --git a/frontend/util.lua b/frontend/util.lua index 851ae1c89..457ee3775 100644 --- a/frontend/util.lua +++ b/frontend/util.lua @@ -35,7 +35,8 @@ Source: http ----@string str string to split ----@param pattern the pattern to split against ----@bool capture -function util.gsplit(str, pattern, capture) +----@bool capture_empty_entity +function util.gsplit(str, pattern, capture, capture_empty_entity) pattern = pattern and tostring(pattern) or '%s+' if (''):find(pattern) then error('pattern matches empty string!', 2) @@ -45,7 +46,7 @@ function util.gsplit(str, pattern, capture) repeat local first, last = str:find(pattern, index) if first and last then - if index < first then + if index < first or (index == first and capture_empty_entity) then coroutine.yield(str:sub(index, first - 1)) end if capture then @@ -372,4 +373,18 @@ function util.fixUtf8(str, replacement) return str end +--- Splits input string with the splitter into a table. This function ignores the last empty entity. +-- +--- @string str the string to be split +--- @string splitter +--- @bool capture_empty_entity +--- @treturn an array-like table +function util.splitToArray(str, splitter, capture_empty_entity) + local result = {} + for word in util.gsplit(str, splitter, false, capture_empty_entity) do + table.insert(result, word) + end + return result +end + return util diff --git a/plugins/storagestat.koplugin/main.lua b/plugins/storagestat.koplugin/main.lua deleted file mode 100644 index 8e98716d5..000000000 --- a/plugins/storagestat.koplugin/main.lua +++ /dev/null @@ -1,56 +0,0 @@ - -local Device = require("device") - -local filter - --- TODO(Hzj_jie): Find the right filter for PocketBook -if Device:isKobo() or Device:isPocketBook() then - filter = "mmcblk" -elseif Device:isKindle() then - filter = "' /mnt/us$'" -elseif Device:isSDL() then - filter = "/dev/sd" -else - return { disabled = true, } -end - -local InfoMessage = require("ui/widget/infomessage") -local UIManager = require("ui/uimanager") -local WidgetContainer = require("ui/widget/container/widgetcontainer") -local _ = require("gettext") - -local StorageStat = WidgetContainer:new{ - name = "storagestat", - menuItem = { - text = _("Storage statistics"), - callback = function() - local std_out = io.popen( - "df -h | sed -r 's/ +/ /g' | grep " .. filter .. - " | cut -d ' ' -f 2,3,4,5,6 | " .. - "awk '{print $5\": \\n Available: \" $3\"/\" $1 \"\\n Used: \" $4}'" - ) - local msg - if std_out then - msg = std_out:read("*all") - std_out:close() - end - if msg == nil or msg == "" then - msg = _("Failed to retrieve storage information.") - end - - UIManager:show(InfoMessage:new{ - text = msg, - }) - end - } -} - -function StorageStat:init() - self.ui.menu:registerToMainMenu(self) -end - -function StorageStat:addToMainMenu(menu_items) - menu_items.storage_stat = self.menuItem -end - -return StorageStat diff --git a/plugins/systemstat.koplugin/main.lua b/plugins/systemstat.koplugin/main.lua index 3548313d5..244dde826 100644 --- a/plugins/systemstat.koplugin/main.lua +++ b/plugins/systemstat.koplugin/main.lua @@ -1,6 +1,8 @@ +local Device = require("device") local KeyValuePage = require("ui/widget/keyvaluepage") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") +local util = require("util") local _ = require("gettext") local SystemStat = { @@ -13,6 +15,197 @@ local SystemStat = { discharge_count = 0, } +function SystemStat:init() + if Device:isKobo() or Device:isPocketBook() then + self.storage_filter = "mmcblk" + elseif Device:isKindle() then + self.storage_filter = "' /mnt/us$'" + elseif Device:isSDL() then + self.storage_filter = "/dev/sd" + end +end + +function SystemStat:put(p) + table.insert(self.kv_pairs, p) +end + +function SystemStat:appendCounters() + self:put({_("KOReader Started at"), os.date("%c", self.start_sec)}) + if self.suspend_sec then + self:put({_(" Last suspend time"), os.date("%c", self.suspend_sec)}) + end + if self.resume_sec then + self:put({_(" Last resume time"), os.date("%c", self.resume_sec)}) + end + self:put({_(" Up hours"), + string.format("%.2f", os.difftime(os.time(), self.start_sec) / 60 / 60)}) + self:put({_("Counters"), ""}) + self:put({_(" wake-ups"), self.wakeup_count}) + self:put({_(" sleeps"), self.sleep_count}) + self:put({_(" charge cycles"), self.charge_count}) + self:put({_(" discharge cycles"), self.discharge_count}) +end + +local function systemInfo() + local result = {} + do + local stat = io.open("/proc/stat", "r") + if stat ~= nil then + for line in util.gsplit(stat:read("*all"), "\n", false) do + local t = util.splitToArray(line, " ") + if #t >= 5 and string.lower(t[1]) == "cpu" then + local n1, n2, n3, n4 + n1 = tonumber(t[2]) + n2 = tonumber(t[3]) + n3 = tonumber(t[4]) + n4 = tonumber(t[5]) + if n1 ~= nil and n2 ~= nil and n3 ~= nil and n4 ~= nil then + result.cpu = { + user = n1, + nice = n2, + system = n3, + idle = n4, + total = n1 + n2 + n3 + n4 + } + break + end + end + end + stat:close() + end + end + + do + local meminfo = io.open("/proc/meminfo", "r") + if meminfo ~= nil then + result.memory = {} + for line in util.gsplit(meminfo:read("*all"), "\n", false) do + local t = util.splitToArray(line, " ") + if #t >= 2 then + if string.lower(t[1]) == "memtotal:" then + local n = tonumber(t[2]) + if n ~= nil then + result.memory.total = n + end + elseif string.lower(t[1]) == "memfree:" then + local n = tonumber(t[2]) + if n ~= nil then + result.memory.free = n + end + elseif string.lower(t[1]) == "memavailable:" then + local n = tonumber(t[2]) + if n ~= nil then + result.memory.available = n + end + end + end + end + meminfo:close() + end + end + return result +end + +function SystemStat:appendSystemInfo() + local stat = systemInfo() + if stat.cpu ~= nil then + self:put({_("System information"), ""}) + self:put({_(" Total ticks (million)"), + string.format("%.2f", stat.cpu.total / 1000000)}) + self:put({_(" Idle ticks (million)"), + string.format("%.2f", stat.cpu.idle / 1000000)}) + self:put({_(" Processor usage %"), + string.format("%.2f", (1 - stat.cpu.idle / stat.cpu.total) * 100)}) + end + if stat.memory ~= nil then + if stat.memory.total ~= nil then + self:put({_(" Total memory (MB)"), + string.format("%.2f", stat.memory.total / 1024)}) + end + if stat.memory.free ~= nil then + self:put({_(" Free memory (MB)"), + string.format("%.2f", stat.memory.free / 1024)}) + end + if stat.memory.available ~= nil then + self:put({_(" Available memory (MB)"), + string.format("%.2f", stat.memory.available / 1024)}) + end + end +end + +function SystemStat:appendProcessInfo() + local stat = io.open("/proc/self/stat", "r") + if stat == nil then return end + + local t = util.splitToArray(stat:read("*all"), " ") + stat:close() + + local n1, n2 + + if #t == 0 then return end + self:put({_("Process"), ""}) + + self:put({_(" ID"), t[1]}) + + if #t < 14 then return end + n1 = tonumber(t[14]) + n2 = tonumber(t[15]) + if n1 ~= nil then + if n2 ~= nil then + n1 = n1 + n2 + end + local sys_stat = systemInfo() + if sys_stat.cpu ~= nil and sys_stat.cpu.total ~= nil then + self:put({_(" Processor usage %"), + string.format("%.2f", n1 / sys_stat.cpu.total * 100)}) + else + self:put({_(" Processor usage ticks (million)"), n1 / 1000000}) + end + end + + if #t < 20 then return end + n1 = tonumber(t[20]) + if n1 ~= nil then + self:put({_(" Threads"), tostring(n1)}) + end + + if #t < 23 then return end + n1 = tonumber(t[23]) + if n1 ~= nil then + self:put({_(" Virtual memory (MB)"), string.format("%.2f", n1 / 1024 / 1024)}) + end + + if #t < 24 then return end + n1 = tonumber(t[24]) + if n1 ~= nil then + self:put({_(" RAM usage (MB)"), string.format("%.2f", n1 / 256)}) + end +end + +function SystemStat:appendStorageInfo() + if self.storage_filter == nil then return end + + local std_out = io.popen( + "df -h | sed -r 's/ +/ /g' | grep " .. self.storage_filter .. + " | sed 's/ /\\t/g' | cut -f 2,4,5,6" + ) + if not std_out then return end + + self:put({_("Storage information"), ""}) + for line in util.gsplit(std_out:read("*all"), "\n", false) do + local t = util.splitToArray(line, "\t") + if #t ~= 4 then + self:put({_(" Unexpected"), line}) + else + self:put({_(" Mount point"), t[4]}) + self:put({_(" Available"), t[2]}) + self:put({_(" Total"), t[1]}) + self:put({_(" Used percentage"), t[3]}) + end + end + std_out:close() +end + function SystemStat:onSuspend() self.suspend_sec = os.time() self.sleep_count = self.sleep_count + 1 @@ -32,28 +225,19 @@ function SystemStat:onNotCharging() end function SystemStat:showStatistics() - local kv_pairs = { - {_("KOReader Started at"), os.date("%c", self.start_sec)}, - {_("Up hours"), string.format("%.2f", os.difftime(os.time(), self.start_sec) / 60 / 60)}, - {_("Number of wake-ups"), self.wakeup_count}, - {_("Number of sleeps"), self.sleep_count}, - {_("Number of charge cycles"), self.charge_count}, - {_("Number of discharge cycles"), self.discharge_count}, - } - if self.suspend_sec then - local kv_pairs_suspend = {_("Last suspend time"), os.date("%c", self.suspend_sec)} - table.insert(kv_pairs, kv_pairs_suspend) - end - if self.resume_sec then - local kv_pairs_resume = {_("Last resume time"), os.date("%c", self.resume_sec)} - table.insert(kv_pairs, kv_pairs_resume) - end + self.kv_pairs = {} + self:appendCounters() + self:appendProcessInfo() + self:appendStorageInfo() + self:appendSystemInfo() UIManager:show(KeyValuePage:new{ title = _("System statistics"), - kv_pairs = kv_pairs, + kv_pairs = self.kv_pairs, }) end +SystemStat:init() + local SystemStatWidget = WidgetContainer:new{ name = "systemstat", } diff --git a/plugins/terminal.koplugin/main.lua b/plugins/terminal.koplugin/main.lua index a305e1d55..d5fee3980 100644 --- a/plugins/terminal.koplugin/main.lua +++ b/plugins/terminal.koplugin/main.lua @@ -13,6 +13,7 @@ local Screen = require("device").screen local Terminal = WidgetContainer:new{ name = "terminal", dump_file = util.realpath(DataStorage:getDataDir()) .. "/terminal_output.txt", + command = "", } function Terminal:init() @@ -22,6 +23,7 @@ end function Terminal:start() self.input = InputDialog:new{ title = _("Enter a command and press \"Execute\""), + input = self.command, text_height = Screen:getHeight() * 0.4, input_type = "string", buttons = {{{ @@ -43,14 +45,14 @@ function Terminal:start() end function Terminal:execute() - local command = self.input:getInputText() + self.command = self.input:getInputText() UIManager:show(InfoMessage:new{ text = _("Executing…"), timeout = 0.1, }) UIManager:forceRePaint() - local std_out = io.popen(command) - local entries = { command } + local std_out = io.popen(self.command) + local entries = { self.command } if std_out then while true do local line = std_out:read() diff --git a/spec/unit/util_spec.lua b/spec/unit/util_spec.lua index eeb9eb6d6..0184a83d5 100644 --- a/spec/unit/util_spec.lua +++ b/spec/unit/util_spec.lua @@ -38,6 +38,22 @@ describe("util module", function() assert.are_same(argv, {"./sdcv", "-nj", "words", "a lot", "more or less", "--data-dir=dict"}) end) + it("should split with splitter", function() + local words = {} + for word in util.gsplit("a-b-c-d", "-", false) do + table.insert(words, word) + end + assert.are_same(words, {"a", "b", "c", "d"}) + end) + + it("should also split with splitter", function() + local words = {} + for word in util.gsplit("a-b-c-d-", "-", false) do + table.insert(words, word) + end + assert.are_same(words, {"a", "b", "c", "d"}) + end) + it("should split line into words", function() local words = util.splitToWords("one two,three four . five") assert.are_same(words, { @@ -251,4 +267,18 @@ describe("util module", function() assert.is_equal(util.fixUtf8("glück schließen", "_"), "glück schließen") end) + it("should split input to array", function() + assert.are_same(util.splitToArray("100\tabc\t\tdef\tghi200\t", "\t", true), + {"100", "abc", "", "def", "ghi200"}) + end) + + it("should also split input to array", function() + assert.are_same(util.splitToArray("abcabcabcabca", "a", true), + {"", "bc", "bc", "bc", "bc"}) + end) + + it("should split input to array without empty entities", function() + assert.are_same(util.splitToArray("100 abc def ghi200 ", " ", false), + {"100", "abc", "def", "ghi200"}) + end) end)