diff --git a/frontend/apps/cloudstorage/cloudstorage.lua b/frontend/apps/cloudstorage/cloudstorage.lua index 2eaecba59..9ab959207 100644 --- a/frontend/apps/cloudstorage/cloudstorage.lua +++ b/frontend/apps/cloudstorage/cloudstorage.lua @@ -1,14 +1,17 @@ local BD = require("ui/bidi") local ButtonDialog = require("ui/widget/buttondialog") local ButtonDialogTitle = require("ui/widget/buttondialogtitle") +local CheckButton = require("ui/widget/checkbutton") local ConfirmBox = require("ui/widget/confirmbox") local DataStorage = require("datastorage") local DropBox = require("apps/cloudstorage/dropbox") local FFIUtil = require("ffi/util") local Ftp = require("apps/cloudstorage/ftp") local InfoMessage = require("ui/widget/infomessage") +local InputDialog = require("ui/widget/inputdialog") local LuaSettings = require("luasettings") local Menu = require("ui/widget/menu") +local PathChooser = require("ui/widget/pathchooser") local UIManager = require("ui/uimanager") local WebDav = require("apps/cloudstorage/webdav") local lfs = require("libs/libkoreader-lfs") @@ -155,14 +158,20 @@ function CloudStorage:openCloudServer(url) end tbl, e = WebDav:run(self.address, self.username, self.password, url) end - if tbl and #tbl > 0 then + if tbl then self:switchItemTable(url, tbl) - self:setTitleBarIconAndText("home") - self.onExtraButtonTap = function() - self:init() + if self.type == "dropbox" then + self.onExtraButtonTap = function() + self:showPlusMenu(url) + end + else + self:setTitleBarIconAndText("home") + self.onExtraButtonTap = function() + self:init() + end end return true - elseif not tbl then + else logger.err("CloudStorage:", e) UIManager:show(InfoMessage:new{ text = _("Cannot fetch list of folder contents\nPlease check your configuration or network connection."), @@ -170,9 +179,6 @@ function CloudStorage:openCloudServer(url) }) table.remove(self.paths) return false - else - UIManager:show(InfoMessage:new{ text = _("Empty folder") }) - return false end end @@ -371,7 +377,6 @@ function CloudStorage:onMenuHold(item) { { text = _("Info"), - enabled = true, callback = function() UIManager:close(cs_server_dialog) self:infoServer(item) @@ -379,7 +384,6 @@ function CloudStorage:onMenuHold(item) }, { text = _("Edit"), - enabled = true, callback = function() UIManager:close(cs_server_dialog) self:editCloudServer(item) @@ -388,7 +392,6 @@ function CloudStorage:onMenuHold(item) }, { text = _("Delete"), - enabled = true, callback = function() UIManager:close(cs_server_dialog) self:deleteCloudServer(item) @@ -408,7 +411,6 @@ function CloudStorage:onMenuHold(item) }, { text = _("Synchronize settings"), - enabled = true, callback = function() UIManager:close(cs_server_dialog) self:synchronizeSettings(item) @@ -565,6 +567,119 @@ function CloudStorage:synchronizeSettings(item) UIManager:show(syn_dialog) end +function CloudStorage:showPlusMenu(url) + local button_dialog + button_dialog = ButtonDialog:new{ + buttons = { + { + { + text = _("Upload file"), + callback = function() + UIManager:close(button_dialog) + self:uploadFile(url) + end, + }, + }, + { + { + text = _("New folder"), + callback = function() + UIManager:close(button_dialog) + self:createFolder(url) + end, + }, + }, + {}, + { + { + text = _("Return to cloud storage list"), + callback = function() + UIManager:close(button_dialog) + self:init() + end, + }, + }, + }, + } + UIManager:show(button_dialog) +end + +function CloudStorage:uploadFile(url) + local path_chooser + path_chooser = PathChooser:new{ + select_directory = false, + detailed_file_info = true, + path = self.last_path, + onConfirm = function(file_path) + self.last_path = file_path:match("(.*)/") + if self.last_path == "" then self.last_path = "/" end + if lfs.attributes(file_path, "size") > 157286400 then + UIManager:show(InfoMessage:new{ + text = _("File size must be less than 150 MB."), + }) + else + local callback_close = function() + self:openCloudServer(url) + end + UIManager:nextTick(function() + UIManager:show(InfoMessage:new{ + text = _("Uploading…"), + timeout = 1, + }) + end) + UIManager:tickAfterNext(function() + DropBox:uploadFile(url, self.password, file_path, callback_close) + end) + end + end + } + UIManager:show(path_chooser) +end + +function CloudStorage:createFolder(url) + local input_dialog, check_button_enter_folder + input_dialog = InputDialog:new{ + title = _("New folder"), + buttons = { + { + { + text = _("Cancel"), + callback = function() + UIManager:close(input_dialog) + end, + }, + { + text = _("Create"), + is_enter_default = true, + callback = function() + local folder_name = input_dialog:getInputText() + if folder_name == "" then return end + UIManager:close(input_dialog) + local callback_close = function() + if check_button_enter_folder.checked then + table.insert(self.paths, { + url = url, + }) + url = url .. "/" .. folder_name + end + self:openCloudServer(url) + end + DropBox:createFolder(url, self.password, folder_name, callback_close) + end, + }, + } + }, + } + check_button_enter_folder = CheckButton:new{ + text = _("Enter folder after creation"), + checked = false, + parent = input_dialog, + } + input_dialog:addWidget(check_button_enter_folder) + UIManager:show(input_dialog) + input_dialog:onShowKeyboard() +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 b085a241e..79c2134d9 100644 --- a/frontend/apps/cloudstorage/dropbox.lua +++ b/frontend/apps/cloudstorage/dropbox.lua @@ -61,6 +61,36 @@ function DropBox:downloadFileNoUI(url, password, path) end end +function DropBox:uploadFile(url, password, file_path, callback_close) + local code_response = DropBoxApi:uploadFile(url, password, file_path) + local __, filename = util.splitFilePathName(file_path) + if code_response == 200 then + UIManager:show(InfoMessage:new{ + text = T(_("File uploaded:\n%1"), filename), + }) + if callback_close then + callback_close() + end + else + UIManager:show(InfoMessage:new{ + text = T(_("Could not upload file:\n%1"), filename), + }) + end +end + +function DropBox:createFolder(url, password, folder_name, callback_close) + local code_response = DropBoxApi:createFolder(url, password, folder_name) + if code_response == 200 then + if callback_close then + callback_close() + end + else + UIManager:show(InfoMessage:new{ + text = T(_("Could not create folder:\n%1"), folder_name), + }) + 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 f338849e7..e387a348f 100644 --- a/frontend/apps/cloudstorage/dropboxapi.lua +++ b/frontend/apps/cloudstorage/dropboxapi.lua @@ -1,10 +1,13 @@ local DocumentRegistry = require("document/documentregistry") local JSON = require("json") local http = require("socket.http") +local lfs = require("libs/libkoreader-lfs") local logger = require("logger") local ltn12 = require("ltn12") local socket = require("socket") local socketutil = require("socketutil") +local util = require("util") +local BaseUtil = require("ffi/util") local _ = require("gettext") local DropBoxApi = { @@ -13,6 +16,8 @@ local DropBoxApi = { local API_URL_INFO = "https://api.dropboxapi.com/2/users/get_current_account" local API_LIST_FOLDER = "https://api.dropboxapi.com/2/files/list_folder" local API_DOWNLOAD_FILE = "https://content.dropboxapi.com/2/files/download" +local API_UPLOAD_FILE = "https://content.dropboxapi.com/2/files/upload" +local API_CREATE_FOLDER = "https://api.dropboxapi.com/2/files/create_folder_v2" local API_LIST_ADD_FOLDER = "https://api.dropboxapi.com/2/files/list_folder/continue" function DropBoxApi:fetchInfo(token) @@ -100,6 +105,48 @@ function DropBoxApi:downloadFile(path, token, local_path) return code end +function DropBoxApi:uploadFile(path, token, file_path) + local data = "{\"path\": \"" .. path .. "/" .. BaseUtil.basename(file_path) .. + "\",\"mode\": \"add\",\"autorename\": true,\"mute\": false,\"strict_conflict\": false}" + socketutil:set_timeout(socketutil.FILE_BLOCK_TIMEOUT, socketutil.FILE_TOTAL_TIMEOUT) + local code, _, status = socket.skip(1, http.request{ + url = API_UPLOAD_FILE, + method = "POST", + headers = { + ["Authorization"] = "Bearer ".. token, + ["Dropbox-API-Arg"] = data, + ["Content-Type"] = "application/octet-stream", + ["Content-Length"] = lfs.attributes(file_path, "size"), + }, + source = ltn12.source.file(io.open(file_path, "r")), + }) + socketutil:reset_timeout() + if code ~= 200 then + logger.warn("DropBoxApi: Upload failure:", status or code or "network unreachable") + end + return code +end + +function DropBoxApi:createFolder(path, token, folder_name) + local data = "{\"path\": \"" .. path .. "/" .. folder_name .. "\",\"autorename\": false}" + socketutil:set_timeout() + local code, _, status = socket.skip(1, http.request{ + url = API_CREATE_FOLDER, + method = "POST", + headers = { + ["Authorization"] = "Bearer ".. token, + ["Content-Type"] = "application/json", + ["Content-Length"] = #data, + }, + source = ltn12.source.string(data), + }) + socketutil:reset_timeout() + if code ~= 200 then + logger.warn("DropBoxApi: Folder creation failure:", status or code or "network unreachable") + end + return code +end + -- 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) @@ -124,6 +171,7 @@ function DropBoxApi:listFolder(path, token, folder_mode) or G_reader_settings:isTrue("show_unsupported")) and not folder_mode then table.insert(dropbox_file, { text = text, + mandatory = util.getFriendlySize(files.size), url = files.path_display, type = tag, }) @@ -147,6 +195,7 @@ function DropBoxApi:listFolder(path, token, folder_mode) for _, files in ipairs(dropbox_file) do table.insert(dropbox_list, { text = files.text, + mandatory = files.mandatory, url = files.url, type = files.type, })