2 // huffware script: huff-update server, by fred huffhines.
4 // this script is the server side of the update process. it should reside in an object that
5 // has all the newest versions of scripts and objects that will be updated. when rezzed, and
6 // at periodic intervals, it announces on a private chat channel that updates are available.
7 // when objects respond that they might like an update, it tells them the scripts that it has
8 // stored inside of it. if any of those scripts are an older version inside the client
9 // (update requesting) object, then the client will request the newer versions. the server
10 // object will stuff them into it and tell them to start running.
12 // this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
13 // do not use it in objects without fully realizing you are implicitly accepting that license.
16 integer IS_OPENSIM = TRUE; // must be set to true for opensim, false for second life.
18 integer DEBUGGING = FALSE; // set this to true for noisier diagnostics.
22 // updater dependency section:
23 // should be moved to a notecard!!!
25 // format is a list of strings, where each string has a pair wrapped by the
26 // huffware item separators. the pair contains (1) the basename of a script
27 // that has a new dependency and (2) the basename of that new dependency.
28 list known_script_dependencies;
35 known_script_dependencies = [
36 llDumpList2String(["jaunt wik rez", "jaunt config funcs"], ITEM_LIST_SEPARATOR),
37 llDumpList2String(["jaunt wik rez", "data cow"], ITEM_LIST_SEPARATOR),
38 llDumpList2String(["jaunt wik rez", "jaunt rezolator"], ITEM_LIST_SEPARATOR),
39 llDumpList2String(["remotely personable", "inventory exchanger"], ITEM_LIST_SEPARATOR),
40 llDumpList2String(["animote main driver", "exchange driver hudimation"], ITEM_LIST_SEPARATOR),
41 llDumpList2String(["animote main driver", "avatar timer manager"], ITEM_LIST_SEPARATOR),
42 llDumpList2String(["animote main driver", "avatar choice memory"], ITEM_LIST_SEPARATOR),
43 llDumpList2String(["giftorse", "particle projector"], ITEM_LIST_SEPARATOR),
44 llDumpList2String(["huff-search brainiac", "searchbert armature"], ITEM_LIST_SEPARATOR),
47 //special purpose -- removes the updater from the elevator buttons...
48 //llDumpList2String(["comfortable sitting", "zap updater from elevators"], ITEM_LIST_SEPARATOR),
49 // => do not uncomment unless you want your elevators to shed their ability to update.
52 // this allows us to add or remove items above at will without complaints about comma.
53 llDumpList2String(["xyzzy", "hopefully-never-matches"], ITEM_LIST_SEPARATOR)
60 // global constants...
62 integer USER_COMMAND_CHANNEL = 4; // channel where we listen to user commands.
64 float ANNOUNCEMENT_PERIOD = 14.0; // how often we tell other objects that we have tasty treats.
66 integer UPDATE_ANNOUNCEMENT_CHANNEL = -420108; // used by server to brag about itself.
67 integer OLD_REQUEST_INVENTORY_CHANNEL = -421008; // used by clients to request an update list.
69 string UPDATE_ANNOUNCEMENT_PREFIX = "#huff-update#"; // first part of any announcement.
70 string REQUEST_INVENTORY_PREFIX = "#huff-reqinv#"; // first part of request for inventory list.
71 string REPORT_AVAILABLE_SCRIPTS = "#scripts#"; // server's keyword to let client know script inventory.
72 string REQUEST_SCRIPT_UPDATE = "#updatego#"; // keyword used by client to request some updates.
73 string SHUT_THEM_DOWN = "#huffdown#"; // server tells client to stop any non-updater scripts.
74 string READY_TO_UPDATE = "#listoneeds#"; // the client tells the server the scripts it wants.
75 string SCRIPTS_ARE_CURRENT = "#gottemthx#"; // client says this when all new scripts are in place.
76 string START_THEM_UP = "#huffup#"; // server tells client to start up other scripts again.
77 string DONE_UPDATING = "#finito#"; // the client is done updating.
78 string BUSY_BUSY = "#busymuch#"; // a signal that the server is too busy to update us.
80 integer UPDATER_SCRIPT_PIN = -1231008; // the hook for our scripts to be modified.
82 string RESTART_UPDATER_COMMAND = "#restart"; // said in open chat to recrank the updater.
83 string SHOW_SCRIPTS_COMMAND = "#show"; // said in open chat to list out the scripts.
84 string SHUTDOWN_COMMAND = "#destroy"; // shuts down object and destroys it.
86 string UPDATER_PARM_SEPARATOR = "~~~";
87 // three tildes is an uncommon thing to have otherwise, so we use it to separate
88 // our commands in linked messages.
90 string SCRIPT_DEPENDENCY_MARK = "DEP"; // signals that a dependency is coming.
92 integer ENTRIES_PER_LINE = 4; // number of items from a list shown on one line of text.
94 integer MAXIMUM_ACTIVE_CLIENTS = 5; // number of conversations we will allow at a time.
96 float LONGEST_SLACK_PER_CLIENT = 84.0;
97 // we allow a client to be out of touch with us for this many seconds. after that,
98 // we decide it's deceased and remove it from our list.
100 integer MESSAGE_SIZE_LIMIT = 800; // longest thing that can be safely said without clipping (guess).
102 float SCRIPT_LIST_PAUSE_INTERVAL = 1.4; // pause between large chunks of update text.
104 string ITEM_LIST_SEPARATOR = "``"; // separates dependencies.
106 float CHANGED_INVENTORY_SNOOZER = 7.0;
107 // the number of seconds we sleep once we see an inventory change. we don't want to
108 // react to this immediately. this overrides the normal announcement cycle until it's
109 // dealt with by the timer.
111 string CONTINUANCE_MARKER = "...";
112 // a string sent when the update list is too long and needs to be continued in another chat.
114 string UPDATER_BASE_NAME = "huff-update client";
115 // the name of the updater script that keeps everything in sync.
119 // global variables...
121 list scripts_available; // the list of scripts we have in our inventory for distribution.
122 list objects_available; // list of objects for handing out.
124 list active_clients; // list of keys for clients that are updating currently.
125 list active_update_channels; // active conversations on client chosen channels.
126 list active_listen_ids; // the ids for the listener on those channels.
127 list active_timestamps; // tracks when the client was last active.
129 integer inventory_request_channel; // our personal channel that the update client should talk with.
131 integer dealing_with_change; // has the inventory changed? we will deal with this in the timer.
133 // displays the status of the update server.
134 show_status(key who_says)
136 string title = "[mem free=" + (string)llGetFreeMemory() + "]";
137 title = "Listening for requests on channel " + (string)inventory_request_channel;
138 if (llGetListLength(active_update_channels))
139 title += "\nactive channels="
140 + dump_list(active_update_channels, FALSE, ENTRIES_PER_LINE);
142 title += "\nNo channels active.";
144 if (llGetListLength(active_clients))
145 title += "\nactive clients=" + dump_keyed_list(active_clients, TRUE, 2);
147 title += "\nNo clients active.";
149 if (llGetOwner() == who_says) {
150 string addition = " ";
151 if (USER_COMMAND_CHANNEL != 0) addition = "/" + (string)USER_COMMAND_CHANNEL + " ";
152 title += "\n[ \"" + addition + SHOW_SCRIPTS_COMMAND + "\" lists all the scripts, "
153 + "\"" + addition + SHUTDOWN_COMMAND + "\" zaps the updater, "
154 + "\"" + addition + RESTART_UPDATER_COMMAND + "\" refreshes the updater ]";
160 // plink a quietous string of resonance...
163 string snd = llGetInventoryName(INVENTORY_SOUND, indy);
164 if (snd != "") llTriggerSound(snd, 1.0);
167 // list out the scripts that the server's object contains.
170 string title = (string)llGetListLength(scripts_available) + " scripts available:";
171 show_list(title, scripts_available);
172 title = (string)llGetListLength(objects_available) + " objects available:";
173 show_list(title, objects_available);
176 // lets the client know that we're here and have some scripts available.
179 if (llGetListLength(active_clients) < MAXIMUM_ACTIVE_CLIENTS) {
180 // only announce if we're not already booked up.
181 llWhisper(UPDATE_ANNOUNCEMENT_CHANNEL, UPDATE_ANNOUNCEMENT_PREFIX + (string)inventory_request_channel);
185 // lifted from "clear text and effects" script; should be nearly identical
186 // to that, except that we set the texture animation.
189 llSetText("", <0,0,0>, 0); // clear any text above object.
190 llSetSitText("Sit Here"); // reset sit text to default state.
191 llSetTouchText("Touch"); // similarly for touch.
192 llSitTarget(ZERO_VECTOR, ZERO_ROTATION); // reset sit target position.
193 llParticleSystem([]); // turn off all particles.
194 llSensorRemove(); // stop any running sensors.
195 llTargetOmega(<0.0, 0.0, 0.0>, 0, 1.0); // stop rotations of object.
196 llSetLinkAlpha(LINK_SET, 1.0, ALL_SIDES); // turn off any transparency.
197 // keep it from being physical and from disapparating.
198 llSetPrimitiveParams([PRIM_TEMP_ON_REZ, FALSE, PRIM_PHYSICS, FALSE,
199 PRIM_PHANTOM, TRUE]);
200 llSetLinkColor(LINK_SET, <1.0, 1.0, 1.0>, ALL_SIDES); // reset color to white.
203 // the following are specific to the huffotronic update server.
206 // we re-assert our special texture set here, in case some wayward scripts have
208 integer textures_held = llGetInventoryNumber(INVENTORY_TEXTURE);
210 for (indy = 0; indy < textures_held; indy++) {
211 string curr_tex = llGetInventoryName(INVENTORY_TEXTURE, indy);
212 // we have a simple scheme for putting textures on the updater.
213 // we have an inside, an outside and the ends.
214 if (is_prefix(curr_tex, "~~s0")) {
215 llSetTexture(curr_tex, ALL_SIDES);
216 } else if (is_prefix(curr_tex, "~~s1")) {
217 llSetTexture(curr_tex, 1);
218 } else if (is_prefix(curr_tex, "~~s2")) {
219 llSetTexture(curr_tex, 0);
220 llSetTexture(curr_tex, 3);
224 // re-assert our texture animation also.
225 llSetTextureAnim(ANIM_ON | LOOP | SMOOTH | ROTATE,
226 ALL_SIDES, 0, 0, 0, TWO_PI, 1.0 / 36.0);
229 // set up the update server object.
232 // set up our particular "look".
235 // shut down any competing scripts in this object.
236 // we try to swat any other script that's been added before we let them do weird
237 // stuff. for example, a pet script might start acting like one. this
238 // function is not guaranteed to run before that bad stuff can happen,
239 // and that's maybe the one major issue with this approach. as long as
240 // the contained scripts aren't evil (like if they jump someplace else
241 // as soon as they start), then there shouldn't be any serious problems.
242 knock_down_other_scripts();
244 // reset our variables.
245 active_update_channels = [];
246 active_listen_ids = [];
248 active_timestamps = [];
249 scripts_available = [];
250 objects_available = [];
251 dealing_with_change = FALSE; // not handling any inventory changes.
253 // make sure we know about any scripts that have new dependencies.
256 // clean out any older versions of the scripts before we make our
258 destroy_older_versions();
260 // now accumulate the list of scripts in our inventory.
261 integer items_held = llGetInventoryNumber(INVENTORY_SCRIPT);
263 for (indy = 0; indy < items_held; indy++) {
264 string curr_script = llGetInventoryName(INVENTORY_SCRIPT, indy);
265 // we don't provide our own script for updating; it must be kept from
266 // floating around, like into other objects that are not updaters.
267 //// if (curr_script != llGetScriptName())
268 scripts_available += curr_script;
270 items_held = llGetInventoryNumber(INVENTORY_OBJECT);
271 for (indy = 0; indy < items_held; indy++) {
272 objects_available += llGetInventoryName(INVENTORY_OBJECT, indy);
273 //log_it("added obj: " + llGetInventoryName(INVENTORY_OBJECT, indy));
275 items_held = llGetInventoryNumber(INVENTORY_NOTECARD);
276 for (indy = 0; indy < items_held; indy++) {
277 objects_available += llGetInventoryName(INVENTORY_NOTECARD, indy);
278 //log_it("added note: " + llGetInventoryName(INVENTORY_NOTECARD, indy));
281 // listen to the owner.
282 llListen(USER_COMMAND_CHANNEL, "", llGetOwner(), "");
283 // listen for any requests from our loyal clients.
284 inventory_request_channel = random_channel();
285 llListen(inventory_request_channel, "", NULL_KEY, "");
286 // listen for older clients too.
287 llListen(OLD_REQUEST_INVENTORY_CHANNEL, "", NULL_KEY, "");
289 // set up the periodic announcements.
290 llSetTimerEvent(ANNOUNCEMENT_PERIOD);
295 if (dealing_with_change) {
296 dealing_with_change = FALSE;
297 state rerun; // zoom back to the starting point.
300 integer timecheck = llGetUnixTime(); // use for whole loop.
301 for (indy = llGetListLength(active_timestamps) - 1; indy >= 0; indy--) {
302 integer last_time = llList2Integer(active_timestamps, indy);
303 if (llAbs(last_time - timecheck) > LONGEST_SLACK_PER_CLIENT) {
304 // we need to clear out this item. we know we can whack the client
305 // at the same index and that will take care of this slacker.
306 key curr_key = llList2Key(active_clients, indy);
307 log_it("timed-out client: " + llKey2Name(curr_key) + " [" + (string)curr_key + "]");
308 remove_client(curr_key);
312 // let the objects nearby know that we are open for business by
313 // announcing the script inventory.
317 // turns a list into a nicely formatted string.
318 string dump_list(list to_show, integer initial_line_break, integer entries_per_line)
322 for (indy = 0; indy < llGetListLength(to_show); indy++) {
323 // we break every Nth entry, but not if it's the first line and
324 // they said to not have the initial line break.
325 if ( !(indy % entries_per_line) && (indy || initial_line_break) )
327 string cursc = llList2String(to_show, indy);
329 // add commas where needed.
330 if (indy < llGetListLength(to_show) - 1)
336 // similar to dump_keyed_list, but only shows the names, each on their own line.
337 string dump_names_for_keys(list to_show)
341 for (indy = 0; indy < llGetListLength(to_show); indy++) {
342 // we only line break after the first entry.
343 if (indy > 0) msg += "\n";
344 string keystr = llList2String(to_show, indy);
345 msg += llKey2Name(keystr);
350 // similar to dump_list
351 string dump_keyed_list(list to_show, integer initial_line_break, integer entries_per_line)
355 for (indy = 0; indy < llGetListLength(to_show); indy++) {
356 // we break every Nth entry, but not if it's the first line and
357 // they said to not have the initial line break.
358 if ( !(indy % entries_per_line) && (indy || initial_line_break) )
360 string keystr = llList2String(to_show, indy);
361 msg += llKey2Name(keystr) + " (" + keystr + ")";
362 // add commas where needed.
363 if (indy < llGetListLength(to_show) - 1)
369 // shows the list specified in a compact manner.
370 show_list(string title, list to_show)
372 string to_say = title + dump_list(to_show, TRUE, ENTRIES_PER_LINE);
373 // flush some memory.
377 // say the output in pieces to avoid over-clogging chat.
378 for (indy = 0; indy < llStringLength(to_say); indy += MESSAGE_SIZE_LIMIT) {
379 integer last_indy = indy + MESSAGE_SIZE_LIMIT - 1;
381 if (last_indy < llStringLength(to_say)) addition = CONTINUANCE_MARKER;
382 llWhisper(0, llGetSubString(to_say, indy, last_indy) + addition);
386 // stops all the scripts besides this one.
387 knock_down_other_scripts()
389 // set all scripts but this to not be running.
391 string self_script = llGetScriptName();
392 list split = compute_basename_and_version(self_script);
393 string self_base = llList2String(split, 0);
394 self_script = ""; split = []; // free memory.
395 integer count = llGetInventoryNumber(INVENTORY_SCRIPT);
396 // we set all other scripts that are not versions of this script to not be running.
397 for (indy = 0; indy < count; indy++) {
398 string curr_script = llGetInventoryName(INVENTORY_SCRIPT, indy);
399 if (!is_prefix(curr_script, self_base)
400 && !is_prefix(curr_script, UPDATER_BASE_NAME) ) {
401 llSetScriptState(curr_script, FALSE);
406 // set a text label on the updater with the list of clients.
410 if (llGetListLength(active_clients) > 0) label = "[updating]\n";
411 llSetText(label + dump_names_for_keys(active_clients), <0.8, 0.95, 0.92>, 1.0);
414 // clean out a client that we should be done with.
415 remove_client(key id)
417 // locate said client of deceased nature...
418 integer indy = find_in_list(active_clients, id);
420 // if (DEBUGGING) log_it("failure to find client to remove: " + (string)id);
423 active_clients = llDeleteSubList(active_clients, indy, indy);
424 // also clean out the channel and stop listening to it.
425 integer act_chan = llList2Integer(active_update_channels, indy);
426 active_update_channels = llDeleteSubList(active_update_channels, indy, indy);
427 integer listen_to_remove = llList2Integer(active_listen_ids, indy);
428 //log_it("remove listen " + (string)listen_to_remove + " on chan " + (string)act_chan);
429 llListenRemove(listen_to_remove);
430 active_listen_ids = llDeleteSubList(active_listen_ids, indy, indy);
431 active_timestamps = llDeleteSubList(active_timestamps, indy, indy);
435 // fix a partial match to a script name if we can't find the exact item.
436 string backpatch_script_name(string partial)
438 if (llGetInventoryType(partial) == INVENTORY_SCRIPT) return partial; // all set.
440 for (dep_indy = 0; dep_indy < llGetInventoryNumber(INVENTORY_SCRIPT); dep_indy++) {
441 string curr_name = llGetInventoryName(INVENTORY_SCRIPT, dep_indy);
442 if (is_prefix(curr_name, partial)) {
443 // log_it("found real name " + curr_name + " for part: " + partial);
447 // log_it("no matches for partial script name!");
448 return ""; // no matches!
451 // moves the upgrade process with "id" along to the next step given the request in
453 propel_upgrade_process(integer channel, key id, string message)
455 if (DEBUGGING) log_it("got upgrade note from " + (string)id + " with msg=" + message);
456 if (message == REQUEST_SCRIPT_UPDATE) {
457 // begins the update process with the client.
458 llSay(channel, SHUT_THEM_DOWN);
459 } else if (is_prefix(message, READY_TO_UPDATE)) {
460 // whack the prefix so we can get the list they want.
461 message = llDeleteSubString(message, 0, llStringLength(READY_TO_UPDATE) - 1);
462 list requests = llParseString2List(message, [UPDATER_PARM_SEPARATOR], []);
464 // send over the scripts the client asked for, since it seems to be ready for them.
465 if (llGetListLength(requests)) {
466 show_list("updating " + llKey2Name(id) + " (key " + (string)id + ") with", requests);
468 for (indy = 0; indy < llGetListLength(requests); indy++) {
469 string curr = llList2String(requests, indy);
470 if (find_in_list(objects_available, curr) >= 0) {
471 //log_it("handing object over: " + curr);
472 // it's an object, so treat it that way.
473 llGiveInventory(id, curr);
475 //log_it("handing script over: " + curr);
476 // assume it's a script, and use script pin to stuff it.
477 curr = backpatch_script_name(curr);
479 // second life was okay with scripts being plugged in unstarted; opensim is not.
480 // and second life appears to be unhappy when scripts are added to objects in
481 // a running state. so we have an impasse. so 'running' should be true
482 // for opensim, and false for second life.
483 integer running = IS_OPENSIM;
484 // if (DEBUGGING) log_it("installing script using updater pin.");
485 llRemoteLoadScriptPin(id, curr, UPDATER_SCRIPT_PIN, running, 0);
490 } else if (message == SCRIPTS_ARE_CURRENT) {
491 // the client thinks it's ready to get back up and running.
492 //log_it("heard client is ready!");
493 llSay(channel, START_THEM_UP);
494 // kludge for older clients (pre 10.4 version) to try to help them start up.
495 //old and not useful. llSleep(0.2); llSay(channel, START_THEM_UP); llSleep(0.2); llSay(channel, START_THEM_UP);
497 } else if (message == DONE_UPDATING) {
498 // this client has nothing to do for now.
499 //log_it("heard client is done: " + (string)id);
502 //log_it("weird note from client: " + message);
508 // blasts out the inventory list to a curious client.
509 spew_inventory_list(integer channel, string message, key id)
511 if (!is_prefix(message, REQUEST_INVENTORY_PREFIX)) {
513 // this is an old style update alert that we still use at startup of the client
514 // to ensure that finishing replacement of the updater script is never unnoticed.
515 if (is_prefix(message, DONE_UPDATING)) {
516 // if (DEBUGGING) log_it("found very special message from startup of updater.");
517 propel_upgrade_process(channel, id, message);
520 // argh, this is not the right kind of message on our channel.
523 string chan_str = llDeleteSubString(message, 0, llStringLength(REQUEST_INVENTORY_PREFIX) - 1);
524 integer new_update_channel = (integer)chan_str;
525 if (llGetListLength(active_clients) >= MAXIMUM_ACTIVE_CLIENTS) {
526 // got to tell them "not right now". we'll pretend we have no
527 // scripts; they'll know what we mean if the update client is
528 // recent enough. really old clients will just go to sleep until later.
529 if (DEBUGGING) log_it("having to disallow new client '" + llKey2Name(id) + "', too many now.");
530 llSay(new_update_channel, REPORT_AVAILABLE_SCRIPTS + BUSY_BUSY);
534 // looks like we're going to try to handle the request for them.
535 if (DEBUGGING) log_it("server heard update req on chan " + (string)channel + " from: " + llKey2Name(id));
537 //log_it("add client convo chan " + (string)new_update_channel);
538 integer existing_indy = find_in_list(active_clients, id);
539 if (existing_indy < 0) {
540 active_clients += id;
541 active_update_channels += new_update_channel;
542 integer new_listen_id = llListen(new_update_channel, "", id, "");
543 //log_it("add listen " + (string)new_listen_id + " on chan " + (string)new_update_channel);
544 active_listen_ids += new_listen_id;
545 active_timestamps += llGetUnixTime();
548 // if (DEBUGGING) log_it("same client came back before finishing previous upgrade, rolling with it.");
549 // delete old listener so we don't leave it dangling.
550 integer old_listen_id = llList2Integer(active_listen_ids, existing_indy);
551 //log_it("remove old listen " + (string)old_listen_id);
552 llListenRemove(old_listen_id);
553 // update the channel and listener id for the new registration.
554 active_update_channels = chop_list(active_update_channels, 0, existing_indy - 1)
555 + [ new_update_channel ]
556 + chop_list(active_update_channels, existing_indy + 1,
557 llGetListLength(active_update_channels) - 1);
558 integer new_listen_id = llListen(new_update_channel, "", id, "");
559 //log_it("add new listen " + (string)new_listen_id);
560 active_listen_ids = chop_list(active_listen_ids, 0, existing_indy - 1)
562 + chop_list(active_listen_ids, existing_indy + 1,
563 llGetListLength(active_listen_ids) - 1);
564 active_timestamps = chop_list(active_timestamps, 0, existing_indy - 1)
566 + chop_list(active_timestamps, existing_indy + 1,
567 llGetListLength(active_timestamps) - 1);
570 // our report name is always called available scripts, but it can actually have
571 // script dependency definitions, script names and object names.
572 string msg = REPORT_AVAILABLE_SCRIPTS;
573 string curr; // temp.
575 // add in the huff updater, since that's the most crucial that they know we have.
577 string UPDATE_CLIENT_SCRIPT = "huff-update client";
578 for (posn = 0; posn < llGetListLength(scripts_available); posn++) {
579 curr = llList2String(scripts_available, posn);
580 if (llDeleteSubString(curr, llStringLength(UPDATE_CLIENT_SCRIPT), -1)
581 == UPDATE_CLIENT_SCRIPT) {
582 //log_it("found " + curr);
583 msg += curr + UPDATER_PARM_SEPARATOR;
584 posn = 99999; // jump out.
586 if (DEBUGGING && (posn == llGetListLength(scripts_available) - 1) ) {
587 log_it("epic fail, found no updater client script.");
591 // speak about the dependencies that we know.
592 for (posn = 0; posn < llGetListLength(known_script_dependencies); posn++) {
593 msg += SCRIPT_DEPENDENCY_MARK
594 + llList2String(known_script_dependencies, posn) + UPDATER_PARM_SEPARATOR;
595 if (llStringLength(msg) > MESSAGE_SIZE_LIMIT - 50) {
596 llSay(new_update_channel, msg + CONTINUANCE_MARKER);
597 //log_it(msg + CONTINUANCE_MARKER);
598 llSleep(SCRIPT_LIST_PAUSE_INTERVAL);
599 msg = REPORT_AVAILABLE_SCRIPTS;
602 // tell this new client what scripts we have.
603 for (posn = 0; posn < llGetListLength(scripts_available); posn++) {
604 curr = llList2String(scripts_available, posn);
605 if (llDeleteSubString(curr, llStringLength(UPDATE_CLIENT_SCRIPT), -1)
606 != UPDATE_CLIENT_SCRIPT) {
607 // add in the next item, along with the parameter separator.
608 msg += curr + UPDATER_PARM_SEPARATOR;
609 //log_it("adding " + curr);
610 if (llStringLength(msg) > MESSAGE_SIZE_LIMIT - 50) {
611 llSay(new_update_channel, msg + CONTINUANCE_MARKER);
612 //log_it(msg + CONTINUANCE_MARKER);
613 if (channel == OLD_REQUEST_INVENTORY_CHANNEL) {
614 // stop sending the list to them since they may not know how
615 // to interpret a multiple part update list.
618 llSleep(SCRIPT_LIST_PAUSE_INTERVAL);
619 msg = REPORT_AVAILABLE_SCRIPTS;
623 // mention any objects that are available.
624 for (posn = 0; posn < llGetListLength(objects_available); posn++) {
625 // add in the next item, along with the parameter separator.
626 msg += llList2String(objects_available, posn)
627 + UPDATER_PARM_SEPARATOR;
628 if (llStringLength(msg) > MESSAGE_SIZE_LIMIT - 50) {
629 llSay(new_update_channel, msg + CONTINUANCE_MARKER);
630 //log_it(msg + CONTINUANCE_MARKER);
631 llSleep(SCRIPT_LIST_PAUSE_INTERVAL);
632 msg = REPORT_AVAILABLE_SCRIPTS;
635 // say final bit, even if it's mostly blank. we need to let them know
636 // that we're done and not adding that continuation flag string.
637 llSay(new_update_channel, msg);
640 // handles verbal commands from objects that want updates.
641 process_verbal_requests(integer channel, string name, key id, string message)
643 if ( (channel == OLD_REQUEST_INVENTORY_CHANNEL) || (channel == inventory_request_channel) ) {
644 spew_inventory_list(channel, message, id);
646 } else if (channel == USER_COMMAND_CHANNEL) {
647 if (DEBUGGING) log_it("heard orders: " + message);
648 // simple verbal commands.
649 if (message == RESTART_UPDATER_COMMAND) {
650 log_it("Restarting now.");
652 } else if (message == SHOW_SCRIPTS_COMMAND) {
654 } else if (message == SHUTDOWN_COMMAND) {
655 // we will de-rez now (i.e., die) if we are not one of the special names that is undying.
656 if (!matches_substring(llGetObjectName(), "keeper")) {
657 // log_it("server " + (string)inventory_request_channel + " now disintegrating.");
666 // see if the channel is for one of our valid updates that's in progress.
667 for (indy = 0; indy < llGetListLength(active_update_channels); indy++) {
668 integer cur_chan = llList2Integer(active_update_channels, indy);
669 if (cur_chan == channel) {
670 // yes, this is really for that guy.
671 propel_upgrade_process(channel, id, message);
680 integer debug_num = 0;
682 // a debugging output method. can be disabled entirely in one place.
683 log_it(string to_say)
686 // tell this to the owner.
687 // llOwnerSay(llGetDate() + ": " + llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
688 llWhisper(0, llGetDate() + ": " + llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
689 // say this on an unusual channel for chat if it's not intended for general public.
690 // llSay(108, llGetDate() + ": " + llGetScriptName() + "[" + (string)debug_num + "] " + to_say);
691 // say this on open chat that anyone can hear. we take off the bling for this one.
695 // returns TRUE if the "pattern" is found in the "full_string".
696 integer matches_substring(string full_string, string pattern)
697 { return (find_substring(full_string, pattern) >= 0); }
699 // returns the index of the first occurrence of "pattern" inside
700 // the "full_string". if it is not found, then a negative number is returned.
701 integer find_substring(string full_string, string pattern)
702 { return llSubStringIndex(llToLower(full_string), llToLower(pattern)); }
704 // returns TRUE if the "prefix" string is the first part of "compare_with".
705 integer is_prefix(string compare_with, string prefix)
706 { return find_substring(compare_with, prefix) == 0; }
708 // joins a list of parameters using the parameter sentinel for the library.
709 string wrap_parameters(list to_flatten)
710 { return llDumpList2String(to_flatten, UPDATER_PARM_SEPARATOR); }
712 // locates the string "text" in the list to "search_in".
713 integer find_in_list(list search_in, string text)
715 integer len = llGetListLength(search_in);
717 for (i = 0; i < len; i++) {
718 if (llList2String(search_in, i) == text)
724 // returns the portion of the list between start and end, but only if they are
725 // valid compared with the list length. an attempt to use negative start or
726 // end values also returns a blank list.
727 list chop_list(list to_chop, integer start, integer end)
729 integer last_len = llGetListLength(to_chop) - 1;
730 if ( (start < 0) || (end < 0) || (start > last_len) || (end > last_len) ) return [];
731 return llList2List(to_chop, start, end);
734 // a random channel for the first interaction with the client.
735 integer random_channel() { return -(integer)(llFrand(800000) + 20000); }
737 // note that this new, lower memory version, depends on the inventory functions returning
738 // items in alphabetical order.
739 scrub_items_by_type(string this_guy, integer inventory_type)
743 for (outer = 0; outer < llGetInventoryNumber(inventory_type); outer++) {
744 string curr = llGetInventoryName(inventory_type, outer);
745 list split = compute_basename_and_version(curr);
746 // make sure there was a comparable version number in this name.
747 if ( (curr != this_guy) && llGetListLength(split)) {
748 string curr_base = llList2String(split, 0);
749 float curr_ver = (float)llList2String(split, 1);
750 //log_it("outer: " + curr_base + " / " + (string)curr_ver);
752 for (inner = outer + 1; inner < llGetInventoryNumber(inventory_type); inner++) {
753 string next_guy = llGetInventoryName(inventory_type, inner);
754 list comp_split = compute_basename_and_version(next_guy);
755 if (llGetListLength(comp_split)) {
756 string comp_base = llList2String(comp_split, 0);
757 float comp_ver = (float)llList2String(comp_split, 1);
758 // okay, now we can actually compare.
759 if (curr_base != comp_base) {
760 // break out of inner loop. we are past where the names can matter.
761 inner = 2 * llGetInventoryNumber(inventory_type);
763 //log_it("inner: " + comp_base + " / " + (string)comp_ver);
764 if (curr_ver <= comp_ver) {
765 // the script at inner index is comparable or better than
766 // the script at the outer index.
767 removal_list += curr;
769 // this inner script must be inferior to the outer one,
770 // somehow, which defies our expectation of alphabetical ordering.
771 removal_list += next_guy;
779 // now actually do the deletions.
780 for (outer = 0; outer < llGetListLength(removal_list); outer++) {
781 string to_whack = llList2String(removal_list, outer);
782 log_it("removing older asset: " + to_whack);
783 llRemoveInventory(to_whack);
786 if (IS_OPENSIM && (llGetListLength(removal_list) > 0) ) {
787 // log_it("now restarting to avoid opensim late updating change event.");
792 // ensures that only the latest version of any script or object is kept in our inventory.
793 destroy_older_versions()
795 // firstly, iterate across scripts to clean out older versions.
796 scrub_items_by_type(llGetScriptName(), INVENTORY_SCRIPT);
797 // secondly, try to clean out the objects.
798 scrub_items_by_type(llGetScriptName(), INVENTORY_OBJECT);
799 // thirdly, try to clean out the notecards.
800 scrub_items_by_type(llGetScriptName(), INVENTORY_NOTECARD);
804 // huffware script: auto-retire, by fred huffhines, version 2.4.
805 // distributed under BSD-like license.
806 // partly based on the self-upgrading scripts from markov brodsky and jippen faddoul.
807 // the function auto_retire() should be added *inside* a version numbered script that
808 // you wish to give the capability of self-upgrading.
809 // this script supports a notation for versions embedded in script names where a 'v'
810 // is followed by a number in the form "major.minor", e.g. "grunkle script by ted v8.2".
811 // when the containing script is dropped into an object with a different version, the
812 // most recent version eats any existing ones.
813 // keep in mind that this code must be *copied* into your script you wish to add
814 // auto-retirement capability to.
815 // example usage of the auto-retirement script:
818 // auto_retire(); // make sure newest addition is only version of script.
822 string self = llGetScriptName(); // the name of this script.
823 list split = compute_basename_and_version(self);
824 if (llGetListLength(split) != 2) return; // nothing to do for this script.
825 string basename = llList2String(split, 0); // script name with no version attached.
826 string version_string = llList2String(split, 1); // the version found.
828 // find any scripts that match the basename. they are variants of this script.
829 for (posn = llGetInventoryNumber(INVENTORY_SCRIPT) - 1; posn >= 0; posn--) {
830 string curr_script = llGetInventoryName(INVENTORY_SCRIPT, posn);
831 if ( (curr_script != self) && (llSubStringIndex(curr_script, basename) == 0) ) {
832 // found a basic match at least.
833 list inv_split = compute_basename_and_version(curr_script);
834 if (llGetListLength(inv_split) == 2) {
835 // see if this script is more ancient.
836 string inv_version_string = llList2String(inv_split, 1); // the version found.
837 // must make sure that the retiring script is completely the identical basename;
838 // just matching in the front doesn't make it a relative.
839 if ( (llList2String(inv_split, 0) == basename)
840 && ((float)inv_version_string < (float)version_string) ) {
841 // remove script with same name from inventory that has inferior version.
842 llRemoveInventory(curr_script);
849 // separates the base script name and version number. used by auto_retire.
850 list compute_basename_and_version(string to_chop_up)
852 // minimum script name is 2 characters plus a version.
853 integer space_v_posn;
854 // find the last useful space and 'v' combo.
855 for (space_v_posn = llStringLength(to_chop_up) - 3;
856 (space_v_posn >= 2) && (llGetSubString(to_chop_up, space_v_posn, space_v_posn + 1) != " v");
858 // look for space and v but do nothing else.
860 if (space_v_posn < 2) return []; // no space found.
861 // now we zoom through the stuff after our beloved v character and find any evil
862 // space characters, which are most likely from SL having found a duplicate item
863 // name and not so helpfully renamed it for us.
865 for (indy = llStringLength(to_chop_up) - 1; indy > space_v_posn; indy--) {
866 if (llGetSubString(to_chop_up, indy, indy) == " ") {
867 // found one; zap it. since we're going backwards we don't need to
868 // adjust the loop at all.
869 to_chop_up = llDeleteSubString(to_chop_up, indy, indy);
872 string full_suffix = llGetSubString(to_chop_up, space_v_posn, -1);
873 // ditch the space character for our numerical check.
874 string chop_suffix = llGetSubString(full_suffix, 1, llStringLength(full_suffix) - 1);
875 // strip out a 'v' if there is one.
876 if (llGetSubString(chop_suffix, 0, 0) == "v")
877 chop_suffix = llGetSubString(chop_suffix, 1, llStringLength(chop_suffix) - 1);
878 // if valid floating point number and greater than zero, that works for our version.
879 string basename = to_chop_up; // script name with no version attached.
880 if ((float)chop_suffix > 0.0) {
881 // this is a big success right here.
882 basename = llGetSubString(to_chop_up, 0, -llStringLength(full_suffix) - 1);
883 return [ basename, chop_suffix ];
885 // seems like we found nothing useful.
899 initialize_root(); // get set up to start answering requests.
900 llWhisper(0, llGetScriptName() + " started... touch for more info.");
904 state_exit() { llSetTimerEvent(0); }
906 on_rez(integer parm) {
910 timer() { handle_timer(); }
912 touch_start(integer count) {
913 show_status(llDetectedKey(0));
916 listen(integer channel, string name, key id, string message) {
917 // make sure that the object is something we should even talk to.
918 if (llGetOwnerKey(id) != llGetOwner()) {
921 // looks okay, let's see if this is useful communication.
922 process_verbal_requests(channel, name, id, message);
925 changed(integer change) {
926 if (change & CHANGED_INVENTORY) {
927 log_it("inventory changed, scheduled a restart.");
928 // sleep a little bit; otherwise we get SL noise about scripts not being
929 // there when it told us they were there. this can lead to some scripts
930 // doing bizarre things if they are running when added.
931 dealing_with_change = TRUE;
932 llSetTimerEvent(0.0); // kludge to get around second life bug.
933 llSetTimerEvent(CHANGED_INVENTORY_SNOOZER);
938 state rerun { state_entry() { state default; } }