dw_fluffos_v2/
dw_fluffos_v2/fluffos-2.9-ds2.05/
dw_fluffos_v2/fluffos-2.9-ds2.05/ChangeLog.old/
dw_fluffos_v2/fluffos-2.9-ds2.05/Win32/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/simuls/
dw_fluffos_v2/fluffos-2.9-ds2.05/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/clone/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/command/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/data/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/etc/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/master/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/log/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/compiler/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/efuns/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/operators/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/u/
dw_fluffos_v2/fluffos-2.9-ds2.05/tmp/
dw_fluffos_v2/fluffos-2.9-ds2.05/windows/
dw_fluffos_v2/lib/
dw_fluffos_v2/lib/binaries/cmds/
dw_fluffos_v2/lib/binaries/cmds/creator/
dw_fluffos_v2/lib/binaries/cmds/living/
dw_fluffos_v2/lib/binaries/cmds/player/
dw_fluffos_v2/lib/binaries/d/admin/obj/
dw_fluffos_v2/lib/binaries/d/liaison/
dw_fluffos_v2/lib/binaries/global/virtual/
dw_fluffos_v2/lib/binaries/global/virtual/setup_compiler/
dw_fluffos_v2/lib/binaries/obj/handlers/autodoc/
dw_fluffos_v2/lib/binaries/obj/handlers/terrain_things/
dw_fluffos_v2/lib/binaries/obj/misc/
dw_fluffos_v2/lib/binaries/obj/misc/buckets/
dw_fluffos_v2/lib/binaries/obj/monster/
dw_fluffos_v2/lib/binaries/obj/reactions/
dw_fluffos_v2/lib/binaries/obj/reagents/
dw_fluffos_v2/lib/binaries/secure/cmds/creator/
dw_fluffos_v2/lib/binaries/secure/master/
dw_fluffos_v2/lib/binaries/std/
dw_fluffos_v2/lib/binaries/std/dom/
dw_fluffos_v2/lib/binaries/std/effects/object/
dw_fluffos_v2/lib/binaries/std/guilds/
dw_fluffos_v2/lib/binaries/std/languages/
dw_fluffos_v2/lib/binaries/std/races/
dw_fluffos_v2/lib/binaries/std/room/
dw_fluffos_v2/lib/binaries/std/room/basic/
dw_fluffos_v2/lib/binaries/std/shops/
dw_fluffos_v2/lib/binaries/std/shops/inherit/
dw_fluffos_v2/lib/binaries/www/
dw_fluffos_v2/lib/cmds/guild-race/
dw_fluffos_v2/lib/cmds/guild-race/crafts/
dw_fluffos_v2/lib/cmds/guild-race/other/
dw_fluffos_v2/lib/cmds/playtester/
dw_fluffos_v2/lib/cmds/playtester/senior/
dw_fluffos_v2/lib/d/admin/
dw_fluffos_v2/lib/d/admin/log/
dw_fluffos_v2/lib/d/admin/mapper/31-10-01/mapmaker/event/
dw_fluffos_v2/lib/d/admin/meetings/
dw_fluffos_v2/lib/d/admin/obj/
dw_fluffos_v2/lib/d/admin/room/we_care/
dw_fluffos_v2/lib/d/admin/save/
dw_fluffos_v2/lib/d/dist/
dw_fluffos_v2/lib/d/dist/mtf/
dw_fluffos_v2/lib/d/dist/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/chars/
dw_fluffos_v2/lib/d/dist/pumpkin/desert/
dw_fluffos_v2/lib/d/dist/pumpkin/gumboot/
dw_fluffos_v2/lib/d/dist/pumpkin/hospital/
dw_fluffos_v2/lib/d/dist/pumpkin/inherit/
dw_fluffos_v2/lib/d/dist/pumpkin/map/
dw_fluffos_v2/lib/d/dist/pumpkin/plain/
dw_fluffos_v2/lib/d/dist/pumpkin/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/save/
dw_fluffos_v2/lib/d/dist/pumpkin/squash/
dw_fluffos_v2/lib/d/dist/pumpkin/terrain/
dw_fluffos_v2/lib/d/dist/pumpkin/woods/
dw_fluffos_v2/lib/d/dist/start/
dw_fluffos_v2/lib/d/learning/TinyTown/buildings/
dw_fluffos_v2/lib/d/learning/TinyTown/map/
dw_fluffos_v2/lib/d/learning/TinyTown/roads/
dw_fluffos_v2/lib/d/learning/add_command/
dw_fluffos_v2/lib/d/learning/arms_and_weps/
dw_fluffos_v2/lib/d/learning/chars/
dw_fluffos_v2/lib/d/learning/cutnpaste/
dw_fluffos_v2/lib/d/learning/examples/npcs/
dw_fluffos_v2/lib/d/learning/examples/player_houses/npcs/
dw_fluffos_v2/lib/d/learning/examples/terrain_map/basic/
dw_fluffos_v2/lib/d/learning/functions/
dw_fluffos_v2/lib/d/learning/handlers/
dw_fluffos_v2/lib/d/learning/help_topics/npcs/
dw_fluffos_v2/lib/d/learning/help_topics/objects/
dw_fluffos_v2/lib/d/learning/help_topics/rcs_demo/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/crowd/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/situations/
dw_fluffos_v2/lib/d/learning/items/
dw_fluffos_v2/lib/d/learning/save/
dw_fluffos_v2/lib/d/liaison/
dw_fluffos_v2/lib/d/liaison/NEWBIE/doc/
dw_fluffos_v2/lib/d/liaison/NEWBIE/save/oldlog/
dw_fluffos_v2/lib/db/
dw_fluffos_v2/lib/doc/
dw_fluffos_v2/lib/doc/creator/
dw_fluffos_v2/lib/doc/creator/autodoc/include/reaction/
dw_fluffos_v2/lib/doc/creator/autodoc/include/ritual_system/
dw_fluffos_v2/lib/doc/creator/autodoc/include/talker/
dw_fluffos_v2/lib/doc/creator/autodoc/include/terrain_map/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/baggage/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clock/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clothing/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/cont_save/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/corpse/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/money/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/monster/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/scabbard/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/service_provider/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/state_changer/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/wand/
dw_fluffos_v2/lib/doc/creator/autodoc/std/book_dir/
dw_fluffos_v2/lib/doc/creator/autodoc/std/key/
dw_fluffos_v2/lib/doc/creator/autodoc/std/learning/
dw_fluffos_v2/lib/doc/creator/autodoc/std/map/
dw_fluffos_v2/lib/doc/creator/autodoc/std/race/
dw_fluffos_v2/lib/doc/creator/autodoc/std/weapon_logic/
dw_fluffos_v2/lib/doc/creator/files/
dw_fluffos_v2/lib/doc/creator/policy/
dw_fluffos_v2/lib/doc/creator/room/
dw_fluffos_v2/lib/doc/effects/
dw_fluffos_v2/lib/doc/ideas/
dw_fluffos_v2/lib/doc/known_command/
dw_fluffos_v2/lib/doc/lpc/basic_manual/
dw_fluffos_v2/lib/doc/lpc/intermediate/
dw_fluffos_v2/lib/doc/new/add_command/
dw_fluffos_v2/lib/doc/new/handlers/
dw_fluffos_v2/lib/doc/new/living/
dw_fluffos_v2/lib/doc/new/living/race/
dw_fluffos_v2/lib/doc/new/living/spells/
dw_fluffos_v2/lib/doc/new/player/
dw_fluffos_v2/lib/doc/new/room/guild/
dw_fluffos_v2/lib/doc/new/room/outside/
dw_fluffos_v2/lib/doc/new/room/storeroom/
dw_fluffos_v2/lib/doc/object/
dw_fluffos_v2/lib/doc/playtesters/
dw_fluffos_v2/lib/doc/policy/
dw_fluffos_v2/lib/doc/weapons/
dw_fluffos_v2/lib/global/handlers/
dw_fluffos_v2/lib/global/virtual/setup_compiler/
dw_fluffos_v2/lib/include/
dw_fluffos_v2/lib/include/cmds/
dw_fluffos_v2/lib/include/effects/
dw_fluffos_v2/lib/include/npc/
dw_fluffos_v2/lib/include/shops/
dw_fluffos_v2/lib/net/daemon/chars/
dw_fluffos_v2/lib/net/inherit/
dw_fluffos_v2/lib/net/intermud3/
dw_fluffos_v2/lib/net/intermud3/services/
dw_fluffos_v2/lib/net/obj/
dw_fluffos_v2/lib/net/save/
dw_fluffos_v2/lib/net/smnmp/
dw_fluffos_v2/lib/net/snmp/
dw_fluffos_v2/lib/obj/amulets/
dw_fluffos_v2/lib/obj/b_day/
dw_fluffos_v2/lib/obj/examples/
dw_fluffos_v2/lib/obj/food/alcohol/
dw_fluffos_v2/lib/obj/food/chocolates/
dw_fluffos_v2/lib/obj/food/fruits/
dw_fluffos_v2/lib/obj/food/meat/
dw_fluffos_v2/lib/obj/food/nuts/
dw_fluffos_v2/lib/obj/food/seafood/
dw_fluffos_v2/lib/obj/food/vegetables/
dw_fluffos_v2/lib/obj/fungi/
dw_fluffos_v2/lib/obj/furnitures/artwork/
dw_fluffos_v2/lib/obj/furnitures/bathroom/
dw_fluffos_v2/lib/obj/furnitures/beds/
dw_fluffos_v2/lib/obj/furnitures/cabinets/
dw_fluffos_v2/lib/obj/furnitures/chairs/
dw_fluffos_v2/lib/obj/furnitures/chests/
dw_fluffos_v2/lib/obj/furnitures/clocks/
dw_fluffos_v2/lib/obj/furnitures/crockery/
dw_fluffos_v2/lib/obj/furnitures/cupboards/
dw_fluffos_v2/lib/obj/furnitures/cushions/
dw_fluffos_v2/lib/obj/furnitures/fake_plants/
dw_fluffos_v2/lib/obj/furnitures/lamps/
dw_fluffos_v2/lib/obj/furnitures/mirrors/
dw_fluffos_v2/lib/obj/furnitures/outdoor/
dw_fluffos_v2/lib/obj/furnitures/safes/
dw_fluffos_v2/lib/obj/furnitures/shelves/
dw_fluffos_v2/lib/obj/furnitures/sideboards/
dw_fluffos_v2/lib/obj/furnitures/sofas/
dw_fluffos_v2/lib/obj/furnitures/stoves/
dw_fluffos_v2/lib/obj/furnitures/tables/
dw_fluffos_v2/lib/obj/furnitures/wardrobes/
dw_fluffos_v2/lib/obj/handlers/
dw_fluffos_v2/lib/obj/handlers/autodoc/
dw_fluffos_v2/lib/obj/jewellery/anklets/
dw_fluffos_v2/lib/obj/jewellery/bracelets/
dw_fluffos_v2/lib/obj/jewellery/earrings/
dw_fluffos_v2/lib/obj/jewellery/misc/
dw_fluffos_v2/lib/obj/jewellery/necklaces/
dw_fluffos_v2/lib/obj/jewellery/rings/
dw_fluffos_v2/lib/obj/media/
dw_fluffos_v2/lib/obj/misc/buckets/
dw_fluffos_v2/lib/obj/misc/jars/
dw_fluffos_v2/lib/obj/misc/papers/
dw_fluffos_v2/lib/obj/misc/player_shop/
dw_fluffos_v2/lib/obj/misc/shops/
dw_fluffos_v2/lib/obj/misc/traps/
dw_fluffos_v2/lib/obj/monster/
dw_fluffos_v2/lib/obj/monster/godmother/
dw_fluffos_v2/lib/obj/monster/transport/
dw_fluffos_v2/lib/obj/plants/inherit/
dw_fluffos_v2/lib/obj/potions/
dw_fluffos_v2/lib/open/boards/
dw_fluffos_v2/lib/save/autodoc/
dw_fluffos_v2/lib/save/bank_accounts/
dw_fluffos_v2/lib/save/boards/frog/
dw_fluffos_v2/lib/save/books/bed_catalog/
dw_fluffos_v2/lib/save/creators/
dw_fluffos_v2/lib/save/mail/
dw_fluffos_v2/lib/save/mail/p/
dw_fluffos_v2/lib/save/soul/data/
dw_fluffos_v2/lib/save/tasks/
dw_fluffos_v2/lib/save/vaults/
dw_fluffos_v2/lib/secure/cmds/lord/
dw_fluffos_v2/lib/secure/config/
dw_fluffos_v2/lib/secure/items/
dw_fluffos_v2/lib/secure/player/
dw_fluffos_v2/lib/soul/
dw_fluffos_v2/lib/soul/i/
dw_fluffos_v2/lib/soul/j/
dw_fluffos_v2/lib/soul/k/
dw_fluffos_v2/lib/soul/o/
dw_fluffos_v2/lib/soul/q/
dw_fluffos_v2/lib/soul/to_approve/
dw_fluffos_v2/lib/soul/u/
dw_fluffos_v2/lib/soul/v/
dw_fluffos_v2/lib/soul/wish_list/
dw_fluffos_v2/lib/soul/y/
dw_fluffos_v2/lib/soul/z/
dw_fluffos_v2/lib/std/creator/
dw_fluffos_v2/lib/std/effects/
dw_fluffos_v2/lib/std/effects/attached/
dw_fluffos_v2/lib/std/effects/external/
dw_fluffos_v2/lib/std/effects/fighting/
dw_fluffos_v2/lib/std/effects/other/
dw_fluffos_v2/lib/std/environ/
dw_fluffos_v2/lib/std/guilds/
dw_fluffos_v2/lib/std/hospital/
dw_fluffos_v2/lib/std/house/
dw_fluffos_v2/lib/std/house/onebedhouse/
dw_fluffos_v2/lib/std/house/onebedhut/
dw_fluffos_v2/lib/std/house/tworoomflat/
dw_fluffos_v2/lib/std/languages/
dw_fluffos_v2/lib/std/liquids/
dw_fluffos_v2/lib/std/nationality/
dw_fluffos_v2/lib/std/nationality/accents/
dw_fluffos_v2/lib/std/nationality/accents/national/
dw_fluffos_v2/lib/std/nationality/accents/regional/
dw_fluffos_v2/lib/std/npc/goals/
dw_fluffos_v2/lib/std/npc/goals/basic/
dw_fluffos_v2/lib/std/npc/goals/misc/
dw_fluffos_v2/lib/std/npc/inherit/
dw_fluffos_v2/lib/std/npc/plans/
dw_fluffos_v2/lib/std/npc/plans/basic/
dw_fluffos_v2/lib/std/outsides/
dw_fluffos_v2/lib/std/races/shadows/
dw_fluffos_v2/lib/std/room/basic/topography/
dw_fluffos_v2/lib/std/room/controller/
dw_fluffos_v2/lib/std/room/controller/topography/
dw_fluffos_v2/lib/std/room/furniture/games/
dw_fluffos_v2/lib/std/room/furniture/inherit/
dw_fluffos_v2/lib/std/room/inherit/carriage/
dw_fluffos_v2/lib/std/room/inherit/topography/
dw_fluffos_v2/lib/std/room/punishments/
dw_fluffos_v2/lib/std/room/topography/area/
dw_fluffos_v2/lib/std/room/topography/iroom/
dw_fluffos_v2/lib/std/room/topography/milestone/
dw_fluffos_v2/lib/std/shadows/
dw_fluffos_v2/lib/std/shadows/attached/
dw_fluffos_v2/lib/std/shadows/curses/
dw_fluffos_v2/lib/std/shadows/disease/
dw_fluffos_v2/lib/std/shadows/fighting/
dw_fluffos_v2/lib/std/shadows/room/
dw_fluffos_v2/lib/std/shops/controllers/
dw_fluffos_v2/lib/std/shops/objs/
dw_fluffos_v2/lib/std/shops/player_shop/
dw_fluffos_v2/lib/std/shops/player_shop/office_code/
dw_fluffos_v2/lib/std/socket/
dw_fluffos_v2/lib/www/
dw_fluffos_v2/lib/www/external/autodoc/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/images/
dw_fluffos_v2/lib/www/external/java/telnet/examples/
dw_fluffos_v2/lib/www/external/java/telnet/tools/
dw_fluffos_v2/lib/www/pics/
dw_fluffos_v2/lib/www/secure/creator/
dw_fluffos_v2/lib/www/secure/editors/
dw_fluffos_v2/lib/www/secure/survey_results/
dw_fluffos_v2/win32/
/*  -*- LPC -*-  */
/*
 * $Locker:  $
 * $Id: board_handler.c,v 1.71 2003/05/06 22:31:22 ceres Exp $
 */
