diff --git a/frontend/ui/widget/doublespinwidget.lua b/frontend/ui/widget/doublespinwidget.lua index efd3e523a..ac02ac3ee 100644 --- a/frontend/ui/widget/doublespinwidget.lua +++ b/frontend/ui/widget/doublespinwidget.lua @@ -83,6 +83,8 @@ function DoubleSpinWidget:update() value = self.left_value, value_min = self.left_min, value_max = self.left_max, + value_step = self.left_step, + value_hold_step = self.left_hold_step, wrap = false, update_callback = function() picker_update_callback() end, } @@ -92,6 +94,8 @@ function DoubleSpinWidget:update() value = self.right_value, value_min = self.right_min, value_max = self.right_max, + value_step = self.right_step, + value_hold_step = self.right_hold_step, wrap = false, update_callback = function() picker_update_callback() end, } diff --git a/frontend/ui/widget/textwidget.lua b/frontend/ui/widget/textwidget.lua index c0ccac3c9..475452b43 100644 --- a/frontend/ui/widget/textwidget.lua +++ b/frontend/ui/widget/textwidget.lua @@ -56,6 +56,29 @@ local TextWidget = Widget:new{ _xshaping = nil, } +-- Helper function to be used before instantiating a TextWidget instance +-- (This is more precise than the one with the same name in TextBoxWidget, +-- as we use the real font metrics.) +function TextWidget:getFontSizeToFitHeight(font_name, height_px, padding) + -- Get a font size that would fit the text in height_px. + if not padding then + padding = self.padding -- (TextWidget default above: Size.padding.small) + end + -- We need to iterate (skip 1 early as font_size is always smaller + -- than font height) + local font_size = height_px + repeat + font_size = font_size - 1 + if font_size <= 1 then + break + end + local face = Font:getFace(font_name, font_size) + local face_height = face.ftface:getHeightAndAscender() + face_height = math.ceil(face_height) + 2*padding + until face_height <= height_px + return font_size +end + function TextWidget:updateSize() if self._updated then return diff --git a/plugins/statistics.koplugin/calendarview.lua b/plugins/statistics.koplugin/calendarview.lua index cc44daf41..63c6413e8 100644 --- a/plugins/statistics.koplugin/calendarview.lua +++ b/plugins/statistics.koplugin/calendarview.lua @@ -115,8 +115,8 @@ local CalendarDay = InputContainer:new{ is_future = false, font_face = "xx_smallinfofont", font_size = nil, + show_histo = true, histo_height = nil, - nb_not_shown = nil, } function CalendarDay:init() @@ -154,15 +154,20 @@ function CalendarDay:init() } local inner_w = self.width - 2*self.border local inner_h = self.height - 2*self.border - if not self.histo_height then - self.histo_height = inner_h / 3 + if self.show_histo then + if not self.histo_height then + self.histo_height = inner_h / 3 + end + self.histo_w = BottomContainer:new{ + dimen = Geom:new{w = inner_w, h = inner_h}, + HistogramWidget:new{ + width = inner_w, + height = self.histo_height, + nb_items = 24, + ratios = self.ratio_per_hour, + } + } end - self.histo_w = HistogramWidget:new{ - width = inner_w, - height = self.histo_height, - nb_items = 24, - ratios = self.ratio_per_hour, - } self[1] = FrameContainer:new{ padding = 0, color = self.is_future and Blitbuffer.COLOR_GRAY or Blitbuffer.COLOR_BLACK, @@ -170,12 +175,10 @@ function CalendarDay:init() width = self.width, height = self.height, OverlapGroup:new{ + dimen = { w = inner_w }, self.daynum_w, self.nb_not_shown_w, - BottomContainer:new{ - dimen = Geom:new{w = inner_w, h = inner_h}, - self.histo_w, - }, + self.histo_w, -- nil if not show_histo } } end @@ -203,6 +206,7 @@ local CalendarWeek = InputContainer:new{ day_padding = 0, day_border = 0, nb_book_spans = 0, + histo_shown = nil, span_height = nil, font_size = 0, font_face = "xx_smallinfofont", @@ -299,17 +303,50 @@ function CalendarWeek:update() -- Create and add BookSpans local bspan_margin_h = Size.margin.tiny + self.day_border local bspan_margin_v = Size.margin.tiny - local bspan_padding = Size.padding.tiny -- only used to compute the adequate font size + local bspan_padding_h = Size.padding.tiny local bspan_border = Size.border.thin - local text_height = self.span_height - 2 * (bspan_margin_v + bspan_border + bspan_padding) - local inner_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0) + + -- We need a smaller font size than the one provided + local text_height = self.span_height - 2 * (bspan_margin_v + bspan_border) + -- We don't use any bspan_padding_v, we let that be handled by CenterContainer + -- and choosing an appropriate font size. + -- We use TextBoxWidget:getFontSizeToFitHeight() to get a fitting + -- font size. It's less precise than the TextWidget equivalent, + -- but it handles padding as 'em'. + -- Use a 1.3em line height + local inner_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0.3) + -- If font size gets really small, get a larger one by using a smaller + -- line height: tall glyphs may bleed on the border, but we won't notice + -- at such small size, and we'll appreciate the readability. + -- (threshold values decided from visual testing) + if inner_font_size <= 12 then + inner_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0.1) + elseif inner_font_size <= 15 then + inner_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0.2) + end + -- But cap it to the day num font size + inner_font_size = math.min(inner_font_size, self.font_size) + + local offset_y_fixup + if self.histo_shown then + -- No real y positionning needed, but push it a bit down + -- over histogram, as histograms rarely reach 100%, and + -- will be drawn last, so possibly over last book span if + -- really near 100% + offset_y_fixup = Size.margin.small + else + -- No histogram: ensure last book span bottom margin + -- is equal to bspan_margin_v for a nice fit + offset_y_fixup = self.height - self.span_height * (self.nb_book_spans + 1) - bspan_margin_v + end + for col, day_books in ipairs(self.days_books) do for row, book in ipairs(day_books) do if book and book.start_day == col then local fgcolor, bgcolor = unpack(SPAN_COLORS[(book.id % #SPAN_COLORS)+1]) local offset_x = (col-1) * (self.day_width + self.day_padding) local offset_y = row * self.span_height -- 1st real row used by day num - offset_y = offset_y + Size.margin.small -- push it a bit down, over histogram + offset_y = offset_y + offset_y_fixup local width = book.span_days * self.day_width + self.day_padding * (book.span_days-1) -- We use two FrameContainers, as (unlike HTML) a FrameContainer -- draws the background color outside its borders, in the margins @@ -337,7 +374,7 @@ function CalendarWeek:update() }, TextWidget:new{ text = BD.auto(book.title), - max_width = width - 2 * (bspan_margin_h + bspan_border + bspan_padding), + max_width = width - 2 * (bspan_margin_h + bspan_border + bspan_padding_h), face = Font:getFace(self.font_face, inner_font_size), padding = 0, fgcolor = fgcolor, @@ -363,16 +400,19 @@ local CalendarView = InputContainer:new{ reader_statistics = nil, monthTranslation = nil, shortDayOfWeekTranslation = nil, - startDayOfWeek = 2, -- 2 = Monday, 1-7 = Sunday-Saturday - nbFutureMonths = 0, -- nb of future months to allow browsing + longDayOfWeekTranslation = nil, + start_day_of_week = 2, -- 2 = Monday, 1-7 = Sunday-Saturday + show_hourly_histogram = true, + browse_future_months = false, nb_book_spans = 3, + font_face = "xx_smallinfofont", title = "", width = nil, height = nil, cur_month = nil, weekdays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } -- in Lua wday order - -- (These do not need translations: they are the key into - -- the provided self.shortDayOfWeekTranslation) + -- (These do not need translations: they are the key into the provided + -- self.shortDayOfWeekTranslation and self.longDayOfWeekTranslation) } function CalendarView:init() @@ -419,15 +459,6 @@ function CalendarView:init() if not self.cur_month then self.cur_month = self.max_month end - if self.nbFutureMonths > 0 then - local t = os.time({ - year = self.max_month:sub(1,4), - month = self.max_month:sub(6), - day = 15, - }) - t = t + 86400 * 30 * self.nbFutureMonths - self.max_month = os.date("%Y-%m", t) - end -- group for page info local chevron_left = "resources/icons/appbar.chevron.left.png" @@ -522,7 +553,7 @@ function CalendarView:init() self.day_names = HorizontalGroup:new{} for i = 0, 6 do local dayname = TextWidget:new{ - text = self.shortDayOfWeekTranslation[self.weekdays[(self.startDayOfWeek-1+i)%7 + 1]], + text = self.shortDayOfWeekTranslation[self.weekdays[(self.start_day_of_week-1+i)%7 + 1]], face = Font:getFace("xx_smallinfofont"), bold = true, } @@ -544,9 +575,32 @@ function CalendarView:init() - self.page_info:getSize().h - self.day_names:getSize().h self.week_height = math.floor((available_height - 5*self.inner_padding) / 6) self.day_border = Size.border.default - -- day num + nb_book_spans + histogram - self.span_height = math.ceil((self.week_height - 2*self.day_border) / (self.nb_book_spans + 2)) - self.span_font_size = TextBoxWidget:getFontSizeToFitHeight(self.span_height, 1, 0.3) + if self.show_hourly_histogram then + -- day num + nb_book_spans + histogram: ceil() as histogram rarely + -- reaches 100% and is stuck to bottom + self.span_height = math.ceil((self.week_height - 2*self.day_border) / (self.nb_book_spans+2)) + else + -- day num + nb_book_span: floor() to get some room for bottom padding + self.span_height = math.floor((self.week_height - 2*self.day_border) / (self.nb_book_spans+1)) + end + -- Limit font size to 1/3 of available height, and so that + -- the day number and the +nb-not-shown do not overlap + local text_height = math.min(self.span_height, self.week_height/3) + self.span_font_size = TextBoxWidget:getFontSizeToFitHeight(text_height, 1, 0.3) + local day_inner_width = self.day_width - 2*self.day_border -2*self.inner_padding + while true do + local test_w = TextWidget:new{ + text = " 30 + 99 ", -- we want this to be displayed in the available width + face = Font:getFace(self.font_face, self.span_font_size), + bold = true, + } + if test_w:getWidth() <= day_inner_width then + test_w:free() + break + end + self.span_font_size = self.span_font_size - 1 + test_w:free() + end self.main_content = VerticalGroup:new{} self:_populateItems() @@ -593,9 +647,9 @@ function CalendarView:_populateItems() -- Update footer self.page_info_text:setText(self.cur_month) self.page_info_left_chev:enableDisable(self.cur_month > self.min_month) - self.page_info_right_chev:enableDisable(self.cur_month < self.max_month) + self.page_info_right_chev:enableDisable(self.cur_month < self.max_month or self.browse_future_months) self.page_info_first_chev:enableDisable(self.cur_month > self.min_month) - self.page_info_last_chev:enableDisable(self.cur_month < self.max_month) + self.page_info_last_chev:enableDisable(self.cur_month < self.max_month or self.browse_future_months) local ratio_per_hour_by_day = self.reader_statistics:getReadingRatioPerHourByDay(self.cur_month) local books_by_day = self.reader_statistics:getReadBookByDay(self.cur_month) @@ -612,7 +666,7 @@ function CalendarView:_populateItems() if cur_date.month ~= this_month then break end - if not cur_week or cur_date.wday == self.startDayOfWeek then + if not cur_week or cur_date.wday == self.start_day_of_week then if cur_week then table.insert(self.main_content, VerticalSpan:new{ width = self.inner_padding }) end @@ -623,15 +677,17 @@ function CalendarView:_populateItems() day_padding = self.inner_padding, day_border = self.day_border, nb_book_spans = self.nb_book_spans, + histo_shown = self.show_hourly_histogram, span_height = self.span_height, + font_face = self.font_face, font_size = self.span_font_size, show_parent = self, } table.insert(self.weeks, cur_week) table.insert(self.main_content, cur_week) - if cur_date.wday ~= self.startDayOfWeek then + if cur_date.wday ~= self.start_day_of_week then -- Add fake days to fill week - local day = self.startDayOfWeek + local day = self.start_day_of_week while day ~= cur_date.wday do cur_week:addDay(CalendarDay:new{ filler = true, @@ -649,25 +705,28 @@ function CalendarView:_populateItems() end local day_s = os.date("%Y-%m-%d", cur_ts) local day_text = string.format("%s (%s)", day_s, - self.shortDayOfWeekTranslation[self.weekdays[cur_date.wday]]) + self.longDayOfWeekTranslation[self.weekdays[cur_date.wday]]) local day_ts = os.time({ year = cur_date.year, month = cur_date.month, day = cur_date.day, hour = 0, }) + local is_future = day_s > today_s cur_week:addDay(CalendarDay:new{ + show_histo = self.show_hourly_histogram, histo_height = self.span_height, + font_face = self.font_face, font_size = self.span_font_size, border = self.day_border, - is_future = day_s > today_s, + is_future = is_future, daynum = cur_date.day, height = self.week_height, width = self.day_width, ratio_per_hour = ratio_per_hour_by_day[day_s], read_books = books_by_day[day_s], show_parent = self, - callback = function() + callback = not is_future and function() -- Just as ReaderStatistics:callbackDaily(), but without any window stacking UIManager:show(KeyValuePage:new{ title = day_text, @@ -696,7 +755,7 @@ function CalendarView:nextMonth() }) t = t + 86400 * 30 -- 30 days later local next_month = os.date("%Y-%m", t) - if next_month <= self.max_month then + if self.browse_future_months or next_month <= self.max_month then self.cur_month = next_month self:_populateItems() end diff --git a/plugins/statistics.koplugin/main.lua b/plugins/statistics.koplugin/main.lua index 3afefad09..c8223c3e2 100644 --- a/plugins/statistics.koplugin/main.lua +++ b/plugins/statistics.koplugin/main.lua @@ -6,7 +6,6 @@ local Device = require("device") local DocSettings = require("docsettings") local InfoMessage = require("ui/widget/infomessage") local KeyValuePage = require("ui/widget/keyvaluepage") -local MultiInputDialog = require("ui/widget/multiinputdialog") local ReaderFooter = require("apps/reader/modules/readerfooter") local ReaderProgress = require("readerprogress") local ReadHistory = require("readhistory") @@ -29,18 +28,24 @@ local db_location = DataStorage:getSettingsDir() .. "/statistics.sqlite3" local PAGE_INSERT = 50 local DEFAULT_MIN_READ_SEC = 5 local DEFAULT_MAX_READ_SEC = 120 +local DEFAULT_CALENDAR_START_DAY_OF_WEEK = 2 -- Monday +local DEFAULT_CALENDAR_NB_BOOK_SPANS = 3 local ReaderStatistics = Widget:extend{ name = "statistics", page_min_read_sec = DEFAULT_MIN_READ_SEC, page_max_read_sec = DEFAULT_MAX_READ_SEC, + calendar_start_day_of_week = DEFAULT_CALENDAR_START_DAY_OF_WEEK, + calendar_nb_book_spans = DEFAULT_CALENDAR_NB_BOOK_SPANS, + calendar_show_histogram = true, + calendar_browse_future_months = false, 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, + convert_to_db = nil, -- true when migration to DB has been done total_read_pages = 0, total_read_time = 0, avg_time = nil, @@ -59,6 +64,8 @@ local ReaderStatistics = Widget:extend{ }, } +local weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } -- in Lua wday order + local shortDayOfWeekTranslation = { ["Mon"] = _("Mon"), ["Tue"] = _("Tue"), @@ -69,6 +76,16 @@ local shortDayOfWeekTranslation = { ["Sun"] = _("Sun"), } +local longDayOfWeekTranslation = { + ["Mon"] = _("Monday"), + ["Tue"] = _("Tuesday"), + ["Wed"] = _("Wednesday"), + ["Thu"] = _("Thursday"), + ["Fri"] = _("Friday"), + ["Sat"] = _("Saturday"), + ["Sun"] = _("Sunday"), +} + local monthTranslation = { ["January"] = _("January"), ["February"] = _("February"), @@ -97,6 +114,10 @@ function ReaderStatistics:init() 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) + self.calendar_start_day_of_week = settings.calendar_start_day_of_week + self.calendar_nb_book_spans = settings.calendar_nb_book_spans + self.calendar_show_histogram = settings.calendar_show_histogram + self.calendar_browse_future_months = settings.calendar_browse_future_months self.is_enabled = not (settings.is_enabled == false) self.convert_to_db = settings.convert_to_db self.ui.menu:registerToMainMenu(self) @@ -228,7 +249,7 @@ end function ReaderStatistics:checkInitDatabase() local conn = SQ3.open(db_location) - if self.convert_to_db then -- if conversion to sqlite was doing earlier + if self.convert_to_db then -- if conversion to sqlite DB has already been done if not conn:exec("pragma table_info('book');") then UIManager:show(ConfirmBox:new{ text = T(_([[ @@ -251,7 +272,7 @@ Do you want to create an empty database? end, }) end - else -- first time convertion to sqlite database + else -- Migrate stats for books in history from metadata.lua to sqlite database self.convert_to_db = true if not conn:exec("pragma table_info('book');") then local filename_first_history, quickstart_filename, __ @@ -671,48 +692,6 @@ function ReaderStatistics:getStatisticEnabledMenuItem() } end -function ReaderStatistics:updateSettings() - self.settings_dialog = MultiInputDialog:new { - title = _("Statistics settings"), - fields = { - { - text = self.page_min_read_sec, - description = T(_("Min seconds, default is %1"), DEFAULT_MIN_READ_SEC), - input_type = "number", - }, - { - text = self.page_max_read_sec, - description = T(_("Max seconds, default is %1"), DEFAULT_MAX_READ_SEC), - input_type = "number", - }, - }, - buttons = { - { - { - text = _("Cancel"), - callback = function() - self.settings_dialog:onClose() - UIManager:close(self.settings_dialog) - end - }, - { - text = _("Apply"), - callback = function() - self:saveSettings(MultiInputDialog:getFields()) - self.settings_dialog:onClose() - UIManager:close(self.settings_dialog) - end - }, - }, - }, - width = Screen:getWidth() * 0.95, - height = Screen:getHeight() * 0.2, - input_type = "number", - } - UIManager:show(self.settings_dialog) - self.settings_dialog:onShowKeyboard() -end - function ReaderStatistics:addToMainMenu(menu_items) menu_items.statistics = { text = _("Reading statistics"), @@ -720,15 +699,130 @@ function ReaderStatistics:addToMainMenu(menu_items) self:getStatisticEnabledMenuItem(), { text = _("Settings"), - keep_menu_open = true, - callback = function() self:updateSettings() end, + sub_item_table = { + { + text_func = function() + return T(_("Read page duration limits: %1 s / %2 s"), + self.page_min_read_sec, self.page_max_read_sec) + end, + callback = function(touchmenu_instance) + local DoubleSpinWidget = require("/ui/widget/doublespinwidget") + local durations_widget + durations_widget = DoubleSpinWidget:new{ + left_text = _("Min"), + left_value = self.page_min_read_sec, + left_default = DEFAULT_MIN_READ_SEC, + left_min = 3, + left_max = 120, + left_step = 1, + left_hold_step = 10, + right_text = _("Max"), + right_value = self.page_max_read_sec, + right_default = DEFAULT_MAX_READ_SEC, + right_min = 10, + right_max = 7200, + right_step = 10, + right_hold_step = 60, + default_values = true, + default_text = _("Use defaults"), + title_text = _("Read page duration limits"), + info_text = _([[ +Set min and max time spent (in seconds) on a page for it to be counted as read in statistics. +The min value ensures pages you quickly browse and skip are not included. +The max value ensures a page you stay on for a long time (because you fell asleep or went away) will be included, but with a duration capped to this specified max value.]]), + callback = function(min, max) + if not min then min = DEFAULT_MIN_READ_SEC end + if not max then max = DEFAULT_MAX_READ_SEC end + if min > max then + min, max = max, min + end + self.page_min_read_sec = min + self.page_max_read_sec = max + self:saveSettings() + UIManager:close(durations_widget) + touchmenu_instance:updateItems() + end, + } + UIManager:show(durations_widget) + end, + keep_menu_open = true, + separator = true, + }, + { + text_func = function() + return T(_("Calendar weeks start on %1"), + longDayOfWeekTranslation[weekDays[self.calendar_start_day_of_week]]) + end, + sub_item_table = { + { -- Friday (Bangladesh and Maldives) + text = longDayOfWeekTranslation[weekDays[6]], + checked_func = function() return self.calendar_start_day_of_week == 6 end, + callback = function() self.calendar_start_day_of_week = 6 end + }, + { -- Saturday (some Middle East countries) + text = longDayOfWeekTranslation[weekDays[7]], + checked_func = function() return self.calendar_start_day_of_week == 7 end, + callback = function() self.calendar_start_day_of_week = 7 end + }, + { -- Sunday + text = longDayOfWeekTranslation[weekDays[1]], + checked_func = function() return self.calendar_start_day_of_week == 1 end, + callback = function() self.calendar_start_day_of_week = 1 end + }, + { -- Monday + text = longDayOfWeekTranslation[weekDays[2]], + checked_func = function() return self.calendar_start_day_of_week == 2 end, + callback = function() self.calendar_start_day_of_week = 2 end + }, + }, + }, + { + text_func = function() + return T(_("Books per calendar day: %1"), self.calendar_nb_book_spans) + end, + callback = function(touchmenu_instance) + local SpinWidget = require("ui/widget/spinwidget") + UIManager:show(SpinWidget:new{ + width = Screen:getWidth() * 0.6, + value = self.calendar_nb_book_spans, + value_min = 1, + value_max = 5, + ok_text = _("Set"), + title_text = _("Books per calendar day"), + text = _("Set the max number of book spans to show for a day"), + callback = function(spin) + self.calendar_nb_book_spans = spin.value + touchmenu_instance:updateItems() + end, + extra_text = _("Use default"), + extra_callback = function() + self.calendar_nb_book_spans = DEFAULT_CALENDAR_NB_BOOK_SPANS + touchmenu_instance:updateItems() + end + }) + end, + keep_menu_open = true, + }, + { + text = _("Show hourly histogram in calendar days"), + checked_func = function() return self.calendar_show_histogram end, + callback = function() + self.calendar_show_histogram = not self.calendar_show_histogram + end, + }, + { + text = _("Allow browsing coming months"), + checked_func = function() return self.calendar_browse_future_months end, + callback = function() + self.calendar_browse_future_months = not self.calendar_browse_future_months + end, + }, + }, }, { - text = _("Reset book statistics"), - keep_menu_open = true, - callback = function() - self:resetBook() - end + text = _("Reset statistics"), + sub_item_table = self:genResetBookSubItemTable(), + separator = true, }, { text = _("Current book"), @@ -779,6 +873,11 @@ function ReaderStatistics:addToMainMenu(menu_items) reader_statistics = self, monthTranslation = monthTranslation, shortDayOfWeekTranslation = shortDayOfWeekTranslation, + longDayOfWeekTranslation = longDayOfWeekTranslation, + start_day_of_week = self.calendar_start_day_of_week, + nb_book_spans = self.calendar_nb_book_spans, + show_hourly_histogram = self.calendar_show_histogram, + browse_future_months = self.calendar_browse_future_months, }) end, }, @@ -1580,6 +1679,32 @@ function ReaderStatistics:getTotalStats() return T(_("Total hours read %1"), util.secondsToClock(total_books_time, false)), total_stats end +function ReaderStatistics:genResetBookSubItemTable() + local sub_item_table = {} + table.insert(sub_item_table, { + text = _("Reset statistics per book"), + keep_menu_open = true, + callback = function() + self:resetBook() + end, + separator = true, + }) + local reset_minutes = { 1, 5, 15, 30, 60 } + for _, minutes in ipairs(reset_minutes) do + local text = T(N_("Reset stats for books read for < 1 m", + "Reset stats for books read for < %1 m", + minutes), minutes) + table.insert(sub_item_table, { + text = text, + keep_menu_open = true, + callback = function() + self:deleteBooksByTotalDuration(minutes) + end, + }) + end + return sub_item_table +end + function ReaderStatistics:resetBook() local total_stats = {} local kv_reset_book @@ -1675,6 +1800,56 @@ function ReaderStatistics:deleteBook(id_book) conn:close() end +function ReaderStatistics:deleteBooksByTotalDuration(max_total_duration_mn) + local max_total_duration_sec = max_total_duration_mn * 60 + UIManager:show(ConfirmBox:new{ + text = T(N_("Permanently remove statistics for books read for less than 1 minute?", + "Permanently remove statistics for books read for less than %1 minutes?", + max_total_duration_mn), max_total_duration_mn), + ok_text = _("Remove"), + ok_callback = function() + local conn = SQ3.open(db_location) + local sql_stmt = [[ + DELETE from page_stat + WHERE id_book in ( + select id from book where id != ? and (total_read_time is NULL or total_read_time < ?) + ) + ]] + local stmt = conn:prepare(sql_stmt) + stmt:reset():bind(self.id_curr_book, max_total_duration_sec):step() + sql_stmt = [[ + DELETE from book + WHERE id != ? and (total_read_time is NULL or total_read_time < ?) + ]] + stmt = conn:prepare(sql_stmt) + stmt:reset():bind(self.id_curr_book, max_total_duration_sec):step() + stmt:close() + -- Get nb of deleted books + sql_stmt = [[ + SELECT changes() + ]] + local nb_deleted = conn:rowexec(sql_stmt) + nb_deleted = nb_deleted and tonumber(nb_deleted) or 0 + if max_total_duration_mn >= 30 and nb_deleted >= 10 then + -- Do a VACUUM to reduce db size (but not worth doing if not much was removed) + conn:exec("PRAGMA temp_store = 2") -- use memory for temp files + local ok, errmsg = pcall(conn.exec, conn, "VACUUM") -- this may take some time + if not ok then + logger.warn("Failed compacting statistics database:", errmsg) + end + end + conn:close() + UIManager:show(InfoMessage:new{ + text = nb_deleted > 0 and T(N_("Statistics for 1 book removed.", + "Statistics for %1 books removed.", + nb_deleted), nb_deleted) + or T(_("No statistics removed.")) + }) + end, + }) +end + + function ReaderStatistics:onPosUpdate(pos, pageno) if self.curr_page ~= pageno then self:onPageUpdate(pageno) @@ -1777,17 +1952,16 @@ function ReaderStatistics:onResume() self.pages_stats[self.start_current_period] = self.curr_page end -function ReaderStatistics:saveSettings(fields) - if fields then - self.page_min_read_sec = tonumber(fields[1]) - self.page_max_read_sec = tonumber(fields[2]) - end - +function ReaderStatistics:saveSettings() local settings = { 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 + convert_to_db = self.convert_to_db, + calendar_start_day_of_week = self.calendar_start_day_of_week, + calendar_nb_book_spans = self.calendar_nb_book_spans, + calendar_show_histogram = self.calendar_show_histogram, + calendar_browse_future_months = self.calendar_browse_future_months, } G_reader_settings:saveSetting("statistics", settings) end