skylib_mudos_v1/
skylib_mudos_v1/bin/
skylib_mudos_v1/bin/db/
skylib_mudos_v1/mudlib/banish/a/
skylib_mudos_v1/mudlib/banish/b/
skylib_mudos_v1/mudlib/banish/c/
skylib_mudos_v1/mudlib/banish/d/
skylib_mudos_v1/mudlib/banish/e/
skylib_mudos_v1/mudlib/banish/f/
skylib_mudos_v1/mudlib/banish/g/
skylib_mudos_v1/mudlib/banish/h/
skylib_mudos_v1/mudlib/banish/j/
skylib_mudos_v1/mudlib/banish/l/
skylib_mudos_v1/mudlib/banish/m/
skylib_mudos_v1/mudlib/banish/n/
skylib_mudos_v1/mudlib/banish/o/
skylib_mudos_v1/mudlib/banish/p/
skylib_mudos_v1/mudlib/banish/r/
skylib_mudos_v1/mudlib/banish/s/
skylib_mudos_v1/mudlib/banish/t/
skylib_mudos_v1/mudlib/banish/u/
skylib_mudos_v1/mudlib/banish/w/
skylib_mudos_v1/mudlib/cmds/
skylib_mudos_v1/mudlib/cmds/admin/
skylib_mudos_v1/mudlib/cmds/guild-race/
skylib_mudos_v1/mudlib/cmds/guild-race/crafts/
skylib_mudos_v1/mudlib/cmds/guild-race/magic/
skylib_mudos_v1/mudlib/cmds/guild-race/other/
skylib_mudos_v1/mudlib/cmds/living/broken/
skylib_mudos_v1/mudlib/cmds/player/group_cmds/
skylib_mudos_v1/mudlib/d/admin/
skylib_mudos_v1/mudlib/d/admin/room/
skylib_mudos_v1/mudlib/d/admin/room/we_care/
skylib_mudos_v1/mudlib/d/admin/save/
skylib_mudos_v1/mudlib/d/admin/text/
skylib_mudos_v1/mudlib/d/learning/TinyTown/buildings/
skylib_mudos_v1/mudlib/d/learning/TinyTown/map/
skylib_mudos_v1/mudlib/d/learning/TinyTown/roads/
skylib_mudos_v1/mudlib/d/learning/chars/
skylib_mudos_v1/mudlib/d/learning/functions/
skylib_mudos_v1/mudlib/d/learning/handlers/
skylib_mudos_v1/mudlib/d/learning/help_topics/
skylib_mudos_v1/mudlib/d/learning/help_topics/npcs/
skylib_mudos_v1/mudlib/d/learning/help_topics/objects/
skylib_mudos_v1/mudlib/d/learning/help_topics/rcs_demo/
skylib_mudos_v1/mudlib/d/learning/help_topics/rcs_demo/RCS/
skylib_mudos_v1/mudlib/d/learning/help_topics/rooms/
skylib_mudos_v1/mudlib/d/learning/help_topics/rooms/crowd/
skylib_mudos_v1/mudlib/d/learning/help_topics/rooms/situations/
skylib_mudos_v1/mudlib/d/learning/save/
skylib_mudos_v1/mudlib/d/learning/school/
skylib_mudos_v1/mudlib/d/learning/school/add_sc/
skylib_mudos_v1/mudlib/d/learning/school/characters/
skylib_mudos_v1/mudlib/d/learning/school/general/
skylib_mudos_v1/mudlib/d/learning/school/getting-started/
skylib_mudos_v1/mudlib/d/learning/school/getting-started/basic_commands/
skylib_mudos_v1/mudlib/d/learning/school/getting-started/edtutor/
skylib_mudos_v1/mudlib/d/learning/school/getting-started/unix_tutor/
skylib_mudos_v1/mudlib/d/learning/school/items/
skylib_mudos_v1/mudlib/d/learning/school/npc_school/
skylib_mudos_v1/mudlib/d/learning/school/room_school/
skylib_mudos_v1/mudlib/d/learning/school/room_school/room_basic/
skylib_mudos_v1/mudlib/d/learning/school/room_school/situations/
skylib_mudos_v1/mudlib/d/learning/school/room_school/terrain_tutor/
skylib_mudos_v1/mudlib/d/learning/text/
skylib_mudos_v1/mudlib/d/liaison/
skylib_mudos_v1/mudlib/d/mudlib/
skylib_mudos_v1/mudlib/d/mudlib/changes/
skylib_mudos_v1/mudlib/d/playtesters/
skylib_mudos_v1/mudlib/d/playtesters/effects/
skylib_mudos_v1/mudlib/d/playtesters/handlers/
skylib_mudos_v1/mudlib/d/playtesters/items/
skylib_mudos_v1/mudlib/d/sage/
skylib_mudos_v1/mudlib/doc/
skylib_mudos_v1/mudlib/doc/creator/
skylib_mudos_v1/mudlib/doc/driver/
skylib_mudos_v1/mudlib/doc/driver/efuns/arrays/
skylib_mudos_v1/mudlib/doc/driver/efuns/buffers/
skylib_mudos_v1/mudlib/doc/driver/efuns/compile/
skylib_mudos_v1/mudlib/doc/driver/efuns/filesystem/
skylib_mudos_v1/mudlib/doc/driver/efuns/floats/
skylib_mudos_v1/mudlib/doc/driver/efuns/functions/
skylib_mudos_v1/mudlib/doc/driver/efuns/general/
skylib_mudos_v1/mudlib/doc/driver/efuns/mappings/
skylib_mudos_v1/mudlib/doc/driver/efuns/mixed/
skylib_mudos_v1/mudlib/doc/driver/efuns/mudlib/
skylib_mudos_v1/mudlib/doc/driver/efuns/numbers/
skylib_mudos_v1/mudlib/doc/driver/efuns/parsing/
skylib_mudos_v1/mudlib/doc/known_command/
skylib_mudos_v1/mudlib/doc/login/
skylib_mudos_v1/mudlib/doc/lpc/basic_manual/
skylib_mudos_v1/mudlib/doc/lpc/intermediate/
skylib_mudos_v1/mudlib/doc/new/add_command/
skylib_mudos_v1/mudlib/doc/new/events/
skylib_mudos_v1/mudlib/doc/new/handlers/
skylib_mudos_v1/mudlib/doc/new/living/race/
skylib_mudos_v1/mudlib/doc/new/living/spells/
skylib_mudos_v1/mudlib/doc/new/object/
skylib_mudos_v1/mudlib/doc/new/player/
skylib_mudos_v1/mudlib/doc/new/room/guild/
skylib_mudos_v1/mudlib/doc/new/room/outside/
skylib_mudos_v1/mudlib/doc/new/room/storeroom/
skylib_mudos_v1/mudlib/doc/object/
skylib_mudos_v1/mudlib/doc/playtesters/
skylib_mudos_v1/mudlib/doc/policy/
skylib_mudos_v1/mudlib/doc/weapons/
skylib_mudos_v1/mudlib/global/
skylib_mudos_v1/mudlib/global/creator/
skylib_mudos_v1/mudlib/global/handlers/
skylib_mudos_v1/mudlib/global/virtual/setup_compiler/
skylib_mudos_v1/mudlib/include/cmds/
skylib_mudos_v1/mudlib/include/effects/
skylib_mudos_v1/mudlib/include/npc/
skylib_mudos_v1/mudlib/include/room/
skylib_mudos_v1/mudlib/include/shops/
skylib_mudos_v1/mudlib/net/daemon/
skylib_mudos_v1/mudlib/net/daemon/chars/
skylib_mudos_v1/mudlib/net/inherit/
skylib_mudos_v1/mudlib/net/obj/
skylib_mudos_v1/mudlib/obj/amulets/
skylib_mudos_v1/mudlib/obj/b_day/
skylib_mudos_v1/mudlib/obj/clothes/
skylib_mudos_v1/mudlib/obj/dwarmours/plate/
skylib_mudos_v1/mudlib/obj/dwclothes/transport/horse/
skylib_mudos_v1/mudlib/obj/dwscabbards/
skylib_mudos_v1/mudlib/obj/dwweapons/axes/
skylib_mudos_v1/mudlib/obj/dwweapons/chains/
skylib_mudos_v1/mudlib/obj/faith/symbols/
skylib_mudos_v1/mudlib/obj/fungi/
skylib_mudos_v1/mudlib/obj/gatherables/
skylib_mudos_v1/mudlib/obj/instruments/
skylib_mudos_v1/mudlib/obj/magic/
skylib_mudos_v1/mudlib/obj/media/
skylib_mudos_v1/mudlib/obj/misc/player_shop/
skylib_mudos_v1/mudlib/obj/monster/godmother/
skylib_mudos_v1/mudlib/obj/monster/transport/
skylib_mudos_v1/mudlib/obj/rings/
skylib_mudos_v1/mudlib/obj/spells/
skylib_mudos_v1/mudlib/obj/stationery/
skylib_mudos_v1/mudlib/obj/stationery/envelopes/
skylib_mudos_v1/mudlib/obj/stationery/papers/
skylib_mudos_v1/mudlib/obj/toys/
skylib_mudos_v1/mudlib/obj/vessels/
skylib_mudos_v1/mudlib/obj/weapons/swords/
skylib_mudos_v1/mudlib/save/autodoc/
skylib_mudos_v1/mudlib/save/leaflets/
skylib_mudos_v1/mudlib/save/mail/
skylib_mudos_v1/mudlib/save/new_soul/data/
skylib_mudos_v1/mudlib/save/parcels/
skylib_mudos_v1/mudlib/save/playerinfo/
skylib_mudos_v1/mudlib/save/players/d/
skylib_mudos_v1/mudlib/save/random_names/
skylib_mudos_v1/mudlib/save/random_names/data/
skylib_mudos_v1/mudlib/save/terrains/
skylib_mudos_v1/mudlib/save/terrains/tutorial_desert/
skylib_mudos_v1/mudlib/save/terrains/tutorial_grassy_field/
skylib_mudos_v1/mudlib/save/terrains/tutorial_mountain/
skylib_mudos_v1/mudlib/save/todo_lists/
skylib_mudos_v1/mudlib/secure/
skylib_mudos_v1/mudlib/secure/cmds/admin/
skylib_mudos_v1/mudlib/secure/cmds/lord/
skylib_mudos_v1/mudlib/secure/config/
skylib_mudos_v1/mudlib/secure/handlers/autodoc/
skylib_mudos_v1/mudlib/secure/handlers/intermud/
skylib_mudos_v1/mudlib/secure/include/global/
skylib_mudos_v1/mudlib/secure/save/
skylib_mudos_v1/mudlib/secure/save/handlers/
skylib_mudos_v1/mudlib/secure/std/classes/
skylib_mudos_v1/mudlib/secure/std/modules/
skylib_mudos_v1/mudlib/std/commands/
skylib_mudos_v1/mudlib/std/commands/shadows/
skylib_mudos_v1/mudlib/std/creator/
skylib_mudos_v1/mudlib/std/dom/
skylib_mudos_v1/mudlib/std/effects/
skylib_mudos_v1/mudlib/std/effects/external/
skylib_mudos_v1/mudlib/std/effects/fighting/
skylib_mudos_v1/mudlib/std/effects/priest/
skylib_mudos_v1/mudlib/std/effects/room/
skylib_mudos_v1/mudlib/std/environ/
skylib_mudos_v1/mudlib/std/guilds/
skylib_mudos_v1/mudlib/std/guilds/old/
skylib_mudos_v1/mudlib/std/languages/
skylib_mudos_v1/mudlib/std/languages/BACKUPS/
skylib_mudos_v1/mudlib/std/liquids/
skylib_mudos_v1/mudlib/std/npc/
skylib_mudos_v1/mudlib/std/npc/goals/
skylib_mudos_v1/mudlib/std/npc/goals/basic/
skylib_mudos_v1/mudlib/std/npc/goals/misc/
skylib_mudos_v1/mudlib/std/npc/plans/
skylib_mudos_v1/mudlib/std/npc/plans/basic/
skylib_mudos_v1/mudlib/std/npc/types/
skylib_mudos_v1/mudlib/std/npc/types/helper/
skylib_mudos_v1/mudlib/std/npcs/
skylib_mudos_v1/mudlib/std/outsides/
skylib_mudos_v1/mudlib/std/races/shadows/
skylib_mudos_v1/mudlib/std/room/basic/topography/
skylib_mudos_v1/mudlib/std/room/controller/
skylib_mudos_v1/mudlib/std/room/inherit/topography/
skylib_mudos_v1/mudlib/std/room/topography/area/
skylib_mudos_v1/mudlib/std/room/topography/iroom/
skylib_mudos_v1/mudlib/std/room/topography/milestone/
skylib_mudos_v1/mudlib/std/shadows/curses/
skylib_mudos_v1/mudlib/std/shadows/disease/
skylib_mudos_v1/mudlib/std/shadows/fighting/
skylib_mudos_v1/mudlib/std/shadows/healing/
skylib_mudos_v1/mudlib/std/shadows/magic/
skylib_mudos_v1/mudlib/std/shadows/poison/
skylib_mudos_v1/mudlib/std/shadows/rituals/
skylib_mudos_v1/mudlib/std/shadows/room/
skylib_mudos_v1/mudlib/std/shops/controllers/
skylib_mudos_v1/mudlib/std/shops/objs/
skylib_mudos_v1/mudlib/std/shops/player_shop/
skylib_mudos_v1/mudlib/std/socket/
skylib_mudos_v1/mudlib/std/soul/
skylib_mudos_v1/mudlib/std/soul/d/
skylib_mudos_v1/mudlib/std/soul/e/
skylib_mudos_v1/mudlib/std/soul/i/
skylib_mudos_v1/mudlib/std/soul/j/
skylib_mudos_v1/mudlib/std/soul/k/
skylib_mudos_v1/mudlib/std/soul/l/
skylib_mudos_v1/mudlib/std/soul/n/
skylib_mudos_v1/mudlib/std/soul/o/
skylib_mudos_v1/mudlib/std/soul/q/
skylib_mudos_v1/mudlib/std/soul/u/
skylib_mudos_v1/mudlib/std/soul/v/
skylib_mudos_v1/mudlib/std/soul/y/
skylib_mudos_v1/mudlib/std/soul/z/
skylib_mudos_v1/mudlib/std/stationery/
skylib_mudos_v1/mudlib/w/
skylib_mudos_v1/mudlib/w/default/
skylib_mudos_v1/mudlib/w/default/armour/
skylib_mudos_v1/mudlib/w/default/clothes/
skylib_mudos_v1/mudlib/w/default/item/
skylib_mudos_v1/mudlib/w/default/npc/
skylib_mudos_v1/mudlib/w/default/room/
skylib_mudos_v1/mudlib/w/default/weapon/
skylib_mudos_v1/mudlib/www/
skylib_mudos_v1/mudlib/www/download/
skylib_mudos_v1/mudlib/www/java/
skylib_mudos_v1/mudlib/www/secure/
skylib_mudos_v1/mudlib/www/secure/lpc/advanced/
skylib_mudos_v1/mudlib/www/secure/lpc/intermediate/
skylib_mudos_v1/v22.2b14-DSv10/
skylib_mudos_v1/v22.2b14-DSv10/ChangeLog.old/
skylib_mudos_v1/v22.2b14-DSv10/Win32/
skylib_mudos_v1/v22.2b14-DSv10/compat/
skylib_mudos_v1/v22.2b14-DSv10/compat/simuls/
skylib_mudos_v1/v22.2b14-DSv10/include/
skylib_mudos_v1/v22.2b14-DSv10/mudlib/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/clone/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/command/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/data/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/etc/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/include/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/inherit/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/inherit/master/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/log/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/single/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/single/tests/compiler/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/single/tests/efuns/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/single/tests/operators/
skylib_mudos_v1/v22.2b14-DSv10/testsuite/u/
skylib_mudos_v1/v22.2b14-DSv10/tmp/
skylib_mudos_v1/v22.2b14-DSv10/windows/
/**
 * The leaflet handler keeps track of pages in leaflets,
 * so that each of 100 identical leaflets won't have to
 * store all text in their save files.  It also caches
 * the data a little to reduce disk access.
 * @author Sandoz, 2002.
 */