/**
 * Keeps track of all the bulletin board notes and related information.
 *
 * @index bulletin
 * @author Pinkfish
 */
#include <board.h>
#include <mail.h>
#include <clubs.h>
#include <player_handler.h>

//#define DEBUG 1

#define T_MIN 0
#define T_MAX 1
#define T_TIMEOUT 2
#define DEFAULT_MIN 10
#define DEFAULT_MAX 80
#define DEFAULT_TIMEOUT 14
#define ARCHIVE_DIR "/open/boards/"
// maximum archive file size
#define MAX_ARCHIVE_SIZE 1048576

#define BACKUPS_DIR "/save/board_backups/"
#define NEWSRC_SAVE_DIR "/save/newsrc/"
#define BACKUP_TIME_OUT (7 * 24 * 60 * 60)
#define BOARD_HANDLE_VERSION 1

#ifdef USE_RAMDISK
#define CACHE_SIZE 10
#else
#define CACHE_SIZE 250
#endif

#define CACHE_TIMEOUT 1800
#define NEWSRC_SAVE_DELAY 300

class newsrc {
  int cached;
  int changed;
  mapping newsrc;
  string *kill;
  string *board_order;
}

class read_access_cache {
   int read_time;
   int read_access;
}

private int num;
private int board_version;
private mapping archives;
private mapping boards;
private mapping priv;
private mapping security;
private mapping timeouts;
private nosave string *_allowed_objects;
private nosave mapping message_cache;
private nosave mapping _newsrc_cache;
private nosave int total_reads, cache_hits, ram_hits, clean_out;
private nosave int _ram_exists;
private nosave int _newsrc_reads, _newsrc_cache_hits;
private mapping _idiots;
private nosave mapping _read_access_cache;

