1 /*
2  * Purple Plugin Pack
3  * Copyright (C) 2003-2008
4  * See ../AUTHORS for a list of all authors
5  *
6  * listhandler: Provides importing, exporting, and copying functions
7  * 				for accounts' buddy lists.
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
22  */
23 
24 #include "listhandler.h"
25 #include "gen_xml_files.h"
26 
27 static const gchar *target_prpl_id = NULL;
28 static gchar *file_contents = NULL, *filename = NULL;
29 static gsize length;
30 static PurpleAccount *target_account = NULL, *source_account = NULL;
31 static PurpleBuddyList *buddies = NULL;
32 static PurpleConnection *gc = NULL;
33 static xmlnode *root = NULL;
34 
35 static gboolean
lh_import_filter(PurpleAccount * account)36 lh_import_filter(PurpleAccount *account)
37 {
38 	const gchar *prpl_id = purple_account_get_protocol_id(account);
39 
40 	if(!prpl_id)
41 		return FALSE;
42 
43 	if(!strcmp(prpl_id, target_prpl_id))
44 		return TRUE;
45 
46 	return FALSE;
47 }
48 
49 static void
lh_generic_import_privacy(xmlnode * privacy)50 lh_generic_import_privacy(xmlnode *privacy)
51 {
52 	/* XXX: This is here awaiting Bleeter's privacy rewrite that will allow
53 	 *      importing and exporting of privacy options, lists, etc. */
54 }
55 
56 static void
lh_generic_import_blist(xmlnode * blist)57 lh_generic_import_blist(xmlnode *blist)
58 {
59 	const gchar *group_name = NULL;
60 	PurpleGroup *purple_group = NULL;
61 	xmlnode *buddy = NULL;
62 	/* get the first group */
63 	xmlnode *group = xmlnode_get_child(blist, "group");
64 
65 	while(group) {
66 		/* get the group's name */
67 		group_name = xmlnode_get_attrib(group, "name");
68 
69 		purple_debug_info("listhandler: import", "Current group in XML is %s\n",
70 				group_name);
71 
72 		/* create and/or get a pointer to the PurpleGroup */
73 		purple_group = purple_group_new(group_name);
74 
75 		/* get the first buddy in this group */
76 		buddy = xmlnode_get_child(group, "buddy");
77 
78 		while(buddy) {
79 			/* add the buddy to Purple's blist */
80 			lh_util_add_buddy(group_name, purple_group,
81 					xmlnode_get_attrib(buddy, "screenname"),
82 					xmlnode_get_attrib(buddy, "alias"), target_account,
83 					xmlnode_get_attrib(buddy, "notes"), 0, 0, 0, 0, NULL, NULL, NULL);
84 
85 			/* get the next buddy in the current group */
86 			buddy = xmlnode_get_next_twin(buddy);
87 		}
88 
89 		/* get the next group in the exported blist */
90 		group = xmlnode_get_next_twin(group);
91 	}
92 
93 	return;
94 }
95 
96 static void
lh_generic_import_target_request_cb(void * ignored,PurpleRequestFields * fields)97 lh_generic_import_target_request_cb(void *ignored, PurpleRequestFields *fields)
98 {
99 	/* get the target account */
100 	target_account = purple_request_fields_get_account(fields, "generic_target_acct");
101 
102 	purple_debug_info("listhandler: import",
103 			"Got the target account and its connection.\n");
104 
105 	purple_debug_info("listhandler: import", "Beginning to parse XML.\n");
106 
107 	/* call separate functions to import the privacy and blist */
108 	lh_generic_import_privacy(xmlnode_get_child(root, "privacy"));
109 	lh_generic_import_blist(xmlnode_get_child(root, "blist"));
110 
111 	purple_debug_info("listhandler: import", "Finished parsing XML.  "
112 			"Freeing allocated memory.\n");
113 
114 	xmlnode_free(root);
115 }
116 
117 static void
lh_generic_import_target_request(void)118 lh_generic_import_target_request(void)
119 {
120 	PurpleRequestFields *request;
121 	PurpleRequestFieldGroup *group;
122 	PurpleRequestField *field;
123 	GError *error = NULL;
124 
125 	/* we need to make sure which purple prpl this buddy list came from so we
126 	 * can filter the accounts list before showing it */
127 
128 	/* read the file */
129 	g_file_get_contents(filename, &file_contents, &length, &error);
130 
131 	root = xmlnode_from_str(file_contents, length);
132 
133 	target_prpl_id = xmlnode_get_attrib(xmlnode_get_child(xmlnode_get_child(root, "config"),
134 				"prpl"), "id");
135 
136 	purple_debug_info("listhandler: import", "Beginning Request API calls\n");
137 
138 	/* It seems Purple is super-picky about the order of these first three calls */
139 	/* create a request */
140 	request = purple_request_fields_new();
141 
142 	/* now create a field group */
143 	group = purple_request_field_group_new(NULL);
144 	/* and add that group to the request created above */
145 	purple_request_fields_add_group(request, group);
146 
147 	/* create a field */
148 	field = purple_request_field_account_new("generic_target_acct", _("Account"), NULL);
149 	/* set the account field filter so we only see accounts with the same
150 	 * prpl as the blist was exported from */
151 	purple_request_field_account_set_filter(field, lh_import_filter);
152 	/* mark the field as required */
153 	purple_request_field_set_required(field, TRUE);
154 
155 	/* add the field to the group created above */
156 	purple_request_field_group_add_field(group, field);
157 
158 	/* and finally we can create the request */
159 	purple_request_fields(purple_get_blist(), _("Listhandler - Importing"),
160 						_("Choose the account to import to:"), NULL, request,
161 						_("_Import"),
162 						G_CALLBACK(lh_generic_import_target_request_cb),
163 						_("_Cancel"), NULL, NULL, NULL, NULL, NULL);
164 
165 	purple_debug_info("listhandler: import", "Ending Request API calls\n");
166 
167 	g_free(filename);
168 
169 	return;
170 }
171 
172 static void
lh_generic_import_request_cb(void * user_data,const char * file)173 lh_generic_import_request_cb(void *user_data, const char *file)
174 {
175 	purple_debug_info("listhandler: import", "Beginning import\n");
176 
177 	if(file) {
178 		filename = g_strdup(file);
179 
180 		lh_generic_import_target_request();
181 	}
182 }
183 
184 static void
lh_generic_build_config_tree(xmlnode * parent)185 lh_generic_build_config_tree(xmlnode *parent)
186 { /* we may need/want to expand the config area later for future feature
187 	 enhancements; this is why this tree gets its own building function. */
188 
189 	xmlnode_set_attrib(xmlnode_new_child(parent, "config-version"), "version", "2");
190 	xmlnode_set_attrib(xmlnode_new_child(parent, "config-type"), "type", "buddy-list");
191 	xmlnode_set_attrib(xmlnode_new_child(parent, "prpl"), "id",
192 			purple_account_get_protocol_id(source_account));
193 	xmlnode_set_attrib(xmlnode_new_child(parent, "source"), "account",
194 			purple_account_get_username(source_account));
195 
196 	return;
197 }
198 
199 static void
lh_generic_build_privacy_tree(xmlnode * parent)200 lh_generic_build_privacy_tree(xmlnode *parent)
201 {
202 	/* XXX: This function does nothing pending Bleeter's privacy rewrite, which
203 	 *      will allow exporting of privacy options, lists, etc. */
204 	return;
205 }
206 
207 static void
lh_generic_build_blist_tree(xmlnode * parent)208 lh_generic_build_blist_tree(xmlnode *parent)
209 {
210 	/*               root of tree           group      contact    buddy */
211 	PurpleBlistNode *root = buddies->root, *g = NULL, *c = NULL, *b = NULL;
212 	xmlnode *group = NULL, *buddy = NULL;
213 	PurpleBuddy *tmpbuddy = NULL;
214 	const char *tmpalias = NULL, *tmpname = NULL, *tmpsetting = NULL;
215 
216 	/* iterate through the groups */
217 	for(g = root; g; g = g->next) {
218 		if(PURPLE_BLIST_NODE_IS_GROUP(g)) {
219 			const char *group_name = ((PurpleGroup *)g)->name;
220 
221 			purple_debug_info("listhandler: export", "Node is group.  Name is: %s\n",
222 					group_name);
223 
224 			/* add the group to the tree */
225 			group = xmlnode_new_child(parent, "group");
226 			xmlnode_set_attrib(group, "name", group_name);
227 
228 			/* iterate through the contacts */
229 			for(c = g->child; c; c= c->next) {
230 				if(PURPLE_BLIST_NODE_IS_CONTACT(c)) {
231 					purple_debug_info("listhandler: export",
232 							"Node is contact.  Will parse its children.\n");
233 
234 					/* iterate through the buddies */
235 					for(b = c->child; b && PURPLE_BLIST_NODE_IS_BUDDY(b); b = b->next) {
236 						tmpbuddy = (PurpleBuddy *)b;
237 						if(purple_buddy_get_account(tmpbuddy) == source_account) {
238 							tmpalias = purple_buddy_get_contact_alias(tmpbuddy);
239 							tmpname = purple_buddy_get_name(tmpbuddy);
240 							tmpsetting = purple_blist_node_get_string(b, "notes");
241 
242 							buddy = xmlnode_new_child(group, "buddy");
243 							xmlnode_set_attrib(buddy, "screenname", tmpname);
244 							xmlnode_set_attrib(buddy, "notes", tmpsetting);
245 
246 							if(strcmp(tmpalias, tmpname))
247 								xmlnode_set_attrib(buddy, "alias", tmpalias);
248 							else
249 								xmlnode_set_attrib(buddy, "alias", NULL);
250 						}
251 					}
252 				}
253 			}
254 		}
255 	}
256 
257 	return;
258 }
259 
260 static xmlnode *
lh_generic_build_tree(void)261 lh_generic_build_tree(void)
262 {
263 	xmlnode *root_node = xmlnode_new("exported_buddy_list");
264 
265 	/* since building this tree is really building three smaller trees that
266 	 * share a common parent, we'll build each tree separately to make this
267 	 * easier to read and understand what goes in each tree (hopefully). */
268 	lh_generic_build_config_tree(xmlnode_new_child(root_node, "config"));
269 	lh_generic_build_privacy_tree(xmlnode_new_child(root_node, "privacy"));
270 	lh_generic_build_blist_tree(xmlnode_new_child(root_node, "blist"));
271 
272 	return root_node;
273 }
274 
275 static void
lh_generic_export_request_cb(void * user_data,const char * filename)276 lh_generic_export_request_cb(void *user_data, const char *filename)
277 {
278 	FILE *export = fopen(filename, "w");
279 
280 	if(export) {
281 		int xmlstrlen = 0;
282 		xmlnode *tree = lh_generic_build_tree();
283 		char *xmlstring = xmlnode_to_formatted_str(tree, &xmlstrlen);
284 
285 		purple_debug_info("listhandler: export",
286 				"XML tree built and converted to string.  String is:\n\n%s\n",
287 				xmlstring);
288 
289 		fprintf(export, "%s\n", xmlstring);
290 
291 		fclose(export);
292 
293 		g_free(xmlstring);
294 		xmlnode_free(tree);
295 	} else
296 		purple_debug_info("listhandler: export", "Can't save file %s\n",
297 				filename ? filename : "NULL");
298 
299 	return;
300 }
301 
302 static void
lh_generic_export_cb(void * ignored,PurpleRequestFields * fields)303 lh_generic_export_cb(void *ignored, PurpleRequestFields *fields)
304 {
305 	/* get the source account from the dialog we requested */
306 	source_account = purple_request_fields_get_account(fields, "generic_source_acct");
307 
308 	/* get the connection from the account */
309 	gc = purple_account_get_connection(source_account);
310 
311 	/* this grabs the purple buddy list, which will be walked thru later */
312 	buddies = purple_get_blist();
313 
314 	if(buddies)
315 		purple_request_file(listhandler, _("Save Generic .blist File"), NULL,
316 				TRUE, G_CALLBACK(lh_generic_export_request_cb), NULL,
317 				source_account, NULL, NULL, NULL);
318 	else
319 		purple_debug_info("listhandler: export", "blist not returned\n");
320 
321 	return;
322 }
323 
324 void /* do some work and export the damn blist already */
lh_generic_export_action_cb(PurplePluginAction * action)325 lh_generic_export_action_cb(PurplePluginAction *action)
326 {
327 	PurpleRequestFields *request;
328 	PurpleRequestFieldGroup *group;
329 	PurpleRequestField *field;
330 
331 	/* It seems Purple is super-picky about the order of these first three calls */
332 	/* create a request */
333 	request = purple_request_fields_new();
334 
335 	/* now create a field group */
336 	group = purple_request_field_group_new(NULL);
337 	/* and add that group to the request created above */
338 	purple_request_fields_add_group(request, group);
339 
340 	/* create a field */
341 	field = purple_request_field_account_new("generic_source_acct", _("Account"), NULL);
342 
343 	/* mark the field as required */
344 	purple_request_field_set_required(field, TRUE);
345 
346 	/* let's show offline accounts too */
347 	purple_request_field_account_set_show_all(field, TRUE);
348 
349 	/* add the field to the group created above */
350 	purple_request_field_group_add_field(group, field);
351 
352 	/* and finally we can create the request */
353 	purple_request_fields(purple_get_blist(), _("Listhandler - Exporting"),
354 						_("Choose the account to export from:"), NULL, request,
355 						_("_Export"), G_CALLBACK(lh_generic_export_cb), _("_Cancel"),
356 						NULL, NULL, NULL, NULL, NULL);
357 
358 	return;
359 }
360 
361 void
lh_generic_import_action_cb(PurplePluginAction * action)362 lh_generic_import_action_cb(PurplePluginAction *action)
363 {
364 	purple_debug_info("listhandler: import", "Requesting the file.\n");
365 
366 	purple_request_file(listhandler, _("Choose A Generic Buddy List File To Import"),
367 			NULL, FALSE, G_CALLBACK(lh_generic_import_request_cb),
368 			NULL, NULL, NULL, NULL, NULL);
369 
370 	return;
371 }
372 
373