The synchronization script used by Project Alexandria will periodically poll (set to 1s) a user-defined directory on the OpenSim server (in this case /home/opensim/books/) for files. When a new file is found, it uses the OSSL function osMakeNotecard to create a notecard from that file.

The path can be changed by modifying the line:

string DOCUMENT_PATH = "/home/opensim/books/";

and setting an absolute path to a top-level directory. The script appends the object description to that path (path-traversal unaware) and scans the resulting folder for files.

Following the video on the main Project Alexandria page, the bookcase is given the description My Documents. The script below thus scans the path:

"/home/opensim/books/" + llGetObjectDesc();

which, since the description of the object is My Documents, turns out to be:

/home/opensim/books/My Documents

Thus, the script will pick-up any file placed under /home/opensim/books/My Documents and create a notecard with the contents of the file.


This script was tested and works on OpenSim version 0.7.4!

//  Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3      //
//  Please see: for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
string DOCUMENT_PATH = @"/home/opensim/books/";
string filesHash = "";
public void default_event_state_entry() {
    DOCUMENT_PATH += llGetObjectDesc();
public void default_event_timer() {
    System.Collections.Generic.List<string> path = new System.Collections.Generic.List<string>(System.IO.Directory.GetFiles(DOCUMENT_PATH));
    // If no documents are available, skip.
    if(path.Count == 0) return;
    string hash = CalculateMD5Hash(string.Join("", path.ToArray()));
    // If the last known MD5 hash is different from the newly computed hash, skip.
    if(hash == filesHash) return;
    // Save the hash since the file listing has changed.
    filesHash = hash;
    // Remove all notecards in inventory or skip if none available.
    LSL_Types.LSLInteger cards = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
    if(cards == -1) goto Import;
    do {
        llRemoveInventory(llGetInventoryName(INVENTORY_NOTECARD, cards));
    } while(--cards>-1);
    foreach(string file in path) {
        string notecardName = System.IO.Path.GetFileName(file);
        // If notecard exists, skip.
        if(llGetInventoryType(notecardName) == INVENTORY_NOTECARD) continue;
        // Skip UNIX dot-files.
        if(llGetSubString(notecardName, 0, 0) == ".") continue;
        // Attempt to read file, skip if not possible.
        string data = "";
        try {
            data = System.IO.File.ReadAllText(file);
        catch(System.Exception) {
            // If file could not be read, for any reason, skip.
        // Create the notecard.
        osMakeNotecard(notecardName, (LSL_Types.LSLString) data);
private string CalculateMD5Hash(string input) {
    System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
    byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
    byte[] hash = md5.ComputeHash(inputBytes);
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    for (int i = 0; i < hash.Length; i++)
    return sb.ToString();