&games,
const char *gameid,
const char *username,
unsigned revision,
bool force,
string &msg)
{
bool ret = false;
auto g = games.find(gameid);
if (g != games.end()){
if (g->second->get_revision() != revision){
msg = "Revision mismatch";
}else if (!force && g->second->get_nbwait() > 0){
msg = "User waitings";
}else{
ret = true;
if (!force){
for (auto &gg:games){
if (gg.second != g->second){
auto tb = gg.second->get_embed_list();
if (tb.count(gameid)>0){
ret = false;
msg = string_f("game is embedded in document %s",gg.first.c_str());
break;
}
}
}
}
if (ret){
string msg = string_f(MSG_U(I_DELETED,"Attention"
"The document has been deleted by %s
"
"
"
"The document may be undeleted if needed"),username);
documentd_create_popup (msg,g->second);
games.erase (g);
}
}
}else{
msg = "Unknown game id";
}
return ret;
}
static void closetb(int tb[2])
{
close (tb[0]);
close (tb[1]);
}
static void forgettb(int tb[2])
{
tb[0] = tb[1] = -1;
}
static void dup_close (int fd, int target_fd)
{
dup2 (fd,target_fd);
close (fd);
}
/*
Convert a relative path (relative to the project) to absolute path.
If the relative path starts with a /, it means it is a path relative to the project.
If not, it is relative to the folder holding the gameid.
The project is extracted from the gameid.
*/
string documentd_rel2abs (PARAM_STRING gameid, PARAM_STRING relpath)
{
string ret;
auto tb = str_splitline (gameid.ptr,'/');
if (tb.size() >= 4){
ret = string_f("/%s/%s/%s",tb[1].c_str(),tb[2].c_str(),tb[3].c_str());
if (relpath.ptr[0] == '/'){
ret += relpath.ptr;
}else{
for (unsigned i=4; i 0){
auto g = pt_games->find(docpath);
if (g != pt_games->end()){
auto game = g->second;
vector steps;
VARVAL_receive var;
var.var = command.ptr;
var.val = option.ptr;
steps.push_back(var);
vector res;
DOC_CONTEXT ctx;
ctx.session = "";
ctx.username = "";
ctx.maywrite = true;
ctx.docnum = docnum;
vector unotifies;
game->manyexec(steps,ctx,sp,res,unotifies);
for (auto &r:res){
if (is_any_of(r.var,VAR_CONTENT,VAR_STYLES)){
ret = move(r.val);
}else if (is_any_of(r.var,VAR_DEFSCRIPT)){
script = move(r.val);
}
}
}
}
return ret;
}
static GAME_P documentd_findsubgame(const char *gameid, const string &document)
{
GAME_P ret;
string docpath = documentd_rel2abs(gameid,document);
if (docpath.size() > 0){
auto p = pt_games->find(docpath);
if (p != pt_games->end()){
ret = p->second;
}
}
return ret;
}
/*
Get all the scripts, styles and templates to support document embedding in this document
*/
void documentd_imbeds (
GAME *game,
string &lines,
const DOC_UI_SPECS_receive &sp)
{
set type_seens; // Document types already processed
auto tb = game->get_embed_specs();
type_seens.insert(game->getclass()); // A document may imbed another document of the same
// type, so no need to source the styles and scripts
for (auto &t:tb){
string docpath = documentd_rel2abs(game->get_gameid(),t.document);
auto p = pt_games->find(docpath);
if (p != pt_games->end()){
GAME_P sub = p->second;
vector steps;
VARVAL_receive var;
if (type_seens.insert(sub->getclass()).second){
// We only need those once per document type
var.var = REQ_FUNCTIONS;
steps.push_back(var);
var.var = REQ_STYLES;
steps.push_back(var);
}
var.var = REQ_REGION;
var.val = t.region.c_str();
steps.push_back(var);
vector res;
DOC_CONTEXT ctx;
ctx.docnum = t.docnum;
vector unotifies;
sub->manyexec(steps,ctx,sp,res,unotifies);
for (auto &r:res){
if (r.var == VAR_STYLES){
lines += "\n";
}else if (r.var == VAR_DEFSCRIPT){
lines += "\n";
}else if (r.var == VAR_CONTENT){
lines += string_f("\n",t.docnum);
lines += r.val + "\n";
game->set_embed_options(t,r.val.find("not-square")!=string::npos);
}
}
}
}
}
/*
Insert a new imbedded document into a document currently displayed.
We are using this function when a user is adding a new document.
*/
void documentd_insert_imbed(
GAME *game,
VARVAL ¬ify_var,
DOCUMENT_EMBED &imbed,
const DOC_UI_SPECS_receive &sp)
{
// We have to know if another document of the same type is currently imbedded.
// Potentially, the parent document is imbedding a sub-document of the same type.
const char *gameid = game->get_gameid();
bool found = false;
auto newsub = documentd_findsubgame(gameid,imbed.document);
if (newsub == nullptr){
tlmp_error ("documentd_insert_imbed: unknown document %s\n",imbed.document.c_str());
return;
}
if (is_eq(game->getclass(),newsub->getclass())){
found = true;
}else{
auto tb = game->get_embed_specs();
for (auto &t:tb){
auto sub = documentd_findsubgame(gameid,t.document);
if (is_eq(sub->getclass(),newsub->getclass())){
found = true;
break;
}
}
}
string script;
//tlmp_warning ("insert_imbed process functions and styles: %s %s found=%d\n",gameid,imbed.document.c_str(),found);
if (!found){
documentd_imbed (gameid,imbed.document,REQ_FUNCTIONS,"",imbed.docnum,sp,script);
notify_var.val += script;
string content = documentd_imbed (gameid,imbed.document,REQ_STYLES,"",imbed.docnum,sp,script);
notify_var.val += "document.body.innerHTML+=`";
notify_var.val += "`;\n";
script.clear();
}
string content = documentd_imbed (gameid,imbed.document,REQ_REGION,imbed.region,imbed.docnum,sp,script);
// First we add the sub-document template definition, then the scripts
notify_var.val += "document.body.innerHTML+=`";
notify_var.val += string_f("\n",imbed.docnum);
notify_var.val += content;
notify_var.val += "`;\n";
notify_var.val += script;
if (content.find("not-square")!=string::npos) imbed.not_square = true;
}
/*
Generate the function to loop inside all javascript object, then search for an id and apply a function
*/
string documentd_js_loop_function(const char *board_prefix, const char *prefix, int docnum)
{
string lines;
if (docnum == 0){
lines += "var doc_lst=[];\n";
lines += "var doc_cur_gameid='';\n";
lines += "function gen_loop_findid(svg,tag,id,chs,fct){\n"
"\tif (svg != null){\n"
"\t\tvar elms=svg.getElementsByTagName(tag);\n"
"\t\tvar e_found = null;\n"
"\t\tvar min_depth = 1000;\n"
// We have to find the element closer to the parent.
// With embeded/linked document, we may have several elements
// with the same id.
"\t\tfor (var j=0; j tb;
str_splitline (_command.ptr,' ',tb);
const char *tbp[tb.size()+1];
for (unsigned i=0; i(n));
}
SUBPROGRAM &SUBPROGRAM::operator =(SUBPROGRAM &&n)
{
if (this != &n){
subswap(forward(n));
}
return *this;
}
SUBPROGRAM::~SUBPROGRAM()
{
//printf ("pid=%d fdin=%d fdout=%d fderr=%d command=%s\n",(int)pid,fdin,fdout,fderr,command.c_str());
close (fdin);
close (fdout);
close (fderr);
if (pid != (pid_t)-1){
//printf ("kill %u\n",pid);
kill (pid,SIGTERM);
}
}
void SUBPROGRAM::send(PARAM_STRING gameid, PARAM_STRING line)
{
tosend.emplace_back(gameid,line);
sendmore();
}
/*
Send more lines to the engine until we reach an empty line.
This means we expect some answer from the engine before sending more.
*/
int SUBPROGRAM::sendmore()
{
int ret = 0;
if (tosend.size() > 0
&& (gameid.empty() || tosend[0].gameid == gameid)){
gameid = tosend[0].gameid;
for (auto &l:tosend){
if (l.gameid != gameid){
break;
}else{
ret++;
write (fdin,l.line.c_str(),l.line.size());
}
}
tosend.erase(tosend.begin(),tosend.begin()+ret);
nbsend += ret;
}
return ret;
}
void subprogram_exec (PARAM_STRING gameid, const char *cmd)
{
}
void documentd_init_specs (DOC_UI_SPECS_receive &sp)
{
sp.width = sp.height = sp.content_width = sp.content_height = 1000;
sp.mobile = false;
sp.fontsize = 14;
}
static void documentd_chess_move(vector &programs, map &games, const char *gameid, PARAM_STRING move)
{
if (strlen(move.ptr)==4
&& isalpha(move.ptr[0])
&& isdigit(move.ptr[1])
&& isalpha(move.ptr[2])
&& isdigit(move.ptr[3])){
DOC_UI_SPECS_receive sp;
documentd_init_specs (sp);
vector res;
unsigned col1 = move.ptr[0] - 'a';
unsigned line1 = 8-(move.ptr[1] - '0');
unsigned col2 = move.ptr[2] - 'a';
unsigned line2 = 8-(move.ptr[3] - '0');
string cmd1 = string_f("%u,%u,1",line1,col1);
string cmd2 = string_f("%u,%u,1",line2,col2);
vector steps;
VARVAL_receive st;
st.var = "place";
st.val = cmd1.c_str();
steps.emplace_back(st);
st.val = cmd2.c_str();
steps.emplace_back(st);
string msg;
bool unknown;
DOC_CONTEXT ctx;
ctx.session = "session";
ctx.username = "user";
ctx.maywrite = true;
documentd_playstep (programs,games,gameid,steps,ctx,sp,res,msg,unknown);
}
}
static void documentd_chess_print (const char *user, map &games, const char *gameid)
{
DOC_UI_SPECS_receive sp;
documentd_init_specs (sp);
vector res;
string msg;
bool unknown;
vector steps;
VARVAL_receive st;
st.var = "print";
st.val = "console";
steps.emplace_back(st);
vector programs;
DOC_CONTEXT ctx;
ctx.session = "session";
ctx.username = "user";
ctx.maywrite = true;
documentd_playstep (programs,games,gameid,steps,ctx,sp,res,msg,unknown);
for (auto &r:res){
if (r.var == VAR_CONTENT){
printf("\n%s\n",r.val.c_str());
break;
}
}
}
/*
Start all available game engines
*/
static void documentd_start_engines(vector &programs)
{
if (file_type("/usr/bin/stockfish")!=-1){
programs.emplace_back(CLASS_CHESS,"/usr/bin/stockfish");
auto &s = programs[programs.size()-1];
for (auto line:{"uci\n","ucinewgame\n","setoption name Skill Level value 0\n","isready\n"}){
s.send ("",line);
}
}else if (file_type("/usr/bin/gnuchess")!=-1){
programs.emplace_back(CLASS_CHESS,"/usr/bin/gnuchess -u");
auto &s = programs[programs.size()-1];
for (auto line:{"uci\n","","ucinewgame\n","isready\n"}){
s.send ("",line);
}
}
}
static map flags;
/*
Return the value of a flag.
Return nullptr if the flag is not defined.
*/
const char *documentd_getflag(const char *flag)
{
const char *ret = nullptr;
auto f = flags.find(flag);
if (f != flags.end()){
ret = f->second.c_str();
}
return ret;
}
/*
Erase the last character (UTF8) of a string
*/
void documentd_eraselast (string &txt)
{
size_t size = txt.size();
if (size > 0){
unsigned pos = 0;
while (1){
unsigned charsize = utf8_codepoint_size(txt[pos]);
unsigned newpos = pos + charsize;
if (newpos < size){
pos = newpos;
}else{
break;
}
}
txt.resize(pos);
}
}
/*
Copy some results from full to part without exceding REQ_CONTENT_CHUNK
*/
static void documentd_copy_chunk_res(vector &part, vector &full)
{
size_t size = 0;
// No ++p in this loop as we remove the first entries until we break out the of loop
// The erase method return the next p
for (auto p=full.begin(); p != full.end(); ){
auto next_size = p->val.size() + size;
if (next_size <= REQ_CONTENT_CHUNK){
part.emplace_back(move(*p));
p = full.erase(p);
size = next_size;
}else{
auto diff = REQ_CONTENT_CHUNK - size;
if (diff > 0){
VARVAL tmp;
tmp.var = p->var;
tmp.val = p->val.substr(0,diff);
p->val.erase(0,diff);
part.emplace_back(move(tmp));
}
break;
}
}
}
int main (int argc, char *argv[])
{
glocal int ret = -1;
glocal const char *control = "/var/run/documentd.sock";
glocal const char *clientsock = "/tmp/documentd_client.sock";
glocal const char *user = "bolixo";
glocal bool daemon = false;
glocal const char *client_secretfile = "/etc/bolixo/secrets.client";
glocal const char *pidfile = "/var/run/documentd.pid";
glocal const char *hostname = NULL;
glocal vector programs;
translat_setlang ("eng,fr");
static const char *tbdic[]={"bolixo",NULL};
signal (SIGPIPE,SIG_IGN);
glocal.ret = (argc,argv,tbdic);
setproginfo ("documentd",VERSION,"Process document content");
setgrouparg ("Networking");
setarg ('c',"control","Unix socket for documentd-control",glocal.control,false);
setarg ('C',"clientsock","Unix socket for documentd-client",glocal.clientsock,false);
setgrouparg ("Misc.");
setarg (' ',"user","Run the program as this user",glocal.user,false);
setarg (' ',"daemon","Run in background",glocal.daemon,false);
setarg (' ',"pidfile","File holding the PID of the process",glocal.pidfile,false);
setarg (' ',"client-secrets","File holding client secrets for communication",glocal.client_secretfile,false);
setarg (' ',"nodename",MSG_R(O_NODENAME),nodename,true);
if (glocal.daemon){
syslog (LOG_ERR,"%s",msg);
}else{
fprintf (stderr,"%s",msg);
}
if (glocal.daemon){
syslog (LOG_WARNING,"%s",msg);
}else{
fprintf (stderr,"%s",msg);
}
// This is used for testing/debug
//long long start = fdpass_getnow();
//long long end = fdpass_getnow();
//long long diff = end - start;
//tlmp_warning ("set_para_spec %Ld.%06Ld",diff/1000000,diff%1000000);
void wordproc_testparagraph (const char *line, unsigned width, unsigned fontsize, unsigned para_cursor);
if (strcmp(argv[0],"testparagraph")==0){
if (argc > 1){
for (int i=1; i games;
documentd_startgame(glocal.games,"boBOCHES",glocal.gameid,msg);
// Trick to assign the users
documentd_chess_print ("user1",glocal.games,glocal.gameid);
documentd_chess_print ("robot",glocal.games,glocal.gameid);
if (argc == 1){
documentd_start_engines(glocal.programs);
}else{
string cmd = argv[1];
for (int i=2; i();
printf ("endclient %d\n",no);
auto &s = glocal.programs[0];
if (no == 0){
// stdin
documentd_chess_move(glocal.programs,glocal.games,glocal.gameid,line);
documentd_chess_print("user1",glocal.games,glocal.gameid);
}else if (s.is_fdout(no)){
const char *gameid = s.get_gameid();
if (gameid[0] == '\0'){
printf ("out: %s\n",line);
}else{
printf ("out %s: %s\n",gameid,line);
for (auto &m:glocal.games){
if (m.first == gameid){
string notify;
bool done = false;
m.second->engine_reply(line,notify,done);
if (notify.size() > 0) documentd_chess_print("user1",glocal.games,glocal.gameid);
if (done) s.reset_gameid();
s.sendmore();
break;
}
}
}
}else if (s.is_fderr(no)){
printf ("err: %s\n",line);
}
for (auto &s:glocal.programs){
o.inject (s.get_fdout(),nullptr);
o.inject (s.get_fderr(),nullptr);
}
o.inject (0,nullptr);
o.loop();
}else if (strcmp(argv[0],"testcalc")==0){
void calc_eval (int argc, char *argv[]);
calc_eval (argc-1,argv+1);
}else if (strcmp(argv[0],"doc2html")==0){
if (argc == 2){
string msg;
FILE *fin = fopen (argv[1],"r");
if (fin == nullptr){
msg = "Can't open document";
}else{
char buf[100];
if (fgets(buf,sizeof(buf)-1,fin)!=NULL){
GAME_P p = documentd_newgame(buf,"id",msg);
if (p != NULL){
DOC_READER doc(fin);
p->load (doc,msg);
if (msg.size() == 0){
DOC_CONTEXT ctx;
DOC_UI_SPECS_receive sp;
documentd_init_specs (sp);
vector res;
vector steps;
VARVAL_receive rec;
rec.var = REQ_PRINT;
rec.val = "console";
steps.push_back(rec);
vector unotifies;
p->manyexec (steps,ctx,sp,res,unotifies);
for (auto &r:res) printf ("var=%s: %s\n",r.var.c_str(),r.val.c_str());
}
}
}
fclose (fin);
}
if (msg.size() > 0) tlmp_error ("Error: %s\n",msg.c_str());
}
}else{
tlmp_error ("Invalid test command\n");
}
return 0;
int ret = -1;
glocal map games;
glocal unsigned messages_sent = 0;
glocal string controlport = string_f("unix:%s",glocal.control);
glocal string clientport = string_f("unix:%s",glocal.clientsock);
glocal map client_secrets;
glocal map> handles; // Handles used for playstep_more
fdpass_readsecrets (glocal.client_secretfile,glocal.client_secrets);
signal (SIGCHLD,SIG_IGN);
documentd_start_engines(glocal.programs);
(glocal.clientport,5);
HANDLE_INFO *n = new HANDLE_INFO;
info.data = n;
// tlmp_error ("port=%s control=%s client=%s\n",info.port,glocal.controlport.c_str(),glocal.clientport.c_str());
if (string_cmp(info.port,glocal.controlport)==0){
n->type = TYPE_CONTROL;
}else if (string_cmp(info.port,glocal.clientport)==0){
n->req.secret = fdpass_findsecret (glocal.client_secrets,info.port);
n->type = TYPE_CLIENT;
}
// Is this client waiting for a notification ?
bool found = false;
for (auto g:glocal.games){
if (g.second->del_notification_fd(no)!=-1){
found = true;
break;
}
}
if (!found){
for (auto p=glocal.programs.begin(); p != glocal.programs.end(); p++){
if (p->is_fdout(no) || p->is_fderr(no)){
tlmp_error ("Engine %s ending",p->getclass());
glocal.programs.erase(p);
break;
}
}
}
debug_printf (D_PROTO,"receive line: %s\n",line);
HANDLE_INFO *c = (HANDLE_INFO*)info.data;
static const char *tbtype[]={"none","control request","client request","subprogram"};
ERROR_PREFIX prefix ("%s: ",tbtype[c->type]);
if (c->type == TYPE_CONTROL){
(this,c->req,line, info.linelen,endserver, endclient, no,c);
vector tb;
tb.push_back(string_f ("Version %s",VERSION));
instrument_status(tb);
unsigned nbwait=0;
for (auto g:glocal.games){
DATEASC act,mod;
fdpass_asctime(g.second->get_last_activity(),act);
if (g.second->is_modified()) fdpass_asctime(g.second->get_modified(),mod);
tb.push_back(string_f("gameid: %s last_activity=%s modified=%s modified_by=%s revision=%u nbwait=%u sequence=%u"
,g.first.c_str()
,act.buf,mod.buf,g.second->get_modified_by(),g.second->get_revision()
,g.second->get_nbwait(),g.second->get_sequence()));
nbwait += g.second->get_nbwait();
}
tb.push_back(string_f("protocolstats: %d",protocol_stats));
tb.push_back(string_f("chessmaxskill: %u",chess_getmaxskill()));
tb.push_back(string_f("bytes sent: requests=%u content=%zu script=%zu unotify=%zu notify=%zu"
,total_requests,total_content_size,total_script_size,total_unotify_size,total_notify_size));
tb.push_back(string_f("nbwaiting: %u",nbwait));
tb.push_back(string_f("handles: %zu",glocal.handles.size()));
tb.push_back(string_f("subprograms: %zu",glocal.programs.size()));
for (auto &p:glocal.programs){
tb.push_back(string_f("subprogram %s: nbsend=%u nbrec=%u gameid=%s command=%s"
,p.getclass(),p.getnbsend(),p.getnbrec(),p.getgameid(),p.getcommand()));
}
rep_status(tb);
vector stats;
for (auto g:glocal.games){
GAMESTAT st;
st.gameid = g.first;
st.modified = g.second->is_modified() ? g.second->get_modified() : 0;
st.modified_by = g.second->get_modified_by();
st.last_activity = g.second->get_last_activity();
st.revision = g.second->get_revision();
stats.emplace_back(st);
}
rep_listgames(stats);
toggle_instrument_file(on);
protocol_stats = on;
chess_setmaxskill(maxskill);
// Save all games/documents in a file
unsigned num=0;
for (auto &g:glocal.games){
glocal GAME_P game = g.second;
glocal const char *gameid = g.first.c_str();
(string_f("/tmp/game.%u",num),false);
fprintf (fout,"%s\nmodified=%lu\nmodified_by=%s\nactivity=%lu\nboBO%s\n"
,glocal.gameid,glocal.game->get_modified()
,glocal.game->get_modified_by()
,glocal.game->get_last_activity()
,glocal.game->getclass());
DOC_WRITER doc(fout);
glocal.game->save(doc,true);
return 0;
num++;
}
endserver = true;
if (on){
debug_seton();
}else{
debug_setoff();
}
debug_setfdebug (filename);
// gamename gameid = success:b msg
string msg;
bool success = documentd_startgame(glocal.games,gamename,gameid,msg);
rep_startgame(success,msg);
// gameid = success:b msg
string msg;
bool success = documentd_endgame(glocal.games,gameid,"admin",revision,force,msg);
rep_endgame (success,msg);
glocal.games.clear();
// gameid = success:b msg
bool success = false;
string msg;
auto g = glocal.games.find(gameid);
if (g != glocal.games.end()){
success = true;
g->second->resetgame();
}else{
msg = "Unknown game id";
}
rep_resetgame (success,msg);
// gameid steps:U{VARVAL}v width:u height:u = success:b msg res:U{VARVAL}v
vector res;
string msg;
bool unknown;
DOC_UI_SPECS_receive sp;
sp.width = sp.height = 1000;
sp.content_width = sp.content_height = 1000;
sp.mobile = false;
sp.fontsize = 14;
DOC_CONTEXT ctx;
ctx.session = "sessadmin";
ctx.username = "admin";
ctx.maywrite = true;
bool success = documentd_playstep (glocal.programs,glocal.games,gameid,steps,ctx,sp,res,msg,unknown);
rep_playstep(success,unknown,msg,res);
// gameid = success:b msg
bool success = false;
string msg;
auto g = glocal.games.find(gameid);
if (g != glocal.games.end()){
glocal GAME_P game = g->second;
success = true;
(documentd_path(gameid),false);
fprintf (fout,"boBO%s\n",glocal.game->getclass());
DOC_WRITER doc(fout);
glocal.game->save(doc,true);
return 0;
}else{
msg = "Unknown game id";
}
rep_save(success,msg);
// gameid = success:b msg
bool success = false;
string msg;
auto g = glocal.games.find(gameid);
if (g != glocal.games.end()){
glocal.games.erase (g);
}
string tmp = documentd_path(gameid);
FILE *fin = fopen (tmp.c_str(),"r");
if (fin == NULL){
msg = "Gameid file does not exist";
}else{
char buf[100];
if (fgets(buf,sizeof(buf)-1,fin)!=NULL){
GAME_P p = documentd_newgame(buf,gameid,msg);
if (p != NULL){
glocal.games[gameid] = p;
DOC_READER doc(fin);
p->load (doc,msg);
success = msg.size() == 0 ? true : false;
}
}
fclose (fin);
}
rep_load(success,msg);
flags[flag] = value;
tlmp_error ("Invalid command: %s\n",line);
endclient = true;
}else if (c->type == TYPE_CLIENT){
(this,c->req,line,info.linelen, endserver, endclient,no,c);
rep_test(true);
// gamename gameid = success:b msg
string msg;
translat_selectlang(lang);
bool success = documentd_startgame(glocal.games,gamename,gameid,msg);
rep_startgame(success,msg);
// gameid revision:u username force:b lang = success:b msg
string msg;
translat_selectlang(lang);
bool success = documentd_endgame(glocal.games,gameid,username,revision,force,msg);
rep_endgame (success,msg);
vector res;
string msg;
translat_selectlang(lang);
bool unknown;
DOC_CONTEXT ctx;
ctx.session = session;
ctx.username = username;
ctx.maywrite = maywrite;
ctx.docnum = docnum;
ctx.connectid = connectid;
bool success = documentd_playstep (glocal.programs,glocal.games,gameid,steps,ctx,sp,res,msg,unknown);
size_t size = 0;
for (auto &r:res) size += r.val.size();
if (size <= REQ_CONTENT_CHUNK){
rep_playstep(success,unknown,msg,res,false,"");
}else{
// playstep_more will be needed.
vector part;
documentd_copy_chunk_res(part,res);
string handle = fs_makeid();
glocal.handles[handle] = move(res);
rep_playstep(success,unknown,msg,part,true,handle);
}
auto h = glocal.handles.find(handle);
if (h == glocal.handles.end()){
vector res;
rep_playstep_more(false,"invalid handle",res,false);
}else{
auto &hres = h->second;
size_t size = 0;
for (auto &r:hres) size += r.val.size();
if (size < REQ_CONTENT_CHUNK){
rep_playstep_more (true,"",hres,false);
glocal.handles.erase(handle);
}else{
vector res;
documentd_copy_chunk_res(res,hres);
rep_playstep_more (true,"",res,true);
}
}
// Rename a document. It is possible that old_gameid is not a document, but a folder, so we must
// rename all documents in this folder.
string msg;
translat_selectlang(lang);
vector> to_renames; // Will contain the entries to rename
// We do a first pass to identify all entries to rename
// The second pass will complete the work.
// We do this because glocal.games must not be changes during
// the first pass.
for (auto &g:glocal.games){
const char *pt;
if (g.first == old_gameid){
// We have found a document with this name
to_renames.emplace_back(old_gameid,new_gameid);
break;
}else if (is_start_any_of(g.first,pt,old_gameid) && pt[0] == '/'){
// old_gameid is in fact a directory.
// We assume new_gameid is the new name of the directory.
to_renames.emplace_back(g.first,string_f("%s%s",new_gameid,pt));
}
}
for (auto &r:to_renames){
// This is ok if the old document is missing
auto oldg = glocal.games.find(r.first);
auto newg = glocal.games.find(r.second);
if (newg != glocal.games.end()){
msg = MSG_U(E_NEWEXIST,"Can't rename document, new name exist");
break;
}else{
auto doc = oldg->second;
const char *relname = r.second.c_str();
const char *pt;
if (is_start_any_of(r.second,pt,"/projects/")) relname = pt;
string msg = string_f(MSG_U(I_RENAMED,"Attention"
"The document has been renamed by %s
"
"Its new name is
"
"
%s"
"Close the document and reopen it using the new name
"
"All your modifications are preserved"),username,relname);
documentd_create_popup(msg,doc);
doc->setgameid(r.second);
glocal.games.erase(r.first);
glocal.games[r.second] = doc;
}
}
if (msg.size() > 0){
rep_rename (false,msg);
}else{
rep_rename (true,"");
}
string msg;
BOB_TYPE content;
auto g = glocal.games.find(gameid);
if (g != glocal.games.end()){
GAME_P game = g->second;
DOC_WRITER doc;
doc.write (string_f("boBO%s\n",game->getclass()));
game->save(doc,false);
content = doc.getcontent();
game->resetmodified();
}else{
msg = "Unknown game id";
}
if (msg.size() > 0){
content.clear();
rep_save(false,msg,content,false);
}else{
rep_save (true,"",content,false);
}
BOB_TYPE content;
rep_savemore (false,"Not inplemented",content,false);
string msg;
auto g = glocal.games.find(gameid);
const char *type = (const char *)content.getbuffer();
GAME_P game = nullptr;
if (g != glocal.games.end()){
game = g->second;
}else{
game = documentd_newgame(type,gameid,msg);
}
if (game != nullptr){
glocal.games[gameid] = game;
DOC_READER doc(type+9);
game->load(doc,msg);
}
vector imbeds;
if (game != nullptr && msg.size() > 0){
rep_load (false,msg,imbeds);
}else{
auto sub = game->get_embed_list();
for (auto &s:sub){
if (glocal.games.find(s)==glocal.games.end()) imbeds.push_back(s);
}
rep_load (true,"",imbeds);
}
// gameid = success:b msg modified:b
bool modified = false;
const char *modified_by = "";
string msg;
auto g = glocal.games.find(gameid);
if (g != glocal.games.end()){
modified = g->second->is_modified();
modified_by = g->second->get_modified_by();
}else{
msg = "Unknown document";
}
if (msg.size() > 0){
rep_is_modified(false,msg,false,"",0);
}else{
rep_is_modified(true,"",modified,modified_by,g->second->get_revision());
}
// connectid gameid username sequence:u = success:b msg script sequence:u
auto g = glocal.games.find(gameid);
if (g == glocal.games.end()){
rep_waitevent (false,"Invalid gameid","",0);
}else{
GAME_P game = g->second;
// This is an ASYNC. Fairly unique in Bolixo.
// The connection is reserved for one waitevent call.
// It means we can call rep_waitevent repeatedly
// as well as documentd_client_rep_waitevent later.
string rep;
while (1){
const char *script = game->locate_event(sequence);
if (script != nullptr){
//rep += script;
total_notify_size += strlen(script);
rep_waitevent (true,"",script,sequence);
}else{
break;
}
}
if (rep.size() > 0){
total_notify_size += rep.size();
rep_waitevent (true,"",rep,sequence);
}
{
// tlmp_warning ("waitevent no=%d,%d username=%s connectid=%s",glocal.no,no,username,connectid);
game->add_notification_fd(no,username,connectid);
if (!game->waiting_user(username)){
string line;
game->update_waiting_users(line);
game->add_notification(line);
}
}
}
for (auto g:glocal.games) g.second->remove_session(session);
rep_removesession(true,"");
tlmp_error ("Invalid command: %s\n",line);
endclient = true;
}else if (c->type == TYPE_SUBPROGRAM){
for (auto &s:glocal.programs){
if (s.is_fdout(no)){
//printf ("out %s: %s\n",s.get_gameid(),line);
s.inc_nbrec();
for (auto &m:glocal.games){
if (m.first == s.get_gameid()){
string notify;
bool done = false;
m.second->engine_reply(line,notify,done);
if (done) s.reset_gameid();
s.sendmore();
m.second->add_notification(notify);
break;
}
}
}else if (s.is_fderr(no)){
tlmp_error ("engine %s error: %s\n",s.get_gameid(),line);
}
}
}
bool some_errors = false;
if (fdpass_setcontrol(s,glocal.control,glocal.user)==-1){
some_errors = true;
}
// Register all subprograms
for (auto &p:glocal.programs){
HANDLE_INFO *n = new HANDLE_INFO;
n->type = TYPE_SUBPROGRAM;
s.inject (p.get_fdout(),n);
n = new HANDLE_INFO;
n->type = TYPE_SUBPROGRAM;
s.inject (p.get_fderr(),n);
}
if (!some_errors && s.is_ok()){
// Load saved games/documents
if (file_type("/tmp/game.0")==0){
("/tmp");
if (strncmp(basename,"game.",5)==0){
FILE *fin = fopen (path,"r");
if (fin == nullptr){
tlmp_error ("Can't open saved game %s (%s)\n",path,strerror(errno));
}else{
char buf1[1000],buf2[1000];
if (fgets(buf1,sizeof(buf1)-1,fin)!=nullptr
&& fgets(buf2,sizeof(buf2)-1,fin)!=nullptr){
strip_end (buf1);
const char *pt;
time_t mod = 0;
if (is_start_any_of(buf2,pt,"modified=")){
mod = atoi(pt);
fgets(buf2,sizeof(buf2)-1,fin);
}
string mod_by;
if (is_start_any_of(buf2,pt,"modified_by=")){
mod_by = pt;
strip_end (mod_by);
fgets(buf2,sizeof(buf2)-1,fin);
}
time_t act = 0;
if (is_start_any_of(buf2,pt,"activity=")){
act = atoi(pt);
fgets(buf2,sizeof(buf2)-1,fin);
}
strip_end (buf2);
string msg;
if (!documentd_startgame(glocal.games,buf2,buf1,msg)){
tlmp_error ("Can't initialise game %s/%s (%s)\n",buf1,buf2,msg.c_str());
}else{
DOC_READER doc(fin);
auto g = glocal.games[buf1];
g->load(doc,msg);
g->setmodified(mod,mod_by.c_str());
g->setactivity(act);
}
}
fclose (fin);
}
unlink (path);
}
}
chmod (glocal.clientsock,0666);
s.setrawmode(true);
if (glocal.daemon){
daemon_init(glocal.pidfile,glocal.user);
}
(5);
for (auto &g:glocal.games){
string line;
g.second->update_waiting_users(line);
if (line.size() > 0) g.second->add_notification(line);
}
netevent_loop(s,idle);
ret = 0;
}
return ret;
return glocal.ret;
}
#include
#include FT_FREETYPE_H
#define UTF8_ONE_BYTE_MASK 0b10000000
#define UTF8_ONE_BYTE_COUNT 0
#define UTF8_TWO_BYTE_MASK 0b11100000
#define UTF8_TWO_BYTE_COUNT 0b11000000
#define UTF8_THREE_BYTE_MASK 0b11110000
#define UTF8_THREE_BYTE_COUNT 0b11100000
#define UTF8_FOUR_BYTE_MASK 0b11111000
#define UTF8_FOUR_BYTE_COUNT 0b11110000
// This one could use a better name, I just don't know a better one (yet?)
#define UTF8_OTHER_MASK 0b00111111
size_t utf8_codepoint_size(uint8_t text)
{
if((text & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_COUNT) {
return 1;
}
if((text & UTF8_TWO_BYTE_MASK) == UTF8_TWO_BYTE_COUNT) {
return 2;
}
if((text & UTF8_THREE_BYTE_MASK) == UTF8_THREE_BYTE_COUNT) {
return 3;
}
return 4;
}
/*
Compute the width and height of the string using a font library.
*/
unsigned documentd_displaylen (const char *title, unsigned fontsize, float size)
{
unsigned ret = 0;
static bool some_errors = false;
static bool is_init = false;
static FT_Library library;
static FT_Face face; /* handle to face object */
if (!is_init){
is_init = true;
some_errors = true;
int error = FT_Init_FreeType( &library );
if ( error ){
tlmp_error (" ... an error occurred during library initialization ...\n");
}else{
const char *fontfile = nullptr;
for (auto s:{
"/usr/share/fonts/dejavu/DejaVuSerif.ttf",
"/usr/share/fonts/liberation-sans/LiberationSans-Regular.ttf",
"/usr/share/fonts/liberation/LiberationSans-Regular.ttf",
"/usr/share/fonts/dejavu/DejaVuSans.ttf"
}){
if (file_type(s)!=-1){
fontfile = s;
break;
}
}
if (fontfile == nullptr){
some_errors = true;
tlmp_error ("tlmpweb_displaylen: No font file found");
}else{
error = FT_New_Face(library,fontfile,0,&face);
if ( error == FT_Err_Unknown_File_Format ){
tlmp_error ("... the font file could be opened and read, but it appears\n"
"... that its font format is unsupported\n");
}else if ( error ){
tlmp_error ("... another error code means that the font file could not\n"
"... be opened or read, or that it is broken...\n");
}else{
some_errors = false;
}
}
}
}
unsigned font_charsize = fontsize*64*size;
//font_charsize = 13.6*64*size;
if (!some_errors){
static unsigned last_charsize=0;
//unsigned charsize = 13.6*64*size;
if (font_charsize != last_charsize){
last_charsize = font_charsize;
int error = FT_Set_Char_Size(
face, /* handle to face object */
0, /* char_width in 1/64th of points */
font_charsize, /* char_height in 1/64th of points */
0, /* horizontal device resolution */
0 ); /* vertical device resolution */
if (error){
tlmp_error ("Set_Char_Size %u error\n",font_charsize);
}else{
some_errors = false;
}
}
}
if (some_errors){
ret = strlen(title)*9;
}else{
const char *pt = title;
while (*pt != '\0'){
unsigned t = *pt++;
size_t charsize = utf8_codepoint_size(t);
switch(charsize){
case 1:
break;
case 2:
if (*pt == '\0') break;
t = (t<<8)+*pt++;
break;
case 3:
break;
case 4:
break;
}
struct CACHECHAR{
unsigned car;
unsigned font_charsize;
CACHECHAR(unsigned _car, unsigned _font_charsize){
car = _car;
font_charsize = _font_charsize;
}
bool operator < (const CACHECHAR &n) const {
return tie(font_charsize,car) < tie(n.font_charsize,n.car);
}
};
static map cache;
auto &cached_width = cache[CACHECHAR(t,font_charsize)];
if (cached_width == 0){
unsigned glyph_index = FT_Get_Char_Index( face, t );
/* load glyph image into the slot (erase previous one) */
int error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );
if ( error ) continue; /* ignore errors */
/* convert to an anti-aliased bitmap */
error = FT_Render_Glyph( face->glyph, FT_RENDER_MODE_NORMAL );
if ( error ) continue;
//tlmp_error ("dislay_len: %c -> %u\n",t,(unsigned)(face->glyph->advance.x >> 6));
cached_width = face->glyph->advance.x >> 6;
//ret += 1; // One pixel between characters. advance takes care of that
// but for now, we are not using the same font as the browser
}
ret += cached_width;
}
}
return ret;
}
void _F_button_bar::status(const char *gameid, string &lines)
{
}
// Draw the button bar
void button_bar (_F_button_bar &c, GAME *game, bool mobile, bool maywrite, string &lines)
{
if (maywrite){
const char *gameid = game->get_gameid();
string but_lines;
DOC_BUTTON_SPECS specs(mobile);
documentd_button_start(but_lines,gameid);
c.draw (specs,gameid,but_lines);
documentd_button_end(but_lines);
string st_lines;
c.status (gameid,st_lines);
if (st_lines.empty()){
lines += "