-- -- *** IMPORTANT *** -- *** Set this to your World of Warcraft retail installation directory *** -- *** Use forward slashes ONLY and ensure a forward slash is at the end *** -- *** IMPORTANT *** -- -- example: -- wowdir = 'C:/Program Files (x86)/World of Warcraft/_retail_/' wowdir = 'C:/Program Files (x86)/World of Warcraft/_retail_/' masterdb = wowdir..'altoholic_merge/' -- Lua implementation of PHP scandir function function listdir(directory) local i, t, popen = 0, {}, io.popen for filename in popen('dir "'..directory..'" /b'):lines() do --msg(filename) i = i + 1 t[i] = filename end return t end --- Check if a file or directory exists in this path function exists(file) local ok, err, code = os.rename(file, file) if not ok then if code == 13 then -- Permission denied, but it exists return true end end return ok, err end --- Check if a directory exists in this path function isdir(path) -- "/" works on both Unix and Windows return exists(path.."/") end -- string splitter function split(source, delimiters) local elements = {} local pattern = '([^'..delimiters..']+)' string.gsub(source, pattern, function(value) elements[#elements + 1] = value; end); return elements end -- lua keywords local keywords = {["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true, ["elseif"]=true, ["end"]=true, ["false"]=true, ["for"]=true, ["function"]=true, ["if"]=true, ["in"]=true, ["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true, ["repeat"]=true, ["return"]=true, ["then"]=true, ["true"]=true, ["until"]=true, ["while"]=true} local function isLuaIdentifier(str) if type(str) ~= "string" then return false end -- must be nonempty if str:len() == 0 then return false end -- can only contain a-z, A-Z, 0-9 and underscore if str:find("[^%d%a_]") then return false end -- cannot begin with digit if tonumber(str:sub(1, 1)) then return false end -- cannot be keyword if keywords[str] then return false end return true end -- wrapper for r_repr that initializes depth at 0 depth = 0 function repr(value) depth = 0 -- begin recursive repr return r_repr(value) end -- lobotomized repr function from repr.lua$ function r_repr(v) INDENT = "\t" local tabs = INDENT:rep(depth) if type(v) == "string" then return ("%q"):format(v) elseif type(v) == "number" then if v == math.huge then return "math.huge" end if v == -math.huge then return "-math.huge" end return tonumber(v) elseif type(v) == "boolean" then return tostring(v) elseif type(v) == "nil" then return "nil" elseif type(v) == "table" and type(v.__tostring) == "function" then return tostring(v.__tostring(v)) elseif type(v) == "table" and getmetatable(v) and type(getmetatable(v).__tostring) == "function" then return tostring(getmetatable(v).__tostring(v)) elseif type(v) == "table" then local str = "{\n" .. INDENT .. tabs local isArray = true local arrayMax = 0 local arrayCount = 0 for k, v in pairs(v) do if type(k) ~= "number" then isArray = false break elseif k <= 0 then isArray = false break elseif k > arrayMax then arrayMax = k end arrayCount = arrayCount + 1 end if isArray and arrayMax == arrayCount then for i = 1, #v do if i ~= 1 then str = str .. ",\n" .. INDENT .. tabs end depth = depth + 1 str = str .. r_repr(v[i]) depth = depth - 1 end else local keyOrder = {} local keyValueStrings = {} for k, v in pairs(v) do depth = depth + 1 local kStr = isLuaIdentifier(k) and k or ("[" .. r_repr(k) .. "]") local vStr = r_repr(v) table.insert(keyOrder, kStr) keyValueStrings[kStr] = vStr depth = depth - 1 end local first = true for _, kStr in pairs(keyOrder) do if not first then str = str .. ",\n" .. INDENT .. tabs end str = str .. ("%s = %s"):format(kStr, keyValueStrings[kStr]) first = false end end str = str .. "\n" .. tabs str = str .. "}" return str end end function load_file_db(scriptfile) -- loads Datastore DB without polluting or compromising globals local env = setmetatable({}, {__index=_G}) assert(pcall(setfenv(assert(loadfile(scriptfile)), env))) setmetatable(env, nil) return env end function pull_acct_datastore(acct, acctdir, ds) local dsfile, ds = ds, string.sub(ds,1,-5) local dsdbname = ds..'DB' local masterds = masterdb..dsfile print("Pulling datastore: "..dsdbname .. " with " .. masterds) --dofile(acctdir..dsfile) local dsdb = load_file_db(acctdir..dsfile) dsdb = dsdb[dsdbname] if dsdb ~= nil then local oldmasterdata, oldgdata = {}, {} if exists(masterds) then oldmasterdata = load_file_db(masterds) oldgdata = oldmasterdata['GuildsDB'] oldmasterdata = oldmasterdata['CharactersDB'] end if dsdb['global'] ~= nil then local masterdata, mastergdata, masterident = {}, {}, nil if dsdb['global']['Characters'] ~= nil then local dsc = dsdb['global']['Characters'] local charnames = {} if oldmasterdata ~= nil then for k, v in pairs(oldmasterdata) do masterdata[k] = v end end local i = 0 for cident, cdata in pairs(dsc) do local lu = cdata.lastUpdate local cis = split(cident, ".") local cacct, cserv, cname = cis[1], cis[2], cis[3] --print(cacct.. " -- "..cserv.." -- "..cname.. " = "..lu) if cacct == "Default" then masterident = acct.."."..cserv.."."..cname if oldmasterdata == nil or oldmasterdata[masterident] == nil or oldmasterdata[masterident].lastUpdate == nil then masterdata[masterident] = cdata elseif lu == nil then masterdata[masterident] = cdata elseif oldmasterdata[masterident].lastUpdate <= lu then masterdata[masterident] = cdata end if servers[cserv] == nil then print("Identified server "..cserv.." for "..acct) servers[cserv] = lu elseif servers[cserv] < lu then servers[cserv] = lu end end end end if dsdb['global']['Guilds'] ~= nil then local dsg = dsdb['global']['Guilds'] local gnames = {} if oldgdata ~= nil then for k, v in pairs(oldgdata) do mastergdata[k] = v end end local i = 0 for gident, gdata in pairs(dsg) do local lu = gdata.lastUpdate local gis = split(gident, ".") local gacct, gserv, gname = gis[1], gis[2], gis[3] --print(gacct.. " -- "..gserv.." -- "..gname.. " = "..lu) gacct = "Default" masterident = gacct.."."..gserv.."."..gname if oldgdata == nil or oldgdata[masterident] == nil or oldgdata[masterident].lastUpdate == nil then mastergdata[masterident] = gdata elseif lu == nil then mastergdata[masterident] = gdata elseif oldgdata[masterident].lastUpdate <= lu then mastergdata[masterident] = gdata end if servers[gserv] == nil then if lu == nil then servers[gserv] = 0 else servers[gserv] = lu end elseif lu == nil then servers[gserv] = 0 elseif servers[gserv] < lu then servers[gserv] = lu end end end masterfile = io.open(masterds, "w+") masterfile:write("\nCharactersDB = "..repr(masterdata).."\n") masterfile:write("\nGuildsDB = "..repr(mastergdata).."\n") masterfile:close() --print(repr(charnames)) end end end function push_acct_datastore(acct, acctdir, ds) local dsfile, ds = ds, string.sub(ds,1,-5) local dsdbname = ds..'DB' local masterds = masterdb..dsfile print("Pushing datastore: "..dsdbname .. " with " .. masterds) --dofile(acctdir..dsfile) local dsdb = load_file_db(acctdir..dsfile) local masterdb, mastergdb = {}, {} if exists(masterds) then masterdb = load_file_db(masterds) mastergdb = masterdb["GuildsDB"] masterdb = masterdb["CharactersDB"] end if dsdb ~= nil then if dsdb[dsdbname] ~= nil then if dsdb[dsdbname]['global'] ~= nil then if dsdb[dsdbname]['global']['Characters'] ~= nil then local dsc = dsdb[dsdbname]['global']['Characters'] for cident, cdata in pairs(masterdb) do local cis = split(cident, ".") local cacct, cserv, cname = cis[1], cis[2], cis[3] local dident = cident if cacct == acct then dident = "Default."..cserv.."."..cname end local ddata = dsc[dident] if ddata ~= nil then if cdata.lastUpdate == nil or ddata.lastUpdate == nil or cdata.lastUpdate >= ddata.lastUpdate then dsc[dident] = cdata end else dsc[dident] = cdata end end end if dsdb[dsdbname]['global']['Guilds'] ~= nil then local dsg = dsdb[dsdbname]['global']['Guilds'] for gident, gdata in pairs(mastergdb) do local gis = split(gident, ".") local gacct, gserv, gname = gis[1], gis[2], gis[3] local dident = "Default."..gserv.."."..gname local ddata = dsg[dident] if ddata ~= nil then if gdata.lastUpdate == nil or ddata.lastUpdate == nil or gdata.lastUpdate >= ddata.lastUpdate then dsg[dident] = gdata end else dsg[dident] = gdata end end end end end outfile = io.open(acctdir..dsfile..".syncnew", "w+") for k, v in pairs(dsdb) do outfile:write("\n"..k.." = "..repr(v).."\n") end outfile:close() os.rename(acctdir..dsfile, acctdir..dsfile..".syncbak") os.remove(acctdir..dsfile) os.rename(acctdir..dsfile..".syncnew", acctdir..dsfile) end end domains = {} servers = {} function pull_acct(acct, acctdir) servers = {} if exists(acctdir) then print("Pulling account "..acct) for i, dirname in ipairs(listdir(acctdir)) do if string.match(dirname, "^DataStore_") and string.match(dirname, "[.]lua$") then pull_acct_datastore(acct, acctdir, dirname) elseif dirname == "DataStore.lua" then pull_acct_datastore(acct, acctdir, dirname) end end end if domains[acct] == nil then domains[acct] = {} end for k, v in pairs(servers) do print("Adding server domain "..k) domains[acct][k] = { ["lastSharingTimestamp"] = v, ["lastUpdatedWith"] = " " } end end function push_acct(acct, acctdir) local altfile = "Altoholic.lua" if exists(acctdir) and exists(acctdir..altfile) then print("Pushing account "..acct) for i, dirname in ipairs(listdir(acctdir)) do if string.match(dirname, "^DataStore_") and string.match(dirname, "[.]lua$") then push_acct_datastore(acct, acctdir, dirname) elseif dirname == "DataStore.lua" then push_acct_datastore(acct, acctdir, dirname) end end -- data stores updated, now update altoholic config to register the new accounts if needed local acctdomains = {} for dsacct, servlist in pairs(domains) do for dsserv, v in pairs(servlist) do if dsacct == acct then dsacct = 'Default' end local dskey = dsacct..'.'..dsserv acctdomains[dskey] = v end end local altdb = load_file_db(acctdir..altfile) if altdb ~= nil then print("Found altoholicdb for "..acct) altdb = altdb['AltoholicDB'] end if altdb ~= nil then if altdb['global'] == nil then altdb['global'] = {} end if altdb['global']['Sharing'] == nil then altdb['global']['Sharing'] = {} end print("Found global domains for "..acct) altdb['global']['Sharing']['Domains'] = acctdomains end if altdb ~= nil then --print("\nAltoholicDB = "..repr(altdb).."\n") outfile = io.open(acctdir..altfile..".syncnew", "w+") outfile:write("\nAltoholicDB = "..repr(altdb).."\n") outfile:close() os.rename(acctdir..altfile, acctdir..altfile..".syncbak") os.remove(acctdir..altfile) os.rename(acctdir..altfile..".syncnew", acctdir..altfile) end end end function run() local accounts = {} if not exists(masterdb) then os.execute("mkdir \""..masterdb.."\"") end if exists(wowdir..'WTF/Account/') then for i, dirname in ipairs(listdir(wowdir..'WTF/Account/')) do if dirname ~= "SavedVariables" then accounts[i] = dirname end end end for accidx, acc in pairs(accounts) do --io.write(acc.." ") local acctdir = wowdir..'WTF/Account/'..acc..'/SavedVariables/' pull_acct(acc,acctdir) end for accidx, acc in pairs(accounts) do --io.write(acc.." ") local acctdir = wowdir..'WTF/Account/'..acc..'/SavedVariables/' push_acct(acc,acctdir) end --print() end run()