Example: Auto-Image-Magic

This example is a layer 1 script to make a special “magic” directory in which image files will be converted automatically therein.

The full script:

local formats = { jpg = true, gif = true, png = true }

convert = {
	delay = 0,

	maxProcesses = 99,

	action = function(inlet)
		local event = inlet.getEvent()

		if event.isdir then
			-- ignores events on dirs
			inlet.discardEvent(event)
			return
		end

		-- extract extension and basefilename
		local p    = event.pathname
		local ext  = string.match(p, ".*%.([^.]+)$")
		local base = string.match(p, "(.*)%.[^.]+$")
		if not formats[ext] then
			-- an unknown extenion
			log("Normal", "not doing something on ."..ext)
			inlet.discardEvent(event)
			return
		end

		-- autoconvert on create and modify
		if event.etype == "Create" or event.etype == "Modify" then
			-- builds one bash command
			local cmd = ""
			-- do for all other extensions
			for k, _ in pairs(formats) do
				if k ~= ext then
					-- excludes files to be created, so no
					-- followup actions will occur
					inlet.addExclude(base..'.'..k)
					if cmd ~= ""  then
						cmd = cmd .. " && "
					end
					cmd = cmd..
						'/usr/bin/convert "'..
						event.source..p..'" "'..
						event.source..base..'.'..k..
						'" || /bin/true'
				end
			end
			log("Normal", "Converting "..p)
			spawnShell(event, cmd)
			return
		end

		-- deletes all formats if you delete one
		if event.etype == "Delete" then
			-- builds one bash command
			local cmd = ""
			-- do for all other extensions
			for k, _ in pairs(formats) do
				if k ~= ext then
					-- excludes files to be deleted, so no
					-- followup actions will occur
					inlet.addExclude(base..'.'..k)
					if cmd ~= ""  then
						cmd = cmd .. " && "
					end
					cmd = cmd..
						'rm "'..event.source..base..'.'..k..
						'" || /bin/true'
				end
			end
			log("Normal", "Deleting all "..p)
			spawnShell(event, cmd)
			return
		end

		-- ignores other events.
		inlet.discardEvent(event)
	end,

	-----
	-- Removes excludes when convertions are finished
	--
	collect = function(event, exitcode)
		local p     = event.pathname
		local ext   = string.match(p, ".*%.([^.]+)$")
		local base  = string.match(p, "(.*)%.[^.]+$")
		local inlet = event.inlet

		if event.etype == "Create" or
		   event.etype == "Modify" or
		   event.etype == "Delete"
		then
			for k, _ in pairs(formats) do
				inlet.rmExclude(base..'.'..k)
			end
		end
	end,

}

sync{convert, source="magicdir", recursive=false}

This creates a local table of all supported file formats. The file formats are used as keys.

local formats = { jpg=true, gif=true, png=true,  }

Configures actions to be instant and there is unlimits the amount the conversion to be done at once. Well not unlimited but set the limit pretty high.

convert = {
	delay = 0,
	maxProcesses = 99,

This script uses the layer 1 inlet interface altough it greps only single events and not lists. It does this instead of layer 2 as it needs to do common operations for all kind of events.

	action = function(inlet)
		local event = inlet.getEvent()

Ignores directories. As using layer 1 it has to explicitly discard events it does not spawn actions for.

		if event.isdir then
			-- ignores events on dirs
			inlet.discardEvent(event)
			return
		end

Uses Lua string patterns to extract the file extension from the rest - here called base.

		-- extract extension and basefilename
		local p    = event.pathname
		local ext  = string.match(p, ".*%.([^.]+)$")
		local base = string.match(p, "(.*)%.[^.]+$")

Looks the extension up in the formats table. This can be done, since formats are keys in that table. If not an image format it bails out.

		if not formats[ext] then
			-- an unknown extenion
			log("Normal", "not doing something on ."..ext)
			inlet.discardEvent(event)
			return
		end

Following actions will done on “Create” and “Modify” events.

		-- autoconvert on create and modify
		if event.etype == "Create" or event.etype == "Modify" then

This script builds a bash command using a string.

			-- builds one bash command
			local cmd = ""

It iterates for all image formats and excludes the one which is the source image.

			-- do for all other extensions
			for k, _ in pairs(formats) do
				if k ~= ext then

This is a little trick. It creates Exclusions for the converted images. As this images are not placed in a target directory but right next to the source image in the source directory they would otherwise trigger Create actions as well.

					-- excludes files to be created, so no
					-- followup actions will occur
					inlet.addExclude(base..'.'..k)

And for every image to be converted adds the calls to the arguments. It uses " || /bin/true " to let the shell continue if one conversion fails. In that it chains the conversion with ‘&&’ they will be called sequentially.

					if cmd ~= ""  then
						cmd = cmd .. " && "
					end
					cmd = cmd..
						'/usr/bin/convert "'..
						event.source..p..'" "'..
						event.source..base..'.'..k..
						'" || /bin/true'

And eventually it spawns the shell doing the conversions and is finished.

				end
			end
			log("Normal", "Converting "..p)
			spawnShell(event, cmd)
			return
		end

For deletions it does technically something similar, but it deletes all other file formats of the image.

		-- deletes all formats if you delete one
		if event.etype == "Delete" then
			-- builds one bash command
			local cmd = ""
			-- do for all other extensions
			for k, _ in pairs(formats) do
				if k ~= ext then
					-- excludes files to be deleted, so no
					-- followup actions will occur
					inlet.addExclude(base..'.'..k)
					if cmd ~= ""  then
						cmd = cmd .. " && "
					end
					cmd = cmd..
						'rm "'..event.source..base..'.'..k..
						'" || /bin/true'
				end
			end
			log("Normal", "Deleting all "..p)
			spawnShell(event, cmd)
			return
		end

and not to forget to nicely discard all other events.

		-- ignores other events.
		inlet.discardEvent(event)
	end,

collect is called when the conversions finished. It will remove the temporary excludes again.

	-----
	-- Removes excludes when convertions are finished
	--
	collect = function(event, exitcode)
		local p     = event.pathname
		local ext   = string.match(p, ".*%.([^.]+)$")
		local base  = string.match(p, "(.*)%.[^.]+$")
		local inlet = event.inlet

		if event.etype == "Create" or
		   event.etype == "Modify" or
		   event.etype == "Delete"
		then
			for k, _ in pairs(formats) do
				inlet.rmExclude(base..'.'..k)
			end
		end
	end,

And finally use the configuration to watch “magicdir”.

sync{convert, source="magicdir", recursive=false}