#include "../shared/ReplyToClient.h" #include "../shared/XmlSupport.h" #include "../shared/SimpleLogFile.h" #include "AlertConfig.h" #include "UserInfo.h" #include "Types.h" #include "ConfigWindow.h" static void 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]; } static XmlNode &addStrategy(UserId userId, DatabaseWithRetry &database, XmlNode &parent, std::string settings, std::string icon = "!", std::string description = "", std::string name = "", std::string htmlHelp = "") { XmlNode &strategy = parent[-1]; strategy.name = "STRATEGY"; AlertConfig config; config.load(settings, userId, database, true, 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. // We are already sending so much data, why send more? TI Pro doesn't // require it, but e*trade asked for it. strategy.properties["SETTINGS"] = settings; strategy.properties["ICON"] = icon; strategy.properties["DESCRIPTION"] = description; config.getSettingsForEditor(strategy["CONFIG"]); addTimeFrame(strategy, htmlHelp); return strategy; } // This is like our getting started page. These are the common strategies // for everyone. These are customizable, but only for large groups. These // are arranged in trees, so this function is recursive. static void addPrimaryStrategyList(XmlNode &parent, std::string listName, UserId userId, DatabaseWithRetry &database, std::set< std::string > &added) { 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 " "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(); } } // This can fill in any number of items into the top level. For the default, // we (initially) have one list. For the others we (initially) have two lists, // one for their additional stuff, plus our default. But this can do more. static void addPrimaryStrategies(XmlNode &parent, UserId userId, std::string whichSamples, DatabaseWithRetry &database) { // 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. This gives TI support the most control. // We know that some of these people will also have something in their // ini file, but that was part of the old system. Who knows how long it // might take for all of that to get cleaned out. MysqlResultRef result = database.tryQueryUntilSuccess("SELECT list_name " "FROM top_level_strategies, users " "WHERE id=" + ntoa(userId) + " " "AND source='white_label' " "AND external_name=wl_include"); if (result->fieldIsEmpty(0)) { // If we couldn't find anything using the whitelabel, we will try the // ini file. We prefer the white_label going forward. But a lot of // people still use this system. result = database.tryQueryUntilSuccess("SELECT list_name " "FROM top_level_strategies " "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 " "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); } } // Currently these only exist in the Java API. e*trade specifically requested // this. See SymbolLists.C for more details. static void addUserStrategies(XmlNode &parent, UserId userId, DatabaseWithRetry &database) { MysqlResultRef result = database.tryQueryUntilSuccess ("SELECT * FROM user_strategies WHERE user_id=" + ntoa(userId)); if (result->rowIsValid()) { XmlNode &allUserSettings = parent[-1]; allUserSettings.name = "FOLDER"; allUserSettings.properties["NAME"] = "User Strategies"; while (result->rowIsValid()) { XmlNode &strategy = addStrategy(userId, database, allUserSettings, result->getStringField("settings"), ":)", result->getStringField("description")); strategy.properties["STRATEGY_ID"] = result->getStringField("strategy_id"); result->nextRow(); } } } typedef std::pair< time_t, std::string > Item; static void addOneRecentStrategy(UserId userId, DatabaseWithRetry &database, XmlNode &parent, Item const &item) { 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)); } // Ideally these would all be defined in addRecentSettings(). For some reason // the compiler choked on the last item, the multimap. It couldn't work when // the second argument was a local type. 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; // These are the most recently used settings for this particular user. static void addRecentSettings(XmlNode &parent, UserId userId, DatabaseWithRetry &database) { MysqlResultRef result = database.tryQueryUntilSuccess ("SELECT UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(start_time) AS age, settings" " FROM view_mru " "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); AlertConfig config; item.second = result->getStringField("settings"); config.load(item.second, userId, database, true, 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(userId, 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(userId, database, group, item); } } } } // These were the settings in the alert window before the user selected // "Configure Alerts..." from the menu. // In some ways this seems redundant as the user gets these initially, and he // can return to them by clicking "cancel". However, this is a good way to // populate the "overview" section of that window. Without this we'd have to // have seperate code to preview what you already have vs the other settings. static void addCurrentSettings(XmlNode &node, UserId userId, DatabaseWithRetry &database, std::string const ¤tSettings) { PropertyList propertyList; parseUrlRequest(propertyList, currentSettings); if (AlertConfig::hasConfigInfo(propertyList)) { XmlNode &strategy = addStrategy(userId, database, node, currentSettings, ":)", "These were your settings before you requested " "the configuration window.", "Current Settings"); strategy.properties["CURRENT"] = "1"; strategy.properties["USER_MUST_MODIFY"] = "1"; } } // This is the skeleton. This is the list of alerts, filters, etc, which // could be chosen, without any current values. static void addOptions(XmlNode &parent, UserId userId, DatabaseWithRetry &database, bool disabledSupported, bool useFilterPairs, std::string const &language, bool allowSymbolListFolders, bool allowNegativeListIds) { /* sendToLogFile(TclList()<