FTP Website Manager.fh_lua--[[
@Title: FTP Website Manager
@Author: Jane Taubman & Mike Tate
@Version: 1.3
@LastUpdated: 05 Feb 2015
@Description: Uploads Family Historian Generated Websites to an FTP site
When run it builds a database of the pages and on a second run only uploads the pages which have changed.
Note: If you include the current date in the pages this will class as all pages having changed.
V1.1 Fix Module Loader problem
V1.2 If one File Fails give option to continue upload.
V1.3 Change ftp.lua module to use "PASV" for rootsweb. Fix "DELE" source 'no such file' error. Fix 'Cancel' from settings prompt. Handle subfolders. Allow prime ' in settings. Other minor fixes. All commented with -- V1.3
]]
-- debug = true
setting = {} -- Holds the settings for Source Directory and FTP Host, Folder, Username, Password, and Trace
function main()
local dbenv,dbcon = opendb(fhGetPluginDataFileName())
setting = loadSettings(dbcon)
local bclose = false
local iButton
while not(bclose) do
if lfs.attributes(setting.directory,"mode") ~= "directory" then -- V1.3 warn that directory is missing
fhMessageBox(setting.directory.."\n\nDirectory does not exist, and if not corrected,\nthen all FTP server files will get deleted!")
end
if setting.new then
local promptMessage = 'Please use the Change Settings button to set up.'
iButton = iupButtons('FTP Website Manager',promptMessage,'V','Change Settings','Help','Exit')
if iButton > 0 then iButton = iButton + 2 end
else
local directory = setting.directory
if #directory > 60 then
directory = '...'..directory:sub(-60) -- V1.3 if more than 60 chars prefix truncated directory with elipsis
end
local promptMessage = string.format('From:\n %s\nTo:\n %s/%s',directory,setting.host,setting.folder)
iButton = iupButtons('FTP Website Manager',promptMessage,'V','Update Site','Clear Page History','Change Settings','Help','Exit')
end
if iButton == 1 then
local list = buildpagelist(dbcon,setting)
if list then
if #list == 0 then
fhMessageBox('No files found to update')
return
else
local ret = uploadFiles(dbcon,list)
local msg = ' Files Updated'
if #list == 1 then msg = msg:gsub('Files','File') end
fhMessageBox(#list..msg)
end
end
elseif iButton == 2 then
resetDatabase(dbcon)
elseif iButton == 3 then
settingsPrompt(dbcon)
elseif iButton == 4 then
-- Show Help Window
GUI_HelpDialogue()
elseif iButton == 5 or iButton == 0 then
bclose = true
end
end
closedb(dbenv,dbcon)
end
function settingsPrompt(dbcon)
local ret
local settings = {}
for a,b in pairs (setting) do settings[a] = b end -- V1.3 preserve settings in case Cancel selected
local defdir = setting.directory or ""
if lfs.attributes(defdir,"mode") ~= "directory" then -- V1.3 restore default if setting.directory missing
defdir = default
end
ret,setting.directory,setting.host,setting.folder,setting.userid,setting.password,setting.trace =
iup.GetParam("FTP Website Manager - Settings", iup.NULL,
"Source Folder: %f[DIR||"..defdir.."]\n".. -- V1.3 correct iupFileDlg params
"FTP Host URL: %s\n"..
"FTP Folder: %s\n"..
"FTP Username: %s\n"..
"FTP Password: %s\n"..
"FTP Trace: %b[ No thanks , Yes please ]\n",
setting.directory,setting.host,setting.folder,setting.userid,setting.password,setting.trace)
if (ret == true) then
setting.directory = setting.directory:gsub('/','\\'):gsub('\\+$','') -- V1.3 change all / to \ and erase all trailing \
setting.folder = setting.folder:gsub('\\','/'):gsub('/+$','') -- V1.3 change all \ to / and erase all trailing /
dump(setting.directory)
saveSettings(dbcon,setting)
setting.new = nil
else
for a,b in pairs (settings) do setting[a] = b end -- V1.3 restore original settings after Cancel
end
end
function uploadFiles(dbcon,list)
-- Connect to FTP server
ProgressDisplay.Start('Updating Files on FTP Server',100)
local ipos = 0
local step = 1/#list * 100
local bQuit = false
for i,filedata in pairs(list) do
local tofile = filedata.filename:gsub(setting.directory..'\\','') -- V1.3 remove '\' between folder and filename
ipos = ipos + 1
ProgressDisplay.SetMessage(filedata.action..': '..tofile..' ('..ipos..'/'..#list..')')
ProgressDisplay.Step(step)
if ProgressDisplay.Cancel() then
break
end
local status = ftpPutFile(filedata.filename,filedata.action) -- V1.3 do not need tofile param
if status == 'OK' then
updRecord(dbcon,filedata.filename,filedata.md5hash)
else
local strMsg = 'An error occured updating: '..tofile..'\n'..status..'\nDo you want to continue with other files?'
local a = fhMessageBox(strMsg,'MB_YESNO','MB_ICONQUESTION')
if a == 'No' then
bQuit = true
end
end
if bQuit then
break
end
end
ProgressDisplay.Reset()
ProgressDisplay.Close()
end
function ftpPutFile(fromfile,action) -- V1.3 do not need tofile param, use file instead
local actions = {update='STOR',delete='DELE'}
local path, file, ext = SplitFilename(fromfile)
local stype = 'i'
if ext == 'html' or ext == 'css' or ext == 'js' then
-- Use Binary for everything other than the html and css files.
stype = 'a'
end
if action == 'delete' then fromfile = 'nul' end -- V1.3 avoid 'no such file error' for source
local p = {
host = setting.host,
user = setting.userid,
password = setting.password,
command = actions[action],
argument = setting.folder..'/'..file, -- V1.3 use file instead of tofile
type = stype,
source = ltn12.source.file(io.open(fromfile, "rb"))
}
local f, e = ftp.put(p)
showtrace('ftp.put',p,setting.trace)
return e or 'OK'
end
-- Update record for new files
function updRecord(dbcon,filename,md5hash) -- V1.3 use "%s" to allow prime ' in filename
local sql = string.format('UPDATE pagelist set md5hash = "%s" where filename="%s"',md5hash,filename)
local res = assert(dbcon:execute(sql))
end
function getFTPList(setting)
local t = {}
local g = {
host = setting.host,
sink = ltn12.sink.table(t),
user = setting.userid,
password = setting.password,
argument = setting.folder, -- V1.3 use argument instead of appending to command
command = 'NLST',
type = 'a'
}
local _, e = ftp.get(g)
showtrace('ftp.get',g,setting.trace)
if e then
return nil,e
else
local t2 = table.concat(t)
local t3 = split(t2,'\r\n')
return t3
end
end
function showtrace(func,ftp,trace) -- V1.3 tp trace debug listing
local strTrace = '\nOperation:\t'..func..' '..ftp.command..' '..ftp.argument..'\n'
strTrace = strTrace..table.concat(tp.GetTrace(),'\n')
strTrace = strTrace:gsub('Response:','Response: ')
if trace == 1 then
if fhMessageBox('FTP Website Manager - Trace Log\n'..strTrace,'MB_OKCANCEL') == 'Cancel' then setting.trace = 0 end
end
print(strTrace)
end
function buildpagelist(dbcon,setting)
local function fliptable(table)
local newtable = {}
for i,v in pairs(table) do
v = v:gsub(setting.folder.."/","") -- V1.3 remove folder from filename
newtable[v] = i
end
return newtable
end
local function chkrecord(filename)
-- Add records for new files -- V1.3 use "%s" to allow prime ' in filename
local sql = string.format('INSERT OR IGNORE INTO pagelist (filename,md5hash) VALUES ("%s"," ")',filename)
local res = assert(dbcon:execute(sql))
end
local function getHash(filename)
-- Add records for new files -- V1.3 use "%s" to allow prime ' in filename
local sql = string.format('select md5hash from pagelist where filename = "%s"',filename)
local cur = assert(dbcon:execute(sql))
local row = cur:fetch()
cur:close()
return row
end
local basedir = setting.directory
local tblList = {}
local tblAll = {}
ProgressDisplay.Start('Building Transfer List',100)
ProgressDisplay.SetMessage('Retrieving List From FTP Server')
local ftplist, e = getFTPList(setting)
if e then
fhMessageBox('Error Occured Connecting to FTP site. Please check your settings\n'..e)
ProgressDisplay.Reset()
ProgressDisplay.Close()
return
end
ftplist = fliptable(ftplist)
-- Quick Count Files
local icount = 0
for filename, attr in dirtree(basedir) do
local path, file, ext = SplitFilename(filename) -- V1.3 only count contents of basedir and not sub-folders
if path == basedir..'\\'
and attr.mode == 'file' then icount = icount + 1 end -- V1.3 correct istep to icount, and only count files not folders
end
local istep = 1 / icount * 100
ProgressDisplay.SetMessage(#tblList..' files found to update... ')
for filename, attr in dirtree(basedir) do
local path, file, ext = SplitFilename(filename) -- V1.3 only check contents of basedir and not sub-folders
if path == basedir..'\\' then
if attr.mode == 'file' then -- V1.3 handle files here
ProgressDisplay.Step(istep)
if ProgressDisplay.Cancel() then
break
end
ProgressDisplay.SetMessage(#tblList..' files found to update... Checking: '..file)
chkrecord(filename) -- Add if needed
local oldhash = getHash(filename)
-- Remove file from FTP List as it does not need deleting
ftplist[file] = nil
-- compute MD5 routine
local md5hash = md5.sumhexa(LoadFromFile(filename))
if oldhash ~= md5hash then
table.insert(tblList,{filename=filename, md5hash=md5hash, action='update'})
ProgressDisplay.SetMessage(#tblList..' files found to update... ')
end
else
ftplist[file] = nil -- V1.3 remove matching folders from FTP List to prevent deletion
end
end
end
for i,v in pairs(ftplist) do
-- In the Delete List remove any files starting with . and any folders
if i:sub(1,1) ~= '.' then
local path, file, ext = SplitFilename(i)
if file ~= ext then -- V1.3 exclude folders to prevent deletion
chkrecord(file) -- Add if needed
table.insert(tblList,{filename=file, md5hash='0', action='delete'}) -- V1.3 only need filename for Delete
end
end
end
ProgressDisplay.Reset()
ProgressDisplay.Close()
return tblList
end
function resetDatabase(dbcon)
local sql = 'delete from pagelist'
local res = assert(dbcon:execute(sql))
fhMessageBox('Page Database cleared')
end
function opendb(dbname)
-- Check for Settings Database and create if needed
local db = fhGetPluginDataFileName()
local dbenv = assert (luasql.sqlite3())
-- connect to data source, if the file does not exist it will be created
local dbcon = assert (dbenv:connect(db))
-- check table for page list
checkTable(dbcon,'pagelist',
[[CREATE TABLE pagelist(filename varchar(500), md5hash varchar(32), UNIQUE (filename))
]])
-- create table for settings
checkTable(dbcon,'settings',
[[CREATE TABLE settings(key varchar(20), directory varchar(500),
host varchar(500), folder varchar(50), userid varchar(50), password varchar(50), UNIQUE (key))
]])
return dbenv,dbcon
end
function checkTable(dbcon,table,createString)
local sql = string.format([[SELECT count(name) as count FROM sqlite_master WHERE type='table' AND name='%s']],table)
local cur = assert(dbcon:execute(sql))
local rowcount = cur:fetch (row, "a")
cur:close()
if tonumber(rowcount) == 0 then
-- Table not found create it
local res,err = assert(dbcon:execute(createString))
end
end
function closedb(dbenv,dbcon)
dbcon:close()
dbenv:close()
end
function loadSettings(dbcon)
local sql = [[SELECT * FROM settings]]
local cur,err = assert(dbcon:execute(sql))
local row = cur:fetch({},'a')
cur:close()
if row then
for item, text in pairs (row) do
if type(text) == "string" then
row[item] = text:gsub("\127","'") -- V1.3 convert delete char back to prime ' char
end
end
row.trace = 0
return row
else
-- return default values
return {
directory = default, -- V1.3 default to Project...\Public\FH Website
host = 'websitehost',
folder = '/',
userid = 'user',
password = 'password',
trace = 0,
new = 'yes'
}
end
end
function saveSettings(dbcon,settings)
local row = {}
for item, text in pairs (settings) do
if type(text) == "string" then
row[item] = text:gsub("'","\127") -- V1.3 prime ' not allowed in sql values so convert to delete char
end
end
-- Check for Settings
if row.new == 'yes' then
-- Create
sql = string.format([[insert into settings (directory, host, folder, userid, password, trace) Values('%s','%s','%s','%s','%s')]],row.directory,row.host,row.folder,row.userid,row.password)
else
-- Update
sql = string.format([[update settings set directory = '%s', host = '%s', folder = '%s', userid = '%s', password = '%s']],row.directory,row.host,row.folder,row.userid,row.password)
end
local res = assert(dbcon:execute(sql))
end
function split(str, pat)
local t = {} -- NOTE: use {n = 0} in Lua-5.0
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(t,cap)
end
last_end = e+1
s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
cap = str:sub(last_end)
table.insert(t, cap)
end
return t
end
function dirtree(dir)
assert(dir and dir ~= "", "directory parameter is missing or empty")
if string.sub(dir, -1) == "/" then
dir=string.sub(dir, 1, -2)
end
local function yieldtree(dir)
for entry in lfs.dir(dir) do
if entry ~= "." and entry ~= ".." then
entry=dir.."\\"..entry
local attr=lfs.attributes(entry)
coroutine.yield(entry,attr)
if attr.mode == "directory" then
yieldtree(entry)
end
end
end
end
return coroutine.wrap(function() yieldtree(dir) end)
end
--[[
@Title: Progress Display (drop in)
@Author: Jane Taubman / Mike Tate
@LastUpdated: May 2012
@Description: Allows easy adding of a Progress Bar to any long running Plugin
]]
ProgressDisplay = {
Start = function(strTitle,intMax) -- Create and start the Progress Display window controls
local StrWhite = "255 255 255"
if not dlgProgress then
cancelflag = false
local cancelbutton = iup.button { title="Cancel", rastersize="200x30",
action = function()
cancelflag = true -- Signal that Cancel button was pressed
return iup.CLOSE
end
}
gaugeProgress = iup.progressbar { rastersize="400x30", max=intMax } -- Set progress bar maximum range
messageline = iup.label { title=" ", expand="YES", alignment="ACENTER" }
dlgProgress = iup.dialog { title=strTitle, dialogframe="YES", background=strWhite, -- Remove Windows minimize/maximize menu
iup.vbox { alignment="ACENTER", gap="10", margin="10x10",
messageline,
gaugeProgress,
cancelbutton
}
}
dlgProgress.close_cb = cancelbutton.action -- Windows Close button acts as Cancel button
dlgProgress:showxy(iup.CENTER, iup.CENTER) -- Show the Progress Display dialogue window
end
end,
SetMessage = function(strMessage) -- Set the progress message
if dlgProgress then messageline.title = strMessage end
end,
Step = function(iStep) -- Step the Progress Bar forward
if dlgProgress then
gaugeProgress.value = gaugeProgress.value + iStep
local val = tonumber(gaugeProgress.value)
local max = tonumber(gaugeProgress.max)
if val > max then
gaugeProgress.value = 0
end
iup.LoopStep()
end
end,
Reset = function() -- Reset progress bar
if dlgProgress then gaugeProgress.value = 0 end
end,
Cancel = function() -- Check if Cancel button pressed
return cancelflag
end,
Close = function() -- Close the dialogue window
cancelflag = false
if dlgProgress then dlgProgress:destroy() dlgProgress = nil end
end,
}
-------------------------------------------------------------------------------------------- End Progress Bar
-- Open File and return Handle --
function OpenFile(strFileName,strMode)
local fileHandle, strError = io.open(strFileName,strMode)
if not fileHandle then
error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..tostring(strError).." \n")
end
return fileHandle
end -- function OpenFile
-- Load string from file --
function LoadFromFile(strFileName)
local fileHandle = OpenFile(strFileName,"rb")
local strString = fileHandle:read("*all")
assert(fileHandle:close())
return strString
end -- function LoadFromFile
-- Save string to file --
function SaveStringToFile(strString,strFileName)
local fileHandle = OpenFile(strFileName,"w")
fileHandle:write(strString)
assert(fileHandle:close())
end -- function SaveStringToFile
-- Return the Path, Filename, and extension as 3 values
function SplitFilename(strFilename)
return strFilename:match("(.-)([^\\]-([^\\%.]+))$")
end -- function SplitFilename
-- Load Module
function loadrequire(module,extended)
if not(extended) then extended = module end
local function installmodule(module,filename)
local bmodule = false
if not(filename) then
filename = module..'.mod'
bmodule = true
end
local storein = fhGetContextInfo('CI_APP_DATA_FOLDER')..'\\Plugins\\'
-- Check if subdirectory needed
local path = string.match(filename, "(.-)[^/]-[^%.]+$")
if path ~= "" then
path = path:gsub('/','\\')
-- Create sub-directory
lfs.mkdir(storein..path)
end
-- Get file down and install it
local http = luacom.CreateObject("winhttp.winhttprequest.5.1")
local url = "http://www.family-historian.co.uk/lnk/getpluginmodule.php?file="..filename
http:Open("GET",url,false)
http:Send()
http:WaitForResponse(30)
local status = http.StatusText
if status == 'OK' then
length = http:GetResponseHeader('Content-Length')
data = http.ResponseBody
if bmodule then
local modlist = loadstring(http.ResponseBody)
for _,f in pairs(modlist()) do
if not(installmodule(module,f)) then
break
end
end
else
local function OpenFile(strFileName,strMode)
local fileHandle, strError = io.open(strFileName,strMode)
if not fileHandle then
error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..tostring(strError).." \n")
end
return fileHandle
end -- OpenFile
local function SaveStringToFile(strString,strFileName)
local fileHandle = OpenFile(strFileName,"wb")
fileHandle:write(strString)
assert(fileHandle:close())
end -- SaveStringToFile
SaveStringToFile(data,storein..filename)
end
return true
else
fhMessageBox('An error occurred in Download please try later')
return false
end
end
local function requiref(module)
require(module)
end
local res = pcall(requiref,extended)
if not(res) then
local ans = fhMessageBox(
'This plugin requires '..module..' support, please click OK to download and install the module',
'MB_OKCANCEL','MB_ICONEXCLAMATION')
if ans ~= 'OK' then
return false
end
if installmodule(module) then
package.loaded[extended] = nil -- Reset Failed Load
require(extended)
else
return false
end
end
return true
end
--[[
@Title: User Interface Buttons Snippet
@Author: Mike Tate / Jane Taubman
@LastUpdated: May 2012
@Version: 1.4
@Description: GUI dialogue for multiple buttons
@params
strTitle: Title of Message Box
strMessage: Message to show above buttons
strBoxType: Either "H" for Horizontal buttons or "V" for Vertical ones.
... : All other parameters will be treated as button titles.
]]
function iupButtons(strTitle,strMessage,strBoxType,...)
local intButton = 0 -- Returned value if X Close button is used
-- Create the GUI labels and buttons
local lblMessage = iup.label{title=strMessage,expand="YES"}
local lblLineSep = iup.label{separator="HORIZONTAL"}
local iupBox = iup.hbox{homogeneous="YES"}
if strBoxType == "V" then
iupBox = iup.vbox{homogeneous="YES"}
end
for intArgNum, strButton in ipairs(arg) do
local btnName = iup.button{title=strButton,expand="YES",padding="4",action=function() intButton=intArgNum return iup.CLOSE end}
iup.Append(iupBox,btnName)
end
-- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
local dialogue = iup.dialog{title=strTitle,iup.vbox{lblMessage,lblLineSep,iupBox},dialogframe="YES",background="250 250 250",gap="8",margin="8x8"}
dialogue:show()
if (iup.MainLoopLevel()==0) then iup.MainLoop() end
dialogue:destroy()
return intButton
end -- function iupButtons
--[[
@Title: Display FHUG Wiki Help Snippet
@Author: Mike Tate
@LastUpdated: May 2012
@Version: 1.1
@Description: Displays FHUG Wiki help pages in a popup GUI.
]]
-- GUI Help & Advice Dialogue --
function GUI_HelpDialogue()
local StrPlugin = "Display FHUG Wiki" -- Plugin title & version
local StrIssue = " V1.0"
local StrRed = "255 000 000" -- Color attributes
local StrGreen = "000 120 000"
local StrBlue = "000 000 255"
local StrBlack = "000 000 000"
local StrWhite = "255 255 255"
local StrBigMargin = "10x10" -- Layout attributes
local StrMinMargin = "1x1"
local StrGap = "10"
local StrFontFace = string.gsub(iup.GetGlobal("DEFAULTFONT"),",.*","")
local StrFontHead = StrFontFace..", Bold -16"
local StrFontBody = StrFontFace..", -16"
local StrFHUG = "http://www.fhug.org.uk/wiki/wiki/doku.php?id="
local function doActivateMainHelpButton()
if BtnMainHelp then BtnMainHelp.active = "YES" end
end
-- Create the WebBrowser based on its ProgID and connect it to LuaCOM
local oleControl = iup.olecontrol{ "Shell.Explorer.1", designmode="NO", }
oleControl:CreateLuaCOM()
-- Create each GUI button with title and tooltip
local btnClose = iup.button { title="Close this Window" , tip="Close this Help and Advice window" }
-- The following control is global to allow Main GUI to alter font
HboxHelp = iup.hbox { font=StrFontBody, margin=StrMinMargin, homogeneous="YES", btnMain, btnPath, btnClose, }
local strExpChild = "NO"
local iupVersion = iup.GetGlobal("VERSION")
if iupVersion == "3.5" then strExpChild = "YES" end -- for IUP 3.11.2
local dialogHelp = iup.dialog { title=StrPlugin.." Help & Advice", background=StrWhite, startfocus=btnClose, rastersize="1000x700",
iup.vbox { alignment="ACENTER", gap=StrGap, margin=StrBigMargin, expandchildren=strExpChild,
oleControl,
HboxHelp,
},
close_cb=function() doActivateMainHelpButton() end,
}
local strFHUG = StrFHUG.."plugins:help:"
-- Set other GUI control attributes
for iupName, tblAttr in pairs( {
-- Control= 1~fgcolor , 2~Navigate URL , 3~action function()
[btnClose]= { StrRed , false , function() dialogHelp:destroy() doActivateMainHelpButton() return iup.CLOSE end },
} ) do
iupName.expand = "HORIZONTAL"
iupName.size = "x10"
iupName.fgcolor = tblAttr[1]
if tblAttr[2] then iupName.action = function() oleControl.com:Navigate(tblAttr[2]) end end
if tblAttr[3] then iupName.action = tblAttr[3] end
end
dialogHelp:show()
dialogHelp.rastersize=iup.NULL -- Allow window to be resized
oleControl.com:Navigate(strFHUG.."ftp_website_manager")
if (iup.MainLoopLevel()==0) then iup.MainLoop() end
end -- function GUI_HelpDialogue
function dump (tt, indent, done, label)
if label == nil then
label = 'Dump'
end
if debug == true then
done = done or {}
indent = indent or 0
if type(tt) == "table" then
if indent == 0 then
io.write(string.rep (" ", indent))
io.write(label..'\n')
end
for key, value in pairs (tt) do
io.write(string.rep (" ", indent)) -- indent it
if type (value) == "table" and not done [value] then
done [value] = true
io.write(string.format("[%s] => table\n", tostring (key)));
io.write(string.rep (" ", indent+4)) -- indent it
io.write("(\n");
dump (tostring(key),value, indent + 7, done)
io.write(string.rep (" ", indent+4)) -- indent it
io.write(")\n");
else
io.write(string.format("[%s] => %s\n",
tostring (key), tostring(value)))
end
end
else
io.write(label..':'..tostring(tt))
end
else
return
end
end
------------------------------------------------------ End of Functions
require 'lfs'
require 'luacom'
require 'iupluaole'
if not(loadrequire('luasql','luasql.sqlite3')) then return end
if not(loadrequire('md5')) then return end
if not(loadrequire('socket')) then return end
local socket = fhGetContextInfo('CI_APP_DATA_FOLDER')..'\\Plugins\\socket'
local module = socket..'\\tp.lua'
local data = LoadFromFile(module)
if not data:match('Trace') then -- V1.3 add tp trace debug feature
data = data:gsub('(TIMEOUT = 60)', '%1\n Trace = {}\n function GetTrace() local t={} for i,v in base.ipairs(Trace) do t[i]=v end Trace={} return t end' )
data = data:gsub('(local reply = line)', '%1\n if Trace then Trace[#Trace+1] = "Response:\t"..(err or reply) end' )
data = data:gsub('(function metat%.__index:command%(cmd, arg%))', '%1\n if Trace then Trace[#Trace+1] = "Command:\t"..cmd.." "..(arg or "") end' )
data = data:gsub('(function connect%(host, port, timeout, create%))', '%1\n if Trace then Trace[#Trace+1] = "Connecting:\t"..host.."\tPort: "..port end' )
SaveStringToFile(data,module)
module = socket..'\\ftp.lua'
data = LoadFromFile(module)
data = data:gsub('"pasv"','"PASV"') -- V1.3 fix ftp module PASV for rootsweb
SaveStringToFile(data,module)
elseif data:match('return Trace') then
data = data:gsub('return Trace','local t={} for i,v in base.ipairs(Trace) do t[i]=v end Trace={} return t')
SaveStringToFile(data,module)
end
ftp = require("socket.ftp")
tp = require("socket.tp")
default = fhGetContextInfo('CI_PROJECT_PUBLIC_FOLDER') -- V1.3 default Source Folder
if lfs.attributes(default..'\\FH Website',"mode") == "directory" then
default = default..'\\FH Website'
end
main()
--[[
@Title: FTP Website Manager
@Author: Jane Taubman & Mike Tate
@Version: 1.3
@LastUpdated: 05 Feb 2015
@Description: Uploads Family Historian Generated Websites to an FTP site
When run it builds a database of the pages and on a second run only uploads the pages which have changed.
Note: If you include the current date in the pages this will class as all pages having changed.
V1.1 Fix Module Loader problem
V1.2 If one File Fails give option to continue upload.
V1.3 Change ftp.lua module to use "PASV" for rootsweb. Fix "DELE" source 'no such file' error. Fix 'Cancel' from settings prompt. Handle subfolders. Allow prime ' in settings. Other minor fixes. All commented with -- V1.3
]]
-- debug = true
setting = {} -- Holds the settings for Source Directory and FTP Host, Folder, Username, Password, and Trace
function main()
local dbenv,dbcon = opendb(fhGetPluginDataFileName())
setting = loadSettings(dbcon)
local bclose = false
local iButton
while not(bclose) do
if lfs.attributes(setting.directory,"mode") ~= "directory" then -- V1.3 warn that directory is missing
fhMessageBox(setting.directory.."\n\nDirectory does not exist, and if not corrected,\nthen all FTP server files will get deleted!")
end
if setting.new then
local promptMessage = 'Please use the Change Settings button to set up.'
iButton = iupButtons('FTP Website Manager',promptMessage,'V','Change Settings','Help','Exit')
if iButton > 0 then iButton = iButton + 2 end
else
local directory = setting.directory
if #directory > 60 then
directory = '...'..directory:sub(-60) -- V1.3 if more than 60 chars prefix truncated directory with elipsis
end
local promptMessage = string.format('From:\n %s\nTo:\n %s/%s',directory,setting.host,setting.folder)
iButton = iupButtons('FTP Website Manager',promptMessage,'V','Update Site','Clear Page History','Change Settings','Help','Exit')
end
if iButton == 1 then
local list = buildpagelist(dbcon,setting)
if list then
if #list == 0 then
fhMessageBox('No files found to update')
return
else
local ret = uploadFiles(dbcon,list)
local msg = ' Files Updated'
if #list == 1 then msg = msg:gsub('Files','File') end
fhMessageBox(#list..msg)
end
end
elseif iButton == 2 then
resetDatabase(dbcon)
elseif iButton == 3 then
settingsPrompt(dbcon)
elseif iButton == 4 then
-- Show Help Window
GUI_HelpDialogue()
elseif iButton == 5 or iButton == 0 then
bclose = true
end
end
closedb(dbenv,dbcon)
end
function settingsPrompt(dbcon)
local ret
local settings = {}
for a,b in pairs (setting) do settings[a] = b end -- V1.3 preserve settings in case Cancel selected
local defdir = setting.directory or ""
if lfs.attributes(defdir,"mode") ~= "directory" then -- V1.3 restore default if setting.directory missing
defdir = default
end
ret,setting.directory,setting.host,setting.folder,setting.userid,setting.password,setting.trace =
iup.GetParam("FTP Website Manager - Settings", iup.NULL,
"Source Folder: %f[DIR||"..defdir.."]\n".. -- V1.3 correct iupFileDlg params
"FTP Host URL: %s\n"..
"FTP Folder: %s\n"..
"FTP Username: %s\n"..
"FTP Password: %s\n"..
"FTP Trace: %b[ No thanks , Yes please ]\n",
setting.directory,setting.host,setting.folder,setting.userid,setting.password,setting.trace)
if (ret == true) then
setting.directory = setting.directory:gsub('/','\\'):gsub('\\+$','') -- V1.3 change all / to \ and erase all trailing \
setting.folder = setting.folder:gsub('\\','/'):gsub('/+$','') -- V1.3 change all \ to / and erase all trailing /
dump(setting.directory)
saveSettings(dbcon,setting)
setting.new = nil
else
for a,b in pairs (settings) do setting[a] = b end -- V1.3 restore original settings after Cancel
end
end
function uploadFiles(dbcon,list)
-- Connect to FTP server
ProgressDisplay.Start('Updating Files on FTP Server',100)
local ipos = 0
local step = 1/#list * 100
local bQuit = false
for i,filedata in pairs(list) do
local tofile = filedata.filename:gsub(setting.directory..'\\','') -- V1.3 remove '\' between folder and filename
ipos = ipos + 1
ProgressDisplay.SetMessage(filedata.action..': '..tofile..' ('..ipos..'/'..#list..')')
ProgressDisplay.Step(step)
if ProgressDisplay.Cancel() then
break
end
local status = ftpPutFile(filedata.filename,filedata.action) -- V1.3 do not need tofile param
if status == 'OK' then
updRecord(dbcon,filedata.filename,filedata.md5hash)
else
local strMsg = 'An error occured updating: '..tofile..'\n'..status..'\nDo you want to continue with other files?'
local a = fhMessageBox(strMsg,'MB_YESNO','MB_ICONQUESTION')
if a == 'No' then
bQuit = true
end
end
if bQuit then
break
end
end
ProgressDisplay.Reset()
ProgressDisplay.Close()
end
function ftpPutFile(fromfile,action) -- V1.3 do not need tofile param, use file instead
local actions = {update='STOR',delete='DELE'}
local path, file, ext = SplitFilename(fromfile)
local stype = 'i'
if ext == 'html' or ext == 'css' or ext == 'js' then
-- Use Binary for everything other than the html and css files.
stype = 'a'
end
if action == 'delete' then fromfile = 'nul' end -- V1.3 avoid 'no such file error' for source
local p = {
host = setting.host,
user = setting.userid,
password = setting.password,
command = actions[action],
argument = setting.folder..'/'..file, -- V1.3 use file instead of tofile
type = stype,
source = ltn12.source.file(io.open(fromfile, "rb"))
}
local f, e = ftp.put(p)
showtrace('ftp.put',p,setting.trace)
return e or 'OK'
end
-- Update record for new files
function updRecord(dbcon,filename,md5hash) -- V1.3 use "%s" to allow prime ' in filename
local sql = string.format('UPDATE pagelist set md5hash = "%s" where filename="%s"',md5hash,filename)
local res = assert(dbcon:execute(sql))
end
function getFTPList(setting)
local t = {}
local g = {
host = setting.host,
sink = ltn12.sink.table(t),
user = setting.userid,
password = setting.password,
argument = setting.folder, -- V1.3 use argument instead of appending to command
command = 'NLST',
type = 'a'
}
local _, e = ftp.get(g)
showtrace('ftp.get',g,setting.trace)
if e then
return nil,e
else
local t2 = table.concat(t)
local t3 = split(t2,'\r\n')
return t3
end
end
function showtrace(func,ftp,trace) -- V1.3 tp trace debug listing
local strTrace = '\nOperation:\t'..func..' '..ftp.command..' '..ftp.argument..'\n'
strTrace = strTrace..table.concat(tp.GetTrace(),'\n')
strTrace = strTrace:gsub('Response:','Response: ')
if trace == 1 then
if fhMessageBox('FTP Website Manager - Trace Log\n'..strTrace,'MB_OKCANCEL') == 'Cancel' then setting.trace = 0 end
end
print(strTrace)
end
function buildpagelist(dbcon,setting)
local function fliptable(table)
local newtable = {}
for i,v in pairs(table) do
v = v:gsub(setting.folder.."/","") -- V1.3 remove folder from filename
newtable[v] = i
end
return newtable
end
local function chkrecord(filename)
-- Add records for new files -- V1.3 use "%s" to allow prime ' in filename
local sql = string.format('INSERT OR IGNORE INTO pagelist (filename,md5hash) VALUES ("%s"," ")',filename)
local res = assert(dbcon:execute(sql))
end
local function getHash(filename)
-- Add records for new files -- V1.3 use "%s" to allow prime ' in filename
local sql = string.format('select md5hash from pagelist where filename = "%s"',filename)
local cur = assert(dbcon:execute(sql))
local row = cur:fetch()
cur:close()
return row
end
local basedir = setting.directory
local tblList = {}
local tblAll = {}
ProgressDisplay.Start('Building Transfer List',100)
ProgressDisplay.SetMessage('Retrieving List From FTP Server')
local ftplist, e = getFTPList(setting)
if e then
fhMessageBox('Error Occured Connecting to FTP site. Please check your settings\n'..e)
ProgressDisplay.Reset()
ProgressDisplay.Close()
return
end
ftplist = fliptable(ftplist)
-- Quick Count Files
local icount = 0
for filename, attr in dirtree(basedir) do
local path, file, ext = SplitFilename(filename) -- V1.3 only count contents of basedir and not sub-folders
if path == basedir..'\\'
and attr.mode == 'file' then icount = icount + 1 end -- V1.3 correct istep to icount, and only count files not folders
end
local istep = 1 / icount * 100
ProgressDisplay.SetMessage(#tblList..' files found to update... ')
for filename, attr in dirtree(basedir) do
local path, file, ext = SplitFilename(filename) -- V1.3 only check contents of basedir and not sub-folders
if path == basedir..'\\' then
if attr.mode == 'file' then -- V1.3 handle files here
ProgressDisplay.Step(istep)
if ProgressDisplay.Cancel() then
break
end
ProgressDisplay.SetMessage(#tblList..' files found to update... Checking: '..file)
chkrecord(filename) -- Add if needed
local oldhash = getHash(filename)
-- Remove file from FTP List as it does not need deleting
ftplist[file] = nil
-- compute MD5 routine
local md5hash = md5.sumhexa(LoadFromFile(filename))
if oldhash ~= md5hash then
table.insert(tblList,{filename=filename, md5hash=md5hash, action='update'})
ProgressDisplay.SetMessage(#tblList..' files found to update... ')
end
else
ftplist[file] = nil -- V1.3 remove matching folders from FTP List to prevent deletion
end
end
end
for i,v in pairs(ftplist) do
-- In the Delete List remove any files starting with . and any folders
if i:sub(1,1) ~= '.' then
local path, file, ext = SplitFilename(i)
if file ~= ext then -- V1.3 exclude folders to prevent deletion
chkrecord(file) -- Add if needed
table.insert(tblList,{filename=file, md5hash='0', action='delete'}) -- V1.3 only need filename for Delete
end
end
end
ProgressDisplay.Reset()
ProgressDisplay.Close()
return tblList
end
function resetDatabase(dbcon)
local sql = 'delete from pagelist'
local res = assert(dbcon:execute(sql))
fhMessageBox('Page Database cleared')
end
function opendb(dbname)
-- Check for Settings Database and create if needed
local db = fhGetPluginDataFileName()
local dbenv = assert (luasql.sqlite3())
-- connect to data source, if the file does not exist it will be created
local dbcon = assert (dbenv:connect(db))
-- check table for page list
checkTable(dbcon,'pagelist',
[[CREATE TABLE pagelist(filename varchar(500), md5hash varchar(32), UNIQUE (filename))
]])
-- create table for settings
checkTable(dbcon,'settings',
[[CREATE TABLE settings(key varchar(20), directory varchar(500),
host varchar(500), folder varchar(50), userid varchar(50), password varchar(50), UNIQUE (key))
]])
return dbenv,dbcon
end
function checkTable(dbcon,table,createString)
local sql = string.format([[SELECT count(name) as count FROM sqlite_master WHERE type='table' AND name='%s']],table)
local cur = assert(dbcon:execute(sql))
local rowcount = cur:fetch (row, "a")
cur:close()
if tonumber(rowcount) == 0 then
-- Table not found create it
local res,err = assert(dbcon:execute(createString))
end
end
function closedb(dbenv,dbcon)
dbcon:close()
dbenv:close()
end
function loadSettings(dbcon)
local sql = [[SELECT * FROM settings]]
local cur,err = assert(dbcon:execute(sql))
local row = cur:fetch({},'a')
cur:close()
if row then
for item, text in pairs (row) do
if type(text) == "string" then
row[item] = text:gsub("\127","'") -- V1.3 convert delete char back to prime ' char
end
end
row.trace = 0
return row
else
-- return default values
return {
directory = default, -- V1.3 default to Project...\Public\FH Website
host = 'websitehost',
folder = '/',
userid = 'user',
password = 'password',
trace = 0,
new = 'yes'
}
end
end
function saveSettings(dbcon,settings)
local row = {}
for item, text in pairs (settings) do
if type(text) == "string" then
row[item] = text:gsub("'","\127") -- V1.3 prime ' not allowed in sql values so convert to delete char
end
end
-- Check for Settings
if row.new == 'yes' then
-- Create
sql = string.format([[insert into settings (directory, host, folder, userid, password, trace) Values('%s','%s','%s','%s','%s')]],row.directory,row.host,row.folder,row.userid,row.password)
else
-- Update
sql = string.format([[update settings set directory = '%s', host = '%s', folder = '%s', userid = '%s', password = '%s']],row.directory,row.host,row.folder,row.userid,row.password)
end
local res = assert(dbcon:execute(sql))
end
function split(str, pat)
local t = {} -- NOTE: use {n = 0} in Lua-5.0
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(t,cap)
end
last_end = e+1
s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
cap = str:sub(last_end)
table.insert(t, cap)
end
return t
end
function dirtree(dir)
assert(dir and dir ~= "", "directory parameter is missing or empty")
if string.sub(dir, -1) == "/" then
dir=string.sub(dir, 1, -2)
end
local function yieldtree(dir)
for entry in lfs.dir(dir) do
if entry ~= "." and entry ~= ".." then
entry=dir.."\\"..entry
local attr=lfs.attributes(entry)
coroutine.yield(entry,attr)
if attr.mode == "directory" then
yieldtree(entry)
end
end
end
end
return coroutine.wrap(function() yieldtree(dir) end)
end
--[[
@Title: Progress Display (drop in)
@Author: Jane Taubman / Mike Tate
@LastUpdated: May 2012
@Description: Allows easy adding of a Progress Bar to any long running Plugin
]]
ProgressDisplay = {
Start = function(strTitle,intMax) -- Create and start the Progress Display window controls
local StrWhite = "255 255 255"
if not dlgProgress then
cancelflag = false
local cancelbutton = iup.button { title="Cancel", rastersize="200x30",
action = function()
cancelflag = true -- Signal that Cancel button was pressed
return iup.CLOSE
end
}
gaugeProgress = iup.progressbar { rastersize="400x30", max=intMax } -- Set progress bar maximum range
messageline = iup.label { title=" ", expand="YES", alignment="ACENTER" }
dlgProgress = iup.dialog { title=strTitle, dialogframe="YES", background=strWhite, -- Remove Windows minimize/maximize menu
iup.vbox { alignment="ACENTER", gap="10", margin="10x10",
messageline,
gaugeProgress,
cancelbutton
}
}
dlgProgress.close_cb = cancelbutton.action -- Windows Close button acts as Cancel button
dlgProgress:showxy(iup.CENTER, iup.CENTER) -- Show the Progress Display dialogue window
end
end,
SetMessage = function(strMessage) -- Set the progress message
if dlgProgress then messageline.title = strMessage end
end,
Step = function(iStep) -- Step the Progress Bar forward
if dlgProgress then
gaugeProgress.value = gaugeProgress.value + iStep
local val = tonumber(gaugeProgress.value)
local max = tonumber(gaugeProgress.max)
if val > max then
gaugeProgress.value = 0
end
iup.LoopStep()
end
end,
Reset = function() -- Reset progress bar
if dlgProgress then gaugeProgress.value = 0 end
end,
Cancel = function() -- Check if Cancel button pressed
return cancelflag
end,
Close = function() -- Close the dialogue window
cancelflag = false
if dlgProgress then dlgProgress:destroy() dlgProgress = nil end
end,
}
-------------------------------------------------------------------------------------------- End Progress Bar
-- Open File and return Handle --
function OpenFile(strFileName,strMode)
local fileHandle, strError = io.open(strFileName,strMode)
if not fileHandle then
error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..tostring(strError).." \n")
end
return fileHandle
end -- function OpenFile
-- Load string from file --
function LoadFromFile(strFileName)
local fileHandle = OpenFile(strFileName,"rb")
local strString = fileHandle:read("*all")
assert(fileHandle:close())
return strString
end -- function LoadFromFile
-- Save string to file --
function SaveStringToFile(strString,strFileName)
local fileHandle = OpenFile(strFileName,"w")
fileHandle:write(strString)
assert(fileHandle:close())
end -- function SaveStringToFile
-- Return the Path, Filename, and extension as 3 values
function SplitFilename(strFilename)
return strFilename:match("(.-)([^\\]-([^\\%.]+))$")
end -- function SplitFilename
-- Load Module
function loadrequire(module,extended)
if not(extended) then extended = module end
local function installmodule(module,filename)
local bmodule = false
if not(filename) then
filename = module..'.mod'
bmodule = true
end
local storein = fhGetContextInfo('CI_APP_DATA_FOLDER')..'\\Plugins\\'
-- Check if subdirectory needed
local path = string.match(filename, "(.-)[^/]-[^%.]+$")
if path ~= "" then
path = path:gsub('/','\\')
-- Create sub-directory
lfs.mkdir(storein..path)
end
-- Get file down and install it
local http = luacom.CreateObject("winhttp.winhttprequest.5.1")
local url = "http://www.family-historian.co.uk/lnk/getpluginmodule.php?file="..filename
http:Open("GET",url,false)
http:Send()
http:WaitForResponse(30)
local status = http.StatusText
if status == 'OK' then
length = http:GetResponseHeader('Content-Length')
data = http.ResponseBody
if bmodule then
local modlist = loadstring(http.ResponseBody)
for _,f in pairs(modlist()) do
if not(installmodule(module,f)) then
break
end
end
else
local function OpenFile(strFileName,strMode)
local fileHandle, strError = io.open(strFileName,strMode)
if not fileHandle then
error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..tostring(strError).." \n")
end
return fileHandle
end -- OpenFile
local function SaveStringToFile(strString,strFileName)
local fileHandle = OpenFile(strFileName,"wb")
fileHandle:write(strString)
assert(fileHandle:close())
end -- SaveStringToFile
SaveStringToFile(data,storein..filename)
end
return true
else
fhMessageBox('An error occurred in Download please try later')
return false
end
end
local function requiref(module)
require(module)
end
local res = pcall(requiref,extended)
if not(res) then
local ans = fhMessageBox(
'This plugin requires '..module..' support, please click OK to download and install the module',
'MB_OKCANCEL','MB_ICONEXCLAMATION')
if ans ~= 'OK' then
return false
end
if installmodule(module) then
package.loaded[extended] = nil -- Reset Failed Load
require(extended)
else
return false
end
end
return true
end
--[[
@Title: User Interface Buttons Snippet
@Author: Mike Tate / Jane Taubman
@LastUpdated: May 2012
@Version: 1.4
@Description: GUI dialogue for multiple buttons
@params
strTitle: Title of Message Box
strMessage: Message to show above buttons
strBoxType: Either "H" for Horizontal buttons or "V" for Vertical ones.
... : All other parameters will be treated as button titles.
]]
function iupButtons(strTitle,strMessage,strBoxType,...)
local intButton = 0 -- Returned value if X Close button is used
-- Create the GUI labels and buttons
local lblMessage = iup.label{title=strMessage,expand="YES"}
local lblLineSep = iup.label{separator="HORIZONTAL"}
local iupBox = iup.hbox{homogeneous="YES"}
if strBoxType == "V" then
iupBox = iup.vbox{homogeneous="YES"}
end
for intArgNum, strButton in ipairs(arg) do
local btnName = iup.button{title=strButton,expand="YES",padding="4",action=function() intButton=intArgNum return iup.CLOSE end}
iup.Append(iupBox,btnName)
end
-- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
local dialogue = iup.dialog{title=strTitle,iup.vbox{lblMessage,lblLineSep,iupBox},dialogframe="YES",background="250 250 250",gap="8",margin="8x8"}
dialogue:show()
if (iup.MainLoopLevel()==0) then iup.MainLoop() end
dialogue:destroy()
return intButton
end -- function iupButtons
--[[
@Title: Display FHUG Wiki Help Snippet
@Author: Mike Tate
@LastUpdated: May 2012
@Version: 1.1
@Description: Displays FHUG Wiki help pages in a popup GUI.
]]
-- GUI Help & Advice Dialogue --
function GUI_HelpDialogue()
local StrPlugin = "Display FHUG Wiki" -- Plugin title & version
local StrIssue = " V1.0"
local StrRed = "255 000 000" -- Color attributes
local StrGreen = "000 120 000"
local StrBlue = "000 000 255"
local StrBlack = "000 000 000"
local StrWhite = "255 255 255"
local StrBigMargin = "10x10" -- Layout attributes
local StrMinMargin = "1x1"
local StrGap = "10"
local StrFontFace = string.gsub(iup.GetGlobal("DEFAULTFONT"),",.*","")
local StrFontHead = StrFontFace..", Bold -16"
local StrFontBody = StrFontFace..", -16"
local StrFHUG = "http://www.fhug.org.uk/wiki/wiki/doku.php?id="
local function doActivateMainHelpButton()
if BtnMainHelp then BtnMainHelp.active = "YES" end
end
-- Create the WebBrowser based on its ProgID and connect it to LuaCOM
local oleControl = iup.olecontrol{ "Shell.Explorer.1", designmode="NO", }
oleControl:CreateLuaCOM()
-- Create each GUI button with title and tooltip
local btnClose = iup.button { title="Close this Window" , tip="Close this Help and Advice window" }
-- The following control is global to allow Main GUI to alter font
HboxHelp = iup.hbox { font=StrFontBody, margin=StrMinMargin, homogeneous="YES", btnMain, btnPath, btnClose, }
local strExpChild = "NO"
local iupVersion = iup.GetGlobal("VERSION")
if iupVersion == "3.5" then strExpChild = "YES" end -- for IUP 3.11.2
local dialogHelp = iup.dialog { title=StrPlugin.." Help & Advice", background=StrWhite, startfocus=btnClose, rastersize="1000x700",
iup.vbox { alignment="ACENTER", gap=StrGap, margin=StrBigMargin, expandchildren=strExpChild,
oleControl,
HboxHelp,
},
close_cb=function() doActivateMainHelpButton() end,
}
local strFHUG = StrFHUG.."plugins:help:"
-- Set other GUI control attributes
for iupName, tblAttr in pairs( {
-- Control= 1~fgcolor , 2~Navigate URL , 3~action function()
[btnClose]= { StrRed , false , function() dialogHelp:destroy() doActivateMainHelpButton() return iup.CLOSE end },
} ) do
iupName.expand = "HORIZONTAL"
iupName.size = "x10"
iupName.fgcolor = tblAttr[1]
if tblAttr[2] then iupName.action = function() oleControl.com:Navigate(tblAttr[2]) end end
if tblAttr[3] then iupName.action = tblAttr[3] end
end
dialogHelp:show()
dialogHelp.rastersize=iup.NULL -- Allow window to be resized
oleControl.com:Navigate(strFHUG.."ftp_website_manager")
if (iup.MainLoopLevel()==0) then iup.MainLoop() end
end -- function GUI_HelpDialogue
function dump (tt, indent, done, label)
if label == nil then
label = 'Dump'
end
if debug == true then
done = done or {}
indent = indent or 0
if type(tt) == "table" then
if indent == 0 then
io.write(string.rep (" ", indent))
io.write(label..'\n')
end
for key, value in pairs (tt) do
io.write(string.rep (" ", indent)) -- indent it
if type (value) == "table" and not done [value] then
done [value] = true
io.write(string.format("[%s] => table\n", tostring (key)));
io.write(string.rep (" ", indent+4)) -- indent it
io.write("(\n");
dump (tostring(key),value, indent + 7, done)
io.write(string.rep (" ", indent+4)) -- indent it
io.write(")\n");
else
io.write(string.format("[%s] => %s\n",
tostring (key), tostring(value)))
end
end
else
io.write(label..':'..tostring(tt))
end
else
return
end
end
------------------------------------------------------ End of Functions
require 'lfs'
require 'luacom'
require 'iupluaole'
if not(loadrequire('luasql','luasql.sqlite3')) then return end
if not(loadrequire('md5')) then return end
if not(loadrequire('socket')) then return end
local socket = fhGetContextInfo('CI_APP_DATA_FOLDER')..'\\Plugins\\socket'
local module = socket..'\\tp.lua'
local data = LoadFromFile(module)
if not data:match('Trace') then -- V1.3 add tp trace debug feature
data = data:gsub('(TIMEOUT = 60)', '%1\n Trace = {}\n function GetTrace() local t={} for i,v in base.ipairs(Trace) do t[i]=v end Trace={} return t end' )
data = data:gsub('(local reply = line)', '%1\n if Trace then Trace[#Trace+1] = "Response:\t"..(err or reply) end' )
data = data:gsub('(function metat%.__index:command%(cmd, arg%))', '%1\n if Trace then Trace[#Trace+1] = "Command:\t"..cmd.." "..(arg or "") end' )
data = data:gsub('(function connect%(host, port, timeout, create%))', '%1\n if Trace then Trace[#Trace+1] = "Connecting:\t"..host.."\tPort: "..port end' )
SaveStringToFile(data,module)
module = socket..'\\ftp.lua'
data = LoadFromFile(module)
data = data:gsub('"pasv"','"PASV"') -- V1.3 fix ftp module PASV for rootsweb
SaveStringToFile(data,module)
elseif data:match('return Trace') then
data = data:gsub('return Trace','local t={} for i,v in base.ipairs(Trace) do t[i]=v end Trace={} return t')
SaveStringToFile(data,module)
end
ftp = require("socket.ftp")
tp = require("socket.tp")
default = fhGetContextInfo('CI_PROJECT_PUBLIC_FOLDER') -- V1.3 default Source Folder
if lfs.attributes(default..'\\FH Website',"mode") == "directory" then
default = default..'\\FH Website'
end
main()
Source:FTP-Website-Manager2.fh_lua