2
0
mirror of https://github.com/koreader/koreader synced 2024-11-18 03:25:46 +00:00
koreader/plugins/batterystat.koplugin/main.lua
NiLuJe fadee1f5dc
Clarify our OOP semantics across the codebase (#9586)
Basically:

* Use `extend` for class definitions
* Use `new` for object instantiations

That includes some minor code cleanups along the way:

* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
  * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 02:14:48 +02:00

321 lines
9.2 KiB
Lua

local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local Dispatcher = require("dispatcher")
local KeyValuePage = require("ui/widget/keyvaluepage")
local LuaSettings = require("luasettings")
local PowerD = require("device"):getPowerDevice()
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local dbg = require("dbg")
local time = require("ui/time")
local util = require("util")
local _ = require("gettext")
local State = {}
function State:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
if o.percentage == nil or o.timestamp == nil then
o.percentage = PowerD:getCapacityHW()
o.timestamp = time.boottime_or_realtime_coarse()
end
return o
end
function State:toString()
return string.format("{%d @ %s}", self.percentage, os.date("%c", time.to_s(self.timestamp)))
end
local Usage = {}
local INDENTATION = " " -- Three spaces.
function Usage:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
if o.percentage == nil or o.time == nil then
o.percentage = 0
o.time = 0
end
return o
end
function Usage:append(state)
local curr = State:new()
self.percentage = self.percentage + math.abs(state.percentage - curr.percentage)
self.time = self.time + curr.timestamp - state.timestamp
end
function Usage:percentageRate()
if self.time == 0 then
return 0
else
return self.percentage / time.to_s(self.time)
end
end
function Usage:percentageRatePerHour()
return self:percentageRate() * 3600
end
function Usage:remainingTime()
if self:percentageRate() == 0 then return "N/A" end
local curr = State:new()
return curr.percentage / self:percentageRate()
end
function Usage:chargingTime()
if self:percentageRate() == 0 then return "N/A" end
local curr = State:new()
return math.abs(curr.percentage - 100) / self:percentageRate()
end
local function shorten(number)
if number == "N/A" then return _("N/A") end
return string.format("%.2f%%", number)
end
local function duration(number)
local duration_fmt = G_reader_settings:readSetting("duration_format", "classic")
return type(number) ~= "number" and number or
util.secondsToClockDuration(duration_fmt, number, true, true, true)
end
function Usage:dump(kv_pairs, id)
local name = id or _("Consumed:")
table.insert(kv_pairs, {INDENTATION .. _("Total time:"), duration(time.to_s(self.time)) })
table.insert(kv_pairs, {INDENTATION .. name, shorten(self.percentage), "%"})
table.insert(kv_pairs, {INDENTATION .. _("Change per hour:"), shorten(self:percentageRatePerHour())})
end
function Usage:dumpRemaining(kv_pairs)
table.insert(kv_pairs, {INDENTATION .. _("Estimated remaining time:"), duration(self:remainingTime())})
end
function Usage:dumpCharging(kv_pairs)
table.insert(kv_pairs, {INDENTATION .. _("Estimated time for charging:"), duration(self:chargingTime())})
end
local BatteryStat = {
settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/battery_stats.lua"),
kv_page = nil,
}
function BatteryStat:init()
self.awake = Usage:new(self.settings:readSetting("awake"))
self.sleeping = Usage:new(self.settings:readSetting("sleeping"))
self.charging = Usage:new(self.settings:readSetting("charging"))
self.discharging = Usage:new(self.settings:readSetting("discharging"))
-- Note: these fields are not the "real" timestamp and battery usage, but
-- the unaccumulated values.
self.awake_state = State:new()
self.charging_state = State:new()
-- Whether the device was suspending before current timestamp.
self.was_suspending = false
-- Whether the device was charging before current timestamp.
self.was_charging = PowerD:isCharging()
if self.was_charging then
self:reset(true, false)
end
-- Check if the battery was charging when KO was turned off.
local battery_before_off = self.settings:readSetting("awake_state")
if battery_before_off and battery_before_off.percentage
and self.awake_state.percentage > battery_before_off.percentage then
self:reset(false, true)
end
end
function BatteryStat:onFlushSettings()
self.settings:reset({
charging = self.charging,
discharging = self.discharging,
awake = self.awake,
sleeping = self.sleeping,
charging_state = self.charging_state,
awake_state = self.awake_state,
})
self.settings:flush()
end
function BatteryStat:accumulate()
if self.was_suspending and not self.was_charging then
-- Suspending to awake.
self.sleeping:append(self.awake_state)
elseif not self.was_suspending and not self.was_charging then
-- Awake to suspending, time between self.awake_state and now should belong to awake.
self.awake:append(self.awake_state)
end
if self.was_charging then
-- Decharging to charging.
self.charging:append(self.charging_state)
else
self.discharging:append(self.charging_state)
end
self.awake_state = State:new()
self.charging_state = State:new()
end
function BatteryStat:onSuspend()
if not self.was_suspending then
self:accumulate()
end
self.was_suspending = true
end
function BatteryStat:onResume()
if self.was_suspending then
self:accumulate()
end
self.was_suspending = false
end
function BatteryStat:onCharging()
if not self.was_charging then
self:reset(true, true)
self:accumulate()
end
self.was_charging = true
end
function BatteryStat:onNotCharging()
if self.was_charging then
self:reset(false, true)
self:accumulate()
end
self.was_charging = false
end
function BatteryStat:showStatistics()
local function askResetData()
UIManager:show(ConfirmBox:new{
text = _("Are you sure that you want to clear battery statistics?"),
ok_text = _("Clear"),
ok_callback = function()
self:resetAll()
self:restart()
end,
})
end
self:accumulate()
local kv_pairs = self:dump()
kv_pairs[#kv_pairs].separator = true
table.insert(kv_pairs, {_("Tap to reset the data."), "",
callback = function()
UIManager:setDirty(self.kv_page, "fast")
UIManager:scheduleIn(0.1, function()
askResetData()
end)
end})
self.kv_page = KeyValuePage:new{
title = _("Battery statistics"),
kv_pairs = kv_pairs,
single_page = true,
}
UIManager:show(self.kv_page)
end
function BatteryStat:reset(withCharging, withDischarging)
self.awake = Usage:new()
self.sleeping = Usage:new()
if withCharging then
self.charging = Usage:new()
end
if withDischarging then
self.discharging = Usage:new()
end
self.awake_state = State:new()
end
function BatteryStat:resetAll()
self:reset(true, true)
self.charging_state = State:new()
self.awake_state = State:new()
end
function BatteryStat:restart()
dbg.dassert(self.kv_page ~= nil)
UIManager:close(self.kv_page)
self:showStatistics()
end
function BatteryStat:dump()
local kv_pairs = {}
table.insert(kv_pairs, {_("Awake since last charge"), ""})
self.awake:dump(kv_pairs)
self.awake:dumpRemaining(kv_pairs)
table.insert(kv_pairs, {_("Sleeping since last charge"), ""})
self.sleeping:dump(kv_pairs)
self.sleeping:dumpRemaining(kv_pairs)
table.insert(kv_pairs, {_("During last charge"), ""})
self.charging:dump(kv_pairs, _("Charged:"))
self.charging:dumpCharging(kv_pairs)
table.insert(kv_pairs, {_("Since last charge"), ""})
self.discharging:dump(kv_pairs)
self.discharging:dumpRemaining(kv_pairs)
return kv_pairs
end
BatteryStat:init()
local BatteryStatWidget = WidgetContainer:extend{
name = "batterystat",
}
function BatteryStatWidget:onDispatcherRegisterActions()
Dispatcher:registerAction("battery_statistics", {category="none", event="ShowBatteryStatistics", title=_("Battery statistics"), device=true, separator=true})
end
function BatteryStatWidget:init()
-- self.ui is nil in test cases.
if not self.ui or not self.ui.menu then return end
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
end
function BatteryStatWidget:addToMainMenu(menu_items)
menu_items.battery_statistics = {
text = _("Battery statistics"),
keep_menu_open = true,
callback = function()
BatteryStat:showStatistics()
end,
}
end
function BatteryStatWidget:onShowBatteryStatistics()
BatteryStat:showStatistics()
end
function BatteryStatWidget:onFlushSettings()
BatteryStat:onFlushSettings()
end
function BatteryStatWidget:onSuspend()
BatteryStat:onSuspend()
end
function BatteryStatWidget:onResume()
BatteryStat:onResume()
end
function BatteryStatWidget:onCharging()
BatteryStat:onCharging()
end
function BatteryStatWidget:onNotCharging()
BatteryStat:onNotCharging()
end
-- Test only
function BatteryStatWidget:stat()
return BatteryStat
end
return BatteryStatWidget