1 package Thrasher
::Backend
::XDB
;
5 use base
'Thrasher::Backend';
9 use Digest
::MD5
qw(md5_hex);
15 Thrasher::Backend::XDB - Read-only XDB-based backend storage, for
16 migration from python transports
20 PyAIMt ( http://code.google.com/p/pyaimt/ ), PyICQt
21 ( http://code.google.com/p/pyicqt/ ), and PyMSNt
22 ( http://delx.net.au/projects/pymsnt/ ) all use some variant on
23 "XDB", which is a format the old Jabber world used a lot when
24 storing things on disk. This module exists primarily to
25 read those files and thereby provide a clean transition path away
26 from Py* to Thrasher Bird. It is not a generic "XDB" reader,
27 if XDB is even a specified standard anywhere.
29 Some minor differences between how the three transports used
30 the file require you to specify which transport you are
31 transitioning away from.
33 XDB files tend to look like this (with suitable substitutions for
34 account name and password, of course):
37 <query xmlns='jabber:iq:register' xdbns='jabber:iq:register'>
38 <username>[legacy username]</username>
39 <password>[legacy password]</password>
41 <query xdbns='jabber:iq:roster' xmlns='jabber:iq:roster'>
42 <item jid='test_account@something.invalid'/>
43 <item jid='another_account@else.invalid'/>
47 The transports store additional, apparently useless data with the
48 roster items, and this format notably does not appear to correctly
49 store gateway-format-translated names. (In fact looking at how
50 these files are used by the three transports, I'm not sure how
51 they can work correctly with some pathological cases at all.)
53 Because this format neither contains nor stores enough information
54 for Thrasher to work correctly (it would only be able to work
55 half-correctly), this can only be used for import purposes.
59 XDB requires some parameters to work:
70 my $parameters = shift;
72 my $self = bless $parameters, $class;
75 for my $required qw(directory component_name) {
76 if (!defined($self->{$required})) {
77 push @missing, $required;
81 die "$class required the following parameters that weren't "
82 ."provided: " . join(', ', @missing);
85 if (!-e
$self->{directory
}) {
86 die "XDB source directory $self->{directory} does not exist.";
88 if (!-d
$self->{directory
}) {
89 die "XDB source directory $self->{directory} is not a directory";
99 for my $xml_file (glob($self->{directory
} . "/*/*")) {
100 if ($xml_file =~ /.*\/([^%]+)%([^%]+?
)\
.xml
/) {
101 push @possible_jids, "$1\@$2";
105 return \
@possible_jids;
108 # This is the meat of this file, which actually loads up a user
109 # and loads in the roster and registration information, isolating
110 # most of the differences into this function. OO dogma would have
111 # me subclass three classes and change this method, but that's just
112 # silly-heavy-weightness.
117 if (my $info = $self->{user_info
}->{$jid}) {
122 $filename =~ s/\@/\%/;
124 if ($self->{flavor
} eq 'pyaim' || $self->{flavor
} eq 'pyicq') {
125 my $subdir = substr $jid, 0, 2;
126 $filename = "$self->{directory}/$subdir/$filename";
127 } elsif ($self->{flavor
} eq 'pymsn') {
128 my $md5_portion = $jid;
129 $md5_portion =~ s/\@/\%/;
130 my $subdir = md5_hex
($md5_portion);
131 $subdir = substr $subdir, 0, 3;
132 $filename = "$self->{directory}/$subdir/$filename";
134 die "Can't load a user unless flavor is one of "
135 .'pyaim, pymsn, or pyicq.';
139 die "While trying to load user info for $jid, "
140 ."couldn't find data file $filename.";
143 my $dom = XML
::DOM
::Parser
->new;
144 my $doc = $dom->parsefile($filename);
146 my $queries = $doc->getElementsByTagName('query');
148 $self->{user_info
}->{$jid} = {};
150 for (my $i = 0; $i < $queries->getLength; $i++) {
151 my $query = $queries->item($i);
153 my $namespace = $query->getAttributeNode('xdbns')->getValue;
155 if ($namespace eq 'jabber:iq:register') {
157 $query->getElementsByTagName('username')->item(0)->getFirstChild->getData;
159 $query->getElementsByTagName('password')->item(0)->getFirstChild->getData;
160 $self->{user_info
}->{$jid}->{registration
} =
161 {username
=> $username,
162 password
=> $password};
163 } elsif ($namespace eq 'jabber:iq:roster') {
164 my $items = $query->getElementsByTagName('item');
166 for (my $j = 0; $j < $items->getLength; $j++) {
167 my $item = $items->item($j);
169 $item->getAttributeNode('jid')->getValue;
170 my $jid = $self->legacy_name_to_jid
171 ($jid, $legacy_name, $self->{component_name
});
174 $self->{user_info
}->{$jid}->{roster
} = $roster;
178 return $self->{user_info
}->{$jid};
181 sub retrieve_legacy_name_to_jid
{
183 my $user_jid = shift;
184 my $legacy_username = shift;
186 if ($self->{user_info
}->{$user_jid}) {
187 return $self->{user_info
}->{$user_jid}->{legacy_to_jid
}->{$legacy_username};
190 return $self->load_user($user_jid)->{legacy_to_jid
}->{$legacy_username};
193 sub jid_has_legacy_name
{
195 my $user_jid = shift;
196 my $target_jid = shift;
199 !!($self->load_user($user_jid)->{jid_to_legacy
}->{$target_jid});
202 sub store_username_mapping
{
204 my $user_jid = shift;
205 my $legacy_username = shift;
206 my $mapped_jid = shift;
208 my $data = $self->load_user($user_jid);
210 $data->{jid_to_legacy
}->{$mapped_jid} = $legacy_username;
211 $data->{legacy_to_jid
}->{$legacy_username} = $mapped_jid;
218 return $self->load_user($jid)->{registration
};
221 # While some of these transports supported avatars, it's easier
222 # just to re-retrieve them.
239 return $self->load_user($jid)->{legacy_to_jid
};