diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 053dbed34..c696294fd 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -384,9 +384,11 @@ function ReaderHighlight:highlightFromHoldPos() end end +--[[ function ReaderHighlight:onHighlight() self:saveHighlight() end +]] function ReaderHighlight:getHighlightBookmarkItem() if self.hold_pos and not self.selected_text then @@ -408,6 +410,7 @@ function ReaderHighlight:getHighlightBookmarkItem() end function ReaderHighlight:saveHighlight() + self:handleEvent(Event:new("Highlight")) DEBUG("save highlight") local page = self.hold_pos.page if self.hold_pos and self.selected_text and self.selected_text.pos0 @@ -469,6 +472,7 @@ function ReaderHighlight:exportToDocument(page, item) end function ReaderHighlight:addNote() + self:handleEvent(Event:new("addNote")) DEBUG("add Note") end diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index 402fcc9b2..26f580954 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -156,6 +156,10 @@ function CreDocument:getCoverPageImage() end end +function Document:getProps() + return self._document:getDocumentProps() +end + function CreDocument:getWordFromPosition(pos) local word_box = self._document:getWordFromPosition(pos.x, pos.y) DEBUG("CreDocument: get word box", word_box) diff --git a/frontend/document/pdfdocument.lua b/frontend/document/pdfdocument.lua index bcf6f2622..d0cc3a6a0 100644 --- a/frontend/document/pdfdocument.lua +++ b/frontend/document/pdfdocument.lua @@ -8,6 +8,7 @@ local DEBUG = require("dbg") local PdfDocument = Document:new{ _document = false, + is_pdf = true, dc_null = DrawContext.new(), options = KoptOptions, koptinterface = nil, diff --git a/frontend/document/picdocument.lua b/frontend/document/picdocument.lua index a4685abba..e298e5ff0 100644 --- a/frontend/document/picdocument.lua +++ b/frontend/document/picdocument.lua @@ -4,6 +4,7 @@ local pic = nil local PicDocument = Document:new{ _document = false, + is_pic = true, dc_null = DrawContext.new() } diff --git a/frontend/ui/widget/multiinputdialog.lua b/frontend/ui/widget/multiinputdialog.lua index 96a46d9e3..c319f8a3f 100644 --- a/frontend/ui/widget/multiinputdialog.lua +++ b/frontend/ui/widget/multiinputdialog.lua @@ -37,6 +37,7 @@ function MultiInputDialog:init() input_field[k] = InputText:new{ text = field.text or "", hint = field.hint or "", + input_type = field.input_type or "string", face = self.input_face, width = self.width * 0.9, focused = k == 1 and true or false, diff --git a/plugins/statistic.koplugin/main.lua b/plugins/statistic.koplugin/main.lua new file mode 100755 index 000000000..33d62b5be --- /dev/null +++ b/plugins/statistic.koplugin/main.lua @@ -0,0 +1,423 @@ +local InputContainer = require("ui/widget/container/inputcontainer") +local MultiInputDialog = require("ui/widget/multiinputdialog") +local CenterContainer = require("ui/widget/container/centercontainer") +local UIManager = require("ui/uimanager") +local Screen = require("device").screen +local DEBUG = require("dbg") +local Menu = require("ui/widget/menu") +local Font = require("ui/font") +local _ = require("gettext") +local TimeVal = require("ui/timeval") +local dump = require("dump") +local lfs = require("libs/libkoreader-lfs") +local tableutil = require("tableutil") + +local statistic_dir = "./statistics" + +local ReaderStatistic = InputContainer:new { + last_time = nil, + page_min_read_sec = 5, + page_max_read_sec = 90, + current_period = 0, + is_enabled = nil, + data = { + title = "", + authors = "", + language = "", + series = "", + details = {}, + total_time = 0, + highlights = 0, + notes = 0, + pages = 0, + }, +} + +function ReaderStatistic:init() + if self.ui.document.is_djvu or self.ui.document.is_pdf or self.ui.document.is_pic then + return + end + + self.ui.menu:registerToMainMenu(self) + self.current_period = 0 + + local settings = G_reader_settings:readSetting("statistic") or {} + self.page_min_read_sec = tonumber(settings.min_sec) + self.page_max_read_sec = tonumber(settings.max_sec) + self.is_enabled = not (settings.is_enabled == false) + + self.last_time = TimeVal:now() + UIManager:scheduleIn(0.1, function() self:initData() end) +end + +function ReaderStatistic:initData() + --first execution + if self.is_enabled then + local book_properties = self:getBookProperties() + self.data = self:importFromFile(book_properties.title .. ".stat") + self:savePropertiesInToData(book_properties) + self.data.pages = self.view.document:getPageCount() + return + end +end + +function ReaderStatistic:addToMainMenu(tab_item_table) + table.insert(tab_item_table.plugins, { + text = _("Statistic"), + sub_item_table = { + self:getStatisticEnabledMenuTable(), + self:getStatisticSettingsMenuTable(), + self:getStatisticForCurrentBookMenuTable(), + self:getStatisticTotalStatisticMenuTable(), + } + }) +end + +function ReaderStatistic:getStatisticEnabledMenuTable() + return { + text_func = function() + return _("Enabled") + end, + checked_func = function() return self.is_enabled end, + callback = function() + -- if was enabled, have to save data to file + if self.last_time and self.is_enabled then + self:exportToFile(self:getBookProperties()) + end + + self.is_enabled = not self.is_enabled + -- if was disabled have to get data from file + if self.is_enabled then + self:initData() + end + self:onSaveSettings() + end, + } +end + +function ReaderStatistic:getStatisticSettingsMenuTable() + return { + text_func = function() + return _("Settings") + end, + checked_func = function() return false end, + callback = function() + self:updateSettings() + end, + } +end + +function ReaderStatistic:updateSettings() + self.settings_dialog = MultiInputDialog:new { + title = _("Statistic settings"), + fields = { + { + text = "", + input_type = "number", + hint = _("Min seconds, default is 5"), + }, + { + text = "", + input_type = "number", + hint = _("Max seconds, default is 90"), + }, + }, + buttons = { + { + { + text = _("Cancel"), + callback = function() + self.settings_dialog:onClose() + UIManager:close(self.settings_dialog) + end + }, + { + text = _("Update"), + callback = function() + self.settings_dialog:onClose() + UIManager:close(self.settings_dialog) + self:onSaveSettings(MultiInputDialog:getFields()) + end + }, + }, + }, + width = Screen:getWidth() * 0.95, + height = Screen:getHeight() * 0.2, + input_type = "number", + } + self.settings_dialog:onShowKeyboard() + UIManager:show(self.settings_dialog) +end + +function ReaderStatistic:getStatisticForCurrentBookMenuTable() + self.status_menu = {} + + local book_status = Menu:new { + title = _("Status"), + item_table = self:updateCurrentStat(), + is_borderless = true, + is_popout = false, + is_enable_shortcut = false, + width = Screen:getWidth(), + height = Screen:getHeight(), + cface = Font:getFace("cfont", 20), + } + + self.status_menu = CenterContainer:new { + dimen = Screen:getSize(), + book_status, + } + + book_status.close_callback = function() + UIManager:close(self.status_menu) + end + + book_status.show_parent = self.status_menu + + return { + text = "Current", + enabled_func = function() return true end, + checked_func = function() return false end, + callback = function() + book_status:swithItemTable(nil, self:updateCurrentStat()) + UIManager:show(self.status_menu) + return true + end + } +end + +function ReaderStatistic:getStatisticTotalStatisticMenuTable() + self.total_status = Menu:new { + title = _("Total"), + item_table = self:updateTotalStat(), + is_borderless = true, + is_popout = false, + is_enable_shortcut = false, + width = Screen:getWidth(), + height = Screen:getHeight(), + cface = Font:getFace("cfont", 20), + } + + self.total_menu = CenterContainer:new { + dimen = Screen:getSize(), + self.total_status, + } + + self.total_status.close_callback = function() + UIManager:close(self.total_menu) + end + + self.total_status.show_parent = self.total_menu + + return { + text = "Total", + callback = function() + self.total_status:swithItemTable(nil, self:updateTotalStat()) + UIManager:show(self.total_menu) + return true + end + } +end + +function ReaderStatistic:updateCurrentStat() + local stats = {} + local dates = {} + + for k, v in pairs(self.data.details) do + dates[os.date("%Y-%m-%d", v.time)] = "" + end + + table.insert(stats, { text = _("Current period"), mandatory = os.date("!%X", self.current_period) }) + table.insert(stats, { text = _("Total time"), mandatory = os.date("!%X", self.data.total_time) }) + table.insert(stats, { text = _("Total highlights"), mandatory = self.data.highlights }) + table.insert(stats, { text = _("Total notes"), mandatory = self.data.notes }) + table.insert(stats, { text = _("Total days"), mandatory = tableutil.tablelength(dates) }) + table.insert(stats, { text = _("Average time per page"), mandatory = os.date("!%X", self.data.total_time / tableutil.tablelength(self.data.details)) }) + table.insert(stats, { text = _("Readed pages/Total pages"), mandatory = tableutil.tablelength(self.data.details) .. "/" .. self.data.pages }) + return stats +end + +function ReaderStatistic:getDatesForBook(book) + local dates = {} + local result = {} + + for k, v in pairs(book.details) do + local date_text = os.date("%Y-%m-%d", v.time) + if not dates[date_text] then + dates[date_text] = { + date = v.time, + read = v.read, + count = 1 + } + else + dates[date_text] = { + read = dates[date_text].read + v.read, + count = dates[date_text].count + 1, + date = dates[date_text].date + } + end + end + + table.insert(result, { text = _(book.title) }) + for k, v in tableutil.spairs(dates, function(t, a, b) return t[b].date > t[a].date end) do + table.insert(result, { text = _(k), mandatory = "Pages(" .. v.count .. ") Time: " .. os.date("!%X", v.read) }) + end + + return result +end + + +function ReaderStatistic:updateTotalStat() + local total_stats = {} + local total_books_time = 0 + for curr_file in lfs.dir(statistic_dir) do + local path = statistic_dir .. "/" .. curr_file + if lfs.attributes(path, "mode") == "file" then + local book_result = self:importFromFile(curr_file) + if book_result and book_result.title ~= self.data.title then + table.insert(total_stats, { + text = _(book_result.title), + mandatory = os.date("!%X", tonumber(book_result.total_time)), + callback = function() + self.total_status:swithItemTable(nil, self:getDatesForBook(book_result)) + UIManager:show(self.total_menu) + return true + end, + }) + total_books_time = total_books_time + book_result.total_time + end + end + end + total_books_time = total_books_time + tonumber(self.data.total_time) + table.insert(total_stats, 1, { text = _("All time"), mandatory = os.date("!%X", total_books_time) }) + table.insert(total_stats, 2, { text = _("----------------------------------------------------") }) + table.insert(total_stats, 3, { + text = _(self.data.title), + mandatory = os.date("!%X", tonumber(self.data.total_time)), + callback = function() + self.total_status:swithItemTable(nil, self:getDatesForBook(self.data)) + UIManager:show(self.total_menu) + return true + end, + }) + return total_stats +end + +function ReaderStatistic:getBookProperties() + local props = self.view.document:getProps() + if props.title == "No document" or props.title == "" then --sometime crengine returns "No document" try to get one more time + props = self.view.document:getProps() + end + return props +end + +function ReaderStatistic:onPageUpdate(pageno) + if self.is_enabled then + local curr_time = TimeVal:now() + local diff_time = curr_time.sec - self.last_time.sec + + -- if last update was more then 10 minutes then current period set to 0 + if (diff_time > 600) then + self.current_period = 0 + end + + if diff_time >= self.page_min_read_sec and diff_time <= self.page_max_read_sec then + self.current_period = self.current_period + diff_time + self.data.total_time = self.data.total_time + diff_time + local timeData = { + time = curr_time.sec, + read = diff_time, + page = pageno + } + table.insert(self.data.details, timeData) + end + + self.last_time = curr_time + end +end + +function ReaderStatistic:exportToFile(book_properties) + if book_properties then + self:savePropertiesInToData(book_properties) + end + + local statistics = io.open(statistic_dir .. "/" .. self.data.title .. ".stat", "w") + if statistics then + local current_locale = os.setlocale() + os.setlocale("C") + local data = dump(self.data) + statistics:write("return ") + statistics:write(data) + statistics:write("\n") + statistics:close() + os.setlocale(current_locale) + end +end + + +function ReaderStatistic:savePropertiesInToData(item) + self.data.title = item.title + self.data.authors = item.authors + self.data.language = item.language + self.data.series = item.series +end + +function ReaderStatistic:importFromFile(item) + item = string.gsub(item, "^%s*(.-)%s*$", "%1") --trim + if lfs.attributes(statistic_dir, "mode") ~= "directory" then + lfs.mkdir("statistics") + end + local statisticFile = statistic_dir .. "/" .. item + local ok, stored = pcall(dofile, statisticFile) + if ok then + return stored + else + DEBUG(stored) + end +end + +function ReaderStatistic:onCloseDocument() + if self.last_time and self.is_enabled then + self:exportToFile() + end +end + +function ReaderStatistic:onHighlight() + self.data.highlights = self.data.highlights + 1 +end + +function ReaderStatistic:onAddNote() + self.data.notes = self.data.notes + 1 +end + +-- in case when screensaver starts +function ReaderStatistic:onFlushSettings() + self:onSaveSettings() + self:exportToFile() + self.current_period = 0 + return true +end + +-- screensaver off +function ReaderStatistic:onResume() + self.current_period = 0 + return true +end + +function ReaderStatistic:onSaveSettings(fields) + if fields then + self.page_min_read_sec = tonumber(fields[1]) + self.page_max_read_sec = tonumber(fields[2]) + end + + local settings = { + min_sec = self.page_min_read_sec, + max_sec = self.page_max_read_sec, + is_enabled = self.is_enabled, + } + G_reader_settings:saveSetting("statistic", settings) +end + + +return ReaderStatistic + diff --git a/plugins/statistic.koplugin/tableutil.lua b/plugins/statistic.koplugin/tableutil.lua new file mode 100644 index 000000000..012cf81b0 --- /dev/null +++ b/plugins/statistic.koplugin/tableutil.lua @@ -0,0 +1,34 @@ +local tableutil = {} + + +--http://stackoverflow.com/questions/15706270/sort-a-table-in-lua +function tableutil.spairs(t, order) + -- collect the keys + local keys = {} + for k in pairs(t) do keys[#keys + 1] = k end + + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort(keys, function(a, b) return order(t, a, b) end) + else + table.sort(keys) + end + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end + end +end + +function tableutil.tablelength(T) + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count +end + +return tableutil