#include <leaflet.h>
#include <language.h>
#include <origin.h>

#define LEAFLET_SAVE_DIR   SAVE_DIR "/leaflets/"
#define PRINT_RUN_SAVE_DIR LEAFLET_SAVE_DIR "print_runs/"

#define FILE(x)            LEAFLET_SAVE_DIR+x
#define PRINTER_DIR(x)     PRINT_RUN_SAVE_DIR+x+"/"

#define MAX_CACHE          5
#define DAY                (60*60*24)
#define TIME_OUT           (DAY*30)

/**
 * This class stores the data for a specific leaflet.
 * @member author the person who printed the leaflet
 * @member id the id of the leaflet
 * @member created the time this leaflet was created
 * @member time the time this leaflet was last accessed
 * @member text the content of the leaflet
 * @member copyright the copyright holder of the leaflet
 */
class leaflet_data {
    string author;
    int id;
    int created;
    int time;
    mixed text;
    string copyright;
}

/**
 * This class stores the data for different printers.
 * @member name the name of the printer
 * @member long the long desc of their leaflets
 */
class printer_data {
    string name;
    string long;
}

/**
 * This method stores the data for a single player
 * in the specified print shop.
 * @member id the id of the shop the data is for
 * @member name the name of the player the data is for
 * @member works the pending works for the player in the shop
 */
