// InboundDataParser // This script is responsible for parsing data sent from the server // via inbound XMLRPC. It invokes received* events to convey this // data to other modules in the object. // Copyright (C) 2005-2006 Francisco V. Saldana // // 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, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // Francisco V. Saldana can be contacted using his email account // username: dressedinblue, at domain: gmail.com // and in Second Life by IMming Christopher Omega list replaceListSlice(list dest, list src, integer start) { integer srcLen = llGetListLength(src); return llListInsertList(llDeleteSubList(dest, start, start + srcLen - 1), src, start); } list list2ListStrided(list src, integer start, integer end, integer stride) { return llList2ListStrided(llList2List(src, start, end), 0, end, stride); } list chopString(string src, integer elementLength) { list stringParts = []; integer stringIndex = 0; integer length = llStringLength(src); for (stringIndex = 0; stringIndex < length; stringIndex += elementLength) { stringParts += llGetSubString(src, stringIndex, stringIndex + elementLength - 1); } return stringParts; } list wordWrap(string src, integer lineMaxLength) { if (lineMaxLength < 2) lineMaxLength = 2; list lines; list words = llParseString2List(src, [" "], []); string curLine = ""; integer wordIndex; // I use llGetListLength in the iteration because the length of words // may increase. // Quick speedup tip: Make the llGetListLength call update a variable // when the list is mutated. for (wordIndex = 0; wordIndex < llGetListLength(words); wordIndex++) { string word = llList2String(words, wordIndex); integer wordLength = llStringLength(word); if (wordLength > lineMaxLength) { // Get the chunk of the word that will fit on the screen // minus one character, for the dash. string truncated = llGetSubString(word, 0, lineMaxLength - 2) + "-"; string rest = llDeleteSubString(word, 0, lineMaxLength - 2); // Add them to the list after the current index, we want them // to be handled like normal words. words = llListInsertList(words, [truncated], wordIndex + 1); words = llListInsertList(words, [rest], wordIndex + 2); } else { integer curLineLength = llStringLength(curLine); integer lenAfterAdd = curLineLength + wordLength; if (lenAfterAdd > lineMaxLength) { lines += curLine; curLine = ""; words = llListInsertList(words, [word], wordIndex + 1); } else if (lenAfterAdd == lineMaxLength) { lines += curLine + word; curLine = ""; } else { curLine += word; curLine += " "; } } } if (curLine != "") lines += curLine; return lines; } // ========== For method invocation ========== string randomSeperator(integer len) { integer firstChar = (integer)llFrand(60) + 20; // Range of printable chars = 0x20 to 0x7E if (len <= 1) return llUnescapeURL("%"+(string)firstChar); integer lastChar; do { // Last char must not equal first char. lastChar = (integer)llFrand(60) + 20; } while (lastChar == firstChar); string ret = llUnescapeURL("%"+(string)firstChar); for (len -= 2; len > 0; --len) ret += llUnescapeURL("%" + (string)((integer)llFrand(60) + 20)); return ret + llUnescapeURL("%"+(string)lastChar); } string listToString(list src) { string chars = (string) src; // Squashes all elements together. string seperator; do { // Find a seperator that's not in the list's string form seperator = randomSeperator(3); // so we dont kill data. } while (llSubStringIndex(chars, seperator) != -1); return seperator + llDumpList2String(src, seperator); } list stringToList(string src) { // First 3 chars is seperator. return llParseStringKeepNulls(llDeleteSubString(src, 0, 2), [llGetSubString(src, 0, 2)], []); } callMethod(integer identifyer, string methodName, list parameters) { llMessageLinked(LINK_THIS, identifyer, // ID only necessary for return value. listToString(parameters), methodName); } returnValue(integer identifyer, string methodName, list value) { callMethod(identifyer, methodName + "_ret", value); } // ============================================= trigger_getMenuTextAreaSize() { callMethod(0, "getMenuTextAreaSize", []); } // Events triggered by script: trigger_receivedMenuCategory(string menu, string categoryTitle, integer categoryID) { callMethod(0, "receivedMenuCategory", [menu, categoryTitle, categoryID]); } trigger_receivedMenuContent(string menu, string contentName, integer contentID) { callMethod(0, "receivedMenuContent", [menu, contentName, contentID]); } trigger_receivedMenuKeywordSearch(string menu, string displayText) { callMethod(0, "receivedMenuKeywordSearch", [menu, displayText]); } trigger_receivedMenuText(string menu, string text) { callMethod(0, "receivedMenuText", [menu, text]); } trigger_receivedMenuSummary(string menu, string summaryTitle, integer summaryID) { callMethod(0, "receivedMenuSummary", [menu, summaryTitle, summaryID]); } trigger_receivedMenuBegin(string menuName, string menuTitle) { callMethod(0, "receivedMenuBegin", [menuName, menuTitle]); } trigger_receivedMenuEnd(string menuName) { callMethod(0, "receivedMenuEnd", [menuName]); } trigger_receivedTerminalReset() { callMethod(0, "receivedTerminalReset", []); } trigger_receivedServerError(string details) { callMethod(0, "receivedServerError", [details]); } trigger_pong(string moduleName) { callMethod(0, "pong", [moduleName]); } // ====================== XMLRPC request constants: ====================== \\ // Sent by the client to the server when the server polls // and the client has no data to send. integer NO_REQUESTS = 5000; // Sent by the server to the client when a poll occurs. integer SERVER_POLL = 6000; // Sent by client to server requesting that the server stop polling. integer STOP_POLLING = 6001; // Sent by server to client that tells client that the RPC string contains multiple command seperated // by COMMAND_SEPERATOR. integer MULTI_COMMAND = 6002; // Sent by server to client requesting that the client restart. integer TERMINAL_RESET = 6003; // Sent by the client to the server (or external entity) requesting a // piece of content: integer GET_CATEGORY = 7001; // String value: catagory requested integer GET_NOTECARD = 7002; // String value: Notecard name requested. integer GET_PREV = 7003; // String value: "" integer GET_NEXT = 7004; // String value: "" integer GET_KEYWORD_SEARCH = 7005; // String value: Keyword requested. integer GET_SUMMARY = 7007; integer GET_BUTTON = 7008; // Sent by the server to the client, between MENU_BEGIN and MENU_END // requesting that it display text. integer SET_CATEGORY = 8001; // String value: Catagory name to display. integer SET_NOTECARD = 8002; // String value: Notecard name to display. integer SET_PREV = 8003; // String value ignored integer SET_NEXT = 8004; // String value ignored. integer SET_KEYWORD_SEARCH = 8005; // String value ignored. integer SET_TEXT = 8006; // String value: any text. integer SET_SUMMARY = 8007; integer SET_BUTTON = 8008; // Sent by the server to the client specifying the start // and end of a menu. integer MENU_BEGIN = 9001; // String value: Menu title integer MENU_END = 9002; // ====================== /XMLRPC request constants ====================== \\ // For use with the MULTI_COMMAND RPC integer. string COMMAND_SEPERATOR = "]^["; // Notecard title parameter seperator: string NOTECARD_SEPERATOR = " || "; // Category name and key seperator: string CATEGORY_SEPERATOR = "]_["; // For wordwrap: integer charsPerLine; string curMenuName = ""; triggerAppropriateEventFor(integer commandConstant, string commandData) { if (commandConstant == TERMINAL_RESET) { trigger_receivedTerminalReset(); } else if (commandConstant == MENU_BEGIN) { list parsedCommandData = llParseStringKeepNulls(commandData, [CATEGORY_SEPERATOR], []); string menuTitle = llList2String(parsedCommandData, 0); string menuName = llList2String(parsedCommandData, 1); // TODO: Differentiate between menu names and titles. trigger_receivedMenuBegin(menuName, menuTitle); curMenuName = menuName; } else if (commandConstant == MENU_END) { list parsedCommandData = llParseStringKeepNulls(commandData, [CATEGORY_SEPERATOR], []); //string menuTitle = llList2String(parsedCommandData, 0); string menuName = llList2String(parsedCommandData, 1); trigger_receivedMenuEnd(menuName); } else if (commandConstant == SET_CATEGORY) { list parsedCommandData = llParseStringKeepNulls(commandData, [CATEGORY_SEPERATOR], []); string categoryName = llList2String(parsedCommandData, 0); integer categoryID = (integer) llList2String(parsedCommandData, 1); trigger_receivedMenuCategory(curMenuName, categoryName, categoryID); } else if (commandConstant == SET_NOTECARD) { list parsedCommandData = llParseStringKeepNulls(commandData, [NOTECARD_SEPERATOR], []); string contentName = llList2String(parsedCommandData, 0); integer contentID = (integer) llList2String(parsedCommandData, 1); trigger_receivedMenuContent(curMenuName, contentName, contentID); } else if (commandConstant == SET_KEYWORD_SEARCH) { list parsedCommandData = llParseStringKeepNulls(commandData, [CATEGORY_SEPERATOR], []); string displayText = llList2String(parsedCommandData, 0); trigger_receivedMenuKeywordSearch(curMenuName, displayText); } else if (commandConstant == SET_TEXT) { if (llStringLength(commandData) > charsPerLine) { // Note, this is basically the same as the wordwrap function above // but to make everything appear to run faster, I unrolled it and // stuck it here. integer lineMaxLength = charsPerLine; list words = llParseString2List(commandData, [" "], []); string curLine = ""; integer wordIndex; // I use llGetListLength in the iteration because the length of words // may increase. // Quick speedup tip: Make the llGetListLength call update a variable // when the list is mutated. for (wordIndex = 0; wordIndex < llGetListLength(words); wordIndex++) { string word = llList2String(words, wordIndex); integer wordLength = llStringLength(word); if (wordLength > lineMaxLength) { // Get the chunk of the word that will fit on the screen // minus one character, for the dash. string truncated = llGetSubString(word, 0, lineMaxLength - 2) + "-"; string rest = llDeleteSubString(word, 0, lineMaxLength - 2); // Add them to the list after the current index, we want them // to be handled like normal words. words = llListInsertList(words, [truncated], wordIndex + 1); words = llListInsertList(words, [rest], wordIndex + 2); } else { integer curLineLength = llStringLength(curLine); integer lenAfterAdd = curLineLength + wordLength; if (lenAfterAdd > lineMaxLength) { trigger_receivedMenuText(curMenuName, curLine); curLine = ""; words = llListInsertList(words, [word], wordIndex + 1); } else if (lenAfterAdd == lineMaxLength) { trigger_receivedMenuText(curMenuName, curLine + word); curLine = ""; } else { curLine += word; curLine += " "; } } } if (curLine != "") trigger_receivedMenuText(curMenuName, curLine); } else { trigger_receivedMenuText(curMenuName, commandData); } } else if (commandConstant == SET_SUMMARY) { list parsedCommandData = llParseStringKeepNulls(commandData, [CATEGORY_SEPERATOR], []); string summaryName = llList2String(parsedCommandData, 0); integer summaryID = (integer) llList2String(parsedCommandData, 1); trigger_receivedMenuSummary(curMenuName, summaryName, summaryID); } else if (commandConstant == MULTI_COMMAND) { list commandsInData = llParseString2List(commandData, [COMMAND_SEPERATOR], []); list intDatas = list2ListStrided(commandsInData, 0, -1, 2); list strDatas = list2ListStrided(commandsInData, 1, -1, 2); integer i; integer numCommands = llGetListLength(intDatas); for(i = 0; i < numCommands; i++) { integer command = (integer) llList2String(intDatas, i); string data = llList2String(strDatas, i); triggerAppropriateEventFor(command, data); } } } string this; default { state_entry() { this = llGetScriptName(); trigger_getMenuTextAreaSize(); } link_message(integer sender, integer num, string parameters, key methodName) { if (methodName == "getMenuTextAreaSize_ret") { list paramList = stringToList(parameters); // Method signature: // getMenuTextAreaSize_ret(integer width, integer height) integer width = (integer) llList2String(paramList, 0); integer height = (integer) llList2String(paramList, 1); charsPerLine = width; state main; } } } state main { link_message(integer sender, integer num, string parameters, key methodName) { if (methodName == "receivedRpcData") { list paramList = stringToList(parameters); // Method signature: // receivedRpcData(integer iData, string sData) integer iData = (integer) llList2String(paramList, 0); string sData = llList2String(paramList, 1); // Work is done in triggerAppropriateEventFor because I can // use recursion there. Recursion is necessesary for the MULTI_COMMAND command. // The only way I can recurse the link_message event is by triggering // another link_message, further clogging the limited script2script message // space. triggerAppropriateEventFor(iData, sData); } else if (methodName == "receivedEmailData") { list paramList = stringToList(parameters); // Method signature: // receivedEmailData(integer timestamp, string sender, string subject, string message) integer timestamp = (integer) llList2String(paramList, 0); string sender = llList2String(paramList, 1); string subject = llList2String(paramList, 2); string message = llList2String(paramList, 3); if (subject == "ERROR") { trigger_receivedServerError(message); } } else if (methodName == "moduleReady") { list paramList = stringToList(parameters); string module = llList2String(paramList, 0); if (module == this) returnValue(num, methodName, [TRUE]); } } }