diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 684d488..0d49f3c 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -1,6 +1,6 @@ --[[ This file is part of darktable, - copyright (c) 2018 Bill Ferguson + copyright (c) 2018, 2020 Bill Ferguson darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,29 +18,42 @@ --[[ script_manager.lua - a tool for managing the darktable lua scripts - script_manager is designed to run as a standalone script so that it - may be used as a drop in luarc file in the user's $HOME/.config/darktable - ($HOME/AppData/Local/darktable on windows) directory. It may also be - required from a luarc file. + script_manager is designed to be called from the users luarc file and used to + manage the lua scripts. - On startup script_manager checks to see if there is an existing scripts directory. - If there is an existing lua scripts directory then it is read to see what scripts are present. - Scripts are sorted by "category" based on what subdirectory they are found in, thus with a lua - scripts directory that matched the current repository the categories would be contrib, examples, - offical, and tools. Each script has an Enable/Disable button to enable or disable the script. + On startup script_manager scans the lua scripts directory to see what scripts are present. + Scripts are sorted by 'category' based on what sub-directory they are in. With no + additional script repositories iinstalled, the categories are contrib, examples, official + and tools. When a category is selected the buttons show the script name and whether the + script is started or stopped. The button is a toggle, so if the script is stopped click + the button to start it and vice versa. - A link is created to the user's Downloads directory on linux, unix and MacOS. Windows users must create the - link manually using mklink.exe. Additional "un-official" scripts may be downloaded - from other sources and placed in the users Downloads directory. These scripts all fall in a downloads category. - They also each have an Enable/Disable button. + Features + + * the number of script buttons shown can be changed to any number between 5 and 20. The + default is 10 buttons. This can be changed in the configuration action. + + * additional repositories of scripts may be installed using from the install/update action. + + * installed scripts can be updated from the install/update action. This includes extra + repositories that have been installed. + + * the scripts can be disabled if desired from the install/update action. This can only + be reversed manually. To enable the "Disable Scripts" button, check the checkbox to + endable it. This is to prevent accidentally disabling the scripts. Click the + "Disable Scripts" button and the luarc file is renamed to luarc.disable. If at + a later time you want to enable the scripts again, simply rename the luarc.disabled + file to luarc and the scripts will run. ]] + local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" local ds = require "lib/dtutils.string" local dtsys = require "lib/dtutils.system" +local log = require "lib/dtutils.log" local gettext = dt.gettext @@ -52,22 +65,41 @@ local function _(msgid) return gettext.dgettext("script_manager", msgid) end -collectgarbage("stop") +-- api check + +du.check_min_api_version("5.0.0", "script_manager") + -- - - - - - - - - - - - - - - - - - - - - - - - -- C O N S T A N T S -- - - - - - - - - - - - - - - - - - - - - - - - + -- path separator local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- command separator local CS = dt.configuration.running_os == "windows" and "&" or ";" +local MODULE = "script_manager" + +local MIN_BUTTONS_PER_PAGE = 5 +local MAX_BUTTONS_PER_PAGE = 20 +local DEFAULT_BUTTONS_PER_PAGE = 10 + +local DEFAULT_LOG_LEVEL = log.error + local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" -local CURR_API_VERSION = dt.configuration.api_version_string -dt.print_log("LUA_DIR is " .. LUA_DIR) +local CURR_API_STRING = dt.configuration.api_version_string + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- L O G L E V E L +-- - - - - - - - - - - - - - - - - - - - - - - - + +local old_log_level = log.log_level() + +log.log_level(DEFAULT_LOG_LEVEL) -- - - - - - - - - - - - - - - - - - - - - - - - -- N A M E S P A C E @@ -76,20 +108,47 @@ dt.print_log("LUA_DIR is " .. LUA_DIR) local script_manager = {} local sm = script_manager +sm.executables = {} +sm.executables.git = df.check_if_bin_exists("git") + +sm.module_installed = false +sm.event_registered = false + +-- set up tables to contain all the widgets and choices + +sm.widgets = {} +sm.categories = {} +sm.scripts = {} +sm.page_status = {} +sm.page_status.num_buttons = DEFAULT_BUTTONS_PER_PAGE +sm.page_status.buttons_created = 0 +sm.page_status.current_page = 0 +sm.page_status.category = "" + +-- installed script repositories +sm.installed_repositories = { + {name = "lua-scripts", directory = LUA_DIR}, +} + + +-- don't let it run until everything is in place +sm.run = false + -- - - - - - - - - - - - - - - - - - - - - - - - -- F U N C T I O N S -- - - - - - - - - - - - - - - - - - - - - - - - -local function prequire(script) - dt.print_log("Loading " .. script) - local status, lib = pcall(require, script) - if status then - dt.print_log("Loaded " .. script) - else - dt.print_error("Error loading " .. script) - dt.print_error(lib) +local function pref_read(name, pref_type) + log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type) + local val = dt.preferences.read(MODULE, name, pref_type) + if not string.match(pref_type, "bool") then + log.msg(log.debug, "read value " .. tostring(val)) end - return status, lib + return val +end + +local function pref_write(name, pref_type, value) + dt.preferences.write(MODULE, name, pref_type, value) end local function update_combobox_choices(combobox, choice_table, selected) @@ -103,70 +162,84 @@ local function update_combobox_choices(combobox, choice_table, selected) combobox[j] = nil end end + if not selected then + selected = 1 + end + combobox.value = selected end -local function install_scripts() - local result = false - - if df.check_if_file_exists(LUA_DIR) then - if df.check_if_file_exists(LUA_DIR .. ".orig") then - if dt.configuration.running_os == "windows" then - os.execute("rmdir /s " .. LUA_DIR .. ".orig") - else - os.execute("rm -rf " .. LUA_DIR .. ".orig") - end - end - os.rename(LUA_DIR, LUA_DIR .. ".orig") +local function add_script_category(category) + if #sm.categories == 0 or not string.match(du.join(sm.categories, " "), ds.sanitize_lua(category)) then + table.insert(sm.categories, category) + sm.scripts[category] = {} + log.msg(log.debug, "created category " .. category) end - - local git = df.check_if_bin_exists("git") - - if not git then - dt.print("ERROR: git not found. Install or specify the location of the git executable.") - return - end - - local git_command = git .. " clone " .. sm.lua_repository .. " " .. LUA_DIR - dt.print_log("install git command is " .. git_command) - - result = os.execute(git_command) - - if not df.check_if_file_exists(LUA_DIR .. PS .. "downloads") then - os.execute("mkdir " .. LUA_DIR .. PS .. "downloads") - end - - return result end -local function update_scripts() - local result = false - - local git = df.check_if_bin_exists("git") - - if not git then - dt.print("ERROR: git not found. Install or specify the location of the git executable.") - return - end - - local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " pull" - dt.print_log("update git command is " .. git_command) - - if dt.configuration.running_os == "windows" then - result = dtsys.windows_command(git_command) +local function get_script_doc(script) + local description = nil + f = io.open(LUA_DIR .. PS .. script .. ".lua") + if f then + -- slurp the file + local content = f:read("*all") + f:close() + -- assume that the second block comment is the documentation + description = string.match(content, "%-%-%[%[.-%]%].-%-%-%[%[(.-)%]%]") else - result = os.execute(git_command) + log.msg(log.error, _("Cant read from " .. script)) end - - if dt.preferences.read("script_manager", "use_lua_scripts_version", "bool") and - dt.configuration.running_os == "windows" then - use_lua_scripts_version() + if description then + return description + else + return "No documentation available" end - - return result end -local function add_script_data(script_file) +local function activate(script) + log.msg(log.info, "activating " .. script.name) + local status, err = du.prequire(script.path) + if status then + pref_write(script.script_name, "bool", true) + log.msg(log.screen, _("Loaded ") .. script.script_name) + script.running = true + else + log.msg(log.screen, script.script_name .. _(" failed to load")) + log.msg(log.error, "Error loading " .. script.script_name) + log.msg(log.error, "Error message: " .. err) + end + return status +end + +local function deactivate(script) + -- presently the lua api doesn't support unloading gui elements therefore + -- we just mark then inactive for the next time darktable starts + + -- deactivate it.... + + pref_write(script.script_name, "bool", false) + script.running = false + log.msg(log.info, "setting " .. script.script_name .. " to not start") + log.msg(log.screen, script.name .. _(" will not start when darktable is restarted")) +end + +local function add_script_name(name, path, category) + log.msg(log.debug, "category is " .. category) + log.msg(log.debug, "name is " .. name) + local script = { + name = name, + path = category .. "/" .. path .. name, + running = false, + doc = get_script_doc(category .. "/" .. path .. name), + script_name = category .. "/" .. name + } + table.insert(sm.scripts[category], script) + if pref_read(script.script_name, "bool") then + activate(script) + end +end + +local function process_script_data(script_file) -- the script file supplied is category/filename.filetype -- the following pattern splits the string into category, path, name, fileename, and filetype @@ -185,274 +258,382 @@ local function add_script_data(script_file) pattern = "(.-)\\(.-)(([^\\]-)%.?([^%.\\]*))$" end - dt.print_log("processing " .. script_file) + log.msg(log.info, "processing " .. script_file) -- add the script data local category,path,name,filename,filetype = string.match(script_file, pattern) - dt.print_log("category is " .. category) - dt.print_log("name is " .. name) + log.msg(log.debug, "category is " .. category) + log.msg(log.debug, "name is " .. name) + + add_script_category(category) - if #sm.script_categories == 0 or not string.match(du.join(sm.script_categories, " "), category) then - sm.script_categories[#sm.script_categories + 1] = category - sm.script_names[category] = {} - end if name then - dt.print_log("category is " .. category) - dt.print_log("name is " .. name) - if not string.match(du.join(sm.script_names[category], " "), name) then - sm.script_names[category][#sm.script_names[category] + 1] = name - sm.script_paths[category .. "/" .. name] = category .. "/" .. path .. name - if category == "downloads" then - sm.have_downloads = true - end - end + add_script_name(name, path, category) end end -local function scan_scripts() - local find_cmd = "find -L " .. LUA_DIR .. " -name \\*.lua -print | sort" +local function scan_scripts(script_dir) + local script_count = 0 + local find_cmd = "find -L " .. script_dir .. " -name \\*.lua -print | sort" if dt.configuration.running_os == "windows" then - find_cmd = "dir /b/s " .. LUA_DIR .. "\\*.lua | sort" + find_cmd = "dir /b/s " .. script_dir .. "\\*.lua | sort" end + log.msg(log.debug, _("find command is ") .. find_cmd) -- scan the scripts local output = io.popen(find_cmd) for line in output:lines() do - local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off - local script_file = l:sub(1,-5) - if not string.match(script_file, "script_manager") then -- let's not include ourself - if not string.match(script_file, "plugins") then -- skip plugins - if not string.match(script_file, "lib" .. PS) then -- let's not try and run libraries - if not string.match(script_file, "include_all") then -- skip include_all.lua - if not string.match(script_file, "yield") then -- special case, because everything needs this - add_script_data(script_file) - else - -- prequire(script_file) -- load yield.lua + local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off + local script_file = l:sub(1,-5) -- strip off .lua\n + if not string.match(script_file, "script_manager") then -- let's not include ourself + if not string.match(script_file, "plugins") then -- skip plugins + if not string.match(script_file, "lib" .. PS) then -- let's not try and run libraries + if not string.match(script_file, "%.git") then -- don't match files in the .git directory + process_script_data(script_file) + script_count = script_count + 1 + end + end + end + end + end + return script_count +end + +local function update_scripts() + local result = false + + local git = sm.executables.git + + if not git then + dt.print("ERROR: git not found. Install or specify the location of the git executable.") + return + end + + local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " pull" + log.msg(log.debug, "update git command is " .. git_command) + + if dt.configuration.running_os == "windows" then + result = dtsys.windows_command(git_command) + else + result = os.execute(git_command) + end + + if result == 0 then + dt.print(_("lua scripts successfully updated")) + end + + return result +end + +local function update_script_update_choices() + local installs = {} + local pref_string = "" + for i, repo in ipairs(sm.installed_repositories) do + table.insert(installs, repo.name) + pref_string = pref_string .. i .. "," .. repo.name .. "," .. repo.directory .. "," + end + update_combobox_choices(sm.widgets.update_script_choices, installs, 1) + log.msg(log.debug, "repo pref string is " .. pref_string) + pref_write("installed_repos", "string", pref_string) +end + +local function scan_repositories() + local script_count = 0 + local find_cmd = "find -L " .. LUA_DIR .. " -name \\*.git -print | sort" + if dt.configuration.running_os == "windows" then + find_cmd = "dir /b/s /a:d " .. LUA_DIR .. PS .. "*.git | sort" + end + log.msg(log.debug, _("find command is ") .. find_cmd) + local output = io.popen(find_cmd) + for line in output:lines() do + local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off + local category = string.match(l, "(.-)" .. PS) -- get everything to teh first / + if category then -- if we have a category (.git doesn't) + log.msg(log.debug, "found category " .. category) + if not string.match(category, "plugins") and not string.match(category, "%.git") then -- skip plugins + if #sm.installed_repositories == 1 then + log.msg(log.debug, "only 1 repo, adding " .. category) + table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + else + log.msg(log.debug, "more than 1 repo, we have to search the repos to make sure it's not there") + local found = nil + for _, repo in ipairs(sm.installed_repositories) do + if string.match(repo.name, ds.sanitize_lua(category)) then + log.msg(log.debug, "matched " .. repo.name) + found = true + break end end + if not found then + table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + end end end end end - -- work around because we can't dynamically add a new stack child. We create an empty child that will be - -- populated with downloads as they occur. If there are already downloads then this is just ignored - - add_script_data("downloads" .. PS) + update_script_update_choices() end --- get the script documentation, with some assumptions -local function get_script_doc(script) - local description = nil - f = io.open(LUA_DIR .. PS .. script .. ".lua") - if f then - -- slurp the file - local content = f:read("*all") - f:close() - -- assume that the second block comment is the documentation - description = string.match(content, "%-%-%[%[.-%]%].-%-%-%[%[(.-)%]%]") - else - dt.print_error("Cant read from " .. script) +local function install_scripts() + local url = sm.widgets.script_url.text + local category = sm.widgets.new_category.text + + if string.match(du.join(sm.categories, " "), ds.sanitize_lua(category)) then + log.msg(log.screen, _("category ") .. category .. _(" is already in use. Please specify a different category name.")) + log.msg(log.error, "category " .. category .. " already exists, returning...") + return end - if description then - return description - else - return "No documentation available" + + local result = false + + local git = sm.executables.git + + if not git then + dt.print("ERROR: git not found. Install or specify the location of the git executable.") + return end -end -local function activate(script, scriptname) - dt.print_log("activating " .. scriptname) - local status, err = prequire(sm.script_paths[script]) - if status then - dt.preferences.write("script_manager", script, "bool", true) - dt.print("Loaded " .. scriptname) + local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " clone " .. url .. " " .. category + log.msg(log.debug, "update git command is " .. git_command) + + if dt.configuration.running_os == "windows" then + result = dtsys.windows_command(git_command) else - dt.print(scriptname .. " failed to load") - dt.print_error("Error loading " .. scriptname) - dt.print_error("Error message: " .. err) + result = dtsys.external_command(git_command) end - return status + + log.msg(log.info, "result from import is " .. result) + + if result == 0 then + local count = scan_scripts(LUA_DIR .. PS .. category) + if count > 0 then + update_combobox_choices(sm.widgets.category_selector, sm.categories, sm.widgets.category_selector.selected) + dt.print(_("scripts successfully installed into category ") .. category) + table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + update_script_update_choices() + for i = 1, #sm.widgets.category_selector do + if string.match(sm.widgets.category_selector[i], ds.sanitize_lua(category)) then + log.msg(log.debug, "setting category selector to " .. i) + sm.widgets.category_selector.selected = i + break + end + i = i + 1 + end + log.msg(log.debug, "clearing text fields") + sm.widgets.script_url.text = "" + sm.widgets.new_category.text = "" + sm.widgets.main_menu.selected = 3 + else + dt.print(_("No scripts found to install")) + log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to category_selector") + end + else + dt.print(_("failed to download scripts")) + end + + return result end -local function deactivate(script, scriptname) - -- presently the lua api doesn't support unloading gui elements therefore - -- we just mark then inactive for the next time darktable starts - - -- deactivate it.... - - dt.preferences.write("script_manager", script, "bool", false) - dt.print_log("setting " .. scriptname .. " to not start") - dt.print(scriptname .. " will not be active when darktable is restarted") +local function clear_button(number) + local button = sm.widgets.buttons[number] + button.label = "" + button.sensitive = false end -local function create_enable_disable_button(btext, sname, req) - local button = dt.new_widget("button") - { - label = btext .. sname, - tooltip = get_script_doc(req), - clicked_callback = function (self) - -- split the label into action and target - local action, target = string.match(self.label, "(.+) (.+)") - -- load the script if it's not loaded - dt.print_log("Looking for target " .. target) - local scat = sm.category_selector.value - -- check, and fix, the filename for a - since it is a lua magic character in string match - dt.print_log("checking category " .. scat .. " for target " .. target) - local starget = du.join({scat, target}, "/") - dt.print_log("starget to activate is " .. starget) - dt.print_log("target to activate is " .. target) - if action == "Enable" then - local status = activate(starget, target) - if status then - self.label = "Disable " .. target +local function find_script(category, name) + for _, script in ipairs(sm.scripts[category]) do + if string.match(script.name, "^" .. ds.sanitize_lua(name) .. "$") then + return script + end + end + return nil +end + +local function populate_buttons(category, first, last) + log.msg(log.debug, "category is " .. category .. " and first is " .. first .. " and last is " .. last) + local button_num = 1 + for i = first, last do + script = sm.scripts[category][i] + button = sm.widgets.buttons[button_num] + if script.running then + button.label = script.name .. " started" + else + button.label = script.name .. " stopped" + end + if CURR_API_STRING >= "6.0.1" then + button.ellipsize = "middle" + end + button.sensitive = true + button.tooltip = script.doc + button.clicked_callback = function (this) + local script_name, state = string.match(this.label, "(.-) (.+)") + local script = find_script(sm.widgets.category_selector.value, script_name) + if script then + log.msg(log.debug, "found script " .. script.name .. " with path " .. script.path) + if string.match(state, "started") then + log.msg(log.debug, "deactivating " .. script.name .. " on " .. script.path .. " for button " .. this.label) + deactivate(script) + this.label = script.name .. " stopped" + else + log.msg(log.debug, "activating " .. script.name .. " on " .. script.path .. " for button " .. this.label) + local result = activate(script) + if result then + this.label = script.name .. " started" + end end else - deactivate(starget, target) - self.label = "Enable " .. target + log.msg(log.error, "script " .. script_name .. " not found") end end - } - if CURR_API_VERSION >= "6.0.1" then - button.ellipsize = "middle" + button_num = button_num + 1 end - return button -end - -local function load_script_stack() - -- load the scripts - table.sort(sm.script_categories) - for _,cat in ipairs(sm.script_categories) do - local tmp = {} - table.sort(sm.script_names[cat]) - if not sm.script_widgets[cat] then - for _,sname in ipairs(sm.script_names[cat]) do - local req = du.join({cat, sname}, "/") - local btext = "Enable " - if dt.preferences.read("script_manager", req, "bool") then - local status, err = prequire(sm.script_paths[req]) - if status then - btext = "Disable " - else - dt.print_error("Error loading " .. sname) - dt.print_error("Error message: " .. err) - end - else - dt.preferences.write("script_manager", req, "bool", false) - end - tmp[#tmp + 1] = create_enable_disable_button(btext, sname, req) - end - - sm.script_widgets[cat] = dt.new_widget("box") - { - orientation = "vertical", - table.unpack(tmp), - } - elseif #sm.script_widgets[cat] ~= #sm.script_names[cat] then - for index,sname in ipairs(sm.script_names[cat]) do - local req = du.join({cat, sname}, "/") - dt.print_error("script is " .. sname .. " and index is " .. index) - if sm.script_widgets[cat][index] then - sm.script_widgets[cat][index] = nil - end - sm.script_widgets[cat][index] = create_enable_disable_button("Enable ", sname, req) - end + if button_num <= sm.page_status.num_buttons then + for i = button_num, sm.page_status.num_buttons do + clear_button(i) end end - if not sm.script_stack then - sm.script_stack = dt.new_widget("stack"){} - for i,cat in ipairs(sm.script_categories) do - sm.script_stack[i] = sm.script_widgets[cat] +end + +local function paginate(direction) + local category = sm.page_status.category + log.msg(log.debug, "category is " .. category) + local num_scripts = #sm.scripts[category] + log.msg(log.debug, "num_scripts is " .. num_scripts) + local max_pages = math.ceil(num_scripts / sm.page_status.num_buttons) + local cur_page = sm.page_status.current_page + log.msg(log.debug, "max pages is " .. max_pages) + + local buttons_needed = nil + local first = nil + local last = nil + + if direction == 0 then + cur_page = cur_page - 1 + if cur_page < 1 then + cur_page = 1 end - sm.script_stack.active = 1 - end -end - -local function update_stack_choices(combobox, choice_table) - sm.have_downloads = true - local items = #combobox - local choices = #choice_table - if #sm.script_widgets["downloads"] == 0 then - choices = choices - 1 - sm.have_downloads = false - end - cnt = 1 - for i, name in ipairs(choice_table) do - if (name == "downloads" and sm.have_downloads) or name ~= "downloads" then - combobox[cnt] = name - cnt = cnt + 1 + elseif direction == 1 then + cur_page = cur_page + 1 + if cur_page > max_pages then + cur_page = max_pages end - end - if choices < items then - for j = items, choices + 1, -1 do - combobox[j] = nil - end - end - combobox.value = 1 -end - -local function build_scripts_block() - -- build the whole script block - scan_scripts() - - -- set up the stack for the choices - load_script_stack() - - if not sm.category_selector then - -- set up the combobox for the categories - - sm.category_selector = dt.new_widget("combobox"){ - label = "Category", - tooltip = "Select the script category", - value = 1, "placeholder", - changed_callback = function(self) - local cnt = 1 - for i,cat in ipairs(sm.script_categories) do - if cat == self.value then - sm.script_stack.active = i - end - end - end - } - end - - update_stack_choices(sm.category_selector, sm.script_categories) - - if not sm.scripts then - sm.scripts = dt.new_widget("box"){ - orientation = "vertical", - dt.new_widget("label"){ label = "Scripts" }, - sm.category_selector, - sm.script_stack, - } - end -end - -local function insert_scripts_block() - table.insert(sm.main_menu_choices, "Enable/Disable Scripts") - update_combobox_choices(sm.main_menu, sm.main_menu_choices, 1) - sm.main_stack[#sm.main_stack + 1] = sm.scripts -end - -local function use_lua_scripts_version() - if dt.configuration.running_os == "windows" then - -- copy tools\script_manger.lua to luarc - df.file_copy(LUA_DIR .. PS .. "tools\\script_manager.lua", dt.configuration.config_dir .. PS .. "luarc") else - -- create a symbolic link from luarc to tools/script_manager.lua - if df.check_if_file_exists(dt.configuration.config_dir .. "/luarc") then - os.remove(dt.configuration.config_dir .. "/luarc") - end - os.execute("ln -s " .. LUA_DIR .. "/tools/script_manager.lua " .. dt.configuration.config_dir .. "/luarc") + log.msg(log.debug, "took path 2") + cur_page = 1 end + log.msg(log.debug, "cur_page is " .. cur_page .. " and max_pages is " .. max_pages) + if cur_page == max_pages and cur_page == 1 then + sm.widgets.page_forward.sensitive = false + sm.widgets.page_back.sensitive = false + elseif cur_page == max_pages then + sm.widgets.page_forward.sensitive = false + sm.widgets.page_back.sensitive = true + elseif cur_page == 1 then + sm.widgets.page_forward.sensitive = true + sm.widgets.page_back.sensitive = false + else + sm.widgets.page_forward.sensitive = true + sm.widgets.page_back.sensitive = true + end + + sm.page_status.current_page = cur_page + first = (cur_page * sm.page_status.num_buttons) - (sm.page_status.num_buttons - 1) + if first + sm.page_status.num_buttons > num_scripts then + last = num_scripts + else + last = first + sm.page_status.num_buttons - 1 + end + sm.widgets.page_status.label = _("Page ") .. cur_page .. _(" of ") .. max_pages + + populate_buttons(category, first, last) end -local function link_downloads_directory() - if not df.check_if_file_exists("$HOME/Downloads") then - os.execute("mkdir $HOME/Downloads") +local function change_category(category) + if not category then + log.msg(log.debug "setting category to selector value " .. sm.widgets.category_selector.value) + sm.page_status.category = sm.widgets.category_selector.value + else + log.msg(log.debug, "setting catgory to argument " .. category) + sm.page_status.category = category end - if df.check_if_file_exists(LUA_DIR .. "/downloads") then - os.remove(LUA_DIR .. "/downloads") + + paginate(2) +end + +local function change_num_buttons() + cur_buttons = sm.page_status.num_buttons + new_buttons = sm.widgets.num_buttons.value + pref_write("num_buttons", "integer", new_buttons) + if new_buttons < cur_buttons then + for i = 1, cur_buttons - new_buttons do + table.remove(sm.widgets.scripts) + end + log.msg(log.debug, "finished removing widgets, now there are " .. #sm.widgets.buttons) + elseif new_buttons > cur_buttons then + if new_buttons > sm.page_status.buttons_created then + for i = sm.page_status.buttons_created + 1, new_buttons do + table.insert(sm.widgets.buttons, dt.new_widget("button"){}) + sm.page_status.buttons_created = sm.page_status.buttons_created + 1 + end + end + log.msg(log.debug, "cur_buttons is " .. cur_buttons .. " and new_buttons is " .. new_buttons) + log.msg(log.debug, #sm.widgets.buttons .. " buttons are available") + for i = cur_buttons + 1, new_buttons do + log.msg(log.debug, "inserting button " .. i .. " into scripts widget") + table.insert(sm.widgets.scripts, sm.widgets.buttons[i]) + end + log.msg(log.debug, "finished adding widgets, now there are " .. #sm.widgets.buttons) + else -- no change + log.msg(log.debug, "no change, just returning") + return end - os.execute("ln -s " .. "$HOME/Downloads " .. LUA_DIR .. "/downloads") + sm.page_status.num_buttons = new_buttons + log.msg(log.debug, "num_buttons set to " .. sm.page_status.num_buttons) + paginate(2) -- force the buttons to repopulate + sm.widgets.main_menu.selected = 3 -- jump back to start/stop scripts +end + +local function load_preferences() + -- load the prefs and update settings + -- update_script_choices + local pref_string = pref_read("installed_repos", "string") + local entries = du.split(pref_string, ",") + while #entries > 2 do + local num = table.remove(entries, 1) + local name = table.remove(entries, 1) + local directory = table.remove(entries, 1) + if not string.match(sm.installed_repositories[1].name, "^" .. ds.sanitize_lua(name) .. "$") then + table.insert(sm.installed_repositories, {name = name, directory = directory}) + end + end + update_script_update_choices() + log.msg(log.debug, "updated installed scripts") + -- category selector + local val = pref_read("category_selector", "integer") + if val == 0 then + val = 1 + end + sm.widgets.category_selector.selected = val + sm.page_status.category = sm.widgets.category_selector.value + log.msg(log.debug, "updated category selector and set it to " .. sm.widgets.category_selector.value) + -- num_buttons + local val = pref_read("num_buttons", "integer") + if val == 0 then + val = DEFAULT_BUTTONS_PER_PAGE + end + sm.widgets.num_buttons.value = val + log.msg(log.debug, "set page buttons to " .. val) + change_num_buttons() + log.msg(log.debug, "paginated") + -- main menu + local val = pref_read("main_menu_action", "integer") + log.msg(log.debug, "read " .. val .. " for main menu") + if val == 0 then + val = 3 + end + sm.widgets.main_menu.selected = val + log.msg(log.debug, "set main menu to val " .. val .. " which is " .. sm.widgets.main_menu.value) + + log.msg(log.debug, "set main menu to " .. sm.widgets.main_menu.value) end local function install_module() @@ -462,312 +643,232 @@ local function install_module() "script manager", -- Visible name true, -- expandable false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers - dt.new_widget("box") -- widget - { - orientation = "vertical", - sm.main_box, - }, + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_BOTTOM", 600}}, -- containers + sm.widgets.main_box, nil,-- view_enter nil -- view_leave ) sm.module_installed = true end + sm.run = true + log.msg(log.debug, "set run to true, loading preferences") + load_preferences() + scan_repositories() end -- - - - - - - - - - - - - - - - - - - - - - - - -- M A I N P R O G R A M -- - - - - - - - - - - - - - - - - - - - - - - - +scan_scripts(LUA_DIR) +log.msg(log.debug, "finished processing scripts") --- api check +-- - - - - - - - - - - - - - - - - - - - - - - - +-- U S E R I N T E R F A C E +-- - - - - - - - - - - - - - - - - - - - - - - - -du.check_min_api_version("5.0.0") +sm.widgets = {} --- set up tables to contain all the widgets and choices +-- update the scripts -sm.script_widgets = {} -sm.script_categories = {} -sm.script_names = {} -sm.script_paths = {} -sm.main_menu_choices = {} -sm.main_stack_items = {} -sm.event_registered = false -sm.module_installed = false - --- see if we've run this before - -sm.initialized = dt.preferences.read("script_manager", "initialized", "bool") - -if not sm.initialized then - -- write out preferences - dt.preferences.write("script_manager", "lua_repository", "string", LUA_SCRIPT_REPO) - dt.preferences.write("script_manager", "use_distro_version", "bool", false) - dt.preferences.write("script_manager", "link_downloads_directory", "bool", false) - dt.preferences.write("script_manager", "initialized", "bool", true) -end - -sm.have_scripts = df.check_if_file_exists(LUA_DIR) - -sm.git_managed = df.check_if_file_exists(LUA_DIR .. PS .. ".git") - -if sm.have_scripts then - dt.print_log("found lua scripts directory") -else - dt.print_log("lua scripts directory not found") -end - -if sm.git_managed then - dt.print_log("scripts managed by git") -else - dt.print_log("scripts not managed") -end - -local git = df.check_if_bin_exists("git") - -if git then - dt.print_log("git found at " .. git) - sm.need_git = false -else - dt.print_log("git not found") - sm.need_git = true -end - -sm.repository = dt.new_widget("entry") -{ - text = dt.preferences.read("script_manager", "lua_repository", "string"), - editable = true, +sm.widgets.update_script_choices = dt.new_widget("combobox"){ + label = _("scripts to update"), + tooltip = _("select the scripts installation to update"), + selected = 1, + changed_callback = function(this) + pref_write("update_script_choices", "integer", this.selected) + end, + "placeholder", } -sm.need_install = false - -local install_update_text = _("install") -if sm.have_scripts and sm.git_managed then - install_update_text = _("update") -else - sm.need_install = true -end - -sm.install_update_button = dt.new_widget("button"){ - label = install_update_text .. _(" scripts"), - clicked_callback = function(self) - sm.lua_repository = sm.repository.text - dt.preferences.write("script_manager", "lua_repository", "string", sm.repository.text) - if sm.need_install then - local result = install_scripts() - if result then - build_scripts_block() - insert_scripts_block() - sm.have_scripts = true - sm.git_managed = true - sm.need_install = false - self.label = _("update scripts") - dt.print(_("installed scripts from " .. sm.repository.text)) - else - dt.print(_("Error installing scripts from " .. sm.repository.text)) - end - else - local result = update_scripts() - if result then - --build_scripts_block() - dt.print(_("updated scripts from " .. sm.repository.text)) - else - dt.print(_("Error updating scripts from " .. sm.repository.text)) - end - end +sm.widgets.update = dt.new_widget("button"){ + label = _("update scripts"), + tooltip = _("update the lua scripts from the repository"), + clicked_callback = function(this) + update_scripts() end } -if not sm.need_install then - sm.reinstall_button = dt.new_widget("button"){ - label = "reinstall scripts", - clicked_callback = function(self) - sm.lua_repository = sm.repository.text - dt.preferences.write("script_manager", "lua_repository", "string", sm.repository.text) - local result = install_scripts() - if result then - build_scripts_block() - insert_scripts_block() - sm.have_scripts = true - sm.git_managed = true - sm.need_install = false - sm.install_update_button.label = "update scripts" - dt.print(_("reinstalled scripts from " .. sm.repository.text)) - dt.print_log(_("scripts reinstalled")) - else - dt.print(_("ERROR: script reinstallation failed")) - dt.print_error(_("script reinstall failed")) - end +-- add additional scripts + +sm.widgets.script_url = dt.new_widget("entry"){ + text = "", + placeholder = "https://", + tooltip = _("enter the URL of the git repository containing the scripts you wish to add") +} + +sm.widgets.new_category = dt.new_widget("entry"){ + text = "", + placeholder = _("name of new category"), + tooltip = _("enter a category name for the additional scripts") +} + +sm.widgets.add_scripts = dt.new_widget("box"){ + orientation = vertical, + dt.new_widget("label"){label = _("URL to download additional scripts from")}, + sm.widgets.script_url, + dt.new_widget("label"){label = _("new category to place scripts in")}, + sm.widgets.new_category, + dt.new_widget("button"){ + label = _("install additional scripts"), + clicked_callback = function(this) + install_scripts() end } - - sm.install_update_widgets = { - sm.install_update_button, - sm.reinstall_button, - } -else - sm.install_update_widgets = { - sm.install_update_button, - } -end - -sm.install_update_box = dt.new_widget("box"){ - orientation = "vertical", - dt.new_widget("label"){ label = "Install/Update scripts" }, - table.unpack(sm.install_update_widgets), } -table.insert(sm.main_menu_choices, "Install/Update Scripts") -table.insert(sm.main_stack_items, sm.install_update_box) - --- configuration items - -sm.repository_update = dt.new_widget("button"){ - label = "update", - clicked_callback = function() - dt.preferences.write("script_manager", "lua_repository", "string", sm.repository.text) - sm.install_update_button.label = "install" - sm.install_update_button.sensitive = true - sm.reinstall_button.sensitive = false - sm.need_install = true - end -} - -sm.repository_reset = dt.new_widget("button"){ - label = "reset", - clicked_callback = function() - sm.repository.text = LUA_SCRIPT_REPO - dt.preferences.write("script_manager", "lua_repository", "string", LUA_SCRIPT_REPO) - sm.install_update_button.label = "install" - sm.install_update_button.sensitive = true - sm.reinstall_button.sensitive = false - sm.need_install = true - end -} - -sm.update_reset = dt.new_widget("box"){ - orientation = "horizontal", - sm.repository_update, - sm.repository_reset, -} - --- replace standalone version of script_manager with distributed version, i.e. tools/script_manager.lua - -- link on linux/MacOS, copy on windows - -- this option won't be active until the repository version of this script is accepted - -sm.use_lua_scripts_version = dt.new_widget("check_button"){ - label = "Use lua scripts distributed version", - tooltip = "Use the standalone version (false) or the distributed version (true)", - value = dt.preferences.read("script_manager", "use_distro_version", "bool"), - clicked_callback = function(self) - if dt.preferences.read("script_manager", "use_distro_version", "bool") == self.value then - -- do nothing - else - sm.apply_configuration.sensitive = true +sm.widgets.allow_disable = dt.new_widget("check_button"){ + label = _('Enable "Dsiable Scripts" button'), + value = false, + clicked_callback = function(this) + if this.value == true then + sm.widgets.disable_scripts.sensitive = true end - end + end, } --- link downloads to $HOME/downloads on linux and MacOS - -sm.link_downloads_directory = dt.new_widget("check_button"){ - label = "Link lua/downloads to $HOME/Downloads", - tooltip = "Linking the directories enables dropping a script in $HOME/downloads\nand having it recognized the next time darktable starts", - value = dt.preferences.read("script_manager", "link_downloads_directory", "bool"), - clicked_callback = function(self) - if dt.preferences.read("script_manager", "link_downloads_directory", "bool") == self.value then - -- do nothing - else - sm.apply_configuration.sensitive = true - end - end -} - -sm.apply_configuration = dt.new_widget("button"){ - label = "Apply", +sm.widgets.disable_scripts = dt.new_widget("button"){ + label = _("Disable Scripts"), sensitive = false, - clicked_callback = function(self) - dt.preferences.write("script_manager", "use_distro_version", "bool", sm.use_lua_scripts_version.value) - if sm.use_lua_scripts_version.value then - use_lua_scripts_version() - end - if dt.configuration.running_os ~= "windows" then - dt.preferences.write("script_manager", "link_downloads_directory", "bool", sm.link_downloads_directory.value) - if sm.link_downloads_directory.value then - link_downloads_directory() - end - end + clicked_callback = function(this) + local LUARC = dt.configuration.config_dir .. PS .. "luarc" + df.file_move(LUARC, LUARC .. ".disabled") + log.msg(log.info, "lua scripts disabled") + dt.print(_("lua scripts will not run the next time darktable is started")) end } --- get git location on windows - -sm.git_location = df.executable_path_widget({"git"}) - -sm.configuration_widgets = { - sm.repository, - sm.update_reset, - dt.new_widget("separator"){}, - dt.new_widget("separator"){}, -} - -if dt.configuration.running_os == "windows" or sm.need_git then - sm.configuration_widgets[#sm.configuration_widgets + 1] = sm.git_location -end - -if dt.configuration.running_os ~= "windows" then - sm.configuration_widgets[#sm.configuration_widgets + 1] = sm.use_lua_scripts_version - sm.configuration_widgets[#sm.configuration_widgets + 1] = sm.link_downloads_directory -end -sm.configuration_widgets[#sm.configuration_widgets + 1] = sm.apply_configuration - -sm.config_box = dt.new_widget("box"){ +sm.widgets.install_update = dt.new_widget("box"){ orientation = "vertical", - dt.new_widget("label") { label = "Configuration" }, - table.unpack(sm.configuration_widgets), + dt.new_widget("section_label"){label = _("update scripts")}, + sm.widgets.update_script_choices, + sm.widgets.update, + dt.new_widget("section_label"){label = _("add more scripts")}, + sm.widgets.add_scripts, + dt.new_widget("section_label"){label = _("disable scripts")}, + sm.widgets.allow_disable, + sm.widgets.disable_scripts } +-- manage the scripts -table.insert(sm.main_menu_choices, "Configure") -table.insert(sm.main_stack_items, sm.config_box) - --- set up the outside stack for config, install/update, and download - - -- make a stack for the choices - -sm.main_stack = dt.new_widget("stack"){ - table.unpack(sm.main_stack_items), -} - -if CURR_API_VERSION >= "6.0.1" then - sm.main_stack.h_size_fixed = false - sm.main_stack.v_size_fixed = false -end - - - -- make a combobox for the selector - -sm.main_menu = dt.new_widget("combobox"){ - label = "Action", - tooltip = "Select the action you want to perform", - value = 1, "No actions available", +sm.widgets.category_selector = dt.new_widget("combobox"){ + label = _("category"), + tooltip = _( "select the script category"), + selected = 1, changed_callback = function(self) - for pos,str in ipairs(sm.main_menu_choices) do - if self.value == str then - sm.main_stack.active = pos - dt.preferences.write("script_manager", "sm_main_menu_value", "integer", pos) - end + if sm.run then + pref_write("category_selector", "integer", self.selected) + change_category(self.value) + end + end, + table.unpack(sm.categories), +} + +sm.widgets.buttons ={} +for i =1, DEFAULT_BUTTONS_PER_PAGE do + table.insert(sm.widgets.buttons, dt.new_widget("button"){}) + sm.page_status.buttons_create = sm.page_status.buttons_created + 1 +end + +local page_back = "<" +local page_forward = ">" +if CURR_API_STRING < "6.1.0" then + page_back = " < " + page_forward = " > " +end + +sm.widgets.page_status = dt.new_widget("label"){label = _("Page:")} +sm.widgets.page_back = dt.new_widget("button"){ + label = page_back, + clicked_callback = function(this) + if sm.run then + paginate(0) end end } -if #sm.main_menu_choices > 0 then - update_combobox_choices(sm.main_menu, sm.main_menu_choices, 1) +sm.widgets.page_forward = dt.new_widget("button"){ + label = page_forward, + clicked_callback = function(this) + if sm.run then + paginate(1) + end + end +} + +sm.widgets.page_control = dt.new_widget("box"){ + orientation = "horizontal", + sm.widgets.page_back, + sm.widgets.page_status, + sm.widgets.page_forward, +} + +sm.widgets.scripts = dt.new_widget("box"){ + orientation = vertical, + dt.new_widget("label"){label = _("Scripts")}, + sm.widgets.category_selector, + sm.widgets.page_control, + table.unpack(sm.widgets.buttons) +} + +-- configure options + +sm.widgets.num_buttons = dt.new_widget("slider"){ + label = _("scripts per page"), + tooltip = _("select number of start/stop buttons to display"), + soft_min = MIN_BUTTONS_PER_PAGE, + hard_min = MIN_BUTTONS_PER_PAGE, + soft_max = MAX_BUTTONS_PER_PAGE, + hard_max = MAX_BUTTONS_PER_PAGE, + step = 1, + digits = 0, + value = 10 +} + +sm.widgets.change_buttons = dt.new_widget("button"){ + label = _("change number of buttons"), + clicked_callback = function(this) + change_num_buttons() + end +} + +sm.widgets.configure = dt.new_widget("box"){ + orientation = "vertical", + dt.new_widget("label"){label = _("Configuration")}, + sm.widgets.num_buttons, + sm.widgets.change_buttons, +} + +-- stack for the options + +sm.widgets.main_stack = dt.new_widget("stack"){ + sm.widgets.install_update, + sm.widgets.configure, + sm.widgets.scripts, +} + +if CURR_API_STRING >= "6.0.1" then + sm.widgets.main_stack.h_size_fixed = false + sm.widgets.main_stack.v_size_fixed = false end -sm.main_box = dt.new_widget("box"){ - orientation = "vertical", - sm.main_menu, - sm.main_stack, +-- main menu + +sm.widgets.main_menu = dt.new_widget("combobox"){ + label = _("action"), + changed_callback = function(self) + sm.widgets.main_stack.active = self.selected + pref_write("main_menu_action", "integer", self.selected) + log.msg(log.debug, "saved " .. self.selected .. " for main menu") + end, + _("install/update scripts"), _("configure"), _("start/stop scripts") +} + +-- widget for module + +sm.widgets.main_box = dt.new_widget("box"){ + sm.widgets.main_menu, + sm.widgets.main_stack } @@ -791,18 +892,3 @@ else end end --- set up the scripts block if we have them otherwise we'll wait until we download them - -if sm.have_scripts then - - -- scan for scripts and populate the categories - build_scripts_block() - - -- add the widgets to the lib - insert_scripts_block() - - sm.main_menu.selected = 3 - -end - -collectgarbage("restart")