rewriting array/queue/counter prototypes. restructuring mantle

This commit is contained in:
Axel Kittenberger 2018-03-14 17:50:51 +01:00
parent 41aec3a3f8
commit cd8ce93a1e
12 changed files with 2495 additions and 2306 deletions

View File

@ -35,6 +35,15 @@ include_directories("${PROJECT_BINARY_DIR}")
# building and compiling the part of lsyncd written in Lua
set( LUA_CODE
${PROJECT_SOURCE_DIR}/mantle/array.lua
${PROJECT_SOURCE_DIR}/mantle/counter.lua
${PROJECT_SOURCE_DIR}/mantle/queue.lua
${PROJECT_SOURCE_DIR}/mantle/lock.lua
${PROJECT_SOURCE_DIR}/mantle/delay.lua
${PROJECT_SOURCE_DIR}/mantle/inotify.lua
${PROJECT_SOURCE_DIR}/mantle/combiner.lua
${PROJECT_SOURCE_DIR}/mantle/inlet.lua
${PROJECT_SOURCE_DIR}/mantle/filter.lua
${PROJECT_SOURCE_DIR}/mantle/lsyncd.lua
${PROJECT_SOURCE_DIR}/mantle/wrapup.lua
${PROJECT_SOURCE_DIR}/default/default.lua

168
mantle/array.lua Normal file
View File

@ -0,0 +1,168 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- array.lua
--
--
-- Array tables error if accessed with a non-number.
-- They maintain their length as an attribute and are zero based.
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
--
-- Metatable.
--
local mt = { }
--
-- Key to native table.
--
local k_nt = { }
--
-- Key to size entry.
--
local k_size = { }
--
-- On accessing a nil index.
--
mt.__index = function
(
self,
k -- key used to access
)
if type( k ) ~= 'number'
then
error( 'Array, key "'..k..'" invalid', 2 )
end
if k < 0 or k >= self[ k_size ]
then
error( 'Array, key "'..k..'" out of bonds', 2 )
end
return self[ k_nt ][ k ]
end
--
-- On assigning a new index.
--
mt.__newindex = function
(
self,
k, -- key value to assign to
v -- value to assign
)
if type( k ) ~= 'number'
then
error( 'Array, key "'..k..'" invalid', 2 )
end
if k < 0 or k > self[ k_size ]
then
error( 'Array, key "'..k..'" out of bonds', 2 )
end
if k == self[ k_size ]
then
self[ k_size ] = self[ k_size ] + 1
end
self[ k_nt ][ k ] = v
end
--
-- Returns the length of the array.
--
mt.__len = function
(
self
)
return self[ k_size ]
end
--
-- Errors on use of pairs( )
--
mt.__pairs = function
(
self
)
error( 'Array, do not use pairs( )', 2 )
end
--
-- Returns next value in iterator.
--
local function iter
(
self,
pos
)
pos = pos + 1
if pos == self[ k_size ] then return nil end
return pos, self[ k_nt ][ pos ]
end
--
-- Allows walking throw the array.
--
mt.__ipairs = function
(
self
)
return iter, self, -1
end
--
-- Pushes a new new value on the end of the array
--
local function push
(
self,
v
)
self[ k_nt ][ self[ k_size ] ] = v
self[ k_size ] = self[ k_size ] + 1
end
--
-- Creates a new array.
--
local function new
( )
-- k_nt is a native table, private to this object.
local o =
{
push = push,
[ k_size ] = 0,
[ k_nt ] = { }
}
setmetatable( o, mt )
return o
end
--
-- Exported interface.
--
Array = { new = new }

358
mantle/combiner.lua Normal file
View File

@ -0,0 +1,358 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- combiner.lua Live (Mirror) Syncing Demon
--
--
-- Combines delays.
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
--
-- The new delay replaces the old one if it's a file
--
local function refi
(
d1, -- old delay
d2 -- new delay
)
-- but a directory blocks
if d2.path:byte( -1 ) == 47
then
log(
'Delay',
d2.etype,': ',d2.path,
' blocked by ',
d1.etype,': ',d1.path
)
return 'stack'
end
log(
'Delay',
d2.etype, ': ', d2.path,
' replaces ',
d1.etype, ': ', d1.path
)
return 'replace'
end
--
-- Table on how to combine events that dont involve a move.
--
local combineNoMove =
{
Attrib =
{
Attrib = 'absorb',
Modify = 'replace',
Create = 'replace',
Delete = 'replace'
},
Modify =
{
Attrib = 'absorb',
Modify = 'absorb',
Create = 'replace',
Delete = 'replace'
},
Create =
{
Attrib = 'absorb',
Modify = 'absorb',
Create = 'absorb',
Delete = 'replace'
},
Delete =
{
Attrib = 'absorb',
Modify = 'absorb',
Create = 'replace file,block dir',
Delete = 'absorb'
},
}
--
-- Returns the way two Delay should be combined.
--
-- Result:
-- nil -- They don't affect each other.
-- 'stack' -- Old Delay blocks new Delay.
-- 'replace' -- Old Delay is replaced by new Delay.
-- 'absorb' -- Old Delay absorbs new Delay.
-- 'toDelete,stack' -- Old Delay is turned into a Delete
-- and blocks the new Delay.
-- 'split' -- New Delay a Move is to be split
-- into a Create and Delete.
--
local function combine
(
d1, -- old delay
d2 -- new delay
)
if d1.etype == 'Init' or d1.etype == 'Blanket'
then
return 'stack'
end
-- two normal events
if d1.etype ~= 'Move' and d2.etype ~= 'Move'
then
if d1.path == d2.path
then
-- lookups up the function in the combination matrix
-- and calls it
local result = combineNoMove[ d1.etype ][ d2.etype ]
if result == 'replace file,block dir'
then
if d2.path:byte( -1 ) == 47
then
return 'stack'
else
return 'replace'
end
end
end
-- if one is a parent directory of another, events are blocking
if d1.path:byte( -1 ) == 47 and string.starts( d2.path, d1.path )
or d2.path:byte( -1 ) == 47 and string.starts( d1.path, d2.path )
then
return 'stack'
end
return nil
end
-- non-move event on a move.
if d1.etype == 'Move' and d2.etype ~= 'Move'
then
-- if the move source could be damaged the events are stacked
if d1.path == d2.path
or d2.path:byte( -1 ) == 47 and string.starts( d1.path, d2.path )
or d1.path:byte( -1 ) == 47 and string.starts( d2.path, d1.path )
then
return 'stack'
end
-- the event does something with the move destination
if d1.path2 == d2.path
then
if d2.etype == 'Delete'
or d2.etype == 'Create'
then
return 'toDelete,stack'
end
-- on 'Attrib' or 'Modify' simply stack on moves
return 'stack'
end
if d2.path:byte( -1 ) == 47 and string.starts( d1.path2, d2.path )
or d1.path2:byte( -1 ) == 47 and string.starts( d2.path, d1.path2 )
then
return 'stack'
end
return nil
end
-- a move upon a non-move event
if d1.etype ~= 'Move' and d2.etype == 'Move'
then
if d1.path == d2.path
or d1.path == d2.path2
or d1.path:byte( -1 ) == 47 and string.starts( d2.path, d1.path )
or d1.path:byte( -1 ) == 47 and string.starts( d2.path2, d1.path )
or d2.path:byte( -1 ) == 47 and string.starts( d1.path, d2.path )
or d2.path2:byte( -1 ) == 47 and string.starts( d1.path, d2.path2 )
then
return 'split'
end
return nil
end
--
-- a move event upon a move event
--
if d1.etype == 'Move' and d2.etype == 'Move'
then
-- TODO combine moves,
if d1.path == d2.path
or d1.path == d2.path2
or d1.path2 == d2.path
or d2.path2 == d2.path
or d1.path:byte( -1 ) == 47 and string.starts( d2.path, d1.path )
or d1.path:byte( -1 ) == 47 and string.starts( d2.path2, d1.path )
or d1.path2:byte( -1 ) == 47 and string.starts( d2.path, d1.path2 )
or d1.path2:byte( -1 ) == 47 and string.starts( d2.path2, d1.path2 )
or d2.path:byte( -1 ) == 47 and string.starts( d1.path, d2.path )
or d2.path:byte( -1 ) == 47 and string.starts( d1.path2, d2.path )
or d2.path2:byte( -1 ) == 47 and string.starts( d1.path, d2.path2 )
or d2.path2:byte( -1 ) == 47 and string.starts( d1.path2, d2.path2 )
then
return 'split'
end
return nil
end
error( 'reached impossible state' )
end
--
-- The new delay is absorbed by an older one.
--
local function logAbsorb
(
d1, -- old delay
d2 -- new delay
)
log( 'Delay', d2.etype, ': ',d2.path, ' absorbed by ', d1.etype,': ',d1.path )
end
--
-- The new delay replaces the old one if it's a file.
--
local function logReplace
(
d1, -- old delay
d2 -- new delay
)
log( 'Delay', d2.etype, ': ', d2.path, ' replaces ', d1.etype, ': ', d1.path )
end
--
-- The new delay splits on the old one.
--
local function logSplit
(
d1, -- old delay
d2 -- new delay
)
log(
'Delay', d2.etype, ': ', d2.path, ' -> ', d2.path2,
' splits on ', d1.etype, ': ', d1.path
)
end
--
-- The new delay is blocked by the old delay.
--
local function logStack
(
d1, -- old delay
d2 -- new delay
)
local active = ''
if d1.active then active = 'active ' end
if d2.path2
then
log(
'Delay',
d2.etype, ': ',
d2.path, '->', d2.path2,
' blocked by ',
active,
d1.etype, ': ', d1.path
)
else
log(
'Delay',
d2.etype, ': ', d2.path,
' blocked by ',
active,
d1.etype, ': ', d1.path
)
end
end
--
-- The new delay turns the old one (a move) into a delete and is blocked.
--
local function logToDeleteStack
(
d1, -- old delay
d2 -- new delay
)
if d1.path2
then
log(
'Delay',
d2.etype, ': ', d2.path,
' turns ',
d1.etype, ': ', d1.path, ' -> ', d1.path2,
' into Delete: ', d1.path
)
else
log(
'Delay',
d2.etype, ': ', d2.path,
' turns ',
d1.etype, ': ', d1.path,
' into Delete: ', d1.path
)
end
end
local logFuncs =
{
absorb = logAbsorb,
replace = logReplace,
split = logSplit,
stack = logStack,
[ 'toDelete,stack' ] = logToDeleteStack
}
--
-- Prints the log message for a combination result
--
local function log
(
result, -- the combination result
d1, -- old delay
d2 -- new delay
)
local lf = logFuncs[ result ]
if not lf
then
error( 'unknown combination result: ' .. result )
end
lf( d1, d2 )
end
--
-- Public interface
--
Combiner = { combine = combine, log = log }

124
mantle/counter.lua Normal file
View File

@ -0,0 +1,124 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- counter.lua
--
--
-- Couter tables simply keep a count of the number of elements
-- in them
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
--
-- Metatable.
--
local mt = { }
--
-- Key to native table.
--
local k_nt = { }
--
-- Key to size entry.
--
local k_size = { }
--
-- On accessing a nil index.
--
mt.__index = function
(
self,
k -- key used to access
)
return self[ k_nt ][ k ]
end
--
-- On assigning a new index.
--
mt.__newindex = function
(
self,
k, -- key value to assign to
v -- value to assign
)
local nt = self[ k_nt ]
if nt[ k ] == nil
then
if v ~= nil then self[ k_size ] = self[ k_size ] + 1 end
else
if v == nil then self[ k_size ] = self[ k_size ] - 1 end
end
nt[ k ] = v
end
--
-- Returns the length of the counter.
--
mt.__len = function
(
self
)
return self[ k_size ]
end
--
-- Allows walking throw the counter.
--
mt.__pairs = function
(
self
)
return pairs( self[ k_nt ] )
end
--
-- Allows integral walking throw the counter.
--
mt.__ipairs = function
(
self
)
return ipairs( self[ k_nt ] )
end
--
-- Creates a new counter.
--
local function new
( )
-- k_nt is a native table, private to this object.
local o =
{
[ k_size ] = 0,
[ k_nt ] = { }
}
setmetatable( o, mt )
return o
end
--
-- Exported interface.
--
Counter = { new = new }

162
mantle/delay.lua Normal file
View File

@ -0,0 +1,162 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- delay.lua Live (Mirror) Syncing Demon
--
--
-- Holds the information about a delayed event for one Sync.
--
-- Valid stati of a delay are:
-- 'wait' ... the event is ready to be handled.
-- 'active' ... there is process running catering for this event.
-- 'blocked' ... this event waits for another to be handled first.
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
--
-- Metatable.
--
local mt = { }
--
-- Secret key to native table
--
local k_nt = { }
local assignAble =
{
dpos = true,
etype = true,
path = true,
path2 = true,
status = true,
}
--
-- On accessing a nil index.
--
mt.__index = function
(
self,
k -- key value accessed
)
return self[ k_nt ][ k ]
end
--
-- On assigning a new index.
--
mt.__newindex = function
(
self,
k, -- key value to assign to
v -- value to assign
)
if not assignAble[ k ]
then
error( 'Cannot assign new key "' .. k .. '" to Delay' )
end
self[ k_nt ][ k ] = v
end
--
-- This delay is being blocked by another delay
--
local function blockedBy
(
self, -- this delay
delay -- the blocking delay
)
self[ k_nt ].status = 'block'
local blocks = delay[ k_nt ].blocks
if not blocks
then
blocks = { }
delay[ k_nt ].blocks = blocks
end
table.insert( blocks, self )
end
--
-- Sets the delay status to 'active'.
--
local function setActive
(
self
)
self[ k_nt ].status = 'active'
end
--
-- Sets the delay status to 'wait'
--
local function wait
(
self, -- this delay
alarm -- alarm for the delay
)
self[ k_nt ].status = 'wait'
self[ k_nt ].alarm = alarm
end
--
-- Creates a new delay.
--
local function new
(
etype, -- type of event.
-- 'Create', 'Modify', 'Attrib', 'Delete' or 'Move'
sync, -- the Sync this delay belongs to
alarm, -- latest point in time this should be catered for
path, -- path and file-/dirname of the delay relative
-- -- to the syncs root.
path2 -- used only in moves, path and file-/dirname of
-- move destination
)
local delay =
{
blockedBy = blockedBy,
setActive = setActive,
wait = wait,
[ k_nt ] =
{
etype = etype,
sync = sync,
alarm = alarm,
path = path,
path2 = path2,
status = 'wait'
},
}
setmetatable( delay, mt )
return delay
end
--
-- Exported interface
--
Delay = { new = new }

189
mantle/filter.lua Normal file
View File

@ -0,0 +1,189 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- filter.lua
--
--
-- A set of filter patterns.
--
-- Filters allow excludes and includes
--
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
--
-- Turns a rsync like file pattern to a lua pattern.
-- ( at best it can )
--
local function toLuaPattern
(
p -- the rsync like pattern
)
local o = p
p = string.gsub( p, '%%', '%%%%' )
p = string.gsub( p, '%^', '%%^' )
p = string.gsub( p, '%$', '%%$' )
p = string.gsub( p, '%(', '%%(' )
p = string.gsub( p, '%)', '%%)' )
p = string.gsub( p, '%.', '%%.' )
p = string.gsub( p, '%[', '%%[' )
p = string.gsub( p, '%]', '%%]' )
p = string.gsub( p, '%+', '%%+' )
p = string.gsub( p, '%-', '%%-' )
p = string.gsub( p, '%?', '[^/]' )
p = string.gsub( p, '%*', '[^/]*' )
-- this was a ** before
p = string.gsub( p, '%[%^/%]%*%[%^/%]%*', '.*' )
p = string.gsub( p, '^/', '^/' )
if p:sub( 1, 2 ) ~= '^/'
then
-- if does not begin with '^/'
-- then all matches should begin with '/'.
p = '/' .. p;
end
log( 'Filter', 'toLuaPattern "', o, '" = "', p, '"' )
return p
end
--
-- Appends a filter pattern
--
local function append
(
self, -- the filters object
line -- filter line
)
local rule, pattern = string.match( line, '%s*([+|-])%s*(.*)' )
if not rule or not pattern
then
log( 'Error', 'Unknown filter rule: "', line, '"' )
terminate( -1 )
end
local lp = toLuaPattern( pattern )
table.insert( self. list, { rule = rule, pattern = pattern, lp = lp } )
end
--
-- Adds a list of patterns to filter.
--
local function appendList
(
self,
plist
)
for _, v in ipairs( plist )
do
append( self, v )
end
end
--
-- Loads the filters from a file.
--
local function loadFile
(
self, -- self
file -- filename to load from
)
f, err = io.open( file )
if not f
then
log( 'Error', 'Cannot open filter file "', file, '": ', err )
terminate( -1 )
end
for line in f:lines( )
do
if string.match( line, '^%s*#' )
or string.match( line, '^%s*$' )
then
-- a comment or empty line: ignore
else
append( self, line )
end
end
f:close( )
end
--
-- Tests if 'path' is filtered.
-- Returns false if it is to be filtered.
--
local function test
(
self, -- self
path -- the path to test
)
if path:byte( 1 ) ~= 47
then
error( 'Paths for filter tests must start with \'/\'' )
end
for _, entry in ipairs( self.list )
do
local rule = entry.rule
local lp = entry.lp -- lua pattern
if lp:byte( -1 ) == 36
then
-- ends with $
if path:match( lp )
then
return rule == '+'
end
else
-- ends either end with / or $
if path:match( lp .. '/' )
or path:match( lp .. '$' )
then
return rule == '+'
end
end
end
return true
end
--
-- Cretes a new filter set.
--
local function new
( )
return {
list = { },
-- functions
append = append,
appendList = appendList,
loadFile = loadFile,
test = test,
}
end
--
-- Exported interface.
--
Filter = { new = new }

712
mantle/inlet.lua Normal file
View File

@ -0,0 +1,712 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- lsyncd.lua Live (Mirror) Syncing Demon
--
--
-- Creates inlets for syncs: the user interface for events.
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
--
-- Table to receive the delay of an event
-- or the delay list of an event list.
--
-- Keys are events and values are delays.
--
local e2d = { }
--
-- Table to ensure the uniqueness of every event
-- related to a delay.
--
-- Keys are delay and values are events.
--
local e2d2 = { }
--
-- Allows the garbage collector to remove not refrenced
-- events.
--
setmetatable( e2d, { __mode = 'k' } )
setmetatable( e2d2, { __mode = 'v' } )
--
-- Removes the trailing slash from a path.
--
local function cutSlash
(
path -- path to cut
)
if string.byte( path, -1 ) == 47
then
return string.sub( path, 1, -2 )
else
return path
end
end
--
-- Gets the path of an event.
--
local function getPath
(
event
)
if event.move ~= 'To'
then
return e2d[ event ].path
else
return e2d[ event ].path2
end
end
--
-- Interface for user scripts to get event fields.
--
local eventFields =
{
--
-- Returns a copy of the configuration as called by sync.
-- But including all inherited data and default values.
--
-- TODO give user a readonly version.
--
config = function
(
event
)
return e2d[ event ].sync.config
end,
--
-- Returns the inlet belonging to an event.
--
inlet = function
(
event
)
return e2d[ event ].sync.inlet
end,
--
-- Returns the type of the event.
--
-- Can be: 'Attrib', 'Create', 'Delete', 'Modify' or 'Move',
--
etype = function
(
event
)
return e2d[ event ].etype
end,
--
-- Events are not lists.
--
isList = function
( )
return false
end,
--
-- Returns the status of the event.
--
-- Can be:
-- 'wait', 'active', 'block'.
--
status = function
(
event
)
return e2d[ event ].status
end,
--
-- Returns true if event relates to a directory
--
isdir = function
(
event
)
return string.byte( getPath( event ), -1 ) == 47
end,
--
-- Returns the name of the file/dir.
--
-- Includes a trailing slash for dirs.
--
name = function
(
event
)
return string.match( getPath( event ), '[^/]+/?$' )
end,
--
-- Returns the name of the file/dir
-- excluding a trailing slash for dirs.
--
basename = function
(
event
)
return string.match( getPath( event ), '([^/]+)/?$')
end,
--
-- Returns the file/dir relative to watch root
-- including a trailing slash for dirs.
--
path = function
(
event
)
local p = getPath( event )
if string.byte( p, 1 ) == 47
then
p = string.sub( p, 2, -1 )
end
return p
end,
--
-- Returns the directory of the file/dir relative to watch root
-- Always includes a trailing slash.
--
pathdir = function
(
event
)
local p = getPath( event )
if string.byte( p, 1 ) == 47
then
p = string.sub( p, 2, -1 )
end
return string.match( p, '^(.*/)[^/]+/?' ) or ''
end,
--
-- Returns the file/dir relativ to watch root
-- excluding a trailing slash for dirs.
--
pathname = function
(
event
)
local p = getPath( event )
if string.byte( p, 1 ) == 47
then
p = string.sub( p, 2, -1 )
end
return cutSlash( p )
end,
--
-- Returns the absolute path of the watch root.
-- All symlinks are resolved.
--
source = function
(
event
)
return e2d[ event ].sync.source
end,
--
-- Returns the absolute path of the file/dir
-- including a trailing slash for dirs.
--
sourcePath = function
(
event
)
return e2d[ event ].sync.source .. getPath( event )
end,
--
-- Returns the absolute dir of the file/dir
-- including a trailing slash.
--
sourcePathdir = function
(
event
)
return(
e2d[event].sync.source
.. (
string.match( getPath( event ), '^(.*/)[^/]+/?' )
or ''
)
)
end,
--
-- Returns the absolute path of the file/dir
-- excluding a trailing slash for dirs.
--
sourcePathname = function
(
event
)
return e2d[ event ].sync.source .. cutSlash( getPath( event ) )
end,
--
-- Returns the configured target.
--
target = function
(
event
)
return e2d[ event ].sync.config.target
end,
--
-- Returns the relative dir/file appended to the target
-- including a trailing slash for dirs.
--
targetPath = function
(
event
)
return e2d[ event ].sync.config.target .. getPath( event )
end,
--
-- Returns the dir of the dir/file appended to the target
-- including a trailing slash.
--
targetPathdir = function
(
event
)
return(
e2d[ event ].sync.config.target
.. (
string.match( getPath( event ), '^(.*/)[^/]+/?' )
or ''
)
)
end,
--
-- Returns the relative dir/file appended to the target
-- excluding a trailing slash for dirs.
--
targetPathname = function( event )
return(
e2d[ event ].sync.config.target
.. cutSlash( getPath( event ) )
)
end,
}
--
-- Retrievs event fields for the user script.
--
local eventMeta =
{
__index = function
(
event,
field
)
local f = eventFields[ field ]
if not f
then
if field == 'move'
then
-- possibly undefined
return nil
end
error( 'event does not have field "' .. field .. '"', 2 )
end
return f( event )
end
}
--
-- Interface for user scripts to get list fields.
--
local eventListFuncs =
{
--
-- Returns a list of paths of all events in list.
--
--
getPaths = function
(
elist, -- handle returned by getevents( )
mutator -- if not nil called with ( etype, path, path2 )
-- returns one or two strings to add.
)
local dlist = e2d[ elist ]
if not dlist
then
error( 'cannot find delay list from event list.' )
end
local result = { }
local resultn = 1
for k, d in ipairs( dlist )
do
local s1, s2
if mutator
then
s1, s2 = mutator( d.etype, d.path, d.path2 )
else
s1, s2 = d.path, d.path2
end
result[ resultn ] = s1
resultn = resultn + 1
if s2
then
result[ resultn ] = s2
resultn = resultn + 1
end
end
return result
end
}
--
-- Retrievs event list fields for the user script
--
local eventListMeta =
{
__index = function
(
elist,
func
)
if func == 'isList'
then
return true
end
if func == 'config'
then
return e2d[ elist ].sync.config
end
local f = eventListFuncs[ func ]
if not f
then
error(
'event list does not have function "' .. func .. '"',
2
)
end
return function
( ... )
return f( elist, ... )
end
end
}
--
-- Table of all inlets with their syncs.
--
local inlets = { }
--
-- Allows the garbage collector to remove entries.
--
setmetatable( inlets, { __mode = 'v' } )
--
-- Encapsulates a delay into an event for the user script.
--
local function d2e
(
delay -- delay to encapsulate
)
-- already created?
local eu = e2d2[ delay ]
if delay.etype ~= 'Move'
then
if eu then return eu end
local event = { }
setmetatable( event, eventMeta )
e2d[ event ] = delay
e2d2[ delay ] = event
return event
else
-- moves have 2 events - origin and destination
if eu then return eu[1], eu[2] end
local event = { move = 'Fr' }
local event2 = { move = 'To' }
setmetatable( event, eventMeta )
setmetatable( event2, eventMeta )
e2d[ event ] = delay
e2d[ event2 ] = delay
e2d2[ delay ] = { event, event2 }
-- move events have a field 'move'
return event, event2
end
end
--
-- Encapsulates a delay list into an event list for the user script.
--
local function dl2el
(
dlist
)
local eu = e2d2[ dlist ]
if eu then return eu end
local elist = { }
setmetatable( elist, eventListMeta )
e2d [ elist ] = dlist
e2d2[ dlist ] = elist
return elist
end
--
-- The functions the inlet provides.
--
local inletFuncs =
{
--
-- Appens a filter.
--
appendFilter = function
(
sync, -- the sync of the inlet
rule, -- '+' or '-'
pattern -- exlusion pattern to add
)
sync:appendFilter( rule, pattern )
end,
--
-- Gets the list of filters and excldues
-- as rsync-like filter/patterns form.
--
getFilters = function
(
sync -- the sync of the inlet
)
-- creates a copy
local e = { }
local en = 1;
if sync.filters
then
for _, entry in ipairs( sync.filters.list )
do
e[ en ] = entry.rule .. ' ' .. entry.pattern;
en = en + 1;
end
end
return e;
end,
--
-- Returns true if the sync has filters
--
hasFilters = function
(
sync -- the sync of the inlet
)
return not not sync.filters
end,
--
-- Creates a blanketEvent that blocks everything
-- and is blocked by everything.
--
createBlanketEvent = function
(
sync -- the sync of the inlet
)
return d2e( sync:addBlanketDelay( ) )
end,
--
-- Discards a waiting event.
--
discardEvent = function
(
sync,
event
)
local delay = e2d[ event ]
if delay.status ~= 'wait'
then
log(
'Error',
'Ignored cancel of a non-waiting event of type ',
event.etype
)
return
end
sync:removeDelay( delay )
end,
--
-- Gets the next not blocked event from queue.
--
getEvent = function
(
sync
)
return d2e( sync:getNextDelay( now( ) ) )
end,
--
-- Gets all events that are not blocked by active events.
--
getEvents = function
(
sync, -- the sync of the inlet
test -- if not nil use this function to test if to include an event
)
local dlist = sync:getDelays( test )
return dl2el( dlist )
end,
--
-- Returns the configuration table specified by sync{ }
--
getConfig = function( sync )
-- TODO give a readonly handler only.
return sync.config
end,
}
--
-- Forwards access to inlet functions.
--
local inletMeta =
{
__index = function
(
inlet,
func
)
local f = inletFuncs[ func ]
if not f
then
error( 'inlet does not have function "'..func..'"', 2 )
end
return function( ... )
return f( inlets[ inlet ], ... )
end
end,
}
--
-- Creates a new inlet for a sync.
--
local function newInlet
(
sync -- the sync to create the inlet for
)
-- Lsyncd runner controlled variables
local inlet = { }
-- sets use access methods
setmetatable( inlet, inletMeta )
inlets[ inlet ] = sync
return inlet
end
--
-- Returns the delay from a event.
--
local function getDelayOrList
(
event
)
return e2d[ event ]
end
--
-- Returns the sync from an event or list
--
local function getSync
(
event
)
return e2d[ event ].sync
end
--
-- Exported interface.
--
InletFactory = {
getDelayOrList = getDelayOrList,
d2e = d2e,
dl2el = dl2el,
getSync = getSync,
newInlet = newInlet,
}

314
mantle/inotify.lua Normal file
View File

@ -0,0 +1,314 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- inotify.lua
--
--
-- Interface to inotify core.
--
-- watches recursively subdirs and sends events.
-- All inotify specific implementation is enclosed here.
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
--
-- Safes mantle stuff
--
local core = core
local log = core.log
local Syncs
local uSettings
--
-- Returns the relative part of absolute path if it
-- begins with root
--
local function splitPath
(
path,
root
)
local rlen = #root
local sp = string.sub( path, 1, rlen )
if sp == root
then
return string.sub( path, rlen, -1 )
else
return nil
end
end
--
-- A list indexed by inotify watch descriptors yielding
-- the directories absolute paths.
--
local wdpaths = Counter.new( )
--
-- The same vice versa,
-- all watch descriptors by their absolute paths.
--
local pathwds = { }
--
-- A list indexed by syncs containing yielding
-- the root paths the syncs are interested in.
--
local syncRoots = { }
--
-- Stops watching a directory
--
local function removeWatch
(
path, -- absolute path to unwatch
core -- if false not actually send the unwatch to the kernel
-- ( used in moves which reuse the watch )
)
local wd = pathwds[ path ]
if not wd then return end
if core then core.inotify.rmwatch( wd ) end
wdpaths[ wd ] = nil
pathwds[ path ] = nil
end
--
-- Adds watches for a directory (optionally) including all subdirectories.
--
local function addWatch
(
path -- absolute path of directory to observe
)
log( 'Function', 'Inotify.addWatch( ', path, ' )' )
if not Syncs.concerns( path )
then
log( 'Inotify', 'not concerning "', path, '"')
return
end
-- registers the watch
local inotifyMode = ( uSettings and uSettings.inotifyMode ) or ''
local wd = core.inotify.addwatch( path, inotifyMode )
if wd < 0
then
log( 'Inotify', 'Unable to add watch "', path, '"' )
return
end
do
-- If this watch descriptor is registered already
-- the kernel reuses it since the old dir is gone.
local op = wdpaths[ wd ]
if op and op ~= path then pathwds[ op ] = nil end
end
pathwds[ path ] = wd
wdpaths[ wd ] = path
-- registers and adds watches for all subdirectories
local entries = core.readdir( path )
if not entries then return end
for dirname, isdir in pairs( entries )
do
if isdir then addWatch( path .. dirname .. '/' ) end
end
end
--
-- Adds a Sync to receive events.
--
local function addSync
(
sync, -- object to receive events.
rootdir -- root dir to watch
)
if syncRoots[ sync ]
then
error( 'duplicate sync in Inotify.addSync()' )
end
syncRoots[ sync ] = rootdir
addWatch( rootdir )
end
--
-- Called when an event has occured.
--
local function event
(
etype, -- 'Attrib', 'Modify', 'Create', 'Delete', 'Move'
wd, -- watch descriptor, matches core.inotifyadd()
isdir, -- true if filename is a directory
time, -- time of event
filename, -- string filename without path
wd2, -- watch descriptor for target if it's a Move
filename2 -- string filename without path of Move target
)
if isdir
then
filename = filename .. '/'
if filename2 then filename2 = filename2 .. '/' end
end
if filename2
then
log(
'Inotify',
'got event ', etype, ' ',
filename, '(', wd, ') to ',
filename2, '(', wd2 ,')'
)
else
log(
'Inotify',
'got event ', etype, ' ',
filename, '(', wd, ')'
)
end
-- looks up the watch descriptor id
local path = wdpaths[ wd ]
if path then path = path..filename end
local path2 = wd2 and wdpaths[ wd2 ]
if path2 and filename2 then path2 = path2..filename2 end
if not path and path2 and etype == 'Move'
then
log( 'Inotify', 'Move from deleted directory ', path2, ' becomes Create.' )
path = path2
path2 = nil
etype = 'Create'
end
if not path
then
-- this is normal in case of deleted subdirs
log( 'Inotify', 'event belongs to unknown watch descriptor.' )
return
end
for sync, root in pairs( syncRoots )
do repeat
local relative = splitPath( path, root )
local relative2 = nil
if path2 then relative2 = splitPath( path2, root ) end
if not relative and not relative2
then
-- sync is not interested in this dir
break -- continue
end
-- makes a copy of etype to possibly change it
local etyped = etype
if etyped == 'Move'
then
if not relative2
then
log(
'Normal',
'Transformed Move to Delete for ',
sync.config.name
)
etyped = 'Delete'
elseif not relative
then
relative = relative2
relative2 = nil
log(
'Normal',
'Transformed Move to Create for ',
sync.config.name
)
etyped = 'Create'
end
end
if isdir
then
if etyped == 'Create'
then
addWatch( path )
elseif etyped == 'Delete'
then
removeWatch( path, true )
elseif etyped == 'Move'
then
removeWatch( path, false )
addWatch( path2 )
end
end
sync:delay( etyped, time, relative, relative2 )
until true end
end
--
-- Writes a status report about inotify to a file descriptor
--
local function statusReport( f )
f:write( 'Inotify watching ', #wdpaths, ' directories\n' )
for wd, path in pairs( wdpaths )
do
f:write( ' ', wd, ': ', path, '\n' )
end
end
--
-- Exported interface.
--
Inotify =
{
addSync = addSync,
event = event,
statusReport = statusReport,
-- FIXME
init = function( _Syncs, _uSettings ) Syncs = _Syncs; uSettings = _uSettings end
}

67
mantle/lock.lua Normal file
View File

@ -0,0 +1,67 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- lsyncd.lua Live (Mirror) Syncing Demon
--
--
-- Locks globals.
--
-- No more globals can be created after this!
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
local t = _G
local mt = getmetatable( t ) or { }
-- TODO try to remove the underscore exceptions
mt.__index = function
(
t, -- table being accessed
k -- key used to access
)
if k ~= '_' and string.sub( k, 1, 2 ) ~= '__'
then
error( 'Access of non-existing global "' .. k ..'"', 2 )
else
rawget( t, k )
end
end
mt.__newindex = function
(
t, -- table getting a new index assigned
k, -- key value to assign to
v -- value to assign
)
if k ~= '_' and string.sub( k, 1, 2 ) ~= '__'
then
error(
'Lsyncd does not allow GLOBALS to be created on the fly. '
.. 'Declare "' .. k.. '" local or declare global on load.',
2
)
else
rawset( t, k, v )
end
end
function lockGlobals( )
setmetatable( t, mt )
end

File diff suppressed because it is too large Load Diff

299
mantle/queue.lua Normal file
View File

@ -0,0 +1,299 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- lsyncd.lua Live (Mirror) Syncing Demon
--
--
-- The queue is optimized for FILO operation.
--
--
-- This code assumes your editor is at least 100 chars wide.
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
--
-- Metatable.
--
local mt = { }
--
-- Key to native table.
--
local k_nt = { }
--
-- On accessing a nil index.
--
mt.__index = function
(
self,
k -- key used to access
)
if type( k ) ~= 'number'
then
error( 'Queue, key "'..k..'" invalid', 2 )
end
return self[ k_nt ][ k ]
end
--
-- On assigning a new index.
--
mt.__newindex = function
(
self,
k, -- key value to assign to
v -- value to assign
)
error( 'Queues are not directly assignable.', 2 )
end
--
-- Returns the size of the queue.
--
mt.__len = function
(
self
)
return self[ k_nt ].size
end
--
-- Returns the first item of the Queue.
--
local function first
(
self
)
local nt = self[ k_nt ]
return nt[ nt.first ]
end
--
-- Returns the last item of the Queue.
--
local function last
(
self
)
local nt = self[ k_nt ]
return nt[ nt.last ]
end
--
-- Pushes a value on the queue.
-- Returns the last value
--
local function push
(
self,
value -- value to push
)
if not value
then
error( 'Queue pushing nil value', 2 )
end
local nt = self[ k_nt ]
local last = nt.last + 1
nt.last = last
nt[ last ] = value
nt.size = nt.size + 1
return last
end
--
-- Removes an item at pos from the Queue.
--
local function remove
(
self,
pos -- position to remove
)
local nt = self[ k_nt ]
if nt[ pos ] == nil
then
error( 'Removing nonexisting item in Queue', 2 )
end
nt[ pos ] = nil
-- if removing first or last element,
-- the queue limits are adjusted.
if pos == nt.first
then
local last = nt.last
while nt[ pos ] == nil and pos <= last
do
pos = pos + 1
end
nt.first = pos
elseif pos == nt.last
then
local first = nt.first
while nt[ pos ] == nil and pos >= first
do
pos = pos - 1
end
nt.last = pos
end
-- reset the indizies if the queue is empty
if nt.last < nt.first
then
nt.first = 1
nt.last = 0
end
nt.size = nt.size - 1
end
--
-- Replaces a value.
--
local function replace
(
self,
pos, -- position to replace
value -- the new entry
)
local nt = self[ k_nt ]
if nt[ pos ] == nil
then
error( 'Trying to replace an unset Queue entry.' )
end
nt[ pos ] = value
end
--
-- Queue iterator ( stateless )
--
local function iter
(
self,
pos
)
local nt = self[ k_nt ]
pos = pos + 1
while nt[ pos ] == nil and pos <= nt.last
do
pos = pos + 1
end
if pos > nt.last then return nil end
return pos, nt[ pos ]
end
--
-- Reverse queue iterator (stateless)
--
local function revIter
(
self,
pos
)
local nt = self[ k_nt ]
pos = pos - 1
while nt[ pos ] == nil and pos >= nt.first
do
pos = pos - 1
end
if pos < nt.first
then
return nil
end
return pos, nt[ pos ]
end
--
-- Iteraters through the queue
-- returning all non-nil pos-value entries.
--
local function qpairs
(
self
)
return iter, self, self[ k_nt ].first - 1
end
--
-- Iteraters backwards through the queue
-- returning all non-nil pos-value entries.
--
local function qpairsReverse
(
self
)
return revIter, self, self[ k_nt ].last + 1
end
--
-- Creates a new queue.
--
local function new
( )
local q =
{
first = first,
last = last,
push = push,
qpairs = qpairs,
qpairsReverse = qpairsReverse,
remove = remove,
replace = replace,
[ k_nt ] =
{
first = 1,
last = 0,
size = 0
}
}
setmetatable( q, mt )
return q
end
--
-- Exported interface
--
Queue = { new = new }

View File

@ -1,22 +1,35 @@
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- lsyncd.lua Live (Mirror) Syncing Demon
--
--
-- Wraps up globals of the mantle to set up the Lua
-- space for user scripts.
-- This must come as last mantle file.
--
--
-- License: GPLv2 (see COPYING) or any later version
-- Authors: Axel Kittenberger <axkibe@gmail.com>
--
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if mantle
then
lsyncd.log( 'Error', 'Lsyncd mantle already loaded' )
lsyncd.terminate( -1 )
print( 'Error, Lsyncd mantle already loaded' )
os.exit( -1 )
end
-- Lets the core double check version identity with the mantle
mantle = '3.0.0-devel'
Inotify.init( Syncs, uSettings )
core = nil
lockGlobals = nil
Inotify = nil
Delay = nil
InletFactory = nil
Filter = nil