/**
* 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); }