string query_archive(string board);
protected int zap_message(string board, int num, string name);
int can_delete_message(string board, int off, string pname);
void save_me();

void create() {
  int number;
  int last;
  string line;
  string *lines;

  seteuid(master()->creator_file(file_name(this_object())));
  num = 1;
  boards = ([ ]);
  security = ([ ]);
  _read_access_cache = ([ ]);
  priv = ([ ]); /* Private boards... only lords or specified people
                 * can read. */
  timeouts = ([ ]);
  archives = ([ ]);
  message_cache = ([ ]);
  _newsrc_cache = ([ ]);

  _allowed_objects = ({
    // These are the objects allowed to post...
    BOARD_HAND,
      "/obj/misc/board",
      "/obj/misc/board_mas",
      "/obj/handlers/applications_handler",
      "/w/ceres/board",
      "/w/ceres/board_mas",
      "/www/boards",
      "/www/secure/nboards",
      "/obj/handlers/club_handler",
      "/obj/handlers/deity_handler",
      "/obj/handlers/folder_handler",
      "/obj/handlers/playtesters",
      "/obj/handlers/error_tracker",
      "/obj/handlers/twiki",
      "/d/forn/utils/error_tracker",
      "/d/forn/handlers/peer_review",
      "/d/sur/utils/error_tracker",
      "/d/cwc/utils/error_tracker",
      "/d/Ankh-Morpork_dev/utils/error_tracker",
      "/d/liaison/utils/error_tracker",
      "/d/am/utils/error_tracker",
      "/d/guilds/priests/items/desk",
      "/d/ram/error_tracker",
      "/d/ram/cool/bank",
      "/d/special/error_tracker",
      "/d/klatch/utils/error_tracker",
      "/d/guilds/error_tracker",
      "/d/special/utils/error_tracker",
      "/d/am/patrician/pat_applications",
      "/d/am/patrician/pat_complaints",
      "/cmds/player/news",
      "/cmds/player/apply",
      "/d/guilds/assassins/Ankh-Morpork/admin/vote_room",
      "/d/special/player_shops/tarnach's_office",
      "/std/shops/player_shop/office",
      "/d/playtesters/handlers/applications",
      "/d/guilds/warriors/Ankh-Morpork/voting_booth",
      "/secure/cmds/creator/errors",
      "/obj/handlers/player_council",
      "/d/guilds/wizards/utils/board_hand",
      "/obj/handlers/complaints_handler",
      "/obj/handlers/project_management",
      "/d/guilds/thieves/Ankh-Morpork/rooms/ground/voting_booth",
      "/d/underworld/utils/error_tracker"      
      });

  _idiots = ([ ]);

  if (file_size(BOARD_FILE +".gz") > 0) {
     unguarded((: cp, BOARD_FILE +".gz", BACKUPS_DIR +"boards."+ time() :));
  }
  lines = unguarded((: get_dir, BACKUPS_DIR +"boards.*" :));
  if ( sizeof( lines ) > 6 ) {
    last = time() - BACKUP_TIME_OUT;
    foreach(line in lines) {
      sscanf(line, "boards.%d", number);
      if (number < last)
        unguarded((: rm, BACKUPS_DIR + line :));
    }
  }
  if(!unguarded((: restore_object, BOARD_FILE+".gz" :)))
    unguarded((: restore_object, BOARD_FILE :));

  call_out("expire_boards", 5);
} /* create() */

/**
 * This method returns the file name of the message to use in retreiving
 * the message.
 * @param board the board to get the message from
 * @param num the number of the mesage to download
 */
private string get_filename(string board, int num) {
   string fixed_board;

   fixed_board = replace(board, ({" ", "_", "'", ""}));
 
   // return the filename in the appropriate subdirectory.
   return BOARD_DIR + fixed_board + "/" + num;
} /* get_filename() */

/**
 * This method returns the file name of the ramdisk cach of the message
 * the message.
 * @param board the board to get the message from
 * @param num the number of the mesage to download
 */