class player_print_data {
    int id;
    string player;
    class print_data *works;
}

private int *banned;
private mapping printers, copy;

private nosave mapping cache;
private nosave int current_id;
private nosave class player_print_data current_player_data;
private nosave string *shops;

private void clean_shop();
private void expire_print_runs();

private void save_me() {
    unguarded( (: save_object, LEAFLET_SAVE_DIR+"main" :) );
} /* save_me() */

/** @ignore yes */
void create() {
    if( !unguarded( (: dir_exists, LEAFLET_SAVE_DIR :) ) )
        unguarded( (: mkdir, LEAFLET_SAVE_DIR :) );

    if( !unguarded( (: dir_exists, PRINT_RUN_SAVE_DIR :) ) )
        unguarded( (: mkdir, PRINT_RUN_SAVE_DIR :) );

    if( unguarded( (: file_exists, LEAFLET_SAVE_DIR+"main.o" :) ) )
        unguarded( (: restore_object, LEAFLET_SAVE_DIR+"main" :) );

    cache = ([ ]);

    if( !banned )
        banned = ({ });

    if( !mapp(printers) )
        printers = ([ ]);

    if( !mapp(copy) )
        copy = ([ ]);

    expire_print_runs();

} /* create() */

/**
 * This method returns the time after which unclaimed print
 * runs expire and are deleted.
 */
