├── .gitignore ├── pocketbooksync.koplugin ├── _meta.lua └── main.lua └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | -------------------------------------------------------------------------------- /pocketbooksync.koplugin/_meta.lua: -------------------------------------------------------------------------------- 1 | local _ = require("gettext") 2 | return { 3 | name = "pocketbooksync", 4 | fullname = _("Pocketbook Sync"), 5 | description = _([[Sync reading status with Pocketbook.]]), 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KOReader Pocketbook Sync 2 | 3 | A KOReader plugin that syncs reading progress from KOReader to PocketBook 4 | Library, primarily to make the book progress bars on PocketBook's home screen 5 | accurate. 6 | 7 | ## Installation 8 | 9 | Copy the folder *pocketbooksync.koplugin* to */applications/koreader/plugins* on your PocketBook device.\ 10 | Please mind to keep the folder name as it is. 11 | 12 | ## Usage 13 | 14 | After you've installed the KOReader plugin, syncing will happen silently with each page update. 15 | 16 | Note that the sync is only one way (from KOReader), and it only syncs the 17 | progress bars to Library. It is not meant to sync the reading position between 18 | KOReader and PBReader. 19 | 20 | For further information, read the corresponding thread on MobileRead: 21 | https://www.mobileread.com/forums/showthread.php?t=354026 22 | 23 | -------------------------------------------------------------------------------- /pocketbooksync.koplugin/main.lua: -------------------------------------------------------------------------------- 1 | local Device = require("device") 2 | 3 | if not Device:isPocketBook() then 4 | return { disabled = true, } 5 | end 6 | 7 | local WidgetContainer = require("ui/widget/container/widgetcontainer") 8 | local _ = require("gettext") 9 | local logger = require("logger") 10 | local util = require("util") 11 | local SQ3 = require("lua-ljsqlite3/init") 12 | local pocketbookDbConn = SQ3.open("/mnt/ext1/system/explorer-3/explorer-3.db") 13 | local ffi = require("ffi") 14 | local inkview = ffi.load("inkview") 15 | local bookIds = {} 16 | 17 | -- wait for database locks for up to 1 second before raising an error 18 | pocketbookDbConn:set_busy_timeout(1000) 19 | 20 | local function GetCurrentProfileId() 21 | local profile_name = inkview.GetCurrentProfile() 22 | if profile_name == nil then 23 | return 1 24 | else 25 | local stmt = pocketbookDbConn:prepare("SELECT id FROM profiles WHERE name = ?") 26 | local profile_id = stmt:reset():bind(ffi.string(profile_name)):step() 27 | stmt:close() 28 | return profile_id[1] 29 | end 30 | end 31 | 32 | local profile_id = GetCurrentProfileId() 33 | 34 | local PocketbookSync = WidgetContainer:extend{ 35 | name = "pocketbooksync", 36 | is_doc_only = false, 37 | } 38 | 39 | function PocketbookSync:clearCache() 40 | bookIds = {} 41 | end 42 | 43 | function PocketbookSync:sync() 44 | self:doSync(self:prepareSync()) 45 | end 46 | 47 | function PocketbookSync:prepareSync() 48 | -- onFlushSettings called during koreader exit and after onCloseDocument 49 | -- would raise an error in some of the self.document methods and we can 50 | -- avoid that by checking if self.ui.document is nil 51 | if not self.ui.document then 52 | return nil 53 | end 54 | 55 | local folder, file = self:getFolderFile() 56 | if not folder or folder == "" or not file or file == "" then 57 | logger.info("Pocketbook Sync: No folder/file found for " .. self.view.document.file) 58 | return nil 59 | end 60 | 61 | local globalPage = self.view.state.page 62 | local flow = self.document:getPageFlow(globalPage) 63 | 64 | -- skip sync if not in the main flow 65 | if flow ~= 0 then 66 | return nil 67 | end 68 | 69 | local totalPages = self.document:getTotalPagesInFlow(flow) 70 | local page = self.document:getPageNumberInFlow(globalPage) 71 | 72 | local summary = self.ui.doc_settings:readSetting("summary") 73 | local status = summary and summary.status 74 | local completed = (status == "complete" or page == totalPages) and 1 or 0 75 | 76 | -- hide the progress bar if we're on the title/cover page 77 | -- 78 | -- we'll never set cpage=1 so the progress bar will seem to jump a bit at 79 | -- the start of a book, but there's no nice way to fix that: to use the 80 | -- full range, we'd need to map pages 2 to last-1 to cpages 1 to last-1, 81 | -- and that always skips one position; skipping the first one is the least 82 | -- surprising behaviour 83 | if page == 1 then 84 | page = 0 85 | end 86 | 87 | return { 88 | folder = folder, 89 | file = file, 90 | totalPages = totalPages, 91 | page = page, 92 | completed = completed, 93 | time = os.time(), 94 | } 95 | end 96 | 97 | function PocketbookSync:doSync(data) 98 | if not data then 99 | return 100 | end 101 | 102 | local cacheKey = data.folder .. data.file 103 | 104 | if not bookIds[cacheKey] then 105 | local sql = [[ 106 | SELECT book_id 107 | FROM files 108 | WHERE 109 | folder_id = (SELECT id FROM folders WHERE name = ? LIMIT 1) 110 | AND filename = ? 111 | LIMIT 1 112 | ]] 113 | local stmt = pocketbookDbConn:prepare(sql) 114 | local row = stmt:reset():bind(data.folder, data.file):step() 115 | stmt:close() 116 | 117 | if row == nil then 118 | logger.info("Pocketbook Sync: Book id for " .. data.folder .. "/" .. data.file .. " not found") 119 | return 120 | end 121 | bookIds[cacheKey] = row[1] 122 | end 123 | 124 | local book_id = bookIds[cacheKey] 125 | local sql = [[ 126 | REPLACE INTO books_settings 127 | (bookid, profileid, cpage, npage, completed, opentime) 128 | VALUES (?, ?, ?, ?, ?, ?) 129 | ]] 130 | local stmt = pocketbookDbConn:prepare(sql) 131 | stmt:reset():bind(book_id, profile_id, data.page, data.totalPages, data.completed, data.time):step() 132 | stmt:close() 133 | end 134 | 135 | function PocketbookSync:getFolderFile() 136 | local path = self.view.document.file 137 | local folder, file = util.splitFilePathName(path) 138 | local folderTrimmed = folder:match("(.*)/") 139 | if folderTrimmed ~= nil then 140 | folder = folderTrimmed 141 | end 142 | return folder, file 143 | end 144 | 145 | function PocketbookSync:onFlushSettings() 146 | self:sync() 147 | end 148 | 149 | function PocketbookSync:onCloseDocument() 150 | self:sync() 151 | end 152 | 153 | function PocketbookSync:onEndOfBook() 154 | self:sync() 155 | end 156 | 157 | function PocketbookSync:onSuspend() 158 | self:sync() 159 | end 160 | 161 | return PocketbookSync 162 | --------------------------------------------------------------------------------