private string get_ram_filename(string board, int num) {
   string fixed_board;

   fixed_board = replace(board, ({" ", "_", "'", ""}));

   if(!_ram_exists) {
     _ram_exists = file_size(BOARD_RAM_DIR);
   }

   // return the filename in the appropriate subdirectory.
   // If BOARD_RAM_DIR exists we return the ramdisk file otherwise
   // we return the standard file.
   if(_ram_exists == -2)
     return BOARD_RAM_DIR + fixed_board + "/" + num;
   else
     return BOARD_DIR + fixed_board + "/" + num;
}

/**
 * Returns the names of all the boards.
 * @return the names of all the boards
 */
string *query_boards() {
   return keys(boards);
} /* query_boards() */

/**
 * This method tests to see if the board exists.
 * @param board the name of the board to check
 * @return 1 if the board exists, 0 if it does not
 */
int is_board(string board) {
   if (boards[board]) {
      return 1;
   }
   return 0;
} /* is_board() */

private int query_lord(string str) {
   return (int)master()->query_lord(str);
} /* query_lord() */

/**
 * This method checks to see if the specified person is an idiot or
 * not.
 * @param name the name to check
 * @return 1 if they are an idiot
 */
int is_idiot(string name) {
   if (!_idiots) {
      _idiots = ([ ]);
   }
   if (_idiots[name]) {
      if (_idiots[name] > time()) {
         return 1;
      }
      map_delete(_idiots, name);
   }
   return 0;
} /* is_idiot() */

/**
 * This method adds an idiot to the current list of idiots.
 * @param idiot the idiot to add
 * @param length the amount of days to add them for
 */
void add_idiot(string idiot, int length) {
   _idiots[idiot] = time() + (60 * 60 * 24 * length);
   save_me();
} /* add_idiot() */

/**
 * This method returns if they are an idiot and when their period of
 * idiocy runs out.
 * @param idiot the idiot to check
 * @return 0 if not an idiot, the time left otherwise
 */
int query_idiot(string idiot) {
   return _idiots[idiot];
} /* query_idiot() */

/**
 * Check to see if read access is allowed.
 * @param board the board to check
 * @param previous the previous object
 * @param name the name of the person doing stuff
 * @return 1 if it is allowed, 0 if not
 */
int test_can_read(string board, object previous,
                      string name) {
   int bit;
   int ret;

   /* For the moment we will just log all bad access... */
   if (member_array(base_name(previous), _allowed_objects) == -1 &&
       board != "announcements") {
#ifdef DEBUG
      log_file("BAD_BOARD", ctime(time()) + " (read): [" + board + "] " +
               base_name(previous) +
               sprintf(" (%O)\n", this_player()->query_name()));
#endif
      return 0;
   }
   bit = priv[board] & B_PRIV_TYPE_MASK;
   // The default is easy.
   if (bit == 0) {
      return 1;
   }

   // Check the cache.
   if (_read_access_cache[board] &&
       !undefinedp(_read_access_cache[board][name])) {
      return _read_access_cache[board][name];
   }

   // Default, you can read!
   ret = 1;
   if (bit == B_PRIV_ACCESS_RESTRICTED) {
      ret = query_lord(name) ||
         (member_array(name, security[board]) != -1);
   } else if (bit == B_PRIV_ACCESS_RESTRICTED_METHOD) {
      if (query_lord(name)) {
         ret = 1;
      } else {
         // This will now need to check the method and function to call.
         if (sizeof(security[board]) == 2) {
            ret = call_other(security[board][1],
                           security[board][0],
                           B_ACCESS_READ,
                           board, previous, name);
         }
      }
   }

   if (!_read_access_cache[board]) {
      _read_access_cache[board] = ([ ]);
   }
   _read_access_cache[board][name] = ret;
   return ret;
} /* test_can_read() */

/**
 * Check to see if write access is allowed.
 * @param board the board to check
 * @param previous the previous object
 * @param name the name of the person doing stuff
 * @return 1 if it is allowed, 0 if not
 */
int test_can_write(string board, object previous,
                      string name) {
  int bit;
  
  if (is_idiot(name)) {
     return 0;
  }

  if (member_array(base_name(previous), _allowed_objects) == -1) {
#ifdef DEBUG
    log_file("BAD_BOARD", ctime(time()) + " (write): " + base_name(previous) +
                          sprintf(" (%O)\n", this_player()));
#endif
    return 0;
  }
  bit = priv[board] & B_PRIV_TYPE_MASK;
  if (bit == B_PRIV_ACCESS_RESTRICTED_FILE) {
    return member_array(base_name(previous), security[board]) != -1;
  }
  if (bit == B_PRIV_ACCESS_RESTRICTED_METHOD) {
    if (query_lord(name)) {
      return 1;
    }
    // This will now need to check the method and function to call.
    if (sizeof(security[board]) == 2) {
      return call_other(security[board][1],
                        security[board][0],
                        B_ACCESS_WRITE,
                        board, previous, name);
    }
    return 0;
  }
  if (bit == B_PRIV_ACCESS_RESTRICTED ||
      bit == B_PRIV_READ_ONLY) {
    return query_lord(name) ||
           (member_array(name, security[board]) != -1);
  }
  // Default, you can write!
  return 1;
} /* test_can_write() */

/**
 * Check to see if delete is allowed.
 * @param board the board to check
 * @param previous the previous object
 * @param name the name of the person doing stuff
 * @return 1 if it is allowed, 0 if not
 */
int test_can_delete(string board, object previous,
                      string name) {
  int bit;

  if (member_array(base_name(previous), _allowed_objects) == -1) {
#ifdef DEBUG
    log_file("BAD_BOARD", ctime(time()) + " (write): " + base_name(previous) +
                          sprintf(" (%O)\n", this_player()));
#endif
    return 0;
  }
  bit = priv[board] & B_PRIV_TYPE_MASK;
  if (bit == B_PRIV_ACCESS_RESTRICTED_FILE) {
    return query_lord(name) ||
           member_array(base_name(previous), security[board]) != -1;
  }
  if (bit == B_PRIV_ACCESS_RESTRICTED ||
      bit == B_PRIV_READ_ONLY) {
    return query_lord(name) ||
           (member_array(name, security[board]) != -1);
  }
  if (bit == B_PRIV_ACCESS_RESTRICTED_METHOD) {
    if (query_lord(name)) {
      return 1;
    }
    // This will now need to check the method and function to call.
    if (sizeof(security[board]) == 2) {
      return call_other(security[board][1],
                        security[board][0],
                        B_ACCESS_DELETE,
                        board, previous, name);
    }
  }
  // Default, you can delete if your a lord!
  return query_lord(name);
} /* test_can_delete() */

private string query_destination_eaten_note(string  board, object previous,
                                            string name) {
  int bit;

  bit = priv[board] & B_PRIV_TYPE_MASK;
  if (bit == B_PRIV_ACCESS_RESTRICTED_METHOD) {
    // This will now need to check the method and function to call.
    if (sizeof(security[board]) == 2) {
      return call_other(security[board][1],
                        security[board][0],
                        B_ACCESS_DELETE,
                        board, previous, name);
    }
  }
  return 0;
} /* query_destination_eaten_note() */

/**
 * Get the subjects for the specifed board.  The subjects are
 * returns in a special array format.  See the include file for the
 * defines to get at the members of the array.
 * @see /include/board.h
 * @param name the board name to lookup
 * @return the subject array
 */
