From 2ea4db8e38a549b0869d94b9ee3c961767e82955 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 2 Mar 2018 14:07:29 -0500 Subject: [PATCH] Added windows and macOS external executable compatibility. Added functions set_executable_path_preference(), get_executable_path_preference(), and executable_path_widget() to lib/dtutils/file.lua to get, set and retrieve the location of executables on windows and macOS systems. Modified check_if_bin_exists to use the path preferences so executables can be found on windows and macOS systems as well as linux. Modified contrib/gimp.lua to use the new functions, thus making it usable on windows and macOS too. --- contrib/gimp.lua | 179 ++++++++----------------------------------- lib/dtutils/file.lua | 133 +++++++++++++++++++++++++++++--- 2 files changed, 154 insertions(+), 158 deletions(-) diff --git a/contrib/gimp.lua b/contrib/gimp.lua index f8472c7..c0fba7e 100644 --- a/contrib/gimp.lua +++ b/contrib/gimp.lua @@ -67,109 +67,38 @@ local dt = require "darktable" local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext +local gimp_widget = nil -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +dt.configuration.check_version(...,{5,0,0}) -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/locale/") -local function split_filepath(str) - local result = {} - -- Thank you Tobias Jakobs for the awesome regular expression, which I tweaked a little - result["path"], result["filename"], result["basename"], result["filetype"] = string.match(str, "(.-)(([^\\/]-)%.?([^%.\\/]*))$") - return result -end - -local function get_path(str) - local parts = split_filepath(str) - return parts["path"] -end - -local function get_filename(str) - local parts = split_filepath(str) - return parts["filename"] -end - -local function get_basename(str) - local parts = split_filepath(str) - return parts["basename"] -end - -local function get_filetype(str) - local parts = split_filepath(str) - return parts["filetype"] -end - local function _(msgid) return gettext.dgettext("gimp", msgid) end --- Thanks Tobias Jakobs for the idea and the correction -function checkIfFileExists(filepath) - local file = io.open(filepath,"r") - local ret - if file ~= nil then - io.close(file) - dt.print_error("true checkIfFileExists: "..filepath) - ret = true - else - dt.print_error(filepath.." not found") - ret = false - end - return ret -end - -local function filename_increment(filepath) - - -- break up the filepath into parts - local path = get_path(filepath) - local basename = get_basename(filepath) - local filetype = get_filetype(filepath) - - -- check to see if we've incremented before - local increment = string.match(basename, "_(%d-)$") - - if increment then - -- we do 2 digit increments so make sure we didn't grab part of the filename - if string.len(increment) > 2 then - -- we got the filename so set the increment to 01 - increment = "01" - else - increment = string.format("%02d", tonumber(increment) + 1) - basename = string.gsub(basename, "_(%d-)$", "") - end - else - increment = "01" - end - local incremented_filepath = path .. basename .. "_" .. increment .. "." .. filetype - - dt.print_error("original file was " .. filepath) - dt.print_error("incremented file is " .. incremented_filepath) - - return incremented_filepath -end - -local function groupIfNotMember(img, new_img) +local function group_if_not_member(img, new_img) local image_table = img:get_group_members() local is_member = false for _,image in ipairs(image_table) do - dt.print_error(image.filename .. " is a member") + dt.print_log(image.filename .. " is a member") if image.filename == new_img.filename then is_member = true - dt.print_error("Already in group") + dt.print_log("Already in group") end end if not is_member then - dt.print_error("group leader is "..img.group_leader.filename) + dt.print_log("group leader is "..img.group_leader.filename) new_img:group_with(img.group_leader) - dt.print_error("Added to group") + dt.print_log("Added to group") end end local function sanitize_filename(filepath) - local path = get_path(filepath) - local basename = get_basename(filepath) - local filetype = get_filetype(filepath) + local path = df.get_path(filepath) + local basename = df.get_basename(filepath) + local filetype = df.get_filetype(filepath) local sanitized = string.gsub(basename, " ", "\\ ") @@ -181,60 +110,11 @@ local function show_status(storage, image, format, filename, dt.print(string.format(_("Export Image %i/%i"), number, total)) end -local function fileCopy(fromFile, toFile) - local result = nil - -- if cp exists, use it - if df.check_if_bin_exists("cp") then - result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'") - end - -- if cp was not present, or if cp failed, then a pure lua solution - if not result then - local fileIn, err = io.open(fromFile, 'rb') - if fileIn then - local fileOut, errr = io.open(toFile, 'w') - if fileOut then - local content = fileIn:read(4096) - while content do - fileOut:write(content) - content = fileIn:read(4096) - end - result = true - fileIn:close() - fileOut:close() - else - dt.print_error("fileCopy Error: " .. errr) - end - else - dt.print_error("fileCopy Error: " .. err) - end - end - return result -end - -local function fileMove(fromFile, toFile) - local success = os.rename(fromFile, toFile) - if not success then - -- an error occurred, so let's try using the operating system function - if df.check_if_bin_exists("mv") then - success = os.execute("mv '" .. fromFile .. "' '" .. toFile .. "'") - end - -- if the mv didn't exist or succeed, then... - if not success then - -- pure lua solution - success = fileCopy(fromFile, toFile) - if success then - os.remove(fromFile) - else - dt.print_error("fileMove Error: Unable to move " .. fromFile .. " to " .. toFile .. ". Leaving " .. fromFile .. " in place.") - dt.print(string.format(_("Unable to move edited file into collection. Leaving it as %s"), fromFile)) - end - end - end - return success -- nil on error, some value if success -end - local function gimp_edit(storage, image_table, extra_data) --finalize - if not df.check_if_bin_exists("gimp") then + + local gimp_executable = df.check_if_bin_exists("gimp") + + if not gimp_executable then dt.print_error(_("GIMP not found")) return end @@ -253,9 +133,9 @@ local function gimp_edit(storage, image_table, extra_data) --finalize dt.print(_("Launching GIMP...")) local gimpStartCommand - gimpStartCommand = "gimp "..img_list + gimpStartCommand = gimp_executable .. " " .. img_list - dt.print_error(gimpStartCommand) + dt.print_log(gimpStartCommand) dt.control.execute( gimpStartCommand) @@ -266,28 +146,28 @@ local function gimp_edit(storage, image_table, extra_data) --finalize for image,exported_image in pairs(image_table) do - local myimage_name = image.path .. "/" .. get_filename(exported_image) + local myimage_name = image.path .. "/" .. df.get_filename(exported_image) - while checkIfFileExists(myimage_name) do - myimage_name = filename_increment(myimage_name) + while df.check_if_file_exists(myimage_name) do + myimage_name = df.filename_increment(myimage_name) -- limit to 99 more exports of the original export - if string.match(get_basename(myimage_name), "_(d-)$") == "99" then + if string.match(df.get_basename(myimage_name), "_(d-)$") == "99" then break end end - dt.print_error("moving " .. exported_image .. " to " .. myimage_name) - local result = fileMove(exported_image, myimage_name) + dt.print_log("moving " .. exported_image .. " to " .. myimage_name) + local result = df.file_move(exported_image, myimage_name) if result then - dt.print_error("importing file") + dt.print_log("importing file") local myimage = dt.database.import(myimage_name) - groupIfNotMember(image, myimage) + group_if_not_member(image, myimage) for _,tag in pairs(dt.tags.get_tags(image)) do if not (string.sub(tag.name,1,9) == "darktable") then - dt.print_error("attaching tag") + dt.print_log("attaching tag") dt.tags.attach(tag,myimage) end end @@ -297,6 +177,13 @@ local function gimp_edit(storage, image_table, extra_data) --finalize end -- Register -dt.register_storage("module_gimp", _("Edit with GIMP"), show_status, gimp_edit) + +local executables = {"gimp"} + +if dt.configuration.running_os ~= "linux" then + gimp_widget = df.executable_path_widget(executables) +end + +dt.register_storage("module_gimp", _("Edit with GIMP"), show_status, gimp_edit, nil, nil, gimp_widget) -- diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 22cd5e5..696cdbc 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -23,7 +23,7 @@ dtutils_file.libdoc = { local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +dt.configuration.check_version(...,{5,0,0}) -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("dtutils.file",dt.configuration.config_dir.."/lua/locale/") @@ -34,14 +34,18 @@ end dtutils_file.libdoc.functions["check_if_bin_exists"] = { Name = [[check_if_bin_exists]], - Synopsis = [[check if an executable is in the path]], + Synopsis = [[check if an executable exists]], Usage = [[local df = require "lib/dtutils.file" local result = df.check_if_bin_exists(bin) bin - string - the binary to check for]], - Description = [[check_if_bin_exists checks to see if the specified binary executable is - in the path.]], - Return_Value = [[result - boolean - true if the executable was found, false if not]], + Description = [[check_if_bin_exists checks to see if the specified binary exists. + check_if_bin_exists first checks to see if a preference for the binary has been + registered and uses that if found. The presence of the file is verified, then + quoted and returned. If no preference is specified and the operating system is + linux then the which command is used to check for a binary in the path. If found + that path is returned. If no binary is found, false is returned.]], + Return_Value = [[result - string - the path of the binary, false if not found]], Limitations = [[]], Example = [[]], See_Also = [[]], @@ -51,17 +55,27 @@ dtutils_file.libdoc.functions["check_if_bin_exists"] = { } function dtutils_file.check_if_bin_exists(bin) - local result - if (dt.configuration.running_os == 'linux') then - result = os.execute("which "..bin) + local result = false + local path = nil + + if string.match(bin, "/") or string.match(bin, "\\") then + path = bin else - result = dtutils_file.check_if_file_exists(bin) + path = dtutils_file.get_executable_path_preference(bin) end - if not result then - result = false + if string.len(path) > 0 then + if dtutils_file.check_if_file_exists(path) then + result = "\"" .. path .. "\"" + end + elseif dt.configuration.running_os == "linux" then + local p = io.popen("which " .. bin) + local output = p:read("*a") + p:close() + if string.len(output) > 0 then + result = output:sub(1,-2) + end end - return result end @@ -416,4 +430,99 @@ function dtutils_file.create_unique_filename(filepath) return filepath end + +dtutils_file.libdoc.functions["set_executable_path_preference"] = { + Name = [[set_executable_path_preference]], + Synopsis = [[set a preference for the path to an executable]], + Usage = [[local df = require "lib/dtutils.file" + + df.set_executable_path_preference(executable, path) + executable - string - the name of the executable to set the path for + path - string - the path to the binary]], + Description = [[set_executable_path_preference takes an executable name and path to the + executable and registers the preference for later use.]], + Return_Value = [[]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_file.set_executable_path_preference(executable, path) + dt.preferences.write("executable_paths", executable, "string", path) +end + + +dtutils_file.libdoc.functions["get_executable_path_preference"] = { + Name = [[get_executable_path_preference]], + Synopsis = [[return the path to an executable from a preference]], + Usage = [[local df = require "lib/dtutils.file" + + local result = df.get_executable_path_preference(executable) + executable - string - the name of the executable to get the path for]], + Description = [[get_executable_path_preference returns the path preference to + the requested executable.]], + Return_Value = [[result - string - path to the executable]], + Limitations = [[executable should be the basename of the executable without extensions]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_file.get_executable_path_preference(executable) + return dt.preferences.read("executable_paths", executable, "string") +end + + +dtutils_file.libdoc.functions["executable_path_widget"] = { + Name = [[executable_path_widget]], + Synopsis = [[create a widget to get executable path preferences]], + Usage = [[local df = require "lib/dtutils.file" + + local widget = df.executable_path_widget(executables) + executables - table - a table of strings that are executable names]], + Description = [[executable_path_widget takes a table of executable names + and builds a set of file selector widgets to get the path to the executable. + The resulting widgets are wrapped in a box widget and returned.]], + Return_Value = [[widget - widget - a widget containing a file selector widget for + each executable.]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_file.executable_path_widget(executables) + local box_widgets = {} + table.insert(box_widgets, dt.new_widget("section_label"){label = "select executable(s)"}) + for _, executable in pairs(executables) do + table.insert(box_widgets, dt.new_widget("label"){label = "select " .. executable .. " executable"}) + local path = dtutils_file.get_executable_path_preference(executable) + if not path then + path = "" + end + table.insert(box_widgets, dt.new_widget("file_chooser_button"){ + title = "select " .. executable .. " executable", + value = path, + is_directory = false, + changed_callback = function(self) + if dtutils_file.check_if_bin_exists(self.value) then + dtutils_file.set_executable_path_preference(executable, self.value) + end + end} + ) + end + local box = dt.new_widget("box"){ + orientation = "vertical", + table.unpack(box_widgets) + } + return box +end + return dtutils_file \ No newline at end of file