mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Cloud storage: add Dropbox short-lived tokens (#9496)
This commit is contained in:
@@ -11,6 +11,7 @@ local InfoMessage = require("ui/widget/infomessage")
|
||||
local InputDialog = require("ui/widget/inputdialog")
|
||||
local LuaSettings = require("luasettings")
|
||||
local Menu = require("ui/widget/menu")
|
||||
local NetworkMgr = require("ui/network/manager")
|
||||
local PathChooser = require("ui/widget/pathchooser")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local WebDav = require("apps/cloudstorage/webdav")
|
||||
@@ -139,14 +140,30 @@ function CloudStorage:selectCloudType()
|
||||
return true
|
||||
end
|
||||
|
||||
function CloudStorage:generateDropBoxAccessToken()
|
||||
if self.username or self.address == nil or self.address == "" then
|
||||
-- short-lived token has been generated already in this session
|
||||
-- or we have long-lived token in self.password
|
||||
return true
|
||||
else
|
||||
local token = DropBox:getAccessToken(self.password, self.address)
|
||||
if token then
|
||||
self.password = token -- short-lived token
|
||||
self.username = true -- flag
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CloudStorage:openCloudServer(url)
|
||||
local tbl, e
|
||||
local NetworkMgr = require("ui/network/manager")
|
||||
if self.type == "dropbox" then
|
||||
if NetworkMgr:willRerunWhenOnline(function() self:openCloudServer(url) end) then
|
||||
return
|
||||
end
|
||||
tbl, e = DropBox:run(url, self.password, self.choose_folder_mode)
|
||||
if self:generateDropBoxAccessToken() then
|
||||
tbl, e = DropBox:run(url, self.password, self.choose_folder_mode)
|
||||
end
|
||||
elseif self.type == "ftp" then
|
||||
if NetworkMgr:willRerunWhenConnected(function() self:openCloudServer(url) end) then
|
||||
return
|
||||
@@ -423,31 +440,38 @@ function CloudStorage:onMenuHold(item)
|
||||
end
|
||||
|
||||
function CloudStorage:synchronizeCloud(item)
|
||||
if NetworkMgr:willRerunWhenOnline(function() self:synchronizeCloud(item) end) then
|
||||
return
|
||||
end
|
||||
self.password = item.password
|
||||
self.address = item.address
|
||||
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.")
|
||||
else
|
||||
text = T(N_("Successfully downloaded 1 file from Dropbox to local storage.", "Successfully downloaded %1 files from Dropbox to local storage.", downloaded_files), downloaded_files)
|
||||
if failed_files > 0 then
|
||||
text = text .. "\n" .. T(N_("Failed to download 1 file.", "Failed to download %1 files.", failed_files), failed_files)
|
||||
if self:generateDropBoxAccessToken() then
|
||||
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.")
|
||||
else
|
||||
text = T(N_("Successfully downloaded 1 file from Dropbox to local storage.", "Successfully downloaded %1 files from Dropbox to local storage.", downloaded_files), downloaded_files)
|
||||
if failed_files > 0 then
|
||||
text = text .. "\n" .. T(N_("Failed to download 1 file.", "Failed to download %1 files.", failed_files), failed_files)
|
||||
end
|
||||
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
|
||||
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
|
||||
@@ -468,7 +492,7 @@ function CloudStorage:downloadListFiles(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
local remote_files = DropBox:showFiles(item.sync_source_folder, item.password)
|
||||
local remote_files = DropBox:showFiles(item.sync_source_folder, self.password)
|
||||
if #remote_files == 0 then
|
||||
UI:clear()
|
||||
return false
|
||||
@@ -499,7 +523,7 @@ function CloudStorage:downloadListFiles(item)
|
||||
if not go_on then
|
||||
break
|
||||
end
|
||||
response = DropBox:downloadFileNoUI(file.url, item.password, item.sync_dest_folder .. "/" .. file.text)
|
||||
response = DropBox:downloadFileNoUI(file.url, self.password, item.sync_dest_folder .. "/" .. file.text)
|
||||
if response then
|
||||
success_files = success_files + 1
|
||||
else
|
||||
@@ -686,6 +710,7 @@ function CloudStorage:configCloud(type)
|
||||
table.insert(cs_servers,{
|
||||
name = fields[1],
|
||||
password = fields[2],
|
||||
address = fields[3],
|
||||
type = "dropbox",
|
||||
url = "/"
|
||||
})
|
||||
@@ -732,6 +757,7 @@ function CloudStorage:editCloudServer(item)
|
||||
if server.name == updated_config.text and server.password == updated_config.password then
|
||||
server.name = fields[1]
|
||||
server.password = fields[2]
|
||||
server.address = fields[3]
|
||||
cs_servers[i] = server
|
||||
break
|
||||
end
|
||||
@@ -790,7 +816,15 @@ end
|
||||
|
||||
function CloudStorage:infoServer(item)
|
||||
if item.type == "dropbox" then
|
||||
DropBox:info(item.password)
|
||||
if NetworkMgr:willRerunWhenOnline(function() self:infoServer(item) end) then
|
||||
return
|
||||
end
|
||||
self.password = item.password
|
||||
self.address = item.address
|
||||
if self:generateDropBoxAccessToken() then
|
||||
DropBox:info(self.password)
|
||||
self.username = nil
|
||||
end
|
||||
elseif item.type == "ftp" then
|
||||
Ftp:info(item)
|
||||
elseif item.type == "webdav" then
|
||||
|
||||
@@ -12,6 +12,10 @@ local _ = require("gettext")
|
||||
|
||||
local DropBox = {}
|
||||
|
||||
function DropBox:getAccessToken(refresh_token, app_key_colon_secret)
|
||||
return DropBoxApi:getAccessToken(refresh_token, app_key_colon_secret)
|
||||
end
|
||||
|
||||
function DropBox:run(url, password, choose_folder_mode)
|
||||
return DropBoxApi:listFolder(url, password, choose_folder_mode)
|
||||
end
|
||||
@@ -92,39 +96,31 @@ function DropBox:createFolder(url, password, folder_name, callback_close)
|
||||
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"..
|
||||
"2. Click on >>Create App<<, then select >>Dropbox API app<<.\n"..
|
||||
"3. Now go on with the configuration, choosing the app permissions and access restrictions to your DropBox folder.\n"..
|
||||
"4. Enter the >>App Name<< that you prefer (e.g. KOReader).\n"..
|
||||
"5. Now, click on the >>Create App<< button.\n" ..
|
||||
"6. When your new App is successfully created, please click on the Generate button.\n"..
|
||||
"7. Under the 'Generated access token' section, then enter code in Dropbox token field."
|
||||
local hint_top = _("Your Dropbox name")
|
||||
local text_top = ""
|
||||
local hint_bottom = _("Dropbox token\n\n\n\n")
|
||||
local text_bottom = ""
|
||||
local title
|
||||
local text_button_right = _("Add")
|
||||
local text_info = _([[
|
||||
Dropbox access tokens are short-lived (4 hours).
|
||||
To generate new access token please use Dropbox refresh token and <APP_KEY>:<APP_SECRET> Base64 encoded string.
|
||||
|
||||
Some of the previously generated long-lived tokens are still valid.]])
|
||||
local text_name, text_token, text_appkey
|
||||
if item then
|
||||
title = _("Edit Dropbox account")
|
||||
text_button_right = _("Apply")
|
||||
text_top = item.text
|
||||
text_bottom = item.password
|
||||
else
|
||||
title = _("Add Dropbox account")
|
||||
text_name = item.text
|
||||
text_token = item.password
|
||||
text_appkey = item.address
|
||||
end
|
||||
self.settings_dialog = MultiInputDialog:new {
|
||||
title = title,
|
||||
title = _("Dropbox cloud storage"),
|
||||
fields = {
|
||||
{
|
||||
text = text_top,
|
||||
hint = hint_top ,
|
||||
text = text_name,
|
||||
hint = _("Cloud storage displayed name"),
|
||||
},
|
||||
{
|
||||
text = text_bottom,
|
||||
hint = hint_bottom,
|
||||
scroll = false,
|
||||
text = text_token,
|
||||
hint = _("Dropbox refresh token\nor long-lived token (deprecated)"),
|
||||
},
|
||||
{
|
||||
text = text_appkey,
|
||||
hint = _("Dropbox <APP_KEY>:<APP_SECRET>\n(leave blank for long-lived token)"),
|
||||
},
|
||||
},
|
||||
buttons = {
|
||||
@@ -144,29 +140,20 @@ function DropBox:config(item, callback)
|
||||
end
|
||||
},
|
||||
{
|
||||
text = text_button_right,
|
||||
text = _("Save"),
|
||||
callback = function()
|
||||
local fields = MultiInputDialog:getFields()
|
||||
if fields[1] ~= "" and fields[2] ~= "" then
|
||||
if item then
|
||||
--edit
|
||||
callback(item, fields)
|
||||
else
|
||||
-- add new
|
||||
callback(fields)
|
||||
end
|
||||
self.settings_dialog:onClose()
|
||||
UIManager:close(self.settings_dialog)
|
||||
if item then
|
||||
callback(item, fields)
|
||||
else
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Please fill in all fields.")
|
||||
})
|
||||
callback(fields)
|
||||
end
|
||||
self.settings_dialog:onClose()
|
||||
UIManager:close(self.settings_dialog)
|
||||
end
|
||||
},
|
||||
},
|
||||
},
|
||||
input_type = "text",
|
||||
}
|
||||
UIManager:show(self.settings_dialog)
|
||||
self.settings_dialog:onShowKeyboard()
|
||||
@@ -174,14 +161,17 @@ end
|
||||
|
||||
function DropBox:info(token)
|
||||
local info = DropBoxApi:fetchInfo(token)
|
||||
local info_text
|
||||
if info and info.name then
|
||||
info_text = T(_"Type: %1\nName: %2\nEmail: %3\nCountry: %4",
|
||||
"Dropbox",info.name.display_name, info.email, info.country)
|
||||
else
|
||||
info_text = _("No information available")
|
||||
local space_usage = DropBoxApi:fetchInfo(token, true)
|
||||
if info and space_usage then
|
||||
local account_type = info.account_type and info.account_type[".tag"]
|
||||
local name = info.name and info.name.display_name
|
||||
local space_total = space_usage.allocation and space_usage.allocation.allocated
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_"Type: %1\nName: %2\nEmail: %3\nCountry: %4\nSpace total: %5\nSpace used: %6",
|
||||
account_type, name, info.email, info.country,
|
||||
util.getFriendlySize(space_total), util.getFriendlySize(space_usage.used)),
|
||||
})
|
||||
end
|
||||
UIManager:show(InfoMessage:new{text = info_text})
|
||||
end
|
||||
|
||||
return DropBox
|
||||
|
||||
@@ -13,36 +13,64 @@ local _ = require("gettext")
|
||||
local DropBoxApi = {
|
||||
}
|
||||
|
||||
local API_TOKEN = "https://api.dropbox.com/oauth2/token"
|
||||
local API_URL_INFO = "https://api.dropboxapi.com/2/users/get_current_account"
|
||||
local API_GET_SPACE_USAGE = "https://api.dropboxapi.com/2/users/get_space_usage"
|
||||
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)
|
||||
function DropBoxApi:getAccessToken(refresh_token, app_key_colon_secret)
|
||||
local sink = {}
|
||||
socketutil:set_timeout()
|
||||
local data = "grant_type=refresh_token&refresh_token=" .. refresh_token
|
||||
local request = {
|
||||
url = API_URL_INFO,
|
||||
url = API_TOKEN,
|
||||
method = "POST",
|
||||
headers = {
|
||||
["Authorization"] = "Basic " .. require("ffi/sha2").bin_to_base64(app_key_colon_secret),
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
["Content-Length"] = string.len(data),
|
||||
},
|
||||
source = ltn12.source.string(data),
|
||||
sink = ltn12.sink.table(sink),
|
||||
}
|
||||
socketutil:set_timeout()
|
||||
local code = socket.skip(1, http.request(request))
|
||||
socketutil:reset_timeout()
|
||||
if code == 200 then
|
||||
local headers = table.concat(sink)
|
||||
if headers ~= "" then
|
||||
local _, result = pcall(JSON.decode, headers)
|
||||
return result["access_token"]
|
||||
end
|
||||
end
|
||||
logger.info("Dropbox: cannot get access token")
|
||||
end
|
||||
|
||||
function DropBoxApi:fetchInfo(token, space_usage)
|
||||
local url = space_usage and API_GET_SPACE_USAGE or API_URL_INFO
|
||||
local sink = {}
|
||||
local request = {
|
||||
url = url,
|
||||
method = "POST",
|
||||
headers = {
|
||||
["Authorization"] = "Bearer " .. token,
|
||||
},
|
||||
sink = ltn12.sink.table(sink),
|
||||
}
|
||||
local headers_request = socket.skip(1, http.request(request))
|
||||
socketutil:set_timeout()
|
||||
local code = socket.skip(1, http.request(request))
|
||||
socketutil:reset_timeout()
|
||||
local result_response = table.concat(sink)
|
||||
if headers_request == nil then
|
||||
return nil
|
||||
end
|
||||
if result_response ~= "" then
|
||||
local _, result = pcall(JSON.decode, result_response)
|
||||
return result
|
||||
else
|
||||
return nil
|
||||
if code == 200 then
|
||||
local headers = table.concat(sink)
|
||||
if headers ~= "" then
|
||||
local _, result = pcall(JSON.decode, headers)
|
||||
return result
|
||||
end
|
||||
end
|
||||
logger.info("Dropbox: cannot get account info")
|
||||
end
|
||||
|
||||
function DropBoxApi:fetchListFolders(path, token)
|
||||
|
||||
Reference in New Issue
Block a user