Mercurial > pidgin
annotate libfaim/txqueue.c @ 1771:213607e89598
[gaim-migrate @ 1781]
plug mem leak. don't show evil level if it decreased. mid's utf8 patch for jabber. my girlfriend got an accounting calculator today, you know, with the paper and the printing and things. it's kinda loud. she's really happy about having it. she had bought a different one yesterday but it didn't work so we returned it today. we also went to Albertson's and bought groceries. we bought 72 cans of soda for $15. That's 20 cents per soda. Not bad. we also bought a cow; i'm going to cook it tonight. ben&jerry's ice cream is good.
committer: Tailor Script <tailor@pidgin.im>
author | Eric Warmenhoven <eric@warmenhoven.org> |
---|---|
date | Mon, 30 Apr 2001 01:25:30 +0000 |
parents | ec31e23aadc7 |
children |
rev | line source |
---|---|
1535 | 1 /* |
2 * aim_txqueue.c | |
3 * | |
4 * Herein lies all the mangement routines for the transmit (Tx) queue. | |
5 * | |
6 */ | |
7 | |
8 #define FAIM_INTERNAL | |
9 #include <aim.h> | |
10 | |
11 #ifndef _WIN32 | |
12 #include <sys/socket.h> | |
13 #endif | |
14 | |
15 /* | |
16 * Allocate a new tx frame. | |
17 * | |
18 * This is more for looks than anything else. | |
19 * | |
20 * Right now, that is. If/when we implement a pool of transmit | |
21 * frames, this will become the request-an-unused-frame part. | |
22 * | |
23 * framing = AIM_FRAMETYPE_OFT/OSCAR | |
24 * chan = channel for OSCAR, hdrtype for OFT | |
25 * | |
26 */ | |
1593
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
27 faim_internal struct command_tx_struct *aim_tx_new(struct aim_session_t *sess, |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
28 struct aim_conn_t *conn, |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
29 unsigned char framing, |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
30 int chan, |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
31 int datalen) |
1535 | 32 { |
33 struct command_tx_struct *newtx; | |
34 | |
35 if (!conn) { | |
36 faimdprintf(sess, 0, "aim_tx_new: ERROR: no connection specified\n"); | |
37 return NULL; | |
38 } | |
39 | |
40 /* For sanity... */ | |
41 if ((conn->type == AIM_CONN_TYPE_RENDEZVOUS) || (conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT)) { | |
42 if (framing != AIM_FRAMETYPE_OFT) { | |
43 faimdprintf(sess, 0, "aim_tx_new: attempted to allocate inappropriate frame type for rendezvous connection\n"); | |
44 return NULL; | |
45 } | |
46 } else { | |
47 if (framing != AIM_FRAMETYPE_OSCAR) { | |
48 faimdprintf(sess, 0, "aim_tx_new: attempted to allocate inappropriate frame type for FLAP connection\n"); | |
49 return NULL; | |
50 } | |
51 } | |
52 | |
53 newtx = (struct command_tx_struct *)malloc(sizeof(struct command_tx_struct)); | |
54 if (!newtx) | |
55 return NULL; | |
56 memset(newtx, 0, sizeof(struct command_tx_struct)); | |
57 | |
58 newtx->conn = conn; | |
59 | |
60 if(datalen) { | |
61 newtx->data = (unsigned char *)malloc(datalen); | |
62 newtx->commandlen = datalen; | |
63 } else | |
64 newtx->data = NULL; | |
65 | |
66 newtx->hdrtype = framing; | |
67 if (newtx->hdrtype == AIM_FRAMETYPE_OSCAR) { | |
68 newtx->hdr.oscar.type = chan; | |
69 } else if (newtx->hdrtype == AIM_FRAMETYPE_OFT) { | |
70 newtx->hdr.oft.type = chan; | |
71 newtx->hdr.oft.hdr2len = 0; /* this will get setup by caller */ | |
72 } else { | |
73 faimdprintf(sess, 0, "tx_new: unknown framing\n"); | |
74 } | |
75 | |
76 return newtx; | |
77 } | |
78 | |
79 /* | |
80 * aim_tx_enqeue__queuebased() | |
81 * | |
82 * The overall purpose here is to enqueue the passed in command struct | |
83 * into the outgoing (tx) queue. Basically... | |
84 * 1) Make a scope-irrelevent copy of the struct | |
85 * 2) Lock the struct | |
86 * 3) Mark as not-sent-yet | |
87 * 4) Enqueue the struct into the list | |
88 * 5) Unlock the struct once it's linked in | |
89 * 6) Return | |
90 * | |
91 * Note that this is only used when doing queue-based transmitting; | |
92 * that is, when sess->tx_enqueue is set to &aim_tx_enqueue__queuebased. | |
93 * | |
94 */ | |
95 static int aim_tx_enqueue__queuebased(struct aim_session_t *sess, struct command_tx_struct *newpacket) | |
96 { | |
97 struct command_tx_struct *cur; | |
98 | |
99 if (newpacket->conn == NULL) { | |
100 faimdprintf(sess, 1, "aim_tx_enqueue: WARNING: enqueueing packet with no connecetion\n"); | |
101 newpacket->conn = aim_getconn_type(sess, AIM_CONN_TYPE_BOS); | |
102 } | |
103 | |
104 if (newpacket->hdrtype == AIM_FRAMETYPE_OSCAR) { | |
105 /* assign seqnum */ | |
106 newpacket->hdr.oscar.seqnum = aim_get_next_txseqnum(newpacket->conn); | |
107 } | |
108 /* set some more fields */ | |
109 newpacket->lock = 1; /* lock */ | |
110 newpacket->sent = 0; /* not sent yet */ | |
111 newpacket->next = NULL; /* always last */ | |
112 | |
113 /* see overhead note in aim_rxqueue counterpart */ | |
114 if (sess->queue_outgoing == NULL) { | |
115 sess->queue_outgoing = newpacket; | |
116 } else { | |
117 for (cur = sess->queue_outgoing; | |
118 cur->next; | |
119 cur = cur->next) | |
120 ; | |
121 cur->next = newpacket; | |
122 } | |
123 | |
124 newpacket->lock = 0; /* unlock so it can be sent */ | |
125 | |
126 return 0; | |
127 } | |
128 | |
129 /* | |
130 * aim_tx_enqueue__immediate() | |
131 * | |
132 * Parallel to aim_tx_enqueue__queuebased, however, this bypasses | |
133 * the whole queue mess when you want immediate writes to happen. | |
134 * | |
135 * Basically the same as its __queuebased couterpart, however | |
136 * instead of doing a list append, it just calls aim_tx_sendframe() | |
137 * right here. | |
138 * | |
139 */ | |
140 static int aim_tx_enqueue__immediate(struct aim_session_t *sess, struct command_tx_struct *newpacket) | |
141 { | |
142 if (newpacket->conn == NULL) { | |
143 faimdprintf(sess, 1, "aim_tx_enqueue: ERROR: packet has no connection\n"); | |
144 if (newpacket->data) | |
145 free(newpacket->data); | |
146 free(newpacket); | |
147 return -1; | |
148 } | |
149 | |
150 if (newpacket->hdrtype == AIM_FRAMETYPE_OSCAR) | |
151 newpacket->hdr.oscar.seqnum = aim_get_next_txseqnum(newpacket->conn); | |
152 | |
153 newpacket->lock = 1; /* lock */ | |
154 newpacket->sent = 0; /* not sent yet */ | |
155 | |
156 aim_tx_sendframe(sess, newpacket); | |
157 | |
158 if (newpacket->data) | |
159 free(newpacket->data); | |
160 free(newpacket); | |
161 | |
162 return 0; | |
163 } | |
164 | |
1593
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
165 faim_export int aim_tx_setenqueue(struct aim_session_t *sess, |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
166 int what, |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
167 int (*func)(struct aim_session_t *, struct command_tx_struct *)) |
1535 | 168 { |
169 if (!sess) | |
170 return -1; | |
171 | |
172 if (what == AIM_TX_QUEUED) | |
173 sess->tx_enqueue = &aim_tx_enqueue__queuebased; | |
174 else if (what == AIM_TX_IMMEDIATE) | |
175 sess->tx_enqueue = &aim_tx_enqueue__immediate; | |
176 else if (what == AIM_TX_USER) { | |
177 if (!func) | |
178 return -1; | |
179 sess->tx_enqueue = func; | |
180 } else | |
181 return -1; /* unknown action */ | |
182 | |
183 return 0; | |
184 } | |
185 | |
186 faim_internal int aim_tx_enqueue(struct aim_session_t *sess, struct command_tx_struct *command) | |
187 { | |
188 /* | |
189 * If we want to send a connection thats inprogress, we have to force | |
190 * them to use the queue based version. Otherwise, use whatever they | |
191 * want. | |
192 */ | |
193 if (command && command->conn && (command->conn->status & AIM_CONN_STATUS_INPROGRESS)) { | |
194 return aim_tx_enqueue__queuebased(sess, command); | |
195 } | |
196 return (*sess->tx_enqueue)(sess, command); | |
197 } | |
198 | |
199 /* | |
200 * aim_get_next_txseqnum() | |
201 * | |
202 * This increments the tx command count, and returns the seqnum | |
203 * that should be stamped on the next FLAP packet sent. This is | |
204 * normally called during the final step of packet preparation | |
205 * before enqueuement (in aim_tx_enqueue()). | |
206 * | |
207 */ | |
208 faim_internal unsigned int aim_get_next_txseqnum(struct aim_conn_t *conn) | |
209 { | |
210 u_int ret; | |
211 | |
212 faim_mutex_lock(&conn->seqnum_lock); | |
213 ret = ++conn->seqnum; | |
214 faim_mutex_unlock(&conn->seqnum_lock); | |
215 return ret; | |
216 } | |
217 | |
218 /* | |
219 * aim_tx_flushqueue() | |
220 * | |
221 * This the function is responsable for putting the queued commands | |
222 * onto the wire. This function is critical to the operation of | |
223 * the queue and therefore is the most prone to brokenness. It | |
224 * seems to be working quite well at this point. | |
225 * | |
226 * Procedure: | |
227 * 1) Traverse the list, only operate on commands that are unlocked | |
228 * and haven't been sent yet. | |
229 * 2) Lock the struct | |
230 * 3) Allocate a temporary buffer to store the finished, fully | |
231 * processed packet in. | |
232 * 4) Build the packet from the command_tx_struct data. | |
233 * 5) Write the packet to the socket. | |
234 * 6) If success, mark the packet sent, if fail report failure, do NOT | |
235 * mark the packet sent (so it will not get purged and therefore | |
236 * be attempted again on next call). | |
237 * 7) Unlock the struct. | |
238 * 8) Free the temp buffer | |
239 * 9) Step to next struct in list and go back to 1. | |
240 * | |
241 */ | |
242 faim_internal int aim_tx_sendframe(struct aim_session_t *sess, struct command_tx_struct *cur) | |
243 { | |
244 int buflen = 0; | |
245 unsigned char *curPacket; | |
246 | |
247 if (!cur) | |
248 return -1; /* fatal */ | |
249 | |
250 cur->lock = 1; /* lock the struct */ | |
251 | |
252 if (cur->hdrtype == AIM_FRAMETYPE_OSCAR) | |
253 buflen = cur->commandlen + 6; | |
254 else if (cur->hdrtype == AIM_FRAMETYPE_OFT) | |
255 buflen = cur->hdr.oft.hdr2len + 8; | |
256 else { | |
257 cur->lock = 0; | |
258 return -1; | |
259 } | |
260 | |
261 /* allocate full-packet buffer */ | |
262 if (!(curPacket = (unsigned char *) malloc(buflen))) { | |
263 cur->lock = 0; | |
264 return -1; | |
265 } | |
266 | |
267 if (cur->hdrtype == AIM_FRAMETYPE_OSCAR) { | |
268 /* command byte */ | |
269 curPacket[0] = 0x2a; | |
270 | |
271 /* type/family byte */ | |
272 curPacket[1] = cur->hdr.oscar.type; | |
273 | |
274 /* bytes 3+4: word: FLAP sequence number */ | |
275 aimutil_put16(curPacket+2, cur->hdr.oscar.seqnum); | |
276 | |
277 /* bytes 5+6: word: SNAC len */ | |
278 aimutil_put16(curPacket+4, cur->commandlen); | |
279 | |
280 /* bytes 7 and on: raw: SNAC data */ /* XXX: ye gods! get rid of this! */ | |
281 memcpy(&(curPacket[6]), cur->data, cur->commandlen); | |
282 | |
283 } else if (cur->hdrtype == AIM_FRAMETYPE_OFT) { | |
284 int z = 0; | |
285 | |
286 z += aimutil_put8(curPacket+z, cur->hdr.oft.magic[0]); | |
287 z += aimutil_put8(curPacket+z, cur->hdr.oft.magic[1]); | |
288 z += aimutil_put8(curPacket+z, cur->hdr.oft.magic[2]); | |
289 z += aimutil_put8(curPacket+z, cur->hdr.oft.magic[3]); | |
290 | |
291 z += aimutil_put16(curPacket+z, cur->hdr.oft.hdr2len + 8); | |
292 z += aimutil_put16(curPacket+z, cur->hdr.oft.type); | |
293 | |
294 memcpy(curPacket+z, cur->hdr.oft.hdr2, cur->hdr.oft.hdr2len); | |
295 } | |
296 | |
297 /* | |
298 * For OSCAR, a full image of the raw packet data now in curPacket. | |
299 * For OFT, an image of just the bloated header is in curPacket, | |
300 * since OFT allows us to do the data in a different write (yay!). | |
301 */ | |
302 faim_mutex_lock(&cur->conn->active); | |
303 if (send(cur->conn->fd, curPacket, buflen, 0) != buflen) { | |
304 faim_mutex_unlock(&cur->conn->active); | |
305 cur->sent = 1; | |
306 aim_conn_close(cur->conn); | |
307 return 0; /* bail out */ | |
308 } | |
309 | |
310 if ((cur->hdrtype == AIM_FRAMETYPE_OFT) && cur->commandlen) { | |
311 int curposi; | |
312 for(curposi = 0; curposi < cur->commandlen; curposi++) | |
313 faimdprintf(sess, 0, "%02x ", cur->data[curposi]); | |
314 | |
315 if (send(cur->conn->fd, cur->data, cur->commandlen, 0) != (int)cur->commandlen) { | |
316 /* | |
317 * Theres nothing we can do about this since we've already sent the | |
318 * header! The connection is unstable. | |
319 */ | |
320 faim_mutex_unlock(&cur->conn->active); | |
321 cur->sent = 1; | |
322 aim_conn_close(cur->conn); | |
323 return 0; /* bail out */ | |
324 } | |
325 | |
326 } | |
327 | |
328 cur->sent = 1; /* mark the struct as sent */ | |
329 cur->conn->lastactivity = time(NULL); | |
330 | |
331 faim_mutex_unlock(&cur->conn->active); | |
332 | |
333 if (sess->debug >= 2) { | |
334 int i; | |
335 | |
336 faimdprintf(sess, 2, "\nOutgoing packet: (only valid for OSCAR)"); | |
337 for (i = 0; i < buflen; i++) { | |
338 if (!(i % 8)) | |
339 faimdprintf(sess, 2, "\n\t"); | |
340 faimdprintf(sess, 2, "0x%02x ", curPacket[i]); | |
341 } | |
342 faimdprintf(sess, 2, "\n"); | |
343 } | |
344 | |
345 cur->lock = 0; /* unlock the struct */ | |
346 | |
347 free(curPacket); /* free up full-packet buffer */ | |
348 | |
349 return 1; /* success */ | |
350 } | |
351 | |
352 faim_export int aim_tx_flushqueue(struct aim_session_t *sess) | |
353 { | |
354 struct command_tx_struct *cur; | |
355 | |
356 if (sess->queue_outgoing == NULL) | |
357 return 0; | |
358 | |
359 faimdprintf(sess, 2, "beginning txflush...\n"); | |
360 for (cur = sess->queue_outgoing; cur; cur = cur->next) { | |
361 /* only process if its unlocked and unsent */ | |
362 if (!cur->lock && !cur->sent) { | |
363 | |
364 if (cur->conn && (cur->conn->status & AIM_CONN_STATUS_INPROGRESS)) | |
365 continue; | |
366 | |
367 /* | |
368 * And now for the meager attempt to force transmit | |
369 * latency and avoid missed messages. | |
370 */ | |
371 if ((cur->conn->lastactivity + cur->conn->forcedlatency) >= time(NULL)) { | |
372 /* FIXME FIXME -- should be a break! we dont want to block the upper layers */ | |
373 sleep((cur->conn->lastactivity + cur->conn->forcedlatency) - time(NULL)); | |
374 } | |
375 | |
376 /* XXX XXX XXX this should call the custom "queuing" function!! */ | |
377 if (aim_tx_sendframe(sess, cur) == -1) | |
378 break; | |
379 } | |
380 } | |
381 | |
382 /* purge sent commands from queue */ | |
383 aim_tx_purgequeue(sess); | |
384 | |
385 return 0; | |
386 } | |
387 | |
388 /* | |
389 * aim_tx_purgequeue() | |
390 * | |
391 * This is responsable for removing sent commands from the transmit | |
392 * queue. This is not a required operation, but it of course helps | |
393 * reduce memory footprint at run time! | |
394 * | |
395 */ | |
396 faim_export void aim_tx_purgequeue(struct aim_session_t *sess) | |
397 { | |
398 struct command_tx_struct *cur = NULL; | |
399 struct command_tx_struct *tmp; | |
400 | |
401 if (sess->queue_outgoing == NULL) | |
402 return; | |
403 | |
404 if (sess->queue_outgoing->next == NULL) { | |
405 if (!sess->queue_outgoing->lock && sess->queue_outgoing->sent) { | |
406 tmp = sess->queue_outgoing; | |
407 sess->queue_outgoing = NULL; | |
408 if (tmp->hdrtype == AIM_FRAMETYPE_OFT) | |
409 free(tmp->hdr.oft.hdr2); | |
410 free(tmp->data); | |
411 free(tmp); | |
412 } | |
413 return; | |
414 } | |
415 | |
416 for(cur = sess->queue_outgoing; cur->next != NULL; ) { | |
417 if (!cur->next->lock && cur->next->sent) { | |
418 tmp = cur->next; | |
419 cur->next = tmp->next; | |
420 if (tmp->hdrtype == AIM_FRAMETYPE_OFT) | |
421 free(tmp->hdr.oft.hdr2); | |
422 free(tmp->data); | |
423 free(tmp); | |
424 } | |
425 cur = cur->next; | |
426 | |
427 /* | |
428 * Be careful here. Because of the way we just | |
429 * manipulated the pointer, cur may be NULL and | |
430 * the for() will segfault doing the check unless | |
431 * we find this case first. | |
432 */ | |
433 if (cur == NULL) | |
434 break; | |
435 } | |
436 return; | |
437 } | |
1593
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
438 |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
439 /** |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
440 * aim_tx_cleanqueue - get rid of packets waiting for tx on a dying conn |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
441 * @sess: session |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
442 * @conn: connection that's dying |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
443 * |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
444 * for now this simply marks all packets as sent and lets them |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
445 * disappear without warning. |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
446 * |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
447 * doesn't respect command_tx_struct locks. |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
448 */ |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
449 |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
450 faim_export int aim_tx_cleanqueue(struct aim_session_t *sess, struct aim_conn_t *conn) |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
451 { |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
452 struct command_tx_struct *cur = NULL; |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
453 |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
454 if(!sess || !conn) |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
455 return -1; |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
456 |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
457 /* we don't respect locks here */ |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
458 for(cur = sess->queue_outgoing; cur; cur = cur->next) |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
459 if(cur->conn == conn) |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
460 cur->sent = 1; |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
461 |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
462 return 0; |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
463 } |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
464 |
ec31e23aadc7
[gaim-migrate @ 1603]
Eric Warmenhoven <eric@warmenhoven.org>
parents:
1535
diff
changeset
|
465 |