varargs mixed get_subjects(string name, string person) {
   string pl;
   int bit;

   if (file_name(previous_object()) == BOARD_WEB ||
       file_name(previous_object()) == "/w/ceres/cmds/test") {
      pl = person;
   } else if (this_player()) {
      pl = (string)this_player()->query_name();
   } else {
      pl = "unknown";
   }
   if (!test_can_read(name, previous_object(), pl)) {
      return ({ });
   }
   if (!boards[name] || bit) {
      return ({ });
   }
   return boards[name];
} /* get_subjects() */

/**
 * Get the text of a specific message.  This will look up the
 * text on a board with the given number and return that to the
 * caller.  The number is the offset into the subject array in
 * which to get the message from.
 * @param board the board name to get the message from
 * @param num the message number to use
 * @return the message or 0 if it failed
 */
string get_message(string board, int num, string person) {
  string name, rname;

  if (file_name(previous_object()) == BOARD_WEB) {
     name = person;
  } else  if (file_name(previous_object())[0..10] !=
              "/obj/misc/board"[0..10]) {
    name = "unknown";
  } else {
    name = (string)this_player()->query_name();
  }

  if(board == "lordboard") {
    catch(log_file("/d/admin/log/LORDBOARD", "%s read by prev %s player %s\n",
                   ctime(time()), base_name(previous_object()),
                   this_player()->query_name())); 
  }
  
  if (!test_can_read(board, previous_object(), name)) {
    return 0;
  }

  if (num < 0 || num >= sizeof(boards[board])) {
    return 0;
  }

  if(!message_cache) {
    message_cache = ([ ]);
  }

  total_reads++;

  // if it's not in the cache put it in there.
  if(message_cache[boards[board][num][B_NUM]]) {
    cache_hits++;
    return message_cache[boards[board][num][B_NUM]];
  }

  name = get_filename(board, boards[board][num][B_NUM]);
  if (file_size(name) <= 0) {
    return 0;
  }
  
  if(!clean_out && sizeof(keys(message_cache)) > CACHE_SIZE)
    clean_out = call_out("clean_cache", 10);

#ifdef USE_RAMDISK
  rname = get_ram_filename(board, boards[board][num][B_NUM]);
  if(rname != name) {
    if(file_size(rname) == -1) {
      unguarded((: cp, name, rname :));
    } else
      ram_hits++;
    name = rname;
  }
#endif
  
  if(!_ram_exists || _ram_exists == -1) {
    message_cache[boards[board][num][B_NUM]] = unguarded((: read_file, name :));
    return message_cache[boards[board][num][B_NUM]];
  } else {
    return unguarded((: read_file, name :));
  }
} /* get_message() */

void clean_cache() {
  int i;
  int count;
  int *list;
  string name;

  // prevent the cache getting too big.
  count = sizeof(keys(message_cache));
  list = sort_array(keys(message_cache), 0);
  for(i=0; i < (count - CACHE_SIZE); i++) {
    map_delete(message_cache, list[i]);
  }

  foreach(name in keys(_newsrc_cache)) {
    if(!_newsrc_cache[name]->changed &&
       _newsrc_cache[name]->cached < time() - CACHE_TIMEOUT) {
      map_delete(_newsrc_cache, name);
    }
  }

  clean_out = 0;

   foreach (name in keys(_read_access_cache)) {
      _read_access_cache[name] = ([ ]);
   }
} /* clean_cache() */

/**
 * Saves the state of the object.
 */
void save_me() {
  unguarded((: save_object, BOARD_FILE, 2 :));
} /* save_me() */

/**
 * Adds a new message onto the board.  This call can only be done from
 * verified source, like the bulletin oard objects themselves.  The
 * number used as a reply to should be the message number itself, not
 * the logical index.  If the reply_to is 0 then it is not
 * replying to anything at all.
 * @param board the board to add the message to
 * @param cap_name the name ofthe person posting
 * @param subject the subject of the message
 * @param body the main section of the text
 * @param reply_to the note the message is replying to
 * @return the note number, or 0 on failure
 */
int add_message(string board, string cap_name, string subject, string body,
                int reply_to, class reply_type bing) {
   int test;
   int irp;
   int index;
   string fname;
   string name;
   string from_mess;
   string mail_to;
   class reply_type reply;

   name = lower_case(cap_name);

   /* Check to see what should happen to the reply. */
   if (reply_to) {
      for (index = 0; index < sizeof(boards[board]); index++) {
         if (boards[board][index][B_NUM] == reply_to) {
            if (boards[board][index][B_REPLY_TYPE]) {
               // Do something special.
               reply = (class reply_type)boards[board][index][B_REPLY_TYPE];
               if (reply->type == B_REPLY_POSTER) {
                  mail_to = boards[board][index][B_NAME];
               } else if (reply->type == B_REPLY_NAMED) {
                  mail_to = reply->data;
               }
               if (mail_to) {
                  // Send mail instead.
                  MAILER->do_mail_message(mail_to,
                                          name,
                                          subject,
                                          "",
                                          body);
                  return 1;
               }
            }
         }
      }
   }

   if (!test_can_write(board, previous_object(), name)) {
      return 0;
   }

   if (!boards[board] || test) {
      return 0;
   } else {
      boards[board] += ({ ({ subject,
                                cap_name,
                                num++,
                                time(),
                                bing,
                                reply_to }) });
   }

   if (file_name(previous_object())[0..4] == "/www/") {
      from_mess = " [Web post]";
   } else {
      from_mess = "";
   }

   fname = get_filename(board, num-1);
   unguarded((: rm, fname :));
   fname = get_filename(board, num-1);

   unguarded((: write_file, fname, body :));
   save_me();

   // add this new message to the cache.
   message_cache[num-1] = body;
   // DO not update the message cache here since the num variable does not
   // actually contain the post number any more.
   // Further to this, shouldn't it be num-1 instead of num, that way it should
   // work -- Ceres.

   if (timeouts[board] && timeouts[board][T_MAX] &&
       sizeof(boards[board]) > timeouts[board][T_MAX]) {
      /* should be at most one message we eat...  */
      while (sizeof(boards[board]) > timeouts[board][T_MAX]) {
         zap_message(board, 0, 0);
         irp++;
      }
      if ((priv[board] & B_PRIV_TYPE_MASK) ==
          B_PRIV_ACCESS_RESTRICTED_METHOD) {
         // This will now need to check the method and function to call.
         if (sizeof(security[board]) == 2) {
            call_other(security[board][1],
                       security[board][0],
                       B_ACCESS_INFORM,
                       board, 0, cap_name, irp);
         }
      }
      if ( !(priv[board] & B_PRIV_NO_INFORM) ) {
         user_event( "inform",
                     sprintf( "%s posts a message to %s and %d message%s in sympathy%s",
                              cap_name, board, irp,
                              ( irp > 1 ? "s explode" : " explodes" ),
                              from_mess ),
                     "message",
                     this_player() );
      }
   } else {
      if ((priv[board] & B_PRIV_TYPE_MASK) ==
          B_PRIV_ACCESS_RESTRICTED_METHOD) {
         // This will now need to check the method and function to call.
         if (sizeof(security[board]) == 2) {
            call_other(security[board][1],
                       security[board][0],
                       B_ACCESS_INFORM,
                       board, 0, cap_name, 0);
         }
      }
      if ( !(priv[board] & B_PRIV_NO_INFORM) ) {
         user_event( "inform",
                     sprintf( "%s posts a message to %s%s",
                              cap_name, board, from_mess),
                     "message",
                     this_player() );
      }
   }
   return num-1;
} /* add_message() */

/**
 * Create a new board.
 * @param board the name of the board to create
 * @param priva is this board only allowed prviliged access?
 * @param person the person to add into the security array initialy
 * @return 0 on a failure, 1 on success
 */
