/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // PrimFS™ and PrimDrive™ // // The Primitive Filesystem for the Primitive Hard Drive. // /////////////////////////////////////////////////////////////////////////// // PrimFS™ and PrimDrive™ introduce an unconventional hard-drive that is // able to store data in three-dimensions instead of two. While // conventional hard-drives use superposed disks, PrimDrive™ uses a cube // that is physically sub-divided into smaller blocks called sectors. // // Storing data in three dimensions instead of two allows for some helpful // features, namely: // - each sector can change its own color to indicate how much data // has been written to it and also how much free space is available // - by consequence, looking at the PrimDrive™ will immediatly give // an overview of the clustering of data. // // Wizardry and Steamworks uses key-value data as the underlying data // representation. The simplicity is desirable such that sectors can hold // as much data as possible without too much structure overhead - by // contrast with llJson* functions, for example. // // Due to the fact that each block is a physical object some interesting // qualities can be implicitly observed, for instance: // - defragmenting the drive could be a mechanical operation (optical?) // that the filesystem (PrimFS™) could not even bother with. // - The PrimDrive™ could be extended as easily as attaching sectors. // - The PrimDrive™ could be shrunk as easily as detaching sectors. // - Sectors could be shared between PrimDrive™s just by detaching and // attaching them to other PrimDrive™s. PrimFS™ always uses just as // much sectors space _as needed_ such that it is easy (perhaps, out- // of-code) to identify unoccupied sectors and perform operations // them. // // Traditional drives go from the mathematical property of circles that // have the maximal surface at the minimal perimeter. That is, no other // shape can have a surface larger than a circle at a given perimeter. // // Nevertheless, the idea of disks are far too immature for a 3D world // where data could be stored on three axes instead of two. // // Generalizing the PrimFS™ and PrimDrive™ to a real-adaptable scenario // would be a matter of finding a shape that is closest to an inverse- // sphere and then disposing sectors on its surface. // /////////////////////////////////////////////////////////////////////////// // Capacity = [1] 127 bytes (including descriptors) / sector // // Implemented Operations: Write, Read, Delete, Format // Planned Operations: Defragment, (Extend, Shrink) // // [1] For Linden Labs-VW style implementations. /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Second Life Constants // // PrimFS™ Physical Parameters. // /////////////////////////////////////////////////////////////////////////// // The maximum number of bytes that we can store in a description is set // here to 127 bytes which is the current number of bytes that you can // store in a primitive's description in Second Life. integer BYTES_PER_SECTOR = 127; // Primitives can have a zero-length description but that can only be // accomplished by setting the description to the empty string using a // script. Otherwise, any primitive created with actually have this // string in the description indicating that no description is set. string UNFORMATTED_MARKER = "(No Description)"; /////////////////////////////////////////////////////////////////////////// // Wizardry and Steamworks // // http://grimore.org/fuss/data_structures/key-value_pairs // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// 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) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueSet(string k, string v, string data) { if(llStringLength(data) == 0) return k + "=" + v; if(llStringLength(k) == 0) return ""; if(llStringLength(v) == 0) return ""; integer i = llListFindList( llList2ListStrided( llParseString2List(data, ["&", "="], []), 0, -1, 2 ), [ k ]); if(i != -1) return llDumpList2String( llListReplaceList( llParseString2List(data, ["&"], []), [ k + "=" + v ], i, i), "&"); return data + "&" + k + "=" + v; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueDelete(string k, string data) { if(llStringLength(data) == 0) return ""; if(llStringLength(k) == 0) return ""; integer i = llListFindList( llList2ListStrided( llParseString2List(data, ["&", "="], []), 0, -1, 2 ), [ k ]); if(i != -1) return llDumpList2String( llDeleteSubList( llParseString2List(data, ["&"], []), i, i), "&"); return data; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// list wasKeyValueGetKeys(string data) { return llList2ListStrided( llParseString2List( data, ["&", "="], [] ), 0, -1, 2 ); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // returns the size of a linked primitive description string wasGetLinkDescription(integer link) { string d = (string)llGetLinkPrimitiveParams(link, [PRIM_DESC]); if(d == UNFORMATTED_MARKER || d == "") return ""; return d; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // sets the description of a linked primitive wasSetLinkDescription(integer link, string description) { llSetLinkPrimitiveParamsFast(link, [PRIM_DESC, description]); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// vector wasPercentToGradientBrighness(float percent, vector from, vector to) { return <from.x + (to.x-from.x)*percent/100, from.y + (to.y-from.y)*percent/100, from.z + (to.z-from.z)*percent/100>; } /////////////////////////////////////////////////////////////////////////// // PrimFS™ API // // http://grimore.org/secondlife/primfs_and_primdrive // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Remove all descriptors k and all data associated with descriptor k // from head to tail and return the number of bytes deleted (excluding // the length of the descriptor k). integer wasPrimFSDelete(string k, integer head, integer tail) { integer b; do { string d = wasGetLinkDescription(tail); if(llStringLength(d) == 0) jump continue; b += llStringLength(wasKeyValueGet(k, d)); wasSetLinkDescription(tail, wasKeyValueDelete( k, d ) ); // GC d = ""; @continue; } while(--tail>=head); return b; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Returns a list containing all unique file descriptors. list wasPrimFSGetDescriptors(integer head, integer tail) { list descriptors = []; do { string d = wasGetLinkDescription(tail); if(llStringLength(d) == 0) jump continue; list c = llList2ListStrided( llParseString2List(d, ["&", "="], []), 0, -1, 2 ); // GC d = ""; do { string k = llList2String(c, 0); if(llListFindList( descriptors, (list)k ) != -1) jump skip; descriptors += k; // GC k = ""; @skip; c = llDeleteSubList(c, 0, 0); } while(llGetListLength(c) != 0); // GC c = []; @continue; } while(--tail>=head); return descriptors; } /////////////////////////////////////////////////////////////////////////// // PrimFS™ Read-Write // // Descriptor Level. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Formats all blocks for PrimFS™ between head and tail and returns the // number of sectors for the new partition. // The resulting formatted partions can be found between head and tail. integer wasPrimFSFormat(integer head, integer tail) { integer seek = tail; do { wasSetLinkDescription(seek, ""); } while(--seek>=head); return tail-seek; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Writes data to the filesystem in the partition between ead and tail // and returns the number of written bytes. // // data cannot contain the = (equal, ASCII 061) sign, a restriction of // the key-value structure (http://grimore.org/secondlife:key_value_data). integer wasPrimFSWrite(string k, string data, integer head, integer tail) { integer b; do { string d = wasGetLinkDescription(tail); string v = ""; if(llSubStringIndex(d, k) != -1) v = wasKeyValueGet(k, d); integer s = llStringLength(v); // if the key was found if(s != 0) { s = BYTES_PER_SECTOR - llStringLength(d) + s; v = llGetSubString(data, 0, s-1); if(llStringLength(v) != 0) jump write; wasSetLinkDescription(tail, wasKeyValueDelete( k, d ) ); jump continue; } // if the key was not found if(llStringLength(d) >= BYTES_PER_SECTOR) jump continue; s = BYTES_PER_SECTOR - llStringLength(d) - llStringLength(k) - 1; // & if(llStringLength(d) != 0) --s; v = llGetSubString(data, 0, s-1); if(llStringLength(v) == 0) jump continue; // write @write; b += llStringLength(v) + llStringLength(k) + 1; v = wasKeyValueSet(k, v, d); wasSetLinkDescription(tail,v); // GC v = ""; data = llDeleteSubString(data, 0, s-1); @continue; // GC d = ""; } while(--tail>=head); // return written bytes return b; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Reads the filesystem on the partition between head and tail and returns // the data associated with the file descriptor k. string wasPrimFSRead(string k, integer head, integer tail) { string output = ""; do { string d = wasGetLinkDescription(tail); if(llStringLength(d) == 0) jump continue; if(llListFindList(wasKeyValueGetKeys(d), [ k ]) == -1) jump continue; output += wasKeyValueGet(k, d); @continue; // GC d = ""; } while(--tail>=head); return output; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Returns the number of free space (in bytes) on a partition bounded // by head and tail integer wasPrimFSGetFreeSpace(integer head, integer tail) { integer occupied = 0; integer seek = tail; do { occupied += llStringLength(wasGetLinkDescription(seek)); } while(--seek>=head); return BYTES_PER_SECTOR*(tail-head)-occupied; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Returns the number of free space (in bytes) on a partition bounded // by head and tail integer wasPrimFSGetUsedSpace(integer head, integer tail) { integer occupied = 0; do { occupied += llStringLength(wasGetLinkDescription(tail)); } while(--tail>=head); return occupied; } /////////////////////////////////////////////////////////////////////////// // PrimDrive™ API // // Drive Level. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// // Can be called after "update" (write or append) operations on the // PrimDrive™ in order to highlight the data-consumption and clustering // of sectors. wasPrimDriveLED(integer head, integer tail) { // when tail overlaps head, we're done if(tail < head) return; string d = wasGetLinkDescription(tail); // indicate unformated blocks in red if(d == UNFORMATTED_MARKER) { llSetLinkPrimitiveParamsFast(tail, [ PRIM_COLOR, ALL_SIDES, <1,0,0>, 1] ); jump continue; } integer size = llStringLength(d); // GC d = ""; llSetLinkPrimitiveParamsFast(tail, [ PRIM_COLOR, ALL_SIDES, wasPercentToGradientBrighness( 100*size/(float)BYTES_PER_SECTOR, <1,1,1>, ZERO_VECTOR), .17 + size/(float)BYTES_PER_SECTOR] ); @continue; // GC d = ""; wasPrimDriveLED(head, --tail); } /////////////////////////////////////////////////////////////////////////// list commands = [ "write", "read", "format", "delete", "getfreespace", "getusedspace", "getdescriptors" ]; list commandStack = []; string commandInterpreter(list input) { if(llGetListLength(input) == 0) { // pop command string operation = llList2String(commandStack, 0); commandStack = llDeleteSubList(commandStack, 0, 0); //pop head and tail off the stack integer tail = llList2Integer(commandStack, -1); integer head = llList2Integer(commandStack, -2); integer drive_size = llGetNumberOfPrims(); //drive if(tail == 0 || tail > drive_size || head < 2) { tail = drive_size; head = 2; jump operations; } commandStack = llDeleteSubList(commandStack, -2, -1); @operations; string output = ""; // perform operations if(operation == "write") { // pop key string k = llList2String(commandStack, 0); commandStack = llDeleteSubList(commandStack, 0, 0); // got a key but no values if(llGetListLength(commandStack) == 0) jump feedback; output = (string)wasPrimFSWrite( k, llDumpList2String(commandStack, " "), head, tail ); wasPrimDriveLED(head, tail); jump feedback; } if(operation == "read") { output = wasPrimFSRead( llList2String(commandStack, 0), head, tail ); jump feedback; } if(operation == "format") { output = (string)wasPrimFSFormat(head, tail); wasPrimDriveLED(head, tail); jump feedback; } if(operation == "delete") { output = (string)wasPrimFSDelete( llList2String(commandStack, 0), head, tail ); wasPrimDriveLED(head, tail); jump feedback; } if(operation == "getusedspace") { output = (string)wasPrimFSGetUsedSpace(head, tail); jump feedback; } if(operation == "getfreespace") { output = (string)wasPrimFSGetFreeSpace(head, tail); jump feedback; } if(operation == "getdescriptors") { output = llList2CSV(wasPrimFSGetDescriptors(head, tail)); jump feedback; } @feedback; commandStack = []; return output; } // pop input string i = llList2String(input, 0); input = llDeleteSubList(input, 0, 0); // check for valid command if(llListFindList( commands, (list)i) != -1) { commandStack += i; jump continue; } // check for numbers if(llListFindList( commands, llList2List(commandStack, 0, 0)) != -1) { integer o = (integer)i; // if not an integer, it's a string if(o == 0) { commandStack += i; jump continue; } // otherwise an integer commandStack += o; } @continue; return commandInterpreter(input); } string URL = ""; default { state_entry() { llRequestURL(); } http_request(key id, string method, string body) { if(method == URL_REQUEST_GRANTED) { URL = body; llSay(0, "My URL is: " + URL); state main; } } } state main { state_entry() { llListen(0, "", "", ""); llListen(-673, "", "", ""); llSetTimerEvent(1); } timer() { llGetNextEmail("", ""); } http_request(key id, string method, string body) { body = commandInterpreter(llParseString2List(body, [" "], [])); if(llStringLength(body) == 0) return; llSay(0, body); } link_message(integer sender, integer num, string str, key id) { str = commandInterpreter(llParseString2List(str, [" "], [])); if(llStringLength(str) == 0) return; llSay(0, str); } listen(integer channel, string name, key id, string message) { message = commandInterpreter(llParseString2List(message, [" "], [])); if(llStringLength(message) == 0) return; //llSay(0, message); } email(string time, string address, string subject, string message, integer num_left) { message = commandInterpreter(llParseString2List(message, [" "], [])); if(llStringLength(message) == 0) return; llSay(0, message); } }