#include #include #include "../shared/DatabaseWithRetry.h" #include "../shared/ContainerThread.h" #include "../shared/NewWorkerCluster.h" #include "../shared/ThreadSafeRefCount.h" #include "../shared/DatabaseForThread.h" #include "../ax_alert_server/FormatTime.h" #include "../ax_alert_server/ConfigWindow.h" #include "../shared/MiscSupport.h" #include "../shared/ReplyToClient.h" #include "../shared/XmlSupport.h" #include "../shared/SimpleLogFile.h" #include "../oddsmaker/UserInfo.h" #include "../ax_alert_server/Types.h" #include "../ax_alert_server/AlertConfig.h" #include "../ax_alert_server/TopListConfig.h" #include "../shared/GlobalConfigFile.h" #include "../ax_alert_server/CloudSupport.h" #include "RecordLanguage.h" #include "MiscRODatabase.h" static void recordLanguage(ExternalRequest const *request) { recordLanguage(userInfoGetInfo(request->getSocketInfo()).userId, request->getProperty("language")); } /* This covers various miscelaneous requests, most of which need access to * a slave database, none of which require access to the master database. * This was based heavily on ../ax_alert_server/MiscRODatabase.C. I changed * the structure to use a NewWorkerCluster. This used to be a single thread. * This gives us more flexibility to scale different parts of the system * seperately. The implementation of the individual commands has changed * very little. */ typedef TSRefCount< ExternalRequest > ExternalRequestRef; static void sendNullResponse(ExternalRequest const &request) { ExternalRequest::MessageId messageId = request.getResponseMessageId(); if (messageId.present()) { addToOutputQueue(request.getSocketInfo(), XmlNode().asString("API"), messageId); } } enum { mtEditConfig, /* Populate an old config window. */ mtEditConfigNew, /* Populate a new alert config window. */ mtGeneralInfo, /* A description of alerts and filters. */ mtAllAlertTypes, /* A list of all alert types. */ mtDisconnect, /* The client is asking us to close the socket. Only used * for testing. Normally the client will close the * connection, unless the server detects an error. */ mtEcho, /* Used for testing. Outputs anything the client requests. Can * output large or multiple messages from a single small request, * depeing on settings. */ mtAllAlertInfo, /* This was a request from E*TRADE. Given an alert * id we return the values of all corresponding filters. * It's a little like the filters section of * http://www.trade-ideas.com/StockInfo/_StocksLikeThis.html */ mtCloudList, /* Look at all of the items assigned to the user. These * are the items that the user saved himself. The back * office may have a way to reassign these. */ mtCloudLoad, /* Grab a personal layout based on the id number. */ mtCloudImport, /* Grab a layout based on a share code. */ mtGetProfile, /* Grab company profile for detail view window */ mtGetInsiders, /* Grab insider trades for detail view window */ mtTopListConfig }; static const std::string s_mtEditConfig = "mtEditConfig"; static const std::string s_mtEditConfigNew = "mtEditConfigNew"; static const std::string s_mtGeneralInfo = "mtGeneralInfo"; static const std::string s_mtAllAlertTypes = "mtAllAlertTypes"; static const std::string s_mtDisconnect = "mtDisconnect"; static const std::string s_mtEcho = "mtEcho"; static const std::string s_mtAllAlertInfo = "mtAllAlertInfo"; static const std::string s_mtCloudList = "mtCloudList"; static const std::string s_mtCloudList_null = "mtCloudList null"; static const std::string s_mtCloudLoad = "mtCloudLoad"; static const std::string s_mtCloudLoad_null = "mtCloudLoad null"; static const std::string s_mtCloudImport = "mtCloudImport"; static const std::string s_mtGetProfile = "mtGetProfile"; static const std::string s_mtGetInsiders = "mtGetInsiders"; static const std::string s_mtTopListConfig = "mtTopListConfig"; static void doCloudList(ExternalRequestRef const &original) { ThreadMonitor::SetState tm(s_mtCloudList); tm.increment(s_mtCloudList); DatabaseWithRetry &database = *DatabaseForThread(DatabaseWithRetry::SLAVE); SocketInfo *const socket = original->getSocketInfo(); const bool easternTime = userInfoUseEasternTime(socket); const UserId userId = userInfoGetInfo(socket).userId; std::string sql = "SELECT strategy_id, share_code, icon, clear_previous_layout, " "fill_screen, short_description, long_description, window_count, " "FLOOR(UNIX_TIMESTAMP(creation)) as creation " "FROM cloud_layout " "WHERE user_id=" + ntoa(userId); XmlNode response; XmlNode &rows = response["ROWS"]; for (MysqlResultRef result = database.tryQueryUntilSuccess(sql); result->rowIsValid(); result->nextRow()) { XmlNode &row = rows[-1]; row.properties["ID"] = result->getStringField("strategy_id"); row.properties["CODE"] = CLOUD_PREFIX + result->getStringField("share_code"); row.properties["ICON"] = result->getStringField("icon"); row.properties["CLEAR_PREVIOUS_LAYOUT"] = (result->getStringField("clear_previous_layout") == "Y")?"1":"0"; row.properties["FILL_SCREEN"] = (result->getStringField("fill_screen") == "Y")?"1":"0"; row.properties["SHORT_DESCRIPTION"] = result->getStringField("short_description"); row.properties["LONG_DESCRIPTION"] = result->getStringField("long_description"); row.properties["WINDOW_COUNT"] = result->getStringField("window_count"); time_t creation = result->getIntegerField("creation", 0); if (creation) row.properties["CREATION"] = exportTime(creation, easternTime); } addToOutputQueue(socket, response.asString("API"), original->getResponseMessageId()); } static const std::string s_decompressLayout = "decompressLayout"; static const std::string s_decompressLayout_notCompressed = "decompressLayout not compressed"; static const std::string s_decompressLayout_normal = "decompressLayout normal"; static const std::string s_decompressLayout_notFound = "decompressLayout not found"; static const std::string s_decompressLayout_error = "decompressLayout ERROR"; static std::string decompressLayout(MysqlResultRef const &fromDatabase) { ThreadMonitor::SetState tm(s_decompressLayout); if (!fromDatabase->rowIsValid()) { // Not found. This seems unavoidable. This is different from the data // errors below. Those suggest something more troubling, possibly a // programming error. tm.increment(s_decompressLayout_notFound); return ""; } const std::string body = fromDatabase->getStringField(1); const int length = fromDatabase->getIntegerField(2, 0); if (length <= 0) { // Not compressed. tm.increment(s_decompressLayout_notCompressed); return body; } std::string result(length, '\x00'); uint64_t size = length; int zlibResult = uncompress(reinterpret_cast(&result[0]), &size, reinterpret_cast(&body[0]), body.length()); if ((zlibResult != Z_OK) || (size != (uint64_t)length)) { // Some sort of error. Possibly bad data in the database. Return our // standard "" as an indiciation of an error. tm.increment(s_decompressLayout_error); TclList msg; msg<getStringField("share_code"); sendToLogFile(msg); return ""; } else { tm.increment(s_decompressLayout_normal); return result; } } static void getFromCloud(ExternalRequestRef const &original, std::string const &where) { DatabaseWithRetry &database = *DatabaseForThread(DatabaseWithRetry::SLAVE); std::string sql = "SELECT clear_previous_layout, layout, original_size, short_description, " "share_code, fill_screen " "FROM cloud_layout " "WHERE " + where; MysqlResultRef result = database.tryQueryUntilSuccess(sql); XmlNode response; XmlNode &layout = response["LAYOUT"]; layout.useCdata = true; layout.properties["CLEAR_PREVIOUS"] = (result->getStringField("clear_previous_layout") == "Y")?"1":"0"; layout.properties["FILL_SCREEN"] = (result->getStringField("fill_screen") == "Y")?"1":"0"; layout.properties["NAME"] = result->getStringField("short_description"); layout.text = decompressLayout(result); addToOutputQueue(original->getSocketInfo(), response.asString("API"), original->getResponseMessageId()); } static void doCloudLoad(ExternalRequestRef const &original) { ThreadMonitor::SetState tm(s_mtCloudLoad); tm.increment(s_mtCloudLoad); SocketInfo *const socket = original->getSocketInfo(); const UserId userId = userInfoGetInfo(socket).userId; const int id = strtolDefault(original->getProperty("id"), 0); getFromCloud(original, "user_id=" + ntoa(userId) + " AND strategy_id=" + ntoa(id)); } static void doCloudImport(ExternalRequestRef const &original) { ThreadMonitor::SetState tm(s_mtCloudImport); tm.increment(s_mtCloudImport); std::string shortCode = original->getProperty("code"); if (shortCode.length() > 32) shortCode.erase(0, shortCode.length() - 32); getFromCloud(original, "share_code='" + mysqlEscapeString(shortCode) + "'"); } static void doGetProfile(ExternalRequestRef const &original) { ThreadMonitor::SetState tm(s_mtGetProfile); tm.increment(s_mtGetProfile); DatabaseWithRetry &database = *DatabaseForThread(DatabaseWithRetry::SLAVE); const std::string symbol = original->getProperty("symbol"); std::string sql = "SELECT summary, country, website " "FROM company_profile " "WHERE symbol=\"" + mysqlEscapeString(symbol) + "\" ORDER BY protected DESC LIMIT 1"; MysqlResultRef result = database.tryQueryUntilSuccess(sql); XmlNode response; XmlNode &profile = response["PROFILE"]; profile.useCdata = true; profile.properties["SYMBOL"] = symbol; profile.text = result->getStringField(0); profile.properties["COUNTRY"] = result->getStringField(1); profile.properties["WEBSITE"] = result->getStringField(2); addToOutputQueue(original->getSocketInfo(), response.asString("API"), original->getResponseMessageId()); } static void doGetInsiders(ExternalRequestRef const &original) { ThreadMonitor::SetState tm(s_mtGetInsiders); DatabaseWithRetry &database = *DatabaseForThread(DatabaseWithRetry::SLAVE); const std::string symbol = original->getProperty("symbol"); std::string sql = "SELECT date, filer_name, relationship, relationship_type, transaction_type, ownership_type, " "form_type, price_to, price_from, shares, total_shares_owned " "FROM insiders " "WHERE symbol=\"" + mysqlEscapeString(symbol) + "\" ORDER BY date DESC limit 1000"; MysqlResultRef result = database.tryQueryUntilSuccess(sql); XmlNode response; XmlNode &rows = response["ROWS"]; for (MysqlResultRef result = database.tryQueryUntilSuccess(sql); result->rowIsValid(); result->nextRow()) { XmlNode &row = rows[-1]; row.properties["SYMBOL"] = symbol; row.properties["DATE"] = result->getStringField("date"); row.properties["FILER_NAME"] = result->getStringField("filer_name"); row.properties["RELATIONSHIP_TYPE"] = ntoa(result->getIntegerField("relationship_type",-1)); row.properties["RELATIONSHIP"] = result->getStringField("relationship"); row.properties["TRANSACTION_TYPE"] = ntoa(result->getIntegerField("transaction_type",-1)); row.properties["OWNERSHIP_TYPE"] = ntoa(result->getIntegerField("ownership_type",-1)); row.properties["FORM_TYPE"] = ntoa(result->getIntegerField("form_type",-1)); row.properties["PRICE_TO"] = dtoa(result->getDoubleField("price_to", 0.0)); row.properties["PRICE_FROM"] = dtoa(result->getDoubleField("price_from", 0.0)); row.properties["SHARES"] = ntoa(result->getIntegerField("shares",-1)); row.properties["TOTAL_SHARES_OWNED"] = ntoa(result->getIntegerField("total_shares_owned",-1)); } addToOutputQueue(original->getSocketInfo(), response.asString("API"), original->getResponseMessageId()); } ///////////////////////////////////////////////////////////////////// // TopListConfigRequest ///////////////////////////////////////////////////////////////////// class TopListConfigRequest : public Request { private: const UserId _userId; const ExternalRequest::MessageId _returnMessageId; const std::string _settings; const std::string _language; const std::string _whichSamples; const bool _easternTime; const bool _allowSymbolListFolders; const bool _allowNegativeListIds; const bool _skipStrategies; static void addTimeFrame(XmlNode &strategy, std::string const &html_help); XmlNode &addStrategy(UserId userId, DatabaseWithRetry &database, XmlNode &parent, std::string settings, std::string icon = "!", std::string description = "", std::string name = "", std::string htmlHelp = "") const; void addPrimaryStrategies(XmlNode &parent, std::string whichSamples, DatabaseWithRetry &database) const; void addPrimaryStrategyList(XmlNode &parent, std::string listName, UserId userId, DatabaseWithRetry &database, std::set< std::string > &added) const; typedef std::pair< time_t, std::string > Item; void addOneRecentStrategy(DatabaseWithRetry &database, XmlNode &parent, Item const &item) const; typedef std::set< Item > SimilarItems; typedef std::map< std::string, SimilarItems > AllItems; struct SimilarItemsWithName { std::string name; SimilarItems items; }; typedef std::multimap< int, SimilarItemsWithName > AllItemsByAge; void addRecentSettings(XmlNode &parent, DatabaseWithRetry &database) const; void addCurrentSettings(XmlNode &node, DatabaseWithRetry &database) const; public: TopListConfigRequest(ExternalRequest const *original) : Request(original->getSocketInfo()), _userId(userInfoGetInfo(getSocketInfo()).userId), _returnMessageId(original->getResponseMessageId()), _settings(original->getProperty("settings")), _language(original->getProperty("language")), _whichSamples(original->getProperty("which_samples")), _easternTime(userInfoUseEasternTime(original->getSocketInfo())), _allowSymbolListFolders(original->getProperty("symbol_list_folders") == "1"), _allowNegativeListIds(original->getProperty("allow_negative_list_ids") == "1"), _skipStrategies(original->getProperty("skip_strategies") == "1") { recordLanguage(original); } void doRequest(); }; void TopListConfigRequest::addPrimaryStrategies(XmlNode &parent, std::string whichSamples, DatabaseWithRetry &database ) const { // This is mostly copied from ConfigWindow.C. We look at tables with // different names to file the top list items, but the tables have the // same structure and purpose. // // This table exists mostly to allow us some control over what is exported. // a person cannot ask for a part of an existing tree. They can only // take the trees that we export. // // We start with the white_label. MysqlResultRef result = database.tryQueryUntilSuccess("SELECT list_name " "FROM top_level_strategies_tl, users " "WHERE id=" + ntoa(_userId) + " " "AND source='white_label' " "AND external_name=wl_include"); if (result->fieldIsEmpty(0) && !whichSamples.empty()) { // If we couldn't find anything using the whitelabel, we wil see if the // client requested anything. This is used by the new E*TRADE client. // We wanted to seperate the new E*TRADE client from the old E*TRADE // client because the old client is so brittle. Sometimes changing a // strategy would cause their entire program to crash! result = database.tryQueryUntilSuccess("SELECT list_name " "FROM top_level_strategies_tl " "WHERE external_name ='" + mysqlEscapeString(whichSamples) + "' " + "AND source = 'client_request'"); } if (result->fieldIsEmpty(0)) { // If if there is still a problem, then we try the default result = database.tryQueryUntilSuccess("SELECT list_name " "FROM top_level_strategies_tl " "WHERE external_name ='' " "AND source = 'client_request'"); } if (!result->fieldIsEmpty(0)) { // It's possible that there is a problem still. This would be a server // problem. In that case we display nothing, but we don't crash. std::set< std::string > uniqueList; addPrimaryStrategyList(parent, result->getStringField(0), _userId, database, uniqueList); } } void TopListConfigRequest::addPrimaryStrategyList(XmlNode &parent, std::string listName, UserId userId, DatabaseWithRetry &database, std::set< std::string > &added ) const { if (!added.insert(listName).second) { // This node has already been added to the tree. To avoid infinite // recursion, we do not allow the same node to be added more than once. // The first insertion will act as expected, and all future attempts will // be ingored. return; } MysqlResultRef result = database.tryQueryUntilSuccess("SELECT text_help, " "html_help, " "settings, " "name, " "icon, " "user_must_modify, " "sub_list " "FROM strategies_tl " "WHERE list_name='" + mysqlEscapeString(listName) + "' ORDER BY id"); while (result->rowIsValid()) { if (result->fieldIsEmpty("sub_list")) { // This is an individual strategy. XmlNode &strategy = addStrategy(userId, database, parent, result->getStringField("settings"), result->getStringField("icon"), result->getStringField("text_help"), result->getStringField("name"), result->getStringField("html_help")); if (result->getStringField("user_must_modify") == "Y") { strategy.properties["USER_MUST_MODIFY"] = "1"; } } else { // This is a list of strategies. XmlNode &node = parent[-1]; node.name = "FOLDER"; node.properties["NAME"] = result->getStringField("name"); node.properties["DESCRIPTION"] = result->getStringField("text_help"); addPrimaryStrategyList(node, result->getStringField("sub_list"), userId, database, added); } result->nextRow(); } } void TopListConfigRequest::addOneRecentStrategy(DatabaseWithRetry &database, XmlNode &parent, Item const &item) const { int seconds = item.first; std::string const &settings = item.second; std::string description; if (seconds < 2) { description = "within the last second."; } else if (seconds < 100) { description = ntoa(seconds) + " seconds ago."; } else { int minutes = (seconds + 30) / 60; if (minutes < 100) { description = ntoa(minutes) + " minutes ago."; } else { int hours = (seconds + 1800) / 3600; if (hours <= 36) { description = ntoa(hours) + " hours ago."; } else { int days = (seconds + 43200) / 86400; description = ntoa(days) + " days ago."; } } } description = "You last started this window " + description; XmlNode &strategy = addStrategy(_userId, database, parent, settings, ":)", description); // Forcing this to be at least 0 will make things easier for the // client. strategy.properties["AGE"] = ntoa(std::max(0, seconds)); } void TopListConfigRequest::addRecentSettings(XmlNode &parent, DatabaseWithRetry &database) const { MysqlResultRef result = database.tryQueryUntilSuccess ("SELECT FLOOR(UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(start_time)) AS age, settings" " FROM view_mru_tl " "WHERE user_id=" + ntoa(_userId)); if (!result->rowIsValid()) return; XmlNode &allRecentSettings = parent[-1]; allRecentSettings.name = "FOLDER"; allRecentSettings.properties["NAME"] = "Recent Settings"; // Collect all items, grouping by name. Within a name, sort by age. AllItems allItems; while (result->rowIsValid()) { Item item; item.first = result->getIntegerField("age", 0); TopListConfig config; item.second = result->getStringField("settings"); config.load(item.second, _userId, database, true); const std::string name = config.getWindowName(); allItems[name].insert(item); result->nextRow(); } // Sort the groups. Use the minimum age of any item in the group as the // sort key. The name is no longer a key, but we still need to keep it. AllItemsByAge allItemsByAge; for (AllItems::const_iterator it = allItems.begin(); it != allItems.end(); it++) { SimilarItems const &items = it->second; // The list of items sorted by age. const int key = items.begin()->first; // Smallest age of any member. SimilarItemsWithName value; value.name = it->first; value.items = items; allItemsByAge.insert(AllItemsByAge::value_type(key, value)); } for (AllItemsByAge::const_iterator it = allItemsByAge.begin(); it != allItemsByAge.end(); it++) { SimilarItemsWithName itemsWithName = it->second; SimilarItems const &items = itemsWithName.items; if (items.size() == 1) { Item const &item = *items.begin(); addOneRecentStrategy(database, allRecentSettings, item); } else { XmlNode &group = allRecentSettings[-1]; group.name = "FOLDER"; std::string name = itemsWithName.name; name += " ("; name += ntoa(items.size()); name += ')'; group.properties["NAME"] = name; for (SimilarItems::const_iterator similarItemsIt = items.begin(); similarItemsIt != items.end(); similarItemsIt++) { Item const &item = *similarItemsIt; addOneRecentStrategy(database, group, item); } } } } void TopListConfigRequest::addCurrentSettings(XmlNode &node, DatabaseWithRetry &database) const { if (!_settings.empty()) { XmlNode &strategy = addStrategy(_userId, database, node, _settings, ":)", "These were your settings before you requested " "the configuration window.", "Current Settings"); strategy.properties["CURRENT"] = "1"; strategy.properties["USER_MUST_MODIFY"] = "1"; } } void TopListConfigRequest::addTimeFrame(XmlNode &strategy, std::string const &html_help) { // For the E*TRADE wizard. const std::vector< std::string > pieces = explode("TIME_FRAME:", html_help); if ((pieces.size() == 2) && (pieces[0].empty())) strategy.properties["TIME_FRAME"] = pieces[1]; } XmlNode &TopListConfigRequest::addStrategy(UserId userId, DatabaseWithRetry &database, XmlNode &parent, std::string settings, std::string icon, std::string description, std::string name, std::string htmlHelp) const { XmlNode &strategy = parent[-1]; strategy.name = "STRATEGY"; TopListConfig config; config.load(settings, userId, database, true); if (name == "") { strategy.properties["NAME"] = config.getWindowName(); } else { strategy.properties["NAME"] = name; } // SETTINGS is slightly redundant because we have the same info in CONFIG. strategy.properties["SETTINGS"] = settings; strategy.properties["ICON"] = icon; strategy.properties["DESCRIPTION"] = description; config.getSettingsForEditor(strategy["CONFIG"], _easternTime); addTimeFrame(strategy, htmlHelp); return strategy; } void TopListConfigRequest::doRequest() { // The output should look a lot like the message for an alert config window. // In the C# client one class reads both types of messages becase they have // so much in common. This is based on populateConfigWindow(). ThreadMonitor::SetState tm(s_mtTopListConfig); tm.increment(s_mtTopListConfig); DatabaseWithRetry &database = *DatabaseForThread(DatabaseWithRetry::SLAVE); XmlNode reply; XmlNode &strategies = reply["STRATEGIES"]; //LogFile::primary().sendString(TclList()<getProperty("allow_negative_list_ids") == "1"); } static void doGeneralInfo(ExternalRequestRef const &original) { ThreadMonitor::SetState tm(s_mtGeneralInfo); tm.increment(s_mtGeneralInfo); SocketInfo *socket = original->getSocketInfo(); XmlNode reply; DatabaseWithRetry &database = *DatabaseForThread(DatabaseWithRetry::SLAVE); const UserId userId = userInfoGetInfo(socket).userId; AlertConfig::generalInfo(reply, userId, database); std::string sql = "SELECT FLOOR(UNIX_TIMESTAMP(CONCAT((SELECT date FROM alert_shards WHERE live='Y' ORDER BY date limit 1), ' 01:00:00')))"; MysqlResultRef result = database.tryQueryUntilSuccess(sql); const time_t oldestAlert = result->getIntegerField(0, 0); if (oldestAlert > 0) reply["HISTORY"].properties["OLDEST_ALERT"] = exportTime(oldestAlert, userInfoUseEasternTime(socket)); sql = "SELECT COUNT(*) FROM holidays WHERE (NOT find_in_set('US', closed)) AND EXISTS (SELECT * FROM candles_d WHERE day=date)"; result = database.tryQueryUntilSuccess(sql); const int usCount = result->getIntegerField(0, 0); XmlNode &usCountNode = reply["OM_DAY_COUNT"][-1]; // Note: The first child of OM_DAY_COUNT is the default. This is // what the E*TRADE client will use. Currently we only send one // answer, the US answer. Whenever we start using this in TI Pro, // we should add "Canada" as the second answer. usCountNode.name = "COUNT"; usCountNode.properties["LOCATION"] = "US"; usCountNode.properties["DAYS"] = ntoa(usCount); addToOutputQueue(socket, reply.asString("API"), original->getResponseMessageId()); } static void doAllAlertTypes(ExternalRequestRef const &original) { // This replaces http://www.trade-ideas.com/API2/AX_Types.html // This provides a list of the short names of each valid // alert type, in the standard order. ThreadMonitor::SetState tm("mtAllAlertTypes"); tm.increment("mtAllAlertTypes"); SocketInfo *socket = original->getSocketInfo(); XmlNode reply; AllConfigInfo::instance().getAllAlertTypes(reply); addToOutputQueue(socket, reply.asString("API"), original->getResponseMessageId()); } static void doEditConfig(ExternalRequestRef const &original) { // The logs suggest that someone still calls this, but it's very rare. // (That comment was copied from the ax_alert_server version. I doubt if // any software that knows about the micro_proxy will call this command.) ThreadMonitor::SetState tm(s_mtEditConfig); tm.increment(s_mtEditConfig); SocketInfo *socket = original->getSocketInfo(); AlertConfig config; DatabaseWithRetry &database = *DatabaseForThread(DatabaseWithRetry::SLAVE); const UserId userId = userInfoGetInfo(socket).userId; config.load(makeUrlSafe(original->getProperty("config")), userId, database, // This is an obsolete call. Only very old clients will // use this. So do not allow custom columns here. false, false); XmlNode reply; config.getForEditor(reply["CONFIG"], userId, database); reply["STATUS"].properties["SUCCESS"] = "1"; // Why does the client still need this? addToOutputQueue(socket, reply.asString("API"), original->getResponseMessageId()); } /* This is called by the E*TRADE client, not by TI Pro. I used telnet to test this code: MySQL [mydb]> select max(id) from alerts; +-------------+ | max(id) | +-------------+ | 17835448672 | +-------------+ 1 row in set (0.00 sec) MySQL [mydb]> Bye [phil@kwanzaabot ~]$ telnet becca 9996 Trying 192.168.1.234... Connected to becca. Escape character is '^]'. command=proxy_login&user_id=4 command=all_alert_info&message_id=2&id=17835448672 == MESSAGE 2 ========== 9192 == ... ^] */ static void doAllAlertInfo(ExternalRequestRef const &original) { ThreadMonitor::SetState tm(s_mtAllAlertInfo); tm.increment(s_mtAllAlertInfo); XmlNode reply; SocketInfo *socket = original->getSocketInfo(); const UserId userId = userInfoGetInfo(socket).userId; // This test is a little oversimplified. We should do the same test // as we do in the top list. That looks at the exchange of the alert, // and the user's permissions. (It was very tempting to grab use even // more of that code.) At the moment this is only aimed at E*TRADE, so // it's not an issue. TODO if (userId) { DatabaseWithRetry &database = *DatabaseForThread(DatabaseWithRetry::SLAVE); static const std::string PRICE = "price"; // We are saying that we only want the filters. Really it would make // more sense to give them other types of columns, too. But this // is only aimed at E*TRADE and we want to make as few changes as // possible. Since they didn't get other columns in test, don't give // them the new columns now. PairedFilterList filters(userId, database, false, true); const PairedFilterList::Iterator begin = filters.begin(); const PairedFilterList::Iterator end = filters.end(); std::string sql = "SELECT price "; for (PairedFilterList::Iterator it = begin; it != end; it++) { sql += ", "; sql += PairedFilterList::fixPrice((*it)->sql, PRICE); sql += " as x"; } sql += " FROM alerts LEFT JOIN alerts_daily ON symbol=d_symbol AND date=DATE(timestamp) WHERE id='"; sql += mysqlEscapeString(original->getProperty("id")); sql += '\''; MysqlResultRef result = database.tryQueryUntilSuccess(sql); const double price = result->getDoubleField(0, 10.0); const int pDigits = ((price > 0) && (price < 1))?4:2; XmlNode &filterList = reply["FILTERS"]; int index = 1; for (PairedFilterList::Iterator it = begin; it != end; it++, index++) if (!result->fieldIsEmpty(index)) { XmlNode &filter = filterList[-1]; filter.properties["CODE"] = (*it)->baseName; const int digits = strtolDefault((*it)->format, pDigits); filter.properties["VALUE"] = dtoaFixed(result->getDoubleField(index, 0.0), digits); } } addToOutputQueue(socket, reply.asString("API"), original->getResponseMessageId()); } ///////////////////////////////////////////////////////////////////// // MiscRODatabaseHandler ///////////////////////////////////////////////////////////////////// class MiscRODatabaseHandler : public ForeverThreadUser { private: NewWorkerCluster _workers; protected: virtual void handleRequestInThread(Request *current); virtual void initializeInThread(); public: MiscRODatabaseHandler(); }; void MiscRODatabaseHandler::initializeInThread() { // This is the dispatcher thread. If you need a database, use a worker // thread. assertNoDatabaseThisThread(); const int count = strtolDefault(getConfigItem("misc_ro_database_threads"), 4); assert(count > 0); _workers.createWorkersLambda([]() { // These tasks are explicitly aimed at a read only database. "misc" // tasks for the master database are handled elsewhere. disableDatabaseThisThread(DatabaseWithRetry::MASTER); }, count); } void MiscRODatabaseHandler::handleRequestInThread(Request *current) { ThreadMonitor &tm = ThreadMonitor::find(); // Normally the container thread would delete the request. Instead we're // using a reference counted pointer to handle the deletion. ExternalRequestRef const request = dynamic_cast< ExternalRequest * >(current); keepOriginal(); // Notice the implied assertion. If current is not of type ExternalRequest // we'd catch it here. We'd have to change our memory management if that // was not true. SocketInfo *const socket = request->getSocketInfo(); switch(current->callbackId) { case mtCloudList: { if (userInfoGetInfo(socket).userId) { tm.increment(s_mtCloudList); _workers.addJob1([request]() { doCloudList(request); }, socket); } else { tm.increment(s_mtCloudList_null); sendNullResponse(*request); } break; } case mtCloudLoad: { if (userInfoGetInfo(socket).userId) { tm.increment(s_mtCloudLoad); _workers.addJob1([request]() { doCloudLoad(request); }, socket); } else { tm.increment(s_mtCloudLoad_null); sendNullResponse(*request); } break; } case mtCloudImport: { tm.increment(s_mtCloudImport); _workers.addJob1([request]() { doCloudImport(request); }, socket); break; } case mtGetProfile: { tm.increment(s_mtGetProfile); _workers.addJob1([request]() { doGetProfile(request); }, socket); break; } case mtGetInsiders: { tm.increment(s_mtGetInsiders); _workers.addJob1([request]() { doGetInsiders(request); }, socket); break; } case mtTopListConfig: { tm.increment(s_mtTopListConfig); _workers.addJob1([request]() { TopListConfigRequest(&*request).doRequest(); }, socket); break; } case mtEditConfigNew: tm.increment(s_mtEditConfigNew); _workers.addJob1([request]() { doEditConfigNew(request); }, socket); break; case mtGeneralInfo: tm.increment(s_mtGeneralInfo); _workers.addJob1([request]() { doGeneralInfo(request); }, socket); break; case mtAllAlertTypes: tm.increment(s_mtAllAlertTypes); _workers.addJob1([request]() { doAllAlertTypes(request); }, socket); break; case mtEditConfig: tm.increment(s_mtEditConfig); _workers.addJob1([request]() { doEditConfig(request); }, socket); break; case mtAllAlertInfo: tm.increment(s_mtAllAlertInfo); _workers.addJob1([request]() { doAllAlertInfo(request); }, socket); break; case mtDisconnect: { tm.increment(s_mtDisconnect); DeleteSocketThread::deleteSocket(current->getSocketInfo()); break; } case mtEcho: { tm.setState(s_mtEcho); tm.increment(s_mtEcho); ExternalRequest *request = dynamic_cast(current); SocketInfo *socket = request->getSocketInfo(); XmlNode reply; reply["SOCKET"].properties["REMOTE_ADDR"] = socket->remoteAddr(); for (PropertyList::const_iterator it = request->getProperties().begin(); it != request->getProperties().end(); it++) { XmlNode newProperty; newProperty.properties["NAME"] = it->first; newProperty.properties["VALUE"] = it->second; newProperty.name = "PROPERTY"; reply["ECHO"].orderedChildren.push_back(newProperty); } std::string repeatText = request->getProperty("repeat_text"); int repeatCount = strtolDefault(request->getProperty("repeat_count"), 5); for (int i = 0; i < repeatCount; i++) { reply["ECHO"].text += repeatText; } int randomCount = strtolDefault(request->getProperty("random_count"), 0); for (int i = 0; i < randomCount; i++) { reply["ECHO"].text += (char)(((rand()^i)%96)+32); } int message_count = strtolDefault(request->getProperty("message_count"), 1); for (int i = 0; i < message_count; i++) { addToOutputQueue(request->getSocketInfo(), reply.asString("API"), request->getResponseMessageId()); } break; } } } MiscRODatabaseHandler::MiscRODatabaseHandler() : ForeverThreadUser(IContainerThread::create("MiscRODatabaseHandler")), _workers(getContainer(), getContainer()->getThreadName()) { CommandDispatcher *c = CommandDispatcher::getInstance(); c->listenForCommand("edit_config_new", this, mtEditConfigNew); c->listenForCommand("general_info", this, mtGeneralInfo); c->listenForCommand("all_alert_types", this, mtAllAlertTypes); c->listenForCommand("edit_config", this, mtEditConfig); c->listenForCommand("disconnect", this, mtDisconnect); c->listenForCommand("edit_top_list_config", this, mtTopListConfig); c->listenForCommand("all_alert_info", this, mtAllAlertInfo); //c->listenForCommand("cloud_list", this, mtCloudList); //c->listenForCommand("cloud_load", this, mtCloudLoad); //c->listenForCommand("cloud_import", this, mtCloudImport); c->listenForCommand("get_profile", this, mtGetProfile); c->listenForCommand("get_insiders", this, mtGetInsiders); if (getConfigItem("allow_echo") == "1") { // This command is helpful in development, but possibly dangerous on a // production system. c->listenForCommand("echo", this, mtEcho); } start(); } void initMiscRODatabase() { new MiscRODatabaseHandler(); }