int query_time_out() { return TIME_OUT; }

/** @ignore yes */
private void delete_new_leaflet( class print_data data ) {
    object ob;

    if( ob = clone_object( data->file ) ) {
        int id;

        if( data->save ) {
            if( data->save[0] )
                ob->init_static_arg( data->save[0] );
            if( data->save[1] )
                ob->init_dynamic_arg( data->save[1] );
        }

        if( intp( id = ob->query_leaflet_id() ) && id > 0 ) {
            unguarded( (: rm, FILE(id) :) );
            map_delete( cache, id );
        }

        ob->dest_me();
    }

    if( ob )
        destruct(ob);

} /* delete_new_leaflet() */

/**
 * This method is called by the refresh handler.
 * @param name the name of the player being refreshed
 */
void player_refreshed( string name ) {
    string *all, file;

    if( origin() != ORIGIN_LOCAL && PO != find_object(REFRESH_H) )
        return;

    if( !sizeof( all = unguarded( (: get_dir, PRINT_RUN_SAVE_DIR :) ) ) )
        return;

    foreach( file in all ) {
        file = PRINTER_DIR(file)+name+".o";
        if( unguarded( (: file_exists, file :) ) ) {
            class player_print_data data;

            data = restore_variable( unguarded( (: read_file, file :) ) );

            if( classp(data) ) {
                class print_data run;

                foreach( run in data->works )
                    if( run->new_leaflet )
                        delete_new_leaflet( run );
            }

            unguarded( (: rm, file :) );
        }
    }

    current_player_data = 0;

} /* player_refreshed() */

