psyced/world/net/text.c

497 lines
13 KiB
C

// $Id: text.c,v 1.92 2008/12/01 11:31:32 lynx Exp $ // vim:syntax=lpc
//
// text database handler
//
// cloned on demand by compile_object in master.c
// accessed by all sorts of items using the textc.c
#ifdef Dtext
# undef DEBUG
# define DEBUG Dtext
#endif
#include <net.h>
#include <lang.h>
//#include <files.h>
// textcache currently causes some minor bugs
// so we disable it until one day someone cares to fix it
#ifndef TEXTCACHE
# define NOTEXTCACHE
#endif
#ifndef FILESYSTEM_CHARSET
# define FILESYSTEM_CHARSET SYSTEM_CHARSET
//# define FILESYSTEM_CHARSET "ISO-8859-15"
#endif
inherit NET_PATH "storage";
volatile string path;
volatile object* tob;
#ifdef NOTEXTCACHE
volatile mapping fs;
#endif
link() { return _v; }
#ifdef EXPERIMENTAL // not being used anywhere currently
renderDB() {
string k, t;
string out = "<PSYC:TEXTDB> ## vim:syntax=mail\n";
foreach(k : sort_array(m_indices(fs), #'>)) {
t = fs[k];
if (t) t = chop(t);
else {
P1(("renderDB: skipping %O\n", k))
continue; // entry marked non-existing
}
out += "\n"+ k +"\n|"+
implode(explode(t, "\n"), "\n|")
+"\n";
}
return out;
}
#endif
#ifndef NO_TEXTDB_FILES
parseDB(string in, string filename) {
string e, mc;
P2(("%O parseDB %s\n", ME, filename))
unless (sscanf(in, "<PSYC:TEXTDB>%s\n\n%s", e, in))
raise_error("garbage in "+ filename +" header\n");
// comments are permitted after the file tag until the
// empty line. please prefix them with ## even if there
// is no check for that here. there are also per-entry
// comments, but they have to be indented like the rest.
D3( if (strlen(e)) PP(("%O: %s comment:%s\n", ME, filename, e)); )
# ifdef NOTEXTCACHE
in = explode(in, "\n\n");
foreach(e : in) {
if (sscanf(e, "%s\n|%s", mc, e)) {
# if DEBUG > 0
string t;
array(string) a = explode(e, "|\n");
if (sizeof(a) > 1) foreach(t : a[1..])
if (strlen(t) && t[0] != '|') {
P0(("ERROR in %s: | before mc after "
"%s\n", filename, mc))
}
# endif
# ifdef NEW_LINE
# echo NEW_LINE currently doesn't work as it breaks the ## comment syntax
// Odd server stuff: "# shut up"
// should i pursue fixing NEW_LINE or keep the trailcutting
// business..? since only net/jabber does the chomp() it's not
// a big deal.. however NEW_LINE is the way to ensure that
// psyced does not modify outgoing data that *intentionally*
// had an extra trailing newline! so at some point we should
// probably get this stuff working. TODO
fs[mc] = replace(e, "\n|", "\n");
# else
fs[mc] = replace(e, "\n|", "\n") + "\n";
#endif
} else unless(e == "") {
// allow one trailing newline in database format
P0(("ERROR in %s after %O: %O...\n", filename, mc,
strlen(e) > 23? e[.. 23] : e))
}
P4(("parseDB %O into %O for %O\n", e, fs[mc], mc))
}
return ME;
# else
PT(("Thanx for the "+ filename +" but I can't use it.\n"))
return 0;
# endif
}
#endif
sPath(pa) {
array(string) p;
#ifdef NOTEXTCACHE
fs = ([]);
#endif
if (pa) {
#ifndef NO_TEXTDB_FILES
string in, filename;
filename = pa +".textdb";
if (in = read_file(filename)) {
# ifndef NO_SMART_MSA
// ok, it was a stupid idea to think the textdb will
// never need to look at parts of the path.. the alternative
// would be to find an ascii character that is never needed
// in any textdb format. haha. impossible.
if (in && strstr(pa, "/irc") > 1) for (int i = strstr(in, "%");
i >= 0; i = strstr(in, "%")) {
in[i] = 0x01; // this used to be in net/irc/user, but
// it didn't destinguish between data
// and template. and replacing the %s
// once for the whole textdb is better
// than both solutions before
}
# endif
# if SYSTEM_CHARSET != FILESYSTEM_CHARSET
P2(("SYSTEM_CHARSET %O, FILESYSTEM_CHARSET %O\n",
SYSTEM_CHARSET, FILESYSTEM_CHARSET))
iconv(in, FILESYSTEM_CHARSET, SYSTEM_CHARSET);
# endif
parseDB(in, filename);
}
D2( else P2(("Why don't you make me a neat "+ filename +" ?\n")) )
#endif
path = pa + "/";
}
p = explode(path, "/");
if (sizeof(p) != 4 || strlen(p[1]) < 2) {
tob = 0;
} else {
string la1, la2, la3, heritage;
if (strlen(p[1]) > 2) {
// support for sprachvariationen
la2 = p[1][0..1];
if (la2 != "en") la3 = "en";
}
else {
if (p[1] != "en") la3 = "en";
}
tob = ({});
#define SUMMON_LANGUAGES(LAY, SCM) \
if (la1) tob += ({ summon(LAY, la1, SCM) }); \
if (la2) tob += ({ summon(LAY, la2, SCM) }); \
if (la3) tob += ({ summon(LAY, la3, SCM) }); \
SUMMON_LANGUAGES(p[0], p[2])
la1 = p[1];
if (p[0] != "default") {
// new database inheritance feature, no nesting allowed
heritage = lookup("_INHERIT", "");
if (strlen(heritage) > 1) {
D1(D("DB INHERIT: "+heritage+" for "+pa+".\n");)
SUMMON_LANGUAGES(heritage, p[2])
}
else heritage = 0;
SUMMON_LANGUAGES("default", p[2])
}
if (p[2] != "plain") {
SUMMON_LANGUAGES(p[0], "plain")
if (heritage) {
D1(D("DB INHERIT: "+heritage+"/plain for "+pa+".\n");)
SUMMON_LANGUAGES(heritage, "plain")
}
if (p[0] != "default") {
// la3 = 0; // default/en/plain is the default anyway
SUMMON_LANGUAGES("default", "plain")
}
}
}
P4(("sPath: %O - tob %O\n", path, tob))
}
summon(layout, lang, scheme) {
string t;
t = layout+"/"+lang+"/"+scheme+"/text";
D3(D("summon "+t+"\n");)
t -> load();
return find_object(t);
}
lookup(string mc, mixed fmt, object ghost, object curse) {
string file, in;
mixed ofmt = fmt;
#ifdef NOTEXTCACHE
int dont_cache = 0;
#endif /* NOTEXTCACHE */
// this would return some other database's data, too
//
//if (ghost && _v[mc]) return _v[mc];
//
// we *must* load the file anew, also to make sure any
// recursions get handled correctly!
P4(("DB %O = %O lookup(%O, %O, %O)\n", ME, path, mc, fmt, ghost))
// this happens at init time, especially for _INHERIT .. problem?
// DB net/text#146 != "LynX/en/plain/" lookup("_INHERIT", "", 0)
D2( if (path && object_name(ME) != path+"text")
PP(("DB %O != %O lookup(%O, %O, %O)\n", ME, path, mc,fmt,ghost)); )
#ifdef NOTEXTCACHE //rudimentary fs cache here. just prevents useless
// disk reads.
if (member(fs, mc)) {
in = fs[mc];
} else {
#ifndef NO_TEXTDB_FMT_FILES
file = path + replace(mc[1..], "_", "/") + ".fmt";
in = read_file(file);
# if SYSTEM_CHARSET != FILESYSTEM_CHARSET
if (in && strlen(in)) {
P0(("Warning: Expensive convert_charset for %s\n", file))
iconv(in, FILESYSTEM_CHARSET, SYSTEM_CHARSET);
}
# endif
fs[mc] = in;
#else
P4(("DB: No template for %s: %s\n", path, mc));
# if DEBUG > 0
// this warns us, when we forgot to integrate a .fmt file
file = path + replace(mc[1..], "_", "/") + ".fmt";
if (in = read_file(file))
PP(("Warning: Unused fmt in %s: %O\n", file, in));
# endif
in = 0;
#endif
}
#else
// psyc = mc[0] == 'x';
// file = path + replace(mc[1+psyc..], "_", "/"); // + ".fmt";
// file += psyc ? ".psyc" : ".fmt";
file = path + replace(mc[1..], "_", "/") + ".fmt";
in = read_file(file);
#endif
if (in) {
if (in == "\n" || in == "") {
// request for "no output"
P2(("DB %O: no output for %O\n", ME, mc || file))
in = 0;
} else {
string before, code, after;
// new: data base comment syntax, '##' at start of line
while(sscanf(in, "##%s\n%s", code,in)) { };
while(sscanf(in, "%s\n##%s\n%s", before,code,after)) {
in = before +"\n"+ after;
}
while(sscanf(in, "%s{_%s}%s", before, code, after)
&& code) switch(code) {
#ifdef IMG_URL
case "URL_img":
in = before + IMG_URL + after;
break;
#endif
#ifdef APP_URL
case "URL_applet":
in = before + APP_URL + after;
break;
#endif
#ifdef SYSTEM_CHARSET
case "VAR_charset":
in = before + SYSTEM_CHARSET + after;
break;
#endif
#ifdef PRO_PATH
case "VAR_layout":
in = before + get_layout(ghost||path) + after;
break;
#endif
#ifdef CHATNAME
case "VAR_host":
in = before + CHATNAME + after;
break;
#endif
#ifdef SERVER_VERSION
// not yet in use anywhere
case "VAR_server_version":
in = before + SERVER_VERSION + after;
break;
#endif
#ifdef SERVER_VERSION_MINOR
// even less in use anywhere
case "VAR_server_version_minor":
in = before + SERVER_VERSION_MINOR + after;
break;
#endif
case "VAR_provider":
#ifndef PROVIDER_NAME
#define PROVIDER_NAME "symlynX communications"
#endif
in = before + PROVIDER_NAME + after;
break;
case "VAR_server":
#ifndef SERVER_HOST
#define SERVER_HOST __HOST_IP_NUMBER__ // never happens
#endif
#ifdef _host_XMPP
in = before + SERVER_HOST + after;
break;
#else
#define _host_XMPP SERVER_HOST
#endif
case "VAR_server_XMPP":
in = before + _host_XMPP + after;
break;
case "VAR_server_uniform":
in = before + SERVER_UNIFORM + after;
break;
case "VAR_data": // not sure if this makes sense, --lynX 2016
in = before + ofmt + after;
break;
case "VAR_method":
PT(("using %O for %O\n", mc, code))
code = mc[1..];
// fall thru
default:
#ifdef NOTEXTCACHE
dont_cache = 1;
#endif /* NOTEXTCACHE */
// when recursive use default text
if (mc[1..] == code) {
if (curse == ME) {
P2(("%O ran into own curse.\n",
ME))
return;
}
// let's curse the current object.
// this ensures that the correct order
// of inheritance is maintained.
if (ghost) in = ghost-> lookup(mc,
fmt, 0, ME);
else in = lookup(mc, fmt, 0, ME);
}
//
// kinda server-side includes here.. ;)
//
// they need to start at the top of
// the database hierarchy again
else
if (ghost) in= ghost-> lookup("_"+code);
else in= lookup("_"+code);
if (!in || !strlen(in)) in = before + after;
else in = before + chomp(in) + after;
}
if (in == "" || in == "\n") {
// request for "no output" II
P2(("DB %O: late no output for %O\n",
ME, mc || file))
in = 0;
}
}
#ifdef NO_TEXTDB_FILES
# ifndef NO_SMART_MSA
# echo Warning: % characters will be converted to msa
if (in) for (int i = strstr(in, "%");
i >= 0; i = strstr(in, "%")) {
in[i] = 0x01; // this used to be in net/irc/user, but
// it didn't destinguish between data
// and template. and having the msa char
// in the template is nicer than replacing
// it every time (... we should fix
// the textcache ...).
}
# endif
#endif
#ifndef NOTEXTCACHE
fmt = _v[mc] = in;
#else /* NOTEXTCACHE */
fmt = in;
#endif /* NOTEXTCACHE */
P2(("DB %O: got %O\n", ME, mc || file))
} else {
int i;
if (curse) {
P2(("recursive: %s in %O for %O\n",
mc, ME, curse))
}
// held re-enabled this line.
if (ghost) return -1;
in = -1;
for (i=0; i < sizeof(tob); i++) {
unless (tob[i]) {
sPath();
unless (tob[i])
raise_error("Unable to rehash my databases\n");
PT(("Textdb rehashed for %O\n", ME))
}
// the missing 'curse' treatment may also be the reason
// why the textcache was no longer working
if (curse != tob[i])
in = tob[i] -> lookup(mc, fmt, ME, curse);
if (in != -1) {
#ifndef NOTEXTCACHE
fmt = _v[mc] = in;
#else /* NOTEXTCACHE */
dont_cache = 1;
fmt = in;
#endif /* NOTEXTCACHE */
break;
}
}
if (in == -1) {
if (fmt) {
P2(("DB %O: no %O » %O\n", ME, mc || file, fmt))
#ifndef NOTEXTCACHE
_v[mc] = fmt;
#else /* NOTEXTCACHE */
// _v[mc] = fmt;
#endif /* NOTEXTCACHE */
} else {
string crazy;
// attention! here comes real psyc inheritance!
crazy=mc;
for (i = strlen(crazy)-1; i>1; i--) {
if (crazy[i] == '_') {
--i;
crazy = crazy[..i];
P2(("DB %O: no %s, trying %s\n",
ME, mc, crazy))
if (ghost) fmt = ghost -> lookup(crazy, fmt);
else fmt = lookup(crazy, fmt);
#ifndef NOTEXTCACHE
if (_v[mc] = fmt) return fmt;
#else /* NOTEXTCACHE */
// is this cache legal?
//if (_v[mc] = fmt) return fmt;
if (fmt) return fmt;
#endif /* NOTEXTCACHE */
}
}
unless (fmt) {
P3(("(harmless) textdb returns \"[%s]\" for"
" %O, %O, %O\n", mc, file, ghost, curse))
#if DEBUG > 3
// this warning is currently useless as net/irc
// first requests this sort of template, then
// throws it away. we'd have to change the flow
// of operations (the textdb API really) to make
// this warning useful again.
if (strlen(mc) && mc[0] == '_')
raise_error("DEBUG: textdb incomplete?\n");
#endif
return "["+mc+"]";
}
}
}
}
if (fmt && strlen(fmt)) {
// if (abbrev("_TEXT_", mc) || abbrev("_MISC_", mc)) {
if (mc[1] <= 'Z' && mc[1] >= 'A') {
// D(S("chomping: %O for %O\n", fmt, mc));
#ifndef NOTEXTCACHE
_v[mc] = fmt = chomp(fmt);
#else /* NOTEXTCACHE */
fmt = chomp(fmt);
unless (dont_cache) _v[mc] = fmt;
#endif /* NOTEXTCACHE */
}
}
P4(("DB lookup in %O finally returns %O\n", ME, fmt))
return fmt;
}
#if 0 //def NOTEXTCACHE
void create() {
if(clonep(ME)) fs = DAEMON_PATH "share" -> get("tdbFS");
::create();
}
#endif