#include #include #include "../shared/MiscSupport.h" #include "../shared/ReplyToClient.h" #include "../shared/XmlSupport.h" #include "../shared/LogFile.h" #include "UserInfo.h" #include "../shared/MicroSleep.h" #include "../shared/CommandDispatcher.h" #include "Types.h" #include "AlertConfig.h" #include "CloudSupport.h" #include "SymbolLists.h" /* SymbolLists.C and MiscRODatabase.C each handle a lot of miscelaneous * requests. SymbolLists.C gives access to the master database, and all * requests are subject to the tar pit. MiscRODatabase.C only has access to * a read only database, so it doesn't need the tar pit. */ static void sendNullResponse(ExternalRequest *request) { ExternalRequest::MessageId messageId = request->getResponseMessageId(); if (messageId.present()) { addToOutputQueue(request->getSocketInfo(), XmlNode().asString("API"), messageId); } } enum { mtQuit, mtUpdateList, /* This can do several miscellaneious things, all related to * symbol lists. */ mtGetListOfLists, /* Symbol lists. This doesn't require the master * database. But it is nice to have all the symbol * list code in one file. And the client caches this * so it shouldn't be an issue. Also, the client * requests this in the background, not in a modal * dialog box. */ mtDeleteAllLists, /* Symbol lists. */ mtUserStrategyList, /* Currently only used by the old E*TRADE code. This * doesn't directly need the master database, but I * wanted it int he same thread as mtUserStrategySave. * When you load a strategy, it should be able to * access recently saved strategies. */ mtUserStrategyDelete, /* Currently only used by the old E*TRADE code. */ mtUserStrategySave, /* Currently only used by the old E*TRADE code. */ mtSaveToCloud, /* Save a window or layout. */ mtCloudDelete, /* Completely remove an item. */ mtCloudRevoke, /* Generate a new code for an item. Note that we handle * the cloud requests differently from symbol list and * strategy requests. In this case the read and write * requests are in two seperate threads. We probably * won't see old data on a read because some of these * requests (save at least) bring up a modal dialog box * asking you to wait. In any case, it's worth the * possible problems because we want the load from cloud * stuff to be as fast as possible. */ }; ///////////////////////////////////////////////////////////////////// // ListRequest ///////////////////////////////////////////////////////////////////// class ListRequest : public Request { protected: const UserId _userId; const ExternalRequest::MessageId _returnMessageId; public: ListRequest(UserId userId, ExternalRequest *original) : Request(original->getSocketInfo()), _userId(userId), _returnMessageId(original->getResponseMessageId()) { callbackId = original->callbackId; } virtual void doRequest(DatabaseWithRetry &database) =0; }; ///////////////////////////////////////////////////////////////////// // UpdateListRequest ///////////////////////////////////////////////////////////////////// class UpdateListRequest : public ListRequest { private: const std::string _id; const std::string _name; const std::string _add; const std::string _delete; const bool _deleteList; const bool _deleteAll; const bool _requestList; public: UpdateListRequest(UserId userId, ExternalRequest *original) : ListRequest(userId, original), _id(original->getProperty("id")), _name(original->getProperty("name")), _add(original->getProperty("add")), _delete(original->getProperty("delete")), _deleteList(original->getProperty("delete_list") == "1"), _deleteAll(original->getProperty("delete_all") == "1"), _requestList(original->getProperty("request_list") == "1") { } void doRequest(DatabaseWithRetry &database); bool incurPenalty() const; }; bool UpdateListRequest::incurPenalty() const { // Ideally the requests to read the symbol lists will be fast. Unfortunately // the system wasn't designed to seperate reads and writes very well. In // fact, every request to read the contents of a list will send a message // to the database trying to rename the list. If we see what looks like an // attempt to read the list and nothing else, we will ignore the fact that // we're changing the name and we will pretent that this is only a read // request. // // This assumption is probably not that unreasonable. When the system was // being overloaded, I often saw it adding or deleting symbols. I never // saw it renaming a list. Presumably that's very fast, especially when // we are renaming it to what it already is. // // I suppose if someone knew about this issue they could purposely overwhealm // us with read / rename requests. However, if someone really was malicious // and had access to our source code, they could probably find worse ways // to cause trouble. return _deleteList || _deleteAll || (!_add.empty()) || (!_delete.empty()) || (!_requestList); } static std::string cleanSymbol(std::string symbol) { symbol = trim(symbol); if (!symbol.empty()) { // When scottrade sends us a symbol list, the symbols have a period at // the end. We need to strip it off. if (*symbol.rbegin() == '.') symbol.resize(symbol.size() - 1); } return symbol; } void UpdateListRequest::doRequest(DatabaseWithRetry &database) { //if ($list_id <= 0) // { // AX_errorNoRetry("Invalid list id number!"); // } if (strtolDefault(_id, 0) == 0) { // The client should never send this. A bad value can screw up the // database. The empty string, in particular, will not have the // expected result. if (_returnMessageId.present()) { XmlNode message; message["INVALID_LIST_ID"].text = _id; addToOutputQueue(getSocketInfo(), message.asString("API"), _returnMessageId); } TclList logMsg; logMsg<<"SymbolLists.C" <<"INVALID_LIST_ID" <<_id; LogFile::primary().sendString(logMsg, getSocketInfo()); return; } const std::string safeListId = "'" + mysqlEscapeString(_id) + "'"; const std::string userIdString = ntoa(_userId); ThreadMonitor::find().increment("UpdateListRequest"); // These messages made up the bulk of the messages in the log file. We // know that QuoteTracker causes most of these. It will refresh all of the // lists from scratch when even one symbol changes. It gets espicially // bad when someone automatically add stocks to the portfolio, possibly // based on the result of TI alerts. //TclList logMsg; //logMsg<<"SymbolLists.C" //<<"UpdateListRequest" //<<"UserId" //<<_userId //<<"ListId" //<<_id; std::vector< std::string > sql; sql.push_back("BEGIN"); // Use list 0 to lock the lists for this user. sql.push_back("REPLACE INTO symbol_lists VALUES ('mutex', " + userIdString + ", 0)"); sql.push_back("DELETE FROM symbol_lists WHERE user_id=" + userIdString + " AND id=0"); if (_deleteList) { ThreadMonitor::find().increment("delete_list"); //logMsg<<"delete"<<"list"; sql.push_back("DELETE FROM symbols_in_lists WHERE user_id = " + userIdString + " AND list_id = " + safeListId); sql.push_back("DELETE FROM symbol_lists WHERE user_id = " + userIdString + " AND id = " + safeListId); } else { sql.push_back("REPLACE symbol_lists VALUES ('" + mysqlEscapeString(_name) + "', " + userIdString + ", " + safeListId + ")"); if (_deleteAll) { ThreadMonitor::find().increment("delete_all"); //logMsg<<"delete"<<"all"; sql.push_back("DELETE FROM symbols_in_lists WHERE user_id = " + userIdString + " AND list_id = " + safeListId); } else { int count = 0; for (std::string buffer = _delete; !buffer.empty();) { std::string currentItem = cleanSymbol(getLine(buffer)); if (!currentItem.empty()) { sql.push_back("DELETE FROM symbols_in_lists " "WHERE user_id = " + userIdString + " AND list_id = " + safeListId + " AND symbol = '" + mysqlEscapeString(strtoupper(currentItem)) + "'"); count++; } } if (count) { ThreadMonitor::find().increment("delete"); //logMsg<<"delete"<= 50) { sql.push_back(sqlForAdd); addCount = 0; } if (addCount) { sqlForAdd += ", "; } else { sqlForAdd = "INSERT IGNORE symbols_in_lists " "(symbol, list_id, user_id) VALUES "; } sqlForAdd += "('" + mysqlEscapeString(strtoupper(currentItem)) + "', " + safeListId + ", " + userIdString + ")"; addCount++; totalAddCount++; } } if (addCount) { sql.push_back(sqlForAdd); } if (totalAddCount) { ThreadMonitor::find().increment("add"); //logMsg<<"add"<rowIsValid(); symbolsFromDb->nextRow()) { XmlNode &item = symbolsToClient[-1]; item.name = "SYMBOL"; item.properties["NAME"] = symbolsFromDb->getStringField(0); } } } addToOutputQueue(getSocketInfo(), messageToClient.asString("API"), _returnMessageId); } microSleep(0, std::min(1000000LL, queryTime)); } ///////////////////////////////////////////////////////////////////// // GetListOfListsRequest ///////////////////////////////////////////////////////////////////// class GetListOfListsRequest : public ListRequest { private: const bool _allowNegativeListIds; public: GetListOfListsRequest(UserId userId, ExternalRequest *original) : ListRequest(userId, original), _allowNegativeListIds(original->getProperty("allow_negative_list_ids") == "1") { } void doRequest(DatabaseWithRetry &database); }; void GetListOfListsRequest::doRequest(DatabaseWithRetry &database) { //TclList logMsg; //logMsg<<"SymbolLists.C"<<"list_of_lists"<<_userId; //LogFile::primary().sendString(logMsg, getSocketInfo()); if (_returnMessageId.present()) { XmlNode message; XmlNode &lists = message["LISTS"]; std::string sql = "SELECT id,name FROM symbol_lists WHERE user_id="; sql += ntoa(_userId); if (!_allowNegativeListIds) // Some older clients might get confused by lists with negative id // numbers. In particular, the client is responsible for picking the // id number for a new list. If the only current list has id number // -5, the client might choose -4 for the next id number. By default we // assume an old, naive client. Newer clients can request all symbol // lists by settings allow_negative_list_ids=1 in the request. sql += " AND id > 0"; sql += " ORDER BY id"; for (MysqlResultRef result = database.tryQueryUntilSuccess(sql); result->rowIsValid(); result->nextRow()) { XmlNode &item = lists[-1]; item.name = "LIST"; item.properties["ID"] = result->getStringField(0); item.properties["NAME"] = result->getStringField(1); } addToOutputQueue(getSocketInfo(), message.asString("API"), _returnMessageId); } } ///////////////////////////////////////////////////////////////////// // DeleteAllListsRequest ///////////////////////////////////////////////////////////////////// class DeleteAllListsRequest : public ListRequest { public: DeleteAllListsRequest(UserId userId, ExternalRequest *original) : ListRequest(userId, original) { } void doRequest(DatabaseWithRetry &database); }; void DeleteAllListsRequest::doRequest(DatabaseWithRetry &database) { const std::string userIdString = ntoa(_userId); //TclList logMsg; //logMsg<<"SymbolLists.C"<<"delete_all_lists"<<_userId; //LogFile::primary().sendString(logMsg, getSocketInfo()); std::vector< std::string > sql; sql.push_back("BEGIN"); // Use list 0 to lock the lists for this user. sql.push_back("REPLACE INTO symbol_lists VALUES ('mutex', " + userIdString + ", 0)"); sql.push_back("DELETE FROM symbols_in_lists WHERE user_id=" + userIdString); sql.push_back("DELETE FROM symbol_lists WHERE user_id=" + userIdString); sql.push_back("COMMIT"); database.tryAllUntilSuccess(sql.begin(), sql.end()); if (_returnMessageId.present()) { addToOutputQueue(getSocketInfo(), XmlNode().asString("API"), _returnMessageId); } } ///////////////////////////////////////////////////////////////////// // UserStrategyListRequest ///////////////////////////////////////////////////////////////////// class UserStrategyListRequest : public ListRequest { public: UserStrategyListRequest(UserId userId, ExternalRequest *original) : ListRequest(userId, original) { } void doRequest(DatabaseWithRetry &database); }; void UserStrategyListRequest::doRequest(DatabaseWithRetry &database) { assert(_userId); XmlNode message; XmlNode &body = message["USER_STRATEGY"]; const std::string sql = "SELECT * FROM user_strategies WHERE user_id=" + ntoa(_userId); for (MysqlResultRef result = database.tryQueryUntilSuccess(sql); result->rowIsValid(); result->nextRow()) { XmlNode &row = body[-1]; row.name = "STRATEGY"; const std::string settings = result->getStringField("settings"); row.properties["SHORT_FORM"] = settings; AlertConfig config; // Do not allow custom columns. This is aimed at E*TRADE. config.load(settings, _userId, database, false, false); const std::string name = config.getWindowName(); if (!name.empty()) { row.properties["NAME"] = name; } row.properties["ID"] = result->getStringField("strategy_id"); const std::string description = result->getStringField("description"); if (!description.empty()) { row.properties["DESCRIPTION"] = description; } } addToOutputQueue(getSocketInfo(), message.asString("API"), _returnMessageId); } ///////////////////////////////////////////////////////////////////// // UserStrategyDeleteRequest ///////////////////////////////////////////////////////////////////// class UserStrategyDeleteRequest : public ListRequest { private: const int _strategyId; public: UserStrategyDeleteRequest(UserId userId, ExternalRequest *original) : ListRequest(userId, original), _strategyId(strtolDefault(original->getProperty("id"), -1)) { } void doRequest(DatabaseWithRetry &database); }; void UserStrategyDeleteRequest::doRequest(DatabaseWithRetry &database) { const std::string safeId = ntoa(_strategyId); XmlNode message; message["USER_STRATEGY_DELETE"].properties["ID"] = safeId; if (_userId > 0) { const std::string sql = "DELETE FROM user_strategies " "WHERE strategy_id = " + safeId + " AND user_id = " + ntoa(_userId); database.tryQueryUntilSuccess(sql); } addToOutputQueue(getSocketInfo(), message.asString("API"), _returnMessageId); } ///////////////////////////////////////////////////////////////////// // UserStrategySaveRequest ///////////////////////////////////////////////////////////////////// class UserStrategySaveRequest : public ListRequest { private: const int _strategyId; const std::string _settings; const std::string _description; const bool _doNotOverwrite; public: UserStrategySaveRequest(UserId userId, ExternalRequest *original) : ListRequest(userId, original), _strategyId(strtolDefault(original->getProperty("id", "0"), -1)), _settings(original->getProperty("settings")), _description(original->getProperty("description")), _doNotOverwrite(original->getProperty("do_not_overwrite") == "1") { } void doRequest(DatabaseWithRetry &database); }; void UserStrategySaveRequest::doRequest(DatabaseWithRetry &database) { XmlNode message; XmlNode &body = message["USER_STRATEGY_SAVE"]; AlertConfig config; // Do not allow custom columns. This is aimed at E*TRADE. config.load(_settings, _userId, database, false, false); const std::string settings = config.save(); if (_userId > 0) { if (_strategyId < 0) { // The client should never send this. A bad value can screw up the // database. if (_returnMessageId.present()) { body["INVALID_STRATEGY_ID"].text = "The strategy id should be a non-negative integer."; } } else { const std::string userIdString = ntoa(_userId); if (!_strategyId) { std::vector< std::string > sql; sql.push_back("BEGIN"); // Use id 0 to lock the table for this user. // This will also make sure the the next query returns at least // one result, even if the table was previously empty. sql.push_back("REPLACE INTO user_strategies VALUES (" + userIdString + ", 0, '', '')"); sql.push_back("SELECT @new_id:=A.strategy_id+1 AS new_id " "FROM user_strategies AS A " "LEFT JOIN user_strategies AS B " "ON A.user_id = B.user_id " "AND A.strategy_id+1 = B.strategy_id " "WHERE A.user_id=" + userIdString + " " "AND B.user_id IS NULL " "AND A.strategy_id BETWEEN 0 AND 2147483646 " "LIMIT 1"); sql.push_back("DELETE FROM user_strategies " "WHERE user_id=" + userIdString + " " "AND strategy_id=0"); sql.push_back("REPLACE INTO user_strategies VALUES(" + userIdString + ", @new_id,'" + mysqlEscapeString(settings) + "','" + mysqlEscapeString(_description) + "')"); sql.push_back("COMMIT"); DatabaseWithRetry::ResultList dbResult = database.tryAllUntilSuccess(sql.begin(), sql.end()); body.properties["ID"] = dbResult[2]->getStringField(0); } else { const std::string strategyIdString = ntoa(_strategyId); body.properties["ID"] = strategyIdString; if (_doNotOverwrite) { // This should be a lot simpler, but for some reason // getAffectedRows() doesn't seem to work for me. // "SELECT ... FOR UPDATE" doesn't seem to help, either. // That only works if you know that the row exists. std::vector< std::string > sql; sql.push_back("BEGIN"); // Use id 0 to lock the table for this user. sql.push_back("REPLACE INTO user_strategies VALUES (" + userIdString + ", 0, '', '')"); sql.push_back("DELETE FROM user_strategies " "WHERE user_id=" + userIdString + " " "AND strategy_id=0"); sql.push_back("SELECT COUNT(*) FROM user_strategies " "WHERE user_id=" + userIdString + " " "AND strategy_id=" + strategyIdString); sql.push_back("INSERT IGNORE user_strategies VALUES(" + userIdString + "," + strategyIdString + ",'" + mysqlEscapeString(settings) + "','" + mysqlEscapeString(_description) + "')"); sql.push_back("COMMIT"); DatabaseWithRetry::ResultList dbResult = database.tryAllUntilSuccess(sql.begin(), sql.end()); if (dbResult[3]->getStringField(0) != "0") { body.properties["DUPLICATE"] = "1"; } } else { const std::string sql = "REPLACE INTO user_strategies VALUES(" + userIdString + "," + strategyIdString + ",'" + mysqlEscapeString(settings) + "','" + mysqlEscapeString(_description) + "')"; database.tryQueryUntilSuccess(sql); } } if (!_description.empty()) { body.properties["DESCRIPTION"] = _description; } body.properties["SHORT_FORM"] = settings; const std::string name = config.getWindowName(); if (!name.empty()) { body.properties["NAME"] = name; } } } // This format is a little bit odd. Currently only e*trade uses this // feature. Some XML messages we parse for them. Others they parse. This // is the first mixed message. They already parse the entire edit_config_new // message, so we want them to parse this, too. XmlNode details; config.getSettingsForEditor(details); body.properties["DETAILS"] = details.asString("STRATEGY"); addToOutputQueue(getSocketInfo(), message.asString("API"), _returnMessageId); } ///////////////////////////////////////////////////////////////////// // SaveToCloudRequest ///////////////////////////////////////////////////////////////////// class SaveToCloudRequest : public ListRequest { private: const std::string _layout; const std::string _icon; const std::string _shortDescription; const std::string _longDescription; const int _windowCount; const bool _fillScreen; const bool _clearPreviousLayout; public: SaveToCloudRequest(ExternalRequest *original) : ListRequest(userInfoGetInfo(original->getSocketInfo()).userId, original), _layout(original->getProperty("layout")), _icon(original->getProperty("icon")), _shortDescription(original->getProperty("short_description")), _longDescription(original->getProperty("long_description")), _windowCount(strtolDefault(original->getProperty("window_count"), 0)), _fillScreen(original->getProperty("fill_screen") == "1"), _clearPreviousLayout(original->getProperty("clear_previous_layout") == "1") { } void doRequest(DatabaseWithRetry &database); }; void SaveToCloudRequest::doRequest(DatabaseWithRetry &database) { int originalSize = _layout.length(); std::string toSave(originalSize / 2, '\x00'); uint64_t size = toSave.length(); int compressionResult = compress2(reinterpret_cast(&toSave[0]), &size, reinterpret_cast(&_layout[0]), _layout.length(), Z_BEST_COMPRESSION); if (compressionResult == Z_OK) { assert(size <= toSave.length()); if (size > 65536) { // Too big. We have no direct way to report an error. But this will // have a reasonable result. When someone tries to read this, the client // will see the 0 length result and will display an error message. toSave.clear(); originalSize = 0; } else toSave.resize(size); } else { // Problem compressing it. Probably it was too small to begin with so // compression didn't (significantly) help. toSave = _layout; originalSize = 0; } std::vector< std::string > sql; sql.push_back("INSERT INTO cloud_layout " "SET user_id=" + ntoa(_userId) + ", " "share_code=MD5(CONCAT(" + ntoa(SymbolListHandler::getRandom()) + ",NOW()," + ntoa(_userId) + ")), " "layout='" + mysqlEscapeString(toSave) + "', " "original_size=" + ntoa(originalSize) + ", " "icon='" + mysqlEscapeString(_icon) + "', " "fill_screen='" + (_fillScreen?'Y':'N') + "', " "clear_previous_layout='" + (_clearPreviousLayout?'Y':'N') + "', " "short_description='" + mysqlEscapeString(_shortDescription) + "', " "long_description='" + mysqlEscapeString(_longDescription) + "', " "window_count=" + ntoa(_windowCount) + ", " "creation=NOW()"); sql.push_back("SELECT share_code " "FROM cloud_layout " "WHERE strategy_id=LAST_INSERT_ID()"); DatabaseWithRetry::ResultList result = database.tryAllUntilSuccess(sql.begin(), sql.end()); XmlNode response; response.properties["CODE"] = CLOUD_PREFIX + result[1]->getStringField(0); addToOutputQueue(getSocketInfo(), response.asString("API"), _returnMessageId); } ///////////////////////////////////////////////////////////////////// // CloudDeleteRequest ///////////////////////////////////////////////////////////////////// class CloudDeleteRequest : public ListRequest { private: const int _id; public: CloudDeleteRequest(ExternalRequest *original) : ListRequest(userInfoGetInfo(original->getSocketInfo()).userId, original), _id(strtolDefault(original->getProperty("id"), 0)) { } void doRequest(DatabaseWithRetry &database); }; void CloudDeleteRequest::doRequest(DatabaseWithRetry &database) { std::string sql = "DELETE FROM cloud_layout " "WHERE strategy_id=" + ntoa(_id) + " " "AND user_id=" + ntoa(_userId); database.tryQueryUntilSuccess(sql); addToOutputQueue(getSocketInfo(), XmlNode().asString("API"), _returnMessageId); } ///////////////////////////////////////////////////////////////////// // CloudRevokeRequest ///////////////////////////////////////////////////////////////////// class CloudRevokeRequest : public ListRequest { private: const int _id; public: CloudRevokeRequest(ExternalRequest *original) : ListRequest(userInfoGetInfo(original->getSocketInfo()).userId, original), _id(strtolDefault(original->getProperty("id"), 0)) { } void doRequest(DatabaseWithRetry &database); }; void CloudRevokeRequest::doRequest(DatabaseWithRetry &database) { std::string sql = "UPDATE cloud_layout " "SET share_code=MD5(CONCAT(share_code,NOW()," + ntoa(SymbolListHandler::getRandom()) + ",strategy_id)) " "WHERE user_id=" + ntoa(_userId) + " AND strategy_id=" + ntoa(_id); database.tryQueryUntilSuccess(sql); addToOutputQueue(getSocketInfo(), XmlNode().asString("API"), _returnMessageId); } ///////////////////////////////////////////////////////////////////// // SymbolListHandler ///////////////////////////////////////////////////////////////////// void SymbolListHandler::threadFunction() { while(true) { bool needToSleep = true; while (Request *current = _incomingRequests.getRequest()) { needToSleep = false; switch(current->callbackId) { case mtQuit: { delete current; return; } case mtUpdateList: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); if (UserId userId = userInfoGetInfo(socket).userId) { _requests.push(new UpdateListRequest(userId, request)); } else { sendNullResponse(request); } break; } case mtGetListOfLists: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); if (UserId userId = userInfoGetInfo(socket).userId) { _requests.push(new GetListOfListsRequest(userId, request)); } else { sendNullResponse(request); } break; } case mtDeleteAllLists: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); if (UserId userId = userInfoGetInfo(socket).userId) { _requests.push(new DeleteAllListsRequest(userId, request)); } else { sendNullResponse(request); } break; } case mtUserStrategyList: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); if (UserId userId = userInfoGetInfo(socket).userId) { _requests.push(new UserStrategyListRequest(userId, request)); } else { sendNullResponse(request); } break; } case mtUserStrategyDelete: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); UserId userId = userInfoGetInfo(socket).userId; _requests.push(new UserStrategyDeleteRequest(userId, request)); break; } case mtUserStrategySave: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); UserId userId = userInfoGetInfo(socket).userId; _requests.push(new UserStrategySaveRequest(userId, request)); break; } case mtSaveToCloud: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); if (userInfoGetInfo(socket).userId) _requests.push(new SaveToCloudRequest(request)); else sendNullResponse(request); break; } case mtCloudDelete: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); if (userInfoGetInfo(socket).userId) _requests.push(new CloudDeleteRequest(request)); else sendNullResponse(request); break; } case mtCloudRevoke: { ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); if (userInfoGetInfo(socket).userId) _requests.push(new CloudRevokeRequest(request)); else sendNullResponse(request); break; } case DeleteSocketThread::callbackId: { _requests.remove(current->getSocketInfo()); break; } } delete current; } if (_requests.ready()) { needToSleep = false; ListRequest *request = dynamic_cast(_requests.pop()); request->doRequest(_database); delete request; } if (needToSleep) { _incomingRequests.waitForRequest(_requests.untilNextRequest()); _incomingRequests.resetWaitHandle(); } } } bool SymbolListHandler::needsPenalty(Request *request) const { // Anything that can edit the database gets a penalty. Anything that only // reads does not get a penalty. // mtUpdateList is a special case. switch (request->callbackId) { case mtUpdateList: { UpdateListRequest *current = dynamic_cast< UpdateListRequest * >(request); return current->incurPenalty(); } case mtDeleteAllLists: case mtUserStrategyDelete: case mtUserStrategySave: case mtSaveToCloud: case mtCloudDelete: case mtCloudRevoke: return true; default: return false; } } drand48_data SymbolListHandler::_randomBuffer; long SymbolListHandler::getRandom() { long result; lrand48_r(&_randomBuffer, &result); return result; } SymbolListHandler::SymbolListHandler() : ThreadClass("SymbolListHandler"), _incomingRequests(getName()), _requests(this), _database(false, getName()) { srand48_r(time(NULL), &_randomBuffer); CommandDispatcher *c = CommandDispatcher::getInstance(); c->listenForCommand("update_list", &_incomingRequests, mtUpdateList); c->listenForCommand("get_list_of_lists", &_incomingRequests, mtGetListOfLists); c->listenForCommand("delete_all_lists", &_incomingRequests, mtDeleteAllLists); c->listenForCommand("user_strategy_list", &_incomingRequests, mtUserStrategyList); c->listenForCommand("user_strategy_delete", &_incomingRequests, mtUserStrategyDelete); c->listenForCommand("user_strategy_save", &_incomingRequests, mtUserStrategySave); // See ../misc_ms/MiscNFS.C for the new versions of the cloud related // commands. c->listenForCommand("save_to_cloud", &_incomingRequests, mtSaveToCloud); c->listenForCommand("cloud_delete", &_incomingRequests, mtCloudDelete); c->listenForCommand("cloud_revoke", &_incomingRequests, mtCloudRevoke); startThread(); } SymbolListHandler::~SymbolListHandler() { Request *r = new Request(NULL); r->callbackId = mtQuit; _incomingRequests.newRequest(r); waitForThread(); }