/** @ignore yes */
private void clean_shop() {
    class player_print_data data;
    class print_data run;
    string shop, *players, player;

    if( sizeof(shops) ) {
        shop = PRINTER_DIR(shops[0]);
        shops = shops[1..];
    }

    if( !sizeof(shops) )
        call_out( (: expire_print_runs() :), DAY );
    else
        call_out( (: clean_shop() :), 60 );

    if( !sizeof( players = unguarded( (: get_dir, shop :) ) ) ) {
        unguarded( (: rmdir, shop :) );
        return;
    }

    foreach( player in players ) {
        if( !PLAYER_H->test_user( player[0..<3] ) ) {
            player_refreshed( player[0..<3] );
            continue;
        }

        player = shop + player;
        data = restore_variable( unguarded( (: read_file, player :) ) );

        if( !classp(data) ) {
            unguarded( (: rm, player :) );
            continue;
        }

        foreach( run in data->works ) {
            if( run->time + TIME_OUT < time() ) {
                data->works -= ({ run });
                if( run->new_leaflet )
                    catch( delete_new_leaflet( run ) );
            }
        }

        if( !sizeof(data->works) )
            unguarded( (: rm, player :) );

    }

    if( !sizeof( unguarded( (: get_dir, shop :) ) ) ) {
        unguarded( (: rmdir, shop :) );
        return;
    }

} /* clean_shop() */

/** @ignore yes */
private void expire_print_runs() {
    if( !sizeof( shops = unguarded( (: get_dir, PRINT_RUN_SAVE_DIR :) ) ) ) {
        call_out( (: expire_print_runs() :), DAY );
        return;
    }

    call_out( (: clean_shop() :), 60 );

} /* expire_print_runs() */

/** @ignore yes */
private void check_cache() {
    if( sizeof(cache) > MAX_CACHE && find_call_out("clean_cache") == -1 )
        call_out("clean_cache", 1 );
} /* check_cache() */

/** @ignore yes */
private int load_leaflet( int id ) {
    class leaflet_data leaflet;

    if( cache[id] ) {
        ((class leaflet_data)cache[id])->time = time();
        return 1;
    }

    if( !intp(id) || !file_exists( FILE(id) ) )
        return 0;

    leaflet = restore_variable( unguarded( (: read_file, FILE(id) :) ) );
    leaflet->time = time();
    cache[id] = leaflet;

    check_cache();
    return 1;

} /* load_leaflet() */

/** @ignore yes */
private void save_leaflet( int id ) {
    if( !cache[id] )
        return;

    unguarded( (: write_file, FILE(id), save_variable( cache[id] ), 1 :) );

} /* save_leaflet() */

