2019-12-06 22:55:39 +01:00
local BD = require ( " ui/bidi " )
2016-01-03 00:47:01 -08:00
local Blitbuffer = require ( " ffi/blitbuffer " )
2018-01-01 16:31:39 +01:00
local Button = require ( " ui/widget/button " )
2017-04-12 20:42:28 +02:00
local ButtonDialogTitle = require ( " ui/widget/buttondialogtitle " )
local CenterContainer = require ( " ui/widget/container/centercontainer " )
2018-01-02 16:21:00 +01:00
local ConfirmBox = require ( " ui/widget/confirmbox " )
2017-04-12 20:42:28 +02:00
local Device = require ( " device " )
2020-07-12 14:47:49 -04:00
local DeviceListener = require ( " device/devicelistener " )
2016-06-03 22:05:14 -07:00
local DocSettings = require ( " docsettings " )
2017-04-12 20:42:28 +02:00
local DocumentRegistry = require ( " document/documentregistry " )
2013-10-18 22:38:07 +02:00
local Event = require ( " ui/event " )
2017-04-12 20:42:28 +02:00
local FileChooser = require ( " ui/widget/filechooser " )
2017-07-01 12:11:44 +02:00
local FileManagerBookInfo = require ( " apps/filemanager/filemanagerbookinfo " )
2019-11-06 00:17:28 +01:00
local FileManagerCollection = require ( " apps/filemanager/filemanagercollection " )
2017-04-12 20:42:28 +02:00
local FileManagerConverter = require ( " apps/filemanager/filemanagerconverter " )
2019-03-03 12:43:09 +01:00
local FileManagerFileSearcher = require ( " apps/filemanager/filemanagerfilesearcher " )
2017-04-12 20:42:28 +02:00
local FileManagerHistory = require ( " apps/filemanager/filemanagerhistory " )
local FileManagerMenu = require ( " apps/filemanager/filemanagermenu " )
2018-08-22 22:34:20 +02:00
local FileManagerShortcuts = require ( " apps/filemanager/filemanagershortcuts " )
2016-01-03 00:47:01 -08:00
local Font = require ( " ui/font " )
2017-04-12 20:42:28 +02:00
local FrameContainer = require ( " ui/widget/container/framecontainer " )
2018-01-01 16:31:39 +01:00
local HorizontalGroup = require ( " ui/widget/horizontalgroup " )
2017-09-27 18:15:11 +02:00
local IconButton = require ( " ui/widget/iconbutton " )
2016-10-30 23:24:50 +01:00
local InfoMessage = require ( " ui/widget/infomessage " )
2017-04-12 20:42:28 +02:00
local InputContainer = require ( " ui/widget/container/inputcontainer " )
local InputDialog = require ( " ui/widget/inputdialog " )
2018-07-28 20:30:39 +02:00
local MultiConfirmBox = require ( " ui/widget/multiconfirmbox " )
2016-12-19 23:19:54 -08:00
local PluginLoader = require ( " pluginloader " )
2019-11-06 00:17:28 +01:00
local ReadCollection = require ( " readcollection " )
2018-08-11 22:47:33 +02:00
local ReaderDeviceStatus = require ( " apps/reader/modules/readerdevicestatus " )
2017-01-02 10:12:34 -08:00
local ReaderDictionary = require ( " apps/reader/modules/readerdictionary " )
2017-05-12 18:28:42 +02:00
local ReaderWikipedia = require ( " apps/reader/modules/readerwikipedia " )
2017-04-12 20:42:28 +02:00
local Screenshoter = require ( " ui/widget/screenshoter " )
2017-09-27 18:15:11 +02:00
local Size = require ( " ui/size " )
2017-04-12 20:42:28 +02:00
local TextWidget = require ( " ui/widget/textwidget " )
local VerticalGroup = require ( " ui/widget/verticalgroup " )
local VerticalSpan = require ( " ui/widget/verticalspan " )
local UIManager = require ( " ui/uimanager " )
2017-07-01 12:11:44 +02:00
local filemanagerutil = require ( " apps/filemanager/filemanagerutil " )
2017-04-12 20:42:28 +02:00
local lfs = require ( " libs/libkoreader-lfs " )
local logger = require ( " logger " )
2020-02-03 20:08:18 +01:00
local BaseUtil = require ( " ffi/util " )
local util = require ( " util " )
2017-04-12 20:42:28 +02:00
local _ = require ( " gettext " )
2019-08-24 09:25:38 +02:00
local C_ = _.pgettext
2017-04-12 20:42:28 +02:00
local Screen = Device.screen
2020-09-15 20:39:32 +02:00
local T = BaseUtil.template
2016-02-15 22:34:28 -08:00
2013-10-18 22:38:07 +02:00
local FileManager = InputContainer : extend {
2019-10-21 22:54:29 +10:30
title = _ ( " KOReader " ) ,
2014-03-13 21:52:43 +08:00
root_path = lfs.currentdir ( ) ,
2015-04-22 14:15:04 +08:00
mv_bin = Device : isAndroid ( ) and " /system/bin/mv " or " /bin/mv " ,
cp_bin = Device : isAndroid ( ) and " /system/bin/cp " or " /bin/cp " ,
2018-01-01 16:31:39 +01:00
mkdir_bin = Device : isAndroid ( ) and " /system/bin/mkdir " or " /bin/mkdir " ,
2013-08-14 05:29:05 -04:00
}
2020-07-01 16:17:41 -04:00
function FileManager : onSetRotationMode ( rotation )
if rotation ~= nil and rotation ~= Screen : getRotationMode ( ) then
Screen : setRotationMode ( rotation )
2021-05-11 00:49:35 +02:00
if FileManager.instance then
self : reinit ( self.path , self.focused_file )
UIManager : setDirty ( self.banner , function ( )
return " ui " , self.banner . dimen
2020-07-01 16:17:41 -04:00
end )
end
end
return true
end
2021-01-07 17:58:30 -05:00
function FileManager : setRotationMode ( )
2020-07-09 19:11:44 +02:00
local locked = G_reader_settings : isTrue ( " lock_rotation " )
2021-01-07 17:58:30 -05:00
if not locked then
local rotation_mode = G_reader_settings : readSetting ( " fm_rotation_mode " ) or Screen.ORIENTATION_PORTRAIT
2020-07-01 16:17:41 -04:00
self : onSetRotationMode ( rotation_mode )
end
end
2020-12-19 16:27:53 +01:00
function FileManager : initGesListener ( )
if not Device : isTouchDevice ( ) then
return
end
self : registerTouchZones ( {
{
id = " filemanager_swipe " ,
ges = " swipe " ,
screen_zone = {
ratio_x = 0 , ratio_y = 0 ,
ratio_w = Screen : getWidth ( ) , ratio_h = Screen : getHeight ( ) ,
2019-08-04 19:59:20 +02:00
} ,
2020-12-19 16:27:53 +01:00
handler = function ( ges )
self : onSwipeFM ( ges )
end ,
} ,
} )
end
function FileManager : onSetDimensions ( dimen )
-- update listening according to new screen dimen
if Device : isTouchDevice ( ) then
2021-03-06 19:27:23 +01:00
self : updateTouchZonesOnScreenResize ( dimen )
2019-08-04 19:59:20 +02:00
end
2020-12-19 16:27:53 +01:00
end
function FileManager : setupLayout ( )
2014-03-13 21:52:43 +08:00
self.show_parent = self.show_parent or self
2020-12-19 12:18:30 +01:00
local icon_size = Screen : scaleBySize ( DGENERIC_ICON_SIZE )
2017-09-27 18:15:11 +02:00
local home_button = IconButton : new {
2020-12-19 12:18:30 +01:00
icon = " home " ,
2018-01-01 16:31:39 +01:00
width = icon_size ,
height = icon_size ,
2017-09-27 18:15:11 +02:00
padding = Size.padding . default ,
2018-01-01 16:31:39 +01:00
padding_left = Size.padding . large ,
padding_right = Size.padding . large ,
padding_bottom = 0 ,
2021-04-17 18:01:59 -05:00
callback = function ( )
self : goHome ( )
end ,
2017-09-27 18:15:11 +02:00
hold_callback = function ( ) self : setHome ( ) end ,
}
2018-01-01 16:31:39 +01:00
local plus_button = IconButton : new {
2020-12-19 12:18:30 +01:00
icon = " plus " ,
2018-01-01 16:31:39 +01:00
width = icon_size ,
height = icon_size ,
padding = Size.padding . default ,
padding_left = Size.padding . large ,
padding_right = Size.padding . large ,
padding_bottom = 0 ,
2020-12-07 14:30:07 -05:00
callback = function ( ) self : onShowPlusMenu ( ) end ,
2018-01-01 16:31:39 +01:00
}
2016-07-04 23:14:53 -07:00
self.path_text = TextWidget : new {
2017-04-29 10:38:09 +02:00
face = Font : getFace ( " xx_smallinfofont " ) ,
2020-01-04 01:18:51 +01:00
text = BD.directory ( filemanagerutil.abbreviate ( self.root_path ) ) ,
2019-10-21 15:20:40 +02:00
max_width = Screen : getWidth ( ) - 2 * Size.padding . small ,
truncate_left = true ,
2016-07-04 23:14:53 -07:00
}
2016-07-09 01:52:33 -07:00
self.banner = FrameContainer : new {
padding = 0 ,
bordersize = 0 ,
2018-01-01 16:31:39 +01:00
VerticalGroup : new {
CenterContainer : new {
dimen = { w = Screen : getWidth ( ) , h = nil } ,
HorizontalGroup : new {
home_button ,
VerticalGroup : new {
Button : new {
2018-01-05 21:24:24 +01:00
readonly = true ,
2018-01-01 16:31:39 +01:00
bordersize = 0 ,
padding = 0 ,
text_font_bold = false ,
text_font_face = " smalltfont " ,
text_font_size = 24 ,
text = self.title ,
width = Screen : getWidth ( ) - 2 * icon_size - 4 * Size.padding . large ,
} ,
2017-09-27 18:15:11 +02:00
} ,
2018-01-01 16:31:39 +01:00
plus_button ,
}
2016-07-09 01:52:33 -07:00
} ,
2018-01-01 16:31:39 +01:00
CenterContainer : new {
dimen = { w = Screen : getWidth ( ) , h = nil } ,
self.path_text ,
} ,
VerticalSpan : new { width = Screen : scaleBySize ( 5 ) } ,
}
2014-03-13 21:52:43 +08:00
}
2014-06-04 17:22:45 +08:00
2021-03-06 22:44:18 +01:00
local show_hidden
if G_reader_settings : has ( " show_hidden " ) then
show_hidden = G_reader_settings : isTrue ( " show_hidden " )
else
show_hidden = DSHOWHIDDENFILES
end
local show_unsupported = G_reader_settings : isTrue ( " show_unsupported " )
2014-03-13 21:52:43 +08:00
local file_chooser = FileChooser : new {
2018-12-05 12:56:54 +01:00
-- remember to adjust the height when new item is added to the group
2014-03-13 21:52:43 +08:00
path = self.root_path ,
2017-10-21 19:53:56 +02:00
focused_path = self.focused_file ,
2014-12-02 09:51:31 +08:00
collate = G_reader_settings : readSetting ( " collate " ) or " strcoll " ,
2017-12-04 17:25:27 +01:00
reverse_collate = G_reader_settings : isTrue ( " reverse_collate " ) ,
2014-03-13 21:52:43 +08:00
show_parent = self.show_parent ,
show_hidden = show_hidden ,
2014-11-10 20:46:45 +08:00
width = Screen : getWidth ( ) ,
2014-03-13 21:52:43 +08:00
height = Screen : getHeight ( ) - self.banner : getSize ( ) . h ,
is_popout = false ,
is_borderless = true ,
has_close_button = true ,
2019-07-20 17:36:41 +02:00
show_unsupported = show_unsupported ,
2014-03-13 21:52:43 +08:00
file_filter = function ( filename )
2018-02-10 18:36:18 +01:00
if DocumentRegistry : hasProvider ( filename ) then
2014-03-13 21:52:43 +08:00
return true
end
2014-06-10 15:57:10 +08:00
end ,
close_callback = function ( ) return self : onClose ( ) end ,
2018-09-29 23:15:57 +02:00
-- allow left bottom tap gesture, otherwise it is eaten by hidden return button
return_arrow_propagation = true ,
2018-12-05 12:56:54 +01:00
-- allow Menu widget to delegate handling of some gestures to GestureManager
is_file_manager = true ,
2014-03-13 21:52:43 +08:00
}
self.file_chooser = file_chooser
2017-10-21 19:53:56 +02:00
self.focused_file = nil -- use it only once
2014-03-13 21:52:43 +08:00
2016-07-04 17:38:04 -07:00
function file_chooser : onPathChanged ( path ) -- luacheck: ignore
2020-01-04 01:18:51 +01:00
FileManager.instance . path_text : setText ( BD.directory ( filemanagerutil.abbreviate ( path ) ) )
2016-07-09 01:52:33 -07:00
UIManager : setDirty ( FileManager.instance , function ( )
2019-04-12 17:57:22 +02:00
return " ui " , FileManager.instance . path_text.dimen , FileManager.instance . dithered
2016-07-09 01:52:33 -07:00
end )
2016-07-04 17:38:04 -07:00
return true
end
2016-01-03 01:18:54 -08:00
function file_chooser : onFileSelect ( file ) -- luacheck: ignore
2021-05-11 00:49:35 +02:00
local ReaderUI = require ( " apps/reader/readerui " )
2014-09-03 12:09:25 +08:00
ReaderUI : showReader ( file )
2014-03-13 21:52:43 +08:00
return true
end
2014-06-04 17:22:45 +08:00
2014-03-13 21:52:43 +08:00
local copyFile = function ( file ) self : copyFile ( file ) end
local pasteHere = function ( file ) self : pasteHere ( file ) end
local cutFile = function ( file ) self : cutFile ( file ) end
local deleteFile = function ( file ) self : deleteFile ( file ) end
2016-02-19 01:03:27 +08:00
local renameFile = function ( file ) self : renameFile ( file ) end
2017-09-28 19:33:01 +02:00
local setHome = function ( path ) self : setHome ( path ) end
2014-03-13 21:52:43 +08:00
local fileManager = self
2014-06-04 17:22:45 +08:00
2016-01-03 01:18:54 -08:00
function file_chooser : onFileHold ( file ) -- luacheck: ignore
2021-03-31 23:37:08 +03:00
local is_file = lfs.attributes ( file , " mode " ) == " file "
local is_folder = lfs.attributes ( file , " mode " ) == " directory "
local is_not_parent_folder = BaseUtil.basename ( file ) ~= " .. "
2015-03-12 16:29:15 +08:00
local buttons = {
{
2014-03-13 21:52:43 +08:00
{
2019-08-24 09:25:38 +02:00
text = C_ ( " File " , " Copy " ) ,
2021-03-31 23:37:08 +03:00
enabled = is_not_parent_folder ,
2015-03-12 16:29:15 +08:00
callback = function ( )
copyFile ( file )
UIManager : close ( self.file_dialog )
2021-04-27 22:22:16 +03:00
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Copied to clipboard: \n %1 " ) , BD.filepath ( file ) ) ,
timeout = 2 ,
} )
2015-03-12 16:29:15 +08:00
end ,
2014-03-13 21:52:43 +08:00
} ,
{
2019-08-24 09:25:38 +02:00
text = C_ ( " File " , " Paste " ) ,
2015-03-12 16:29:15 +08:00
enabled = fileManager.clipboard and true or false ,
callback = function ( )
pasteHere ( file )
UIManager : close ( self.file_dialog )
end ,
2014-03-13 21:52:43 +08:00
} ,
2016-12-03 14:27:32 +01:00
{
text = _ ( " Purge .sdr " ) ,
2020-02-03 20:08:18 +01:00
enabled = DocSettings : hasSidecarFile ( BaseUtil.realpath ( file ) ) ,
2016-12-03 14:27:32 +01:00
callback = function ( )
2017-08-13 15:55:52 +02:00
UIManager : show ( ConfirmBox : new {
2020-02-03 20:08:18 +01:00
text = T ( _ ( " Purge .sdr to reset settings for this document? \n \n %1 " ) , BD.filename ( self.file_dialog . title ) ) ,
2017-08-13 15:55:52 +02:00
ok_text = _ ( " Purge " ) ,
ok_callback = function ( )
2017-09-22 18:24:38 +02:00
filemanagerutil.purgeSettings ( file )
2020-05-06 21:11:34 +02:00
require ( " readhistory " ) : fileSettingsPurged ( file )
2017-09-22 18:24:38 +02:00
self : refreshPath ( )
2017-08-13 15:55:52 +02:00
UIManager : close ( self.file_dialog )
end ,
} )
2016-12-03 14:27:32 +01:00
end ,
} ,
2014-03-13 21:52:43 +08:00
} ,
2015-03-12 16:29:15 +08:00
{
{
text = _ ( " Cut " ) ,
2021-03-31 23:37:08 +03:00
enabled = is_not_parent_folder ,
2015-03-12 16:29:15 +08:00
callback = function ( )
cutFile ( file )
UIManager : close ( self.file_dialog )
2021-04-27 22:22:16 +03:00
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Cut to clipboard: \n %1 " ) , BD.filepath ( file ) ) ,
timeout = 2 ,
} )
2015-03-12 16:29:15 +08:00
end ,
} ,
{
text = _ ( " Delete " ) ,
2021-03-31 23:37:08 +03:00
enabled = is_not_parent_folder ,
2015-03-12 16:29:15 +08:00
callback = function ( )
2021-04-27 22:22:16 +03:00
UIManager : close ( self.file_dialog )
2015-03-12 16:29:15 +08:00
UIManager : show ( ConfirmBox : new {
2021-04-27 22:22:16 +03:00
text = is_file and T ( _ ( " Delete file? \n %1 \n If you delete a file, it is permanently lost. " ) , BD.filepath ( file ) ) or
T ( _ ( " Delete folder? \n %1 \n If you delete a folder, its content is permanently lost. " ) , BD.filepath ( file ) ) ,
2017-04-04 15:31:13 +02:00
ok_text = _ ( " Delete " ) ,
2015-03-12 16:29:15 +08:00
ok_callback = function ( )
deleteFile ( file )
2020-05-06 21:11:34 +02:00
require ( " readhistory " ) : fileDeleted ( file )
2015-03-12 16:29:15 +08:00
self : refreshPath ( )
end ,
} )
end ,
} ,
2016-02-19 01:03:27 +08:00
{
text = _ ( " Rename " ) ,
2021-03-31 23:37:08 +03:00
enabled = is_not_parent_folder ,
2016-02-19 01:03:27 +08:00
callback = function ( )
UIManager : close ( self.file_dialog )
fileManager.rename_dialog = InputDialog : new {
2021-04-27 22:22:16 +03:00
title = is_file and _ ( " Rename file " ) or _ ( " Rename folder " ) ,
2020-02-03 20:08:18 +01:00
input = BaseUtil.basename ( file ) ,
2016-02-19 01:03:27 +08:00
buttons = { {
{
2017-04-04 15:31:13 +02:00
text = _ ( " Cancel " ) ,
2016-02-19 01:03:27 +08:00
enabled = true ,
callback = function ( )
UIManager : close ( fileManager.rename_dialog )
end ,
} ,
{
2017-04-04 15:31:13 +02:00
text = _ ( " Rename " ) ,
2016-02-19 01:03:27 +08:00
enabled = true ,
callback = function ( )
2021-04-27 22:22:16 +03:00
if fileManager.rename_dialog : getInputText ( ) ~= " " then
renameFile ( file )
UIManager : close ( fileManager.rename_dialog )
end
2016-02-19 01:03:27 +08:00
end ,
} ,
} } ,
}
UIManager : show ( fileManager.rename_dialog )
2018-03-30 12:46:36 +02:00
fileManager.rename_dialog : onShowKeyboard ( )
2016-02-19 01:03:27 +08:00
end ,
}
2015-03-12 16:29:15 +08:00
} ,
2017-04-12 20:42:28 +02:00
-- a little hack to get visual functionality grouping
2020-02-03 20:08:18 +01:00
{
} ,
2019-11-06 00:17:28 +01:00
}
2020-02-03 20:08:18 +01:00
2021-03-31 23:37:08 +03:00
if is_file and Device : canExecuteScript ( file ) then
2020-02-03 20:08:18 +01:00
-- NOTE: We populate the empty separator, in order not to mess with the button reordering code in CoverMenu
table.insert ( buttons [ 3 ] ,
{
-- @translators This is the script's programming language (e.g., shell or python)
text = T ( _ ( " Execute %1 script " ) , util.getScriptType ( file ) ) ,
enabled = true ,
callback = function ( )
UIManager : close ( self.file_dialog )
local script_is_running_msg = InfoMessage : new {
-- @translators %1 is the script's programming language (e.g., shell or python), %2 is the filename
2020-02-04 19:22:33 +01:00
text = T ( _ ( " Running %1 script %2… " ) , util.getScriptType ( file ) , BD.filename ( BaseUtil.basename ( file ) ) ) ,
2020-02-03 20:08:18 +01:00
}
UIManager : show ( script_is_running_msg )
UIManager : scheduleIn ( 0.5 , function ( )
2020-06-25 21:33:51 +02:00
local rv
if Device : isAndroid ( ) then
Device : setIgnoreInput ( true )
rv = os.execute ( " sh " .. BaseUtil.realpath ( file ) ) -- run by sh, because sdcard has no execute permissions
Device : setIgnoreInput ( false )
else
rv = os.execute ( BaseUtil.realpath ( file ) )
end
2020-02-03 20:08:18 +01:00
UIManager : close ( script_is_running_msg )
if rv == 0 then
UIManager : show ( InfoMessage : new {
text = _ ( " The script exited successfully. " ) ,
} )
else
--- @note: Lua 5.1 returns the raw return value from the os's system call. Counteract this madness.
UIManager : show ( InfoMessage : new {
text = T ( _ ( " The script returned a non-zero status code: %1! " ) , bit.rshift ( rv , 8 ) ) ,
2020-12-19 12:18:30 +01:00
icon = " notice-warning " ,
2020-02-03 20:08:18 +01:00
} )
end
end )
end ,
}
)
end
2021-03-31 23:37:08 +03:00
if is_file then
2019-11-06 00:17:28 +01:00
table.insert ( buttons , {
2018-02-02 21:21:52 +01:00
{
text = _ ( " Open with… " ) ,
2020-08-29 18:25:38 +02:00
enabled = DocumentRegistry : getProviders ( file ) == nil or # ( DocumentRegistry : getProviders ( file ) ) > 1 or fileManager.texteditor ,
2018-02-02 21:21:52 +01:00
callback = function ( )
UIManager : close ( self.file_dialog )
2020-08-29 18:25:38 +02:00
local one_time_providers = { }
if fileManager.texteditor then
table.insert ( one_time_providers , {
provider_name = _ ( " Text editor " ) ,
callback = function ( )
fileManager.texteditor : checkEditFile ( file )
end ,
} )
end
2021-05-11 00:49:35 +02:00
self : showSetProviderButtons ( file , FileManager.instance , one_time_providers )
2018-02-02 21:21:52 +01:00
end ,
} ,
2017-04-12 20:42:28 +02:00
{
2019-11-06 00:17:28 +01:00
text = _ ( " Book information " ) ,
enabled = FileManagerBookInfo : isSupported ( file ) ,
2017-04-12 20:42:28 +02:00
callback = function ( )
2019-11-06 00:17:28 +01:00
FileManagerBookInfo : show ( file )
2017-04-12 20:42:28 +02:00
UIManager : close ( self.file_dialog )
end ,
2019-11-06 00:17:28 +01:00
}
} )
table.insert ( buttons , {
2016-10-30 23:24:50 +01:00
{
2019-11-06 00:17:28 +01:00
text_func = function ( )
if ReadCollection : checkItemExist ( file ) then
return _ ( " Remove from favorites " )
else
return _ ( " Add to favorites " )
end
end ,
enabled = DocumentRegistry : getProviders ( file ) ~= nil ,
2016-10-30 23:24:50 +01:00
callback = function ( )
2019-11-06 00:17:28 +01:00
if ReadCollection : checkItemExist ( file ) then
ReadCollection : removeItem ( file )
else
ReadCollection : addItem ( file )
end
2016-10-30 23:24:50 +01:00
UIManager : close ( self.file_dialog )
end ,
} ,
2019-11-06 00:17:28 +01:00
} )
if FileManagerConverter : isSupported ( file ) then
table.insert ( buttons , {
{
text = _ ( " Convert " ) ,
enabled = true ,
callback = function ( )
UIManager : close ( self.file_dialog )
FileManagerConverter : showConvertButtons ( file , self )
end ,
}
} )
end
end
2021-03-31 23:37:08 +03:00
if is_folder then
2020-02-03 20:08:18 +01:00
local realpath = BaseUtil.realpath ( file )
2015-03-12 16:29:15 +08:00
table.insert ( buttons , {
{
2021-02-22 18:44:16 +01:00
text = _ ( " Set as HOME folder " ) ,
2015-03-12 16:29:15 +08:00
callback = function ( )
2017-09-28 19:33:01 +02:00
setHome ( realpath )
2015-03-12 16:29:15 +08:00
UIManager : close ( self.file_dialog )
end
}
} )
end
2016-12-11 01:06:10 +01:00
2020-01-04 01:18:51 +01:00
local title
2021-03-31 23:37:08 +03:00
if is_folder then
2020-01-04 01:18:51 +01:00
title = BD.directory ( file : match ( " ([^/]+)$ " ) )
else
title = BD.filename ( file : match ( " ([^/]+)$ " ) )
end
2016-12-11 01:06:10 +01:00
self.file_dialog = ButtonDialogTitle : new {
2020-01-04 01:18:51 +01:00
title = title ,
2016-12-11 01:06:10 +01:00
title_align = " center " ,
2015-03-12 16:29:15 +08:00
buttons = buttons ,
2014-03-13 21:52:43 +08:00
}
UIManager : show ( self.file_dialog )
return true
end
self.layout = VerticalGroup : new {
self.banner ,
file_chooser ,
}
local fm_ui = FrameContainer : new {
padding = 0 ,
bordersize = 0 ,
2014-10-22 15:34:11 +02:00
background = Blitbuffer.COLOR_WHITE ,
2014-03-13 21:52:43 +08:00
self.layout ,
}
self [ 1 ] = fm_ui
self.menu = FileManagerMenu : new {
ui = self
}
2020-11-08 16:43:37 -05:00
2020-12-19 16:27:53 +01:00
if Device : hasKeys ( ) then
self.key_events . Home = { { " Home " } , doc = " go home " }
-- Override the menu.lua way of handling the back key
self.file_chooser . key_events.Back = { { " Back " } , doc = " go back " }
if not Device : hasFewKeys ( ) then
-- Also remove the handler assigned to the "Back" key by menu.lua
self.file_chooser . key_events.Close = nil
end
end
end
ReaderUI: Saner FM/RD lifecycle
* Ensure that going from one to the other tears down the former and
its plugins before instantiating the latter and its plugins.
UIManager: Unify Event sending & broadcasting
* Make the two behave the same way (walk the widget stack from top to
bottom), and properly handle the window stack shrinking shrinking
*and* growing.
Previously, broadcasting happened bottom-to-top and didn't really
handle the list shrinking/growing, while sending only handled the list
shrinking by a single element, and hopefully that element being the one
the event was just sent to.
These two items combined allowed us to optimize suboptimal
refresh behavior with Menu and other Menu classes when
opening/closing a document.
e.g., the "opening document" Notification is now properly regional,
and the "open last doc" option no longer flashes like a crazy person
anymore.
Plugins: Allow optimizing Menu refresh with custom menus, too.
Requires moving Menu's close_callback *after* onMenuSelect, which, eh,
probably makes sense, and is probably harmless in the grand scheme of
things.
2021-05-01 18:53:04 +02:00
-- NOTE: The only thing that will *ever* instantiate a new FileManager object is our very own showFiles below!
2020-12-19 16:27:53 +01:00
function FileManager : init ( )
self : setupLayout ( )
2020-11-08 16:43:37 -05:00
local screenshoter = Screenshoter : new { prefix = ' FileManager ' }
table.insert ( self , screenshoter ) -- for regular events
self.active_widgets = { screenshoter } -- to get events even when hidden
2014-03-13 21:52:43 +08:00
table.insert ( self , self.menu )
2021-04-09 23:12:15 +02:00
table.insert ( self , FileManagerHistory : new { ui = self } )
table.insert ( self , FileManagerCollection : new { ui = self } )
2019-03-03 12:43:09 +01:00
table.insert ( self , FileManagerFileSearcher : new { ui = self } )
2019-03-04 22:45:55 +01:00
table.insert ( self , FileManagerShortcuts : new { ui = self } )
2017-01-02 10:12:34 -08:00
table.insert ( self , ReaderDictionary : new { ui = self } )
2017-05-12 18:28:42 +02:00
table.insert ( self , ReaderWikipedia : new { ui = self } )
2018-08-11 22:47:33 +02:00
table.insert ( self , ReaderDeviceStatus : new { ui = self } )
2020-07-12 14:47:49 -04:00
table.insert ( self , DeviceListener : new { ui = self } )
2014-03-13 21:52:43 +08:00
2016-12-19 23:19:54 -08:00
-- koreader plugins
2020-12-19 16:27:53 +01:00
for _ , plugin_module in ipairs ( PluginLoader : loadPlugins ( ) ) do
if not plugin_module.is_doc_only then
local ok , plugin_or_err = PluginLoader : createPluginInstance (
plugin_module , { ui = self , } )
-- Keep references to the modules which do not register into menu.
if ok then
local name = plugin_module.name
if name then self [ name ] = plugin_or_err end
table.insert ( self , plugin_or_err )
logger.info ( " FM loaded plugin " , name ,
" at " , plugin_module.path )
2017-04-20 23:37:04 -07:00
end
2016-12-19 23:19:54 -08:00
end
end
2018-09-29 23:15:57 +02:00
2020-07-12 14:47:49 -04:00
if Device : hasWifiToggle ( ) then
local NetworkListener = require ( " ui/network/networklistener " )
table.insert ( self , NetworkListener : new { ui = self } )
end
2021-03-06 19:27:23 +01:00
self : initGesListener ( )
2014-03-13 21:52:43 +08:00
self : handleEvent ( Event : new ( " SetDimensions " , self.dimen ) )
2021-05-13 13:05:05 +02:00
-- NOTE: ReaderUI has a _getRunningInstance method for this, because it used to store the instance reference in a private module variable.
if FileManager.instance == nil then
logger.dbg ( " Spinning up new FileManager instance " , tostring ( self ) )
else
2021-05-18 20:54:54 +02:00
-- Should never happen, given what we did in showFiles...
2021-05-13 13:05:05 +02:00
logger.err ( " FileManager instance mismatch! Opened " , tostring ( self ) , " while we still have an existing instance: " , tostring ( FileManager.instance ) , debug.traceback ( ) )
end
FileManager.instance = self
2013-08-14 05:29:05 -04:00
end
2018-04-09 09:22:16 +02:00
function FileChooser : onBack ( )
local back_to_exit = G_reader_settings : readSetting ( " back_to_exit " ) or " prompt "
2018-10-10 18:28:51 +02:00
local back_in_filemanager = G_reader_settings : readSetting ( " back_in_filemanager " ) or " default "
if back_in_filemanager == " default " then
if back_to_exit == " always " then
return self : onClose ( )
elseif back_to_exit == " disable " then
return true
elseif back_to_exit == " prompt " then
UIManager : show ( ConfirmBox : new {
text = _ ( " Exit KOReader? " ) ,
ok_text = _ ( " Exit " ) ,
ok_callback = function ( )
self : onClose ( )
end
} )
return true
end
elseif back_in_filemanager == " parent_folder " then
self : changeToPath ( string.format ( " %s/.. " , self.path ) )
2018-04-09 09:22:16 +02:00
return true
end
end
2019-04-07 19:00:15 +02:00
function FileManager : onShowPlusMenu ( )
self : tapPlus ( )
return true
end
2019-08-04 19:59:20 +02:00
function FileManager : onSwipeFM ( ges )
2019-12-06 22:55:39 +01:00
local direction = BD.flipDirectionIfMirroredUILayout ( ges.direction )
if direction == " west " then
2019-08-04 19:59:20 +02:00
self.file_chooser : onNextPage ( )
2019-12-06 22:55:39 +01:00
elseif direction == " east " then
2019-08-04 19:59:20 +02:00
self.file_chooser : onPrevPage ( )
end
return true
end
2018-01-01 16:31:39 +01:00
function FileManager : tapPlus ( )
local buttons = {
{
{
text = _ ( " New folder " ) ,
callback = function ( )
UIManager : close ( self.file_dialog )
self.input_dialog = InputDialog : new {
2021-04-27 22:22:16 +03:00
title = _ ( " New folder " ) ,
2018-01-01 16:31:39 +01:00
input_type = " text " ,
buttons = {
{
{
text = _ ( " Cancel " ) ,
callback = function ( )
self : closeInputDialog ( )
end ,
} ,
{
text = _ ( " Create " ) ,
callback = function ( )
local new_folder = self.input_dialog : getInputText ( )
if new_folder and new_folder ~= " " then
self : createFolder ( self.file_chooser . path , new_folder )
2021-04-27 22:22:16 +03:00
self : closeInputDialog ( )
2018-01-01 16:31:39 +01:00
end
end ,
} ,
}
} ,
}
UIManager : show ( self.input_dialog )
2018-03-30 12:46:36 +02:00
self.input_dialog : onShowKeyboard ( )
2018-01-01 16:31:39 +01:00
end ,
} ,
} ,
{
{
text = _ ( " Paste " ) ,
enabled = self.clipboard and true or false ,
callback = function ( )
self : pasteHere ( self.file_chooser . path )
self : onRefresh ( )
UIManager : close ( self.file_dialog )
end ,
} ,
} ,
{
{
2021-02-22 18:44:16 +01:00
text = _ ( " Set as HOME folder " ) ,
2018-01-01 16:31:39 +01:00
callback = function ( )
self : setHome ( self.file_chooser . path )
UIManager : close ( self.file_dialog )
end
}
} ,
{
{
2021-02-22 18:44:16 +01:00
text = _ ( " Go to HOME folder " ) ,
2018-01-01 16:31:39 +01:00
callback = function ( )
self : goHome ( )
UIManager : close ( self.file_dialog )
end
}
2018-07-28 20:30:39 +02:00
} ,
{
{
text = _ ( " Open random document " ) ,
callback = function ( )
self : openRandomFile ( self.file_chooser . path )
UIManager : close ( self.file_dialog )
end
}
2018-08-22 22:34:20 +02:00
} ,
{
{
text = _ ( " Folder shortcuts " ) ,
callback = function ( )
2019-03-04 22:45:55 +01:00
self : handleEvent ( Event : new ( " ShowFolderShortcutsDialog " ) )
2018-08-22 22:34:20 +02:00
UIManager : close ( self.file_dialog )
end
}
2018-01-01 16:31:39 +01:00
}
}
2019-12-21 15:59:23 +01:00
if Device : canImportFiles ( ) then
table.insert ( buttons , 3 , {
{
text = _ ( " Import files here " ) ,
2020-11-27 20:51:40 +01:00
enabled = Device : isValidPath ( self.file_chooser . path ) ,
2019-12-21 15:59:23 +01:00
callback = function ( )
local current_dir = self.file_chooser . path
UIManager : close ( self.file_dialog )
Device.importFile ( current_dir )
end ,
} ,
} )
end
2020-11-27 20:51:40 +01:00
if Device : hasExternalSD ( ) then
table.insert ( buttons , 4 , {
{
text_func = function ( )
if Device : isValidPath ( self.file_chooser . path ) then
return _ ( " Switch to SDCard " )
else
return _ ( " Switch to internal storage " )
end
end ,
callback = function ( )
if Device : isValidPath ( self.file_chooser . path ) then
local ok , sd_path = Device : hasExternalSD ( )
UIManager : close ( self.file_dialog )
if ok then
self.file_chooser : changeToPath ( sd_path )
end
else
UIManager : close ( self.file_dialog )
self.file_chooser : changeToPath ( Device.home_dir )
end
end ,
} ,
} )
end
2018-01-01 16:31:39 +01:00
self.file_dialog = ButtonDialogTitle : new {
2020-01-04 01:18:51 +01:00
title = BD.dirpath ( filemanagerutil.abbreviate ( self.file_chooser . path ) ) ,
2018-01-01 16:31:39 +01:00
title_align = " center " ,
buttons = buttons ,
}
UIManager : show ( self.file_dialog )
end
2017-10-13 18:43:02 +02:00
function FileManager : reinit ( path , focused_file )
2020-08-19 23:31:57 -04:00
UIManager : flushSettings ( )
2016-04-23 22:56:56 +08:00
self.dimen = Screen : getSize ( )
2015-02-02 01:36:49 +08:00
-- backup the root path and path items
2016-04-23 22:56:56 +08:00
self.root_path = path or self.file_chooser . path
2015-02-02 01:36:49 +08:00
local path_items_backup = { }
for k , v in pairs ( self.file_chooser . path_items ) do
path_items_backup [ k ] = v
end
-- reinit filemanager
2017-10-21 19:53:56 +02:00
self.focused_file = focused_file
2020-12-19 16:27:53 +01:00
self : setupLayout ( )
self : handleEvent ( Event : new ( " SetDimensions " , self.dimen ) )
2015-02-02 01:36:49 +08:00
self.file_chooser . path_items = path_items_backup
2020-12-19 16:27:53 +01:00
-- self:init() has already done file_chooser:refreshPath()
-- (by virtue of rebuilding file_chooser), so this one
-- looks unnecessary (cheap with classic mode, less cheap with
2017-10-21 19:53:56 +02:00
-- CoverBrowser plugin's cover image renderings)
-- self:onRefresh()
2015-02-02 01:36:49 +08:00
end
2020-06-19 12:22:38 +02:00
function FileManager : getCurrentDir ( )
2021-05-11 00:49:35 +02:00
if FileManager.instance then
return FileManager.instance . file_chooser.path
2020-06-19 12:22:38 +02:00
end
end
2013-08-25 00:00:06 -04:00
function FileManager : toggleHiddenFiles ( )
2014-03-13 21:52:43 +08:00
self.file_chooser : toggleHiddenFiles ( )
G_reader_settings : saveSetting ( " show_hidden " , self.file_chooser . show_hidden )
2013-08-25 00:00:06 -04:00
end
2013-08-14 05:29:05 -04:00
2019-07-20 17:36:41 +02:00
function FileManager : toggleUnsupportedFiles ( )
self.file_chooser : toggleUnsupportedFiles ( )
G_reader_settings : saveSetting ( " show_unsupported " , self.file_chooser . show_unsupported )
end
2014-10-30 22:41:49 +08:00
function FileManager : setCollate ( collate )
self.file_chooser : setCollate ( collate )
G_reader_settings : saveSetting ( " collate " , self.file_chooser . collate )
end
function FileManager : toggleReverseCollate ( )
self.file_chooser : toggleReverseCollate ( )
G_reader_settings : saveSetting ( " reverse_collate " , self.file_chooser . reverse_collate )
end
2013-08-14 05:29:05 -04:00
function FileManager : onClose ( )
2016-12-29 00:10:38 -08:00
logger.dbg ( " close filemanager " )
2021-04-29 19:30:18 +02:00
PluginLoader : finalize ( )
2020-02-17 16:53:09 +01:00
self : handleEvent ( Event : new ( " SaveSettings " ) )
2017-09-04 21:05:05 +02:00
G_reader_settings : flush ( )
2014-03-13 21:52:43 +08:00
UIManager : close ( self )
return true
2013-08-14 05:29:05 -04:00
end
2013-10-18 22:38:07 +02:00
2021-05-11 00:49:35 +02:00
function FileManager : onCloseWidget ( )
if FileManager.instance == self then
logger.dbg ( " Tearing down FileManager " , tostring ( self ) )
else
logger.warn ( " FileManager instance mismatch! Closed " , tostring ( self ) , " while the active one is supposed to be " , tostring ( FileManager.instance ) )
end
FileManager.instance = nil
end
ReaderUI: Saner FM/RD lifecycle
* Ensure that going from one to the other tears down the former and
its plugins before instantiating the latter and its plugins.
UIManager: Unify Event sending & broadcasting
* Make the two behave the same way (walk the widget stack from top to
bottom), and properly handle the window stack shrinking shrinking
*and* growing.
Previously, broadcasting happened bottom-to-top and didn't really
handle the list shrinking/growing, while sending only handled the list
shrinking by a single element, and hopefully that element being the one
the event was just sent to.
These two items combined allowed us to optimize suboptimal
refresh behavior with Menu and other Menu classes when
opening/closing a document.
e.g., the "opening document" Notification is now properly regional,
and the "open last doc" option no longer flashes like a crazy person
anymore.
Plugins: Allow optimizing Menu refresh with custom menus, too.
Requires moving Menu's close_callback *after* onMenuSelect, which, eh,
probably makes sense, and is probably harmless in the grand scheme of
things.
2021-05-01 18:53:04 +02:00
function FileManager : onShowingReader ( )
-- Allows us to optimize out a few useless refreshes in various CloseWidgets handlers...
self.tearing_down = true
-- Clear the dither flag to prevent it from infecting the queue and re-inserting a full-screen refresh...
self.dithered = nil
self : onClose ( )
end
-- Same as above, except we don't close it yet. Useful for plugins that need to close custom Menus before calling showReader.
function FileManager : onSetupShowReader ( )
self.tearing_down = true
self.dithered = nil
end
2014-09-10 12:21:07 +08:00
function FileManager : onRefresh ( )
self.file_chooser : refreshPath ( )
return true
end
2017-09-27 18:15:11 +02:00
function FileManager : goHome ( )
local home_dir = G_reader_settings : readSetting ( " home_dir " )
2021-03-06 22:44:18 +01:00
if not home_dir or lfs.attributes ( home_dir , " mode " ) ~= " directory " then
2019-08-02 18:07:31 +02:00
-- Try some sane defaults, depending on platform
2020-06-19 09:41:50 +02:00
home_dir = Device.home_dir
2020-04-10 19:11:41 +02:00
end
if home_dir then
-- Jump to the first page if we're already home
if self.file_chooser . path and home_dir == self.file_chooser . path then
self.file_chooser : onGotoPage ( 1 )
2021-04-19 21:29:26 +02:00
-- Also pick up new content, if any.
self.file_chooser : refreshPath ( )
2019-08-02 18:07:31 +02:00
else
2020-04-10 19:11:41 +02:00
self.file_chooser : changeToPath ( home_dir )
2019-08-02 18:07:31 +02:00
end
2020-04-10 19:11:41 +02:00
else
self : setHome ( )
2017-09-27 18:15:11 +02:00
end
return true
end
function FileManager : setHome ( path )
path = path or self.file_chooser . path
UIManager : show ( ConfirmBox : new {
2021-02-22 18:44:16 +01:00
text = T ( _ ( " Set '%1' as HOME folder? " ) , BD.dirpath ( path ) ) ,
2017-09-27 18:15:11 +02:00
ok_text = _ ( " Set as HOME " ) ,
ok_callback = function ( )
G_reader_settings : saveSetting ( " home_dir " , path )
end ,
} )
return true
end
2018-07-28 20:30:39 +02:00
function FileManager : openRandomFile ( dir )
local random_file = DocumentRegistry : getRandomFile ( dir , false )
if random_file then
UIManager : show ( MultiConfirmBox : new {
2020-02-03 20:08:18 +01:00
text = T ( _ ( " Do you want to open %1? " ) , BD.filename ( BaseUtil.basename ( random_file ) ) ) ,
2018-07-28 20:30:39 +02:00
choice1_text = _ ( " Open " ) ,
choice1_callback = function ( )
2021-05-11 00:49:35 +02:00
local ReaderUI = require ( " apps/reader/readerui " )
2018-07-28 20:30:39 +02:00
ReaderUI : showReader ( random_file )
end ,
2019-08-24 09:25:38 +02:00
-- @translators Another file. This is a button on the open random file dialog. It presents a file with the choices Open/Another.
2018-07-28 20:30:39 +02:00
choice2_text = _ ( " Another " ) ,
choice2_callback = function ( )
self : openRandomFile ( dir )
end ,
} )
UIManager : close ( self.file_dialog )
else
UIManager : show ( InfoMessage : new {
text = _ ( " File not found " ) ,
} )
end
end
2014-01-18 23:15:44 +08:00
function FileManager : copyFile ( file )
2014-03-13 21:52:43 +08:00
self.cutfile = false
self.clipboard = file
2014-01-18 23:15:44 +08:00
end
function FileManager : cutFile ( file )
2014-03-13 21:52:43 +08:00
self.cutfile = true
self.clipboard = file
2014-01-18 23:15:44 +08:00
end
function FileManager : pasteHere ( file )
2014-03-13 21:52:43 +08:00
if self.clipboard then
2020-02-03 20:08:18 +01:00
file = BaseUtil.realpath ( file )
2021-04-27 22:22:16 +03:00
local orig_basename = BaseUtil.basename ( self.clipboard )
2020-02-03 20:08:18 +01:00
local orig = BaseUtil.realpath ( self.clipboard )
2014-03-13 21:52:43 +08:00
local dest = lfs.attributes ( file , " mode " ) == " directory " and
file or file : match ( " (.*/) " )
2018-01-02 16:21:00 +01:00
local function infoCopyFile ( )
-- if we copy a file, also copy its sidecar directory
if DocSettings : hasSidecarFile ( orig ) then
2020-02-03 20:08:18 +01:00
BaseUtil.execute ( self.cp_bin , " -r " , DocSettings : getSidecarDir ( orig ) , dest )
2018-01-02 16:21:00 +01:00
end
2020-02-03 20:08:18 +01:00
if BaseUtil.execute ( self.cp_bin , " -r " , orig , dest ) == 0 then
2018-01-02 16:21:00 +01:00
UIManager : show ( InfoMessage : new {
2021-04-27 22:22:16 +03:00
text = T ( _ ( " Copied: \n %1 \n to: \n %2 " ) , BD.filepath ( orig_basename ) , BD.dirpath ( dest ) ) ,
2018-01-02 16:21:00 +01:00
timeout = 2 ,
} )
else
UIManager : show ( InfoMessage : new {
2021-04-27 22:22:16 +03:00
text = T ( _ ( " Failed to copy: \n %1 \n to: \n %2 " ) , BD.filepath ( orig_basename ) , BD.dirpath ( dest ) ) ,
icon = " notice-warning " ,
2018-01-02 16:21:00 +01:00
} )
end
end
local function infoMoveFile ( )
2016-12-03 14:27:32 +01:00
-- if we move a file, also move its sidecar directory
2017-02-19 12:24:37 +01:00
if DocSettings : hasSidecarFile ( orig ) then
2016-12-03 14:27:32 +01:00
self : moveFile ( DocSettings : getSidecarDir ( orig ) , dest ) -- dest is always a directory
end
2018-01-02 16:21:00 +01:00
if self : moveFile ( orig , dest ) then
2019-11-06 00:17:28 +01:00
-- Update history and collections.
2020-02-03 20:08:18 +01:00
local dest_file = string.format ( " %s/%s " , dest , BaseUtil.basename ( orig ) )
2020-05-06 21:11:34 +02:00
require ( " readhistory " ) : updateItemByPath ( orig , dest_file ) -- (will update "lastfile" if needed)
2019-11-06 00:17:28 +01:00
ReadCollection : updateItemByPath ( orig , dest_file )
2018-01-02 16:21:00 +01:00
UIManager : show ( InfoMessage : new {
2021-04-27 22:22:16 +03:00
text = T ( _ ( " Moved: \n %1 \n to: \n %2 " ) , BD.filepath ( orig_basename ) , BD.dirpath ( dest ) ) ,
2018-01-02 16:21:00 +01:00
timeout = 2 ,
} )
else
UIManager : show ( InfoMessage : new {
2021-04-27 22:22:16 +03:00
text = T ( _ ( " Failed to move: \n %1 \n to: \n %2 " ) , BD.filepath ( orig_basename ) , BD.dirpath ( dest ) ) ,
icon = " notice-warning " ,
2018-01-02 16:21:00 +01:00
} )
2017-12-27 15:03:53 +01:00
end
2014-03-13 21:52:43 +08:00
end
2014-01-18 23:15:44 +08:00
2018-01-02 16:21:00 +01:00
local info_file
if self.cutfile then
info_file = infoMoveFile
else
info_file = infoCopyFile
end
2020-02-03 20:08:18 +01:00
local basename = BaseUtil.basename ( self.clipboard )
2018-01-16 18:20:25 +01:00
local mode = lfs.attributes ( string.format ( " %s/%s " , dest , basename ) , " mode " )
2018-01-02 16:21:00 +01:00
if mode == " file " or mode == " directory " then
local text
if mode == " file " then
2021-04-27 22:22:16 +03:00
text = T ( _ ( " File already exists: \n %1 \n Overwrite file? " ) , BD.filename ( basename ) )
2018-01-02 16:21:00 +01:00
else
2021-04-27 22:22:16 +03:00
text = T ( _ ( " Folder already exists: \n %1 \n Overwrite folder? " ) , BD.directory ( basename ) )
2018-01-02 16:21:00 +01:00
end
UIManager : show ( ConfirmBox : new {
text = text ,
ok_text = _ ( " Overwrite " ) ,
ok_callback = function ( )
info_file ( )
self : onRefresh ( )
2019-05-28 15:16:44 +02:00
self.clipboard = nil
2018-01-02 16:21:00 +01:00
end ,
} )
else
info_file ( )
self : onRefresh ( )
2019-05-28 15:16:44 +02:00
self.clipboard = nil
2018-01-02 16:21:00 +01:00
end
2018-01-01 16:31:39 +01:00
end
end
2018-01-05 21:24:24 +01:00
function FileManager : createFolder ( curr_folder , new_folder )
2018-01-16 18:20:25 +01:00
local folder = string.format ( " %s/%s " , curr_folder , new_folder )
2020-02-03 20:08:18 +01:00
local code = BaseUtil.execute ( self.mkdir_bin , folder )
2018-01-05 21:24:24 +01:00
if code == 0 then
self : onRefresh ( )
2021-04-27 22:22:16 +03:00
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Created folder: \n %1 " ) , BD.directory ( new_folder ) ) ,
timeout = 2 ,
} )
2018-01-05 21:24:24 +01:00
else
2021-04-27 22:22:16 +03:00
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Failed to create folder: \n %1 " ) , BD.directory ( new_folder ) ) ,
icon = " notice-warning " ,
} )
2018-01-05 21:24:24 +01:00
end
end
2014-01-18 23:15:44 +08:00
function FileManager : deleteFile ( file )
2019-11-06 00:17:28 +01:00
local ok , err , is_dir
2020-02-03 20:08:18 +01:00
local file_abs_path = BaseUtil.realpath ( file )
2016-01-06 22:44:07 -08:00
if file_abs_path == nil then
2014-11-13 12:28:57 +01:00
UIManager : show ( InfoMessage : new {
2021-04-27 22:22:16 +03:00
text = T ( _ ( " File not found: \n %1 " ) , BD.filepath ( file ) ) ,
icon = " notice-warning " ,
2016-01-06 22:44:07 -08:00
} )
return
end
2018-02-10 18:36:18 +01:00
local is_doc = DocumentRegistry : hasProvider ( file_abs_path )
2017-12-25 16:07:05 +01:00
if lfs.attributes ( file_abs_path , " mode " ) == " file " then
ok , err = os.remove ( file_abs_path )
else
2020-02-03 20:08:18 +01:00
ok , err = BaseUtil.purgeDir ( file_abs_path )
2019-11-06 00:17:28 +01:00
is_dir = true
2017-12-25 16:07:05 +01:00
end
2018-02-10 18:36:18 +01:00
if ok and not err then
if is_doc then
2018-06-25 23:38:02 +02:00
local doc_settings = DocSettings : open ( file )
-- remove cache if any
local cache_file_path = doc_settings : readSetting ( " cache_file_path " )
if cache_file_path then
os.remove ( cache_file_path )
end
doc_settings : purge ( )
2016-01-06 22:44:07 -08:00
end
2019-11-06 00:17:28 +01:00
ReadCollection : removeItemByPath ( file , is_dir )
2016-01-06 22:44:07 -08:00
UIManager : show ( InfoMessage : new {
2021-04-27 22:22:16 +03:00
text = is_dir and T ( _ ( " Deleted folder: \n %1 " ) , BD.filepath ( file ) ) or
T ( _ ( " Deleted file: \n %1 " ) , BD.filepath ( file ) ) ,
2014-11-14 21:57:06 +01:00
timeout = 2 ,
2014-11-13 12:28:57 +01:00
} )
else
UIManager : show ( InfoMessage : new {
2021-04-27 22:22:16 +03:00
text = T ( _ ( " Failed to delete: \n %1 " ) , BD.filepath ( file ) ) ,
icon = " notice-warning " ,
2014-11-13 12:28:57 +01:00
} )
end
2014-01-18 23:15:44 +08:00
end
2016-02-19 01:03:27 +08:00
function FileManager : renameFile ( file )
2021-06-29 15:01:44 +03:00
local basename = self.rename_dialog : getInputText ( )
if BaseUtil.basename ( file ) ~= basename then
local dest = BaseUtil.joinPath ( BaseUtil.dirname ( file ) , basename )
local function doRenameFile ( )
if self : moveFile ( file , dest ) then
require ( " readhistory " ) : updateItemByPath ( file , dest ) -- (will update "lastfile" if needed)
ReadCollection : updateItemByPath ( file , dest )
if lfs.attributes ( dest , " mode " ) == " file " then
local doc = require ( " docsettings " )
local move_history = true
if lfs.attributes ( doc : getHistoryPath ( file ) , " mode " ) == " file " and
not self : moveFile ( doc : getHistoryPath ( file ) , doc : getHistoryPath ( dest ) ) then
move_history = false
end
if lfs.attributes ( doc : getSidecarDir ( file ) , " mode " ) == " directory " and
not self : moveFile ( doc : getSidecarDir ( file ) , doc : getSidecarDir ( dest ) ) then
move_history = false
end
if move_history then
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Renamed file: \n %1 \n to: \n %2 " ) , BD.filepath ( file ) , BD.filepath ( dest ) ) ,
timeout = 2 ,
} )
else
UIManager : show ( InfoMessage : new {
text = T ( _ ( " Renamed file: \n %1 \n to: \n %2 \n \n Failed to move history data. \n The reading history may be lost. " ) ,
BD.filepath ( file ) , BD.filepath ( dest ) ) ,
icon = " notice-warning " ,
} )
end
2016-02-19 01:03:27 +08:00
else
UIManager : show ( InfoMessage : new {
2021-06-29 15:01:44 +03:00
text = T ( _ ( " Renamed folder: \n %1 \n to: \n %2 " ) , BD.filepath ( file ) , BD.filepath ( dest ) ) ,
timeout = 2 ,
2016-02-19 01:03:27 +08:00
} )
end
2021-04-27 22:22:16 +03:00
else
UIManager : show ( InfoMessage : new {
2021-06-29 15:01:44 +03:00
text = T ( _ ( " Failed to rename: \n %1 \n to: \n %2 " ) , BD.filepath ( file ) , BD.filepath ( dest ) ) ,
icon = " notice-warning " ,
} )
end
end
local mode_dest = lfs.attributes ( dest , " mode " )
local mode_file = lfs.attributes ( file , " mode " )
if mode_dest then
local text , ok_text
if mode_dest ~= mode_file then
if mode_file == " file " then
text = T ( _ ( " Folder already exists: \n %1 \n File cannot be renamed. " ) , BD.directory ( basename ) )
else
text = T ( _ ( " File already exists: \n %1 \n Folder cannot be renamed. " ) , BD.filename ( basename ) )
end
UIManager : show ( InfoMessage : new {
text = text ,
icon = " notice-warning " ,
} )
else
if mode_file == " file " then
text = T ( _ ( " File already exists: \n %1 \n Overwrite file? " ) , BD.filename ( basename ) )
ok_text = _ ( " Overwrite " )
else
text = T ( _ ( " Folder already exists: \n %1 \n Move the folder inside it? " ) , BD.directory ( basename ) )
ok_text = _ ( " Move " )
end
UIManager : show ( ConfirmBox : new {
text = text ,
ok_text = ok_text ,
ok_callback = function ( )
doRenameFile ( )
self : onRefresh ( )
end ,
2021-04-27 22:22:16 +03:00
} )
2016-02-19 01:03:27 +08:00
end
else
2021-06-29 15:01:44 +03:00
doRenameFile ( )
self : onRefresh ( )
2016-02-19 01:03:27 +08:00
end
end
end
2014-10-30 22:41:49 +08:00
function FileManager : getSortingMenuTable ( )
local fm = self
2014-11-28 12:48:15 +01:00
local collates = {
2018-08-07 13:23:19 -06:00
strcoll = { _ ( " filename " ) , _ ( " Sort by filename " ) } ,
2021-07-18 20:21:39 +02:00
natural = { _ ( " natural " ) , _ ( " Sort by filename (natural sorting) " ) } ,
2018-08-11 22:06:07 +02:00
strcoll_mixed = { _ ( " name mixed " ) , _ ( " Sort by name – mixed files and folders " ) } ,
2017-02-15 19:47:45 -08:00
access = { _ ( " date read " ) , _ ( " Sort by last read date " ) } ,
change = { _ ( " date added " ) , _ ( " Sort by date added " ) } ,
modification = { _ ( " date modified " ) , _ ( " Sort by date modified " ) } ,
2017-02-11 18:55:31 -08:00
size = { _ ( " size " ) , _ ( " Sort by size " ) } ,
type = { _ ( " type " ) , _ ( " Sort by type " ) } ,
2018-08-11 22:06:07 +02:00
percent_unopened_first = { _ ( " percent – unopened first " ) , _ ( " Sort by percent – unopened first " ) } ,
percent_unopened_last = { _ ( " percent – unopened last " ) , _ ( " Sort by percent – unopened last " ) } ,
2014-11-28 12:48:15 +01:00
}
2014-10-30 22:41:49 +08:00
local set_collate_table = function ( collate )
return {
2014-11-28 12:48:15 +01:00
text = collates [ collate ] [ 2 ] ,
2014-10-30 22:41:49 +08:00
checked_func = function ( )
return fm.file_chooser . collate == collate
end ,
callback = function ( ) fm : setCollate ( collate ) end ,
}
end
2018-08-10 22:26:07 +02:00
local get_collate_percent = function ( )
local collate_type = G_reader_settings : readSetting ( " collate " )
if collate_type == " percent_unopened_first " or collate_type == " percent_unopened_last " then
return collates [ collate_type ] [ 2 ]
else
return _ ( " Sort by percent " )
end
end
2014-10-30 22:41:49 +08:00
return {
text_func = function ( )
2020-02-03 20:08:18 +01:00
return T (
2017-08-14 13:15:12 +02:00
_ ( " Sort by: %1 " ) ,
2014-11-28 12:48:15 +01:00
collates [ fm.file_chooser . collate ] [ 1 ]
)
2014-10-30 22:41:49 +08:00
end ,
sub_item_table = {
set_collate_table ( " strcoll " ) ,
2021-07-18 20:21:39 +02:00
set_collate_table ( " natural " ) ,
2018-08-10 22:26:07 +02:00
set_collate_table ( " strcoll_mixed " ) ,
2014-10-30 22:41:49 +08:00
set_collate_table ( " access " ) ,
2017-02-11 18:55:31 -08:00
set_collate_table ( " change " ) ,
set_collate_table ( " modification " ) ,
set_collate_table ( " size " ) ,
set_collate_table ( " type " ) ,
2018-08-10 22:26:07 +02:00
{
text_func = get_collate_percent ,
checked_func = function ( )
return fm.file_chooser . collate == " percent_unopened_first "
or fm.file_chooser . collate == " percent_unopened_last "
end ,
sub_item_table = {
set_collate_table ( " percent_unopened_first " ) ,
set_collate_table ( " percent_unopened_last " ) ,
}
} ,
2014-10-30 22:41:49 +08:00
}
}
end
2017-08-14 13:15:12 +02:00
function FileManager : getStartWithMenuTable ( )
local start_with_setting = G_reader_settings : readSetting ( " start_with " ) or " filemanager "
local start_withs = {
filemanager = { _ ( " file browser " ) , _ ( " Start with file browser " ) } ,
history = { _ ( " history " ) , _ ( " Start with history " ) } ,
2020-08-26 20:44:56 +02:00
favorites = { _ ( " favorites " ) , _ ( " Start with favorites " ) } ,
2018-09-12 21:42:24 +02:00
folder_shortcuts = { _ ( " folder shortcuts " ) , _ ( " Start with folder shortcuts " ) } ,
2017-08-14 13:15:12 +02:00
last = { _ ( " last file " ) , _ ( " Start with last file " ) } ,
}
local set_sw_table = function ( start_with )
return {
text = start_withs [ start_with ] [ 2 ] ,
checked_func = function ( )
return start_with_setting == start_with
end ,
callback = function ( )
start_with_setting = start_with
G_reader_settings : saveSetting ( " start_with " , start_with )
end ,
}
end
return {
text_func = function ( )
2020-02-03 20:08:18 +01:00
return T (
2017-08-14 13:15:12 +02:00
_ ( " Start with: %1 " ) ,
start_withs [ start_with_setting ] [ 1 ]
)
end ,
sub_item_table = {
set_sw_table ( " filemanager " ) ,
set_sw_table ( " history " ) ,
2020-08-26 20:44:56 +02:00
set_sw_table ( " favorites " ) ,
2018-09-12 21:42:24 +02:00
set_sw_table ( " folder_shortcuts " ) ,
2017-08-14 13:15:12 +02:00
set_sw_table ( " last " ) ,
}
}
end
2021-05-18 20:54:54 +02:00
--- @note: This is the *only* safe way to instantiate a new FileManager instance!
2017-10-13 18:43:02 +02:00
function FileManager : showFiles ( path , focused_file )
2021-05-11 00:49:35 +02:00
-- Warn about and close any pre-existing FM instances first...
if FileManager.instance then
logger.warn ( " FileManager instance mismatch! Tried to spin up a new instance, while we still have an existing one: " , tostring ( FileManager.instance ) )
-- Close the old one first!
FileManager.instance : onClose ( )
end
2017-07-01 12:11:44 +02:00
path = path or G_reader_settings : readSetting ( " lastdir " ) or filemanagerutil.getDefaultDir ( )
2016-02-01 18:25:23 -08:00
G_reader_settings : saveSetting ( " lastdir " , path )
2020-07-01 16:17:41 -04:00
self : setRotationMode ( )
2016-02-01 18:25:23 -08:00
local file_manager = FileManager : new {
dimen = Screen : getSize ( ) ,
2018-03-17 23:02:32 +01:00
covers_fullscreen = true , -- hint for UIManager:_repaint()
2016-02-01 18:25:23 -08:00
root_path = path ,
2017-10-21 19:53:56 +02:00
focused_file = focused_file ,
2016-02-01 18:25:23 -08:00
}
UIManager : show ( file_manager )
end
2020-05-02 23:02:36 +02:00
--- A shortcut to execute mv.
-- @treturn boolean result of mv command
2016-02-19 01:03:27 +08:00
function FileManager : moveFile ( from , to )
2020-02-03 20:08:18 +01:00
return BaseUtil.execute ( self.mv_bin , from , to ) == 0
2016-02-19 01:03:27 +08:00
end
2020-05-02 23:02:36 +02:00
--- A shortcut to execute cp.
-- @treturn boolean result of cp command
function FileManager : copyFileFromTo ( from , to )
return BaseUtil.execute ( self.cp_bin , from , to ) == 0
end
--- A shortcut to execute cp recursively.
-- @treturn boolean result of cp command
function FileManager : copyRecursive ( from , to )
return BaseUtil.execute ( self.cp_bin , " -r " , from , to ) == 0
end
2018-03-14 22:16:38 +01:00
function FileManager : onHome ( )
return self : goHome ( )
end
2021-05-05 21:43:43 +03:00
function FileManager : onRefreshContent ( )
self : onRefresh ( )
UIManager : show ( InfoMessage : new {
text = _ ( " Content refreshed. " ) ,
timeout = 2 ,
} )
end
2013-10-18 22:38:07 +02:00
return FileManager