/*************************************************************************** FILE: cl_editor.cc LVU : 1.3.3 DESC: This contains the code to create & run editor objects used in entering profiles, mail etc. Copyright (C) Neil Robertson 2003,2004 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ***************************************************************************/ #include "globals.h" #define SRC_PROMPT "\n~IP~FGSave, ~FYRedo, ~FRCancel~RS (s/r/c)?: " /*** Constructor ***/ cl_editor::cl_editor(cl_user *own,int typ, char *path) { struct stat fs; char *str; int fd; error = OK; owner = own; buff = NULL; inc_size = 0; inc_path = NULL; // Can't use the editor when on a remote server since any input the user // enters will go to the remote server and not the local editor. if (owner->server_to) { error = ERR_USER_GONE_REMOTE; return; } type = typ; stage = EDITOR_STAGE_INPUT; bpos = 0; line = 0; malloced = 0; maxchars = 0; maxlines = 0; isbatch = 0; switch(type) { case EDITOR_TYPE_PROFILE: maxchars = max_profile_chars; str = "profile"; break; case EDITOR_TYPE_LOGIN_BATCH: maxlines = max_login_batch_lines; str = "login batch"; isbatch = 1; break; case EDITOR_TYPE_LOGOUT_BATCH: maxlines = max_logout_batch_lines; str = "logout batch"; isbatch = 1; break; case EDITOR_TYPE_SESSION_BATCH: maxlines = max_session_batch_lines; str = "session batch"; isbatch = 1; break; case EDITOR_TYPE_MAIL: maxchars = max_mail_chars; str = "mail"; break; case EDITOR_TYPE_BOARD: maxchars = max_board_chars; str = "board message"; break; case EDITOR_TYPE_GROUP_DESC: maxchars = max_group_desc_chars; str = "group description"; break; case EDITOR_TYPE_BCAST: case EDITOR_TYPE_NET_BCAST: maxchars = max_broadcast_chars; str = "broadcast"; break; default: owner->uprintf("~OL~FRINTERNAL ERROR:~RS Unknown editor type %d in cl_editor::cl_editor()!\n",type); log(1,"INTERNAL ERROR: Unknown editor type %d in cl_editor::cl_editor()!",type); error = ERR_INTERNAL; return; } // Memory map file we wish to include in buffer if (path) { inc_path = strdup(path); if ((fd = open(inc_path,O_RDONLY)) == -1) { error = ERR_CANT_OPEN_FILE; return; } if (fstat(fd,&fs)) { close(fd); error = ERR_CANT_OPEN_FILE; return; } if ((inc_text = (char *)mmap( NULL,fs.st_size,PROT_READ,MAP_SHARED,fd,0)) == MAP_FAILED) { close(fd); error = ERR_MMAP_FAILED; return; } close(fd); inc_size = fs.st_size; if ((error = include_text()) != OK) return; error = OK; } // Batches work in lines if (isbatch) { if (maxlines) owner->uprintf("\n~BB~FG*** Editing %s, maximum of %d lines ***\n\n",str,maxlines); else owner->uprintf("\n~BB~FG*** Editing %s, unlimited lines allowed ***\n\n"); owner->uprintf("Enter a '.' on a line on its own to finish editing.\n"); } else { // Everything else works in characters if (maxchars) owner->uprintf("\n~BB~FG*** Editing %s, maximum of %d characters including newlines ***\n\n",str,maxchars); else owner->uprintf("\n~BB~FG*** Editing %s, unlimited characters allowed ***\n\n",str); owner->uprintf("Enter a '.' on a line on its own to finish editing.\n"); } print_buffer(); } /*** Destructor ***/ cl_editor::~cl_editor() { FREE(buff); owner->editor = NULL; if (inc_path) { // Test inc_size because if error occured on startup memory may // not have been mapped yet. if (inc_size) munmap(inc_text,inc_size); free(inc_path); } } /*** This does the actual business ***/ int cl_editor::run() { cl_splitline *com; int i; com = owner->com; if (stage == EDITOR_STAGE_SRC) { // Choose what to do when done editing if (!com->wcnt || com->word[0][1]) { owner->uprintf(SRC_PROMPT); return OK; } switch(toupper(com->word[0][0])) { case 'S': owner->uprintf("\n~FGEdit complete.\n\n"); stage = EDITOR_STAGE_COMPLETE; return OK; case 'R': FREE(buff); bpos = 0; malloced = 0; line = 0; stage = EDITOR_STAGE_INPUT; if ((error = include_text()) != OK) return error; print_buffer(); return OK; case 'C': owner->uprintf("\n~FREdit cancelled.\n\n"); stage = EDITOR_STAGE_CANCEL; return OK; default: owner->uprintf(SRC_PROMPT); } return OK; } // Input stage. Check if we've finished editing and that user has // entered some input that contains more than whitespace. if (com->wcnt && !strcmp(com->word[0],".")) { if (!malloced) { owner->uprintf("\n~FRNo input, edit cancelled.\n\n"); stage = EDITOR_STAGE_CANCEL; return OK; } for(i=0;i < bpos;++i) if (buff[i] > 32) break; if (i == bpos) { owner->uprintf("\n~FRNo input, edit cancelled.\n\n"); stage = EDITOR_STAGE_CANCEL; return OK; } owner->uprintf(SRC_PROMPT); stage = EDITOR_STAGE_SRC; return OK; } if (append((char *)owner->tbuff) != OK) return ERR_MALLOC; if (append("\n") != OK) return ERR_MALLOC; // Batches are done by line if (isbatch) { if (++line == maxlines) { owner->uprintf(SRC_PROMPT); stage = EDITOR_STAGE_SRC; return OK; } owner->uprintf("~IP~FT%02d>~RS ",line+1); return OK; } // Everything else done by number of characters if (maxchars && bpos >= maxchars) { if (bpos > maxchars) { buff[bpos-1] = '\0'; // Remove newline if (bpos - maxchars > 6) { buff[maxchars+3]='.'; buff[maxchars+4]='.'; buff[maxchars+5]='.'; buff[maxchars+6]='\0'; } owner->uprintf("\n~FYInput too long, truncated from:~RS \"%s\"\n", buff+maxchars); buff[maxchars] = '\0'; } owner->uprintf(SRC_PROMPT); stage = EDITOR_STAGE_SRC; return OK; } owner->uprintf("~IP~FT%03d>~RS ",bpos); return OK; } /*** Append a string into the buffer ***/ int cl_editor::append(char *str) { int len; len = strlen(str); if (bpos + len >= malloced) { malloced = EDITOR_MALLOC * (((bpos + len) / EDITOR_MALLOC) + 1); if (!(buff = (char *)realloc(buff,malloced))) return ERR_MALLOC; if (!bpos) buff[0]='\0'; } strcat(buff + bpos,str); bpos += len; return OK; } /*** Include some text in the buffer prior to in the edit ***/ int cl_editor::include_text() { int len,start_line; char *s; if (!inc_text) return OK; // Count lines for(s=inc_text;*s;++s) line += (*s == '\n'); if (isbatch && maxlines && line > maxlines) return ERR_INCLUDE_FILE_TOO_BIG; len = strlen(inc_text) + line + 1; malloced = ((len / EDITOR_MALLOC) + 1) * EDITOR_MALLOC; if (!(buff = (char *)malloc(malloced))) return ERR_MALLOC; // start_line only used for NON batch files. Ie ones that have max lengths // measured in characters. I know, its confusing. Deal. if (!isbatch) start_line = line > max_include_lines ? line - max_include_lines : 0; do { if (isbatch) bpos = 0; else { buff[0] = '>'; bpos = 1; } // Put included text in buffer for(s=inc_text,line=0;*s;++s) { if (*s == '\n') { line++; // If we don't do this we'll have a lone ">" at // top of edit if (!isbatch && line == start_line) ++s; } if (isbatch || line >= start_line) { // Put '>' at start of each line unless editing // batch file if (!isbatch && *s == '\n' && *(s+1)) { buff[bpos] = '\n'; buff[++bpos] = '>'; } else buff[bpos] = *s; ++bpos; } } /* If bpos has only gone up by 1 (ie user has one big long line which is too long to include and we've simply hit then end newline then get rid of '>' */ if (!isbatch) { if (bpos == 2) bpos=0; start_line++; } buff[bpos] = '\0'; /* If its not a batch include and its too big loop around again and cut off first line. This is for message replies. Theres no point doing the same for a batch file for obvious reasons. Make sure user has at least a third of "maxchars" characters to write his reply in. */ } while(!isbatch && maxchars && bpos >= maxchars - (maxchars / 3)); // Unmap file if (inc_path) { munmap(inc_text,inc_size); FREE(inc_path); } return (!isbatch && maxchars && bpos > maxchars) ? ERR_INCLUDE_FILE_TOO_BIG : OK; } /*** Print prompts & contents of buffer so far. This is for when we have an included message ***/ void cl_editor::print_buffer() { char *s,*s2,c; int i; // Print initial edit prompt if (isbatch) owner->uprintf("\n~IP~FT01>~RS "); else owner->uprintf("\n~IP~FT000>~RS "); if (!bpos) return; // Print buffer and further prompts for(i=0,s2=buff;i < line;++i,s2=s+1) { for(s=s2;*s && *s != '\n';++s); c = *s; *s = '\0'; if (isbatch) owner->uprintf("~NP%s\n~IP~FT%02d>~RS ",s2,i+2); else owner->uprintf("~NP%s\n~IP~FT%03d>~RS ",s2,s - buff + 1); *s = c; if (!c || !*(s+1)) break; } // If included message has hit the or gone beyond limit then go straight // to s/r/c prompt. if ((isbatch && maxlines && line >= maxlines) || (!isbatch && maxchars && bpos >= maxchars)) { owner->uprintf("\n~NP%s",SRC_PROMPT); stage = EDITOR_STAGE_SRC; } }