/**
 * This method deletes a leaflet by id.  Use with care.
 * @param id the id of the leaflet to delete
 * @return 1 upon success, 0 upon failure
 */
int delete_leaflet( int id ) {
    if( !lordp(TP) && !sizeof( filter( previous_object(-1),
        (: $1->query_leaflet_shop() :) ) ) ) {
        write("Sorry, only lords can delete leaflets.\n");
        return 0;
    }

    if( !id || !intp(id) )
        return 0;

    unguarded( (: rm, FILE(id) :) );
    map_delete( cache, id );
    return 1;

} /* save_leaflet() */

/**
 * This method returns the leaflets that have been banned.
 * @return the banned leaflets
 */
int *query_banned() { return banned; }

/**
 * This method bans a leaflet with a specified id.
 * Use with care.
 * @param id the id to ban
 * @return 1 upon success, 0 upon failure
 */
int ban_leaflet( int id ) {
    if( !lordp(TP) ) {
        write("Sorry, only lords can ban leaflets.\n");
        return 0;
    }

    if( !intp(id) || member_array( id, banned ) != -1 )
        return 0;

    banned += ({ id });
    save_me();
    return 1;

} /* ban_leaflet() */

/**
 * This method removes the ban from a specified leaflet id.
 * It is practically useless, as no ids should be unbanned,
 * unless you're absolutely certain there are no leaflets
 * left in game with the id.
 * @param id the id to unban
 */
void unban_leaflet( int id ) {
    if( lordp(TP) )
        banned -= ({ id });
    save_me();
} /* unban_leaflet() */

/**
 * @ignore yes
 * This method cleans up the cache.
 */
void clean_cache() {
    int *ids, id;
    function f;

    if( sizeof(cache) <= MAX_CACHE )
        return;

    f = function( mixed i, mixed j ) {
            i = ((class leaflet_data)cache[i])->time;
            j = ((class leaflet_data)cache[j])->time;
            if( i < j )
                return 1;
            if( i > j )
                return -1;
            return 0;
        };

    ids = sort_array( keys(cache), f );
    ids = ids[MAX_CACHE..];

    // Update the time stamp and delete them from cache.
    foreach( id in ids ) {
        save_leaflet( id );
        map_delete( cache, id );
    }

} /* clean_cache() */

/**
 * This method figures out an id for a new leaflet.
 * @return the new id
 */
int query_new_id() {
    int id;

    id = current_id;

    while( unguarded( (: file_size, FILE(id) :) ) > 0 )
        id++;

    current_id = id + 1;

    return id;

} /* query_new_id() */

/**
 * This method creates a new leaflet and returns
 * the unique id number for it.
 * @param author the author of the leaflet
 * @param text the content of the leaflet
 * @param copyright the copyright holder of the leaflet
 * @return the unique id of the leaflet
 */
int new_leaflet( string author, mixed text, string copyright ) {
    class leaflet_data leaflet;
    int id, tmp;

    if( !sizeof(text) || !author || author == "")
        return 0;

    id = query_new_id();

    if( !sizeof(copyright) )
        copyright = 0;

    leaflet = new( class leaflet_data,
        author : author,
        id : id,
        created : time(),
        time : time(),
        text : text,
        copyright : copyright );

    cache[id] = leaflet;
    save_leaflet(id);

    if( copyright ) {
        tmp = sizeof( text[READ_MESS] );
        if( !pointerp(copy[tmp]) )
            copy[tmp] = ({ });
        copy[tmp] += ({ id });
        save_me();
    }

    check_cache();

    return id;

} /* new_leaflet() */

/**
 * This method copyrights the specified leaflet.
 * This fails if the leaflet already has a copyright holder.
 * @param id the id of the leaflet to copyright
 * @param copyright the copyright holder of the leaflet
 * @return 1 if successful, 0 if not
 */
int copyright_leaflet( int id, string copyright ) {
    int tmp;
    class leaflet_data data;

    if( !sizeof(copyright) || !load_leaflet( id ) ||
        ((class leaflet_data)cache[id])->copyright )
        return 0;

    ((class leaflet_data)cache[id])->copyright = copyright;
    ((class leaflet_data)cache[id])->time = time();
    save_leaflet(id);

    data = cache[id];
    tmp = sizeof( data->text[READ_MESS] );

    if( !pointerp(copy[tmp]) )
        copy[tmp] = ({ });
    copy[tmp] += ({ id });
    save_me();

    return 1;

} /* copyright_leaflet() */

/**
 * This method returns a mapping of all copyrighted leaflets.
 * It will be in the form of -
 * ([ tsize1 : ({ id1, id2 }), tsize2 : ({ id3 }) ]),
 * where tsize is the size of text on the leaflet, and ids are
 * the ids of leaflets with that same amount of text.
 * @return a mapping of all copyrighted leaflets
 */
