corrade-lsl-templates – Rev 40
?pathlinks?
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: CC BY 2.0 //
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
if(llStringLength(data) == 0) return "";
if(llStringLength(k) == 0) return "";
list a = llParseStringKeepNulls(data, ["&", "="], []);
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
if(i != -1) return llList2String(a, 2*i+1);
return "";
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueEncode(list data) {
list k = llList2ListStrided(data, 0, -1, 2);
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
data = [];
do {
data += llList2String(k, 0) + "=" + llList2String(v, 0);
k = llDeleteSubList(k, 0, 0);
v = llDeleteSubList(v, 0, 0);
} while(llGetListLength(k) != 0);
return llDumpList2String(data, "&");
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
// escapes a string in conformance with RFC1738
string wasURLEscape(string i) {
string o = "";
do {
string c = llGetSubString(i, 0, 0);
i = llDeleteSubString(i, 0, 0);
if(c == "") jump continue;
if(c == " ") {
o += "+";
jump continue;
}
if(c == "\n") {
o += "%0D" + llEscapeURL(c);
jump continue;
}
o += llEscapeURL(c);
@continue;
} while(i != "");
return o;
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
// unescapes a string in conformance with RFC1738
string wasURLUnescape(string i) {
return llUnescapeURL(
llDumpList2String(
llParseString2List(
llDumpList2String(
llParseString2List(
i,
["+"],
[]
),
" "
),
["%0D%0A"],
[]
),
"\n"
)
);
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
list wasCSVToList(string csv) {
list l = [];
list s = [];
string m = "";
do {
string a = llGetSubString(csv, 0, 0);
csv = llDeleteSubString(csv, 0, 0);
if(a == ",") {
if(llList2String(s, -1) != "\"") {
l += m;
m = "";
jump continue;
}
m += a;
jump continue;
}
if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
m += a;
csv = llDeleteSubString(csv, 0, 0);
jump continue;
}
if(a == "\"") {
if(llList2String(s, -1) != a) {
s += a;
jump continue;
}
s = llDeleteSubList(s, -1, -1);
jump continue;
}
m += a;
@continue;
} while(csv != "");
// postcondition: length(s) = 0
return l + m;
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
///////////////////////////////////////////////////////////////////////////
string wasListToCSV(list l) {
list v = [];
do {
string a = llDumpList2String(
llParseStringKeepNulls(
llList2String(
l,
0
),
["\""],
[]
),
"\"\""
);
if(llParseStringKeepNulls(
a,
[" ", ",", "\n", "\""], []
) !=
(list) a
) a = "\"" + a + "\"";
v += a;
l = llDeleteSubList(l, 0, 0);
} while(l != []);
return llDumpList2String(v, ",");
}
///////////////////////////////////////////////////////////////////////////
// SQL STATEMENT DEFINITIONS //
///////////////////////////////////////////////////////////////////////////
string SQL_INSERT_VISITOR = "INSERT OR REPLACE INTO visitors (firstname, lastname, lastseen, time, memory) VALUES(:firstname, :lastname, :time, COALESCE((SELECT time FROM visitors WHERE firstname=:firstname AND lastname=:lastname), 0) + 1, :memory)";
string SQL_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS visitors ('firstname' TEXT NOT NULL, 'lastname' TEXT NOT NULL, 'lastseen' TEXT NOT NULL, 'time' INTEGER NOT NULL, 'memory' INTEGER NOT NULL, PRIMARY KEY ('firstname', 'lastname'))";
string SQL_COUNT_VISITORS = "SELECT COUNT(*) AS 'Visits', AVG(time) AS 'Time', AVG(memory) AS 'Memory' FROM visitors";
string SQL_SELECT_VISITOR = "SELECT * FROM visitors ORDER BY lastseen DESC LIMIT 1 OFFSET :offset";
string SQL_DROP_TABLE = "DROP TABLE IF EXISTS visitors";
///////////////////////////////////////////////////////////////////////////
// corrade data
key CORRADE = NULL_KEY;
string GROUP = "";
string PASSWORD = "";
// for holding the callback URL
string callback = "";
// for notecard reading
integer line = 0;
// key-value data will be read into this list
list tuples = [];
list agents = [];
string agentName = "";
default {
state_entry() {
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
llOwnerSay("Sorry, could not find a configuration inventory notecard.");
return;
}
// DEBUG
llOwnerSay("Reading configuration file...");
llGetNotecardLine("configuration", line);
}
dataserver(key id, string data) {
if(data == EOF) {
// invariant, length(tuples) % 2 == 0
if(llGetListLength(tuples) % 2 != 0) {
llOwnerSay("Error in configuration notecard.");
return;
}
CORRADE = llList2Key(
tuples,
llListFindList(
tuples,
[
"corrade"
]
)
+1
);
if(CORRADE == NULL_KEY) {
llOwnerSay("Error in configuration notecard: corrade");
return;
}
GROUP = llList2String(
tuples,
llListFindList(
tuples,
[
"group"
]
)
+1
);
if(GROUP == "") {
llOwnerSay("Error in configuration notecard: group");
return;
}
PASSWORD = llList2String(
tuples,
llListFindList(
tuples,
[
"password"
]
)
+1
);
if(PASSWORD == "") {
llOwnerSay("Error in configuration notecard: password");
return;
}
// DEBUG
llOwnerSay("Read configuration notecard...");
tuples = [];
state url;
}
if(data == "") jump continue;
integer i = llSubStringIndex(data, "#");
if(i != -1) data = llDeleteSubString(data, i, -1);
list o = llParseString2List(data, ["="], []);
// get rid of starting and ending quotes
string k = llDumpList2String(
llParseString2List(
llStringTrim(
llList2String(
o,
0
),
STRING_TRIM),
["\""], []
), "\"");
string v = llDumpList2String(
llParseString2List(
llStringTrim(
llList2String(
o,
1
),
STRING_TRIM),
["\""], []
), "\"");
if(k == "" || v == "") jump continue;
tuples += k;
tuples += v;
@continue;
llGetNotecardLine("configuration", ++line);
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
state url {
state_entry() {
// DEBUG
llOwnerSay("Requesting URL...");
llRequestURL();
}
http_request(key id, string method, string body) {
if(method != URL_REQUEST_GRANTED) return;
callback = body;
// DEBUG
llOwnerSay("Got URL...");
state detect;
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
state detect {
state_entry() {
// DEBUG
llOwnerSay("Detecting if Corrade is online...");
llSetTimerEvent(5);
}
timer() {
llRequestAgentData(CORRADE, DATA_ONLINE);
}
dataserver(key id, string data) {
if(data != "1") {
// DEBUG
llOwnerSay("Corrade is not online, sleeping...");
llSetTimerEvent(30);
return;
}
state initialize;
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
state initialize {
state_entry() {
// DEBUG
llOwnerSay("Creating the database if it does not exist...");
llInstantMessage(
CORRADE,
wasKeyValueEncode(
[
"command", "database",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"SQL", wasURLEscape(SQL_CREATE_TABLE),
"callback", wasURLEscape(callback)
]
)
);
// alarm 60
llSetTimerEvent(60);
}
timer() {
// DEBUG
llOwnerSay("Timeout creating table...");
llResetScript();
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
if(wasKeyValueGet("command", body) == "database") {
integer success = wasKeyValueGet("success", body) == "True";
if(!success) {
// DEBUG
llOwnerSay("Failed to create the table: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
return;
}
// DEBUG
llOwnerSay("Table created...");
state show;
}
}
link_message(integer sender_num, integer num, string str, key id) {
if(str == "reset") {
state reset;
}
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
state show {
state_entry() {
// DEBUG
llOwnerSay("Updating display with the number of recorded visitors...");
llInstantMessage(
CORRADE,
wasKeyValueEncode(
[
"command", "database",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"SQL", wasURLEscape(SQL_COUNT_VISITORS),
"callback", wasURLEscape(callback)
]
)
);
// alarm 60
llSetTimerEvent(60);
}
timer() {
// DEBUG
llOwnerSay("Timeout reading rows from visitors table...");
llResetScript();
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
if(wasKeyValueGet("command", body) == "database") {
integer success = wasKeyValueGet("success", body) == "True";
if(!success) {
// DEBUG
llOwnerSay("Failed to enumerate visitors: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
llResetScript();
}
list data = wasCSVToList(
wasURLUnescape(
wasKeyValueGet(
"data",
body
)
)
);
// DEBUG
//llOwnerSay("Data: " + llDumpList2String(data, ","));
integer visits = llList2Integer(
data,
llListFindList(
data,
(list)"Visits"
) + 1
);
integer time = llList2Integer(
data,
llListFindList(
data,
(list)"Time"
) + 1
);
integer memory = llList2Integer(
data,
llListFindList(
data,
(list)"Memory"
) + 1
);
// Send message to xyz script.
llMessageLinked(LINK_ROOT, 204000, "V:" + (string)visits, "0");
llMessageLinked(LINK_ROOT, 204000, "T:" + (string)time + "m", "1");
llMessageLinked(LINK_ROOT, 204000, "M:" + (string)memory + "k", "2");
// Get the list of agents.
agents = llGetAgentList(AGENT_LIST_REGION, []);
state insert_trampoline;
}
}
link_message(integer sender_num, integer num, string str, key id) {
if(str == "reset")
state reset;
if(str == "display") {
line = 0;
state display;
}
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
state insert_trampoline {
state_entry() {
llSetTimerEvent(1);
}
timer() {
state insert;
}
link_message(integer sender_num, integer num, string str, key id) {
if(str == "reset")
state reset;
if(str == "display") {
line = 0;
state display;
}
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
state insert {
state_entry() {
// Once the list is empty, go back to display.
if(llGetListLength(agents) == 0) {
state show;
}
key agent = llList2Key(agents, 0);
agents = llDeleteSubList(agents, 0, 0);
list name = llParseString2List(llKey2Name(agent), [" "], []);
if(llGetListLength(name) != 2) {
return;
}
string firstname = llList2String(name, 0);
string lastname = llList2String(name, 1);
agentName = firstname + " " + lastname;
string memory = (string)(
(integer)(
llList2Float(
llGetObjectDetails(
agent,
[OBJECT_SCRIPT_MEMORY]
),
0
)
/
1024 /*in kib, to mib 1048576*/
)
);
// DEBUG
//llOwnerSay("Memory: " + memory);
// The command sent to Corrade responsible for adding a visitor
// or updating the data for the visitor in case the visitor is
// already entered into the visitors table. This is performed
// with an INSER OR REPLACE sqlite command given the first name
// and the last name of the avatar are unique primary keys.
llInstantMessage(
CORRADE,
wasKeyValueEncode(
[
"command", "database",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"SQL", wasURLEscape(SQL_INSERT_VISITOR),
"data", wasListToCSV(
[
"firstname",
firstname,
"lastname",
lastname,
"time",
llGetTimestamp(),
"memory",
memory
]
),
"callback", wasURLEscape(callback)
]
)
);
// Command timeout.
llSetTimerEvent(60);
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
// DEBUG
//llOwnerSay(wasURLUnescape(body));
if(wasKeyValueGet("command", body) == "database") {
integer success = wasKeyValueGet("success", body) == "True";
if(!success) {
// DEBUG
llOwnerSay("Failed to insert visitor " + agentName + " due to: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
state insert_trampoline;
}
// DEBUG
llOwnerSay("Processed visitor named " + agentName + "...");
state insert_trampoline;
}
}
timer() {
// DEBUG
llOwnerSay("Inserting visitors has timed out, resetting...");
state insert_trampoline;
}
link_message(integer sender_num, integer num, string str, key id) {
if(str == "reset") {
state reset;
}
if(str == "display") {
line = 0;
state display;
}
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
state display_trampoline {
state_entry() {
++line;
state display;
}
link_message(integer sender_num, integer num, string str, key id) {
if(str == "reset")
state reset;
}
}
state display {
state_entry() {
llInstantMessage(
CORRADE,
wasKeyValueEncode(
[
"command", "database",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"SQL", wasURLEscape(SQL_SELECT_VISITOR),
"data", wasListToCSV(
[
"offset",
(string)line
]
),
"callback", wasURLEscape(callback)
]
)
);
// alarm 60
llSetTimerEvent(60);
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
if(wasKeyValueGet("command", body) == "database") {
integer success = wasKeyValueGet("success", body) == "True";
if(!success) {
// DEBUG
llOwnerSay("Failed to query the visitors table: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
return;
}
// Grab the data key if it exists.
string dataKey = wasURLUnescape(
wasKeyValueGet(
"data",
body
)
);
// We got no more rows, so switch back to scanning.
if(dataKey == "")
state show;
list data = wasCSVToList(dataKey);
string firstname = llList2String(
data,
llListFindList(
data,
(list)"firstname"
) + 1
);
string lastname = llList2String(
data,
llListFindList(
data,
(list)"lastname"
) + 1
);
string lastseen = llList2String(
data,
llListFindList(
data,
(list)"lastseen"
) + 1
);
llOwnerSay(firstname + " " + lastname + " @ " + lastseen);
state display_trampoline;
}
}
link_message(integer sender_num, integer num, string str, key id) {
if(str == "reset") {
state reset;
}
// If the display button is pressed again, go back to the display and stop.
if(str == "display") {
state show;
}
}
timer() {
// DEBUG
llOwnerSay("Timeout reading rows from visitors table...");
llResetScript();
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
state reset {
state_entry() {
// DEBUG
llOwnerSay("Resetting all visitors...");
llInstantMessage(
CORRADE,
wasKeyValueEncode(
[
"command", "database",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"SQL", wasURLEscape(SQL_DROP_TABLE),
"callback", wasURLEscape(callback)
]
)
);
// alarm 60
llSetTimerEvent(60);
}
timer() {
// DEBUG
llOwnerSay("Timeout deleting database...");
llResetScript();
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
if(wasKeyValueGet("command", body) == "database") {
integer success = wasKeyValueGet("success", body) == "True";
if(!success) {
// DEBUG
llOwnerSay("Failed to drop the visitors table: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
llResetScript();
}
// DEBUG
llOwnerSay("Table dropped...");
llResetScript();
}
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}