From b61ba851da0157ace3bdfc1ebbf87156b0b76413 Mon Sep 17 00:00:00 2001 From: Kirill Volinsky Date: Wed, 6 Jun 2012 08:58:27 +0000 Subject: protocols plugins moved to protocols git-svn-id: http://svn.miranda-ng.org/main/trunk@327 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/Twitter/connection.cpp | 435 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 protocols/Twitter/connection.cpp (limited to 'protocols/Twitter/connection.cpp') diff --git a/protocols/Twitter/connection.cpp b/protocols/Twitter/connection.cpp new file mode 100644 index 0000000000..83660a8256 --- /dev/null +++ b/protocols/Twitter/connection.cpp @@ -0,0 +1,435 @@ +/* +Copyright © 2009 Jim Porter + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "common.h" +#include "proto.h" + +void CALLBACK TwitterProto::APC_callback(ULONG_PTR p) +{ + reinterpret_cast(p)->LOG("***** Executing APC"); +} + +template +inline static T db_pod_get(HANDLE hContact, const char *module, const char *setting, + T errorValue) +{ + DBVARIANT dbv; + DBCONTACTGETSETTING cgs; + + cgs.szModule = module; + cgs.szSetting = setting; + cgs.pValue = &dbv; + if (CallService(MS_DB_CONTACT_GETSETTING, (WPARAM)hContact, (LPARAM)&cgs)) + return errorValue; + + // TODO: remove this, it's just a temporary workaround + if (dbv.type == DBVT_DWORD) + return dbv.dVal; + + if (dbv.cpbVal != sizeof(T)) + return errorValue; + return *reinterpret_cast(dbv.pbVal); +} + +template +inline static INT_PTR db_pod_set(HANDLE hContact, const char *module, const char *setting, + T val) +{ + DBCONTACTWRITESETTING cws; + + cws.szModule = module; + cws.szSetting = setting; + cws.value.type = DBVT_BLOB; + cws.value.cpbVal = sizeof(T); + cws.value.pbVal = reinterpret_cast(&val); + return CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)hContact, (LPARAM)&cws); +} + +void TwitterProto::SignOn(void*) +{ + LOG("***** Beginning SignOn process"); + WaitForSingleObject(&signon_lock_, INFINITE); + + // Kill the old thread if it's still around + if (hMsgLoop_) + { + LOG("***** Requesting MessageLoop to exit"); + QueueUserAPC(APC_callback, hMsgLoop_, (ULONG_PTR)this); + LOG("***** Waiting for old MessageLoop to exit"); + WaitForSingleObject(hMsgLoop_, INFINITE); + CloseHandle(hMsgLoop_); + } + if (NegotiateConnection()) // Could this be? The legendary Go Time?? + { + if (!in_chat_ && db_byte_get(0, m_szModuleName, TWITTER_KEY_CHATFEED, 0)) + OnJoinChat(0, true); + + SetAllContactStatuses(ID_STATUS_ONLINE); + hMsgLoop_ = ForkThreadEx(&TwitterProto::MessageLoop, this); + } + + ReleaseMutex(signon_lock_); + LOG("***** SignOn complete"); +} + +bool TwitterProto::NegotiateConnection() +{ + LOG("***** Negotiating connection with Twitter"); + + int old_status = m_iStatus; + std::string user, pass; + DBVARIANT dbv; + + if ( !DBGetContactSettingString(0, m_szModuleName, TWITTER_KEY_UN, &dbv)) { + user = dbv.pszVal; + DBFreeVariant(&dbv); + } + else { + ShowPopup(TranslateT("Please enter a username.")); + return false; + } + + if ( !DBGetContactSettingString(0, m_szModuleName, TWITTER_KEY_PASS, &dbv)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal)+1, + reinterpret_cast(dbv.pszVal)); + pass = dbv.pszVal; + DBFreeVariant(&dbv); + } + else { + ShowPopup(TranslateT("Please enter a password.")); + return false; + } + + if ( !DBGetContactSettingString(0, m_szModuleName, TWITTER_KEY_BASEURL, &dbv)) { + ScopedLock s(twitter_lock_); + twit_.set_base_url(dbv.pszVal); + DBFreeVariant(&dbv); + } + + bool success; + { + ScopedLock s(twitter_lock_); + success = twit_.set_credentials(user, pass); + } + + if (!success) { + ShowPopup(TranslateT("Your username/password combination was incorrect.")); + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_FAILED, + (HANDLE)old_status, m_iStatus); + + // Set to offline + old_status = m_iStatus; + m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, + (HANDLE)old_status, m_iStatus); + + return false; + } + + m_iStatus = m_iDesiredStatus; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus); + return true; +} + + +void TwitterProto::MessageLoop(void*) +{ + LOG("***** Entering Twitter::MessageLoop"); + + since_id_ = db_pod_get(0, m_szModuleName, TWITTER_KEY_SINCEID, 0); + dm_since_id_ = db_pod_get(0, m_szModuleName, TWITTER_KEY_DMSINCEID, 0); + + bool new_account = db_byte_get(0, m_szModuleName, TWITTER_KEY_NEW, 1) != 0; + bool popups = db_byte_get(0, m_szModuleName, TWITTER_KEY_POPUP_SIGNON, 1) != 0; + + int poll_rate = db_dword_get(0, m_szModuleName, TWITTER_KEY_POLLRATE, 80); + + for(unsigned int i=0;;i++) + { + if (m_iStatus != ID_STATUS_ONLINE) + goto exit; + if (i%4 == 0) + UpdateFriends(); + + if (m_iStatus != ID_STATUS_ONLINE) + goto exit; + UpdateStatuses(new_account, popups); + + if (m_iStatus != ID_STATUS_ONLINE) + goto exit; + UpdateMessages(new_account); + + if (new_account) // Not anymore! + { + new_account = false; + DBWriteContactSettingByte(0, m_szModuleName, TWITTER_KEY_NEW, 0); + } + + if (m_iStatus != ID_STATUS_ONLINE) + goto exit; + LOG("***** TwitterProto::MessageLoop going to sleep..."); + if (SleepEx(poll_rate*1000, true) == WAIT_IO_COMPLETION) + goto exit; + LOG("***** TwitterProto::MessageLoop waking up..."); + + popups = true; + } + +exit: + { + ScopedLock s(twitter_lock_); + twit_.set_credentials("", "", false); + } + LOG("***** Exiting TwitterProto::MessageLoop"); +} + +struct update_avatar +{ + update_avatar(HANDLE hContact, const std::string &url) : hContact(hContact), url(url) {} + HANDLE hContact; + std::string url; +}; + +void TwitterProto::UpdateAvatarWorker(void *p) +{ + if (p == 0) + return; + std::auto_ptr data( static_cast(p)); + DBVARIANT dbv; + + if (DBGetContactSettingTString(data->hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) + return; + + std::string ext = data->url.substr(data->url.rfind('.')); + std::string filename = GetAvatarFolder() + '\\' + dbv.pszVal + ext; + DBFreeVariant(&dbv); + + PROTO_AVATAR_INFORMATION ai = {sizeof(ai)}; + ai.hContact = data->hContact; + ai.format = ext_to_format(ext); + strncpy(ai.filename, filename.c_str(), MAX_PATH); + + LOG("***** Updating avatar: %s", data->url.c_str()); + WaitForSingleObjectEx(avatar_lock_, INFINITE, true); + if (CallService(MS_SYSTEM_TERMINATED, 0, 0)) + { + LOG("***** Terminating avatar update early: %s", data->url.c_str()); + return; + } + + if (save_url(hAvatarNetlib_, data->url, filename)) + { + DBWriteContactSettingString(data->hContact, m_szModuleName, TWITTER_KEY_AV_URL, + data->url.c_str()); + ProtoBroadcastAck(m_szModuleName, data->hContact, ACKTYPE_AVATAR, + ACKRESULT_SUCCESS, &ai, 0); + } + else + ProtoBroadcastAck(m_szModuleName, data->hContact, ACKTYPE_AVATAR, + ACKRESULT_FAILED, &ai, 0); + ReleaseMutex(avatar_lock_); + LOG("***** Done avatar: %s", data->url.c_str()); +} + +void TwitterProto::UpdateAvatar(HANDLE hContact, const std::string &url, bool force) +{ + DBVARIANT dbv; + + if ( !force && + ( !DBGetContactSettingString(hContact, m_szModuleName, TWITTER_KEY_AV_URL, &dbv) && + url == dbv.pszVal)) + { + LOG("***** Avatar already up-to-date: %s", url.c_str()); + } + else + { + // TODO: more defaults (configurable?) + if (url == "http://static.twitter.com/images/default_profile_normal.png") + { + PROTO_AVATAR_INFORMATION ai = {sizeof(ai), hContact}; + + db_string_set(hContact, m_szModuleName, TWITTER_KEY_AV_URL, url.c_str()); + ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_AVATAR, + ACKRESULT_SUCCESS, &ai, 0); + } + else + { + ForkThread(&TwitterProto::UpdateAvatarWorker, this, + new update_avatar(hContact, url)); + } + } + + DBFreeVariant(&dbv); +} + +void TwitterProto::UpdateFriends() +{ + try + { + ScopedLock s(twitter_lock_); + std::vector friends = twit_.get_friends(); + s.Unlock(); + + for(std::vector::iterator i=friends.begin(); i!=friends.end(); ++i) { + if (i->username == twit_.get_username()) + continue; + + HANDLE hContact = AddToClientList( _A2T(i->username.c_str()), i->status.text.c_str()); + UpdateAvatar(hContact, i->profile_image_url); + } + LOG("***** Friends list updated"); + } + catch(const bad_response &) + { + LOG("***** Bad response from server, signing off"); + SetStatus(ID_STATUS_OFFLINE); + } + catch(const std::exception &e) + { + ShowPopup( (std::string("While updating friends list, an error occurred: ") + +e.what()).c_str()); + LOG("***** Error updating friends list: %s", e.what()); + } + +} + +void TwitterProto::ShowContactPopup(HANDLE hContact, const std::tstring &text) +{ + if (!ServiceExists(MS_POPUP_ADDPOPUPT) || DBGetContactSettingByte(0, m_szModuleName, TWITTER_KEY_POPUP_SHOW, 0) == 0) + return; + + POPUPDATAT popup = {}; + popup.lchContact = hContact; + popup.iSeconds = db_dword_get(0, m_szModuleName, TWITTER_KEY_POPUP_TIMEOUT, 0); + + popup.colorBack = db_dword_get(0, m_szModuleName, TWITTER_KEY_POPUP_COLBACK, 0); + if (popup.colorBack == 0xFFFFFFFF) + popup.colorBack = GetSysColor(COLOR_WINDOW); + popup.colorText = db_dword_get(0, m_szModuleName, TWITTER_KEY_POPUP_COLTEXT, 0); + if (popup.colorBack == 0xFFFFFFFF) + popup.colorBack = GetSysColor(COLOR_WINDOWTEXT); + + DBVARIANT dbv; + if ( !DBGetContactSettingString(hContact, "CList", "MyHandle", &dbv) || + !DBGetContactSettingString(hContact, m_szModuleName, TWITTER_KEY_UN, &dbv)) + { + mbcs_to_tcs(CP_UTF8, dbv.pszVal, popup.lptzContactName, MAX_CONTACTNAME); + DBFreeVariant(&dbv); + } + + CallService(MS_POPUP_ADDPOPUPT, reinterpret_cast(text.c_str()), 0); +} + +void TwitterProto::UpdateStatuses(bool pre_read, bool popups) +{ + try + { + ScopedLock s(twitter_lock_); + twitter::status_list updates = twit_.get_statuses(200, since_id_); + s.Unlock(); + + if (!updates.empty()) + since_id_ = std::max(since_id_, updates[0].status.id); + + for(twitter::status_list::reverse_iterator i=updates.rbegin(); i!=updates.rend(); ++i) + { + if (!pre_read && in_chat_) + UpdateChat(*i); + + if (i->username == twit_.get_username()) + continue; + + HANDLE hContact = AddToClientList( _A2T(i->username.c_str()), _T("")); + + DBEVENTINFO dbei = {sizeof(dbei)}; + + dbei.pBlob = (BYTE*)(i->status.text.c_str()); + dbei.cbBlob = i->status.text.size()+1; + dbei.eventType = TWITTER_DB_EVENT_TYPE_TWEET; + dbei.flags = DBEF_UTF; + //dbei.flags = DBEF_READ; + dbei.timestamp = static_cast(i->status.time); + dbei.szModule = m_szModuleName; + CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei); + + DBWriteContactSettingTString(hContact, "CList", "StatusMsg", i->status.text.c_str()); + + if ( !pre_read && popups ) + ShowContactPopup(hContact, i->status.text); + } + + db_pod_set(0, m_szModuleName, TWITTER_KEY_SINCEID, since_id_); + LOG("***** Status messages updated"); + } + catch(const bad_response &) + { + LOG("***** Bad response from server, signing off"); + SetStatus(ID_STATUS_OFFLINE); + } + catch(const std::exception &e) + { + ShowPopup( (std::string("While updating status messages, an error occurred: ") + +e.what()).c_str()); + LOG("***** Error updating status messages: %s", e.what()); + } +} + +void TwitterProto::UpdateMessages(bool pre_read) +{ + try + { + ScopedLock s(twitter_lock_); + twitter::status_list messages = twit_.get_direct(dm_since_id_); + s.Unlock(); + + if (messages.size()) + dm_since_id_ = std::max(dm_since_id_, messages[0].status.id); + + for(twitter::status_list::reverse_iterator i=messages.rbegin(); i!=messages.rend(); ++i) { + HANDLE hContact = AddToClientList( _A2T(i->username.c_str()), _T("")); + + PROTORECVEVENT recv = {}; + CCSDATA ccs = {}; + + recv.flags = PREF_TCHAR; + if (pre_read) + recv.flags |= PREF_CREATEREAD; + recv.szMessage = ( char* )i->status.text.c_str(); + recv.timestamp = static_cast(i->status.time); + + ccs.hContact = hContact; + ccs.szProtoService = PSR_MESSAGE; + ccs.wParam = ID_STATUS_ONLINE; + ccs.lParam = reinterpret_cast(&recv); + CallService(MS_PROTO_CHAINRECV, 0, reinterpret_cast(&ccs)); + } + + db_pod_set(0, m_szModuleName, TWITTER_KEY_DMSINCEID, dm_since_id_); + LOG("***** Direct messages updated"); + } + catch(const bad_response &) + { + LOG("***** Bad response from server, signing off"); + SetStatus(ID_STATUS_OFFLINE); + } + catch(const std::exception &e) + { + ShowPopup( (std::string("While updating direct messages, an error occurred: ") + +e.what()).c_str()); + LOG("***** Error updating direct messages: %s", e.what()); + } +} \ No newline at end of file -- cgit v1.2.3