mapping query_copyrighted() { return copy; }

/**
 * This method tests whether or not the text we are trying
 * to copy is under copyright.
 * @param text the text to test
 * @return the id of the leaflet we're identical with, 0 if not
 */
int query_copyright_protected( string text ) {
    int id, *arr;
    class leaflet_data data;

    if( !arr = copy[sizeof(text)] )
        return 0;

    foreach( id in arr )
        if( load_leaflet(id) ) {
            data = cache[id];
            if( data->text[READ_MESS] == text )
                return id;
        }

    return 0;

} /* query_copyright_protected() */

/** @ignore yes */
class leaflet_data query_leaflet_data( int id ) {
    if( !load_leaflet( id ) )
        return 0;

    ((class leaflet_data)cache[id])->time = time();

    return cache[id];

} /* query_leaflet_data() */

/**
 * This method returns the text for the specified leaflet id.
 * @param id the id to get the pages for
 * @return the text for the specified leaflet
 */
mixed query_text( int id ) {
    class leaflet_data data;

    if( member_array( id, banned ) != -1 )
        return ({"Sorry, your leaflet appears to have been banned.", 0,
            "common", 1 });

    if( !classp( data = query_leaflet_data(id) ) )
        return ({ });

    data->time = time();
    return data->text;

} /* query_text() */

/**
 * This method returns the author of the specified
 * leaflet.
 * @param id the id to get the author for
 * @return the author of the specified leaflet
 */
string query_author( int id ) {
    if( !load_leaflet( id ) )
        return 0;
    ((class leaflet_data)cache[id])->time = time();
    return ((class leaflet_data)cache[id])->author;
} /* query_author() */

/**
 * This method returns the person who holds the copyright of
 * the specified leaflet.
 * @param id the id to get the copyright for
 * @return the person holding the copyright of the leaflet
 */
string query_copyright( int id ) {
    if( !load_leaflet( id ) )
        return 0;
    ((class leaflet_data)cache[id])->time = time();
    return ((class leaflet_data)cache[id])->copyright;
} /* query_copyright() */

/** @ignore yes */
mapping query_cache() { return cache; }

/**
 * This method queries if the specified printer exists.
 * @param id the id of the printer to test
 * @return 1 if they exist, 0 if not
 */
int query_printer( int id ) { return !undefinedp(printers[id]); }

/**
 * This method returns the id of the specified printer name.
 * @param str the name of the printer to get the id for
 * @return the id, or 0 if there is none by the name
 */
int query_printer_id( string str ) {
    int id;
    class printer_data data;

    foreach( id, data in printers )
        if( data->name == str )
            return id;

    return 0;

} /* query_printer_id() */

/**
 * This method figures out a new id for a new printer.
 * @return the new printer id
 */
int query_new_printer_id() {
    int id;

    while( query_printer(++id) );

    return id;

} /* query_new_printer_id() */

/**
 * This method adds a new printer into the handler.
 * @param the name of the printer to add
 * @param the str desc of their leaflets
 * @return the new id of the printer, or 0 if we failed
 */
int add_printer( string name, string str ) {
    int id;
    class printer_data data;

    if( !name || name == "")
        return 0;

    if( id = query_printer_id( name ) )
        return id;

    if( !str || str == "")
        str = "This $size$ leaflet looks like it has been churned out from "
              "one of the cities' printing presses.";

    id = query_new_printer_id();

    data = new( class printer_data,
        name : name,
        long : str );

    printers[id] = data;
    save_me();
    return id;

} /* add_printer() */

/**
 * This method returns the long description of the
 * specified leaflet company.
 * @param id the id of the leaflet company to get the long desc for
 * @return the long desc for the company
 */
string query_leaflet_long( int id ) {
    if( !query_printer(id) )
        return "This $size$ leaflet looks like it has been churned out from "
               "one of the cities' printing presses.\n";

    return replace_string( printers[id]->long+"\n",
        "$shop$", printers[id]->name );

} /* query_leaflet_long() */

/**
 * This method sets the long description for leaflets
 * printed in the specified company.
 * @param id the printer id set the long desc for
 * @param str the long description
 * @return 1 upon success, 0 upon failure
 */
int set_leaflet_long( int id, string str ) {
    if( !query_printer(id) || !str || str == "")
        return 0;

    printers[id]->long = str;
    save_me();
    return 1;

} /* set_leaflet_long() */

/**
 * This method returns the name of the specified printing company.
 * @param id the id of the name to get
 * @return the name of the printer
 */
string query_printer_name( int id ) {
    if( !query_printer(id) )
        return "Leaflets Galore";

    return printers[id]->name;

} /* query_printer_name() */