int create_board(string board,
                 int priviliges,
                 string person) {
   if (boards[board]) {
      return 0;
   }
   if (!person) {
      person = this_player()->query_name();
   }
   boards[board] = ({ });
   security[board] = ({ person });
   if (priviliges) {
      priv[board] = priviliges;
   }
   save_me();
   //printf("Created board %s.\n", board);
   return 1;
} /* create_board() */

/**
 * Adds a member into the security array for a board.  This allows
 * certain people to read boards they may not normaly have
 * access to.
 * @param board the board to change the access on
 * @param name the name of the person to add to the array
 * @return 0 on failure, 1 on success
 */
int add_allowed(string board, string name) {
  string nam;
  int board_type;

  board_type = priv[board] & B_PRIV_TYPE_MASK;
  if (sscanf(file_name(previous_object()), "/obj/misc/board%s", nam) != 1) {
    return 0;
  }
  nam = (string)this_player()->query_name();
  if (!test_can_write(board, previous_object(), nam) ||
      (board_type != B_PRIV_ACCESS_RESTRICTED &&
       board_type != B_PRIV_READ_ONLY &&
       board_type != B_PRIV_ACCESS_RESTRICTED_FILE)) {
    return 0;
  }
  if (!PLAYER_HANDLER->test_user(name)) {
    return 0;
  }
  security[board] += ({ name });
  save_me();
  printf("Added %s to the security array for %s.\n",name, board);
  return 1;
} /* add_allowed() */

/**
 * Remove someone from the allowed array of the board.
 * @param board the board to remove the person from
 * @param name the name of the person to remove
 * @return 0 on nfailure and 1 on success
 */
int remove_allowed(string board, string name) {
  string nam;
  int i;
  int board_type;

  if (sscanf(file_name(previous_object()), "/obj/misc/board%s", nam) != 1) {
    return 0;
  }
  nam = geteuid(previous_object());
  board_type = priv[board] & B_PRIV_TYPE_MASK;
  if (!test_can_write(board, previous_object(), nam) ||
      (board_type != B_PRIV_ACCESS_RESTRICTED &&
       board_type != B_PRIV_READ_ONLY &&
       board_type != B_PRIV_ACCESS_RESTRICTED_FILE)) {
    return 0;
  }
  security[board] = delete(security[board], i, 1);
  save_me();
  printf("Removed %s from the security array for %s.\n", name, board);
  return 1;
} /* add_allowed() */

/**
 * This method sets the method to call to check for allowed postings
 * to a board setup as an method controlled post board.
 * @param board the name of the board to setup the method for
 * @param method the method to call on the object
 * @param name the object to call the method on
 * @return 0 if the method failed, 1 if it was successful
 */
int set_method_access_call(string board, string method, string name) {
   string pl;

   if (!boards[board] ||
       (priv[board] & B_PRIV_TYPE_MASK) != B_PRIV_ACCESS_RESTRICTED_METHOD) {
      return 0;
   }
   if (this_player()) {
      pl = (string)this_player()->query_name();
   } else {
      pl = "unknown";
   }
   if (query_lord(pl) ||
       file_name(previous_object()) == CLUB_HANDLER) {
      security[board] = ({ method, name });
      save_me();
      return 1;
   }
   return 0;
} /* set_method_access_call() */

/**
 * This method changes the type of the board to be a method access call
 * access restriction, instead of whatever it had before.
 * @param board the name of the board to control the access for
 */
int force_board_method_access_restricted(string board) {
   string pl;

   if (!boards[board]) {
      return 0;
   }
   if (this_player()) {
      pl = (string)this_player()->query_name();
   } else {
      pl = "unknown";
   }
   if (query_lord(pl) ||
       file_name(previous_object()) == CLUB_HANDLER) {
      priv[board] = (priv[board] & ~B_PRIV_TYPE_MASK) |
                    B_PRIV_ACCESS_RESTRICTED_METHOD;
      save_me();
      return 1;
   }
   return 0;
} /* force_board_method_access_restricted() */

/**
 * @ignore yes
 */
protected int zap_message(string board, int off, string name) {
  int num;
  string nam, archive;

  if (off < 0 || off >= sizeof(boards[board])) {
    return 0; /* out of range error */
  }
  num = boards[board][off][B_NUM];
  nam = get_filename(board, num);
  archive = query_archive(board);
  if (archive) {
    mixed *stuff;

    stuff = boards[board][off];
    // When we archive it, strip the colours out.
    unguarded((: write_file, archive,
               sprintf("\n----\nNote #%d by %s posted at %s\nTitle: '%s'\n\n",
                       off, stuff[B_NAME], ctime(stuff[B_TIME]),
                       stuff[B_SUBJECT])+unguarded((: read_file, nam :)) :));

    if(unguarded( (: file_size, archive :) ) > MAX_ARCHIVE_SIZE)
      unguarded((: rename, archive, archive+"."+time() :));
  }
  boards[board] = delete(boards[board],off,1);
  unguarded((: rm, nam :));
  save_me();
  return 1;
} /* zap_message() */

/**
 * Remove a message from a board.  The offset is the offset into the
 * subjects array.
 * @param board the board to remove the message from
 * @param off the offset to delete
 * @param override_name used by the web boards
 * @return 0 on failure and 1 on success
 */
int delete_message(string board, int off, string override_name) {
  string nam;

  if (file_name(previous_object()) == "/www/boards") {
    nam = override_name;
  } else {
    nam = this_player()->query_name();
  }

  if (!can_delete_message(board, off, nam)) {
    return 0;
  }

  return zap_message(board, off, nam);
} /* delete_message() */

/**
 * Check to see if the named person can delete the message.
 * @param pname the player name
 * @param board the board name
 * @param off the offset to delete
 * @see delete_message()
 */
int can_delete_message(string board, int off, string pname) {
  if (!boards[board]) {
    return 0;
  }

  if (off >= sizeof(boards[board])) {
    return 0;
  }

  if (!test_can_delete(board, previous_object(), pname) &&
      (lower_case(boards[board][off][B_NAME]) != lower_case(pname))) {
    return 0; /* not allowed to delete the notes */
  }

  return 1;
} /* can_delete_message() */

/**
 * Returns the security array for the given board.
 * @param board the board to get the security array for
 * @return the security array
 */
string *query_security(string board) {
  string *str;

  str = security[board];
  if (!str) {
    return str;
  }
  return copy(str);
} /* query_security() */

/**
 * Complete erase a board.
 * @param board the board to delete
 * @return 0 on failure and 1 on success
 */
int delete_board(string board) {
  string nam;

  if (!boards[board]) {
    return 0;
  }
  nam = geteuid(previous_object());
  if (!query_lord(nam) ||
      file_name(previous_object()) == CLUB_HANDLER) {
    return 0; /* not allowed to delete the notes */
  }
  while (sizeof(boards[board])) {
    if (!zap_message(board, 0, 0)) {
        return 0;
    }
  }
  map_delete(boards, board);
  map_delete(security, board);
  map_delete(archives, board);
  map_delete(timeouts, board);
  save_me();
  return 1;
} /* delete_board() */

/**
 * The names of all the boards.
 * @see query_boards()
 * @return the list of all the boards
 */
string *list_of_boards() {
  return keys(boards);
} /* list_of_boards() */

/**
 * Change the time before a message automatic gets deleted off a
 * board.
 * @param board the name of the board to set the timeout for
 * @param timeout the timeout (in seconds)
 * @return 0 on failure and 1 on success
 */
