3 -- Copyright (C) 2008-2010 Matthew Wild
4 -- Copyright (C) 2008-2010 Waqas Hussain
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
12 package
.path
= package
.path
..";../?.lua";
14 local my_name
= arg
[0];
15 if my_name
:match("[/\\]") then
16 package
.path
= package
.path
..";"..my_name
:gsub("[^/\\]+$", "../?.lua");
17 package
.cpath
= package
.cpath
..";"..my_name
:gsub("[^/\\]+$", "../?.so");
21 local serialize
= require
"util.serialization".serialize
;
22 local st
= require
"util.stanza";
23 local parse_xml
= require
"util.xml".parse
;
24 package
.loaded
["util.logger"] = {init
= function() return function() end; end}
25 local dm
= require
"util.datamanager"
26 dm
.set_data_path("data");
28 function parseFile(filename
)
34 local function read(expected
)
37 ch
= last
; last
= nil;
40 if ch
== "\n" then line
= line
+ 1; end
42 if expected
and ch
~= expected
then error("expected: "..expected
.."; got: "..(ch
or "nil").." on line "..line
); end
46 if not last
then last
= read(); end
63 local function unescape(s
)
64 return escapes
[s
] or error("Unknown escape sequence: "..s
);
66 local function readString()
72 s
= s
..unescape(read()..read());
82 local function readNonString()
85 if peek() == "," or peek() == ")" then
93 local function readItem()
97 return readNonString();
100 local function readTuple()
103 while peek() ~= ")" do
104 table.insert(items
, readItem());
105 if peek() == ")" then break; end
111 local function readTuples()
112 if peek() ~= "(" then read("("); end
115 table.insert(tuples
, readTuple());
116 if peek() == "," then read() end
117 if peek() == ";" then break; end
121 local function readTableName()
123 while peek() ~= "`" do tname
= tname
..read(); end
126 local function readInsert()
127 if peek() == nil then return nil; end
128 for ch
in ("INSERT INTO `"):gmatch(".") do -- find line starting with this
131 else -- match failed, skip line
132 while peek() and read() ~= "\n" do end
136 local tname
= readTableName();
137 read("`"); read(" ") -- expect this
138 if peek() == "(" then -- skip column list
139 repeat until read() == ")";
142 for ch
in ("VALUES "):gmatch(".") do read(ch
); end -- expect this
143 local tuples
= readTuples();
144 read(";"); read("\n");
145 return tname
, tuples
;
148 local function readFile(filename
)
149 file
= io
.open(filename
);
150 if not file
then error("File not found: "..filename
); os
.exit(0); end
153 local tname
, tuples
= readInsert();
156 local t_name
= t
[tname
];
158 table.insert(t_name
, tuples
[i
]);
163 elseif peek() == nil then
170 return readFile(filename
);
175 local arg
, hostname
= ...;
176 local help
= "/? -? ? /h -h /help -help --help";
177 if not(arg
and hostname
) or help
:find(arg
, 1, true) then
178 print([[ejabberd SQL DB dump importer for Prosody
180 Usage: ejabberdsql2prosody.lua filename.txt hostname
182 The file can be generated using mysqldump:
183 mysqldump db_name > filename.txt]]);
187 ["last"] = {"username", "seconds", "state"};
188 ["privacy_default_list"] = {"username", "name"};
189 ["privacy_list"] = {"username", "name", "id"};
190 ["privacy_list_data"] = {"id", "t", "value", "action", "ord", "match_all", "match_iq", "match_message", "match_presence_in", "match_presence_out"};
191 ["private_storage"] = {"username", "namespace", "data"};
192 ["rostergroups"] = {"username", "jid", "grp"};
193 ["rosterusers"] = {"username", "jid", "nick", "subscription", "ask", "askmessage", "server", "subscribe", "type"};
194 ["spool"] = {"username", "xml", "seq"};
195 ["users"] = {"username", "password"};
196 ["vcard"] = {"username", "vcard"};
197 --["vcard_search"] = {};
200 local parsed
= parseFile(arg
);
201 for name
, data
in pairs(parsed
) do
204 if #data
> 0 and #data
[1] ~= #m
then
205 print("[warning] expected "..#m
.." columns for table `"..name
.."`, found "..#data
[1]);
216 --print(serialize(t));
218 for _
, row
in ipairs(parsed
["users"] or NULL
) do
219 local node
, password
= row
.username
, row
.password
;
220 local ret
, err
= dm
.store(node
, hostname
, "accounts", {password
= password
});
221 print("["..(err
or "success").."] accounts: "..node
.."@"..hostname
);
224 function roster(node
, host
, jid
, item
)
225 local roster
= dm
.load(node
, host
, "roster") or {};
227 local ret
, err
= dm
.store(node
, host
, "roster", roster
);
228 print("["..(err
or "success").."] roster: " ..node
.."@"..host
.." - "..jid
);
230 function roster_pending(node
, host
, jid
)
231 local roster
= dm
.load(node
, host
, "roster") or {};
232 roster
.pending
= roster
.pending
or {};
233 roster
.pending
[jid
] = true;
234 local ret
, err
= dm
.store(node
, host
, "roster", roster
);
235 print("["..(err
or "success").."] roster-pending: " ..node
.."@"..host
.." - "..jid
);
237 function roster_group(node
, host
, jid
, group
)
238 local roster
= dm
.load(node
, host
, "roster") or {};
239 local item
= roster
[jid
];
240 if not item
then print("Warning: No roster item "..jid
.." for user "..node
..", can't put in group "..group
); return; end
241 item
.groups
[group
] = true;
242 local ret
, err
= dm
.store(node
, host
, "roster", roster
);
243 print("["..(err
or "success").."] roster-group: " ..node
.."@"..host
.." - "..jid
.." - "..group
);
245 function private_storage(node
, host
, xmlns
, stanza
)
246 local private
= dm
.load(node
, host
, "private") or {};
247 private
[stanza
.name
..":"..xmlns
] = st
.preserialize(stanza
);
248 local ret
, err
= dm
.store(node
, host
, "private", private
);
249 print("["..(err
or "success").."] private: " ..node
.."@"..host
.." - "..xmlns
);
251 function offline_msg(node
, host
, t
, stanza
)
252 stanza
.attr
.stamp
= os
.date("!%Y-%m-%dT%H:%M:%SZ", t
);
253 stanza
.attr
.stamp_legacy
= os
.date("!%Y%m%dT%H:%M:%S", t
);
254 local ret
, err
= dm
.list_append(node
, host
, "offline", st
.preserialize(stanza
));
255 print("["..(err
or "success").."] offline: " ..node
.."@"..host
.." - "..os
.date("!%Y-%m-%dT%H:%M:%SZ", t
));
257 for _
, row
in ipairs(parsed
["rosterusers"] or NULL
) do
258 local node
, contact
= row
.username
, row
.jid
;
259 local name
= row
.nick
;
260 if name
== "" then name
= nil; end
261 local subscription
= row
.subscription
;
262 if subscription
== "N" then
263 subscription
= "none"
264 elseif subscription
== "B" then
265 subscription
= "both"
266 elseif subscription
== "F" then
267 subscription
= "from"
268 elseif subscription
== "T" then
270 else error("Unknown subscription type: "..subscription
) end;
274 elseif ask
== "O" then
276 elseif ask
== "I" then
277 roster_pending(node
, hostname
, contact
);
279 elseif ask
== "B" then
280 roster_pending(node
, hostname
, contact
);
282 else error("Unknown ask type: "..ask
); end
283 local item
= {name
= name
, ask
= ask
, subscription
= subscription
, groups
= {}};
284 roster(node
, hostname
, contact
, item
);
286 for _
, row
in ipairs(parsed
["rostergroups"] or NULL
) do
287 roster_group(row
.username
, hostname
, row
.jid
, row
.grp
);
289 for _
, row
in ipairs(parsed
["vcard"] or NULL
) do
290 local stanza
, err
= parse_xml(row
.vcard
);
292 local ret
, err
= dm
.store(row
.username
, hostname
, "vcard", st
.preserialize(stanza
));
293 print("["..(err
or "success").."] vCard: "..row
.username
.."@"..hostname
);
295 print("[error] vCard XML parse failed: "..row
.username
.."@"..hostname
);
298 for _
, row
in ipairs(parsed
["private_storage"] or NULL
) do
299 local stanza
, err
= parse_xml(row
.data
);
301 private_storage(row
.username
, hostname
, row
.namespace
, stanza
);
303 print("[error] Private XML parse failed: "..row
.username
.."@"..hostname
);
306 table.sort(parsed
["spool"] or NULL
, function(a
,b
) return a
.seq
< b
.seq
; end); -- sort by sequence number, just in case
307 local time_offset
= os
.difftime(os
.time(os
.date("!*t")), os
.time(os
.date("*t"))) -- to deal with timezones
308 local date_parse
= function(s
)
309 local year
, month
, day
, hour
, min, sec
= s
:match("(....)-?(..)-?(..)T(..):(..):(..)");
310 return os
.time({year
=year
, month
=month
, day
=day
, hour
=hour
, min=min, sec
=sec
-time_offset
});
312 for _
, row
in ipairs(parsed
["spool"] or NULL
) do
313 local stanza
, err
= parse_xml(row
.xml
);
315 local last_child
= stanza
.tags
[#stanza
.tags
];
316 if not last_child
or last_child
~= stanza
[#stanza
] then error("Last child of offline message is not a tag"); end
317 if last_child
.name
~= "x" and last_child
.attr
.xmlns
~= "jabber:x:delay" then error("Last child of offline message is not a timestamp"); end
318 stanza
[#stanza
], stanza
.tags
[#stanza
.tags
] = nil, nil;
319 local t
= date_parse(last_child
.attr
.stamp
);
320 offline_msg(row
.username
, hostname
, t
, stanza
);
322 print("[error] Offline message XML parsing failed: "..row
.username
.."@"..hostname
);