/* * libretroshare/src/services msgservice.cc * * Services for RetroShare. * * Copyright 2004-2008 by Robert Fernie. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License Version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. * * Please report all bugs and problems to "retroshare@lunamutt.com". * */ #include "rsiface/rsiface.h" #include "pqi/pqibin.h" #include "pqi/pqiarchive.h" #include "pqi/p3connmgr.h" #include "services/p3msgservice.h" #include "pqi/pqinotify.h" #include "util/rsdebug.h" #include "util/rsdir.h" #include #include const int msgservicezone = 54319; /* Another little hack ..... unique message Ids * will be handled in this class..... * These are unique within this run of the server, * and are not stored long term.... * * Only 3 entry points: * (1) from network.... * (2) from local send * (3) from storage... */ p3MsgService::p3MsgService(p3ConnectMgr *cm) :p3Service(RS_SERVICE_TYPE_MSG), pqiConfig(CONFIG_TYPE_MSGS), mConnMgr(cm), msgChanged(1), mMsgUniqueId(1) { addSerialType(new RsMsgSerialiser()); } uint32_t p3MsgService::getNewUniqueMsgId() { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ return mMsgUniqueId++; } /****** Mods/Notifications ****/ bool p3MsgService::MsgsChanged() { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ bool m1 = msgChanged.Changed(); return (m1); } bool p3MsgService::MsgNotifications() { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ return (msgNotifications.size() > 0); } bool p3MsgService::getMessageNotifications(std::list ¬eList) { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ noteList = msgNotifications; msgNotifications.clear(); return (noteList.size() > 0); } int p3MsgService::tick() { pqioutput(PQL_DEBUG_BASIC, msgservicezone, "p3MsgService::tick()"); /* don't worry about increasing tick rate! * (handled by p3service) */ incomingMsgs(); //checkOutgoingMessages(); return 0; } int p3MsgService::status() { pqioutput(PQL_DEBUG_BASIC, msgservicezone, "p3MsgService::status()"); return 1; } int p3MsgService::incomingMsgs() { RsMsgItem *mi; int i = 0; bool changed = false ; while((mi = (RsMsgItem *) recvItem()) != NULL) { changed = true ; ++i; mi -> recvTime = time(NULL); mi -> msgFlags = RS_MSG_FLAGS_NEW; mi -> msgId = getNewUniqueMsgId(); std::string mesg; RsStackMutex stack(mMsgMtx); /*** STACK LOCKED MTX ***/ if (mi -> PeerId() == mConnMgr->getOwnId()) { /* from the loopback device */ mi -> msgFlags |= RS_MSG_FLAGS_OUTGOING; } else { /* from a peer */ MsgInfoSummary mis; initRsMIS(mi, mis); // msgNotifications.push_back(mis); pqiNotify *notify = getPqiNotify(); if (notify) { notify->AddPopupMessage(RS_POPUP_MSG, mi->PeerId(), "from: "); std::ostringstream out; out << mi->msgId; notify->AddFeedItem(RS_FEED_ITEM_MESSAGE, out.str(), "", ""); } } imsg[mi->msgId] = mi; msgChanged.IndicateChanged(); IndicateConfigChanged(); /**** INDICATE MSG CONFIG CHANGED! *****/ /**** STACK UNLOCKED ***/ } if(changed) rsicontrol->getNotify().notifyListChange(NOTIFY_LIST_MESSAGELIST,NOTIFY_TYPE_MOD); return 1; } void p3MsgService::statusChange(const std::list &plist) { /* should do it properly! */ checkOutgoingMessages(); } int p3MsgService::checkOutgoingMessages() { /* iterate through the outgoing queue * * if online, send */ bool changed = false ; { const std::string ownId = mConnMgr->getOwnId(); std::list::iterator it; std::list toErase; RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ std::map::iterator mit; for(mit = msgOutgoing.begin(); mit != msgOutgoing.end(); mit++) { if (mit->second->msgFlags & RS_MSG_FLAGS_TRASH) { continue; } /* find the certificate */ std::string pid = mit->second->PeerId(); peerConnectState pstate; bool toSend = false; if (mConnMgr->getFriendNetStatus(pid, pstate)) { if (pstate.state & RS_PEER_S_CONNECTED) { toSend = true; } } else if (pid == ownId) /* FEEDBACK Msg to Ourselves */ { toSend = true; } if (toSend) { /* send msg */ pqioutput(PQL_DEBUG_BASIC, msgservicezone, "p3MsgService::checkOutGoingMessages() Sending out message"); /* remove the pending flag */ (mit->second)->msgFlags &= ~RS_MSG_FLAGS_PENDING; sendItem(mit->second); toErase.push_back(mit->first); changed = true ; } else { pqioutput(PQL_DEBUG_BASIC, msgservicezone, "p3MsgService::checkOutGoingMessages() Delaying until available..."); } } /* clean up */ for(it = toErase.begin(); it != toErase.end(); it++) { mit = msgOutgoing.find(*it); if (mit != msgOutgoing.end()) { msgOutgoing.erase(mit); } } if (toErase.size() > 0) { IndicateConfigChanged(); /**** INDICATE MSG CONFIG CHANGED! *****/ } } if(changed) rsicontrol->getNotify().notifyListChange(NOTIFY_LIST_MESSAGELIST,NOTIFY_TYPE_MOD); return 0; } bool p3MsgService::saveConfiguration() { pqioutput(PQL_DEBUG_BASIC, msgservicezone, "p3MsgService::save_config()"); /* now we create a pqiarchive, and stream all the msgs into it */ std::string msgfile = Filename(); std::string msgfiletmp = Filename()+".tmp"; RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ RsSerialiser *rss = new RsSerialiser(); rss->addSerialType(new RsMsgSerialiser()); BinFileInterface *out = new BinFileInterface(msgfiletmp.c_str(), BIN_FLAGS_WRITEABLE | BIN_FLAGS_HASH_DATA); pqiarchive *pa_out = new pqiarchive(rss, out, BIN_FLAGS_WRITEABLE | BIN_FLAGS_NO_DELETE); bool written = true; std::map::iterator mit; for(mit = imsg.begin(); mit != imsg.end(); mit++) written = written && pa_out -> SendItem(mit->second) ; for(mit = msgOutgoing.begin(); mit != msgOutgoing.end(); mit++) written = written && pa_out -> SendItem(mit->second) ; setHash(out->gethash()); delete pa_out; if(!written) return false ; if(!RsDirUtil::renameFile(msgfiletmp,msgfile)) { getPqiNotify()->AddSysMessage(0, RS_SYS_WARNING, "File rename error", "Error while renaming file " + msgfile) ; return false ; } return true; } bool p3MsgService::loadConfiguration(std::string &loadHash) { std::string msgfile = Filename(); RsSerialiser *rss = new RsSerialiser(); rss->addSerialType(new RsMsgSerialiser()); BinFileInterface *in = new BinFileInterface(msgfile.c_str(), BIN_FLAGS_READABLE | BIN_FLAGS_HASH_DATA); pqiarchive *pa_in = new pqiarchive(rss, in, BIN_FLAGS_READABLE); RsItem *item; RsMsgItem *mitem; while((item = pa_in -> GetItem())) { if (NULL != (mitem = dynamic_cast(item))) { /* switch depending on the PENDING * flags */ /* STORE MsgID */ mitem->msgId = getNewUniqueMsgId(); if (mitem -> msgFlags & RS_MSG_FLAGS_PENDING) { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ //std::cerr << "MSG_PENDING"; //std::cerr << std::endl; //mitem->print(std::cerr); msgOutgoing[mitem->msgId] = mitem; } else { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ imsg[mitem->msgId] = mitem; } } else { delete item; } } std::string hashin = in->gethash(); delete pa_in; if (hashin != loadHash) { /* big error message! */ std::cerr << "p3MsgService::loadConfiguration() FAILED! Msgs Tampered" << std::endl; std::string msgfileold = msgfile + ".failed"; rename(msgfile.c_str(), msgfileold.c_str()); std::cerr << "Moving Old file to: " << msgfileold << std::endl; std::cerr << "removing dodgey msgs" << std::endl; RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ std::map::iterator mit; for(mit = imsg.begin(); mit != imsg.end(); mit++) { delete (mit->second); } imsg.clear(); for(mit = msgOutgoing.begin(); mit != msgOutgoing.end(); mit++) { delete (mit->second); } msgOutgoing.clear(); setHash(""); return false; } setHash(hashin); return true; } void p3MsgService::loadWelcomeMsg() { /* Load Welcome Message */ RsMsgItem *msg = new RsMsgItem(); msg -> PeerId(mConnMgr->getOwnId()); msg -> sendTime = time(NULL); msg -> subject = L"Welcome to Retroshare"; msg -> message = L"Send and receive messages\n"; msg -> message += L"with your friends...\n\n"; msg -> message += L"These can hold recommendations\n"; msg -> message += L"from your local shared files\n\n"; msg -> message += L"Add recommendations through\n"; msg -> message += L"the Local Files Dialog\n\n"; msg -> message += L"Enjoy.\n"; msg -> msgId = getNewUniqueMsgId(); RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ imsg[msg->msgId] = msg; } /***********************************************************************/ /***********************************************************************/ /***********************************************************************/ /***********************************************************************/ /****************************************/ /****************************************/ bool p3MsgService::getMessageSummaries(std::list &msgList) { /* do stuff */ msgList.clear(); RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ std::map::iterator mit; for(mit = imsg.begin(); mit != imsg.end(); mit++) { MsgInfoSummary mis; initRsMIS(mit->second, mis); msgList.push_back(mis); } for(mit = msgOutgoing.begin(); mit != msgOutgoing.end(); mit++) { MsgInfoSummary mis; initRsMIS(mit->second, mis); msgList.push_back(mis); } return true; } bool p3MsgService::getMessage(std::string mId, MessageInfo &msg) { std::map::iterator mit; uint32_t msgId = atoi(mId.c_str()); RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ mit = imsg.find(msgId); if (mit == imsg.end()) { mit = msgOutgoing.find(msgId); if (mit == msgOutgoing.end()) { return false; } } /* mit valid */ initRsMI(mit->second, msg); return true; } void p3MsgService::getMessageCount(unsigned int *pnInbox, unsigned int *pnInboxNew, unsigned int *pnOutbox, unsigned int *pnDraftbox, unsigned int *pnSentbox, unsigned int *pnTrashbox) { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ if (pnInbox) *pnInbox = 0; if (pnInboxNew) *pnInboxNew = 0; if (pnOutbox) *pnOutbox = 0; if (pnDraftbox) *pnDraftbox = 0; if (pnSentbox) *pnSentbox = 0; if (pnTrashbox) *pnTrashbox = 0; std::map::iterator mit; std::map *apMsg [2] = { &imsg, &msgOutgoing }; for (int i = 0; i < 2; i++) { for (mit = apMsg [i]->begin(); mit != apMsg [i]->end(); mit++) { MsgInfoSummary mis; initRsMIS(mit->second, mis); if (mis.msgflags & RS_MSG_TRASH) { if (pnTrashbox) (*pnTrashbox)++; continue; } switch (mis.msgflags & RS_MSG_BOXMASK) { case RS_MSG_INBOX: if (pnInbox) (*pnInbox)++; if ((mis.msgflags & RS_MSG_NEW) == RS_MSG_NEW) { if (pnInboxNew) (*pnInboxNew)++; } break; case RS_MSG_OUTBOX: if (pnOutbox) (*pnOutbox)++; break; case RS_MSG_DRAFTBOX: if (pnDraftbox) (*pnDraftbox)++; break; case RS_MSG_SENTBOX: if (pnSentbox) (*pnSentbox)++; break; } } } } /* remove based on the unique mid (stored in sid) */ bool p3MsgService::removeMsgId(std::string mid) { std::map::iterator mit; uint32_t msgId = atoi(mid.c_str()); bool changed = false ; { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ mit = imsg.find(msgId); if (mit != imsg.end()) { changed = true ; RsMsgItem *mi = mit->second; imsg.erase(mit); delete mi; // msgChanged.IndicateChanged(); IndicateConfigChanged(); /**** INDICATE MSG CONFIG CHANGED! *****/ return true; } mit = msgOutgoing.find(msgId); if (mit != msgOutgoing.end()) { changed = true ; RsMsgItem *mi = mit->second; msgOutgoing.erase(mit); delete mi; // msgChanged.IndicateChanged(); IndicateConfigChanged(); /**** INDICATE MSG CONFIG CHANGED! *****/ return true; } } if(changed) rsicontrol->getNotify().notifyListChange(NOTIFY_LIST_MESSAGELIST,NOTIFY_TYPE_MOD); return false; } bool p3MsgService::markMsgIdRead(std::string mid) { std::map::iterator mit; uint32_t msgId = atoi(mid.c_str()); bool changed = false ; { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ mit = imsg.find(msgId); if (mit != imsg.end()) { changed = true ; RsMsgItem *mi = mit->second; mi -> msgFlags &= ~(RS_MSG_FLAGS_NEW); IndicateConfigChanged(); /**** INDICATE MSG CONFIG CHANGED! *****/ } } if(changed) // no need to notify, because the msg is always return true; // marked as read from the gui itself, so the gui else // can mark it as read. return false; } /****************************************/ /****************************************/ /* Message Items */ int p3MsgService::sendMessage(RsMsgItem *item) { pqioutput(PQL_DEBUG_BASIC, msgservicezone, "p3MsgService::sendMessage()"); item -> msgId = getNewUniqueMsgId(); /* grabs Mtx as well */ { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ /* add pending flag */ item->msgFlags |= (RS_MSG_FLAGS_OUTGOING | RS_MSG_FLAGS_PENDING); /* STORE MsgID */ msgOutgoing[item->msgId] = item; } IndicateConfigChanged(); /**** INDICATE MSG CONFIG CHANGED! *****/ checkOutgoingMessages(); return 1; } bool p3MsgService::MessageSend(MessageInfo &info) { std::list::const_iterator pit; for(pit = info.msgto.begin(); pit != info.msgto.end(); pit++) { RsMsgItem *msg = initMIRsMsg(info, *pit); if (msg) { sendMessage(msg); } } for(pit = info.msgcc.begin(); pit != info.msgcc.end(); pit++) { RsMsgItem *msg = initMIRsMsg(info, *pit); if (msg) { sendMessage(msg); } } for(pit = info.msgbcc.begin(); pit != info.msgbcc.end(); pit++) { RsMsgItem *msg = initMIRsMsg(info, *pit); if (msg) { sendMessage(msg); } } /* send to ourselves as well */ RsMsgItem *msg = initMIRsMsg(info, mConnMgr->getOwnId()); if (msg) { sendMessage(msg); } return true; } bool p3MsgService::MessageToDraft(MessageInfo &info) { RsMsgItem *msg = initMIRsMsg(info, mConnMgr->getOwnId()); if (msg) { uint32_t msgId = 0; if (info.msgId.empty() == false) { msgId = atoi(info.msgId.c_str()); } if (msgId) { msg->msgId = msgId; } else { msg->msgId = getNewUniqueMsgId(); /* grabs Mtx as well */ } { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ /* add pending flag */ msg->msgFlags |= (RS_MSG_OUTGOING | RS_MSG_FLAGS_DRAFT); if (msgId) { // remove existing message std::map::iterator mit; mit = imsg.find(msgId); if (mit != imsg.end()) { RsMsgItem *mi = mit->second; imsg.erase(mit); delete mi; } } /* STORE MsgID */ imsg[msg->msgId] = msg; } IndicateConfigChanged(); /**** INDICATE MSG CONFIG CHANGED! *****/ rsicontrol->getNotify().notifyListChange(NOTIFY_LIST_MESSAGELIST,NOTIFY_TYPE_MOD); return true; } return false; } /* move message to trash based on the unique mid */ bool p3MsgService::MessageToTrash(std::string mid, bool bTrash) { std::map::iterator mit; uint32_t msgId = atoi(mid.c_str()); bool bChanged = false; bool bFound = false; { RsStackMutex stack(mMsgMtx); /********** STACK LOCKED MTX ******/ RsMsgItem *mi = NULL; mit = imsg.find(msgId); if (mit != imsg.end()) { mi = mit->second; } else { mit = msgOutgoing.find(msgId); if (mit != msgOutgoing.end()) { mi = mit->second; } } if (mi) { bFound = true; if (bTrash) { if ((mi->msgFlags & RS_MSG_FLAGS_TRASH) == 0) { mi->msgFlags |= RS_MSG_FLAGS_TRASH; bChanged = true; } } else { if (mi->msgFlags & RS_MSG_FLAGS_TRASH) { mi->msgFlags &= ~RS_MSG_FLAGS_TRASH; bChanged = true; } } } } if (bChanged) { IndicateConfigChanged(); /**** INDICATE MSG CONFIG CHANGED! *****/ checkOutgoingMessages(); rsicontrol->getNotify().notifyListChange(NOTIFY_LIST_MESSAGELIST,NOTIFY_TYPE_MOD); } return bFound; } /****************************************/ /****************************************/ /****************************************/ /**** HELPER FNS For Chat/Msg/Channel Lists ************ * These aren't required to be locked, unless * the data used is from internal stores -> then they should be. */ void p3MsgService::initRsMI(RsMsgItem *msg, MessageInfo &mi) { mi.msgflags = 0; /* translate flags, if we sent it... outgoing */ if ((msg->msgFlags & RS_MSG_FLAGS_OUTGOING) || (msg->PeerId() == mConnMgr->getOwnId())) { mi.msgflags |= RS_MSG_OUTGOING; } /* if it has a pending flag, then its in the outbox */ if (msg->msgFlags & RS_MSG_FLAGS_PENDING) { mi.msgflags |= RS_MSG_PENDING; } if (msg->msgFlags & RS_MSG_FLAGS_DRAFT) { mi.msgflags |= RS_MSG_DRAFT; } if (msg->msgFlags & RS_MSG_FLAGS_NEW) { mi.msgflags |= RS_MSG_NEW; } mi.ts = msg->sendTime; mi.srcId = msg->PeerId(); { //msg->msgId; std::ostringstream out; out << msg->msgId; mi.msgId = out.str(); } std::list::iterator pit; for(pit = msg->msgto.ids.begin(); pit != msg->msgto.ids.end(); pit++) { mi.msgto.push_back(*pit); } for(pit = msg->msgcc.ids.begin(); pit != msg->msgcc.ids.end(); pit++) { mi.msgcc.push_back(*pit); } for(pit = msg->msgbcc.ids.begin(); pit != msg->msgbcc.ids.end(); pit++) { mi.msgbcc.push_back(*pit); } mi.title = msg->subject; mi.msg = msg->message; mi.attach_title = msg->attachment.title; mi.attach_comment = msg->attachment.comment; mi.count = 0; mi.size = 0; std::list::iterator it; for(it = msg->attachment.items.begin(); it != msg->attachment.items.end(); it++) { FileInfo fi; fi.fname = RsDirUtil::getTopDir(it->name); fi.size = it->filesize; fi.hash = it->hash; fi.path = it->path; mi.files.push_back(fi); mi.count++; mi.size += fi.size; } } void p3MsgService::initRsMIS(RsMsgItem *msg, MsgInfoSummary &mis) { mis.msgflags = 0; /* translate flags, if we sent it... outgoing */ if ((msg->msgFlags & RS_MSG_FLAGS_OUTGOING) || (msg->PeerId() == mConnMgr->getOwnId())) { mis.msgflags |= RS_MSG_OUTGOING; } /* if it has a pending flag, then its in the outbox */ if (msg->msgFlags & RS_MSG_FLAGS_PENDING) { mis.msgflags |= RS_MSG_PENDING; } if (msg->msgFlags & RS_MSG_FLAGS_DRAFT) { mis.msgflags |= RS_MSG_DRAFT; } if (msg->msgFlags & RS_MSG_FLAGS_NEW) { mis.msgflags |= RS_MSG_NEW; } if (msg->msgFlags & RS_MSG_FLAGS_TRASH) { mis.msgflags |= RS_MSG_TRASH; } mis.srcId = msg->PeerId(); { //msg->msgId; std::ostringstream out; out << msg->msgId; mis.msgId = out.str(); } mis.title = msg->subject; mis.count = msg->attachment.items.size(); mis.ts = msg->sendTime; } RsMsgItem *p3MsgService::initMIRsMsg(MessageInfo &info, std::string to) { RsMsgItem *msg = new RsMsgItem(); msg -> PeerId(to); msg -> msgFlags = 0; msg -> msgId = 0; msg -> sendTime = time(NULL); msg -> recvTime = 0; msg -> subject = info.title; msg -> message = info.msg; std::list::iterator pit; for(pit = info.msgto.begin(); pit != info.msgto.end(); pit++) { msg -> msgto.ids.push_back(*pit); } for(pit = info.msgcc.begin(); pit != info.msgcc.end(); pit++) { msg -> msgcc.ids.push_back(*pit); } /* We don't fill in bcc (unless to ourselves) */ if (to == mConnMgr->getOwnId()) { for(pit = info.msgbcc.begin(); pit != info.msgbcc.end(); pit++) { msg -> msgbcc.ids.push_back(*pit); } } msg -> attachment.title = info.attach_title; msg -> attachment.comment = info.attach_comment; std::list::iterator it; for(it = info.files.begin(); it != info.files.end(); it++) { RsTlvFileItem mfi; mfi.hash = it -> hash; mfi.name = it -> fname; mfi.filesize = it -> size; msg -> attachment.items.push_back(mfi); } //std::cerr << "p3MsgService::initMIRsMsg()" << std::endl; //msg->print(std::cerr); return msg; }