You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/plugins/zsync.koplugin/main.lua

311 lines
9.7 KiB
Lua

local DataStorage = require("datastorage")
local InputContainer = require("ui/widget/container/inputcontainer")
local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager")
local ltn12 = require("ltn12")
local DEBUG = require("dbg")
local _ = require("gettext")
local ffi = require("ffi")
local C = ffi.C
ffi.cdef[[
int remove(const char *);
int rmdir(const char *);
]]
require("ffi/zeromq_h")
local ZSync = InputContainer:new{
name = "zsync",
is_doc_only = true,
}
function ZSync:init()
self.ui.menu:registerToMainMenu(self)
self.outbox = DataStorage:getFullDataDir().."/zsync-outbox"
self.server_config = self.path.."/server.cfg"
self.client_config = self.path.."/client.cfg"
end
function ZSync:addToMainMenu(menu_items)
menu_items.zsync = {
text = _("ZSync"),
sub_item_table = {
{
text_func = function()
return not self.filemq_server
and _("Share this document")
or _("Stop sharing books")
end,
enabled_func = function()
return self.filemq_client == nil
end,
callback = function()
local NetworkMgr = require("ui/network/manager")
Various Wi-Fi QoL improvements (#6424) * Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback). * Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental. * Consistently use "Wi-Fi" everywhere. * On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery. (i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again). * Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic). * restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it). * We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed. * Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state. * Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing. * Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421 Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results). * For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
4 years ago
local connect_callback = function()
if not self.filemq_server then
self:publish()
else
self:unpublish()
end
end
Various Wi-Fi QoL improvements (#6424) * Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback). * Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental. * Consistently use "Wi-Fi" everywhere. * On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery. (i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again). * Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic). * restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it). * We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed. * Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state. * Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing. * Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421 Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results). * For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
4 years ago
NetworkMgr:runWhenOnline(connect_callback)
end
},
{
text_func = function()
return not self.filemq_client
and _("Subscribe to book share")
or _("Unsubscribe from book share")
end,
enabled_func = function()
return self.filemq_server == nil
end,
callback = function()
local NetworkMgr = require("ui/network/manager")
Various Wi-Fi QoL improvements (#6424) * Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback). * Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental. * Consistently use "Wi-Fi" everywhere. * On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery. (i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again). * Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic). * restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it). * We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed. * Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state. * Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing. * Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421 Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results). * For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
4 years ago
local connect_callback = function()
if not self.filemq_client then
self:subscribe()
else
self:unsubscribe()
end
end
Various Wi-Fi QoL improvements (#6424) * Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback). * Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental. * Consistently use "Wi-Fi" everywhere. * On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery. (i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again). * Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic). * restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it). * We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed. * Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state. * Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing. * Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421 Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results). * For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
4 years ago
NetworkMgr:runWhenOnline(connect_callback)
end
}
}
}
end
function ZSync:initServerZyreMQ()
local ZyreMessageQueue = require("ui/message/zyremessagequeue")
if self.zyre_messagequeue == nil then
self.server_zyre = ZyreMessageQueue:new{
header = {["FILEMQ-SERVER"] = tostring(self.fmq_port)},
}
self.server_zyre:start()
self.zyre_messagequeue = UIManager:insertZMQ(self.server_zyre)
end
end
function ZSync:initClientZyreMQ()
local ZyreMessageQueue = require("ui/message/zyremessagequeue")
if self.zyre_messagequeue == nil then
self.client_zyre = ZyreMessageQueue:new{}
self.client_zyre:start()
self.zyre_messagequeue = UIManager:insertZMQ(self.client_zyre)
end
end
function ZSync:initServerFileMQ(outboxes)
local FileMessageQueue = require("ui/message/filemessagequeue")
local filemq = ffi.load("libs/libfmq.so.1")
if self.file_messagequeue == nil then
self.filemq_server = filemq.fmq_server_new()
self.file_messagequeue = UIManager:insertZMQ(FileMessageQueue:new{
server = self.filemq_server
})
self.fmq_port = filemq.fmq_server_bind(self.filemq_server, "tcp://*:*")
filemq.fmq_server_configure(self.filemq_server, self.server_config)
filemq.fmq_server_set_anonymous(self.filemq_server, true)
end
UIManager:scheduleIn(1, function()
for _, outbox in ipairs(outboxes) do
DEBUG("publish", outbox.path, outbox.alias)
filemq.fmq_server_publish(self.filemq_server, outbox.path, outbox.alias)
end
end)
end
function ZSync:initClientFileMQ(inbox)
local FileMessageQueue = require("ui/message/filemessagequeue")
local filemq = ffi.load("libs/libfmq.so.1")
if self.file_messagequeue == nil then
self.filemq_client = filemq.fmq_client_new()
self.file_messagequeue = UIManager:insertZMQ(FileMessageQueue:new{
client = self.filemq_client
})
filemq.fmq_client_configure(self.filemq_client, self.client_config)
end
UIManager:scheduleIn(1, function()
filemq.fmq_client_set_inbox(self.filemq_client, inbox)
end)
end
local function clearDirectory(dir, rmdir)
for f in lfs.dir(dir) do
local path = dir.."/"..f
local mode = lfs.attributes(path, "mode")
if mode == "file" then
C.remove(path)
elseif mode == "directory" and f ~= "." and f ~= ".." then
clearDirectory(path, true)
end
end
if rmdir then
C.rmdir(dir)
end
end
local function mklink(path, filename)
local basename = filename:match(".*/(.*)") or filename
local linkname = path .. "/" .. basename .. ".ln"
local linkfile = io.open(linkname, "w")
if linkfile then
linkfile:write(filename .. "\n")
linkfile:close()
end
end
-- add directory directly into outboxes
function ZSync:outboxesAddDirectory(outboxes, dir)
if lfs.attributes(dir, "mode") == "directory" then
local basename = dir:match(".*/(.*)") or dir
table.insert(outboxes, {
path = dir,
alias = "/"..basename,
})
end
end
-- link file in root outbox
function ZSync:outboxAddFileLink(filename)
local mode = lfs.attributes(filename, "mode")
if mode == "file" then
mklink(self.outbox, filename)
end
end
-- copy directory content into root outbox(no recursively)
function ZSync:outboxCopyDirectory(dir)
local basename = dir:match(".*/(.*)") or dir
local newdir = self.outbox.."/"..basename
lfs.mkdir(newdir)
if pcall(lfs.dir, dir) then
for f in lfs.dir(dir) do
local filename = dir.."/"..f
if lfs.attributes(filename, "mode") == "file" then
local newfile = newdir.."/"..f
ltn12.pump.all(
ltn12.source.file(assert(io.open(filename, "rb"))),
ltn12.sink.file(assert(io.open(newfile, "wb")))
)
end
end
end
end
function ZSync:publish()
DEBUG("publish document", self.view.document.file)
lfs.mkdir(self.outbox)
clearDirectory(self.outbox)
local file = self.view.document.file
local sidecar = file:match("(.*)%.")..".sdr"
self:outboxAddFileLink(file)
self:outboxCopyDirectory(sidecar)
local outboxes = {}
table.insert(outboxes, {
path = self.outbox,
alias = "/",
})
-- init filemq first to get filemq port
self:initServerFileMQ(outboxes)
self:initServerZyreMQ()
end
function ZSync:unpublish()
DEBUG("ZSync unpublish")
clearDirectory(self.outbox)
self:stopZyreMQ()
self:stopFileMQ()
end
function ZSync:onChooseInbox(inbox)
DEBUG("choose inbox", inbox)
self.inbox = inbox
-- init zyre first for filemq endpoint
self:initClientZyreMQ()
self:initClientFileMQ(inbox)
return true
end
function ZSync:subscribe()
DEBUG("subscribe documents")
self.received = {}
local zsync = self
require("ui/downloadmgr"):new{
show_hidden = G_reader_settings:isTrue("show_hidden"),
onConfirm = function(inbox)
G_reader_settings:saveSetting("inbox_dir", inbox)
zsync:onChooseInbox(inbox)
end,
}:chooseDir()
end
function ZSync:unsubscribe()
DEBUG("ZSync unsubscribe")
self.received = {}
self:stopFileMQ()
self:stopZyreMQ()
end
function ZSync:onZyreEnter(id, name, header, endpoint)
local filemq = ffi.load("libs/libfmq.so.1")
if header and endpoint and header["FILEMQ-SERVER"] then
self.server_zyre_endpoint = endpoint
local port = header["FILEMQ-SERVER"]
local host = endpoint:match("(.*:)") or "*:"
local fmq_server_endpoint = host..port
DEBUG("connect filemq server at", fmq_server_endpoint)
-- wait for filemq server setup befor connecting
UIManager:scheduleIn(2, function()
filemq.fmq_client_set_resync(self.filemq_client, true)
filemq.fmq_client_subscribe(self.filemq_client, "/")
filemq.fmq_client_connect(self.filemq_client, fmq_server_endpoint)
end)
end
return true
end
function ZSync:onFileDeliver(filename, fullname)
-- sometimes several FileDelever msgs are sent from filemq
if self.received[filename] then return end
UIManager:show(InfoMessage:new{
text = _("Received file:") .. "\n" .. filename,
timeout = 1,
})
self.received[filename] = true
end
--[[
-- We assume that ZSync is running in either server mode or client mode
-- but never both. The zyre_messagequeue may be a server_zyre or client_zyre.
-- And the file_messagequeue may be a filemq_server or filemq_client.
--]]
function ZSync:stopZyreMQ()
if self.zyre_messagequeue then
self.zyre_messagequeue:stop()
UIManager:removeZMQ(self.zyre_messagequeue)
self.zyre_messagequeue = nil
self.server_zyre = nil
self.client_zyre = nil
end
end
function ZSync:stopFileMQ()
if self.file_messagequeue then
self.file_messagequeue:stop()
UIManager:removeZMQ(self.file_messagequeue)
self.file_messagequeue = nil
self.filemq_server = nil
self.filemq_client = nil
end
end
function ZSync:onCloseReader()
self:stopZyreMQ()
self:stopFileMQ()
end
return ZSync