From 0e2d20353a225930ebed2048aac8d38f3ba92c0c Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 29 Aug 2017 17:34:49 +0200 Subject: [PATCH] Statistics plugin v2 (SQLite) (#2893) --- frontend/apps/reader/modules/readerfooter.lua | 13 +- plugins/statistics.koplugin/main.lua | 1253 ++++++++++++----- .../statistics.koplugin/readerprogress.lua | 60 +- 3 files changed, 937 insertions(+), 389 deletions(-) diff --git a/frontend/apps/reader/modules/readerfooter.lua b/frontend/apps/reader/modules/readerfooter.lua index b11b9c05f..05a8788d3 100644 --- a/frontend/apps/reader/modules/readerfooter.lua +++ b/frontend/apps/reader/modules/readerfooter.lua @@ -208,6 +208,7 @@ function ReaderFooter:init() if self.settings.auto_refresh_time then self:setupAutoRefreshTime() end + self.average_time_per_page = nil end function ReaderFooter:setupAutoRefreshTime() @@ -496,12 +497,9 @@ function ReaderFooter:setTocMarkers() end function ReaderFooter:getDataFromStatistics(title, pages) - local statistics_data = self.ui.doc_settings:readSetting("stats") local sec = 'na' - if statistics_data and statistics_data.performance_in_pages then - local read_pages = util.tableSize(statistics_data.performance_in_pages) - local average_time_per_page = statistics_data.total_time_in_sec / read_pages - sec = util.secondsToClock(pages * average_time_per_page, true) + if self.average_time_per_page then + sec = util.secondsToClock(pages * self.average_time_per_page, true) end return title .. sec end @@ -684,6 +682,11 @@ function ReaderFooter:onSuspend() end end +function ReaderFooter:onUpdateStats(avg_time_page) + self.average_time_per_page = avg_time_page + return true +end + function ReaderFooter:onFrontlightStateChanged() if self.settings.frontlight then self:updateFooter() diff --git a/plugins/statistics.koplugin/main.lua b/plugins/statistics.koplugin/main.lua index 5ba0aa0df..4418a60f4 100755 --- a/plugins/statistics.koplugin/main.lua +++ b/plugins/statistics.koplugin/main.lua @@ -1,32 +1,44 @@ -local Widget = require("ui/widget/widget") -local MultiInputDialog = require("ui/widget/multiinputdialog") +local ConfirmBox = require("ui/widget/confirmbox") +local DataStorage = require("datastorage") +local DocSettings = require("docsettings") +local Event = require("ui/event") +local InfoMessage = require("ui/widget/infomessage") local KeyValuePage = require("ui/widget/keyvaluepage") -local UIManager = require("ui/uimanager") -local Screen = require("device").screen +local MultiInputDialog = require("ui/widget/multiinputdialog") +local ReaderProgress = require("readerprogress") +local ReadHistory = require("readhistory") +local SQ3 = require("lua-ljsqlite3/init") local TimeVal = require("ui/timeval") -local DataStorage = require("datastorage") +local UIManager = require("ui/uimanager") +local Widget = require("ui/widget/widget") local lfs = require("libs/libkoreader-lfs") -local DEBUG = require("dbg") -local T = require("ffi/util").template -local joinPath = require("ffi/util").joinPath -local _ = require("gettext") +local logger = require("logger") local util = require("util") -local tableutil = require("tableutil") -local ReadHistory = require("readhistory") -local DocSettings = require("docsettings") -local ReaderProgress = require("readerprogress") -local statistics_dir = DataStorage:getDataDir() .. "/statistics/" +local _ = require("gettext") +local joinPath = require("ffi/util").joinPath +local Screen = require("device").screen +local T = require("ffi/util").template --- a copy of page_max_read_sec -local page_max_time +local statistics_dir = DataStorage:getDataDir() .. "/statistics/" +local db_location = DataStorage:getSettingsDir() .. "/book.sqlite3" +local PAGE_INSERT = 50 +local DEFAULT_MIN_READ_SEC = 5 +local DEFAULT_MAX_READ_SEC = 120 local ReaderStatistics = Widget:extend{ - last_time = nil, - page_min_read_sec = 5, - page_max_read_sec = 90, - current_period = 0, - pages_current_period = 0, + page_min_read_sec = DEFAULT_MIN_READ_SEC, + page_max_read_sec = DEFAULT_MAX_READ_SEC, + start_current_period = 0, + curr_page = 0, + id_curr_book = nil, + curr_total_time = 0, + curr_total_pages = 0, is_enabled = nil, + convert_to_db = nil, + total_read_pages = 0, + total_read_time = 0, + avg_time = nil, + pages_stats = {}, data = { title = "", authors = "", @@ -37,6 +49,7 @@ local ReaderStatistics = Widget:extend{ highlights = 0, notes = 0, pages = 0, + md5 = nil, }, } @@ -73,58 +86,433 @@ function ReaderStatistics:init() if not self:isDocless() and self.ui.document.is_pic then return end - - self.ui.menu:registerToMainMenu(self) - self.current_period = 0 - self.pages_current_period = 0 - + self.start_current_period = TimeVal:now().sec + self.pages_stats = {} local settings = G_reader_settings:readSetting("statistics") or {} self.page_min_read_sec = tonumber(settings.min_sec) self.page_max_read_sec = tonumber(settings.max_sec) - -- use later in getDatesFromBook - page_max_time = self.page_max_read_sec self.is_enabled = not (settings.is_enabled == false) - self.last_time = TimeVal:now() -end - -function ReaderStatistics:getBookProperties() - local props = self.view.document:getProps() - if props.title == "No document" or props.title == "" then - -- FIXME: sometimes crengine returns "No document", try one more time - props = self.view.document:getProps() - end - return props + self.convert_to_db = settings.convert_to_db + self.ui.menu:registerToMainMenu(self) + self:checkInitDatabase() end -function ReaderStatistics:initData(config) +function ReaderStatistics:initData() if self:isDocless() or not self.is_enabled then return end -- first execution if not self.data then self.data = { performance_in_pages= {} } - self:inplaceMigration(); -- first time merge data end - local book_properties = self:getBookProperties() self.data.title = book_properties.title + if self.data.title == nil or self.data.title == "" then + self.data.title = self.document.file:match("^.+/(.+)$") + end self.data.authors = book_properties.authors self.data.language = book_properties.language self.data.series = book_properties.series self.data.pages = self.view.document:getPageCount() - return + self.data.md5 = self:partialMd5(self.document.file) + self.curr_total_time = 0 + self.curr_total_pages = 0 + self.id_curr_book = self:getIdBookDB() + self.total_read_pages, self.total_read_time = self:getPageTimeTotalStats(self.id_curr_book) + if self.total_read_pages > 0 then + local avg_time = math.ceil(self.total_read_time / self.total_read_pages) + self.avg_time = avg_time + self.ui:handleEvent(Event:new("UpdateStats", avg_time)) + self.view.footer:updateFooter() + end end -local function generateReadBooksTable(title, dates) - local result = {} - for k, v in tableutil.spairs(dates, function(t, a, b) return t[b].date < t[a].date end) do - table.insert(result, { - k, - T(_("Pages (%1) Time: %2"), v.count, util.secondsToClock(v.read, false)) - }) +function ReaderStatistics:checkInitDatabase() + local conn = SQ3.open(db_location) + if self.convert_to_db then -- if conversion to sqlite was doing earlier + if not conn:exec("pragma table_info('book');") then + UIManager:show(ConfirmBox:new{ + text = T(_("Cannot open database in %1.\nThe database may have been moved or deleted.\n " .. + "Do you want to create an empty database?"), db_location), + cancel_text = _("Close"), + cancel_callback = function() + return + end, + ok_text = _("Create"), + ok_callback = function() + local conn_new = SQ3.open(db_location) + self:createDB(conn_new) + conn_new:close() + UIManager:show(InfoMessage:new{text =_("A new empty database has been created."), timeout = 3 }) + self:initData() + end, + }) + end + else -- first time convertion to sqlite database + self.convert_to_db = true + if not conn:exec("pragma table_info('book');") then + if #ReadHistory.hist > 0 then + local info = InfoMessage:new{ + text =_("New version of statistics plugin is detected.\n" .. + "Statistics data needs to be converted into the new database format.\n".. + "It make take a few minutes.\nPlease wait…")} + UIManager:show(info) + UIManager:forceRePaint() + local nr_book = self:migrateToDB(conn) + UIManager:close(info) + UIManager:forceRePaint() + UIManager:show(InfoMessage:new{ + text =T(_("Convertion completed.\nImported %1 books to database."),nr_book), timeout = 4 }) + else + self:createDB(conn) + end + end + self:saveSettings() + end + conn:close() +end + +function ReaderStatistics:partialMd5(file) + if file == nil then + return nil + end + local md5 = require("ffi/MD5") + local lshift = bit.lshift + local step, size = 1024, 1024 + local m = md5.new() + local file_handle = io.open(file, 'rb') + for i = -1, 10 do + file_handle:seek("set", lshift(step, 2*i)) + local sample = file_handle:read(size) + if sample then + m:update(sample) + else + break + end end - return result + return m:sum() +end + +function ReaderStatistics:createDB(conn) + local sql_stmt = [[ + CREATE TABLE IF NOT EXISTS book + ( + id integer PRIMARY KEY autoincrement, + title text, + authors text, + notes integer, + last_open integer, + highlights integer, + pages integer, + series text, + language text, + md5 text, + total_read_time integer, + total_read_pages integer + ); + CREATE TABLE IF NOT EXISTS page_stat + ( + id_book integer PRIMARY KEY, + page integer NOT NULL, + start_time integer NOT NULL, + period integer NOT NULL, + UNIQUE (page, start_time), + FOREIGN KEY(id_book) REFERENCES book(id) + ); + CREATE TABLE IF NOT EXISTS info + ( + version integer + ); + ]] + conn:exec(sql_stmt) + --DB structure version - now is version 1 + local stmt = conn:prepare("INSERT INTO info values (?)") + stmt:reset():bind("1"):step() + stmt:close() +end + +function ReaderStatistics:addBookStatToDB(book_stats, conn) + local id_book + local last_open_book = 0 + local start_open_page + local diff_time + local total_read_pages = 0 + local total_read_time = 0 + local sql_stmt + if book_stats.total_time_in_sec and book_stats.total_time_in_sec > 0 + and util.tableSize(book_stats.performance_in_pages) > 0 then + local read_pages = util.tableSize(book_stats.performance_in_pages) + logger.dbg("Insert to database: " .. book_stats.title) + sql_stmt = [[ + SELECT count(id) + FROM book + WHERE title = ? + AND authors = ? + AND md5 = ? + ]] + local stmt = conn:prepare(sql_stmt) + local result = stmt:reset():bind(self.data.title, self.data.authors, self.data.md5):step() + local nr_id = tonumber(result[1]) + if nr_id == 0 then + stmt = conn:prepare("INSERT INTO book VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + stmt:reset():bind(book_stats.title, book_stats.authors, book_stats.notes, + last_open_book, book_stats.highlights, book_stats.pages, + book_stats.series, book_stats.language, self:partialMd5(book_stats.file), total_read_time, total_read_pages) :step() + sql_stmt = [[ + SELECT last_insert_rowid() AS num; + ]] + id_book = conn:rowexec(sql_stmt) + else + sql_stmt = [[ + SELECT id + FROM book + WHERE title = ? + AND authors = ? + AND md5 = ? + ]] + stmt = conn:prepare(sql_stmt) + result = stmt:reset():bind(self.data.title, self.data.authors, self.data.md5):step() + id_book = result[1] + + end + local sorted_performance = {} + for k, _ in pairs(book_stats.performance_in_pages) do + table.insert(sorted_performance, k) + end + table.sort(sorted_performance) + + conn:exec('BEGIN') + stmt = conn:prepare("INSERT OR IGNORE INTO page_stat VALUES(?, ?, ?, ?)") + local avg_time = math.ceil(book_stats.total_time_in_sec / read_pages) + local first_read_page = book_stats.performance_in_pages[sorted_performance[1]] + if first_read_page > 1 then + first_read_page = first_read_page - 1 + end + start_open_page = sorted_performance[1] + --first page + stmt:reset():bind(id_book, first_read_page, start_open_page - avg_time, avg_time):step() + for i=2, #sorted_performance do + start_open_page = sorted_performance[i-1] + diff_time = sorted_performance[i] - sorted_performance[i-1] + if diff_time <= self.page_max_read_sec then + stmt:reset():bind(id_book, book_stats.performance_in_pages[sorted_performance[i-1]], + start_open_page, diff_time):step() + elseif diff_time > self.page_max_read_sec then --and diff_time <= 2 * avg_time then + stmt:reset():bind(id_book, book_stats.performance_in_pages[sorted_performance[i-1]], + start_open_page, avg_time):step() + end + end + --last page + stmt:reset():bind(id_book, book_stats.performance_in_pages[sorted_performance[#sorted_performance]], + sorted_performance[#sorted_performance], avg_time):step() + --last open book + last_open_book = sorted_performance[#sorted_performance] + avg_time + conn:exec('COMMIT') + sql_stmt = [[ + SELECT count(DISTINCT page), + sum(period) + FROM page_stat + WHERE id_book = %s; + ]] + total_read_pages, total_read_time = conn:rowexec(string.format(sql_stmt, tonumber(id_book))) + sql_stmt = [[ + UPDATE book + SET last_open = ?, + total_read_time = ?, + total_read_pages = ? + WHERE id = ? + ]] + stmt = conn:prepare(sql_stmt) + stmt:reset():bind(last_open_book, total_read_time, total_read_pages, id_book):step() + stmt:close() + return true + end +end + +function ReaderStatistics:migrateToDB(conn) + self:createDB(conn) + local nr_of_conv_books = 0 + local exclude_titles = {} + for _, v in pairs(ReadHistory.hist) do + local book_stats = DocSettings:open(v.file):readSetting('stats') + if book_stats and book_stats.title == "" then + book_stats.title = v.file:match("^.+/(.+)$") + end + if book_stats then + book_stats.file = v.file + if self:addBookStatToDB(book_stats, conn) then + nr_of_conv_books = nr_of_conv_books + 1 + exclude_titles[book_stats.title] = true + else + logger.dbg("Book not converted: " .. book_stats.title) + end + else + logger.dbg("Empty stats for file: ", v.file) + end + end + -- import from stats files (for backward compatibility) + if lfs.attributes(statistics_dir, "mode") == "directory" then + for curr_file in lfs.dir(statistics_dir) do + local path = statistics_dir .. curr_file + if lfs.attributes(path, "mode") == "file" then + local old_data = self:importFromFile(statistics_dir, curr_file) + if old_data and old_data.total_time > 0 and not exclude_titles[old_data.title] then + local book_stats = {} + for _, v in pairs(old_data.details) do + book_stats.performance_in_pages[v.time] = v.page + end + book_stats.title = old_data.title + book_stats.authors = old_data.authors + book_stats.notes = old_data.notes + book_stats.highlights = old_data.highlights + book_stats.pages = old_data.pages + book_stats.series = old_data.series + book_stats.language = old_data.language + book_stats.file = nil + if self:addBookStatToDB(book_stats, conn) then + nr_of_conv_books = nr_of_conv_books + 1 + else + logger.dbg("Book not converted (old stats): " .. book_stats.title) + end + end + end + end + end + return nr_of_conv_books +end + +function ReaderStatistics:getIdBookDB() + local conn = SQ3.open(db_location) + local id_book + local sql_stmt = [[ + SELECT count(id) + FROM book + WHERE title = ? + AND authors = ? + AND md5 = ? + ]] + local stmt = conn:prepare(sql_stmt) + local result = stmt:reset():bind(self.data.title, self.data.authors, self.data.md5):step() + local nr_id = tonumber(result[1]) + if nr_id == 0 then + stmt = conn:prepare("INSERT INTO book VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + stmt:reset():bind(self.data.title, self.data.authors, self.data.notes, + TimeVal:now().sec, self.data.highlights, self.data.pages, + self.data.series, self.data.language, self.data.md5, self.curr_total_time, self.curr_total_pages):step() + sql_stmt = [[ + SELECT last_insert_rowid() AS num; + ]] + id_book = conn:rowexec(sql_stmt) + else + sql_stmt = [[ + SELECT id + FROM book + WHERE title = ? + AND authors = ? + AND md5 = ? + ]] + stmt = conn:prepare(sql_stmt) + result = stmt:reset():bind(self.data.title, self.data.authors, self.data.md5):step() + id_book = result[1] + end + conn:close() + return tonumber(id_book) +end + +function ReaderStatistics:insertDB(id_book) + self.pages_stats[TimeVal:now().sec] = self.curr_page + if id_book == nil or util.tableSize(self.pages_stats) < 2 then + return + end + local diff_time + local conn = SQ3.open(db_location) + local sorted_performance = {} + for time, pages in pairs(self.pages_stats) do + table.insert(sorted_performance, time) + end + table.sort(sorted_performance) + conn:exec('BEGIN') + local stmt = conn:prepare("INSERT OR IGNORE INTO page_stat VALUES(?, ?, ?, ?)") + for i=1, #sorted_performance - 1 do + diff_time = sorted_performance[i+1] - sorted_performance[i] + if diff_time >= self.page_min_read_sec then + stmt:reset():bind(id_book, + self.pages_stats[sorted_performance[i]], + sorted_performance[i], + math.min(diff_time, self.page_max_read_sec)):step() + end + end + conn:exec('COMMIT') + local sql_stmt = [[ + SELECT count(DISTINCT page), + sum(period) + FROM page_stat + WHERE id_book = '%s' + ]] + local total_read_pages, total_read_time = conn:rowexec(string.format(sql_stmt, id_book)) + sql_stmt = [[ + UPDATE book + SET last_open = ?, + notes = ?, + highlights = ?, + total_read_time = ?, + total_read_pages = ?, + pages = ? + WHERE id = ? + ]] + stmt = conn:prepare(sql_stmt) + stmt:reset():bind(TimeVal:now().sec, self.data.notes, self.data.highlights, total_read_time, total_read_pages, + self.data.pages, id_book):step() + if total_read_pages then + self.total_read_pages = tonumber(total_read_pages) + else + self.total_read_pages = 0 + end + if total_read_time then + self.total_read_time = tonumber(total_read_time) + else + self.total_read_time = 0 + end + self.pages_stats = {} + -- last page must be added once more + self.pages_stats[TimeVal:now().sec] = self.curr_page + conn:close() +end + +function ReaderStatistics:getPageTimeTotalStats(id_book) + if id_book == nil then + return + end + local conn = SQ3.open(db_location) + local sql_stmt = [[ + SELECT total_read_pages, + total_read_time + FROM book + WHERE id = '%s' + ]] + local total_pages, total_time = conn:rowexec(string.format(sql_stmt, id_book)) + if total_pages then + total_pages = tonumber(total_pages) + else + total_pages = 0 + end + if total_time then + total_time = tonumber(total_time) + else + total_time = 0 + end + conn:close() + return total_pages, total_time +end + +function ReaderStatistics:getBookProperties() + local props = self.view.document:getProps() + if props.title == "No document" or props.title == "" then + -- FIXME: sometimes crengine returns "No document", try one more time + props = self.view.document:getProps() + end + return props end function ReaderStatistics:getStatisticEnabledMenuItem() @@ -133,14 +521,23 @@ function ReaderStatistics:getStatisticEnabledMenuItem() 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 and not self:isDocless() then + if self.is_enabled and not self:isDocless() then + self:insertDB(self.id_curr_book) self.ui.doc_settings:saveSetting("stats", self.data) end self.is_enabled = not self.is_enabled - -- if was disabled have to get data from file + -- if was disabled have to get data from db if self.is_enabled and not self:isDocless() then - self:initData(self.ui.doc_settings) + self:initData() + self.pages_stats = {} + self.start_current_period = TimeVal:now().sec + if self.document.info.has_pages then + self.curr_page = self.ui.paging.current_page + else + self.curr_page = self.document:getCurrentPage() + end + self.pages_stats[self.start_current_period] = self.curr_page end self:saveSettings() end, @@ -154,14 +551,14 @@ function ReaderStatistics:updateSettings() { text = "", input_type = "number", - hint = T(_("Min seconds, default is 5. Current value: %1"), - self.page_min_read_sec), + hint = T(_("Min seconds, default is %1. Current value: %2"), + DEFAULT_MIN_READ_SEC, self.page_min_read_sec), }, { text = "", input_type = "number", - hint = T(_("Max seconds, default is 90. Current value: %1"), - self.page_max_read_sec), + hint = T(_("Max seconds, default is %1. Current value: %2"), + DEFAULT_MAX_READ_SEC, self.page_max_read_sec), }, }, buttons = { @@ -200,12 +597,18 @@ function ReaderStatistics:addToMainMenu(menu_items) text = _("Settings"), callback = function() self:updateSettings() end, }, + { + text = _("Reset book statistics"), + callback = function() + self:resetBook() + end + }, { text = _("Current book"), callback = function() UIManager:show(KeyValuePage:new{ title = _("Statistics"), - kv_pairs = self:getCurrentStat(), + kv_pairs = self:getCurrentStat(self.id_curr_book), }) end, enabled = not self:isDocless() @@ -223,10 +626,15 @@ function ReaderStatistics:addToMainMenu(menu_items) { text = _("Reading progress"), callback = function() + self:insertDB(self.id_curr_book) + local current_period, current_pages = self:getCurrentBookStats() + local today_period, today_pages = self:getTodayBookStats() UIManager:show(ReaderProgress:new{ - dates = self:getDatesFromAll(7, "daily_weekday"), - current_period = self.current_period, - current_pages = self.pages_current_period, + dates = self:getReadingProgressStats(7), + current_period = current_period, + current_pages = current_pages, + today_period = today_period, + today_pages = today_pages, }) end }, @@ -238,7 +646,7 @@ function ReaderStatistics:addToMainMenu(menu_items) callback = function() UIManager:show(KeyValuePage:new{ title = _("Last week"), - kv_pairs = generateReadBooksTable("", self:getDatesFromAll(7, "daily_weekday")), + kv_pairs = self:getDatesFromAll(7, "daily_weekday"), }) end, }, @@ -247,7 +655,7 @@ function ReaderStatistics:addToMainMenu(menu_items) callback = function() UIManager:show(KeyValuePage:new{ title = _("Last month by day"), - kv_pairs = generateReadBooksTable("", self:getDatesFromAll(30, "daily_weekday")), + kv_pairs = self:getDatesFromAll(30, "daily_weekday"), }) end, }, @@ -256,7 +664,7 @@ function ReaderStatistics:addToMainMenu(menu_items) callback = function() UIManager:show(KeyValuePage:new{ title = _("Last year by day"), - kv_pairs = generateReadBooksTable("", self:getDatesFromAll(365, "daily")), + kv_pairs = self:getDatesFromAll(365, "daily"), }) end, }, @@ -265,7 +673,7 @@ function ReaderStatistics:addToMainMenu(menu_items) callback = function() UIManager:show(KeyValuePage:new{ title = _("Last year by week"), - kv_pairs = generateReadBooksTable("", self:getDatesFromAll(365, "weekly")), + kv_pairs = self:getDatesFromAll(365, "weekly"), }) end, }, @@ -274,7 +682,7 @@ function ReaderStatistics:addToMainMenu(menu_items) callback = function() UIManager:show(KeyValuePage:new{ title = _("Last 10 years by month"), - kv_pairs = generateReadBooksTable("", self:getDatesFromAll(3650, "monthly")), + kv_pairs = self:getDatesFromAll(3650, "monthly"), }) end, }, @@ -284,83 +692,113 @@ function ReaderStatistics:addToMainMenu(menu_items) } end -function ReaderStatistics:getCurrentStat() - local dates = {} +function ReaderStatistics:getTodayBookStats() local now_stamp = os.time() local now_t = os.date("*t") local from_begin_day = now_t.hour * 3600 + now_t.min * 60 + now_t.sec local start_today_time = now_stamp - from_begin_day - local pages_today_period = 0 - local time_today_period = 0 - local last_period = 0 - local sorted_today_performance = {} - local diff - for k, v in pairs(self.data.performance_in_pages) do - dates[os.date("%Y-%m-%d", k)] = true - if k >= start_today_time then - pages_today_period = pages_today_period + 1 - table.insert(sorted_today_performance, k) - end + local conn = SQ3.open(db_location) + local sql_stmt = [[ + SELECT count(*), + sum(sum_period) + FROM ( + SELECT sum(period) AS sum_period + FROM page_stat + WHERE start_time >= '%s' + GROUP BY id_book, page + ) + ]] + local today_pages, today_period = conn:rowexec(string.format(sql_stmt, start_today_time)) + if today_pages == nil then + today_pages = 0 end - local total_days = util.tableSize(dates) - local read_pages = util.tableSize(self.data.performance_in_pages) - local current_page = self.view.state.page -- get current page from the view - local avg_time_per_page = self.data.total_time_in_sec / read_pages - - table.sort(sorted_today_performance) - for _, n in pairs(sorted_today_performance) do - if last_period == 0 then - last_period = n - time_today_period = avg_time_per_page - else - diff = n - last_period - if (diff <= page_max_time and diff > 0) then - time_today_period = time_today_period + diff - else - time_today_period = time_today_period + avg_time_per_page - end - last_period = n - end + if today_period == nil then + today_period = 0 + end + today_period = tonumber(today_period) + today_pages = tonumber(today_pages) + conn:close() + return today_period, today_pages +end +function ReaderStatistics:getCurrentBookStats() + local conn = SQ3.open(db_location) + local sql_stmt = [[ + SELECT count(*), + sum(sum_period) + FROM ( + SELECT sum(period) AS sum_period + FROM page_stat + WHERE start_time >= '%s' + GROUP BY id_book, page + ) + ]] + local current_pages, current_period = conn:rowexec(string.format(sql_stmt, self.start_current_period)) + if current_pages == nil then + current_pages = 0 end - return { - { _("Current period"), util.secondsToClock(self.current_period, false) }, - { _("Current pages"), self.pages_current_period }, - { _("Today period"), util.secondsToClock(time_today_period, false) }, - { _("Today pages"), pages_today_period }, - { _("Time to read"), util.secondsToClock((self.data.pages - current_page) * avg_time_per_page, false) }, - { _("Total time"), util.secondsToClock(self.data.total_time_in_sec, false) }, - { _("Total highlights"), self.data.highlights }, - { _("Total notes"), self.data.notes }, - { _("Total days"), total_days }, - { _("Average time per page"), util.secondsToClock(avg_time_per_page, false) }, - { _("Read pages/Total pages"), read_pages .. "/" .. self.data.pages }, - { _("Percentage completed"), math.floor(read_pages / self.data.pages * 100 + 0.5) .. "%" }, -- adding 0.5 rounds to nearest integer with math.floor - } + if current_period == nil then + current_period = 0 + end + current_period = tonumber(current_period) + current_pages = tonumber(current_pages) + return current_period, current_pages end --- For backward compatibility -local function getDatesForBookOldFormat(book) - local dates = {} - - 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 +function ReaderStatistics:getCurrentStat(id_book) + if id_book == nil then + return end + self:insertDB(id_book) + local today_period, today_pages = self:getTodayBookStats() + local current_period, current_pages = self:getCurrentBookStats() - return generateReadBooksTable(book.title, dates) + local conn = SQ3.open(db_location) + local notes, highlights = conn:rowexec(string.format("SELECT notes, highlights FROM book WHERE id = '%s';)", id_book)) + local sql_stmt = [[ + SELECT count(*) + FROM ( + SELECT strftime('%%Y-%%m-%%d', start_time, 'unixepoch', 'localtime') AS dates + FROM page_stat + WHERE id_book = '%s' + GROUP BY dates + ) + ]] + local total_days = conn:rowexec(string.format(sql_stmt, id_book)) + sql_stmt = [[ + SELECT sum(period), + count(DISTINCT page) + FROM page_stat + WHERE id_book = '%s' + ]] + local total_time_book, total_read_pages = conn:rowexec(string.format(sql_stmt, id_book)) + conn:close() + + if total_time_book == nil then + total_time_book = 0 + end + if total_read_pages == nil then + total_read_pages = 0 + end + self.data.pages = self.view.document:getPageCount() + total_time_book = tonumber(total_time_book) + total_read_pages = tonumber(total_read_pages) + local avg_time_per_page = total_time_book / total_read_pages + return { + { _("Current period"), util.secondsToClock(current_period, false) }, + { _("Current pages"), tonumber(current_pages) }, + { _("Today period"), util.secondsToClock(today_period, false) }, + { _("Today pages"), tonumber(today_pages) }, + { _("Time to read"), util.secondsToClock((self.data.pages - self.view.state.page) * avg_time_per_page, false) }, + { _("Total time"), util.secondsToClock(total_time_book, false) }, + { _("Total highlights"), tonumber(highlights) }, + { _("Total notes"), tonumber(notes) }, + { _("Total days"), tonumber(total_days) }, + { _("Average time per page"), util.secondsToClock(avg_time_per_page, false) }, + { _("Read pages/Total pages"), total_read_pages .. "/" .. self.data.pages }, + -- adding 0.5 rounds to nearest integer with math.floor + { _("Percentage completed"), math.floor(total_read_pages / self.data.pages * 100 + 0.5) .. "%" }, + } end -- sdays -> number of days to show @@ -369,236 +807,356 @@ end -- weekly - show weekly -- monthly - show monthly function ReaderStatistics:getDatesFromAll(sdays, ptype) - local dates = {} - local sorted_performance_in_pages - local diff + local results = {} local now_t = os.date("*t") local from_begin_day = now_t.hour *3600 + now_t.min*60 + now_t.sec local now_stamp = os.time() - local one_day = 24 * 3600 -- one day in seconds - local avg_time_per_page - local period = now_stamp - ((sdays -1) * one_day) - from_begin_day - for __, v in pairs(ReadHistory.hist) do - local book_stats = DocSettings:open(v.file):readSetting('stats') - if book_stats ~= nil then - -- if current reading book - if book_stats.title == self.data.title then - book_stats = self.data - local read_pages = util.tableSize(self.data.performance_in_pages) - avg_time_per_page = self.data.total_time_in_sec / read_pages - else - avg_time_per_page = book_stats.total_time_in_sec / book_stats.pages - end - --zeros table sorted_performance_in_pages - sorted_performance_in_pages = {} - for k1, v1 in pairs(book_stats.performance_in_pages) do - if k1 >= period then - table.insert(sorted_performance_in_pages, k1) - end --if period - end -- for book_performance - -- sort table by time (unix timestamp) - local date_text - table.sort(sorted_performance_in_pages) - for i, n in pairs(sorted_performance_in_pages) do - if ptype == "daily_weekday" then - date_text = string.format("%s (%s)", - os.date("%Y-%m-%d", n), - shortDayOfWeekTranslation[os.date("%a", n)]) - elseif ptype == "daily" then - date_text = os.date("%Y-%m-%d", n) - elseif ptype == "weekly" then - date_text = T(_("%1 Week %2"), os.date("%Y "), os.date(" %W", n)) - elseif ptype == "monthly" then - date_text = monthTranslation[os.date("%B", n)] .. os.date(" %Y", n) - else - date_text = os.date("%Y-%m-%d", n) - end --if ptype - if not dates[date_text] then - dates[date_text] = { - -- first pages of day is set to average of all pages - read = avg_time_per_page, - date = n, - count = 1 - } - else - local entry = dates[date_text] - diff = n - entry.date - -- page_max_time - if (diff <= page_max_time and diff > 0) then - entry.read = entry.read + n - entry.date - else - --add average time if time > page_max_time - entry.read = avg_time_per_page + entry.read - end --if diff - if diff < 0 then - entry.read = avg_time_per_page + entry.read - end - entry.date = n - entry.count = entry.count + 1 - end --if not dates[] - end -- for sorted_performance_in_pages - end -- if book_status - end --for pairs(ReadHistory.hist) - return dates -end - -local function getDatesForBook(book) - local dates = {} - local sorted_performance_in_pages = {} - local diff - local read_pages = util.tableSize(book.performance_in_pages) - for k, v in pairs(book.performance_in_pages) do - table.insert(sorted_performance_in_pages, k) - end - -- sort table by time (unix timestamp) - table.sort(sorted_performance_in_pages) - for i, n in pairs(sorted_performance_in_pages) do - local date_text = os.date("%Y-%m-%d", n) - if not dates[date_text] then - dates[date_text] = { - -- first pages of day is set to average of all pages - read = book.total_time_in_sec / read_pages, - date = n, - count = 1 - } + local one_day = 86400 -- one day in seconds + local period_begin = now_stamp - ((sdays-1) * one_day) - from_begin_day + local sql_stmt_res_book + if ptype == "daily" or ptype == "daily_weekday" then + sql_stmt_res_book = [[ + SELECT dates, + count(*) AS pages, + sum(sum_period) AS periods, + start_time + FROM ( + SELECT strftime('%%Y-%%m-%%d', start_time, 'unixepoch', 'localtime') AS dates, + sum(period) AS sum_period, + start_time + FROM page_stat + WHERE start_time >= '%s' + GROUP BY id_book, page, dates + ) + GROUP BY dates + ORDER BY dates DESC + ]] + elseif ptype == "weekly" then + sql_stmt_res_book = [[ + SELECT dates, + count(*) AS pages, + sum(sum_period) AS periods, + start_time + FROM ( + SELECT strftime('%%Y-%%W', start_time, 'unixepoch', 'localtime') AS dates, + sum(period) AS sum_period, + start_time + FROM page_stat + WHERE start_time >= '%s' + GROUP BY id_book, page, dates + ) + GROUP BY dates + ORDER BY dates DESC + ]] + elseif ptype == "monthly" then + sql_stmt_res_book = [[ + SELECT dates, + count(*) AS pages, + sum(sum_period) AS periods, + start_time + FROM ( + SELECT strftime('%%Y-%%m', start_time, 'unixepoch', 'localtime') AS dates, + sum(period) AS sum_period, + start_time + FROM page_stat + WHERE start_time >= '%s' + GROUP BY id_book, page, dates + ) + GROUP BY dates + ORDER BY dates DESC + ]] + end + self:insertDB(self.id_curr_book) + local conn = SQ3.open(db_location) + local result_book = conn:exec(string.format(sql_stmt_res_book, period_begin)) + conn:close() + if result_book == nil then + return {} + end + for i=1, #result_book.dates do + local date_text + if ptype == "daily_weekday" then + date_text = string.format("%s (%s)", + os.date("%Y-%m-%d", tonumber(result_book[4][i])), + shortDayOfWeekTranslation[os.date("%a", tonumber(result_book[4][i]))]) + elseif ptype == "daily" then + date_text = result_book[1][i] + elseif ptype == "weekly" then + date_text = T(_("%1 Week %2"), os.date("%Y", tonumber(result_book[4][i])), os.date(" %W", tonumber(result_book[4][i]))) + elseif ptype == "monthly" then + date_text = monthTranslation[os.date("%B", tonumber(result_book[4][i]))] .. os.date(" %Y", tonumber(result_book[4][i])) else - local entry = dates[date_text] - diff = n - entry.date - if diff <= page_max_time then - entry.read = entry.read + n - entry.date - else - --add average time if time > page_max_time e.g longer break while reading - entry.read = book.total_time_in_sec / read_pages + entry.read - end - entry.date = n - entry.count = entry.count + 1 + date_text = result_book[1][i] end + table.insert(results, { + date_text, + T(_("Pages: (%1) Time: %2"), tonumber(result_book[2][i]), util.secondsToClock(tonumber(result_book[3][i]), false)) + }) end - return generateReadBooksTable(book.title, dates) + return results +end + +function ReaderStatistics:getReadingProgressStats(sdays) + local results = {} + local pages, period, date_read + local now_t = os.date("*t") + local from_begin_day = now_t.hour *3600 + now_t.min*60 + now_t.sec + local now_stamp = os.time() + local one_day = 86400 -- one day in seconds + local period_begin = now_stamp - ((sdays-1) * one_day) - from_begin_day + local conn = SQ3.open(db_location) + local sql_stmt = [[ + SELECT dates, + count(*) AS pages, + sum(sum_period) AS periods, + start_time + FROM ( + SELECT strftime('%%Y-%%m-%%d', start_time, 'unixepoch', 'localtime') AS dates, + sum(period) AS sum_period, + start_time + FROM page_stat + WHERE start_time >= '%s' + GROUP BY id_book, page, dates + ) + GROUP BY dates + ORDER BY dates DESC + ]] + local result_book = conn:exec(string.format(sql_stmt, period_begin)) + + for i = 1, sdays do + pages = tonumber(result_book[2][i]) + period = tonumber(result_book[3][i]) + date_read = result_book[1][i] + if pages == nil then pages = 0 end + if period == nil then period = 0 end + table.insert(results, { + pages, + period, + date_read + }) + end + conn:close() + return results +end + +local function getDatesForBook(id_book) + local results = {} + local conn = SQ3.open(db_location) + local sql_stmt = [[ + SELECT date(start_time, 'unixepoch', 'localtime') AS dates, + count(DISTINCT page) AS pages, + sum(period) AS periods + FROM page_stat + WHERE id_book = '%s' + GROUP BY Date(start_time, 'unixepoch', 'localtime') + ORDER BY dates DESC + ]] + local result_book = conn:exec(string.format(sql_stmt, id_book)) + conn:close() + if result_book == nil then + return {} + end + for i=1, #result_book.dates do + table.insert(results, { + result_book[1][i], + T(_("Pages: (%1) Time: %2"), tonumber(result_book[2][i]), util.secondsToClock(tonumber(result_book[3][i]), false)) + }) + end + return results end function ReaderStatistics:getTotalStats() + self:insertDB(self.id_curr_book) + local conn = SQ3.open(db_location) + local sql_stmt = [[ + SELECT sum(period) + FROM page_stat + ]] + local total_books_time = conn:rowexec(sql_stmt) + if total_books_time == nil then + total_books_time = 0 + end local total_stats = {} - if not self:isDocless() then - -- empty title - if self.data.title == "" then - self.data.title = self.document.file:match("^.+/(.+)$") - end - total_stats = { - { - self.data.title, - util.secondsToClock(self.data.total_time_in_sec, false), - callback = function() - UIManager:show(KeyValuePage:new{ - title = self.data.title, - kv_pairs = getDatesForBook(self.data), - }) - end, - } - } + sql_stmt = [[ + SELECT id + FROM book + ORDER BY last_open DESC + ]] + local id_book_tbl = conn:exec(sql_stmt) + local nr_books + if id_book_tbl ~= nil then + nr_books = #id_book_tbl.id + else + nr_books = 0 end - -- find stats for all other books in history - local proceded_titles, total_books_time = self:getStatisticsFromHistory(total_stats) - total_books_time = total_books_time + self:getOldStatisticsFromDirectory(proceded_titles, total_stats) - total_books_time = total_books_time + tonumber(self.data.total_time_in_sec) - return T(_("Total hours read %1"), - util.secondsToClock(total_books_time, false)), - total_stats + local total_time_book + for i=1, nr_books do + local id_book = tonumber(id_book_tbl[1][i]) + sql_stmt = [[ + SELECT title + FROM book + WHERE id = '%s' + ]] + local book_title = conn:rowexec(string.format(sql_stmt, id_book)) + sql_stmt = [[ + SELECT sum(period) + FROM page_stat + WHERE id_book = '%s' + ]] + total_time_book = conn:rowexec(string.format(sql_stmt,id_book)) + if total_time_book == nil then + total_time_book = 0 + end + table.insert(total_stats, { + book_title, + util.secondsToClock(total_time_book, false), + callback = function() + UIManager:show(KeyValuePage:new{ + title = book_title, + kv_pairs = getDatesForBook(id_book), + }) + end, + }) + end + conn:close() + return T(_("Total hours read %1"), util.secondsToClock(total_books_time, false)), total_stats end -function ReaderStatistics:getStatisticsFromHistory(total_stats) - local titles = {} - local total_books_time = 0 - for _, v in pairs(ReadHistory.hist) do - local book_stats = DocSettings:open(v.file):readSetting('stats') - -- empty title - if book_stats and book_stats.title == "" then - book_stats.title = v.file:match("^.+/(.+)$") +function ReaderStatistics:resetBook() + local total_stats = {} + local kv_reset_book + + self:insertDB(self.id_curr_book) + local conn = SQ3.open(db_location) + local sql_stmt = [[ + SELECT id + FROM book + ORDER BY last_open DESC + ]] + local id_book_tbl = conn:exec(sql_stmt) + local nr_books + if id_book_tbl ~= nil then + nr_books = #id_book_tbl.id + else + nr_books = 0 + end + + local total_time_book + for i=1, nr_books do + local id_book = tonumber(id_book_tbl[1][i]) + sql_stmt = [[ + SELECT title + FROM book + WHERE id = '%s' + ]] + local book_title = conn:rowexec(string.format(sql_stmt, id_book)) + sql_stmt = [[ + SELECT sum(period) + FROM page_stat + WHERE id_book = '%s' + ]] + total_time_book = conn:rowexec(string.format(sql_stmt,id_book)) + if total_time_book == nil then + total_time_book = 0 end - if book_stats and book_stats.total_time_in_sec > 0 - and book_stats.title ~= self.data.title then - titles[book_stats.title] = true + + if id_book ~= self.id_curr_book then table.insert(total_stats, { - book_stats.title, - util.secondsToClock(book_stats.total_time_in_sec, false), + book_title, + util.secondsToClock(total_time_book, false), + id_book, callback = function() - UIManager:show(KeyValuePage:new{ - title = book_stats.title, - kv_pairs = getDatesForBook(book_stats), + UIManager:show(ConfirmBox:new{ + text = T(_("Do you want to reset statistics for book:\n%1"), book_title), + cancel_text = _("Cancel"), + cancel_callback = function() + return + end, + ok_text = _("Reset"), + ok_callback = function() + for j=1, #total_stats do + if total_stats[j][3] == id_book then + self:deleteBook(id_book) + table.remove(total_stats, j) + break + end + end + --refresh window after delete item + kv_reset_book:_populateItems() + end, }) end, }) - total_books_time = total_books_time + tonumber(book_stats.total_time_in_sec) - end --if book_stats - end --for pairs(ReadHistory.hist) - return titles, total_books_time -end - --- For backward compatibility -function ReaderStatistics:getOldStatisticsFromDirectory(exlude_titles, total_stats) - if lfs.attributes(statistics_dir, "mode") ~= "directory" then - return 0 - end - local total_books_time = 0 - for curr_file in lfs.dir(statistics_dir) do - local path = statistics_dir .. curr_file - if lfs.attributes(path, "mode") == "file" then - local book_result = self:importFromFile(statistics_dir, curr_file) - if book_result and book_result.total_time > 0 - and book_result.title ~= self.data.title - and not exlude_titles[book_result.title] then - table.insert(total_stats, { - book_result.title, - util.secondsToClock(book_result.total_time, false), - callback = function() - UIManager:show(KeyValuePage:new{ - title = book_result.title, - kv_pairs = getDatesForBookOldFormat(book_result), - }) - end, - }) - total_books_time = total_books_time + tonumber(book_result.total_time) - end end end - return total_books_time + kv_reset_book = KeyValuePage:new{ + title = _("Reset book statistics"), + kv_pairs = total_stats, + } + UIManager:show(kv_reset_book) + conn:close() +end + +function ReaderStatistics:deleteBook(id_book) + local conn = SQ3.open(db_location) + local sql_stmt = [[ + DELETE from book + WHERE id = ? + ]] + local stmt = conn:prepare(sql_stmt) + stmt:reset():bind(id_book):step() + + sql_stmt = [[ + DELETE from page_stat + WHERE id_book = ? + ]] + stmt = conn:prepare(sql_stmt) + stmt:reset():bind(id_book):step() + stmt:close() + conn:close() end function ReaderStatistics:onPageUpdate(pageno) if self:isDocless() or not self.is_enabled then return end - 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 - self.pages_current_period = 0 + self.curr_page = pageno + self.pages_stats[TimeVal:now().sec] = pageno + local mem_read_pages = 0 + local mem_read_time = 0 + if util.tableSize(self.pages_stats) > 1 then + mem_read_pages = util.tableSize(self.pages_stats) - 1 + local sorted_performance = {} + for time, page in pairs(self.pages_stats) do + table.insert(sorted_performance, time) + end + table.sort(sorted_performance) + local diff_time + for i=1, #sorted_performance - 1 do + diff_time = sorted_performance[i + 1] - sorted_performance[i] + if diff_time <= self.page_max_read_sec and diff_time >= self.page_min_read_sec then + mem_read_time = mem_read_time + diff_time + elseif diff_time > self.page_max_read_sec then + mem_read_time = mem_read_time + self.page_max_read_sec + end + end 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.pages_current_period = self.pages_current_period + 1 - self.data.total_time_in_sec = self.data.total_time_in_sec + diff_time - self.data.performance_in_pages[curr_time.sec] = pageno - -- we cannot save stats each time this is a page update event, - -- because the self.data may not even be initialized when such a event - -- comes, which will render a blank stats written into doc settings - -- and all previous stats are totally wiped out. - self.ui.doc_settings:saveSetting("stats", self.data) + -- every 50 pages we write stats to database + if util.tableSize(self.pages_stats) % PAGE_INSERT == 0 then + self:insertDB(self.id_curr_book) + mem_read_pages = 0 + mem_read_time = 0 end - - self.last_time = curr_time -end - --- For backward compatibility -function ReaderStatistics:inplaceMigration() - local oldData = self:importFromFile(statistics_dir, self.data.title .. ".stat") - if oldData then - for k, v in pairs(oldData.details) do - self.data.performance_in_pages[v.time] = v.page + -- send avg_time_page + local avg_time + if self.total_read_pages > 0 or mem_read_pages > 0 then + avg_time = math.ceil((self.total_read_time + mem_read_time) / (self.total_read_pages + mem_read_pages)) + if self.avg_time ~= avg_time then + self.avg_time = avg_time + if avg_time > 0 then + self.ui:handleEvent(Event:new("UpdateStats", avg_time)) + end end end end @@ -614,41 +1172,53 @@ function ReaderStatistics:importFromFile(base_path, item) local ok, stored = pcall(dofile, statistic_file) if ok then return stored - else - DEBUG(stored) end end end function ReaderStatistics:onCloseDocument() - if not self:isDocless() and self.last_time and self.is_enabled then + if not self:isDocless() and self.is_enabled then self.ui.doc_settings:saveSetting("stats", self.data) end end function ReaderStatistics:onAddHighlight() self.data.highlights = self.data.highlights + 1 + return true +end + +function ReaderStatistics:onDelHighlight() + if self.data.highlights > 0 then + self.data.highlights = self.data.highlights - 1 + end + return true end function ReaderStatistics:onAddNote() self.data.notes = self.data.notes + 1 end --- in case when screensaver starts function ReaderStatistics:onSaveSettings() self:saveSettings() if not self:isDocless() then self.ui.doc_settings:saveSetting("stats", self.data) - self.current_period = 0 - self.pages_current_period = 0 + self:insertDB(self.id_curr_book) + end +end + +-- in case when screensaver starts +function ReaderStatistics:onSuspend() + if not self:isDocless() then + self.ui.doc_settings:saveSetting("stats", self.data) + self:insertDB(self.id_curr_book) end end -- screensaver off function ReaderStatistics:onResume() - self.current_period = 0 - self.pages_current_period = 0 - + self.start_current_period = TimeVal:now().sec + self.pages_stats = {} + self.pages_stats[self.start_current_period] = self.curr_page end function ReaderStatistics:saveSettings(fields) @@ -661,6 +1231,7 @@ function ReaderStatistics:saveSettings(fields) min_sec = self.page_min_read_sec, max_sec = self.page_max_read_sec, is_enabled = self.is_enabled, + convert_to_db = self.convert_to_db } G_reader_settings:saveSetting("statistics", settings) end diff --git a/plugins/statistics.koplugin/readerprogress.lua b/plugins/statistics.koplugin/readerprogress.lua index 03b41a617..5cdbbb88c 100644 --- a/plugins/statistics.koplugin/readerprogress.lua +++ b/plugins/statistics.koplugin/readerprogress.lua @@ -2,8 +2,8 @@ local Blitbuffer = require("ffi/blitbuffer") local CenterContainer = require("ui/widget/container/centercontainer") local CloseButton = require("ui/widget/closebutton") local Font = require("ui/font") -local Geom = require("ui/geometry") local FrameContainer = require("ui/widget/container/framecontainer") +local Geom = require("ui/geometry") local HorizontalGroup = require("ui/widget/horizontalgroup") local HorizontalSpan = require("ui/widget/horizontalspan") local InputContainer = require("ui/widget/container/inputcontainer") @@ -36,16 +36,6 @@ local dayOfWeekTranslation = { ["Sunday"] = _("Sunday"), } -local shortDayOfWeekTranslation = { - ["Mon"] = _("Mon"), - ["Tue"] = _("Tue"), - ["Wed"] = _("Wed"), - ["Thu"] = _("Thu"), - ["Fri"] = _("Fri"), - ["Sat"] = _("Sat"), - ["Sun"] = _("Sun"), -} - function ReaderProgress:init() self.small_font_face = Font:getFace("smallffont") self.medium_font_face = Font:getFace("ffont") @@ -65,30 +55,16 @@ function ReaderProgress:init() } end -function ReaderProgress:getTotalStats(dates) +function ReaderProgress:getTotalStats(stats_day) local total_time = 0 local total_pages = 0 - for _, v in pairs(dates) do - total_pages = total_pages + v. count - total_time = total_time + v.read + for i=1, stats_day do + total_pages = total_pages + self.dates[i][1] + total_time = total_time + self.dates[i][2] end return total_time, total_pages end -function ReaderProgress:getTodayStats(dates) - local today_time = 0 - local today_pages = 0 - local now_time = os.time() - local today = string.format("%s (%s)", - os.date("%Y-%m-%d", now_time), - shortDayOfWeekTranslation[os.date("%a", now_time)]) - if dates[today] ~= nil then - today_time = dates[today].read - today_pages = dates[today].count - end - return today_time, today_pages -end - function ReaderProgress:getStatusContent(width) return VerticalGroup:new{ align = "left", @@ -188,7 +164,6 @@ end function ReaderProgress:genWeekStats(stats_day) local second_in_day = 86400 - local date_format local date_format_show local select_day_time local diff_time @@ -199,10 +174,11 @@ function ReaderProgress:genWeekStats(stats_day) } local statistics_group = VerticalGroup:new{ align = "left" } local max_week_time = -1 - for _, v in pairs(self.dates) do - if v.read > max_week_time then max_week_time = v.read end + local day_time + for i=1, stats_day do + day_time = self.dates[i][2] + if day_time > max_week_time then max_week_time = day_time end end - local top_padding_span = HorizontalSpan:new{ width = Screen:scaleBySize(15) } local top_span_group = HorizontalGroup:new{ align = "center", @@ -222,13 +198,12 @@ function ReaderProgress:genWeekStats(stats_day) }, } - for i = 1, stats_day , 1 do + local j = 1 + for i = 1, stats_day do diff_time = now_time - second_in_day * (i - 1) - date_format = string.format("%s (%s)", - os.date("%Y-%m-%d", diff_time), - shortDayOfWeekTranslation[os.date("%a", diff_time)]) - if self.dates[date_format] ~= nil then - select_day_time = self.dates[date_format].read + if self.dates[j][3] == os.date("%Y-%m-%d", diff_time) then + select_day_time = self.dates[j][2] + j = j + 1 else select_day_time = 0 end @@ -272,7 +247,6 @@ function ReaderProgress:genWeekStats(stats_day) end function ReaderProgress:genSummaryDay(width) - local today_time, today_pages = self:getTodayStats(self.dates) local height = Screen:scaleBySize(60) local statistics_container = CenterContainer:new{ dimen = Geom:new{ w = width, h = height }, @@ -341,14 +315,14 @@ function ReaderProgress:genSummaryDay(width) CenterContainer:new{ dimen = Geom:new{ w = tile_width, h = tile_height }, TextWidget:new{ - text = today_pages, + text = self.today_pages, face = self.medium_font_face, }, }, CenterContainer:new{ dimen = Geom:new{ w = tile_width, h = tile_height }, TextWidget:new{ - text = util.secondsToClock(today_time, true), + text = util.secondsToClock(self.today_period, true), face = self.medium_font_face, }, }, @@ -367,7 +341,7 @@ end function ReaderProgress:genSummaryWeek(width) local height = Screen:scaleBySize(60) - local total_time, total_pages = self:getTotalStats(self.dates) + local total_time, total_pages = self:getTotalStats(#self.dates) local statistics_container = CenterContainer:new{ dimen = Geom:new{ w = width, h = height }, }