ConnectionManager: Document tracking functions.
[thrasher.git] / perl / lib / Thrasher / Backend / XDB.pm
blob7765506d81f98e09dea11f4ed337b4ee3605f85e
1 package Thrasher::Backend::XDB;
2 use strict;
3 use warnings;
5 use base 'Thrasher::Backend';
7 use XML::DOM;
8 use Data::Dumper;
9 use Digest::MD5 qw(md5_hex);
11 =pod
13 =head1 NAME
15 Thrasher::Backend::XDB - Read-only XDB-based backend storage, for
16 migration from python transports
18 =head1 DESCRIPTION
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):
36 <xdb>
37 <query xmlns='jabber:iq:register' xdbns='jabber:iq:register'>
38 <username>[legacy username]</username>
39 <password>[legacy password]</password>
40 </query>
41 <query xdbns='jabber:iq:roster' xmlns='jabber:iq:roster'>
42 <item jid='test_account@something.invalid'/>
43 <item jid='another_account@else.invalid'/>
44 </query>
45 </xdb>
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.
57 =head1 CONFIGURATION
59 XDB requires some parameters to work:
61 =over 4
63 =back
66 =cut
68 sub new {
69 my $class = shift;
70 my $parameters = shift;
72 my $self = bless $parameters, $class;
74 my @missing;
75 for my $required qw(directory component_name) {
76 if (!defined($self->{$required})) {
77 push @missing, $required;
80 if (@missing) {
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";
92 return $self;
95 sub all_jids {
96 my $self = shift;
98 my @possible_jids;
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.
113 sub load_user {
114 my $self = shift;
115 my $jid = shift;
117 if (my $info = $self->{user_info}->{$jid}) {
118 return $info;
121 my $filename = $jid;
122 $filename =~ s/\@/\%/;
123 $filename .= '.xml';
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";
133 } else {
134 die "Can't load a user unless flavor is one of "
135 .'pyaim, pymsn, or pyicq.';
138 if (!-e $filename) {
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') {
156 my $username =
157 $query->getElementsByTagName('username')->item(0)->getFirstChild->getData;
158 my $password =
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');
165 my $roster = [];
166 for (my $j = 0; $j < $items->getLength; $j++) {
167 my $item = $items->item($j);
168 my $legacy_name =
169 $item->getAttributeNode('jid')->getValue;
170 my $jid = $self->legacy_name_to_jid
171 ($jid, $legacy_name, $self->{component_name});
172 push @$roster, $jid;
174 $self->{user_info}->{$jid}->{roster} = $roster;
178 return $self->{user_info}->{$jid};
181 sub retrieve_legacy_name_to_jid {
182 my $self = shift;
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 {
194 my $self = shift;
195 my $user_jid = shift;
196 my $target_jid = shift;
198 return
199 !!($self->load_user($user_jid)->{jid_to_legacy}->{$target_jid});
202 sub store_username_mapping {
203 my $self = shift;
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;
214 sub registered {
215 my $self = shift;
216 my $jid = shift;
218 return $self->load_user($jid)->{registration};
221 # While some of these transports supported avatars, it's easier
222 # just to re-retrieve them.
223 sub get_avatar {
224 return undef;
227 sub all_avatars {
228 return {};
231 sub get_roster {
232 return {};
235 sub all_mappings {
236 my $self = shift;
237 my $jid = shift;
239 return $self->load_user($jid)->{legacy_to_jid};
242 sub all_misc {
243 return {};