/**
 * This method removes a printer.
 * @param id the id of the printer to remove
 * @return 1 if successfully removed, 0 if not
 */
int remove_printer( int id ) {
    if( !lordp(TP) ) {
        write("Sorry, only lords can remove printers.\n");
        return 0;
    }

    map_delete( printers, id );
    save_me();
    return 1;

} /* remove_printer() */

/**
 * This method returns all printers and their data.
 * @return all data for printers
 */
mapping query_printers() { return printers; }

/** @ignore yes */
string normalize_name( string name ) {
    int i;

    name = lower_case(name);

    for( i = 0; i < strlen(name); i++ ) {
        if( name[i] == ''' ) {
            name = name[0..i-1] + name[i+1..];
            i--;
            continue;
        }

        if( name[i] < 'a' || name[i] > 'z')
            name[i] = '_';
    }

    return implode( explode( name, "_") - ({ 0, "" }), "_");

} /* normalize_name() */

/**
 * This method restores the print runs for the specified player.
 * @param id the shop id to restore the player's print runs for
 * @param player the name of the player to restore the print runs for
 */
private int restore_current_player_data( int id, string player ) {
    class player_print_data data;
    string name;

    if( classp(current_player_data) && current_player_data->id == id &&
        current_player_data->player == player )
        return 1;

    if( !name = query_printer_name( id ) )
        return 0;

    name = normalize_name( name );

    if( unguarded( (: file_exists, PRINTER_DIR(name)+player+".o" :) ) ) {
        data = restore_variable( unguarded( (: read_file,
               PRINTER_DIR(name)+player+".o" :) ) );
        if( classp(data) ) {
            current_player_data = data;
            return 1;
        }
    }

    current_player_data = new( class player_print_data,
                               id     : id,
                               player : player,
                               works  : ({ }) );
    return 1;

} /* restore_current_player_data() */

/**
 * This method saves the print runs for the current player,
 * if the shop id and player name match.
 * @param id the shop id to save the player's print runs for
 * @param player the name of the player to save the print runs for
 */
private int save_current_player_data( int id, string player ) {
    string name;

    if( id != current_player_data->id ||
        player != current_player_data->player )
        return 0;

    if( !name = query_printer_name( id ) )
        return 0;

    name = normalize_name( name );

    if( unguarded( (: file_exists, PRINTER_DIR(name)+player+".o" :) ) &&
        ( !classp(current_player_data) ||
        !sizeof(current_player_data->works) ) ) {
        unguarded( (: rm, PRINTER_DIR(name)+player+".o" :) );
        current_player_data = 0;
        return 1;
    }

    if( !unguarded( (: dir_exists, PRINTER_DIR(name) :) ) )
        unguarded( (: mkdir, PRINTER_DIR(name) :) );

    return unguarded( (: write_file, PRINTER_DIR(name)+player+".o",
                         save_variable( current_player_data ), 1 :) );

} /* save_current_player_data() */

/**
 * This adds an object into the current set to be collected,
 * and saves the auto load info for the object.
 * @param name the name of the person adding the run
 * @param data the print run data
 * @return 1 if successfully added, 0 if not
 * @see query_print_run()
 * @see remove_print_run()
 */
int add_print_run( string name, class print_data data ) {
    if( !classp(data) || !sizeof(name) ||
        !restore_current_player_data( data->id, name ) )
        return 0;

    current_player_data->works += ({ data });

    return save_current_player_data( data->id, name );

} /* add_print_run() */

/**
 * This method returns the array of documents awaiting collection
 * for the specified player in the specified shop.  If there are
 * no documents, this returns 0.
 * @param id the id of the shop to get the documents for
 * @param name the player to get the documents for
 * @return an array containing the player information, or 0 if none
 * @see add_print_run()
 * @see remove_print_run()
 */
class print_data *query_print_run( int id, string name ) {
    if( !sizeof(name) || !restore_current_player_data( id, name ) ||
        !sizeof(((class player_print_data)current_player_data)->works) )
        return 0;

    return current_player_data->works;

} /* query_print_run() */

/**
 * This method will remove a players print run.
 * @param id the id of the shop to remove the print run in
 * @param name the player whose print run is to be removed
 * @param data the run to remove
 * @see add_print_run()
 * @see query_print_run()
 */
int remove_print_run( int id, string name, class print_data data ) {
    if( !sizeof(name) || !restore_current_player_data( data->id, name ) ||
        !sizeof(current_player_data->works) )
        return 0;

    current_player_data->works -= ({ data });

    return save_current_player_data( id, name );

} /* remove_print_run() */

class player_print_data query_current_player_data() {
    return current_player_data;
} /* query_current_player_data() */

/** @ignore yes */
void dest_me() { destruct(TO); }