normalized perms on all files, to avoid relying on any stored executable bits in...
[feisty_meow.git] / huffware / huffotronic_scripts / data_cow_v3.3.txt
1 
2 // huffware script: data cow, by fred huffhines.
3 //
4 // a data cow is a script that manages a list of text items.  it allows an object
5 // to offload the memory burden of managing a list of items, since LSL is very tight
6 // on memory, even when compiled to mono.
7 //
8 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
9 // do not use it in objects without fully realizing you are implicitly accepting that license.
10 //
11
12 // global variables.
13
14 list the_list;
15     // the main list that we manage here.  we support adding to it, retrieving it
16     // and modifying it via our API.
17 list the_names;
18     // the short names for each item in our list.  we are basically supporting a
19     // symbol table data structure here.
20
21 // link message API for the data cow library.
22 //////////////
23 // do not redefine these constants.
24 integer DATA_COW_HUFFWARE_ID = 10017;
25     // the unique id within the huffware system for the jaunt script to
26     // accept commands on.  this is used in llMessageLinked as the num parameter.
27 string HUFFWARE_PARM_SEPARATOR = "{~~~}";
28     // this pattern is an uncommon thing to see in text, so we use it to separate
29     // our commands in link messages.
30 ///string HUFFWARE_ITEM_SEPARATOR = "{|||}";
31     // used to separate lists of items from each other when stored inside a parameter.
32     // this allows lists to be passed as single string parameters if needed.
33 integer REPLY_DISTANCE = 100008;  // offset added to service's huffware id in reply IDs.
34 //////////////
35 //
36 string RESET_LIST_COMMAND = "reset_L";
37     // flushes out the currently held list.  does not send a reply.
38 string ADD_ITEM_COMMAND = "add_I";
39     // adds items to the list.  this is a list of pairs of name/value, where the name is
40     // how the item will be looked up from the list, and the value is the contents to be stored.
41     // this command has no reply.
42 string REMOVE_ITEM_COMMAND = "rm_I";
43     // accepts a list of names for items.  all the mentioned ones will be removed from the list.
44     // this command also has no reply.
45 string GET_COW_LENGTH = "get_Lc";
46     // returns a single integer which is the length of the cow's list currently.
47 string GET_ITEM_COMMAND = "get_I";
48     // retrieves the item's contents for a given name.  first parm is the name.  if there
49     // are other parameters, then they are taken as other items to return also.
50     // the return data will be pairs of <name, entry> for each of the names in the request
51     // that is not empty.  note that you can use an integer index by prefacing it with a
52     // '#' sign.  for example, asking for item "#32" will return index 32 in the list of items.
53 string TAGGED_GET_ITEM_COMMAND = "get_T";
54     // like get item, except the first parameter is an identifier that the caller wants to
55     // use to tag this request.  the other parameters are still taken as names.  the response
56     // will contain the tag as the first item, then the <name, entry> pairs that were found.
57 //////////////
58
59 // sets up the data cow for business.
60 initialize()
61 {
62     // flush all contents.
63     the_list = [];
64     the_names = [];
65 }
66
67 // find a named item in our list, if it exists.
68 integer lookup_item(string name)
69 {
70     integer indy;
71     if (llGetSubString(name, 0, 0) == "#") {
72         // our special sign for just using an index.
73         indy = (integer)llGetSubString(name, 1, -1);
74         if ( (indy >= 0) && (indy < llGetListLength(the_names)) ) return indy;
75     } else {
76         for (indy = 0; indy < llGetListLength(the_names); indy++) {
77             if (name == llList2String(the_names, indy)) return indy;  // we know where it is now.
78         }
79     }
80     return -1;  // never found it.
81 }
82
83 // places a named item in our list.  if there's an existing item with
84 // that name, then it's replaced.
85 integer global_mem_complained = FALSE;
86 add_item(string name, string content)
87 {
88     if (llGetFreeMemory() < 2000) {
89         if (!global_mem_complained) {
90             global_mem_complained = TRUE;
91             llOwnerSay("out of memory at " + name);
92         }
93         return;
94     }
95     integer current = lookup_item(name);
96     if (current < 0) {
97 //llOwnerSay("new item: " + name);
98         // this is a new item.
99         the_names += name;
100         the_list += content;
101     } else {
102 //llOwnerSay("reusing slot " + (string)current + " for " + name);
103         // this is an old item to be replaced.
104         the_list = chop_list(the_list, 0, current - 1)
105             + [ content ]
106             + chop_list(the_list, current + 1, llGetListLength(the_list) - 1);
107     }
108 //log_it("mem=" + (string)llGetFreeMemory());
109 }
110
111 // deletes an existing item if possible.  TRUE is returned on success.
112 integer remove_item(string name)
113 {
114     integer indy = lookup_item(name);
115     if (indy < 0) return FALSE;  // not present.
116     // found our sad whackee.  we know the name and content should be
117     // stored at the exact same index in the two lists.
118     the_names = chop_list(the_names, 0, indy - 1)
119         + chop_list(the_names, indy + 1, llGetListLength(the_names) - 1);
120     the_list = chop_list(the_list, 0, indy - 1)
121         + chop_list(the_list, indy + 1, llGetListLength(the_list) - 1);
122     return TRUE;  // all done here.
123 }
124
125 // supports our link message API by answering requests from clients.
126 handle_link_message(integer sender, integer num, string msg, key id)
127 {
128     if (num != DATA_COW_HUFFWARE_ID) return;  // not for us.
129 //log_it("hlm--mem free=" + (string)llGetFreeMemory());
130 //log_it("rcvd: " + msg + " " + (string)num + " " + (string)id);
131     if (msg == RESET_LIST_COMMAND) {
132         initialize();
133     } else if (msg == ADD_ITEM_COMMAND) {
134         list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
135         integer indy;
136         for (indy = 0; indy < llGetListLength(parms); indy += 2) {
137             add_item(llList2String(parms, indy), llList2String(parms, indy + 1));
138         }
139     } else if (msg == REMOVE_ITEM_COMMAND) {
140         list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
141         integer indy;
142         for (indy = 0; indy < llGetListLength(parms); indy++) {
143             remove_item(llList2String(parms, indy));
144         }
145     } else if (msg == GET_COW_LENGTH) {
146         list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
147         string tag = llList2String(parms, 0);
148         list to_reply;
149         if (tag != "") to_reply = [ tag ];
150         to_reply += [ llGetListLength(the_list) ];
151         send_reply(LINK_THIS, to_reply, msg);
152     } else if ( (msg == GET_ITEM_COMMAND) || (msg == TAGGED_GET_ITEM_COMMAND) ) {
153         list parms = llParseString2List(id, [HUFFWARE_PARM_SEPARATOR], []);
154         integer indy;
155         string tag;
156         if (msg == TAGGED_GET_ITEM_COMMAND) {
157             // extract the tag, if they want that type of command handling.
158             tag = llList2String(parms, 0);
159             parms = llDeleteSubList(parms, 0, 0);
160         }
161         list to_reply;
162         // make sure we return the tag if they gave us one.
163         if (tag != "") to_reply = [ tag ];
164         for (indy = 0; indy < llGetListLength(parms); indy++) {
165             // iterate through the parameters and reply about any that we find.
166             integer found_posn = lookup_item(llList2String(parms, indy));
167             if (found_posn >= 0) {
168                 // this item did exist.
169                 to_reply += [ llList2String(the_names, found_posn), llList2String(the_list, found_posn) ];
170             }
171         }
172         send_reply(LINK_THIS, to_reply, msg);
173     } 
174 }
175
176 //////////////
177 // from hufflets...
178 //
179 //integer debug_num = 0;
180
181 // a debugging output method.  can be disabled entirely in one place.
182 //log_it(string to_say)
183 //{
184 //    debug_num++;
185 //    // tell this to the owner.    
186 //    llOwnerSay(llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
187 //    // say this on open chat, but use an unusual channel.
188 ////    llSay(108, llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
189 //}
190
191 // returns the portion of the list between start and end, but only if they are
192 // valid compared with the list length.  an attempt to use negative start or
193 // end values also returns a blank list.
194 list chop_list(list to_chop, integer start, integer end)
195 {
196     integer last_len = llGetListLength(to_chop) - 1;
197     if ( (start < 0) || (end < 0) || (start > last_len) || (end > last_len) ) return [];
198     return llList2List(to_chop, start, end);
199 }
200
201 // a simple version of a reply for a command that has been executed.  the parameters
202 // might contain an outcome or result of the operation that was requested.
203 send_reply(integer destination, list parms, string command)
204 {
205     llMessageLinked(destination, DATA_COW_HUFFWARE_ID + REPLY_DISTANCE,
206         command,  llDumpList2String(parms, HUFFWARE_PARM_SEPARATOR));
207 }
208
209 //
210 // end hufflets
211 //////////////
212
213 default
214 {
215     state_entry() { if (llSubStringIndex(llGetObjectName(),  "huffotronic") < 0) state real_default; }
216     on_rez(integer parm) { state rerun; }
217 }
218 state rerun { state_entry() { state default; } }
219
220 state real_default
221 {
222     state_entry() { initialize(); }
223
224     link_message(integer sender, integer num, string msg, key id)
225     { handle_link_message(sender, num, msg, id); }
226 }
227