diff --git a/frontend/apps/cloudstorage/cloudstorage.lua b/frontend/apps/cloudstorage/cloudstorage.lua index 6d66b53d6..b17e96e5e 100644 --- a/frontend/apps/cloudstorage/cloudstorage.lua +++ b/frontend/apps/cloudstorage/cloudstorage.lua @@ -10,6 +10,7 @@ local Menu = require("ui/widget/menu") local UIManager = require("ui/uimanager") local WebDav = require("apps/cloudstorage/webdav") local lfs = require("libs/libkoreader-lfs") +local T = require("ffi/util").template local _ = require("gettext") local Screen = require("device").screen @@ -26,17 +27,24 @@ local CloudStorage = Menu:extend{ show_parent = nil, is_popout = false, is_borderless = true, + title = _("Cloud storage") } function CloudStorage:init() self.cs_settings = self:readSettings() - self.menu_select = nil - self.title = _("Cloud storage") self.show_parent = self - self.item_table = self:genItemTableFromRoot() + if self.item then + self.item_table = self:genItemTable(self.item) + self.choose_folder_mode = true + else + self.item_table = self:genItemTableFromRoot() + end self.width = Screen:getWidth() self.height = Screen:getHeight() Menu.init(self) + if self.item then + self.item_table[1].callback() + end end function CloudStorage:genItemTableFromRoot() @@ -57,6 +65,8 @@ function CloudStorage:genItemTableFromRoot() type = server.type, editable = true, url = server.url, + sync_source_folder = server.sync_source_folder, + sync_dest_folder = server.sync_dest_folder, callback = function() self.type = server.type self.password = server.password @@ -69,6 +79,31 @@ function CloudStorage:genItemTableFromRoot() return item_table end +function CloudStorage:genItemTable(item) + local item_table = {} + local added_servers = self.cs_settings:readSetting("cs_servers") or {} + for _, server in ipairs(added_servers) do + if server.name == item.text and server.password == item.password and server.type == item.type then + table.insert(item_table, { + text = server.name, + address = server.address, + username = server.username, + password = server.password, + type = server.type, + url = server.url, + callback = function() + self.type = server.type + self.password = server.password + self.address = server.address + self.username = server.username + self:openCloudServer(server.url) + end, + }) + end + end + return item_table +end + function CloudStorage:selectCloudType() local buttons = { { @@ -117,7 +152,7 @@ function CloudStorage:openCloudServer(url) NetworkMgr:promptWifiOn() return end - tbl = DropBox:run(url, self.password) + tbl = DropBox:run(url, self.password, self.choose_folder_mode) elseif self.type == "ftp" then if not NetworkMgr:isConnected() then NetworkMgr:promptWifiOn() @@ -157,6 +192,8 @@ function CloudStorage:onMenuSelect(item) item.callback() elseif item.type == "file" then self:downloadFile(item) + elseif item.type == "other" then + return true else table.insert(self.paths, { url = item.url, @@ -255,45 +292,254 @@ function CloudStorage:cloudFile(item, path) UIManager:show(self.download_dialog) end +function CloudStorage:updateSyncFolder(item, source, dest) + local cs_settings = self:readSettings() + local cs_servers = cs_settings:readSetting("cs_servers") or {} + for _, server in ipairs(cs_servers) do + if server.name == item.text and server.password == item.password and server.type == item.type then + if source then + server.sync_source_folder = source + end + if dest then + server.sync_dest_folder = dest + end + break + end + end + cs_settings:saveSetting("cs_servers", cs_servers) + cs_settings:flush() +end + function CloudStorage:onMenuHold(item) - if item.editable then - local cs_server_dialog - cs_server_dialog = ButtonDialog:new{ + if item.type == "folder_long_press" then + local title = T(_("Select this directory?\n\n%1"), item.url) + local onConfirm = self.onConfirm + local button_dialog + button_dialog = ButtonDialogTitle:new{ + title = title, buttons = { { { - text = _("Info"), - enabled = true, - callback = function() - UIManager:close(cs_server_dialog) - self:infoServer(item) - end - }, - { - text = _("Edit"), - enabled = true, + text = _("Cancel"), callback = function() - UIManager:close(cs_server_dialog) - self:editCloudServer(item) - - end + UIManager:close(button_dialog) + end, }, { - text = _("Delete"), - enabled = true, + text = _("Select"), callback = function() - UIManager:close(cs_server_dialog) - self:deleteCloudServer(item) - end + if onConfirm then + onConfirm(item.url) + end + UIManager:close(button_dialog) + UIManager:close(self) + end, }, }, - } + }, + } + UIManager:show(button_dialog) + end + if item.editable then + local cs_server_dialog + local buttons = { + { + { + text = _("Info"), + enabled = true, + callback = function() + UIManager:close(cs_server_dialog) + self:infoServer(item) + end + }, + { + text = _("Edit"), + enabled = true, + callback = function() + UIManager:close(cs_server_dialog) + self:editCloudServer(item) + + end + }, + { + text = _("Delete"), + enabled = true, + callback = function() + UIManager:close(cs_server_dialog) + self:deleteCloudServer(item) + end + }, + }, + } + if item.type == "dropbox" then + table.insert(buttons, { + { + text = _("Synchronize now"), + enabled = item.sync_source_folder ~= nil and item.sync_dest_folder ~= nil, + callback = function() + UIManager:close(cs_server_dialog) + self:synchronizeCloud(item) + end + }, + { + text = _("Synchronize settings"), + enabled = true, + callback = function() + UIManager:close(cs_server_dialog) + self:synchronizeSettings(item) + end + }, + }) + end + cs_server_dialog = ButtonDialog:new{ + buttons = buttons } UIManager:show(cs_server_dialog) return true end end +function CloudStorage:synchronizeCloud(item) + local Trapper = require("ui/trapper") + Trapper:wrap(function() + Trapper:setPausedText("Download paused.\nDo you want to continue or abort downloading files?") + local ok, downloaded_files, failed_files = pcall(self.downloadListFiles, self, item) + if ok and downloaded_files then + if not failed_files then failed_files = 0 end + local text + if downloaded_files == 0 and failed_files == 0 then + text = _("No files to download from dropbox.") + elseif downloaded_files > 0 and failed_files == 0 then + text = T(_("Successfuly downloaded %1 files from Dropbox to local storage."), downloaded_files) + else + text = T(_("Successfuly downloaded %1 files from Dropbox to local storage.\nFailed downloaded %2 files."), + downloaded_files, failed_files) + end + UIManager:show(InfoMessage:new{ + text = text, + timeout = 3, + }) + else + Trapper:reset() -- close any last widget not cleaned if error + UIManager:show(InfoMessage:new{ + text = _("No files to download from Dropbox.\nPlease check your configuration and connection."), + timeout = 3, + }) + end + end) +end + +function CloudStorage:downloadListFiles(item) + local local_files = {} + local path = item.sync_dest_folder + local UI = require("ui/trapper") + UI:info(_("Retrieving files…")) + + local ok, iter, dir_obj = pcall(lfs.dir, path) + if ok then + for f in iter, dir_obj do + local filename = path .."/" .. f + local attributes = lfs.attributes(filename) + if attributes.mode == "file" then + local_files[f] = attributes.size + end + end + end + local remote_files = DropBox:showFiles(item.sync_source_folder, item.password) + if #remote_files == 0 then + UI:clear() + return false + end + local files_to_download = 0 + for i, file in ipairs(remote_files) do + if not local_files[file.text] or local_files[file.text] ~= file.size then + files_to_download = files_to_download + 1 + remote_files[i].download = true + end + end + + if files_to_download == 0 then + UI:clear() + return 0 + end + + local response, go_on + local proccessed_files = 0 + local success_files = 0 + local unsuccess_files = 0 + for _, file in ipairs(remote_files) do + if file.download then + proccessed_files = proccessed_files + 1 + print(file.url) + local text = string.format("Downloading file (%d/%d):\n%s", proccessed_files, files_to_download, file.text) + go_on = UI:info(text) + if not go_on then + break + end + response = DropBox:downloadFileNoUI(file.url, item.password, item.sync_dest_folder .. "/" .. file.text) + if response then + success_files = success_files + 1 + else + unsuccess_files = unsuccess_files + 1 + end + end + end + UI:clear() + return success_files, unsuccess_files +end + +function CloudStorage:synchronizeSettings(item) + local syn_dialog + local dropbox_sync_folder = item.sync_source_folder or "not set" + local local_sync_folder = item.sync_dest_folder or "not set" + syn_dialog = ButtonDialogTitle:new { + title = T(_("Dropbox folder:\n%1\nLocal folder:\n%2"), dropbox_sync_folder, local_sync_folder), + title_align = "center", + buttons = { + { + { + text = _("Choose dropbox folder"), + callback = function() + UIManager:close(syn_dialog) + require("ui/cloudmgr"):new{ + item = item, + onConfirm = function(path) + self:updateSyncFolder(item, path) + item.sync_source_folder = path + self:synchronizeSettings(item) + end, + }:chooseDir() + end, + }, + }, + { + { + text = _("Choose local folder"), + callback = function() + UIManager:close(syn_dialog) + require("ui/downloadmgr"):new{ + onConfirm = function(path) + self:updateSyncFolder(item, nil, path) + item.sync_dest_folder = path + self:synchronizeSettings(item) + end, + }:chooseDir() + end, + }, + }, + { + { + text = _("Close"), + callback = function() + UIManager:close(syn_dialog) + end, + }, + }, + } + } + UIManager:show(syn_dialog) +end + function CloudStorage:configCloud(type) local callbackAdd = function(fields) local cs_settings = self:readSettings() diff --git a/frontend/apps/cloudstorage/dropbox.lua b/frontend/apps/cloudstorage/dropbox.lua index 60e5ed4b4..2bc304d62 100644 --- a/frontend/apps/cloudstorage/dropbox.lua +++ b/frontend/apps/cloudstorage/dropbox.lua @@ -10,8 +10,12 @@ local _ = require("gettext") local DropBox = {} -function DropBox:run(url, password) - return DropBoxApi:listFolder(url, password) +function DropBox:run(url, password, choose_folder_mode) + return DropBoxApi:listFolder(url, password, choose_folder_mode) +end + +function DropBox:showFiles(url, password) + return DropBoxApi:showFiles(url, password) end function DropBox:downloadFile(item, password, path, close) @@ -39,6 +43,15 @@ function DropBox:downloadFile(item, password, path, close) end end +function DropBox:downloadFileNoUI(url, password, path) + local code_response = DropBoxApi:downloadFile(url, password, path) + if code_response == 200 then + return true + else + return false + end +end + function DropBox:config(item, callback) local text_info = "How to generate Access Token:\n".. "1. Open the following URL in your Browser, and log in using your account: https://www.dropbox.com/developers/apps.\n".. diff --git a/frontend/apps/cloudstorage/dropboxapi.lua b/frontend/apps/cloudstorage/dropboxapi.lua index d7bcfc382..480584044 100644 --- a/frontend/apps/cloudstorage/dropboxapi.lua +++ b/frontend/apps/cloudstorage/dropboxapi.lua @@ -90,7 +90,9 @@ function DropBoxApi:downloadFile(path, token, local_path) return code_return end -function DropBoxApi:listFolder(path, token) +-- folder_mode - set to true when we want to see only folder. +-- We see also extra folder "Long-press to select current directory" at the beginning. +function DropBoxApi:listFolder(path, token, folder_mode) local dropbox_list = {} local dropbox_file = {} local tag, text @@ -101,6 +103,7 @@ function DropBoxApi:listFolder(path, token) tag = files[".tag"] if tag == "folder" then text = text .. "/" + if folder_mode then tag = "folder_long_press" end table.insert(dropbox_list, { text = text, url = files.path_display, @@ -108,7 +111,7 @@ function DropBoxApi:listFolder(path, token) }) --show only file with supported formats elseif tag == "file" and (DocumentRegistry:hasProvider(text) - or G_reader_settings:isTrue("show_unsupported")) then + or G_reader_settings:isTrue("show_unsupported")) and not folder_mode then table.insert(dropbox_file, { text = text, url = files.path_display, @@ -123,6 +126,14 @@ function DropBoxApi:listFolder(path, token) table.sort(dropbox_file, function(v1,v2) return v1.text < v2.text end) + -- Add special folder. + if folder_mode then + table.insert(dropbox_list, 1, { + text = _("Long-press to select current directory"), + url = path, + type = "folder_long_press", + }) + end for _, files in ipairs(dropbox_file) do table.insert(dropbox_list, { text = files.text, @@ -133,4 +144,23 @@ function DropBoxApi:listFolder(path, token) return dropbox_list end +function DropBoxApi:showFiles(path, token) + local dropbox_files = {} + local tag, text + local ls_dropbox = self:fetchListFolders(path, token) + if ls_dropbox == nil or ls_dropbox.entries == nil then return false end + for _, files in ipairs(ls_dropbox.entries) do + text = files.name + tag = files[".tag"] + if tag == "file" and (DocumentRegistry:hasProvider(text) or G_reader_settings:isTrue("show_unsupported")) then + table.insert(dropbox_files, { + text = text, + url = files.path_display, + size = files.size, + }) + end + end + return dropbox_files +end + return DropBoxApi diff --git a/frontend/ui/cloudmgr.lua b/frontend/ui/cloudmgr.lua new file mode 100644 index 000000000..5269762cd --- /dev/null +++ b/frontend/ui/cloudmgr.lua @@ -0,0 +1,29 @@ +local CloudStorage = require("apps/cloudstorage/cloudstorage") +local UIManager = require("ui/uimanager") +local _ = require("gettext") + +local CloudMgr = { + onConfirm = function() end, +} + +function CloudMgr:new(from_o) + local o = from_o or {} + setmetatable(o, self) + self.__index = self + return o +end + +--- Displays a PathChooser for cloud drive for picking a (source) directory. +-- @treturn string path chosen by the user +function CloudMgr:chooseDir() + local cloud_storage = CloudStorage:new{ + title = _("Long-press to select directory"), + item = self.item, + onConfirm = function(dir_path) + self.onConfirm(dir_path) + end, + } + UIManager:show(cloud_storage) +end + +return CloudMgr