int set_timeout(string board, int timeout) {
  string nam;

  if (!boards[board]) {
    return 0;
  }
  nam = geteuid(previous_object());
  if (!test_can_write(board, previous_object(), nam)) {
    return 0; /* not allowed to delete the notes */
  }
  if (!timeouts[board]) {
    timeouts[board] = ({ DEFAULT_MIN, DEFAULT_MAX, timeout });
    return 1;
  }
  timeouts[board][T_TIMEOUT] = timeout;
  save_me();
  printf("Set the automagic timeout to %d days for %s.\n", timeout, board);
  return 1;
} /* set_timeout() */

/**
 * Sets the minimum number of message to keep on a board.  If there is less
 * than this
 * number then they will not be auto deleted.
 * @param board the board to set the minimum for
 * @param min the number of message to keep
 * @return 0 on failure and 1 on success
 */
int set_minimum(string board, int min) {
  string nam;

  if (!boards[board]) {
    return 0;
  }
  nam = geteuid(previous_object());
  if (!test_can_write(board, previous_object(), nam)) {
    return 0; /* not allowed to delete the notes */
  }
  if (!timeouts[board]) {
    timeouts[board] = ({ min, DEFAULT_MAX, DEFAULT_TIMEOUT });
    return 1;
  }
  timeouts[board][T_MIN] = min;
  save_me();
  printf("Set the minimum number of messages to %d for %s.\n", min, board);
  return 1;
} /* set_minimum() */

/**
 * Set the maximum number of message before they start being auto deleted
 * when someone posts to the board.
 * @param board the board to set the maximum for
 * @param max the maximum number of messages
 * @return 0 if it failed and 1 on success
 */
int set_maximum(string board, int max) {
  string nam;

  if (!boards[board]) {
    return 0;
  }
  nam = geteuid(previous_object());
  if (!test_can_write(board, previous_object(), nam)) {
    return 0; /* not allowed to delete the notes */
  }
  if (!timeouts[board]) {
    timeouts[board] = ({ DEFAULT_MIN, max, DEFAULT_TIMEOUT });
    return 1;
  }
  timeouts[board][T_MAX] = max;
  save_me();
  printf("Set the maximum number of messages to %d for %s.\n", max, board);
  return 1;
} /* set_maximum() */

/**
 * Set the archive file location.  This is where all deleted messages
 * wil be stored.
 * @param board the board to set the archive for
 * @param file the file name to set it to
 * @return 0 on failure and 1 on success
 */
int set_archive(string board, string file) {
  string nam;

  if (!boards[board]) {
    return 0;
  }
  nam = geteuid(previous_object());
  if (!test_can_write(board, previous_object(), nam)) {
    return 0; /* not allowed to delete the notes */
  }
  archives[board] = file;
  save_me();
  printf("Set the archive file to %s for %s.\n", file, board);
  return 1;
} /* set_archive() */

/**
 * Return the timeout time of the board.
 * @param board the board to get the timeout for
 * @return the timeout in seconds
 * @see set_timeout()
 */
int query_timeout(string board) {
  if (!timeouts[board]) {
    return 0;
  }
  return timeouts[board][T_TIMEOUT];
} /* query_timeout() */

/**
 * Return the minimum number of message allowed on the board.
 * @param board the board to get the minimum numbr of message for
 * @return 0 on failure, the minimum number of messages on success
 */
int query_minimum(string board) {
  if (!timeouts[board]) {
    return 0;
  }
  return timeouts[board][T_MIN];
} /* query_minimum() */

/**
 * Return the maximum number of message allowed on the board.
 * @param board the board to get the maximum numbr of message for
 * @return 0 on failure, the maximum number of messages on success
 */
int query_maximum(string board) {
  if (!timeouts[board]) {
    return 0;
  }
  return timeouts[board][T_MAX];
} /* query_maximum() */

/**
 * Return the archive file location for the board.
 * @param board the board to get the archive location for
 * @return the archive file location, 0 on failure
 */
string query_archive(string board) {
  if (!boards[board]) {
    return 0;
  }
  if (undefinedp(archives[board])) {
    return ARCHIVE_DIR+board;
  }
  return archives[board];
} /* query_archive() */

/**
 * This method checks to see if the board is in restricted access mode.
 * @param board the name of the board to check
 * @return 1 if it is, 0 if it is not
 */
int query_restricted_access(string board) {
   return (priv[board] & B_PRIV_TYPE_MASK) == B_PRIV_ACCESS_RESTRICTED;
} /* query_restricted_access() */

/**
 * This method checkes to see if the board is in restricted access file
 * mode.
 * @param board the name of the board to check
 * @return 1 if it is, 0 if it is not
 */
int query_restricted_access_file(string board) {
   return (priv[board] & B_PRIV_TYPE_MASK) == B_PRIV_ACCESS_RESTRICTED_FILE;
} /* query_restricted_access_file() */

/**
 * This method checks to see if the board is in a read only mode.
 * @param board the name of the board to check
 * @return 1 if it is read only, 0 if not
 */
int query_read_only(string board) {
  return (priv[board] & B_PRIV_TYPE_MASK) == B_PRIV_READ_ONLY;
} /* query_read_only() */

/**
 * This method checks to see if the board is in a no inform mode.
 * @param board the name of the board to check
 * @return 1 if it is no inform, 0 if not
 */
int query_no_inform(string board) {
  return priv[board] & B_PRIV_NO_INFORM;
} /* query_no_inform() */

/**
 * This method returns the current privilage level of the board in
 * question.  This should be used for testing only.
 * @return the current privilage level
 */
int query_privilage(string board) {
   return priv[board];
} /* query_privilage() */

/**
 * This method runs through all the messages and expires any which are
 * too old.
 */
void expire_boards() {
  string nam;
  int tim, num, *val;

  foreach (nam, val in timeouts) {
    num = 0;
    if ((tim = val[T_TIMEOUT]) &&
        (sizeof(boards[nam]) > val[T_MIN]) &&
        ((boards[nam][0][B_TIME]+(tim*(24*60*60))) < time())) {
          /* Got to delete some...  */
      while (sizeof(boards[nam]) > val[T_MIN] &&
             boards[nam][0][B_TIME]+(tim*24*60*60) < time()) {
        zap_message(nam, 0, 0);
        num++;
      }
      user_event( "inform", sprintf("Board handler removes %d messages "
                                       "from %s", num, nam), "message");
    }
  }
  if (find_call_out("expire_boards") == -1) {
    call_out("expire_boards", 60*60);
  }
} /* expire_boards() */

/** Prevents the object from being shadowed. */
int query_prevent_shadow() {
  return 1;
} /* query_prevent_shadow() */

/** The current max board number. */
int query_num() {
  return num;
} /* query_num() */

mixed *stats() {
  if(!total_reads)
    total_reads = 1;
  if(!cache_hits)
    cache_hits = 1;
  if(!ram_hits)
    ram_hits = 1;
  return  ({
    ({ "messages read", total_reads, }),
#ifdef USE_CACHE      
      ({ "cache hit percent", (cache_hits * 100) / total_reads, }),
#endif      
      ({ "ram hit percent", (ram_hits * 100) / total_reads, }),
      ({ "messages in cache", sizeof(keys(message_cache)), }),
      ({ "newsrc reads", _newsrc_reads, }),
      ({ "newsrc hit percent", (_newsrc_cache_hits * 100) / _newsrc_reads, }),
        ({ "newsrcs in cache", sizeof(keys(_newsrc_cache)), }),
          });
}

