connwrap - initialize gnutls session in cw_connect
[centerim.git] / src / icqcontact.cc
blob9f6494e243557d50ab24a6d6796b64de93de8812
1 /*
3 * centerim single IM contact class
4 * $Id: icqcontact.cc,v 1.105 2005/02/13 12:10:55 iulica Exp $
6 * Copyright (C) 2001-2004 by Konstantin Klyagin <k@thekonst.net>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or (at
11 * your option) any later version.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 * USA
25 #include "icqcontacts.h"
26 #include "icqgroups.h"
27 #include "icqconf.h"
28 #include "icqface.h"
29 #include "abstracthook.h"
30 #include "imexternal.h"
31 #include "eventmanager.h"
33 #include <time.h>
34 #ifdef HAVE_SSTREAM
35 #include <sstream>
36 #else
37 #include <strstream>
38 #endif
40 icqcontact::icqcontact(const imcontact adesc) {
41 string fname, tname;
42 imevent::imeventtype ie;
43 int i;
45 clear();
46 lastread = fhistoffset = 0;
47 status = offline;
48 finlist = true;
49 congratulated = false;
50 openedforchat = false;
51 idlefor = 0;
53 for(ie = imevent::message; ie != imevent::imeventtype_size; ie++)
54 sound[ie] = "";
56 cdesc = adesc;
58 switch(cdesc.pname) {
59 case infocard:
60 case rss:
61 if(!cdesc.uin) {
62 fname = conf->getdirname() + conf->getprotocolprefix(cdesc.pname);
64 for(i = 1; ; i++) {
65 tname = fname + i2str(i);
66 if(access(tname.c_str(), F_OK)) break;
69 cdesc.uin = i;
72 default:
73 load();
74 scanhistory();
75 break;
79 icqcontact::~icqcontact() {
82 string icqcontact::tosane(const string &p) const {
83 string buf;
84 string::iterator i;
86 for(buf = p, i = buf.begin(); i != buf.end(); ++i) {
87 if(strchr("\n\r", *i)) *i = ' ';
90 return buf;
93 string icqcontact::getdirname() const {
94 string ret;
96 ret = conf->getdirname();
98 switch(cdesc.pname) {
99 case icq:
100 case infocard:
101 case rss:
102 case gadu:
103 ret += conf->getprotocolprefix(cdesc.pname) + i2str(cdesc.uin);
104 break;
105 default:
106 ret += conf->getprotocolprefix(cdesc.pname) + cdesc.nickname;
107 break;
110 ret += "/";
111 return ret;
114 void icqcontact::clear() {
115 fupdated = groupid = fhistoffset = lasttyping = 0;
116 finlist = true;
117 modified = usepgpkey = false;
118 cdesc = contactroot;
120 binfo = basicinfo();
121 minfo = moreinfo();
122 winfo = workinfo();
123 rinfo = reginfo();
125 interests.clear();
126 background.clear();
128 nick = about = dispnick = postponed = lastip = "";
129 lastseen = onlinesince = 0;
132 void icqcontact::save() {
133 ofstream f;
135 if(cdesc == contactroot)
136 return;
138 string lrname = getdirname() + "lastread";
139 string infoname = getdirname() + "info";
140 string aboutname = getdirname() + "about";
141 string postponedname = getdirname() + "postponed";
143 string dname = getdirname();
144 dname.erase(dname.size()-1);
146 modified = modified
147 || access(lrname.c_str(), F_OK)
148 || access(infoname.c_str(), F_OK)
149 || access(aboutname.c_str(), F_OK);
151 if(modified && conf->enoughdiskspace()) {
152 mkdir(dname.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
154 if(!access(getdirname().c_str(), W_OK)) {
155 f.open(lrname.c_str());
156 if(f.is_open()) {
157 f << lastread << endl;
158 f.close();
159 f.clear();
162 f.open(infoname.c_str());
163 if(f.is_open()) {
164 string options(""); /* set initial value */
165 if(binfo.requiresauth) options += "a";
166 if(binfo.authawait) options += "w";
167 if(usepgpkey) options += "p";
169 f << nick << endl <<
170 tosane(binfo.fname) << endl <<
171 tosane(binfo.lname) << endl <<
172 tosane(binfo.email) << endl <<
173 options << endl <<
174 pgpkey << endl <<
175 tosane(binfo.city) << endl <<
176 tosane(binfo.state) << endl <<
177 tosane(binfo.phone) << endl <<
178 tosane(binfo.fax) << endl <<
179 tosane(binfo.street) << endl <<
180 tosane(binfo.cellular) << endl <<
181 tosane(binfo.zip) << endl <<
182 binfo.country << endl <<
183 tosane(winfo.city) << endl <<
184 tosane(winfo.state) << endl <<
185 tosane(winfo.phone) << endl <<
186 tosane(winfo.fax) << endl <<
187 tosane(winfo.street) << endl <<
188 tosane(winfo.zip) << endl <<
189 winfo.country << endl <<
190 tosane(winfo.company) << endl <<
191 tosane(winfo.dept) << endl <<
192 tosane(winfo.position) << endl <<
193 (int) minfo.timezone << endl <<
194 tosane(winfo.homepage) << endl <<
195 (int) minfo.age << endl <<
196 (int) minfo.gender << endl <<
197 tosane(minfo.homepage) << endl <<
198 minfo.lang1 << endl <<
199 minfo.lang2 << endl <<
200 minfo.lang3 << endl <<
201 minfo.birth_day << endl <<
202 minfo.birth_month << endl <<
203 minfo.birth_year << endl <<
204 (interests.size() > 0 ? *(interests.begin()+0) : "") << endl <<
205 (interests.size() > 1 ? *(interests.begin()+1) : "") << endl <<
206 (interests.size() > 2 ? *(interests.begin()+2) : "") << endl <<
207 (interests.size() > 3 ? *(interests.begin()+3) : "") << endl <<
208 (background.size() > 0 ? *(background.begin()+0) : "") << endl <<
209 (background.size() > 1 ? *(background.begin()+1) : "") << endl <<
210 (background.size() > 2 ? *(background.begin()+2) : "") << endl <<
211 minfo.checkfreq << endl <<
212 minfo.checklast << endl <<
213 lastip << endl <<
214 tosane(dispnick) << endl <<
215 lastseen << endl <<
216 endl <<
217 endl <<
218 endl <<
219 endl <<
220 groupid << endl <<
221 tosane(binfo.avatar) << endl;
223 f.close();
224 f.clear();
227 f.open(aboutname.c_str());
228 if(f.is_open()) {
229 f << about;
230 f.close();
231 f.clear();
234 if(!postponed.empty()) {
235 f.open(postponedname.c_str());
236 if(f.is_open()) {
237 f << postponed;
238 f.close();
239 f.clear();
241 } else {
242 unlink(postponedname.c_str());
245 if(!finlist) {
246 f.open((getdirname() + "excluded").c_str());
247 if(f.is_open()) f.close();
251 modified = false;
255 void icqcontact::load() {
256 int i;
257 ifstream f;
258 struct stat st;
259 string tname = getdirname(), fn, buf;
261 imcontact savedesc = cdesc;
262 clear();
263 cdesc = savedesc;
265 f.clear();
266 f.open((fn = tname + "/info").c_str());
268 if(f.is_open()) {
269 for(i = 0; getstring(f, buf); i++) {
270 switch(i) {
271 case 0: nick = buf; break;
272 case 1: binfo.fname = buf; break;
273 case 2: binfo.lname = buf; break;
274 case 3: binfo.email = buf; break;
275 case 4:
276 binfo.requiresauth = (buf.find('a') != -1);
277 binfo.authawait = (buf.find('w') != -1);
278 usepgpkey = (buf.find('p') != -1);
279 break;
280 case 5: pgpkey = buf; break;
281 case 6: binfo.city = buf; break;
282 case 7: binfo.state = buf; break;
283 case 8: binfo.phone = buf; break;
284 case 9: binfo.fax = buf; break;
285 case 10: binfo.street = buf; break;
286 case 11: binfo.cellular = buf; break;
287 case 12: binfo.zip = buf; break;
288 case 13: binfo.country = strtoul(buf.c_str(), 0, 0); break;
289 case 14: winfo.city = buf; break;
290 case 15: winfo.state = buf; break;
291 case 16: winfo.phone = buf; break;
292 case 17: winfo.fax = buf; break;
293 case 18: winfo.street = buf; break;
294 case 19: winfo.zip = buf; break;
295 case 20: winfo.country = strtoul(buf.c_str(), 0, 0); break;
296 case 21: winfo.company = buf; break;
297 case 22: winfo.dept = buf; break;
298 case 23: winfo.position = buf; break;
299 case 24: minfo.timezone = atoi(buf.c_str()); break;
300 case 25: winfo.homepage = buf; break;
301 case 26: minfo.age = atoi(buf.c_str()); break;
302 case 27: minfo.gender = (imgender) atoi(buf.c_str()); break;
303 case 28: minfo.homepage = buf; break;
304 case 29: minfo.lang1 = strtoul(buf.c_str(), 0, 0); break;
305 case 30: minfo.lang2 = strtoul(buf.c_str(), 0, 0); break;
306 case 31: minfo.lang3 = strtoul(buf.c_str(), 0, 0); break;
307 case 32: minfo.birth_day = atoi(buf.c_str()); break;
308 case 33: minfo.birth_month = atoi(buf.c_str()); break;
309 case 34: minfo.birth_year = atoi(buf.c_str()); break;
310 case 35: if(!buf.empty()) interests.push_back(buf); break;
311 case 36: if(!buf.empty()) interests.push_back(buf); break;
312 case 37: if(!buf.empty()) interests.push_back(buf); break;
313 case 38: if(!buf.empty()) interests.push_back(buf); break;
314 case 39: if(!buf.empty()) background.push_back(buf); break;
315 case 40: if(!buf.empty()) background.push_back(buf); break;
316 case 41: if(!buf.empty()) background.push_back(buf); break;
317 case 42: minfo.checkfreq = atoi(buf.c_str()); break;
318 case 43: minfo.checklast = atoi(buf.c_str()); break;
319 case 44: lastip = buf; break;
320 case 45: dispnick = buf; break;
321 case 46: lastseen = strtoul(buf.c_str(), 0, 0); break;
322 case 47: break;
323 case 48: break;
324 case 49: break;
325 case 50: break;
326 case 51: groupid = atoi(buf.c_str()); break;
327 case 52: binfo.avatar = buf; break;
330 f.close();
332 } else {
333 if(cdesc.uin)
334 nick = i2str(cdesc.uin);
338 f.clear();
339 f.open((fn = tname + "/about").c_str());
341 if(f.is_open()) {
342 while(getstring(f, buf)) {
343 if(about.size()) about += '\n';
344 about += buf;
346 f.close();
349 f.clear();
350 f.open((fn = tname + "/postponed").c_str());
352 if(f.is_open()) {
353 while(getstring(f, buf)) {
354 if(postponed.size()) postponed += '\n';
355 postponed += buf;
357 f.close();
360 f.clear();
361 f.open((fn = tname + "/lastread").c_str());
363 if(f.is_open()) {
364 getstring(f, buf);
365 lastread = strtoul(buf.c_str(), 0, 0);
366 f.close();
369 finlist = stat((fn = tname + "/excluded").c_str(), &st);
371 if(!isbirthday())
372 unlink((tname + "/congratulated").c_str());
374 if(nick.empty())
375 nick = cdesc.nickname;
377 if(dispnick.empty())
378 dispnick = nick;
380 if(conf->getgroupmode() != icqconf::nogroups)
381 if(find(groups.begin(), groups.end(), groupid) == groups.end()) {
382 groupid = 1;
386 bool icqcontact::isbirthday() const {
387 bool ret = false;
388 time_t curtime = time(0);
389 struct tm tbd, *tcur = localtime(&curtime);
391 memset(&tbd, 0, sizeof(tbd));
393 tbd.tm_year = tcur->tm_year;
394 tbd.tm_mday = minfo.birth_day;
395 tbd.tm_mon = minfo.birth_month-1;
397 if(tbd.tm_mday == tcur->tm_mday)
398 if(tbd.tm_mon == tcur->tm_mon) {
399 ret = true;
402 return ret;
405 void icqcontact::remove() {
406 string dname = getdirname(), fname;
407 struct dirent *e;
408 struct stat st;
409 DIR *d;
411 gethook(cdesc.pname).removeuser(cdesc);
413 if(d = opendir(dname.c_str())) {
414 while(e = readdir(d)) {
415 fname = dname + "/" + e->d_name;
416 if(!stat(fname.c_str(), &st) && !S_ISDIR(st.st_mode))
417 unlink(fname.c_str());
419 closedir(d);
420 rmdir(dname.c_str());
424 void icqcontact::excludefromlist() {
425 FILE *f;
426 string fname = getdirname() + "excluded";
427 if(f = fopen(fname.c_str(), "w")) fclose(f);
428 finlist = false;
431 void icqcontact::includeintolist(int agroupid, bool reqauth) {
432 binfo.requiresauth = binfo.authawait = reqauth;
433 if(groupid) groupid = agroupid;
435 includeintolist();
438 void icqcontact::includeintolist() {
439 if(cdesc.pname != icq)
440 status = offline;
442 if(!ischannel(cdesc))
443 gethook(cdesc.pname).sendnewuser(cdesc);
445 unlink((getdirname() + "excluded").c_str());
446 finlist = true;
449 bool icqcontact::inlist() const {
450 return finlist;
453 void icqcontact::scanhistory() {
454 string fn = getdirname() + "history", block;
455 char buf[65];
456 int pos, backstep, r;
457 FILE *f = fopen(fn.c_str(), "r");
458 struct stat st;
460 sethasevents(false);
461 stat(fn.c_str(), &st);
463 if(f) {
464 fseek(f, 0, SEEK_END);
465 sethistoffset(0);
466 pos = 0;
468 while(ftell(f)) {
469 backstep = 64;
470 if(ftell(f) < backstep) backstep = ftell(f);
472 if(fseek(f, -backstep, SEEK_CUR)) break;
473 if((r = fread(buf, 1, backstep, f)) <= 0) break;
474 if(fseek(f, -backstep, SEEK_CUR)) break;
476 buf[r] = 0;
477 block.insert(0, buf);
479 if((r = block.find("\f\nIN\n")) != -1) {
480 pos = st.st_size-block.size()+r;
482 block.erase(0, r+2);
484 #ifdef HAVE_SSTREAM
485 ostringstream oevdata;
486 oevdata << block;
487 istringstream evdata(oevdata.str());
488 #else
489 strstream evdata;
490 evdata << block;
491 #endif
493 for(r = 0; (r < 3) && getline(evdata, block); r++);
494 if(r == 3) sethasevents(strtoul(block.c_str(), 0, 0) > lastread);
495 break;
499 fclose(f);
500 if(!gethistoffset()) sethistoffset(pos);
504 void icqcontact::setstatus(imstatus fstatus, bool reflect) {
505 if(status != fstatus) {
506 if(!ischannel(cdesc)) {
507 if(fstatus != offline && status == offline
508 || fstatus == offline && status != offline) {
509 imevent::imeventtype et =
510 status == offline ? imevent::online : imevent::offline;
512 external.exec(imrawevent(et, cdesc, imevent::incoming));
513 playsound(et);
516 } else if(reflect) {
517 abstracthook &h = gethook(cdesc.pname);
518 if(fstatus == offline) h.removeuser(cdesc);
519 else h.sendnewuser(cdesc);
523 if (fstatus == offline && status != offline) {
524 setonlinesince(0);
526 else if (fstatus != offline && status == offline) {
527 setonlinesince(time(NULL));
530 setlastseen();
531 status = fstatus;
532 face.relaxedupdate();
536 void icqcontact::setdesc(const imcontact &ic) {
537 string dir = getdirname();
538 cdesc = ic;
539 rename(dir.c_str(), getdirname().c_str());
542 void icqcontact::setnick(const string &fnick) {
543 nick = fnick;
544 modified = true;
545 fupdated++;
548 void icqcontact::setdispnick(const string &fnick) {
549 dispnick = fnick;
550 modified = true;
553 void icqcontact::setlastread(time_t flastread) {
554 lastread = flastread;
555 scanhistory();
556 modified = true;
559 void icqcontact::unsetupdated() {
560 fupdated = 0;
563 void icqcontact::setbasicinfo(const basicinfo &ainfo) {
564 binfo = ainfo;
565 fupdated++;
566 modified = true;
569 void icqcontact::setmoreinfo(const moreinfo &ainfo) {
570 minfo = ainfo;
571 fupdated++;
572 modified = true;
575 void icqcontact::setworkinfo(const workinfo &ainfo) {
576 winfo = ainfo;
577 fupdated++;
578 modified = true;
581 void icqcontact::setreginfo(const reginfo &arinfo) {
582 rinfo = arinfo;
585 void icqcontact::setinterests(const vector<string> &ainterests) {
586 interests = ainterests;
587 fupdated++;
588 modified = true;
591 void icqcontact::setbackground(const vector<string> &abackground) {
592 background = abackground;
593 fupdated++;
594 modified = true;
597 void icqcontact::setabout(const string &data) {
598 about = data;
599 fupdated++;
600 modified = true;
603 void icqcontact::setsound(imevent::imeventtype event, const string &sf) {
604 sound[event] = sf;
607 void icqcontact::playsound(imevent::imeventtype event) const {
608 string sf = sound[event], cline;
609 int i;
611 if(sf.size()) {
612 if(sf[0] == '!') {
613 static time_t lastmelody = 0;
615 if(time(0)-lastmelody < 5) return;
616 time(&lastmelody);
618 if(sf.substr(1) == "spk1") {
619 for(i = 0; i < 3; i++) {
620 if(i) usleep(90000);
621 setbeep((i+1)*100, 60);
622 printf("\a");
623 fflush(stdout);
625 } else if(sf.substr(1) == "spk2") {
626 for(i = 0; i < 2; i++) {
627 if(i) usleep(90000);
628 setbeep((i+1)*300, 60);
629 printf("\a");
630 fflush(stdout);
632 } else if(sf.substr(1) == "spk3") {
633 for(i = 3; i > 0; i--) {
634 setbeep((i+1)*200, 60-i*10);
635 printf("\a");
636 fflush(stdout);
637 usleep(90000-i*10000);
639 } else if(sf.substr(1) == "spk4") {
640 for(i = 0; i < 4; i++) {
641 setbeep((i+1)*400, 60);
642 printf("\a");
643 fflush(stdout);
644 usleep(90000);
646 } else if(sf.substr(1) == "spk5") {
647 for(i = 0; i < 4; i++) {
648 setbeep((i+1)*250, 60+i);
649 printf("\a");
650 fflush(stdout);
651 usleep(90000-i*5000);
653 } else if(sf.substr(1) == "spk6") {
654 printf("\a");
656 } else {
657 static int pid = 0;
659 if(pid) kill(pid, SIGKILL);
660 pid = fork();
661 if(!pid) {
662 string cline = sf + " >/dev/null 2>&1";
663 execlp("/bin/sh", "/bin/sh", "-c", cline.c_str(), (char *)NULL );
664 exit(0);
667 } else if(cdesc != contactroot) {
668 icqcontact *c = clist.get(contactroot);
669 c->playsound(event);
673 void icqcontact::setlastip(const string &flastip) {
674 lastip = flastip;
675 fupdated++;
676 modified = true;
679 string icqcontact::getpgpkey() const {
680 return pgpkey;
683 void icqcontact::setpgpkey(const string &key) {
684 pgpkey = key;
685 usepgpkey = modified = true;
686 fupdated++;
689 bool icqcontact::getusepgpkey() const {
690 return usepgpkey;
693 void icqcontact::setusepgpkey(bool usekey) {
694 usepgpkey = usekey;
695 modified = true;
698 string icqcontact::getabout() const {
699 return about;
702 string icqcontact::getlastip() const {
703 return lastip;
706 time_t icqcontact::getlastread() const {
707 return lastread;
710 imstatus icqcontact::getstatus() const {
711 if(conf->getnonimonline(cdesc.pname)) return available;
712 return status;
715 string icqcontact::getnick() const {
716 return nick;
719 string icqcontact::getdispnick() const {
720 return dispnick;
723 int icqcontact::updated() const {
724 return fupdated;
727 void icqcontact::setlastseen() {
728 time(&lastseen);
729 fupdated++;
730 modified = true;
733 time_t icqcontact::getlastseen() const {
734 return lastseen;
737 void icqcontact::setidlefor(unsigned int idle) {
738 if (idle > 0)
740 time(&idlefor);
741 idlefor -= (idle * 60);
743 else
744 idlefor = 0;
745 modified = true;
748 time_t icqcontact::getidlefor() const {
749 return idlefor;
752 char icqcontact::getshortstatus() const {
753 if(status >= offline && status < imstatus_size) {
754 return imstatus2char[getstatus()];
755 } else {
756 return imstatus2char[offline];
760 bool icqcontact::operator > (const icqcontact &acontact) const {
761 if(lastread != acontact.lastread) {
762 return lastread < acontact.lastread;
763 } else if(cdesc.uin != acontact.cdesc.uin) {
764 return cdesc.uin > acontact.cdesc.uin;
765 } else {
766 return cdesc.nickname.compare(acontact.cdesc.nickname);
770 // this is the bizaare compare by last read, then uin, then nickname
771 int icqcontact::compare1(const icqcontact& a, const icqcontact& b){
772 return a > b ? 1 : -1;
775 // this is the compare by nickname
776 int icqcontact::compare2(const icqcontact& a, const icqcontact& b){
777 return a.getdispnick().compare(b.getdispnick());
780 void icqcontact::setpostponed(const string &apostponed) {
781 if(apostponed.find_first_not_of(" \r\n") != -1) postponed = apostponed;
782 else postponed = "";
784 modified = true;
787 string icqcontact::getpostponed() const {
788 return postponed;
791 void icqcontact::setgroupid(int agroupid, bool reflect) {
792 groupid = agroupid;
793 modified = true;
795 if(reflect)
796 gethook(cdesc.pname).updatecontact(this);
799 int icqcontact::getgroupid() const {
800 return groupid;
803 const imcontact icqcontact::getdesc() const {
804 return cdesc;
807 int icqcontact::gethistoffset() const {
808 return fhistoffset;
811 void icqcontact::sethistoffset(int aoffset) {
812 fhistoffset = aoffset;
815 void icqcontact::remindbirthday(bool r) {
816 string tname = getdirname() + "congratulated";
818 if(!congratulated && r) {
819 congratulated = !access(tname.c_str(), F_OK);
821 if(!congratulated) {
822 ofstream f(tname.c_str());
823 if(f.is_open()) f.close(), f.clear();
825 em.store(imnotification(getdesc(), _("The user has a birthday today")));
826 congratulated = true;
829 } else if(congratulated && !r) {
830 congratulated = false;
831 unlink(tname.c_str());
836 // ----------------------------------------------------------------------------
838 string icqcontact::moreinfo::strtimezone() const {
839 string r;
841 if(timezone <= 24 && timezone >= -24) {
842 r = abstracthook::getTimezoneIDtoString(timezone) + ", " +
843 abstracthook::getTimezonetoLocaltime(timezone);
846 return r;
849 string icqcontact::moreinfo::strbirthdate() const {
850 string r;
852 static const string smonths[12] = {
853 _("Jan"), _("Feb"), _("Mar"), _("Apr"),
854 _("May"), _("Jun"), _("Jul"), _("Aug"),
855 _("Sep"), _("Oct"), _("Nov"), _("Dec")
858 if((birth_day > 0) && (birth_day <= 31))
859 if((birth_month > 0) && (birth_month <= 12)) {
860 r = i2str(birth_day) + "-" + smonths[birth_month-1] + "-" + i2str(birth_year);
863 return r;