--[[ This file is part of darktable, copyright (c) 2019 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 the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . ]] --[[ filetether.lua - poor man's camera tethering using the file system ]] local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" local PS = dt.configuration.running_os == "windows" and "\\" or "/" local DIR = dt.configuration.running_os == "windows" and "DIR /B/O:-D " or "ls -rt " local filetether = { widgets = {}, } -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- F U N C T I O N S -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - local function file_closed(file) local status = false if dt.configuration.running_os == "windows" then -- try and rename it so see if it's busy -- if you can rename it, rename it back and then return true local result = dt.external_command("rename " .. file .. " tmp.part") if result == 0 then dt.external_command("rename tmp.part " .. file) status = true end else local p = io.popen("lsof " .. file .. " 2>&1 || echo ::ERROR::", "r") local result = p:read("*a") p:close() if string.len(result) == 0 or string.match(result, "::ERROR::") then -- string length of zero means that the file is closed -- ::ERROR:: means that the file didn't exist, so no use returning false and waiting status = true end end return status end local function stop_polling() filetether["run"] = false end local function start_polling() filetether["run"] = true local path = filetether.widgets["path"].value dt.preferences.write("filetether", "path", "string", path) local view_in_darkroom = filetether.widgets["view_in_darkroom"].value dt.preferences.write("filetether", "view_in_darkroom", "bool", view_in_darkroom) dt.print_log("view in darkroom is " .. tostring(view_in_darkroom)) local interval = filetether.widgets["interval"].value * 1000 dt.preferences.write("filetether", "interval", "integer", math.floor(filetether.widgets["interval"].value)) if not df.check_if_file_exists(path) then df.mkdir(path) end local files = {} local f = io.popen(DIR .. path) local old_file_list = f:read("*a") -- strip out all the chanacters that have meaning in a pattern match old_file_list = string.gsub(old_file_list, "%-", "%%-") old_file_list = string.gsub(old_file_list, "%(", "%%(") old_file_list = string.gsub(old_file_list, "%)", "%%)") dt.database.import(path) f:close() while(filetether["run"]) do dt.control.sleep(interval) dt.print_log("view in darkroom is " .. tostring(view_in_darkroom)) -- process the queue of files that was detected last interval local save_state if #files > 1 and not string.match(old_file_list, "%.xmp") then save_state = view_in_darkroom dt.print_log("save state is " .. tostring(save_state)) view_in_darkroom = false end for _,file in ipairs(files) do dt.print_log(file) -- ignore xmp files if not string.match(file, "%.xmp$") then -- do a reasonable check to determine if it's a filename instead of a directory -- on linux and mocos we could ues the file command, but windows doesn't have it if string.len(file) > 4 and string.match(file, "%.") then if df.check_if_file_exists(path .. PS .. file) then dt.print_log("checking if file is complete") while not file_closed(path .. PS .. file) do dt.print_log("not yet complete") dt.control.sleep(2000) end dt.print_log("file is complete") dt.print_log("importing " .. path .. PS .. file) local result = dt.database.import(path .. PS .. file) dt.print_log(result.filename .. " imported with id " .. result.id) dt.gui.selection({result}) if #files > 1 and file == files[#files] and not string.match(old_file_list, "%.xmp") then view_in_darkroom = save_state dt.print_log("view in darkroom set to " .. tostring(view_in_darkroom)) end if view_in_darkroom then if dt.gui.current_view() == dt.gui.views.darkroom then dt.gui.current_view(dt.gui.views.lighttable) dt.control.sleep(500) dt.gui.current_view(dt.gui.views.lighttable) --dt.gui.views.darkroom.display_image(result) else dt.gui.current_view(dt.gui.views.darkroom) end end end end end end files = {} f = io.popen(DIR .. path) local file_list = f:read("*a") f:close() local diff = string.gsub(file_list, old_file_list, "") dt.print_log(diff) files = du.split(diff, "\n") dt.print_log("number of files is " .. #files) old_file_list = file_list old_file_list = string.gsub(old_file_list, "%-", "%%-") old_file_list = string.gsub(old_file_list, "%(", "%%(") old_file_list = string.gsub(old_file_list, "%)", "%%)") end end -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- W I D G E T S -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - local tmp = dt.preferences.read("filetether", "path", "string") dt.print_log("path is " .. tmp) filetether.widgets["path"] = dt.new_widget("file_chooser_button"){ title = "select directory to watch", tooltip = "select directory where files will be loaded to", is_directory = true, value = tmp, } tmp = dt.preferences.read("filetether", "interval", "integer") dt.print_log("interval is " .. tmp) if tmp == 0 then tmp = 10 end filetether.widgets["interval"] = dt.new_widget("slider"){ label = "polling interval", tooltip = "select the number of seconds to wait before checking for new images", soft_min = 1, soft_max = 180, hard_min = 1, hard_max = 180, digits = 0, step = 1, value = tmp, } tmp = dt.preferences.read("filetether", "view_in_darkroom", "bool") dt.print_log("view in darkroom preference is " .. tostring(tmp)) filetether.widgets["view_in_darkroom"] = dt.new_widget("check_button"){ label = "view in darkroom", tooltip = "view imported image in darkroom (single image only)", value = tmp, } filetether.widgets["start_stop"] = dt.new_widget("button"){ label = "start", clicked_callback = function(self) if self.label == "start" then self.label = "stop" start_polling() else self.label = "start" stop_polling() end end } filetether.widgets["filetether"] = dt.new_widget("box"){ orientation = "vertical", dt.new_widget("label"){label = "select directory to monitor"}, filetether.widgets["path"], filetether.widgets["interval"], filetether.widgets["view_in_darkroom"], filetether.widgets["start_stop"], } dt.register_lib( "filetether", "filetether", true, false, {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}}, filetether.widgets["filetether"], nil, nil )