diff --git a/frontend/ui/plugin/background_task_plugin.lua b/frontend/ui/plugin/background_task_plugin.lua new file mode 100644 index 000000000..e430aed13 --- /dev/null +++ b/frontend/ui/plugin/background_task_plugin.lua @@ -0,0 +1,35 @@ +--[[-- +BackgroundTaskPlugin creates a plugin with a switch to enable or disable it and executes a +background task. +See spec/unit/background_task_plugin_spec.lua for the usage. +]] + +local PluginShare = require("pluginshare") +local SwitchPlugin = require("ui/plugin/switch_plugin") + +local BackgroundTaskPlugin = SwitchPlugin:extend() + +function BackgroundTaskPlugin:_schedule(settings_id) + local enabled = function() + if not self.enabled then + return false + end + if settings_id ~= self.settings_id then + return false + end + + return true + end + + table.insert(PluginShare.backgroundJobs, { + when = self.when, + repeated = enabled, + executable = self.executable, + }) +end + +function BackgroundTaskPlugin:_start() + self:_schedule(self.settings_id) +end + +return BackgroundTaskPlugin diff --git a/frontend/ui/plugin/switch_plugin.lua b/frontend/ui/plugin/switch_plugin.lua new file mode 100644 index 000000000..5ece7aea1 --- /dev/null +++ b/frontend/ui/plugin/switch_plugin.lua @@ -0,0 +1,111 @@ +--[[-- +SwitchPlugin creates a plugin with a switch to enable or disable it. +See spec/unit/switch_plugin_spec.lua for the usage. +]] + +local ConfirmBox = require("ui/widget/confirmbox") +local DataStorage = require("datastorage") +local LuaSettings = require("luasettings") +local UIManager = require("ui/uimanager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local logger = require("logger") +local _ = require("gettext") + +local SwitchPlugin = WidgetContainer:new() + +function SwitchPlugin:extend(o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + +function SwitchPlugin:new(o) + o = self:extend(o) + assert(type(o.name) == "string", "name is required"); + o.settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/" .. o.name .. ".lua") + o.settings_id = 0 + SwitchPlugin._init(o) + return o +end + +function SwitchPlugin:_init() + if self.default_enable then + self.enabled = self.settings:nilOrTrue("enable") + else + self.enabled = not self.settings:nilOrFalse("enable") + end + self.settings_id = self.settings_id + 1 + logger.dbg("SwitchPlugin:_init() self.enabled: ", self.enabled, " with id ", self.settings_id) + if self.enabled then + self:_start() + else + self:_stop() + end +end + +function SwitchPlugin:flipSetting() + if self.default_enable then + self.settings:flipNilOrTrue("enable") + else + self.settings:flipNilOrFalse("enable") + end + self:_init() +end + +function SwitchPlugin:onFlushSettings() + self.settings:flush() +end + +--- Show a ConfirmBox to ask for enabling or disabling this plugin. +function SwitchPlugin:_showConfirmBox() + UIManager:show(ConfirmBox:new{ + text = self:_confirmMessage(), + ok_text = self.enabled and _("Disable") or _("Enable"), + ok_callback = function() + self:flipSetting() + end, + }) +end + +function SwitchPlugin:_confirmMessage() + local result = "" + if type(self.confirm_message) == "string" then + result = self.confirm_message .. "\n" + elseif type(self.confirm_message) == "function" then + result = self.confirm_message() .. "\n" + end + if self.enabled then + result = result .. _("Do you want to disable it?") + else + result = result .. _("Do you want to enable it?") + end + return result +end + +function SwitchPlugin:init() + if type(self.menu_item) == "string" and self.ui ~= nil and self.ui.menu ~= nil then + self.ui.menu:registerToMainMenu(self) + end +end + +function SwitchPlugin:addToMainMenu(menu_items) + assert(type(self.menu_item) == "string", + "addToMainMenu should not be called without menu_item.") + assert(type(self.menu_text) == "string", + "Have you forgotten to set \"menu_text\"") + menu_items[self.menu_item] = { + text = self.menu_text, + callback = function() + self:_showConfirmBox() + end, + checked_func = function() return self.enabled end, + } +end + +-- Virtual +function SwitchPlugin:_start() end +-- Virtual +function SwitchPlugin:_stop() end + +return SwitchPlugin diff --git a/plugins/autofrontlight.koplugin/main.lua b/plugins/autofrontlight.koplugin/main.lua index 96047b493..80fe8d19f 100644 --- a/plugins/autofrontlight.koplugin/main.lua +++ b/plugins/autofrontlight.koplugin/main.lua @@ -1,6 +1,6 @@ local Device = require("device") -if not Device:isKindle() or +if not Device:isSDL() or not Device:isKindle() or (Device.model ~= "KindleVoyage" and Device.model ~= "KindleOasis") then return { disabled = true, } end diff --git a/spec/unit/background_task_plugin_spec.lua b/spec/unit/background_task_plugin_spec.lua new file mode 100644 index 000000000..0e00a99b0 --- /dev/null +++ b/spec/unit/background_task_plugin_spec.lua @@ -0,0 +1,139 @@ +describe("BackgroundTaskPlugin", function() + require("commonrequire") + local BackgroundTaskPlugin = require("ui/plugin/background_task_plugin") + local MockTime = require("mock_time") + local UIManager = require("ui/uimanager") + + setup(function() + MockTime:install() + local Device = require("device") + Device.input.waitEvent = function() end + UIManager._run_forever = true + requireBackgroundRunner() + end) + + teardown(function() + MockTime:uninstall() + package.unloadAll() + stopBackgroundRunner() + end) + + local createTestPlugin = function(executable) + return BackgroundTaskPlugin:new({ + name = "test_plugin", + default_enable = true, + when = 2, + executable = executable, + }) + end + + local TestPlugin2 = BackgroundTaskPlugin:extend() + + function TestPlugin2:new(o) + o = o or {} + o.name = "test_plugin2" + o.default_enable = true + o.when = 2 + o.executed = 0 + o.executable = function() + o.executed = o.executed + 1 + end + o = BackgroundTaskPlugin.new(self, o) + return o + end + + it("should be able to create a plugin", function() + local executed = 0 + local test_plugin = createTestPlugin(function() + executed = executed + 1 + end) + MockTime:increase(2) + UIManager:handleInput() + + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(1, executed) + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(2, executed) + + test_plugin:flipSetting() + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(3, executed) -- The last job is still pending. + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(3, executed) + + test_plugin:flipSetting() + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(3, executed) -- The new job has just been inserted. + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(4, executed) + + -- Fake a settings_id increment. + test_plugin:_init() + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(5, executed) -- The job is from last settings_id. + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(5, executed) -- The new job has just been inserted. + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(6, executed) -- The job is from current settings_id. + + -- Ensure test_plugin is stopped. + test_plugin:flipSetting() + MockTime:increase(2) + UIManager:handleInput() + end) + + it("should be able to create a derived plugin", function() + local test_plugin = TestPlugin2:new() + MockTime:increase(2) + UIManager:handleInput() + + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(1, test_plugin.executed) + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(2, test_plugin.executed) + + test_plugin:flipSetting() + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(3, test_plugin.executed) -- The last job is still pending. + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(3, test_plugin.executed) + + test_plugin:flipSetting() + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(3, test_plugin.executed) -- The new job has just been inserted. + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(4, test_plugin.executed) + + -- Fake a settings_id increment. + test_plugin:_init() + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(5, test_plugin.executed) -- The job is from last settings_id. + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(5, test_plugin.executed) -- The new job has just been inserted. + MockTime:increase(2) + UIManager:handleInput() + assert.are.equal(6, test_plugin.executed) -- The job is from current settings_id. + + -- Ensure test_plugin is stopped. + test_plugin:flipSetting() + MockTime:increase(2) + UIManager:handleInput() + end) +end) diff --git a/spec/unit/switch_plugin_spec.lua b/spec/unit/switch_plugin_spec.lua new file mode 100644 index 000000000..b98681d6e --- /dev/null +++ b/spec/unit/switch_plugin_spec.lua @@ -0,0 +1,165 @@ +describe("SwitchPlugin", function() + require("commonrequire") + local SwitchPlugin = require("ui/plugin/switch_plugin") + + local createTestPlugin = function(default_enable, start, stop) + return SwitchPlugin:new({ + name = "test_plugin", + menu_item = "test_plugin_menu", + menu_text = "This is a test plugin", + confirm_message = "This is a test plugin, it's for test purpose only.", + default_enable = default_enable, + _start = function() + start() + end, + _stop = function() + stop() + end, + }) + end + + local TestPlugin2 = SwitchPlugin:extend() + + function TestPlugin2:new(o) + o = o or {} + o.name = "test_plugin2" + o.menu_item = "test_plugin2_menu" + o.menu_text = "This is a test plugin2" + o.confirm_message = "This is a test plugin2, it's for test purpose only." + o.start_called = 0 + o.stop_called = 0 + o = SwitchPlugin.new(self, o) + return o + end + + function TestPlugin2:_start() + self.start_called = self.start_called + 1 + end + + function TestPlugin2:_stop() + self.stop_called = self.stop_called + 1 + end + + it("should be able to create a enabled plugin", function() + local start_called = 0 + local stop_called = 0 + local test_plugin = createTestPlugin( + true, + function() + start_called = start_called + 1 + end, + function() + stop_called = stop_called + 1 + end) + assert.are.equal(1, start_called) + assert.are.equal(0, stop_called) + test_plugin:flipSetting() + assert.are.equal(1, start_called) + assert.are.equal(1, stop_called) + test_plugin:flipSetting() + assert.are.equal(2, start_called) + assert.are.equal(1, stop_called) + + local menu_items = {} + test_plugin:addToMainMenu(menu_items) + assert.are.equal("This is a test plugin", menu_items.test_plugin_menu.text) + end) + + it("should be able to create a disabled plugin", function() + local start_called = 0 + local stop_called = 0 + local test_plugin = createTestPlugin( + false, + function() + start_called = start_called + 1 + end, + function() + stop_called = stop_called + 1 + end) + assert.are.equal(0, start_called) + assert.are.equal(1, stop_called) + test_plugin:flipSetting() + assert.are.equal(1, start_called) + assert.are.equal(1, stop_called) + test_plugin:flipSetting() + assert.are.equal(1, start_called) + assert.are.equal(2, stop_called) + end) + + it("should be able to create a derived enabled plugin", function() + local test_plugin = TestPlugin2:new({ + default_enable = true, + }) + assert.are.equal(1, test_plugin.start_called) + assert.are.equal(0, test_plugin.stop_called) + test_plugin:flipSetting() + assert.are.equal(1, test_plugin.start_called) + assert.are.equal(1, test_plugin.stop_called) + test_plugin:flipSetting() + assert.are.equal(2, test_plugin.start_called) + assert.are.equal(1, test_plugin.stop_called) + + local menu_items = {} + test_plugin:addToMainMenu(menu_items) + assert.are.equal("This is a test plugin2", menu_items.test_plugin2_menu.text) + end) + + it("should be able to create a derived disabled plugin", function() + local test_plugin = TestPlugin2:new() + assert.are.equal(0, test_plugin.start_called) + assert.are.equal(1, test_plugin.stop_called) + test_plugin:flipSetting() + assert.are.equal(1, test_plugin.start_called) + assert.are.equal(1, test_plugin.stop_called) + test_plugin:flipSetting() + assert.are.equal(1, test_plugin.start_called) + assert.are.equal(2, test_plugin.stop_called) + end) + + it("should be able to create an invisible plugin", function() + local test_plugin = SwitchPlugin:new({ + name = "test_plugin", + ui = { + menu = { + registerToMainMenu = function() + assert.is_true(false, "This should not reach.") + end, + }, + }, + }) + test_plugin:init() + end) + + it("should show a correct message box", function() + local UIManager = require("ui/uimanager") + + local confirm_box + UIManager.show = function(self, element) + confirm_box = element + end + + local test_plugin = TestPlugin2:new() + -- The plugin is off by default, we expect an "enable" message. + test_plugin:_showConfirmBox() + assert.is_not_nil(confirm_box) + assert.are.equal( + "This is a test plugin2, it's for test purpose only.\nDo you want to enable it?", + confirm_box.text) + assert.are.equal("Enable", confirm_box.ok_text) + confirm_box.ok_callback() + confirm_box = nil + + -- The plugin is enabled by confirm_box.ok_callback(), we expect a "disable" message. + test_plugin:_showConfirmBox() + assert.is_not_nil(confirm_box) + assert.are.equal( + "This is a test plugin2, it's for test purpose only.\nDo you want to disable it?", + confirm_box.text) + assert.are.equal("Disable", confirm_box.ok_text) + confirm_box.ok_callback() + + assert.is_false(test_plugin.enabled) + + package.unload("ui/uimanager") + end) +end)