void query_cache() {
    printf("%O\n", sort_array(keys(message_cache), 0));
}

/** @ignore yes */
int load_newsrc(string player) {
  string fname, board;

  _newsrc_reads++;

  if(_newsrc_cache[player]) {
    _newsrc_cache_hits++;
    return 1;
  }

  fname = NEWSRC_SAVE_DIR+player[0..0]+"/"+player+ ".o";

  if(unguarded( (: file_size($(fname)) :)) > 0) {
    _newsrc_cache[player] =
      unguarded((: restore_variable(read_file($(fname))) :));
    if (arrayp(_newsrc_cache[player]->newsrc)) {
      _newsrc_cache[player]->newsrc = ([ ]);
      if (find_call_out("flush_newsrc") == -1) {
        call_out("flush_newsrc", NEWSRC_SAVE_DELAY);
      }
    }
    return 1;
  } else {
    _newsrc_cache[player] = new(class newsrc,
                                cached : time(),
                                kill : ({ }),
                                newsrc : ([ ]),
                                board_order : ({ }));
    if(PLAYER_HANDLER->test_property(player, NEWS_RC))
      _newsrc_cache[player]->newsrc =
        PLAYER_HANDLER->test_property(player, NEWS_RC);
    if(PLAYER_HANDLER->test_property(player, BOARD_ORDER))
      _newsrc_cache[player]->board_order =
        PLAYER_HANDLER->test_property(player, BOARD_ORDER);

    foreach(board in keys(boards)) {
      if(PLAYER_HANDLER->test_property(player, "news_kill_"+board)) {
        _newsrc_cache[player]->kill += ({ board });
      }
    }
    _newsrc_cache[player]->changed = time();
    if(find_call_out("flush_newsrc") == -1)
      call_out("flush_newsrc", NEWSRC_SAVE_DELAY);

    return 1;
  }

  return 0;
}

/** @ignore yes */
void flush_newsrc(int force) {
  string fname, player, board, *board_list;
  object ob;

  board_list = map(keys(boards), (: lower_case($1) :));

  foreach(player in keys(_newsrc_cache)) {
    if(!force && (!_newsrc_cache[player]->changed ||
       (_newsrc_cache[player]->changed > time() - NEWSRC_SAVE_DELAY &&
        !find_object("/obj/shut"))))
      continue;

    fname = NEWSRC_SAVE_DIR + player[0..0] + "/" + player + ".o";
    // save their file.
    if(unguarded( (: file_size($(fname)) :)) > 0) {
      _newsrc_cache[player]->newsrc =  
        filter(_newsrc_cache[player]->newsrc, 
               (: member_array(lower_case($1), $(board_list)) != -1 :));
      if(_newsrc_cache[player]->kill)
        _newsrc_cache[player]->kill =  
          filter(_newsrc_cache[player]->kill, 
                 (: member_array(lower_case($1), $(board_list)) != -1 :));
      if(_newsrc_cache[player]->board_order)
        _newsrc_cache[player]->board_order =  
          filter(_newsrc_cache[player]->board_order, 
                 (: member_array(lower_case($1), $(board_list)) != -1 :));
      
      unguarded( (: write_file($(fname), save_variable(_newsrc_cache[$(player)]),
                               1) :));
    } else if(ob = find_player(player)) {
      // if they're around and don't have a file then write a new one
      // and clear their properties.
      unguarded( (: write_file($(fname),
                               save_variable(_newsrc_cache[$(player)]), 1) :));
      ob->remove_property(NEWS_RC);
      ob->remove_property(BOARD_ORDER);
      foreach(board in keys(boards)) {
        ob->remove_property("news_kill_" + lower_case(board));
      }
    } else {
      // they're not around and they don't have a file so just update their
      // properties.
      PLAYER_HANDLER->special_add_property(player, NEWS_RC, _newsrc_cache[player]->newsrc);
    }
    _newsrc_cache[player]->changed = 0;
  }
}

/**
 * Get someones newsrc
 *
 * @param string the name of the player
 * @return mapping their newsrc
 */
mapping query_newsrc(string player) {
  if(!load_newsrc(player))
    return ([ ]);

  _newsrc_cache[player]->cached = time();

  return _newsrc_cache[player]->newsrc;
}

/**
 * Set someones newsrc
 *
 * @param string the name of the player
 * @param mapping their new newsrc
 * @return 1 for success, 0 for failure
 */
int set_newsrc(string player, mapping newsrc) {
  if(!load_newsrc(player))
    return 0;

  _newsrc_cache[player]->newsrc = (mapping)newsrc;
  _newsrc_cache[player]->cached = time();
  _newsrc_cache[player]->changed = time();

  if(find_call_out("flush_newsrc") == -1)
    call_out("flush_newsrc", NEWSRC_SAVE_DELAY);

  return 1;
}

/**
 * Find out if a given board is in a player killfile
 *
 * @param string the name of the player
 * @param string the name of the board (in lowercase)
 * @return 1 if it is, 0 if it isn't
 */
int query_killfile(string player, string board) {
  if(!load_newsrc(player))
    return 0;

  _newsrc_cache[player]->cached = time();
  return member_array(board, _newsrc_cache[player]->kill) != -1;
}

/**
 * Add a board to someones killfile
 *
 * @param string the name of the player
 * @param string the name of the board (in lowercase)
 * @return 1 for success, 0 for failure
 */
int set_killfile(string player, string board) {

  if(!load_newsrc(player))
    return 0;

  if(member_array(board, _newsrc_cache[player]->kill) != -1)
    return 1;

  _newsrc_cache[player]->kill += ({ board });
  _newsrc_cache[player]->changed = time();
  _newsrc_cache[player]->cached = time();

  if(find_call_out("flush_newsrc") == -1)
    call_out("flush_newsrc", NEWSRC_SAVE_DELAY);

  return 1;
}

int remove_killfile(string player, string board) {

  if(!load_newsrc(player))
    return 0;

  if(member_array(board, _newsrc_cache[player]->kill) == -1)
    return 1;

  _newsrc_cache[player]->kill -= ({ board });
  _newsrc_cache[player]->changed = time();
  _newsrc_cache[player]->cached = time();

  if(find_call_out("flush_newsrc") == -1)
    call_out("flush_newsrc", NEWSRC_SAVE_DELAY);

  return 1;
}

/**
 * Retrieve a players killfile list
 *
 * @param string the players name
 * @return string * the list of boards in their killfile
 */
string *list_killfile(string player) {

  if(!load_newsrc(player))
    return ({ });

  return _newsrc_cache[player]->kill;
}

/**
 * Retrieve someones chosen board order.
 *
 * @param string the players name
 * @return the list of boards, in order
 */
string *query_board_order(string player) {
  if(!load_newsrc(player))
    return ({ });

  _newsrc_cache[player]->cached = time();
  if(!_newsrc_cache[player]->board_order)
    return ({ });

  return _newsrc_cache[player]->board_order;
}

/**
 * Set the order boards should be shown in for a given player.
 *
 * @param string a players name
 * @param string * the list of boards
 * @return 1 for success, 0 for failure
 */
int set_board_order(string player, string *new_order) {

  if(!load_newsrc(player))
    return 0;

  _newsrc_cache[player]->board_order = new_order;
  _newsrc_cache[player]->changed = time();
  _newsrc_cache[player]->cached = time();

  if(find_call_out("flush_newsrc") == -1)
    call_out("flush_newsrc", NEWSRC_SAVE_DELAY);

  return 1;
}

/** @ignore yes */
void dest_me() {
  flush_newsrc(1);
  destruct(this_object());

}