pkg://jmr-0.7.23-1.src.rpm:177179/jmr-0.7.23.src.tar.gz
info downloads
jmrsrc-0.7.23/ 40755 764 764 0 6623366026 12452 5 ustar jvuokko jvuokko jmrsrc-0.7.23/src/ 40775 764 764 0 6623366026 13243 5 ustar jvuokko jvuokko jmrsrc-0.7.23/src/filelist.cc 100664 764 764 37750 6623366026 15516 0 ustar jvuokko jvuokko // $Id: filelist.cc,v 1.14 1998/11/14 20:40:56 jvuokko Exp $
/****************************************************************************
* *
* * MODULE : filelist.cc
* *
* * Copyright (c) 1997 Jukka Vuokko
* * See file COPYING for more information about copyrights.
* *
****************************************************************************
* *
* *
* * This module contains methods for handling file lists
* * (expand wildcards, select file etc...)
* *
* *
* *
***************************************************************************/
#include <stdio.h> // sprintf()
#ifdef __EMX__
# include <sys/types.h>
#endif
#ifdef __WATCOMC__
# include <direct.h>
#else
# include <dirent.h>
#endif
#include "jmr.hh"
#include "filelist.hh"
#include "../utilib/String.hh"
#include "../utilib/List.hh"
#include "mail.hh"
extern Terminal_screen Screen;
extern settings_t Settings;
extern Window* Wscript;
static const menuinfo filelist_menu_array[] = {
{"[jmr]", 0 },
{"Exit from menu", 0},
{"Quit jmr", FORCED_QUIT_CMD},
{"@NEXT", 0 },
{"@END", 0 }
};
Filelist::Filelist()
{
menubar.init( filelist_menu_array );
menubar.set_text( MENUBAR_TEXT );
for( int i = Screen.get_xmax(); i >= 0; i--) {
dashline += '-';
}
}
/***************************************************************************
* FUNCTION: compare_name
***************************************************************************
*
* DESCRIPTION: compares two strings. This is for qsort().
*
* EXTERNAL REFERENCES
* IN :
* OUT:
*
* RETURN: <0, 0 or >0
***************************************************************************/
int compare_name (const void *a, const void *b)
{
String **aa, **bb;
aa = (String**) a;
bb = (String**) b;
return (*aa)->compare (*(*bb));
}
Mail*
Filelist::open_file(const String& bbsid)
{
Mail *mail;
open_new = false;
int c;
if (bbsid.is_empty()) {
for (;;) {
mail = 0;
if (open_new == true) {
read_files (Settings.qwkpath);
pattern = Settings.searchpattern;
} else {
read_files (Settings.datadir);
pattern = "*.[gG][dD][bB]";
}
// keep only entries, that matches with pattern
search_matches();
c = select ();
Screen.bottom();
Screen.clr_eol();
//Screen.clear();
if (c == QUIT_CMD || c == FORCED_QUIT_CMD) {
break;
} else if (c == LOAD_CMD) {
open_new = open_new == true ? false : true;
} else if (c == SELECT_CMD) {
mail = Mail::create (filelist.get()->get(), open_new);
break;
}
}
} else {
mail = 0;
read_files(Settings.qwkpath);
pattern = bbsid + ".[qQ][wW][kK]";
if (search_matches()) {
mail = Mail::create (filelist.get()->get(), true);
} else {
filelist.destroy();
read_files (Settings.datadir);
pattern = bbsid + ".[gG][dD][bB]";
if (search_matches()) {
mail = Mail::create(filelist.get()->get(), false);
}
}
}
filelist.destroy();
return mail;
}
//**************************************************************************/
// CLASS: Filelist
// MEMBER FUNCTION: read_files
//
//
// DESCRIPTION: Reads all names of files from directory path
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
//**************************************************************************/
void
Filelist::read_files (const String& path)
{
DIR *dir;
struct dirent *ent;
String *str;
filelist.destroy();
if ( (dir = opendir ( path.get() )) == NULL) {
String tmp = "Unable to open mail directory: " + path;
handle_error (tmp.get(), HALT_FL);
}
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] != '.') {
str = new String;
str->put(ent->d_name);
filelist.add (str);
}
}
filelist.sort (compare_name);
if (closedir(dir) != 0)
handle_error("Unable to close mail directory", HALT_FL);
}
//**************************************************************************/
// CLASS: Filelist
// MEMBER FUNCTION: search_matches
//
//
// DESCRIPTION: Removes all files that does not match with pattern from list
//
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS: false if list is empty
//
//**************************************************************************/
bool
Filelist::search_matches()
{
bool rc;
String *str;
if (filelist.first() == false) {
return false;
}
do {
str = filelist.get();
if (check_matches (str->get(), pattern.get()) == false) {
rc = filelist.remove();
} else {
rc = filelist.next();
}
} while (rc == true);
return filelist.first();
}
//**************************************************************************/
// CLASS: Filelist
// MEMBER FUNCTION: select
//
//
// DESCRIPTION: 'User interface'.
//
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS: selected event.
//
//**************************************************************************/
int
Filelist::select()
{
int c;
first = 0;
current = 0;
ypos = ymin = 6;
ymax = Screen.get_ymax() - 5;
lines = ymax - ymin;
++lines;
//Screen.clear();
sline.init( Screen.get_xmax() + 1);
if (open_new == true) {
iline.set_default( PACKET_LIST_KEYS_MSG );
} else {
iline.set_default( DATABASE_LIST_KEYS_MSG );
}
display();
iline.show_default();
while(1) {
Screen.refresh();
c = Screen.get_ch();
iline.reset();
if (c == ESCAPE ) {
iline.set( MENU_MSG );
iline.show();
c = menubar.select();
display();
iline.reset();
}
switch (c) {
case MESSAGEWIN_CMD:
Wscript->enable();
iline.set ( MESSAGES_MSG );
iline.show();
Screen.get_ch();
iline.reset();
Wscript->disable();
break;
case CODE_UP:
case PREV_CMD:
prev();
break;
case CODE_DOWN:
case NEXT_CMD:
next();
break;
case CODE_NPAGE:
case PAGE_DOWN_CMD:
page_down();
break;
case CODE_PPAGE:
case PAGE_UP_CMD:
page_up();
break;
//case CODE_CTRL_L:
//display();
//break;
case CODE_HOME:
first = 0;
current = 0;
ypos = ymin;
display();
break;
case CODE_END:
current = filelist.count_items()-1;
// next line is not a bug! It's the only
// way to calc first index of page...
first = (current / lines) * lines ;
ypos = ymin + (current - first);
display();
break;
case SELECT_CMD:
case CODE_RIGHT:
if (filelist.check() == true) {
return SELECT_CMD;
}
break;
case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
throw quit_exception();
#endif
case QUIT_CMD:
return QUIT_CMD;
case REFRESH_CMD:
return REFRESH_CMD;
case LOAD_CMD:
return LOAD_CMD;
case HELP_CMD:
show_help();
display();
break;
case 0:
break;
default:
iline.set( INVALID_COMMAND_MSG );
};
iline.show();
}
}
//**************************************************************************/
// CLASS: Filelist
// MEMBER FUNCTION: display
//
//
// DESCRIPTION: display files.
//
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS:
//
//**************************************************************************/
void
Filelist::display()
{
int i = 0;
int y;
Screen.erase();
menubar.show();
display_header();
if (open_new == true) {
sline.set_infotext( "List of QWK packets" );
} else {
sline.set_infotext ("List of databases" );
}
if (filelist.first() == false) {
Screen.gotoxy (0, ymin);
if (open_new == true) {
Screen.print( "No files matches with pattern \'%s\' "
"in a directory : \'%s\'",
pattern.c_str(),
Settings.qwkpath.c_str() );
} else {
Screen.print( "No message databases available." );
}
return;
}
while (i < first) {
filelist.next();
++i;
}
i = first;
y = ymin;
do {
Screen.gotoxy (0,y);
if (i == current) {
Screen.inverse();
ypos = y;
}
draw_current();
Screen.reset_attr();
++y;
++i;
} while (filelist.next() == true && y <= ymax);
if (filelist.count_items() <= ymax - ymin) {
sline.set_percent( ALL_PERCENT );
} else if (0 == first) {
sline.set_percent( TOP_PERCENT );
} else {
sline.set_percent( (100*i)/filelist.count_items() );
}
sline.show();
filelist.first();
i = current;
while (i) {
filelist.next();
--i;
}
iline.show( REFRESH_ALL_FL );
Screen.gotoxy (0,ypos);
}
//**************************************************************************/
// CLASS: Filelist
// MEMBER FUNCTION: draw_current
//
//
// DESCRIPTION: Displays name of the current file.
//
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS:
//
//**************************************************************************/
void
Filelist::draw_current()
{
Screen.empty_line();
Screen.goto_x( 8 );
Screen.print( "%s", filelist.get()->c_str() );
}
//**************************************************************************/
// CLASS: Filelist
// MEMBER FUNCTION: display_header
//
//
// DESCRIPTION: Displays header of screen...
//
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS:
//
//**************************************************************************/
void
Filelist::display_header()
{
Screen.reset_attr();
Screen.gotoxy( 0, 1 );
Screen.textcolor( Settings.header_subject_color );
Screen.print( "%s -- A QWK compatible offline mail reader",
Settings.version_str.c_str() );
Screen.textcolor( Settings.header_author_color );
Screen.print("\nCopyright (c) 1997,1998 Jukka Vuokko. All rights reserved.");
Screen.reset_attr();
Screen.print( "\nThis version is compiled at %s" , __DATE__ );
Screen.print( "\nOfficial jmr homepage: http://iki.fi/"
"jvuokko/jmr/");
Screen.gotoxy( 0, 5 );
set_dashline_color();
Screen.print( dashline.c_str() );
Screen.gotoxy( 7, 5 );
if (open_new == true) {
Screen.print( " packets: " );
} else {
Screen.print( " databases: " );
}
Screen.reset_attr ();
}
//**************************************************************************/
// CLASS: Filelist
// MEMBER FUNCTION: next
//
//
// DESCRIPTION: Moves to next file.
//
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS:
//
//**************************************************************************/
void
Filelist::next()
{
if (current < (filelist.count_items()-1)) {
if (ypos == ymax) {
first = ++current;
display();
} else {
Screen.gotoxy( 0, ypos );
Screen.empty_line();
draw_current();
Screen.gotoxy( 0, ypos+1 );
filelist.next();
Screen.inverse();
draw_current ();
Screen.reset_attr();
++current;
++ypos;
}
} else {
iline.set( AT_TOP_MSG );
}
}
//**************************************************************************/
// CLASS: Filelist
// MEMBER FUNCTION: prev
//
//
// DESCRIPTION: Moves to previous file.
//
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS:
//
//**************************************************************************/
void
Filelist::prev()
{
if (current > 0) {
if (ypos == ymin && first > 0) {
--current;
first -= lines;
display();
} else {
Screen.gotoxy( 0, ypos );
Screen.empty_line();
draw_current ();
Screen.gotoxy( 0, ypos-1);
Screen.inverse();
filelist.prev();
draw_current ();
Screen.reset_attr();
--current;
--ypos;
}
} else {
iline.set( AT_BOTTOM_MSG );
}
}
void
Filelist::page_down()
{
int max = filelist.count_items()-1;
if (current >= max) {
iline.set( AT_LAST_PAGE_MSG );
return;
}
if (ypos == ymax) {
first = ++current;
for (int i=1; i < lines && current < max; i++, current++);
ypos = ymin + (current - first);
} else {
while (current < max && ypos < ymax) {
++current;
++ypos;
}
}
display();
}
void
Filelist::page_up()
{
int i = lines;
if (current == 0) {
iline.set( AT_FIRST_PAGE_MSG );
return;
}
if (ypos == ymin) {
while (i && first > 0) {
--first;
--current;
--i;
}
} else {
while (ypos > ymin) {
--ypos;
--current;
}
}
display();
}
jmrsrc-0.7.23/src/groupselect.cc 100664 764 764 12710 6623366026 16224 0 ustar jvuokko jvuokko // $Id: groupselect.cc,v 1.4 1997/11/17 17:16:59 jvuokko Exp $
/*****************************************************************************
* *
* * MODULE: groupselect.cc
* * --------------
* ***************************************************************************
* *
* *
* * COPYRIGHT (C) 1997 JUKKA VUOKKO. ALL RIGHTS RESERVED
* ***************************************************************************
* *
* * This module contains functions for selecting group from
* * list of all groups.
* *
*****************************************************************************/
#include "groupselect.hh"
#include "mail.hh"
extern Terminal_screen Screen;
extern Mail *mail;
extern settings_t Settings;
Groupselection::Groupselection()
{
grpnode *gnode;
int savegrp;
win = Screen.newwin( 0, 0, Screen.get_xmax()+1, Screen.get_ymax(),
WIN_SLINE | WIN_TITLEBAR);
win->set_sline_text("* Available groups *");
if (Screen.is_color_term() == true ) {
win->set_border_colors( Settings.sline_color,
Settings.slineback_color);
} else {
win->set_border_colors( INVERSE, INVERSE );
}
win->disable();
savegrp = mail->get_group_number();
mail->first_group();
do {
if (mail->get_group_number() >= BASEGROUP_NUMBER) {
continue;
}
gnode = new grpnode;
gnode->number = mail->get_group_number();
gnode->name = mail->get_group_name();
itemlist.add( gnode );
DEBUG("Added to grouplist: " << gnode->name );
} while (mail->next_group() == true );
mail->go_group_number( savegrp );
}
Groupselection::~Groupselection()
{
Screen.endwin( win );
}
grpnode
Groupselection::select()
{
bool quit = false;
grpnode selected;
selected.number = mail->get_group_number();
selected.name = mail->get_group_name();
int savedgrp = selected.number;
ymin = 0;
ymax = win->get_ymax();
current_item = 0;
pagesize = ymax - ymin + 1;
maxitem = itemlist.count_items();
pages = 1+ (maxitem / pagesize);
--maxitem;
// go to current group
itemlist.first();
do {
if ( itemlist.get()->number == selected.number ) {
break;
}
} while( itemlist.next() == true && ++current_item );
current_page = current_item / pagesize;
first_of_page = current_page * pagesize;
ypos = ymin + (current_item - first_of_page );
win->enable();
iline.set_default("@ENTER@=select [@n@]ext [@p@]rev [@q@]uit" );
iline.show_default();
win->clear();
draw_page();
do {
int c = Screen.get_ch();
iline.reset();
switch( c ) {
case PAGE_UP_CMD:
case CODE_PPAGE:
if ( false == ppage() ) {
iline.set( AT_FIRST_PAGE_MSG );
}
break;
case PAGE_DOWN_CMD:
case CODE_NPAGE:
if( false == npage() ) {
iline.set( AT_LAST_PAGE_MSG );
}
break;
case CODE_HOME:
first();
break;
case CODE_END:
last();
break;
case NEXT_CMD:
case CODE_DOWN:
if( false == down() ) {
iline.set( AT_BOTTOM_MSG );
}
break;
case PREV_CMD:
case CODE_UP:
if( false == up() ) {
iline.set( AT_TOP_MSG );
}
break;
case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
throw quit_exception();
#else
Quit_flag = true;
// no break here!!!
#endif
case QUIT_CMD:
quit = true;
break;
case SELECT_CMD:
selected = *itemlist.get();
quit = true;
break;
default:
iline.set( INVALID_COMMAND_MSG );
break;
}
iline.show();
} while ( quit == false );
mail->go_group_number( savedgrp );
win->disable();
return selected;
}
void
Groupselection::draw_item( int flag )
{
String tmp;
grpnode *grp = itemlist.get();
win->reset_attr();
if (flag & EMPHASIZED_FL) {
win->inverse();
}
win->clr_line();
win->goto_x (1);
win->print( "%.3d", current_item );
win->goto_x (10);
win->print( "Group no: %d", grp->number );
win->goto_x (28);
if (!(flag & EMPHASIZED_FL)) {
win->textcolor (Settings.subject_color);
}
win->addstr( grp->name.c_str() );
win->reset_attr();
}
jmrsrc-0.7.23/src/jmr.cc 100664 764 764 201164 6623366026 14503 0 ustar jvuokko jvuokko // $Id: jmr.cc,v 1.43 1998/11/14 20:42:39 jvuokko Exp $
/****************************************************************************
* *
* * MODULE : jmr.cc
* *
* **************************************************************************
* *
* * This file is part of jmr offline mail reader.
* * Copyright (C) 1997,1998 Jukka Vuokko <jvuokko@iki.fi>
* *
* * 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.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program; if not, write to the Free Software
* * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *
* **************************************************************************
* *
* * Main program and signal handling
* *
***************************************************************************/
#include <new.h> // set_new_handler()
#include <stdio.h> // sprintf(), rand(), srand()
#ifdef __WATCOMC__
#include <io.h> // access()
#endif
#include <stdlib.h> // EXIT_FAILURE, EXIT_SUCCESS
#include <signal.h>
#include <time.h> // time()
#ifdef __WATCOMC__
# include <direct.h>
#elif defined (__EMX__)
# include <unistd.h> // F_OK
# include <sys/types.h>
# include <dirent.h>
#else
# include <unistd.h> // F_OK
# include <dirent.h>
#endif
#include "jmr.hh"
#include "menu.hh"
#include "mail.hh"
#include "filelist.hh"
#include "tree.hh"
/*
* Defaults for a resources
*/
const char DEFAULT_PACK_CMD[] = "zip -jkq";
const char DEFAULT_UNPACK_CMD[] = "unzip -oLqq";
const char DEFAULT_QUOTEPREFIX[] = "> ";
const char DEFAULT_WILDCARD[] = "*.[qQ]*";
const char DEFAULT_PTAG[] = "";
const char DEFAULT_TAG[] = "";
const char DEFAULT_FULL_SYSNAME[] = "no";
const bool DEFAULT_CONFIRM_RETURN = true;
const int DEFAULT_REPLYLOGSIZE = 200;
const int DEFAULT_DATABASESIZE = 5000;
const int DEFAULT_QUOTECOLOR = GREEN;
const int DEFAULT_BODYCOLOR = CYAN;
const int DEFAULT_MENU_COLOR = WHITE | BOLD;
const int DEFAULT_MENU_BCOLOR = BLUE | BOLD;
const int DEFAULT_SLINE_COLOR = YELLOW | BOLD;
const int DEFAULT_SLINE_BCOLOR = BLUE | BOLD;
const int DEFAULT_DASHLINE_COLOR = DEFAULT_FCOLOR;
const int DEFAULT_DASHLINE_BCOLOR = DEFAULT_BCOLOR;
const int DEFAULT_BASEGROUP_COLOR = GREEN | BOLD;
const int DEFAULT_SUBJECT_COLOR = GREEN;
const int DEFAULT_AUTHOR_COLOR = CYAN;
const int DEFAULT_ILINE_COLOR = DEFAULT_FCOLOR;
const int DEFAULT_ILINE_ERRCOLOR = RED | BOLD;
const int DEFAULT_HEADER_SUBJECT_COLOR = BOLD;
const int DEFAULT_HEADER_AUTHOR_COLOR = GREEN;
const int DEFAULT_PERSONAL_COLOR = YELLOW | BOLD;
const char DEFAULT_FOLLOWUPSTR[] = "In article <%m> %n wrote:";
const char DEFAULT_REPLYSTR[] = "On a message dated %d you wrote:";
const char DEFAULT_DATESTR[] = "%D.%M.%Y";
const char DEFAULT_WRAPIND = '>';
const char DEFAULT_RECEIVER[] = "";
const char DEFAULT_FOLDER[] = "";
const char DEFAULT_TAGLINE_PROGRAM[] = "";
const char DEFAULT_SIGNATUREFILE[] = "";
const bool DEFAULT_RMOLDPACKET = RM_OLD_NO;
#ifdef F_TO_ALL
const String DEFAULT_FOLLOWUPTO = FUT_WRITER;
#endif
const char DEFAULT_QUOTECHARS[] = ":>%&!/#";
const int DEFAULT_QUOTEDEPTH = 4;
#ifdef __unix__
const char DEFAULT_EDITOR[] = "/usr/bin/jed";
#else
const char DEFAULT_EDITOR[] = "jed.exe";
#endif
//**************************************************************************/
//
// CLASS: Jmrmain
//
//
// DESCRIPTION: Methods for handling mainloop of program.
//
//**************************************************************************/
class Jmrmain {
public:
Jmrmain (int ac, char **av);
~Jmrmain(){};
private:
void init();
void init_paths();
void init_defaults();
void init_settings();
void parse_arguments ();
void init_extractdir();
void get_jmrdir();
void check_jmrdir();
void create_rc_file (char *file);
void read_lines ();
bool parse_rcline (String&, int);
void mainloop();
bool check_argument (int&);
void show_help();
void set_textcolor_value (int& c, String& value);
//void set_backcolor_value (int&, String& );
bool init_all; // if this is true, then write new rc-file
int argc; // number of items in commandline
char **argv; // command parameters
Filelist filelist; // qwk-packet selection level
Groupmenu *groupmenu; // group selection level
String resource_file; // name of resource file
key_tree keywords; // Syntax tree for resources
// Extra variables by Yka
String open_jmr; // Name of packet or database to open.
// If empty assume normal jmr behaviour
};
/************************ GLOBAL VARIABLES ***********************************/
Terminal_screen Screen;
settings_t Settings;
Mail* mail;
Window *Wscript;
#ifdef NO_EXCEPTIONS
bool Quit_flag = false;
#endif
// This array defines rules for keywords in a resource file.
// "*" means that the keyword must have a value.
// "" a value of the keyword can be empty.
// "$" a value must be a number.
//
// Format of an array:
// Keyword, rule1, rule2, ruleN,
// "@NEXT",
// .
// .
// .
// "@NEXT", "@END"
static const char *rc_values[] = {
RC_MAILPATH,"*",
"@NEXT",
RC_REPLYPATH,"*",
"@NEXT",
RC_QUOTEPREFIX,"*",
"@NEXT",
RC_EDITOR, "*",
"@NEXT",
RC_UNPACK, "*",
"@NEXT",
RC_PACK, "*",
"@NEXT",
RC_SYSCHARSET, "latin1", "ibmpc", "7bit",
"@NEXT",
RC_BBSCHARSET, "latin1", "ibmpc", "7bit",
"@NEXT",
RC_WILDCARD, "*",
"@NEXT",
RC_PTAG, "",
"@NEXT",
RC_EXTRACTDIR, "",
"@NEXT",
RC_REPLYLOGSIZE, "$",
"@NEXT",
RC_DATABASESIZE, "$",
"@NEXT",
RC_QUOTECOLOR, COLOR_VALUES,
"@NEXT",
RC_BODYCOLOR, COLOR_VALUES,
"@NEXT",
RC_WRAPMODE, "yes", "no",
"@NEXT",
RC_FOLLOWUPSTR, "",
"@NEXT",
RC_REPLYSTR, "",
"@NEXT",
RC_TAG, "",
"@NEXT",
RC_FULL_SYSNAME, "yes", "no",
"@NEXT",
RC_CONFIRM_RETURN, "yes", "no",
"@NEXT",
RC_DATESTR, "*",
"@NEXT",
RC_MENU_COLOR, COLOR_VALUES,
"@NEXT",
RC_MENU_BCOLOR, COLOR_VALUES,
"@NEXT",
RC_SLINE_COLOR, COLOR_VALUES,
"@NEXT",
RC_SLINE_BCOLOR, COLOR_VALUES,
"@NEXT",
RC_DASHLINE_COLOR, COLOR_VALUES,
"@NEXT",
RC_DASHLINE_BCOLOR, COLOR_VALUES,
"@NEXT",
RC_SUBJECT_COLOR, COLOR_VALUES,
"@NEXT",
RC_AUTHOR_COLOR, COLOR_VALUES,
"@NEXT",
RC_ILINE_COLOR, COLOR_VALUES,
"@NEXT",
RC_ILINE_ERROR_COLOR, COLOR_VALUES,
"@NEXT",
RC_HEADER_SUBJECT_COLOR, COLOR_VALUES,
"@NEXT",
RC_BASEGROUP_COLOR, COLOR_VALUES,
"@NEXT",
RC_HEADER_AUTHOR_COLOR, COLOR_VALUES,
"@NEXT",
RC_PERSONAL_COLOR, COLOR_VALUES,
"@NEXT",
RC_DEFAULT_RECEIVER,"",
"@NEXT",
RC_DEFAULT_FOLDER,"",
"@NEXT",
RC_TAGLINE_PROGRAM,"",
"@NEXT",
RC_SIGNATUREFILE,"",
"@NEXT",
RC_RMOLDPACKET, "yes", "no","ask",
"@NEXT",
// IV
RC_QUOTECHARS, "",
"@NEXT",
RC_QUOTEDEPTH, "$",
"@NEXT",
#ifdef F_TO_ALL
RC_FOLLOWUPTO, "*",
"@NEXT",
#endif
"@END"
};
/************* PROTOTYPES FOR FUNCTIONS IN THIS MODULE ***********************/
void memory_error_handler();
/***************************************************************************
* FUNCTION: main
***************************************************************************
*
* DESCRIPTION: main function of the jmr.
*
* EXTERNAL REFERENCES
* IN :
* OUT: Settings.currentdir, Screen
*
* RETURN : EXIT_SUCCESS
***************************************************************************/
int main(int argc, char** argv)
{
signal (SIGINT, die);
signal (SIGTERM, die);
#ifndef __WATCOMC__
signal (SIGKILL, die);
#endif
#ifdef __unix__
signal (SIGTSTP, suspend);
#endif
set_new_handler (memory_error_handler);
set_misc_lib_error_handler( fatal_error );
Settings.currentdir[0] = '\0';
// initialize random generator
srand( (unsigned int) time( NULL ) );
int h = Screen.get_ymax();
if ( h < 19 || Screen.get_xmax() < 79 ) {
cerr << "Required size of the terminal is 20x80.\n";
cerr << "Your terminal has " << h+1 << " lines and "
<< Screen.get_xmax()+1 << " columns." << endl;
exit( EXIT_FAILURE );
}
Wscript = Screen.newwin( 0, 1, Screen.get_xmax()+1, h - 1,
WIN_SLINE);
Wscript->set_border_colors( DEFAULT_FCOLOR| INVERSE,
DEFAULT_BCOLOR);
Wscript->set_sline_text("* Message Buffer *");
Jmrmain jmrmain (argc, argv);
return EXIT_SUCCESS;
}
/************************************************************************
* FUNCTION : die
************************************************************************
*
* If SIGTERM, SIGKILL or SIGINT signal received, this procedure tries to
* save all necessary data before exit.
*
* EXTERNAL VARIABLE REFERENCES:
* IN : mail
* OUT:
*
* Parameters:
* int sig signal
*
***********************************************************************/
void
die(int sig)
{
//Screen.bottom();
Screen.enable();
Wscript->enable();
Wscript->print( "\n\nTerminate signal (%d) received.\n", sig);
Screen.set_origmode();
if (mail == 0) {
exit (EXIT_FAILURE);
}
if (mail->have_replies() == true) {
Wscript->print( "Emergency exit. Storing new replies...\n");
Screen.refresh();
mail->emergency_exit();
}
exit (EXIT_FAILURE);
}
/***************************************************************************
* FUNCTION: memory_error_handler
***************************************************************************
*
* Function for handling memory errors. The new[]-operator calls this if
* memory is exceed.
*
* EXTERNAL VARIABLE REFERENCES:
* IN :
* OUT:
*
* RETURN:
***************************************************************************/
void
memory_error_handler()
{
handle_error (NO_MEMORY_ERROR, HALT_FL);
}
/***************************************************************************
* FUNCTION: suspend
***************************************************************************
*
* DESCRIPTION: Caughts SIGTSTP-signal (^Z).
*
* EXTERNAL REFERENCES
* IN : Screen
* OUT:
*
***************************************************************************/
#ifdef __unix__
void
suspend(int )
{
sigset_t mask;
sigemptyset( &mask );
sigaddset( &mask, SIGTSTP );
Screen.set_origmode();
sigprocmask( SIG_UNBLOCK, &mask, NULL );
signal (SIGTSTP, SIG_DFL);
kill( getpid(), SIGTSTP );
signal( SIGTSTP, suspend );
Screen.set_rawmode();
Screen.refresh( REDRAW_ALL );
}
#endif
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: Jmrmain
//
// DESCRIPTION: Main funtion of class.
//
//
// EXTERNAL REFERENCES
// IN : Settings, Screen
// OUT: Quit_flag
//
// RETURNS:
//**************************************************************************/
Jmrmain::Jmrmain(int ac, char** av)
{
argc = ac;
argv = av;
init_all = false;
open_jmr = "";
#ifdef NO_EXCEPTIONS
Quit_flag = false;
#endif
parse_arguments();
Screen.enable();
Wscript->enable();
init();
if (Screen.is_color_term()) {
Wscript->set_border_colors( Settings.sline_color,
Settings.slineback_color );
}
// change to working directory
if (change_dir (Settings.workdir.get())) {
char buf[1024];
sprintf (buf, "Cannot change to direcetory : %s",
Settings.workdir.get());
handle_error (buf, HALT_FL);
}
mainloop();
Wscript->enable();
// back to original directory
change_dir (Settings.currentdir);
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: mainloop
//
// DESCRIPTION: Main loop of program
//
//
// EXTERNAL REFERENCES
// IN : Settings, mail, Quit_flag
// OUT: mail
//
//**************************************************************************/
void
Jmrmain::mainloop()
{
bool open_new;
String tmppath;
loop:
open_new = false;
mail = 0;
if (ACCESS (DEADJMR_FILENAME, F_OK) == 0) {
Wscript->enable();
Wscript->print( "\nPrevious session was killed and replies was"
"wrote to recovery file.\n" );
Wscript->print( "Continue killed session with these "
"replies? (y/n)? y" );
Screen.refresh();
if (get_yesno('y') == 'n') {
remove (DEADJMR_FILENAME);
} else if (0 == (mail = Mail::open_dead())) {
handle_error("Cannot open recovery file.",
REPORT_FL);
}
}
if (mail == 0) {
if (open_jmr.is_empty()) {
Wscript->disable();
mail = filelist.open_file();
if (mail == 0) {
return;
}
} else {
Wscript->disable();
mail = filelist.open_file(open_jmr);
// skip interactive mode
if (!mail) {
return;
}
}
}
Wscript->disable();
#ifdef NO_EXCEPTIONS
groupmenu = new Groupmenu();
#else
try {
groupmenu = new Groupmenu();
}
catch( quit_exception ) {
Wscript->enable();
Wscript->reset_attr();
Wscript->textcolor( BOLD );
Wscript->print("\n'Quit immediately' command received!");
Wscript->reset_attr();
Screen.refresh();
delete mail;
Wscript->print( "\nCleaning working directory..." );
Screen.refresh();
clear_workdir();
Wscript->print("OK");
return;
}
#endif
Wscript->enable();
switch (Settings.rmoldpacket)
{
case RM_OLD_YES :
if ( ! mail->get_packet_name().is_empty() )
{
Screen.refresh();
tmppath = Settings.qwkpath;
correct_path( tmppath );
tmppath += mail->get_packet_name();
Wscript->print( "\nRemoving old packet..." );
Wscript->print( mail->get_packet_name() );
rm_files( tmppath );
Wscript->print(" OK");
}
break;
case RM_OLD_NO : break;
case RM_OLD_ASK :
if ( ! mail->get_packet_name().is_empty() )
{
tmppath = Settings.qwkpath;
correct_path( tmppath );
Wscript->print( "\nDo you want to delete packet ");
Wscript->print( mail->get_packet_name() );
Wscript->print( " (y/n)? n" );
Screen.refresh();
if (get_yesno('n') == 'y') {
Wscript->print( "\nRemoving old packet..." );
tmppath += mail->get_packet_name();
rm_files( tmppath );
Wscript->print("OK");
}
}
break;
}
delete groupmenu;
delete mail;
Wscript->enable();
Wscript->print( "\nCleaning working directory..." );
Screen.refresh();
clear_workdir();
Wscript->print("OK");
#ifdef NO_EXCEPTIONS
if (Quit_flag == true || !open_jmr.is_empty()) { // --open-jmr means immediate quit after reading.
return;
}
#endif
goto loop;
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: init
//
// DESCRIPTION: Reads contents of a resource file and initializes Settings.
//
//
// EXTERNAL REFERENCES
// IN : rc_values[]
// OUT: Settings
//
// RETURNS:
//**************************************************************************/
void Jmrmain::init()
{
Wscript->enable();
Wscript->print( "\n*** jmr %s (alpha) ***", VERSION );
Wscript->print( " (compiled@ %s )\n", __DATE__ );
Screen.refresh();
#ifdef __unix__
// check for color capable terminal
char *cterm;
if ( (cterm = getenv( "COLORTERM" )) ) {
Screen.force_colormode();
Wscript->print( "\n\nColor capable terminal '%s' detected!\n",
cterm );
Screen.refresh();
}
#endif /* __unix__ */
keywords.init( rc_values );
init_paths();
read_lines();
init_defaults();
init_settings();
init_extractdir();
if( Settings.full_sysname == "no" ) {
Settings.system = get_system_info();
} else {
Settings.system = get_system_info( ALL_FL );
}
Settings.version_str = "jmr " VERSION " ";
Settings.version_str += Settings.system;
Settings.tag = "* " + Settings.version_str + " * ";
// set up wrap indicator
switch ( Settings.syscharset ) {
case ISO_LATIN_CHARSET:
Settings.wrap_indicator = 0xbb;
break;
case IBM_PC_CHARSET:
Settings.wrap_indicator = 175;
break;
default:
Settings.wrap_indicator = DEFAULT_WRAPIND;
};
Wscript->enable();
Wscript->print( "Resource file is OK" );
Screen.refresh();
DEBUG("---------- SETTINGS ---------");
DEBUG("zipcmd ="<<Settings.zipcmd);
DEBUG("unzipcmd ="<<Settings.unzipcmd);
DEBUG("qwkpath ="<<Settings.qwkpath);
DEBUG("replypath ="<<Settings.replypath);
DEBUG("quoteprefix ="<<Settings.quoteprefix);
DEBUG("searchpattern ="<<Settings.searchpattern);
DEBUG("ptagline ="<<Settings.ptagline);
DEBUG("tagline ="<<Settings.tagline);
DEBUG("replylogsize ="<<Settings.replylogsize);
DEBUG("datebasesize ="<<Settings.databasesize);
DEBUG("extractdir ="<<Settings.extractdir);
DEBUG("quotecolor ="<<Settings.quote_color);
DEBUG("textcolor ="<<Settings.article_color);
DEBUG("followup ="<<Settings.followup);
DEBUG("reply ="<<Settings.reply);
DEBUG("syscharset ="<<Settings.syscharset);
DEBUG("bbscharset ="<<Settings.bbscharset);
DEBUG("menucolor =" <<Settings.menu_color);
DEBUG("menuback = "<<Settings.menuback_color);
DEBUG("slinecolor = "<<Settings.sline_color);
DEBUG("slineback = "<<Settings.slineback_color);
DEBUG("dashcolor = "<<Settings.dash_color);
DEBUG("dashback = "<<Settings.dashback_color);
DEBUG("subjectcolor = "<<Settings.subject_color);
DEBUG("ilinecolor = "<<Settings.iline_color);
DEBUG("default_receiver = "<<Settings.default_receiver);
DEBUG("signaturefile = " << Settings.signaturefile );
DEBUG("taglinre = " << Settings.tagliner );
DEBUG("rmoldpacket = " << (Settings.rmoldpacket ? Settings.rmoldpacket == RM_OLD_YES ? "yes" : "ask" : "no") );
#ifdef F_TO_ALL
DEBUG("followupto = " << Settings.followupto );
#endif
DEBUG("quotechars = " << Settings.quotechars );
DEBUG("quotedepth = " << Settings.quotedepth );
DEBUG("-------- END OF SETTINGS ----");
}
//**************************************************************************/
// CLASS: jmrmain
// MEMBER FUNCTION: init_defaults
//**************************************************************************/
//
// This function initializes contents of Settings structure with a
// default values.
//
// NOTE: All paths must be initialised (init_paths()) before using this.
//
// EXTERNAL VARIABLE REFERENCES
// IN : Settings.currentdir
// OUT: Settings
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void Jmrmain::init_defaults()
{
Settings.zipcmd = DEFAULT_PACK_CMD;
Settings.unzipcmd = DEFAULT_UNPACK_CMD;
Settings.qwkpath = Settings.currentdir;
correct_path( Settings.qwkpath );
Settings.replypath = Settings.currentdir;
correct_path( Settings.replypath );
Settings.quoteprefix = DEFAULT_QUOTEPREFIX;
Settings.searchpattern = DEFAULT_WILDCARD;
Settings.ptagline = DEFAULT_PTAG;
Settings.tagline = DEFAULT_TAG;
Settings.full_sysname = DEFAULT_FULL_SYSNAME;
Settings.confirm_return = DEFAULT_CONFIRM_RETURN;
Settings.replylogsize = DEFAULT_REPLYLOGSIZE;
Settings.databasesize = DEFAULT_DATABASESIZE;
Settings.extractdir = ""; // this is initialized later
Settings.max_subject_len = MAX_SUBJECT_LEN;
Settings.max_bbs_subject_len = MAX_SUBJECT_LEN;
Settings.is_pcboard = false;
Settings.quote_color = DEFAULT_QUOTECOLOR;
Settings.article_color = DEFAULT_BODYCOLOR;
Settings.followup = DEFAULT_FOLLOWUPSTR;
Settings.reply = DEFAULT_REPLYSTR;
Settings.date_fmt = DEFAULT_DATESTR;
Settings.editor = DEFAULT_EDITOR;
Settings.menu_color = DEFAULT_MENU_COLOR;
Settings.menuback_color = DEFAULT_MENU_BCOLOR;
Settings.sline_color = DEFAULT_SLINE_COLOR;
Settings.slineback_color = DEFAULT_SLINE_BCOLOR;
Settings.dash_color = DEFAULT_DASHLINE_COLOR;
Settings.dashback_color = DEFAULT_DASHLINE_BCOLOR;
Settings.subject_color = DEFAULT_SUBJECT_COLOR;
Settings.author_color = DEFAULT_AUTHOR_COLOR;
Settings.iline_color = DEFAULT_ILINE_COLOR;
Settings.iline_err_color = DEFAULT_ILINE_ERRCOLOR;
Settings.header_subject_color = DEFAULT_HEADER_SUBJECT_COLOR;
Settings.basegroup_color = DEFAULT_BASEGROUP_COLOR;
Settings.personal_color = DEFAULT_PERSONAL_COLOR;
Settings.header_author_color = DEFAULT_HEADER_AUTHOR_COLOR;
Settings.is_wrapmode = false;
Settings.default_receiver = DEFAULT_RECEIVER;
Settings.default_folder = DEFAULT_FOLDER;
Settings.tagliner = DEFAULT_TAGLINE_PROGRAM;
Settings.signaturefile = DEFAULT_SIGNATUREFILE;
Settings.rmoldpacket = DEFAULT_RMOLDPACKET;
#ifdef F_TO_ALL
Settings.followupto = DEFAULT_FOLLOWUPTO;
#endif
Settings.quotechars = DEFAULT_QUOTECHARS;
Settings.quotedepth = DEFAULT_QUOTEDEPTH;
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: init_settings
//
// DESCRIPTION: Initializes settings with a values readed from
// a resource file.
//
//
// EXTERNAL REFERENCES
// IN :
// OUT: Settings
//
// RETURNS:
//**************************************************************************/
void Jmrmain::init_settings()
{
String value;
String tmp;
int errors = 0;
// first init required resources
value = keywords.get_value( RC_SYSCHARSET );
if (value.is_empty()) {
++errors;
tmp = "Resource \'";
tmp += RC_SYSCHARSET;
tmp += "\' not found";
handle_error(tmp.get(), REPORT_FL );
} else {
if (value == "ibmpc") {
Settings.syscharset = IBM_PC_CHARSET;
} else if (value == "latin1") {
Settings.syscharset = ISO_LATIN_CHARSET;
} else {
Settings.syscharset = ASCII_7BIT_CHARSET;
}
}
value = keywords.get_value( RC_BBSCHARSET );
if (value.is_empty()) {
++errors;
tmp = "Resource \'";
tmp += RC_BBSCHARSET;
tmp += "\' not found";
handle_error( tmp.get(), REPORT_FL );
} else {
if (value == "ibmpc") {
Settings.bbscharset = IBM_PC_CHARSET;
} else if (value == "latin1") {
Settings.bbscharset = ISO_LATIN_CHARSET;
} else {
Settings.bbscharset = ASCII_7BIT_CHARSET;
}
}
#ifdef F_TO_ALL
value = keywords.get_value( RC_FOLLOWUPTO );
if ( value.is_empty() == false ) {
Settings.followupto = value;
} else {
++errors;
tmp = "Resource \'";
tmp += RC_FOLLOWUPTO;
tmp += "\' not found";
handle_error( tmp.get(), REPORT_FL );
}
#endif
if (errors) {
handle_error("All required resources are not defined!",
HALT_FL);
}
// if resource is defined in file, then override default
value = keywords.get_value( RC_EDITOR );
if (value.is_empty() == false) {
Settings.editor = value;
}
value = keywords.get_value( RC_PACK );
if (value.is_empty() == false) {
Settings.zipcmd = value;
}
value = keywords.get_value( RC_UNPACK );
if (value.is_empty() == false) {
Settings.unzipcmd = value;
}
value = keywords.get_value( RC_MAILPATH );
if (value.is_empty() == false) {
Settings.qwkpath = value;
correct_path( Settings.qwkpath );
}
value = keywords.get_value( RC_REPLYPATH );
if (value.is_empty() == false) {
Settings.replypath = value;
correct_path( Settings.replypath );
}
value = keywords.get_value( RC_QUOTEPREFIX );
if (value.is_empty() == false) {
value.replace_ch( '_', ' ' );
Settings.quoteprefix = value;
}
value = keywords.get_value( RC_WILDCARD );
if (value.is_empty() == false) {
Settings.searchpattern = value;
}
value = keywords.get_value( RC_WRAPMODE );
if ( value.is_empty() == false ) {
Settings.is_wrapmode = value == "yes" ? true : false;
}
value = keywords.get_value( RC_PTAG );
if (value.is_empty() == false) {
Settings.ptagline = value;
}
value = keywords.get_value( RC_TAG );
if (value.is_empty() == false) {
Settings.tagline = value;
}
value = keywords.get_value( RC_FULL_SYSNAME );
if (value.is_empty() == false) {
Settings.full_sysname = value;
}
value = keywords.get_value( RC_CONFIRM_RETURN );
if (value.is_empty() == false) {
// Settings.confirm_return = value == "yes" ? true : false;
Settings.confirm_return = (value == "yes");
}
value = keywords.get_value( RC_REPLYLOGSIZE );
if (value.is_empty() == false) {
Settings.replylogsize = atoi( value.get() );
}
value = keywords.get_value( RC_DATABASESIZE );
if (value.is_empty() == false) {
Settings.databasesize = atoi( value.get() );
}
value = keywords.get_value( RC_EXTRACTDIR );
if (value.is_empty() == false) {
correct_path( value );
Settings.extractdir = value;
}
value = keywords.get_value( RC_QUOTECOLOR );
set_textcolor_value (Settings.quote_color, value);
value = keywords.get_value( RC_BODYCOLOR );
set_textcolor_value (Settings.article_color, value);
value = keywords.get_value( RC_FOLLOWUPSTR );
if (value.is_empty() == false) {
Settings.followup = value;
}
value = keywords.get_value( RC_REPLYSTR );
if (value.is_empty() == false) {
Settings.reply = value;
}
value = keywords.get_value( RC_DATESTR );
if (value.is_empty() == false) {
Settings.date_fmt = value;
}
value = keywords.get_value( RC_MENU_COLOR );
set_textcolor_value (Settings.menu_color, value);
value = keywords.get_value( RC_MENU_BCOLOR );
set_textcolor_value( Settings.menuback_color, value );
value = keywords.get_value( RC_SLINE_COLOR );
set_textcolor_value (Settings.sline_color, value);
value = keywords.get_value( RC_SLINE_BCOLOR );
set_textcolor_value( Settings.slineback_color, value );
value = keywords.get_value( RC_DASHLINE_COLOR );
set_textcolor_value (Settings.dash_color, value);
value = keywords.get_value( RC_DASHLINE_BCOLOR );
set_textcolor_value( Settings.dashback_color, value );
value = keywords.get_value( RC_SUBJECT_COLOR );
set_textcolor_value (Settings.subject_color, value);
value = keywords.get_value( RC_AUTHOR_COLOR );
set_textcolor_value (Settings.author_color, value);
value = keywords.get_value( RC_ILINE_COLOR );
set_textcolor_value (Settings.iline_color, value);
value = keywords.get_value( RC_ILINE_ERROR_COLOR );
set_textcolor_value (Settings.iline_err_color, value);
value = keywords.get_value( RC_BASEGROUP_COLOR );
set_textcolor_value (Settings.basegroup_color, value);
value = keywords.get_value( RC_HEADER_SUBJECT_COLOR );
set_textcolor_value (Settings.header_subject_color, value);
value = keywords.get_value( RC_HEADER_AUTHOR_COLOR );
set_textcolor_value (Settings.header_author_color, value);
value = keywords.get_value( RC_PERSONAL_COLOR );
set_textcolor_value (Settings.personal_color, value);
value = keywords.get_value( RC_DEFAULT_RECEIVER );
if (value.is_empty() == false) {
Settings.default_receiver = value;
}
value = keywords.get_value( RC_DEFAULT_FOLDER );
if (value.is_empty() == false) {
Settings.default_folder = value;
}
value = keywords.get_value( RC_SIGNATUREFILE );
if ( value.is_empty() == false ) {
Settings.signaturefile = value;
expand_path( Settings.signaturefile );
}
value = keywords.get_value( RC_TAGLINE_PROGRAM );
if ( value.is_empty() == false ) {
Settings.tagliner = value;
expand_path( Settings.tagliner );
}
value = keywords.get_value( RC_RMOLDPACKET );
if ( value.is_empty() == false ) {
if (value == "yes")
Settings.rmoldpacket = RM_OLD_YES;
else if (value == "no")
Settings.rmoldpacket = RM_OLD_NO;
else if (value == "ask")
Settings.rmoldpacket = RM_OLD_ASK;
else {
tmp = "Resource \'";
tmp += RC_RMOLDPACKET;
tmp += "\' is terribly wrong";
handle_error( tmp.get(), HALT_FL );
}
}
value = keywords.get_value( RC_QUOTECHARS );
if ( ! value.is_empty() )
Settings.quotechars = value;
value = keywords.get_value( RC_QUOTEDEPTH );
if ( ! value.is_empty() )
Settings.quotedepth = atoi(value.get());
}
void
Jmrmain::set_textcolor_value (int& c, String& value)
{
if (value.is_empty() == false) {
c = atoi( value.get() );
if( c > 7 ) {
c -= 7;
c |= BOLD;
}
}
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: read_lines
//
// This function reads contents of a resource file to a tree structure.
// If syntax of the resource file is not correct, then program is halted.
//
// EXTERNAL REFERENCES
// IN : Settigs.jmrdir
// OUT:
//
// RETURNS:
//**************************************************************************/
void
Jmrmain::read_lines ()
{
String tmp;
char line[MAX_LINE_LEN+1];
int linenum = 1;
bool rc = true;
tmp = Settings.jmrdir + resource_file;
ifstream rcfile( tmp.get(), ios::in );
if (rcfile.fail()) {
String err_str = "Cannot open resource file : " + tmp;
handle_error (err_str.get(), HALT_FL);
}
Wscript->print( "\nParsing resource file \'%s\'....", tmp.c_str() );
Screen.refresh();
do {
rcfile.getline (line, MAX_LINE_LEN);
tmp = line;
// remove preceeding spaces
tmp.cut_first();
// if line is not empty and it is not commented,
// then check syntax of line and get value
// of keyword.
if (tmp.is_empty() == false && tmp[0] != '#') {
// remove trailing spaces
tmp.cut_tail();
// parse the line
if (parse_rcline( tmp, linenum ) == false) {
rc = false;
}
}
++linenum;
} while (!rcfile.eof());
rcfile.close();
if (rc == false) {
handle_error("Fatal errors in resource file.",
HALT_FL);
}
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: parse_rcline
//
// Checks that syntax of a line is right, and initializes keyword that
// is defined in a line with a value given to it.
// If syntax error occures, then false is returned.
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS: true if success.
//**************************************************************************/
bool
Jmrmain::parse_rcline (String& line, int num)
{
int n=0;
String tmp;
// check that assigment operator is in it's place
n = line.findch ('=');
if (n == NOTHING) {
String error_str;
error_str = "Assigment operator is missing in line ";
error_str += num;
handle_error( error_str.get(), REPORT_FL);
return false;
}
// Then get word before an assigment operator and check
// if it matches with some of keywords in a trie
tmp.copy( line, 0, n);
tmp.cut_tail();
if (false == keywords.goto_keyword( tmp )) {
String error_str = "At line ";
error_str += num;
error_str += ": Illegal keyword : \'" + tmp + "\'";
handle_error( error_str.get(), REPORT_FL);
return false;
}
// Check that given value is legal, and store it
// in a trie.
tmp.copy( line, n + 1 );
tmp.cut_first();
tmp.cut_tail();
if (false == keywords.set_value( tmp )) {
String error_str = "Invalid value at line ";
error_str += num;
error_str += ": \'" + tmp + "\'";
handle_error( error_str.get(), REPORT_FL );
return false;
}
return true;
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: init_paths
//
// DESCRIPTION: Initializes directories for use.
//
//
// EXTERNAL REFERENCES
// IN : Settings
// OUT: Settings.workdir
//
// RETURNS:
//**************************************************************************/
void
Jmrmain::init_paths()
{
String cmd;
// initialize jmr's directory
get_jmrdir();
// Create directories for working files and for data files if
// them not exist.
Settings.datadir = Settings.jmrdir + DATA_DIRECTORY;
Settings.workdir = Settings.jmrdir + WORK_DIRECTORY;
if (ACCESS (Settings.datadir.get(), F_OK)) {
// FIXME: use mknod() or similar function instead
cmd = CREATE_DIRECTORY_COMMAND;
cmd += Settings.datadir;
jmrsystem (cmd.get());
}
if (ACCESS (Settings.workdir.get(), F_OK)) {
// FIXME: use mknod() or similar function instead
cmd = CREATE_DIRECTORY_COMMAND;
cmd += Settings.workdir;
jmrsystem (cmd.get());
}
// append path separator ('\' or '/')
correct_path (Settings.datadir);
correct_path (Settings.workdir);
// store current directory
getcwd (Settings.currentdir, MAXPATH);
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: get_jmrdir
//
// DESCRIPTION: Initializes jmr's directory.
//
//
// EXTERNAL REFERENCES
// IN :
// OUT: Settings.jmrdir
//
// RETURNS:
//**************************************************************************/
void
Jmrmain::get_jmrdir()
{
char *dir;
// get jmr's directory (where logs, resources etc. are)
dir = getenv (DIR_ENVIRONMENT);
if (dir == NULL) {
// if environment variable is not set, then read variable
// HOME, and use directory HOME/.jmr
dir = getenv ("HOME");
if (dir == NULL) {
handle_error ("Environment variable JMRDIR or HOME is not set!", HALT_FL);
}
Settings.jmrdir = dir;
correct_path (Settings.jmrdir);
Settings.jmrdir += DEFAULT_DIRECTORY;
} else {
Settings.jmrdir = dir;
}
correct_path (Settings.jmrdir);
check_jmrdir();
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: check_jmrdir
//
// DESCRIPTION: Checks if directory exist. If not, then creates it.
// If directory does not contain file jmrrc, then create file
// and tell user that it's necessary to modify it.
//
// EXTERNAL REFERENCES
// IN : Settings.jmrdir
// OUT:
//
// RETURNS:
//**************************************************************************/
void
Jmrmain::check_jmrdir()
{
String cmd;
String rcfile;
int c = 0;
// check if directory exists. If not, then create it
if (ACCESS (Settings.jmrdir.get(), F_OK)) {
Wscript->enable();
Wscript->print( "\nDirectory \'%s\' does not exist.",
Settings.jmrdir.c_str() );
Wscript->print( "\n\nShall I create it ? (y/n) y" );
Screen.refresh();
c = get_yesno('y');
if (c == 'n') {
Wscript->print( "\nExiting...." );
exit(EXIT_SUCCESS);
}
cmd = CREATE_DIRECTORY_COMMAND;
cmd += Settings.jmrdir;
if (jmrsystem (cmd.get())) {
handle_error("Directory creating failed.",HALT_FL);
}
}
rcfile = Settings.jmrdir;
rcfile += RESOURCE_FILE_NAME;
// check if resource-file exists. If not, create it.
if (ACCESS (rcfile.get(), F_OK) || init_all == true) {
create_rc_file(rcfile.get());
}
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: init_extractdir
//
// DESCRIPTION: Gets directory for storing articles.
//
// EXTERNAL REFERENCES
// IN :
// OUT: Settings.extractdir
//
// RETURN :
//**************************************************************************/
void
Jmrmain::init_extractdir()
{
String cmd;
if ( Settings.extractdir == "" ) {
char *path = getenv ("HOME");
if (path == NULL) {
handle_error ("Either key 'extractdir' must defined in resource file\nor environment variable HOME must set.", HALT_FL);
}
Settings.extractdir = path;
correct_path(Settings.extractdir);
Settings.extractdir += EXTRACT_DIRECTORY;
}
if (ACCESS (Settings.extractdir.get(), F_OK)) {
// FIXME: Should use mknod() or similar function...
cmd = CREATE_DIRECTORY_COMMAND;
cmd += Settings.extractdir;
DEBUG( "Exractdir = '" << Settings.extractdir.c_str() << "'" );
jmrsystem (cmd.get());
}
correct_path( Settings.extractdir );
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: create_rc_file
//
// DESCRIPTION: Writes new resource file
//
//
// EXTERNAL REFERENCES
// IN :
// OUT:
//
// RETURNS:
//**************************************************************************/
void
Jmrmain::create_rc_file (char *file)
{
ofstream fout (file);
if (fout.fail()) {
handle_error ("Cannot create default rc-file.",HALT_FL);
}
fout <<"\n########################################################################";
fout <<"\n#";
fout <<"\n#";
fout <<"\n# Configuration file for jmr QWK offline mail reader. This file sets";
fout <<"\n# up the runtime parameters and options.";
fout <<"\n#";
fout <<"\n# Parameters defined here are global for each bbs, but you can override";
fout <<"\n# some of these using local, bbs related configuration files.";
fout <<"\n# Sample for the local configuration file should be found at";
fout <<"\n# /usr/local/lib/jmr/sample.rc, if jmr is properly installed.";
fout <<"\n#";
fout <<"\n#";
fout <<"\n# Lines that start with a '#' are ignored.";
fout <<"\n#";
fout <<"\n# NOTE: Name of this file should be 'jmrrc' unless you specify name using";
fout <<"\n# parameter -f at commandline.";
fout <<"\n# This file must allways be in the directory, that ";
fout <<"\n# environment variable JMRDIR points or in directory";
fout <<"\n# $HOME/.jmr/";
fout <<"\n#";
fout <<"\n# NOTE: All paths must be absolute, and if your system uses drive-letters";
fout <<"\n# (like MS-DOG), path must start with drive-letter.";
fout <<"\n# e.g. in Unix systems replypath might be something like:";
fout <<"\n# /spool/send/";
fout <<"\n#";
fout <<"\n# And in DOS-familian systems it might be:";
fout <<"\n# c:\\work\\uploads\\";
fout <<"\n#";
fout <<"\n#";
fout <<"\n# Resources are divited to these sections:";
fout <<"\n# Required settings -- Without these jmr wont run";
fout <<"\n# Basic settings -- Defines paths, editor, zip command etc.";
fout <<"\n# Replying & quoting -- Sets up quoteprefix, tagline etc.";
fout <<"\n# Database -- Define size of database and replylog";
fout <<"\n# Colors -- Set up colors ";
fout <<"\n#";
fout <<"\n########################################################################";
fout <<"\n";
fout <<"\n";
fout <<"\n";
fout <<"\n";
fout <<"\n########################################################################";
fout <<"\n#";
fout <<"\n# REQUIRED SETTINGS";
fout <<"\n#";
fout <<"\n# You must set up at least these two resources in order to run jmr.";
fout <<"\n#";
fout <<"\n########################################################################";
fout <<"\n";
fout <<"\n";
fout <<"\n # Define your systems characterset here. Possible values are:";
fout <<"\n # latin1 for ISO-LATIN1 character set. (e.g. Linux uses it)";
fout <<"\n # ibmpc for PC 437 codepage. (e.g. OS/2, MS-DOG)";
fout <<"\n # 7bit for 7bit ascii character set. Some old machines (?)";
fout <<"\n #";
fout <<"\n # E.g. systemcharset = latin1";
fout <<"\nsystemcharset = ";
fout <<"\n";
fout <<"\n";
fout <<"\n #";
fout <<"\n # Character set that bbs is using.";
fout <<"\n #";
fout <<"\nbbscharset = ";
fout <<"\n";
fout <<"\n";
fout <<"\n########################################################################";
fout <<"\n#";
fout <<"\n# BASIC SETTINGS";
fout <<"\n#";
fout <<"\n########################################################################";
fout <<"\n";
fout <<"\n";
fout <<"\n # Shell command to unzip qwk-packet. Normally you don't need to change ";
fout <<"\n # this, unless you are using MS-DOG and you want to use pkzip/pkunzip.";
fout <<"\n # NOTE: Use unzip-program with option, that overwrites existing files!";
fout <<"\n # Default command is 'unzip -oLqq'";
fout <<"\n#unzipcmd = pkunzip";
fout <<"\n";
fout <<"\n";
fout <<"\n # Shell command to pack replies. This should also work in your ";
fout <<"\n # system without changes.";
fout <<"\n # Default command is 'zip -jkq'";
fout <<"\n#zipcmd = pkzip";
fout <<"\n";
fout <<"\n";
fout <<"\n # Set up path to directory, that holds your QWK mail packets.";
fout <<"\n # Default value is './'";
fout <<"\n#qwkpath = c:\\work\\download";
fout <<"\n#qwkpath = ~/work/download";
fout <<"\n";
fout <<"\n";
fout <<"\n # Set up path to directory, where replies goes. Usually it should be";
fout <<"\n # same directory, that you use for uploads in your terminal software.";
fout <<"\n # Default is './'";
fout <<"\n#replypath = ~/work/upload";
fout <<"\n";
fout <<"\n";
fout <<"\n # This is the wildcard file specification to use when searching for";
fout <<"\n # packets to read. Accepts *, ? and list [].";
fout <<"\n # Default pattern is '*.[qQ]*'";
fout <<"\n # NOTE: Pattern is case-sensitive even in Os/2!";
fout <<"\n#searchpattern = *.qwk";
fout <<"\n";
fout <<"\n";
fout <<"\n # Define the editor use for replies.";
fout <<"\n # Default is '/usr/bin/jed' in unix and 'jed.exe' in DOS-style";
fout <<"\n # operating systems.";
fout <<"\n#editor = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Set up path to directory for saved messages.";
fout <<"\n #";
fout <<"\n # Normally program reads environment variable HOME, and extracts saved";
fout <<"\n # messages to subdirectory 'Messages' of directory that HOME points";
fout <<"\n # ($HOME/Messages/).";
fout <<"\n # ";
fout <<"\n # If You want to override HOME-environment variable, uncomment next line";
fout <<"\n#extractdir = ~/work/articles";
fout <<"\n";
fout <<"\n";
fout <<"\n # Default name for file to save articles. ";
fout <<"\n # This name can be simple name like `saved_articles', when jmr creates";
fout <<"\n # it under directory defined by key `extractdir' or $HOME/Messages.";
fout <<"\n # Name of folder can also be relative like `../saved_articles',";
fout <<"\n # absolute like `/home/my_name/saved_articles' or `~/texts/saved_artcles";
fout <<"\n # ";
fout <<"\n#default_folder = saved_articles";
fout <<"\n";
fout <<"\n";
fout <<"\n # This format string defines how date should be displayed. Default format";
fout <<"\n # for date is dd.mm.yyyy (%D.%M.%Y as a format string).";
fout <<"\n # Format specifiers are: %M for month, %D for day and %Y for year.";
fout <<"\n#dateformat = %M/%D/%Y";
fout <<"\n";
fout <<"\n";
fout <<"\n # With this option set, lines are word wrapped at the end of screen,";
fout <<"\n # avoiding truncated messages.";
fout <<"\n # Note that wrap mode can also be changed in jmr using key 'ESC-w'.";
fout <<"\n # Default value is 'no'";
fout <<"\n#wrapmode = yes";
fout <<"\n";
fout <<"\n";
fout <<"\n # If this is set to 'yes', then jmr asks confirm when returning to";
fout <<"\n # packet selection level from group selection level.";
fout <<"\n # Default value is 'yes'";
fout <<"\n#confirm_return = no";
fout <<"\n";
fout <<"\n";
fout <<"\n########################################################################";
fout <<"\n#";
fout <<"\n# SETTINGS FOR REPLYING & QUOTING";
fout <<"\n#";
fout <<"\n########################################################################";
fout <<"\n";
fout <<"\n";
fout <<"\n # Default value for name of receiver when composing new article.";
fout <<"\n#default_receiver = All";
fout <<"\n";
fout <<"\n";
fout <<"\n # This describes how jmr will quote the lines of the source message when";
fout <<"\n # creating a new reply file. '_' indicates space.";
fout <<"\n # Default prefix is '> '";
fout <<"\n#quoteprefix = :_";
fout <<"\n";
fout <<"\n";
fout <<"\n # This specifies all characters that might indicate that the line is";
fout <<"\n # quoted from someone else. Default is \":>%&!/#\".";
fout <<"\n#quote_chars = >";
fout <<"\n";
fout <<"\n";
fout <<"\n # This tells jmr how deep from the line it should search for";
fout <<"\n # quote prefix characters. Default is four";
fout <<"\n#quote_depth = 4";
fout <<"\n";
fout <<"\n";
fout <<"\n # This sets up follow-up string. The following format specifiers are";
fout <<"\n # recognized:";
fout <<"\n # %d = date of quoted article";
fout <<"\n # %t = time";
fout <<"\n # %n = name of author";
fout <<"\n # %s = subject of article";
fout <<"\n # %m = reference message id";
fout <<"\n # %M = number of month";
fout <<"\n # %D = number of day";
fout <<"\n # %Y = year";
fout <<"\n # %% = percent";
fout <<"\n #";
fout <<"\n # E.g. next string 'In article dated %d %n wrote:'";
fout <<"\n # Expands to something like: ";
fout <<"\n # 'In article dated 03-31-1997 John Smith wrote:'";
fout <<"\n #";
fout <<"\n # Default string is 'In article <%m> %n wrote:'";
fout <<"\n#followupstring =";
fout <<"\n";
fout <<"\n";
fout <<"\n # Set up reply string. Syntax is same as in follow-up string, but";
fout <<"\n # this is used only in a private replies.";
fout <<"\n #";
fout <<"\n # Default string is 'On a message dated %d you wrote:'";
fout <<"\n#replystring = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # When this is set to 'no' then only name of the operating system";
fout <<"\n # is displayed at the tagline. Default is 'no'.";
fout <<"\n # With value 'yes', tagline looks like:";
fout <<"\n # * jmr r.x.y.z [machine/system] *";
fout <<"\n # And with 'no', tag is:";
fout <<"\n # * jmr r.x.y.z [system] *";
fout <<"\n #";
fout <<"\n # Note that this settings works only in Unix-like systems.";
fout <<"\n#tag_full_sysname = yes";
fout <<"\n";
fout <<"\n";
fout <<"\n # Write your permanent tagline here.";
fout <<"\n # This line will come right after jmr's own tag.";
fout <<"\n # Usually you might want leave this empty...";
fout <<"\n # Tagline should not be longer than this!!!";
fout <<"\n # (---------------------------------------------------)";
fout <<"\n#ptagline = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # This sets the default tagline for articles.";
fout <<"\n # This string is placed at bottom of each reply, and it will start with";
fout <<"\n # three point ( e.g. '...this is the tagline')";
fout <<"\n #";
fout <<"\n # You can also specify file of taglines by using character '@'";
fout <<"\n # at start of tagline. Example: if tagline is:";
fout <<"\n # @my_taglines";
fout <<"\n # Then jmr will gets random line from file my_taglines as a tagline.";
fout <<"\n #";
fout <<"\n # If file name is given without path, then jmr seeks file from jmr's";
fout <<"\n # directory ($JMRDIR or ~/.jmr) and if file not found, then jmr seeks";
fout <<"\n # from directory /usr/local/lib/jmr ";
fout <<"\n #";
fout <<"\n # NOTE: if you specify file with path, then you should use full path. e.g:";
fout <<"\n # @/home/my_name/sruff/tagline_file";
fout <<"\n # @C:\\misc\\taglines.txt";
fout <<"\n#tagline = This message has been UNIXized for your protection.";
fout <<"\n";
fout <<"\n";
fout <<"\n # Use external program to generate tagline.";
fout <<"\n # This overrides both taglines, 'tagline' and 'ptagline'";
fout <<"\n#tagline_program = /usr/games/fortune";
fout <<"\n";
fout <<"\n";
fout <<"\n # Set up signature file.";
fout <<"\n # If this is set, then jmr reads singature from the given";
fout <<"\n # file. Note that this overrides all tagline settings.";
fout <<"\n#signaturefile = ~/.signature";
fout <<"\n";
fout <<"\n";
fout <<"\n";
fout <<"\n########################################################################";
fout <<"\n#";
fout <<"\n# DATABASE SETTINGS";
fout <<"\n#";
fout <<"\n########################################################################";
fout <<"\n";
fout <<"\n";
fout <<"\n # Maximum count of replies to store in log.";
fout <<"\n # Default value is 200.";
fout <<"\n#replylogsize = 1500";
fout <<"\n";
fout <<"\n";
fout <<"\n # Maximum count of articles to keep in database";
fout <<"\n # NOTE: Database for single BBS with 10000 messages requires about";
fout <<"\n # 8-10MB free disk space in jmr's home directory, and 8-10MB";
fout <<"\n # free disk space in temporary directory.";
fout <<"\n # Default size is 5000";
fout <<"\n#databasesize = 10000";
fout <<"\n";
fout <<"\n";
fout <<"\n#########################################################################";
fout <<"\n#";
fout <<"\n# Experimental code and keywords";
fout <<"\n#";
fout <<"\n#########################################################################";
fout <<"\n";
fout <<"\n";
fout <<"\n # If set to 'yes' jmr will automagically remove read qwk packet.";
fout <<"\n # Setting it to 'ask', will make jmr to ask permission to delete.";
fout <<"\n # Be careful! Not Tested and probably dangerous piece of code!";
fout <<"\n # The default value is 'no'.";
fout <<"\n";
fout <<"\n#rmoldpacket = ask";
fout <<"\n";
#ifdef F_TO_ALL
fout <<"\n # To whom follow up should be addressed to ?";
fout <<"\n # Domain : all, writer, John Doe, ...";
fout <<"\n # Setting to writer assumes normal jmr behaviour.";
fout <<"\n";
fout <<"\nfollowupto = all";
fout <<"\n";
#endif
fout <<"\n";
fout <<"\n#########################################################################";
fout <<"\n#";
fout <<"\n# COLORS";
fout <<"\n#";
fout <<"\n#########################################################################";
fout <<"\n";
fout <<"\n";
fout <<"\n # Set up color for quoted part of article";
fout <<"\n #";
fout <<"\n # Colors:";
fout <<"\n #";
fout <<"\n # 0 use default screen color";
fout <<"\n # 1 red";
fout <<"\n # 2 green";
fout <<"\n # 3 brown";
fout <<"\n # 4 blue";
fout <<"\n # 5 magenta";
fout <<"\n # 6 cyan";
fout <<"\n # 7 lightgray";
fout <<"\n # 8 brightred";
fout <<"\n # 9 brightgreen";
fout <<"\n # 10 yellow";
fout <<"\n # 11 brightblue";
fout <<"\n # 12 brightmagenta";
fout <<"\n # 13 brightcyan";
fout <<"\n # 14 white";
fout <<"\n #";
fout <<"\n # For non-color terminals, colors are shown : 1 - 7 normal, 8 - 14 bold.";
fout <<"\n #";
fout <<"\n # Default color is green ";
fout <<"\n#quote_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Set up color for normal text of article.";
fout <<"\n # Default is cyan ";
fout <<"\n#article_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Foreground color for menubar.";
fout <<"\n # Default is white ";
fout <<"\n#menubar_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Background color for menubar.";
fout <<"\n # Default is brightblue ";
fout <<"\n#menubar_back = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Foreground color for statusline.";
fout <<"\n # Default is yellow ";
fout <<"\n#statusline_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Background color for statusline.";
fout <<"\n # Default is brightblue ";
fout <<"\n#statusline_back = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Foreground color for dashed line that splits screen.";
fout <<"\n # Default is normal text color";
fout <<"\n#dashline_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Background color for dashed line that splits screen.";
fout <<"\n # Default is normal text color";
fout <<"\n#dashline_back = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Color for text of infoline (that is line at bottom of the screen)";
fout <<"\n # Default is normal text color";
fout <<"\n#infoline_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Color for error message that is displayed at the infoline";
fout <<"\n # Default is brightred";
fout <<"\n#infoline_error_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Color for subject that is displayed at the header part of the screen";
fout <<"\n # Default is normal color, but bold text";
fout <<"\n#header_subject_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Color for author that is displayed at the header part of the screen";
fout <<"\n # Default is color is green";
fout <<"\n#header_author_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Color for subject that is displayed at the lists (grouplist, threadlist)";
fout <<"\n # Default is color is green";
fout <<"\n#subject_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Color for name of the author at the thread list and color for the number";
fout <<"\n # of unread articles that is displayed at the group list";
fout <<"\n # Default is color is cyan";
fout <<"\n#author_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Color for highlighting your name at the personal messages. ";
fout <<"\n # Default color is bright yellow";
fout <<"\n#personal_color = ";
fout <<"\n";
fout <<"\n";
fout <<"\n # Color for name of the jmrs basegroups at the group list.";
fout <<"\n # Default is color is bright green";
fout <<"\n#basegroup_color = ";
fout <<"\n";
fout.close();
Wscript->enable();
Wscript->reset_attr();
Wscript->bold();
Wscript->print( "\nResource file (%s) created with default settings.\nEdit file and set up required values.", file);
exit (EXIT_SUCCESS);
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: parse_arguments
//
// DESCRIPTION: Checks correctness of command line arguments
//
// FIXME: Replace by using getopt_long someday...
//
//
// RETURNS:
//**************************************************************************/
void Jmrmain::parse_arguments ()
{
int i;
resource_file = RESOURCE_FILE_NAME;
if (argc == 1) {
return;
}
for (i = 1; i < argc; i++) {
if (check_argument (i) == false) {
cerr << "\njmr: illegal option -- "<<argv[i]<<"\n";
cerr << "try 'jmr --help' for more information.";
cerr << endl;
exit(EXIT_FAILURE);
}
}
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: check_argument
//
// DESCRIPTION: Checks if option <n> is legal.
//
// RETURNS: true if option is ok. Otherwise false.
//**************************************************************************/
bool Jmrmain::check_argument (int& n)
{
if (strcmp (argv[n], "-f") == 0 ||
strcmp (argv[n], "--rcfile") == 0) {
if (argc > n+1) {
resource_file = argv[++n];
return true;
} else {
return false;
}
}
if (strcmp (argv[n], "-c") == 0 || strcmp (argv[n], "--color") == 0) {
Screen.force_colormode();
return true;
}
if (strcmp (argv[n], "-h") == 0 || strcmp (argv[n], "--help") == 0) {
show_help();
exit (EXIT_SUCCESS);
}
if (strcmp (argv[n], "--init") == 0) {
init_all = true;
return true;
}
if (strcmp (argv[n], "--open-jmr") == 0) {
if ( argc > n+1 ) {
open_jmr = argv[++n];
return true;
} else
return false;
}
return false;
}
//**************************************************************************/
// CLASS: Jmrmain
// MEMBER FUNCTION: show_help
//
// DESCRIPTION: Displays help for options of jmr.
//
//**************************************************************************/
void Jmrmain::show_help()
{
cerr << "jmr " << VERSION <<" -- A QWK offline mail reader";
cerr << "\nCopyright (c) 1997,1998 Jukka Vuokko\n\n" ;
cerr <<"Usage: jmr [-chl][-f file][--help][--color][--rcfile file][--init]\n\n";
cerr <<"-h, --help Show this help and exit.\n";
cerr <<"-f file, --rcfile file Use resource file 'file' instead of jmrrc\n";
cerr <<"-c, --color Force ANSI-color mode on. Useful in color-xterm\n";
cerr <<"--init Write new jmrrc file\n";
cerr <<"\nAdvanced option:\n";
cerr <<"--open-jmr <bbsid> Opens bbsid.qwk if its is there, otherwise \n";
cerr <<" bbsid dabase is opened.\n";
}
jmrsrc-0.7.23/src/jmrmisc.cc 100664 764 764 67740 6623366026 15351 0 ustar jvuokko jvuokko // $Id: jmrmisc.cc,v 1.3 1998/05/25 20:06:04 jvuokko Exp $
/*****************************************************************************
* *
* * MODULE: jmrmisc.cc
* * ----------
* *
* *
* * Revision : $Revision: 1.3 $
* * Date(UTC) : $Date: 1998/05/25 20:06:04 $
* * Source : $Source: /usr/local/cvsroot/jmr/src/jmrmisc.cc,v $
* * Author : $Author: jvuokko $
* *
* ***************************************************************************
* * This file is part of jmr offline mail reader.
* *
* * Copyright (C) 1998 Jukka Vuokko <jvuokko@iki.fi>
* *
* * 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.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program; if not, write to the Free Software
* * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* ***************************************************************************
* *
* * This module contains miscallenous helper functions for jmr
* *
*****************************************************************************/
#include <stdio.h> // sprintf(), rand(), srand(), tmpnam()
#include <time.h> // time()
#include <stdlib.h> // EXIT_FAILURE, EXIT_SUCCESS
#include <signal.h>
#include <string.h> // strerror
#include <errno.h>
#ifdef __WATCOMC__ // Watcom stuff
# include <io.h> // access()
# include <direct.h>
#elif defined (__EMX__) // EMX-stuff
# include <unistd.h> // F_OK
# include <sys/types.h>
# include <dirent.h>
#else // UNIX
# include <sys/types.h>
# include <sys/wait.h> // wait()
# include <sys/utsname.h> // uname()
# include <unistd.h> // F_OK
# include <dirent.h>
# include <fcntl.h> // open, write, creat, read
#endif
#include "constants.h"
#include "jmr.hh"
#include "time.hh"
#include "terminal_io.hh"
#include "linereader.hh"
//-------------------------------
// for set current diskdrive and copying file
#ifdef OS2
//# define INCL_DOSERRORS
# define INCL_ERRORS
# define INCL_DOSFILEMGR
# include <os2.h>
#endif
#if defined (WIN32) || defined (DOS)
# include <dos.h>
#endif
#ifdef __unix__
# include <unistd.h> // getcwd, chdir
#endif
//****************** GLOBAL VARIABLES *************************************/
extern settings_t Settings;
extern Terminal_screen Screen;
extern Window *Wscript;
static const char* help_text[] = {
"@COMMON MOVING COMMANDS:",
"",
" HOME, ESC < Beginning of list/article",
" END, ESC > End of list/article",
" PGUP, Ctrl-v Page up",
" PGDN, ESC v Page down",
" UP ARROW Line up",
" DOWN ARROW Line down",
" LEFT ARROW Back to previous level",
" RIGHT ARROW Enter to next level",
"",
"@COMMON EDITING COMMANDS:",
"",
" Ctrl-a, Ctrl-e Move to beginning or end of the line",
" Ctrl-k Delete all characters, from cursor to end of line",
" Ctrl-h Delete the previous character",
" Ctrl-b, Ctrl-f Move backward of forward",
"",
"@MAIL PACKET AND DATABASE SELECTION:",
"",
" r Refresh file or database list",
" n Next packet or database",
" p Previous packet or database",
" ENTER Open packet or database",
" l Switch to list of available databases or list of",
" available mail packets",
" q, Q Quit jmr",
"",
"@GROUP SELECTION:",
"",
" n Next group",
" p Previous group",
" ENTER, -> Enter to group",
" TAB Enter to next group with unread articles",
" w Write an article to current group",
" y Switch between all groups and groups with unread articles",
" c Catchup. Mark all articles in current group as read",
" C Catchup without questions",
" t Tag/untag current group",
" T Tag/untag all groups",
" / Search from current, tagged or all groups",
" q Return to packet selection level",
"",
"@THREAD SELECTION:",
"",
" n Next thread",
" p Previous thread",
" TAB View next unread article within all threads",
" ENTER, -> View first article of current thread",
" w Write an article to current group",
" f Write followup to current article or first article",
" of current thread, if reading mode is by threads",
" r Write private reply to current article (or first",
" article of current thread)",
" y Show all threads or only threads with unread articles",
" t Tag/untag current article (does not work in thread mode)",
" T Tag/untag all articles (not in thread mode)",
" s Sort articles",
" c Catchup. Mark all articles within current group as",
" a read",
" C Forced catchup",
" e Edit current reply. Works only in group 'New Replies'",
" E Edit headers of a current reply.",
" k Kill/unkill article. Works only in groups Replylog,",
" New Replies and Tagged Articles",
" q, <- Return to group selection level",
"",
"@MESSAGE VIEWER:",
"",
" TAB, ENTER View next unread article within current group",
" n View next article",
" p View previous article",
" <, Ctrl-a View first article of a current thread",
" >, Ctrl-e View last article of a current thread",
" N Next article in history",
" P Previous article in history",
" f Write followup to current article",
" r Write private reply to current article",
" e Edit current reply. Works only in group 'New Replies'",
" E Edit headers of a current reply.",
" t Tag/untag current article",
" k Kill/unkill current article. Only in groups:",
" Replylog, New Replies and Tagged Articles",
" s Save current article or thread to file",
" SPC Page down, or if end of current article, then",
" view next unread article",
" ESC-r View article using Rot13",
" ESC-w Toggle line wrapping mode on/off",
" q, LEFT ARROW Return to thread selection level",
"",
"@MISC",
"",
" Ctrl-l Redraw screen",
" m Show message buffer",
" Q Quit jmr",
"@END" // This must be last item of the array!
};
//*************************************************************************/
// FUNCTION: read_tagline
//*************************************************************************/
//
// Function reads random line from given file.
//
//
// RETURN: line as String object.
//*************************************************************************/
String read_tagline (String const &file)
{
List<String> *taglines;
Linereader l( file );
// read lines from file of taglines to linked list
taglines = l.read_all_lines();
// get random line
String tmp;
DEBUG( "read_tagline: Lines = " << taglines->count_items());
int index = rand() % taglines->count_items();
DEBUG( "read_tagline: random is: " << index );
if ( taglines->first() == true ) {
while ( index ) {
taglines->next();
--index;
}
tmp = *taglines->get();
}
delete taglines;
return tmp;
}
//*************************************************************************/
// FUNCTION: get_tagline
//*************************************************************************/
//
// Function gets tagline from file of taglines, or from resource file.
//
// EXTERNAL VARIABLE REFERENCES:
// IN : Settings.jmrdir, Settings.tagline
// OUT:
//
// RETURN: tagline as String
//*************************************************************************/
String
get_tagline ()
{
// default tagline
String tag = Settings.tagline;
tag.cut_tail();
// read random tagline from file.
if (Settings.tagline.nget (0) == '@') {
String file;
String tmp;
tmp.copy (Settings.tagline, 1);
correct_path (tmp); // expand possible ~
tmp.ncut_end (1); // cut trailing '/' or '\'
file = tmp;
if (ACCESS (file.get(), F_OK) == 0) {
tag = read_tagline (file);
} else {
file = "/usr/local/lib/jmr/" + tmp;
if (ACCESS (file.get(), F_OK) == 0) {
tag = read_tagline (file);
} else {
file = Settings.jmrdir + tmp;
if (ACCESS (file.get(), F_OK) == 0) {
tag = read_tagline (file);
} else {
handle_error ("Cannot access file of taglines.");
tag = "";
}
}
}
}
// Remove possible ^M from the tagline. Seems like that
// usually tagline file is in MS-DOG format....
// (where is good old dos2unix?)
tag.strip_ch( '\r' );
return tag;
}
/***************************************************************************
* FUNCTION: clear_workdir
***************************************************************************
*
* DESCRIPTION: Removes all files from working directory
*
* EXTERNAL REFERENCES
* IN : Settings.workdir
* OUT:
*
***************************************************************************/
void
clear_workdir()
{
String path;
String file;
String tmp;
path = Settings.workdir;
correct_path (path);
file = path + "*.[nN][dD][xX]";
rm_files( file );
file = path + "*.[dD][aA][tT]";
rm_files( file );
file = path + "*.[iI][dD]";
rm_files( file );
file = path + "*.[mM][sS][gG]";
rm_files( file );
// remove files with exact name.
// Under Os/2, readdir() returns uppercase name, if file is
// stored to FAT file system. That's why in Os/2 it is needed
// also check for uppercase names...
tmp = DEADJMR_FILENAME;
file = path + tmp;
rm_files( file );
#ifdef OS2
file = path + tmp.upcase();
rm_files( file );
#endif
tmp = DEADJMR_ID_FILENAME;
file = path + tmp;
rm_files( file );
#ifdef OS2
file = path + tmp.upcase();
rm_files( file );
#endif
tmp = REPLY_TEMP_FILENAME;
file = path + tmp;
rm_files( file );
#ifdef OS2
file = path + tmp.upcase();
rm_files( file );
#endif
}
/***************************************************************************
* FUNCTION: handle_error
***************************************************************************
*
* DESCRIPTION: Shows error message, and if flag is HALT_FL, then program
* is halted.
*
* flag:
* DEFAULT_FL Show error message and wait for key
* REPORT_FL Print error message to stderr, and continue
* HALT_FL Print error message to stderr and halt.
*
* EXTERNAL REFERENCES
* IN : Settings.currentdir
* OUT:
*
***************************************************************************/
void
handle_error (const char *msg, int flag)
{
Screen.enable();
Wscript->reset_attr();
Wscript->textcolor (BOLD | RED);
Wscript->enable();
Wscript->print("\n*** ERROR: ");
Wscript->reset_attr();
Wscript->print("%s", msg );
if (flag != HALT_FL && flag != REPORT_FL) {
Wscript->print( "\nPress any key to continue.." );
Screen.get_ch();
Wscript->disable();
Wscript->print("\n -----------------------------");
} else if ( flag == HALT_FL) {
Wscript->print("\n*** Program halted.\n" );
Screen.refresh();
if (Settings.currentdir[0]) {
change_dir (Settings.currentdir);
}
exit (EXIT_FAILURE);
}
Screen.refresh();
}
void
system_error ( const char *msg, int flag )
{
String str = msg;
str += " -- ";
str += strerror( errno );
handle_error( str.c_str(), flag );
}
void
fatal_error( const char *msg )
{
handle_error( msg, HALT_FL );
}
/***************************************************************************
* FUNCTION: convert_to_qwk_date
***************************************************************************
*
* DESCRIPTION: Converts date from format mm-dd-yyyy to QWK style mm-dd-yy
* (and wait year 2000 happy!)
*
*
* RETURN : Pointer to c-string that contains date. Copy it immediately to
* the final location
***************************************************************************/
char*
convert_to_qwk_date (char *s)
{
static char buf[12];
strncpy (buf, s, 6);
buf[6] = s[8];
buf[7] = s[9];
buf[8] = '\0';
return buf;
}
/***************************************************************************
* FUNCTION: convert_to_jmr_date
***************************************************************************
*
* DESCRIPTION: Converts date from stubid QWK format to better mm-dd-yyyy
* format
*
*
* RETURN : pointer to static c-string that contains new date.
***************************************************************************/
char *
convert_to_jmr_date (char *s)
{
Time_handler Time;
static char buf[12];
char *tmp;
tmp = Time.get_date();
strcpy (buf, s);
buf[6] = tmp[6];
buf[7] = tmp[7];
buf[8] = s[6];
buf[9] = s[7];
buf[10] = '\0';
return buf;
}
//*************************************************************************/
// FUNCTION: get_month
//*************************************************************************/
//
// Returns number of month from date that is in jmr's format (mm-dd-yyyy)
//
//*************************************************************************/
int
get_month (char *s)
{
return (int) (10 * ((*s) - 48) + *(s+1) - 48);
}
//*************************************************************************/
// FUNCTION: get_day
//*************************************************************************/
//
// Returns number of day from date string that is in jmr's date format.
//
//*************************************************************************/
int
get_day (char *s)
{
return (int) (10 * (*(s+3) - 48) + *(s+4) -48);
}
//*************************************************************************/
// FUNCTION: get_year
//*************************************************************************/
//
// Returns number of year from date string
//
//*************************************************************************/
int
get_year (char *s)
{
return atoi (s+6);
}
/***************************************************************************
* FUNCTION: strip_re_prefix
***************************************************************************
*
* DESCRIPTION: Checks if prefix 're:' exist. If so, then remove prefix.
*
*
* RETURN : true, if prefix 're:' found.
***************************************************************************/
bool
strip_re_prefix (String& str)
{
bool rc = false;
String tmp = str;
tmp.cut_first();
if (tmp.ifind ("re:") == 0) {
rc = true;
str.copy (tmp, 3);
}
str.cut_first();
str.cut_tail();
return rc;
}
/***************************************************************************
* FUNCTION: get_yesno
***************************************************************************
*
* DESCRIPTION: Reads answer to question y/n. If user just press enter, then
* default_value is token as answer.
*
*
* RETURN : ascii code of answer.
***************************************************************************/
int
get_yesno (int default_value)
{
int c;
do {
c = Screen.get_ch();
} while (c != 'y' && c != SELECT_CMD && c != 'n');
if (c == SELECT_CMD) {
c = default_value;
}
return c;
}
//*************************************************************************/
// FUNCTION: show_help
//*************************************************************************/
//
// This function shows help.
//
// EXTERNAL VARIABLE REFERENCES:
// IN : Screen
// OUT:
//
// RETURN:
//*************************************************************************/
void show_help()
{
int y;
int ymax = Screen.get_ymax() - 3;
int index;
loop:
index = 0;
int c;
y = 0;
Screen.clear();
do {
if (*help_text[index] == '@') {
Screen.textcolor( BOLD );
Screen.print( "%s\n", help_text[index]+1 );
//cout << help_text[index]+1 << "\n";
Screen.reset_attr();
} else {
Screen.print( "%s\n", help_text[index] );
//cout << help_text[index] << "\n";
}
++index;
++y;
if (y == ymax) {
Screen.gotoxy( 0, ymax + 3);
Screen.print( "q to quit, ? to start over or any other key to continue" );
c = Screen.get_ch();
if (c == QUIT_CMD) {
return;
}
if (c == HELP_CMD) {
goto loop;
}
Screen.clear();
y = 0;
}
} while ( strcmp( help_text[index], "@END" ) );
Screen.gotoxy(0, ymax + 3);
Screen.print( "? to start over, any other to exit from help" );
c = Screen.get_ch();
if (c == HELP_CMD) {
goto loop;
}
}
//**************************************************************************/
// CLASS: Infoline
// MEMBER FUNCTION: reset
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Infoline::reset()
{
if (true == active) {
Screen.bottom();
_msg = "";
show_option_str( default_msg.get(), Settings.iline_color );
Screen.clr_eol();
active = false;
}
}
//**************************************************************************/
// CLASS: Infoline
// MEMBER FUNCTION: show
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Infoline::show( int flag)
{
if (true == active || flag & REFRESH_ALL_FL) {
int color = is_error ? Settings.iline_err_color
: Settings.iline_color;
Screen.bottom();
if (_msg.is_empty() == false) {
show_option_str( _msg.c_str(), color );
} else {
show_option_str( default_msg.c_str(), color );
}
Screen.clr_eol();
}
}
//**************************************************************************/
// CLASS: Infoline
// MEMBER FUNCTION: show_default
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Infoline::show_default()
{
Screen.bottom();
show_option_str( default_msg.c_str(), Settings.iline_color | BOLD,
Settings.iline_color);
Screen.clr_eol();
}
//**************************************************************************/
// CLASS: Infoline
// MEMBER FUNCTION: set
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Infoline::set( const char* msg, int fl )
{
active = true;
if (fl & ERROR_FL ) {
_msg = "*** ";
is_error = true;
} else {
_msg = "";
is_error = false;
}
_msg += msg;
}
void
Sline::show()
{
Screen.gotoxy( 0, Screen.get_ymax() - 1 );
Screen.reset_attr();
if( Screen.is_color_term() == true ) {
Screen.textcolor( Settings.sline_color );
Screen.back_color( Settings.slineback_color );
} else {
Screen.inverse();
}
Screen.addstr( get_sline() );
Screen.reset_attr();
}
//*************************************************************************/
// FUNCTION: set_dashline_color
//*************************************************************************/
//
// Sets colors of dashed line on.
//
// EXTERNAL VARIABLE REFERENCES:
// IN : Settings.dash_color, Settings_dashback_color
// OUT: Screen
//
// RETURN:
//*************************************************************************/
void
set_dashline_color()
{
Screen.reset_attr();
if (Settings.dash_color != DEFAULT_FCOLOR) {
Screen.textcolor( Settings.dash_color );
}
if (Settings.dashback_color != DEFAULT_BCOLOR) {
Screen.back_color( Settings.dashback_color );
}
}
//*************************************************************************/
// FUNCTION: show_option_str
//*************************************************************************/
//
// Displays given string using given 'bold_color' in all text that are
// between '@' marks. Color for other text is 'color'.
//
// e.g. If msg is "Press @Q@ for quit", is letter Q highlighted using
// color 'bold_color' and other text is drawed using color 'color'.
//
// EXTERNAL VARIABLE REFERENCES:
// IN : -
// OUT: -
//
// RETURN: -
//*************************************************************************/
void
show_option_str( const char *msg, int bold_color, int color)
{
bool bold = false;
const char *ptr = msg;
Screen.reset_attr();
if (color != DEFAULT_FCOLOR) {
DEBUG("Settings option color : " << color );
Screen.textcolor( color );
}
while (*ptr) {
if ( *ptr == '@' ) {
bold = bold == false ? true : false;
if (bold == true ) {
if (bold_color == DEFAULT_FCOLOR) {
Screen.bold();
} else {
Screen.textcolor( bold_color );
}
} else {
Screen.reset_attr();
if (color != DEFAULT_FCOLOR ) {
Screen.textcolor( color );
}
}
if (! *++ptr ) {
break;
}
}
Screen.addch( *ptr );
++ptr;
}
Screen.reset_attr();
}
//*************************************************************************/
// FUNCTION: get_system_info
//*************************************************************************/
//
// Gets name of system, in long or short form. If flag is ALL_FL, then
// long form is used, i.e. '[processor/system_name]'. Short format is
// '[system_name]'.
//
// EXTERNAL VARIABLE REFERENCES:
// IN : -
// OUT: -
//
// RETURN: System name in string, between square brackets.
//*************************************************************************/
String
get_system_info( int flag )
{
String str;
#ifdef DO_NOT_USE_UNAME
if (flag == ALL_FL ) {
str = LONG_SYSTEM ;
} else {
str = SYSTEM ;
}
#else
struct utsname sysdata;
if ( uname( &sysdata ) <= 0 ) {
str = SYSTEM ;
} else {
str = "[";
if( flag == ALL_FL ) {
str += sysdata.machine;
str += "/";
}
str += sysdata.sysname;
str +="]";
}
#endif
return str;
}
int jmrsystem( const char* cmd )
{
Screen.set_origmode();
#ifdef __unix__
struct sigaction act, save;
act.sa_flags = 0;
act.sa_handler = SIG_DFL;
sigemptyset( &act.sa_mask );
sigaction( SIGTSTP, &act, &save );
int i = _system( cmd );
sigaction( SIGTSTP, &save, NULL );
#else
int i = _system( cmd );
#endif
Screen.set_rawmode();
return i;
}
/***************************************************************************
* FUNCTION : get_integer
***************************************************************************
*
* description : Gets integer value from string in byte_t array.
*
* Example: Array = { 1,5,7,9 } ---> returned value is 1579
*
* return : value readed from array.
***************************************************************************/
int
get_integer (
byte_t *str, // pointer to first number
int len) // amount of numbers
{
int i;
char *tmp = new char[len+1];
for (i=0; i < len; i++) {
tmp[i] = (char) *(str+i);
}
tmp[i] = 0;
i = atoi (tmp);
delete[] tmp;
return i;
}
/***************************************************************************
* FUNCTION : get_ushort
***************************************************************************
*
* description : returns value of two byte little-endian number
*
***************************************************************************/
int
get_ushort (
byte_t *str) // array of two bytes.
{
int i = (int) str[0] + 256*(int) str[1];
return i;
}
jmrsrc-0.7.23/src/linereader.cc 100664 764 764 2311 6623366026 15756 0 ustar jvuokko jvuokko
#include "../utilib/my_types.h"
#include "linereader.hh"
#include "../utilib/String.hh"
#include "../utilib/List.hh"
Linereader::Linereader( const String &fname )
{
fin.open( fname.c_str(), ios::in );
failed = false;
if (fin.fail()) {
failed = true;
}
DEBUG(" ==== Linereader initialized" );
}
Linereader::~Linereader()
{
fin.close();
DEBUG(" ==== Linereader destroyed");
}
String
Linereader::read_line()
{
char tmp[MAX_LINE_LEN];
String line;
if (true == failed) {
DEBUG(" Linereader: read_line failed! ");
return line;
}
fin.getline( tmp, MAX_LINE_LEN );
if ( fin.fail() ) {
failed = true;
}
line = tmp;
return line;
}
List<String>*
Linereader::read_all_lines()
{
List<String> *lines = new List<String>;
String tmp;
DEBUG( "Linereader: Reading contents of file" );
for (;;) {
tmp = read_line();
if ( failed ) {
break;
}
lines->add( new String( tmp ) );
}
return lines;
}
jmrsrc-0.7.23/src/mail.cc 100664 764 764 211353 6623366026 14636 0 ustar jvuokko jvuokko // $Id: mail.cc,v 1.25 1998/11/14 20:41:15 jvuokko Exp $
/*****************************************************************************
* *
* * MODULE: mail.cc
* * ---------
* ***************************************************************************
* *
* *
* * COPYRIGHT (C) 1997 JUKKA VUOKKO. ALL RIGHTS RESERVED
* ***************************************************************************
* *
* * This module contains functions of classes Group and Mail.
* *
*****************************************************************************/
#include <fstream.h>
#include <stdio.h> // remove()
#ifdef __WATCOMC__
# include <io.h> // access()
#else
# include <unistd.h> // F_OK, rename()
#endif
#include <time.h>
#include "mail.hh"
#include "qwk.hh"
#include "jmr.hh"
/************************ GLOBAL VARIABLES *********************************/
extern settings_t Settings;
extern Terminal_screen Screen;
extern Window* Wscript;
// This array defines rules for keywords in a resource file.
// "*" means that the keyword must have a value.
// "" a value of the keyword can be empty.
// "$" a value must be a number.
//
// Format of an array:
// Keyword, rule1, rule2, ruleN,
// "@NEXT",
// .
// .
// .
// "@NEXT", "@END"
static const char *bbs_setting_array[] = {
RC_QUOTEPREFIX,"*",
"@NEXT",
RC_BBSCHARSET, "latin1", "ibmpc", "7bit",
"@NEXT",
RC_PTAG, "",
"@NEXT",
RC_EXTRACTDIR, "",
"@NEXT",
RC_REPLYLOGSIZE, "$",
"@NEXT",
RC_DATABASESIZE, "$",
"@NEXT",
RC_FOLLOWUPSTR, "",
"@NEXT",
RC_REPLYSTR, "",
"@NEXT",
RC_TAG, "",
"@NEXT",
"@END"
};
/***************************************************************************
* FUNCTION : cmp_date
***************************************************************************
*
* description : Compares date and time fields of a two Message class
*
* return : <0, 0 or >0
***************************************************************************/
int cmp_date (const void *a, const void *b)
{
Message **aa, **bb;
aa = (Message**) a;
bb = (Message**) b;
return strcmp ((*aa)->get_cmpdate(), (*bb)->get_cmpdate());
}
/***************************************************************************
* FUNCTION : cmp_subject
***************************************************************************
*
* description : Function for compare two subject field of a Message_t
* structure.
*
* return : <0, if first is less, 0 if equal, >0 if last is less than first.
***************************************************************************/
int cmp_subject (const void *a, const void *b)
{
int rc;
Message **aa, **bb;
Message *cc, *dd;
const String *s1, *s2;
aa = (Message**) a;
bb = (Message**) b;
cc = aa[0];
dd = bb[0];
s1 = &cc->get_subject();
s2 = &dd->get_subject();
if ((rc = s1->compare (*s2)) == 0) {
rc = cmp_date (a, b);
}
return rc;
}
/***************************************************************************
* FUNCTION : cmp_thread
***************************************************************************
*
* description : Function for compare two stripped subject field of a
* Message_t structure.
*
*
* return : <0, 0 or >0
***************************************************************************/
int cmp_thread (const void *a, const void *b)
{
int rc;
Message **aa, **bb;
Message *cc, *dd;
aa = (Message**) a;
bb = (Message**) b;
cc = aa[0];
dd = bb[0];
rc = cc->get_stripped_subject().compare( dd->get_stripped_subject() );
if ( !rc ) {
rc = cmp_date (a, b);
}
return rc;
}
/***************************************************************************
* FUNCTION : cmp_writer
***************************************************************************
*
* description : compares writer-fields of a two Message_t structure
*
* return : guess what :-)
***************************************************************************/
int cmp_writer (const void *a, const void *b)
{
int rc;
Message **aa, **bb;
Message *cc, *dd;
const String *s1, *s2;
aa = (Message**) a;
bb = (Message**) b;
cc = aa[0];
dd = bb[0];
s1 = &cc->get_writer();
s2 = &dd->get_writer();
if ((rc = s1->compare (*s2)) == 0) {
rc = cmp_date (a, b);
}
return rc;
}
/***************************************************************************
* FUNCTION : cmp_receiver
***************************************************************************
*
* description : compares receiver-fields
*
* return : <0, 0 or >0
***************************************************************************/
int cmp_receiver (const void *a, const void *b)
{
int rc;
Message **aa, **bb;
Message *cc, *dd;
const String *s1, *s2;
aa = (Message**) a;
bb = (Message**) b;
cc = aa[0];
dd = bb[0];
s1 = &cc->get_receiver();
s2 = &dd->get_receiver();
if ((rc = s1->compare (*s2)) == 0) {
rc = cmp_date (a, b);
}
return rc;
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: Group
//**************************************************************************/
//
// Constructor for class.
//
//**************************************************************************/
Group::Group (
const int& g_num, // number of group
const String& g_name) // name of group
{
is_mirror = false;
number = g_num;
unread_msgs = 0;
total_msgs = 0;
status_ = DEFAULT_FL;
name = g_name;
numberstr.put (number, 3);
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: ~Group
//**************************************************************************/
//
// Destructor for class. Removes contents of all articles of group, but
// if group is marked as mirrored, then removes only list of messages,
// not contents of that list...
//
//**************************************************************************/
Group::~Group()
{
DEBUG("Deleting group: "<<number<<" "<<name);
if (is_mirror == true) {
DEBUG("Mirrored!");
messages.leave();
} else {
DEBUG("Normal");
messages.destroy();
}
DEBUG("OK");
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: add
//**************************************************************************/
//
// Adds new article to end of exist articles.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN: true if success.
//**************************************************************************/
bool
Group::add (const Message *msg)
{
++total_msgs;
if (! (msg->get_status() & READ_ST)) {
++unread_msgs;
}
messages.last();
return messages.add (msg);
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: remove_article
//**************************************************************************/
//
// Removes current article. If group is mirrored, then does not free
// memory of listnodes.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Group::remove_article()
{
Message *msg;
msg = messages.get();
if (! (msg->get_status() & READ_ST) ) {
--unread_msgs;
}
--total_msgs;
if (is_mirror == false) {
messages.remove();
} else {
messages.leavenode();
}
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: sort
//**************************************************************************/
//
// Sorts contents of group to using given order
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Group::sort (const int order)
{
switch (order) {
case SUBJECT_SORTED:
messages.sort(cmp_subject);
break;
case DATE_SORTED:
messages.sort(cmp_date);
break;
case WRITER_SORTED:
messages.sort(cmp_writer);
break;
case RECEIVER_SORTED:
messages.sort(cmp_receiver);
break;
case THREAD_SORTED:
messages.sort(cmp_thread);
break;
}
sort_order = order;
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: update
//**************************************************************************/
//
// Updates statistics. Counts amount of unread articles.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Group::update()
{
Message *msg;
messages.save_position();
unread_msgs = 0;
total_msgs = 0;
if (messages.first() == true) {
do {
msg = messages.get();
if (! (msg->get_status() & READ_ST)) {
++unread_msgs;
}
++total_msgs;
} while (messages.next() == true);
}
messages.restore_position();
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: mark_all_read
//**************************************************************************/
//
// Marks all articles as read.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Group::mark_all_read()
{
Message *msg;
messages.save_position();
if (messages.first() == true) {
do {
msg = messages.get();
msg->set_status_bits (READ_ST);
} while (messages.next() == true);
}
unread_msgs = 0;
messages.restore_position();
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: unload
//**************************************************************************/
//
// Removes lines (from memory) of those articles, that have valid handle
// to a file, where lines are stored.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Group::unload()
{
if (messages.first() == true) {
do {
messages.get()->unload_lines();
} while (messages.next() == true);
messages.first();
}
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: Mail
//**************************************************************************/
//
// Constructor for class. This should be called in first constructor of
// derived classes.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
// char *packet_name Name of mail packet (QWK etc). Name must in
// format <bbsid>.XXX.
//
// RETURN:
//**************************************************************************/
Mail::Mail (const char *packet_name)
{
DEBUG("Entering Mail::Mail");
tmp_db_file = NULL;
tmp_log_file = NULL;
num_tagged_groups = 0;
// get id of bbs from name of packet.
bbsid = packet_name;
int i = bbsid.findch ('.');
if (i != NOTHING) {
bbsid.ncut_end (bbsid.length() - i);
}
bbsid.s_downcase();
have_new_replies = false;
new_articles = 0;
articles = 0;
dead_flag = false;
replyhandler = new Replyhandler;
// read bbs related settings from resource file, if it exist.
local_settings.set( bbsid );
// read contents of message database and replylog
Wscript->enable();
Wscript->print( "\nReading message database..." );
Screen.refresh();
read_message_database();
Screen.refresh();
// if previous session was killed, then read replies from
// emergency file
if (ACCESS (DEADJMR_FILENAME, F_OK) == 0) {
read_deadjmr();
}
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: create
//**************************************************************************/
//
// Creates new Mail-object, using child class, that matches with type
// of given mail packet.
//
// This function extracts contents of zipped mail packet, and solves
// type of packet by checking extracted files.
//
// If open_new_packet is false, then opens only exist message database, and
// does not open new mail packet.
//
// EXTERNAL VARIABLE REFERENCES
// IN : Settings.unzipcmd
// OUT:
//
// RETURN: Pointer to created object, or 0 if failed.
//**************************************************************************/
Mail*
Mail::create (
const char* packet_name,
bool open_new_packet)
{
DEBUG("Enterting Mail::create");
String tmp;
Mail *mail = 0;
DEBUG("Clearing working directory");
clear_workdir();
Wscript->enable();
Wscript->print( "\n----------------------------------------------\n");
Wscript->print( "Opening mail packet/database: %s", packet_name );
// extract mail packet
if (open_new_packet == true) {
tmp = Settings.unzipcmd + " " + Settings.qwkpath + packet_name;
if ( jmrsystem (tmp.get()) ) {
char buf[256];
sprintf( buf, "Can't exec '%s' for unpacking mail packet.",
Settings.unzipcmd.c_str() );
handle_error( buf, HALT_FL );
}
}
// check if type of packet is QWK.
if (open_new_packet == false || ACCESS ("control.dat", F_OK) == 0) {
// QWK-packet
DEBUG("Creating Qwk object");
mail = new Qwk (packet_name, open_new_packet);
} else {
handle_error ("Not a QWK packet", HALT_FL);
}
#ifdef USE_DEBUG
DEBUG( "GROUPS:" );
mail->grouplist.first();
do {
DEBUG( mail->grouplist.get()->get_numstr() << " -- '"
<< mail->grouplist.get()->get_name() << "'" );
} while ( mail->grouplist.next() );
mail->grouplist.first();
#endif
return mail;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: open_dead
//**************************************************************************/
//
// Creates new Mail object using killed session.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN: 0 if failed, otherwise returns pointer to created object
//**************************************************************************/
Mail*
Mail::open_dead()
{
Mail *mail = 0;
char line[81];
if (ACCESS (DEADJMR_FILENAME, F_OK) != 0) {
return 0;
}
// read bbsid of killed session
ifstream fin (DEADJMR_ID_FILENAME);
if (fin.fail()) {
return 0;
}
fin.getline (line, 80);
fin.close();
// check type of killed session
if (ACCESS ("control.dat", F_OK) == 0) {
// Type was QWK
mail = new Qwk (line, true);
} else {
// if type of killed session is unknown, then read only
// contents of message database and keep new replies
// of killed session
mail = new Qwk (line, false);
}
return mail;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: init
//**************************************************************************/
//
// Moves messages from list 'packet_articles' to message database, and
// sets up all necessary values. Function also removes deadjmr-files.
//
// NOTE: this function must be called before returned from constructor
// of derived class!
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT: Settings.is_pcboard, Settings.max_subject_len
//
// RETURN:
//**************************************************************************/
void
Mail::init()
{
Wscript->enable();
DEBUG("Moving messages to database");
move_messages_to_base();
DEBUG("OK");
DEBUG("Setting up personal mail");
set_personal_mail();
DEBUG("OK");
Wscript->print( "\nCreating article threads..." );
Screen.refresh();
clock_t start_time = clock();
thread_sort_articles();
clock_t end_time = clock();
Wscript->print("OK (sorted in %.2f seconds)",
float((end_time - start_time) /(float) CLOCKS_PER_SEC));
go_group_number (NEW_REPLIES_NUMBER);
if (count_articles() > 0) {
have_new_replies = true;
}
// check if bbs is useing PCBOARD-software, that allows
// long subjects with QWK-packets.
if (door.ifind ("pcboard") != NOTHING) {
Settings.is_pcboard = true;
Settings.max_subject_len = JMR_MAX_SUBJECT_LEN;
} else {
Settings.is_pcboard = false;
Settings.max_subject_len = MAX_SUBJECT_LEN;
}
remove (DEADJMR_FILENAME);
remove (DEADJMR_ID_FILENAME);
write_recovery_file();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: emergency_exit
//**************************************************************************/
//
// Writes new replies to file 'deadjmr.msg' and sets flag
// dead_flag to true. Also writes ID of bbs to file 'deadjmr.id'.
//
// This function should be called if program is terminated abnormally.
//
// EXTERNAL VARIABLE REFERENCES
// IN : -
// OUT: -
//
// RETURN: -
//**************************************************************************/
void
Mail::emergency_exit()
{
dead_flag = true;
write_recovery_file();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_recovery_file
//**************************************************************************/
//
// Writes new replies to recovery file 'deadjmr.msg' and writes ID of the
// bbs to file 'deadjmr.id'.
//
// NOTES: This saves and restores postion in list, so do not call this
// when you have saved group position.
//
// EXTERNAL VARIABLE REFERENCES
// IN : -
// OUT: -
//
// PARAMETERS: -
//
// RETURN: -
//**************************************************************************/
void
Mail::write_recovery_file( void )
{
DEBUG("Creating recovery files");
String fname = DEADJMR_ID_FILENAME;
// write bbsid to file 'deadjmr.id'
ofstream fout (fname.get());
if (fout.fail()) {
system_error ("Cannot write recovery file.. Sad but true.",
HALT_FL);
}
fout << bbsid;
fout.close();
// then write new replies to file 'deadjmr.msg'.
fname = DEADJMR_FILENAME;
if (false == dead_flag) {
grouplist.save_position();
}
go_group_number( NEW_REPLIES_NUMBER );
write_log( fname, NEW_REPLIES_NUMBER, count_articles() +1, true );
if (false == dead_flag) {
grouplist.restore_position();
}
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: ~Mail
//**************************************************************************/
//
// Destructor for class. Writes message database, replylog (if session
// was not killed).
// Removes temporary files.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
Mail::~Mail()
{
Wscript->enable();
if (dead_flag == false) {
Wscript->print( "\nWriting message database..." );
Screen.refresh();
write_message_database();
Wscript->print("OK");
Screen.refresh();
}
delete replyhandler;
DEBUG("Removing database");
grouplist.destroy();
DEBUG("Database removed!");
DEBUG("Closing files");
if (tmp_db_file != 0) {
tmp_db_file->close();
remove (tmp_db_name.get());
delete tmp_db_file;
}
if (tmp_log_file != 0) {
tmp_log_file->close();
remove (tmp_log_name.get());
delete tmp_log_file;
}
// restore original settings
local_settings.restore();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: move_messages_to_base
//**************************************************************************/
//
// Moves contents of packet_articles list to message database. If some of
// articles exist already in database, then ask from user, if it is ok to
// replace all exiest articles with newer.
//
// After function, list 'packet_articles' will be empty.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Mail::move_messages_to_base()
{
Group *grp, *tmp_grp;
Message *msg, *tmp_msg;
int number;
bool replace = false;
bool load = true;
new_articles = 0;
if (packet_articles->first() == false) {
DEBUG( "** No new articles from packet! ** ");
return;
}
// go through every group readed from packet
do {
grp = packet_articles->get();
assert (grp != 0);
if (false == go_group_number (grp->get_number())) {
tmp_grp = new Group (grp->get_number(),
grp->get_name());
grouplist.add (tmp_grp);
} else {
tmp_grp = grouplist.get();
if ( tmp_grp->get_name() != grp->get_name() ) {
tmp_grp->set_name( grp->get_name() );
}
}
assert (tmp_grp != 0);
if (grp->first_article() == false) {
continue;
}
// go through every article within a single group from packet
do {
msg = grp->get_article();
assert (msg != 0);
number = msg->get_number();
if (REPLYLOG_NUMBER == tmp_grp->get_number()
|| NEW_REPLIES_NUMBER==tmp_grp->get_number()) {
tmp_grp->add (msg);
continue;
} else if (false == tmp_grp->first_article()) {
tmp_grp->add (msg);
++new_articles;
continue;
} else if (false == load) {
continue;
}
do {
tmp_msg = tmp_grp->get_article();
if (tmp_msg->get_number() == number) {
if (false == replace) {
load = ask_replace();
if (false == load) {
break;
}
replace = true;
}
// remove old one, if exist.
tmp_grp->remove_article();
break;
}
} while (true == tmp_grp->next_article());
// add new article to group
if (true == load) {
tmp_grp->add (msg);
++new_articles;
}
} while (true == grp->next_article());
} while (true == packet_articles->next());
packet_articles->leave();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: ask_replace
//**************************************************************************/
//
// Asks if user wants to load contents of mail packet, even if articles
// are already in message database.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN: true if it's ok to load packet.
//**************************************************************************/
bool
Mail::ask_replace() const
{
Wscript->enable();
Wscript->print( "\n\nArticle is already in database. " );
Wscript->print( "\nReload contents of mail packet to database (y/n) y" );
int c = get_yesno ('y');
if (c == 'y') {
Wscript->print( "\bYes" );
Screen.refresh();
return true;
}
Wscript->print( "\bNo!" );
Screen.refresh();
return false;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_message_database
//**************************************************************************/
//
// Reads contents of message database and replylog. Does not read lines
// of articles to memory, reads only headers at this point. Lines are
// readed only when it is necessary (using Message::first_line() method).
//
// Before reading, original database and replylog is copied to temporaly
// files.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Mail::read_message_database()
{
Group *grp;
String file = Settings.datadir + bbsid + ".gdb";
String mfile = Settings.datadir + bbsid + ".mdb";
String logfile = Settings.datadir + bbsid + ".log";
// remove exist articles.
// List should already be empty before this but...
grouplist.destroy();
// add basegroups to begin of list.
grp = new Group (PERSONAL_NUMBER, PERSONAL_TEXT);
grp->set_mirror();
grouplist.add (grp);
grp = new Group (REPLYLOG_NUMBER, REPLYLOG_TEXT);
grouplist.add (grp);
grp = new Group (NEW_REPLIES_NUMBER, NEW_REPLIES_TEXT);
grouplist.add (grp);
grp = new Group (TAGGED_ARTICLES_NUMBER, TAGGED_ARTICLES_TEXT);
grp->set_mirror();
grouplist.add (grp);
grp = new Group (SEARCH_RESULTS_NUMBER, SEARCH_RESULTS_TEXT);
grp->set_mirror();
grouplist.add (grp);
DEBUG("Reading message database and replylog");
if (ACCESS (file.get(), F_OK) == 0 &&
ACCESS (mfile.get(), F_OK) == 0) {
DEBUG("Database exist!");
tmp_db_name = get_tmpname( "jmr" );
if (copy_file (mfile.get(), tmp_db_name.get()) == false) {
char buffer[256];
sprintf (buffer, "Cannot create temp file : %s\n",
tmp_db_name.get());
system_error(buffer, HALT_FL );
}
read_groups_from_db();
read_articles_from_db();
Wscript->print("OK");
}
if (ACCESS (logfile.get(), F_OK) == 0) {
tmp_log_name = get_tmpname( "jmr" );
if (copy_file (logfile.get(), tmp_log_name.get()) == false) {
char buffer[256];
sprintf (buffer, "Cannot create temp file : %s\n",
tmp_db_name.get());
system_error (buffer, HALT_FL);
}
read_replylog();
}
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_groups_from_db
//**************************************************************************/
//
// Reads data of bbs, names and numbers of groups from group database
// (<bbsid>.gdb).
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Mail::read_groups_from_db()
{
int pos = 0;
int num;
String name;
String file;
Group *grp;
char *buffer;
int size = 0;
assert (bbsid.is_empty() == false);
file = Settings.datadir + bbsid + ".gdb";
DEBUG("Reading groups : "<< file.get());
fstream fin (file.get(), ios::in
#ifndef __unix__
| ios::binary
#endif
);
if (fin.fail()) {
system_error ("Cannot open message database", HALT_FL);
}
fin.seekg (0, ios::end);
size = fin.tellg();
fin.seekg (0);
// read whole file to buffer
buffer = new char [size + 1];
fin.read (buffer, size);
fin.close();
// get information about bbs from buffer
pos += bbsname.put (buffer) + 1;
DEBUG( "Readed from gdb: '" << bbsname << "'" );
pos += city.put (buffer + pos) +1;
DEBUG( "Readed from gdb: '" << city << "'" );
pos += phone.put (buffer + pos) +1;
DEBUG( "Readed from gdb: '" << phone << "'" );
pos += sysop.put (buffer + pos) +1;
DEBUG( "Readed from gdb: '" << sysop << "'" );
pos += door.put (buffer + pos) +1;
pos += time.put (buffer + pos) +1;
pos += username.put (buffer + pos) +1;
username.convert_charset (LOG_CHARSET, Settings.syscharset);
username.s_capitalize();
Settings.username = username;
if ( door.ifind( "pcboard" ) != NOTHING ) {
Settings.is_pcboard = true;
Settings.max_subject_len = JMR_MAX_SUBJECT_LEN;
}
// get groups from buffer
while (pos < size) {
num = atoi (buffer + pos);
pos += strlen (buffer + pos) + 1;
pos += name.put (buffer + pos) +1;
name.convert_charset (LOG_CHARSET, Settings.syscharset);
DEBUG( "Read group from db: '" << name << "'" );
grp = new Group (num, name);
DEBUG( "Adding group from db: '" << name << "'" );
grouplist.add (grp);
}
delete[] buffer;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_articles_from_db
//**************************************************************************/
//
// Read articles from temporaly copy of message database. Does not
// read lines of articles to memory, they should be read only when
// it is necessary. This program is not memory monster like Microsoft...
//
// File opened here must be keep opened until the destructor closes it.
//
// EXTERNAL VARIABLE REFERENCES
// IN : Settings.syscharset
// OUT:
//
// RETURN:
//**************************************************************************/
void
Mail::read_articles_from_db()
{
Message *msg;
Group *grp;
Group *tag;
char idcode[4];
go_group_number (TAGGED_ARTICLES_NUMBER);
tag = grouplist.get();
assert (bbsid.is_empty() == false);
assert (tmp_db_file == NULL);
DEBUG("Reading articles : "<< tmp_db_name.get());
tmp_db_file = new fstream (tmp_db_name.get(), ios::in
#ifndef __unix__
| ios::binary
#endif
);
if (tmp_db_file->fail()) {
system_error ("Cannot open message database", HALT_FL);
}
tmp_db_file->read ((char*) idcode, 4); // skip over id code
while (NULL != (msg=read_msg_from_log(*tmp_db_file, HEADER_ONLY_FL))) {
if (go_group_number (msg->get_group_number()) == true) {
grp = grouplist.get();
} else {
grp = new Group (msg->get_group_number(), "Unknown");
grouplist.add (grp);
}
msg->set_charset (LOG_CHARSET);
msg->translate (Settings.syscharset);
grp->add (msg);
if (msg->get_status() & TAGGED_ST) {
tag->add (msg);
}
#ifdef HASH_HACK
char a[30];
char b[15];
// unsigned int hkey = ((unsigned) msg->get_group_number()) << 17;
// hkey |= msg->get_number();
msg->get_subject().get_at( a, 10 );
msg->get_writer().get_at( b, 10 );
strcat( a, b );
unsigned int hkey = msg->get_group_number() * msg->get_number();
/* unsigned int foo = 1;
for ( int x = 0; a[x]; ++x ) {
foo = a[x] + 31 * foo;
}
hkey *= foo; */
cerr << "Hashkey: " << hkey << endl;
#endif
}
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_deadjmr
//**************************************************************************/
//
// Read replies of killed session.
// NOTE: contents of message database must be read before this!
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Mail::read_deadjmr()
{
fstream fin;
Message *msgdata;
Group *grp;
char idcode[4];
fin.open (DEADJMR_FILENAME, ios::in
#ifndef __unix__
| ios::binary
#endif
);
if (fin.fail()) {
return;
}
// skip over idcode
fin.read ((char*) idcode, 4);
go_group_number (NEW_REPLIES_NUMBER);
grp = grouplist.get();
msgdata = read_msg_from_log (fin);
while (msgdata != NULL) {
msgdata->set_charset (LOG_CHARSET);
msgdata->translate (Settings.syscharset);
grp->add (msgdata);
msgdata = read_msg_from_log (fin);
}
fin.close();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_message_database
//**************************************************************************/
//
// Writes new message database and replylog
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN:
//**************************************************************************/
void
Mail::write_message_database()
{
write_groups_to_db();
write_articles_to_db();
write_replylog();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_groups_to_db
//**************************************************************************/
//
// Writes information of bbs and numbers and names of each group to
// database file <bbsid>.gdb
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN: true
//**************************************************************************/
bool
Mail::write_groups_to_db()
{
Group *grp;
String str;
String file;
file = Settings.datadir + bbsid + ".gdb";
fstream fout (file.get(), ios::out | ios::trunc
#ifndef __unix__
| ios::binary
#endif
);
if (fout.fail()) {
system_error ("Cannot write message database", HALT_FL);
}
// Write information about bbs to file
DEBUG( "writing to gdb: '" << bbsname.get() << "'" );
fout.write (bbsname.get(), bbsname.length()+1);
DEBUG( "writing to gdb: '" << city.get() << "'" );
fout.write (city.get(), city.length()+1);
DEBUG( "writing to gdb: '" << phone.get() << "'" );
fout.write (phone.get(), phone.length()+1);
DEBUG( "writing to gdb: '" << sysop.get() << "'" );
fout.write (sysop.get(), sysop.length()+1);
fout.write (door.get(), door.length()+1);
fout.write (time.get(), time.length()+1);
username.convert_charset (Settings.syscharset, LOG_CHARSET);
fout.write (username.get(), username.length()+1);
// write number and name of each group
grouplist.first();
do {
grp = grouplist.get();
if (grp->get_number() < BASEGROUP_NUMBER) {
fout.write (grp->get_numstr().get(),
grp->get_numstr().length()+1);
str = grp->get_name();
str.convert_charset (Settings.syscharset, LOG_CHARSET);
fout.write (str.get(), str.length()+1);
DEBUG( "Writing group to gdb: '"<<str<<"'");
}
} while (grouplist.next() == true);
fout.close();
return true;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_articles_to_db
//**************************************************************************/
//
// Writes articles to message database.
//
// Before writing, all articles are copied to one list and sorted in order
// of date. Only Settings.databasesize newest articles are writed to file.
//
// EXTERNAL VARIABLE REFERENCES
// IN : Settings.databasesize, Settings.datadir
// OUT:
//
// RETURN:
//**************************************************************************/
bool
Mail::write_articles_to_db()
{
Group *grp;
Message *msg;
List<Message> msglist;
String file;
char idcode[4];
DEBUG("Writing message database");
file = Settings.datadir + bbsid + ".mdb";
fstream fout (file.get(), ios::out | ios::trunc
#ifndef __unix__
| ios::binary
#endif
);
if (fout.fail()) {
system_error ("Cannot write message database", HALT_FL);
}
put_le_int32 ((byte_t*) idcode, LOG_IDCODE);
fout.write (idcode, 4);
// add all articles to single list
grouplist.first();
do {
grp = grouplist.get();
if (grp->get_number() < BASEGROUP_NUMBER
&& grp->first_article() == true) {
do {
msg = grp->get_article();
msglist.add (msg);
} while (grp->next_article() == true);
}
} while (grouplist.next() == true);
// sort articles by date
DEBUG("Sorting articles by date...");
#ifdef USE_DEBUG
clock_t start_time = clock();
#endif
msglist.sort( cmp_date );
#ifdef USE_DEBUG
clock_t end_time = clock();
cerr <<"Sorted in "<< float((end_time - start_time) / (float) CLK_TCK)
<< " seconds";
#endif
DEBUG("OK");
// Check if size of database is larger than limit.
msglist.first();
int i = msglist.count_items() - Settings.databasesize;
DEBUG("Trashing "<<i<<" oldest articles");
// If size is too big, then mark oldest i articles
// with killed flag.
if (i > 0 ) {
do {
msg = msglist.get();
// If article is not tagged and is not marked as
// killed, then mark it killed now.
if (! (msg->get_status() & TAGGED_ST) &&
!msg->is_killed()) {
msg->invert_kill_flag();
--i;
}
} while (i > 0 && msglist.next());
}
// Write database, skip killed articles.
msglist.first();
do {
msg = msglist.get();
if (msg->is_killed() == true &&
(msg->get_status() & TAGGED_ST) ) {
msg->reset_status_bits( TAGGED_ST);
} else if (msg->is_killed() ) {
continue;
}
msg->first_line();
msg->translate (LOG_CHARSET);
write_msg_to_log (fout, *msg);
// remove lines of article from memory
msg->unload_lines();
} while (msglist.next() == true);
msglist.leave();
fout.close();
DEBUG("OK");
return true;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_replylog
//**************************************************************************/
//
// Reads old replies of current bbs from temporaly replylog file.
// Does not read lines of articles to memory.
//
// NOTE: Variable tmp_log_name must be set before use this!
//
// EXTERNAL VARIABLE REFERENCES
// IN : Settings.datadir, Settings.syscharset
// OUT:
//
//**************************************************************************/
void
Mail::read_replylog()
{
Message *msgdata;
Group *grp;
byte_t idcode[4];
Message* (*read_func)(fstream&, int);
assert (tmp_log_file == NULL);
assert (tmp_log_name.is_empty() == false);
DEBUG("Reading replylog : "<<tmp_log_name.get());
tmp_log_file = new fstream (tmp_log_name.get(), ios::in
#ifndef __unix__
| ios::binary
#endif
);
if (tmp_log_file->fail()) {
return;
}
Wscript->enable();
Wscript->print( "\nReading replylog..." );
Screen.refresh();
// select right function for reading data from log
tmp_log_file->read ((char*) idcode, 4);
if (get_le_int32 (idcode) != LOG_IDCODE) {
Wscript->print( "\nObsolete log format detected. Converting...");
Screen.refresh();
read_func = read_msg_from_obsolete_log;
} else {
read_func = read_msg_from_log;
}
go_group_number (REPLYLOG_NUMBER);
grp = grouplist.get();
msgdata = read_func (*tmp_log_file, HEADER_ONLY_FL);
while (msgdata != NULL) {
msgdata->set_charset (LOG_CHARSET);
msgdata->translate (Settings.syscharset);
grp->add (msgdata);
msgdata = read_func (*tmp_log_file, HEADER_ONLY_FL);
}
Wscript->print( "OK" );
Screen.refresh();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_replylog
//
//
// Writes replylog of current bbs to file <bbsid>.log.
//
//
// EXTERNAL REFERENCES:
// IN : Settings.datadir, Settings.replylogsize
// OUT:
//**************************************************************************/
void
Mail::write_replylog()
{
String fname = Settings.datadir + bbsid + ".log";
write_log (fname, REPLYLOG_NUMBER, Settings.replylogsize);
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_log
//**************************************************************************/
// Writes contents of given group to given file. If group has more articles
// than given maximum count is, then writes only newest <maxsize> articles.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
//**************************************************************************/
void
Mail::write_log (const String &fname, // name of file
const int group_number,// number of group
const int maxsize, // max. number of articles
bool keep_charset) // should charset restored after writing
{
int size,i;
Message *msg;
char idcode[4];
go_group_number (group_number);
// check size of log. Return immediately if there's no replies
size = count_articles();
if (size == 0) {
return;
}
DEBUG("Writing replies to log");
first_article();
DEBUG("Size of logfile is : "<<size);
DEBUG("Max. size is : "<<maxsize);
// skip over oldest articles, if count of articles > maxsize
if (size > maxsize) {
sort (DATE_SORTED);
i = size - maxsize;
DEBUG("Trashing "<<i<<" articles...");
while (i) {
next_article();
i--;
}
}
fstream file (fname.get(), ios::out | ios::trunc
#ifndef __unix__
| ios::binary
#endif
);
if (file.fail()) {
system_error ("Cannot write log");
return;
}
// write contents of log
put_le_int32 ((byte_t*)idcode, LOG_IDCODE);
file.write (idcode, 4); // tunniste
do {
msg = get_article();
if (msg->is_killed() == false) {
// mark article as unread
// msg->reset_status_bits (READ_ST);
msg->first_line();
msg->translate (LOG_CHARSET);
write_msg_to_log (file, *msg);
if ( keep_charset == true ) {
msg->translate( Settings.syscharset );
}
msg->unload_lines();
}
} while (next_article() == true);
file.close();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: set_personal_mail
//**************************************************************************/
//
// Copies pointer of each article, which's receiver matches with
// Settings.username to group 'Personal Mail'. (Personal articles are mirrored
// to group 'Personal Mail').
//
// EXTERNAL VARIABLE REFERENCES
// IN : Settings.username
// OUT:
//
// RETURN:
//**************************************************************************/
void
Mail::set_personal_mail()
{
Group *personal, *grp;
Message *msg;
go_group_number (PERSONAL_NUMBER);
personal = grouplist.get();
assert (personal != 0);
grouplist.first();
do {
grp = grouplist.get();
if (grp->get_number() >= BASEGROUP_NUMBER) {
continue;
}
if (grp->first_article() == false) {
continue;
}
do {
msg = grp->get_article();
assert (msg != NULL);
if (Settings.username == msg->get_receiver()) {
personal->add (msg);
}
} while (grp->next_article() == true);
} while (grouplist.next() == true);
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: thread_sort_articles
//**************************************************************************/
//
// Sorts contents of basegroups by subject, and other groups by threads.
//
//**************************************************************************/
void
Mail::thread_sort_articles ()
{
// contents first 4 groups is sorted by subject (basegroups)
grouplist.first();
assert (grouplist.first() == true);
for (int i = 0; i < FIRST_GROUP_FROM_PACKET; ++i) {
sort (SUBJECT_SORTED);
grouplist.next();
}
// and contents of rest groups is sorted by threads
do {
sort (THREAD_SORTED);
articles += count_articles();
} while (true == grouplist.next());
grouplist.first();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: sort
//**************************************************************************/
//
// Sorts messages of current group to given order. After this function,
// current position within group will be first article.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
//**************************************************************************/
void
Mail::sort (const int order)
{
Group *tmp;
int num;
tmp = grouplist.get();
assert (tmp != 0);
DEBUG("Sorting group: '" << grouplist.get()->get_name() <<"'");
tmp->sort (order);
num = tmp->get_number();
if (num == PERSONAL_NUMBER) {
return;
}
tmp->first_article();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: operator []
//**************************************************************************/
//
// Moves position in list of groups to given index and returns reference
// to that group. If index is out of range, then returned group will be
// last group of list.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN: reference to group
//**************************************************************************/
const Group&
Mail::operator [] (int index)
{
grouplist.first();
for (int i = 0; i < index; i++) {
grouplist.next();
}
assert (grouplist.get() != 0);
return *(grouplist.get());
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: go_group_index
//**************************************************************************/
//
// Goes to group of given index.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN: false if given index is out of range
//**************************************************************************/
bool
Mail::go_group_index (const int n)
{
if (n >= grouplist.count_items()) {
return false;
}
(*this)[n];
return true;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: go_group_number
//**************************************************************************/
//
// Moves position in list of groups to group, that have number matches
// with given value. If group not found, then position will be last group
// of list.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// RETURN: true if group exist. false if not.
//**************************************************************************/
bool
Mail::go_group_number(const int num)
{
Group *tmp;
grouplist.first();
do {
tmp = grouplist.get();
if (tmp->get_number() == num)
return true;
} while (grouplist.next() == true);
return false;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: add_reply
//**************************************************************************/
//
// Adds new reply to end of list of new replies.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
//**************************************************************************/
void
Mail::add_reply (Message *reply)
{
grouplist.save_position();
go_group_number (NEW_REPLIES_NUMBER);
grouplist.get()->add (reply);
grouplist.restore_position();
have_new_replies = true;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: kill_reply
//**************************************************************************/
//
// Removes given reply from list of new replies.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//**************************************************************************/
void
Mail::kill_reply (const Message *msg)
{
Message *tmp;
Group *grp;
grouplist.save_position();
go_group_number (NEW_REPLIES_NUMBER);
grp = grouplist.get();
grouplist.restore_position();
grp->first_article();
do {
tmp = grp->get_article();
if (msg == tmp) {
grp->remove_article();
break;
}
} while (grp->next_article() == true);
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: tag_article
//**************************************************************************/
//
// Marks current message as tagged, or untags message if it's
// already tagged.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//**************************************************************************/
void
Mail::tag_article (Message *msg)
{
Message *tmp;
Group *grp;
int number;
if (!msg) {
return;
}
msg->inv_status_bits (TAGGED_ST);
grouplist.save_position();
go_group_number (TAGGED_ARTICLES_NUMBER);
grp = grouplist.get();
// add message to tagged list list
if (msg->get_status() & TAGGED_ST) {
grp->add (msg);
} else {
// remove message from tagged article list
grp->first_article();
number = msg->get_number();
do {
tmp = grp->get_article();
if (tmp->get_number() == number) {
grp->remove_article();
break;
}
} while (grp->next_article() == true);
}
grouplist.restore_position();
}
int
Mail::search (char* pattern, int mode)
{
int count = 0;
assert( mode & ALL_FL || mode & TAGGED_FL || mode & CURRENT_FL );
// search
if (mode & ALL_FL) {
count = search_all( pattern, mode );
} else if (mode & TAGGED_FL) {
count = search_tagged( pattern, mode );
} else {
count = search_current( pattern, mode );
}
return count;
}
int
Mail::search_all (char* pattern, int mode)
{
Group *grp;
Group *search_grp;
Message *msg;
int count = 0;
int searched_articles = 0;
float percent = 0;
save_group_pos();
// remove results of previous search
go_group_number (SEARCH_RESULTS_NUMBER);
search_grp = grouplist.get();
if (search_grp->first_article() == true) {
do {
search_grp->remove_article();
} while (search_grp->first_article() == true);
}
go_group_index(FIRST_GROUP_FROM_PACKET);
do {
Screen.bottom();
Screen.print( "%d%% of articles searched", (int) percent );
Screen.refresh();
grp = grouplist.get();
if (grp->first_article() == false) {
continue;
}
do {
msg = grp->get_article();
if (msg->search( pattern, mode ) == true) {
++count;
search_grp->add (msg);
}
++searched_articles;
} while (grp->next_article() == true);
percent = (float) searched_articles / (float) articles;
percent *= 100;
} while (grouplist.next() == true);
restore_group_pos();
return count;
}
int
Mail::search_current (char* pattern, int mode)
{
Group *grp;
Group *search_grp;
Message *msg;
int count = 0;
save_group_pos();
// remove results of previous search
go_group_number (SEARCH_RESULTS_NUMBER);
search_grp = grouplist.get();
if (search_grp->first_article() == true) {
do {
search_grp->remove_article();
} while (search_grp->first_article() == true);
}
restore_group_pos();
grp = grouplist.get();
if (grp->first_article() == false) {
return 0;
}
do {
msg = grp->get_article();
if (msg->search( pattern, mode ) == true) {
++count;
search_grp->add( msg );
}
} while (grp->next_article() == true);
return count;
}
int
Mail::search_tagged (char* pattern, int mode)
{
int count = 0;
Group *grp;
Group *search_grp;
Message *msg;
int searched_articles = 0;
int all_articles = 0;
float percent = 0;
save_group_pos();
// remove results of previous search
go_group_number (SEARCH_RESULTS_NUMBER);
search_grp = grouplist.get();
if (search_grp->first_article() == true) {
do {
search_grp->remove_article();
} while (search_grp->first_article() == true);
}
go_group_index(FIRST_GROUP_FROM_PACKET);
// count amount of articles to search
do {
grp = grouplist.get();
if (grp->is_tagged() == false) {
continue;
}
if (grp->first_article() == false) {
continue;
}
all_articles += grp->count_articles();
} while (grouplist.next() == true);
// search
go_group_index( FIRST_GROUP_FROM_PACKET);
do {
Screen.bottom();
Screen.print("%d%% of articles searched", (int) percent );
Screen.refresh();
grp = grouplist.get();
if (grp->is_tagged() == false) {
continue;
}
if (grp->first_article() == false) {
continue;
}
do {
msg = grp->get_article();
if (msg->search( pattern, mode ) == true) {
++count;
search_grp->add (msg);
}
++searched_articles;
} while (grp->next_article() == true);
percent = (float) searched_articles / (float) all_articles;
percent *= 100;
} while (grouplist.next() == true);
restore_group_pos();
return count;
}
void
Mail::tag_group()
{
if (grouplist.get()->get_number() >= BASEGROUP_NUMBER) {
return;
}
grouplist.get()->tag();
if (grouplist.get()->is_tagged() == true) {
++num_tagged_groups;
} else {
--num_tagged_groups;
}
}
int
Mail::count_tagged_groups() const
{
return num_tagged_groups;
}
//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: set
//**************************************************************************/
//
// This reads settings defined in file <bbsid>.rc, and replaces
// original settings using them.
// Contents of original Settings-structure are stored and them
// can be restored when these settings are no longer needed.
//
// EXTERNAL VARIABLE REFERENCES
// IN : Settings.jmrdir
// OUT: Settings
//
// PARAMETERS: id of the current bbs.
//
// RETURN: true if new settings was defined, false if not
//**************************************************************************/
bool
bbs_settings::set (String& bbsid)
{
rcfname = Settings.jmrdir + bbsid + ".rc";
if ( ACCESS( rcfname.get(), F_OK) ) {
save_flag = false;
return false;
}
if (read_resources() == false) {
save_flag = false;
return false;
}
String value;
saved = Settings;
save_flag = true;
DEBUG("Setting up bbs related resources");
value = trie.get_value( RC_BBSCHARSET );
if ( value.is_empty() == false ) {
if (value == "ibmpc") {
Settings.bbscharset = IBM_PC_CHARSET;
} else if (value == "latin1") {
Settings.bbscharset = ISO_LATIN_CHARSET;
} else {
Settings.bbscharset = ASCII_7BIT_CHARSET;
}
}
value = trie.get_value( RC_PTAG );
if (value.is_empty() == false) {
Settings.ptagline = value;
}
value = trie.get_value( RC_TAG );
if (value.is_empty() == false) {
Settings.tagline = value;
}
value = trie.get_value( RC_QUOTEPREFIX );
if (value.is_empty() == false) {
value.replace_ch( '_', ' ' );
Settings.quoteprefix = value;
}
value = trie.get_value( RC_REPLYLOGSIZE );
if (value.is_empty() == false) {
Settings.replylogsize = atoi( value.get() );
}
value = trie.get_value( RC_DATABASESIZE );
if (value.is_empty() == false) {
Settings.databasesize = atoi( value.get() );
}
value = trie.get_value( RC_EXTRACTDIR );
if (value.is_empty() == false) {
correct_path( value );
Settings.extractdir = value;
}
value = trie.get_value( RC_FOLLOWUPSTR );
if (value.is_empty() == false) {
Settings.followup = value;
}
value = trie.get_value( RC_REPLYSTR );
if (value.is_empty() == false) {
Settings.reply = value;
}
return true;
}
//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: read_resources
//**************************************************************************/
//
// This function reads contents of a resource file to a tree structure.
// If syntax of the resource file is not correct, then program is halted.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN: true if syntax of the file was right.
//**************************************************************************/
bool
bbs_settings::read_resources()
{
String tmp;
char line[MAX_LINE_LEN+1];
int linenum = 1;
bool rc = true;
ifstream rcfile( rcfname.get(), ios::in );
if (rcfile.fail()) {
String err_str = "Cannot read resource file : " + rcfname;
system_error (err_str.get());
return false;
}
Wscript->enable();
Wscript->print("\nParsing resource file: \'%s\'....", rcfname.c_str());
Screen.refresh();
do {
rcfile.getline (line, MAX_LINE_LEN);
tmp = line;
// remove preceeding spaces
tmp.cut_first();
// if line is not empty and it is not commented,
// then check syntax of line and get value
// of keyword.
if (tmp.is_empty() == false && tmp[0] != '#') {
// remove trailing spaces
tmp.cut_tail();
// parse the line
if (parse_rcline( tmp, linenum ) == false) {
rc = false;
}
}
++linenum;
} while (!rcfile.eof());
rcfile.close();
if (rc == false) {
handle_error("Fatal errors in bbs related resource file. Game over - Insert a coin.",
HALT_FL);
}
Wscript->print( "OK" );
Screen.refresh();
return true;
}
//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: parse_rcline
//**************************************************************************/
//
// Checks that syntax of a line is right, and initializes keyword that
// is defined in a line with a value given to it.
// If syntax error occures, then false is returned.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS: line Contents of the current line
// num Number of the line
//
// RETURN: true if syntax of the line was right, false if not.
//**************************************************************************/
bool
bbs_settings::parse_rcline (String& line, int num)
{
int n=0;
String tmp;
// check that assigment operator is in it's place
n = line.findch ('=');
if (n == NOTHING) {
String error_str ;
error_str = "Assigment operator is missing at line ";
error_str += num;
handle_error( error_str.get(), REPORT_FL);
return false;
}
// Then get word before an assigment operator and check
// if it matches with some of keywords in a trie
tmp.copy( line, 0, n);
tmp.cut_tail();
if (false == trie.goto_keyword( tmp )) {
String error_str = "At line ";
error_str += num;
error_str += ": Illegal keyword : \'" + tmp + "\'";
handle_error( error_str.get(), REPORT_FL);
return false;
}
// Check that given value is legal, and store it
// in a trie.
tmp.copy( line, n + 1 );
tmp.cut_first();
tmp.cut_tail();
if (false == trie.set_value( tmp )) {
String error_str = "Invalid value at line ";
error_str += num;
error_str += ": \'" + tmp + "\'";
handle_error( error_str.get(), REPORT_FL );
return false;
}
return true;
}
//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: restore
//**************************************************************************/
//
// This function restores original settings.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT: Settings
//
// PARAMETERS:
//
// RETURN: always true
//**************************************************************************/
bool
bbs_settings::restore ()
{
if (save_flag == true) {
DEBUG("Restoring original settings.");
Settings = saved;
save_flag = false;
}
return true;
}
//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: bbs_settings
//**************************************************************************/
//
// Constructor for class. Initializes syntax trie using values defined
// in static array bbs_setting_array
//
// EXTERNAL VARIABLE REFERENCES
// IN : bbs_setting_array
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
bbs_settings::bbs_settings()
{
save_flag = false;
trie.init( bbs_setting_array );
}
jmrsrc-0.7.23/src/menu.cc 100664 764 764 64535 6623366026 14650 0 ustar jvuokko jvuokko // $Id: menu.cc,v 1.16 1997/08/28 18:21:34 jvuokko Exp $
/****************************************************************************
* *
* * MODULE : menu.cc
* *
* * Copyright (c) 1997 Jukka Vuokko
* * See file COPYING for more information about copyrights.
* *
****************************************************************************
* *
* * Functions for handling group selection menu.
* *
* *
* *
* *
* *
***************************************************************************/
#include <time.h> // testiä varten
#include <iostream.h>
#include "jmr.hh"
#include "menu.hh"
#include "mail.hh"
#include "replies.hh"
#include "menubar.hh"
extern Terminal_screen Screen;
extern Window* Wscript;
extern settings_t Settings;
extern Mail* mail;
static const menuinfo group_menu_array[] = {
{"[jmr]", 0 },
{"Exit from menu", 0},
{"Back to mail packet selection", QUIT_CMD},
{"Quit jmr", FORCED_QUIT_CMD},
{"@NEXT", 0 },
{"[Tagging]", 0 },
{"Tag/untag group", TAG_CMD },
{"Tag/untag all", TAG_ALL_CMD },
{"@NEXT", 0 },
{"Write", WRITE_CMD },
{"@NEXT", 0 },
{"Yank in/out", YANK_IN_OUT_CMD },
{"@NEXT", 0 },
{"Search", SEARCH_CMD },
{"@NEXT", 0 },
{"@END", 0 }
};
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: Groupmenu
//
// description: Constructor. This is main function of class.
// NOTE: Mail *mail must be initialized before use this!!
//
// in: Screen, Quit_flag
// out: Quit_flag
//
//**************************************************************************/
Groupmenu::Groupmenu()
{
int c;
int tmp = 0;
Msgmenu msgmenu; // thread selection level
bool quit = false;
show_all_groups = false;
pages = 0;
ymax = Screen.get_ymax() - 2;
ymin = 6;
ypos = ymin;
groups_per_page = ymax - ymin +1;
current_page = 0;
first_of_page = 0;
current_group = 0;
headerline = "------------ Group Name of the Group ----------- Unread Total ";
for (c = Screen.get_xmax() - headerline.length(); c >= 0; c--) {
headerline += "-";
}
menubar.init( group_menu_array );
menubar.set_text( MENUBAR_TEXT );
sline.init( Screen.get_xmax() + 1);
update_all ();
iline.set_default ( GROUP_MENU_KEYS_MSG );
iline.show_default();
do {
c = Screen.get_ch();
iline.reset();
if (c == ESCAPE) {
iline.set( MENU_MSG );
iline.show();
c = menubar.select();
update_all();
iline.reset();
}
switch (c) {
case MESSAGEWIN_CMD:
Wscript->enable();
iline.set ( MESSAGES_MSG );
iline.show();
Screen.get_ch();
iline.reset();
Wscript->disable();
break;
case PREV_CMD:
case CODE_UP:
if (false == up()) {
iline.set( AT_TOP_MSG );
}
break;
case NEXT_CMD:
case CODE_DOWN:
if (false == down()) {
iline.set( AT_BOTTOM_MSG );
}
break;
case CODE_NPAGE:
case PAGE_DOWN_CMD:
if (false == page_down()) {
iline.set( AT_LAST_PAGE_MSG );
}
break;
case CODE_PPAGE:
case PAGE_UP_CMD:
if (false == page_up()) {
iline.set( AT_FIRST_PAGE_MSG );
}
break;
case CODE_HOME:
//pages = 0;
current_group=0;
update_list();
break;
case CODE_END:
last_group();
update_list();
break;
case YANK_IN_OUT_CMD:
show_all_groups = show_all_groups == true ?
false : true;
if (show_all_groups == true ) {
sline.set_infotext( "List Of All Groups" );
} else {
sline.set_infotext( "List Of Unread Groups" );
}
pages = 0;
update_all ();
break;
case NEXT_UNREAD_CMD:
if (true == next_unread()) {
msgmenu.enter();
update_all();
} else {
iline.set( NO_UNREAD_MSG);
}
break;
case CODE_RIGHT:
case SELECT_CMD:
msgmenu.enter();
update_all();
break;
case WRITE_CMD:
if (*groupnumbers.get() < BASEGROUP_NUMBER) {
mail->write();
pages = 0;
update_all();
}
break;
case CATCHUP_CMD:
if (true == catchup()) {
update_all();
iline.set("Articles marked as read");
} else {
iline.show(REFRESH_ALL_FL);
}
break;
case FORCED_CATCHUP_CMD:
mail->catchup();
update_all();
break;
case SEARCH_CMD:
tmp = search();
if (tmp > 0) {
pages = 0;
}
update_all();
if (tmp == 0) {
iline.set("No matching articles found!");
} else if (tmp) {
String _tmp = "Expression found from ";
_tmp += tmp;
_tmp += " articles";
iline.set(_tmp.get());
}
break;
case TAG_CMD:
mail->tag_group();
Screen.gotoxy( 0, ypos );
draw_group_data( EMPHASIZED_FL );
break;
case TAG_ALL_CMD:
if (true == tag_all()) {
update_all();
iline.set("All groups tagged");
} else {
update_all();
iline.set("All tags removed");
}
break;
case HELP_CMD:
show_help();
update_all();
break;
case QUIT_CMD:
quit = confirm_quit();
break;
case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
throw quit_exception();
#else
Quit_flag = true;
break;
#endif
case 0:
break;
default:
iline.set( INVALID_COMMAND_MSG );
break;
}
iline.show();
} while (quit == false
#ifdef NO_EXCEPTIONS
&& Quit_flag == false
#endif
);
Screen.bottom();
Screen.clr_eol();
}
bool
Groupmenu::confirm_quit()
{
if (Settings.confirm_return == false) {
return true;
}
bool rc = true;
Screen.bottom();
show_option_str( "Really return to the packet selection level"
" (y/n) @y@" );
Screen.clr_eol();
if (get_yesno( 'y' ) == 'n' ) {
iline.show( REFRESH_ALL_FL );
rc = false;
}
return rc;
}
// true jos kaikki merkitty, false jos merkit poistettu
bool
Groupmenu::tag_all()
{
static int count;
mail->save_group_pos();
++count;
mail->first_group();
if (count % 2) {
do {
if (mail->is_group_tagged() == false) {
mail->tag_group();
}
} while (mail->next_group() == true);
} else {
do {
if (mail->is_group_tagged() == true) {
mail->tag_group();
}
} while (mail->next_group() == true);
}
mail->restore_group_pos();
if (count % 2) {
return true;
}
return false;
}
int
Groupmenu::get_search_groups()
{
Screen.bottom();
show_option_str("Search from [@a@]ll groups [@c@]urrent group [@t@]agged groups or [@q@]uit? @c@");
Screen.clr_eol();
int c;
while ((c = Screen.get_ch()) != 0) {
switch (c) {
case 'a':
Screen.print( "\ba" );
Screen.refresh();
return ALL_FL;
case 'c':
case SELECT_CMD:
return CURRENT_FL;
case 't':
Screen.print( "\bt" );
Screen.refresh();
return TAGGED_FL;
case 'q':
return QUIT_FL;
}
}
return DEFAULT_FL;
}
// palauttaa löydettyjen määrän, tai -1 jos quit.
int
Groupmenu::search()
{
int c;
int mode;
mode = get_search_groups();
if (mode == QUIT_FL) {
return -1;
}
Screen.bottom();
show_option_str( "And from [@s@]ubject [@a@]uthor [@r@]eceiver [@h@]eader [@A@]rticle or [@q@]uit? @h@" );
Screen.clr_eol();
while ((c = Screen.get_ch()) != 0) {
switch (c) {
case 's':
mode |= SUBJECT_FL;
goto out_of_loop;
case 'a':
mode |= AUTHOR_FL;
goto out_of_loop;
case 'r':
mode |= RECEIVER_FL;
goto out_of_loop;
case SELECT_CMD:
case 'h':
c = 'h';
mode |= SUBJECT_FL | AUTHOR_FL | RECEIVER_FL;
goto out_of_loop;
case 'A':
mode |= SUBJECT_FL | AUTHOR_FL|RECEIVER_FL|ARTICLE_FL;
goto out_of_loop;
case 'q':
return false;
}
}
out_of_loop:
char *buf = new char [82];
memset (buf, 0, 82);
Screen.bottom();
Screen.print( "Type expression : " );
Screen.clr_eol();
Screen.getline (buf, 60, 0);
if (*buf == 0x00) {
delete[] buf;
return false;
}
Screen.bottom();
Screen.clr_eol();
int n = mail->search (buf, mode);
delete[] buf;
return n;
}
bool
Groupmenu::catchup()
{
Screen.bottom();
show_option_str( "Really want mark all articles as read (y/n) @y@" );
Screen.clr_eol();
if (get_yesno ('y') == 'y') {
mail->catchup();
return true;
}
return false;
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: count_pages
//
// description: Counts all values that are necessary for corresponding paging.
// Sets up list contains numbers of all groups to be show.
//
// in: mail
// out:
//
//**************************************************************************/
void
Groupmenu::count_pages ()
{
all_groups = mail->get_num_groups();
int *i;
mail->first_group();
groups = 0;
groupnumbers.destroy();
do {
int n = mail->get_group_number();
if (show_all_groups == false && mail->count_unread() == 0
&& n != REPLYLOG_NUMBER && n != NEW_REPLIES_NUMBER) {
if (! ((n == SEARCH_RESULTS_NUMBER ||
n == TAGGED_ARTICLES_NUMBER) &&
mail->count_articles() > 0) ) {
continue;
}
}
i = new int;
*i = n;
groupnumbers.add (i);
++groups;
} while (mail->next_group() == true);
pages = groups / groups_per_page;
pages++;
while (current_group >= groups) {
--current_group;
}
current_page = current_group / groups_per_page;
first_of_page = current_page * groups_per_page;
ypos = ymin + (current_group - first_of_page);
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: update_all
//
// description: Draws everything.
//
//
// in: mail, Screen, Quit_flag
// out:
//
//**************************************************************************/
void
Groupmenu::update_all ()
{
Screen.erase();
#ifdef NO_EXCEPTIONS
if (Quit_flag == true) {
return;
}
#endif
menubar.show();
iline.show_default();
if (pages == 0) {
count_pages();
}
sline.set_infotext( mail->get_bbs_name().get() );
if (show_all_groups == true) {
sline.set_extra_info( "All Groups" );
} else {
sline.set_extra_info( "Unread Groups" );
}
Screen.gotoxy( 0, 1 );
Screen.print( "Phone : %s (%s)", mail->get_bbs_phone().c_str(),
mail->get_bbs_sysop().c_str());
Screen.gotoxy(0,2);
Screen.print( "Packet time : %s", mail->get_packet_time().c_str() );
Screen.gotoxy(0,3);
Screen.print( "Total number of the articles in the database: %d",
mail->count_all_articles());
Screen.gotoxy(0,4);
Screen.print( "New articles from packet: %d",
mail->count_new_articles() );
Screen.gotoxy(0,5);
set_dashline_color();
Screen.print( headerline.c_str() );
Screen.reset_attr();
update_list();
}
void
Groupmenu::update_list()
{
int i;
int pros;
int y = ymin;
// find first group of current page
groupnumbers.first();
i = 0;
while (i < first_of_page) {
++i;
groupnumbers.next();
}
i = current_group;
current_group = first_of_page;
// display groups
do {
Screen.gotoxy (0,y);
if (current_group == i) {
ypos = y;
draw_group_data(EMPHASIZED_FL);
} else {
draw_group_data();
}
++current_group;
++y;
} while (groupnumbers.next() == true && (y <= ymax));
Screen.reset_attr();
while ( y <= ymax ) {
Screen.gotoxy(0,y++);
Screen.empty_line();
}
if (groups <= groups_per_page ) {
pros = ALL_PERCENT;
} else if ( first_of_page == 0 ) {
pros = TOP_PERCENT;
} else {
pros = 100 * current_group / groups;
}
sline.set_percent( pros );
sline.show();
// Set position in list at current Group
current_group = i;
goto_current_group();
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: draw_group_data
//
// description: Displays data of Group
//
//
// in: mail, Screen, Settings
// out:
//
//**************************************************************************/
void
Groupmenu::draw_group_data (int flag)
{
String tmp;
if (flag & EMPHASIZED_FL) {
Screen.inverse();
}
Screen.empty_line();
Screen.goto_x (6);
Screen.print( "%.3d", current_group );
mail->go_group_number ( *(groupnumbers.get()) );
Screen.goto_x (14);
Screen.addstr( mail->get_group_numstr() );
if (mail->is_group_tagged() == true) {
Screen.goto_x (18);
Screen.print( "T" );
}
Screen.goto_x (20);
if (!(flag & EMPHASIZED_FL)) {
if (*(groupnumbers.get()) >= BASEGROUP_NUMBER) {
Screen.textcolor (Settings.basegroup_color);
} else {
Screen.textcolor (Settings.subject_color);
}
}
Screen.addstr( mail->get_group_name() );
if (!(flag & EMPHASIZED_FL)) {
Screen.textcolor (Settings.author_color);
}
Screen.goto_x(50);
if (mail->count_unread() > 0) {
Screen.print( "%d", mail->count_unread() );
} else {
Screen.addstr( "-" );
}
if (!(flag & EMPHASIZED_FL)) {
Screen.reset_attr();
}
Screen.goto_x (59);
if (mail->count_articles() > 0) {
Screen.print( "%d", mail->count_articles() );
} else {
Screen.addstr( "-" );
}
Screen.reset_attr();
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: goto_current_group
//
// description: Moves listpointers to current Group
//
//
// in: mail
// out:
//
//**************************************************************************/
void
Groupmenu::goto_current_group()
{
int i = 0;
groupnumbers.first();
while (i < current_group) {
groupnumbers.next();
++i;
}
mail->go_group_number (*(groupnumbers.get()));
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: previous_group
//
// description: Moves listpointer to previous group
//
// in: mail
// out:
//
// returns: true if previous exist
//
//**************************************************************************/
bool
Groupmenu::previous_group()
{
bool rc = groupnumbers.prev();
if (rc == true) {
--current_group;
mail->go_group_number ( *(groupnumbers.get()) );
}
return rc;
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: next_group
//
// description: moves listpointer to next group
//
// in: mail
// out:
//
// returns: true if next group exist
//
//**************************************************************************/
bool
Groupmenu::next_group()
{
bool rc = groupnumbers.next();
if (rc == true) {
++current_group;
mail->go_group_number (*(groupnumbers.get()));
}
return rc;
}
//**************************************************************************/
// CLASS: Groupmenu
// MEMBER FUNCTION: last_group
//
//
// DESCRIPTION: Moves pointer to last group
//
// EXTERNAL VARIABLE REFERENCES
// IN : mail
// OUT:
//
//**************************************************************************/
void
Groupmenu::last_group()
{
groupnumbers.last();
mail->go_group_number (*(groupnumbers.get()));
current_group = groups-1;
current_page = current_group / groups_per_page;
first_of_page = current_page * groups_per_page;
ypos = ymin + (current_group - first_of_page);
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: up
//
// description: Moves cursor up, and updates listpointer
//
// in: Screen
// out:
//
//**************************************************************************/
bool
Groupmenu::up()
{
if (current_group > 0) {
if (ypos == ymin) {
ypos = ymax;
first_of_page = current_group-groups_per_page;
--current_group;
--current_page;
update_list();
} else {
Screen.gotoxy (0, ypos);
draw_group_data();
Screen.gotoxy (0,--ypos);
previous_group();
draw_group_data(EMPHASIZED_FL);
Screen.reset_attr();
}
return true;
}
return false;
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: down
//
// description: moves cursor one line down, and moves to next group
//
// in: Screen
// out:
//
//**************************************************************************/
bool
Groupmenu::down()
{
if (current_group < (groups-1)) {
if (ypos == ymax) {
first_of_page = ++current_group;
++current_page;
ypos = ymin;
update_list();
} else {
Screen.gotoxy (0,ypos);
draw_group_data();
Screen.gotoxy (0,++ypos);
next_group();
draw_group_data (EMPHASIZED_FL);
Screen.reset_attr();
}
return true;
}
return false;
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: page_up
//
// description: Moves cursor one page up
//
//
//**************************************************************************/
bool
Groupmenu::page_up()
{
int i = groups_per_page;
if (current_group == 0) {
return false;
}
if (ypos == ymin) {
while (i && first_of_page > 0) {
--first_of_page;
--current_group;
--i;
}
} else {
while (ypos > ymin) {
--ypos;
--current_group;
}
}
goto_current_group();
update_list();
return true;
}
//**************************************************************************/
//
// CLASS: Groupmenu
// MEMBER FUNCTION: page_down
//
// description: moves cursor one page down
//
//**************************************************************************/
bool
Groupmenu::page_down()
{
if (current_group >= groups-1) {
return false;
}
if (ypos == ymax) {
++current_page;
first_of_page = ++current_group;
for (int i=1; i < groups_per_page && current_group < groups-1;
++i, ++current_group);
ypos = ymin + (current_group - first_of_page);
} else {
while (current_group < groups-1 && ypos < ymax) {
++current_group;
++ypos;
}
}
goto_current_group();
update_list();
return true;
}
//**************************************************************************/
// CLASS: Groupmenu
// MEMBER FUNCTION: next_unread
//
// DESCRIPTION: Selects next group with unread articles. If not found,
// then seeks for previous group.
//
// EXTERNAL VARIABLE REFERENCES:
// IN : mail
// OUT:
//
// RETURNS: true if group found.
//**************************************************************************/
// toimivuus voi olla huono. Saattaa sekoittaa nykyisen sijainnin....
// Kirjoiteltu silm„t ristiss„ klo 00:40...
bool
Groupmenu::next_unread()
{
bool rc = false;
int i;
i = current_group;
mail->go_group_number ( *(groupnumbers.get()) );
if (mail->count_unread() > 0) {
rc = true;
} else {
groupnumbers.save_position();
// seek forward for unread articles
while (groupnumbers.next() == true) {
mail->go_group_number ( *(groupnumbers.get()) );
++current_group;
if (mail->count_unread() > 0) {
rc = true;
break;
}
}
// if unread message not found, try seek from first thread.
if (rc == false) {
groupnumbers.first();
current_group = 0;
do {
mail->go_group_number (*(groupnumbers.get()));
if (mail->count_unread() > 0) {
rc = true;
break;
}
++current_group;
} while (groupnumbers.next() == true
&& current_group < i);
if (rc == false) {
groupnumbers.restore_position();
current_group = i;
}
}
}
return rc;
}
jmrsrc-0.7.23/src/menubar.cc 100664 764 764 23316 6623366026 15325 0 ustar jvuokko jvuokko // Module id:
// $Id: menubar.cc,v 1.5 1997/07/17 15:30:09 jvuokko Exp $
/*****************************************************************************
* *
* * MODULE: menubar.cc
* * ----------
* ***************************************************************************
* *
* *
* * COPYRIGHT (C) 1997 JUKKA VUOKKO. ALL RIGHTS RESERVED
* ***************************************************************************
* *
* * Methods for handling simple pull-down menu.
* *
*****************************************************************************/
#include "datatypes.hh"
#include "jmr.hh"
#include "menubar.hh"
/************************ GLOBAL VARIABLES ***********************************/
extern Terminal_screen Screen;
extern settings_t Settings;
//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION:
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Menubar::init( const menuinfo* array )
{
menu.destroy();
int i = 0;
String tmp;
// loop until item "@END" is reached
while (strcmp( array[i].item, "@END") != 0) {
tmp = array[i].item;
menu.add_menu( tmp, array[i].cmd );
++i;
while (strcmp( array[i].item, "@NEXT" ) != 0) {
tmp = array[i].item ;
menu.add_item( tmp, array[i].cmd );
++i;
}
++i;
}
text = "";
xmax = Screen.get_xmax();
ymax = Screen.get_ymax();
x = 0;
y = 0;
}
//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION:
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Menubar::show ()
{
Screen.reset_attr();
if (Screen.is_color_term() == true) {
Screen.textcolor( Settings.menu_color,
Settings.menuback_color );
} else {
Screen.textcolor( INVERSE );
}
Screen.gotoxy( 0, 0 );
Screen.addstr( text );
Screen.clr_eol();
Screen.reset_attr();
}
//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION:
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
int
Menubar::select()
{
int c;
int cmd(0);
refresh_all:
bool quit = false;
Screen.gotoxy( 0,0 );
set_passive_color();
Screen.empty_line();
menu.first_menu();
assert( menu.first_menu() == true );
do {
Screen.addstr ( menu.get_menu() );
Screen.addch (' ');
} while (menu.next_menu() == true);
menu.first_menu();
x = 0;
if (menu.first_item() == true) {
y = 1;
} else {
y = 0;
}
do {
draw_menu_item( EMPHASIZED_FL );
c = Screen.get_ch();
switch (c) {
case CODE_CTRL_L:
goto refresh_all;
case QUIT_CMD:
cmd = QUIT_CMD;
quit = true;
break;
case ESCAPE:
cmd = 0;
quit = true;
break;
case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
throw quit_exception();
#else
cmd = FORCED_QUIT_CMD;
quit = true;
Quit_flag = true;
break;
#endif
case CODE_RIGHT:
draw_menu_item();
if (true == menu.next_menu()) {
++x;
if (true == menu.first_item()) {
y = 1;
} else {
y = 0;
}
}
draw_menu_item( EMPHASIZED_FL );
break;
case CODE_LEFT:
draw_menu_item();
if (true == menu.prev_menu()) {
--x;
if (true == menu.first_item()) {
y = 1;
} else {
y = 0;
}
}
draw_menu_item( EMPHASIZED_FL );
break;
case CODE_DOWN:
draw_menu_item();
if (true == menu.next_item()) {
++y;
}
draw_menu_item( EMPHASIZED_FL );
break;
case CODE_UP:
draw_menu_item();
if (true == menu.prev_item()) {
--y;
}
draw_menu_item( EMPHASIZED_FL );
break;
case SELECT_CMD:
cmd = menu.get_command();
quit = true;
break;
};
} while (false == quit);
Screen.reset_attr();
return cmd;
}
//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION:
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Menubar::set_active_color()
{
Screen.reset_attr();
if (Screen.is_color_term() == true ) {
Screen.textcolor (Settings.menuback_color,
Settings.menu_color );
} else {
Screen.textcolor (BOLD);
}
}
//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION:
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Menubar::set_passive_color()
{
Screen.reset_attr();
if (Screen.is_color_term() == true) {
Screen.textcolor ( Settings.menu_color );
Screen.back_color( Settings.menuback_color );
} else {
Screen.inverse();
}
}
//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION:
//**************************************************************************/
//
//
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
// PARAMETERS:
//
// RETURN:
//**************************************************************************/
void
Menubar::draw_menu_item( int flag )
{
Screen.reset_attr();
int pos;
if (flag & EMPHASIZED_FL ) {
set_active_color();
} else {
for (pos = 1; pos < 4; pos++) {
Screen.gotoxy( 0, pos );
Screen.empty_line();
}
set_passive_color();
}
pos = x;
int xcoord = 0;
menu.first_menu();
while (pos) {
xcoord += menu.get_menu().length();
++xcoord;
--pos;
menu.next_menu();
}
Screen.gotoxy( xcoord, 0 );
Screen.addstr( menu.get_menu() );
if (flag & EMPHASIZED_FL ) {
pos = 1;
if (true == menu.first_item()) {
int len;
do {
Screen.gotoxy( xcoord, pos );
len = menu.get_maxlen();
if (pos == y) {
set_active_color();
Screen.addch( '>' );
} else {
set_passive_color();
Screen.addch( ' ' );
}
Screen.addstr( menu.get_item() );
len -= menu.get_item().length();
while (len > 0 ) {
Screen.addch(' ');
--len;
}
++pos;
} while (menu.next_item() == true );
}
menu.first_item();
for (pos = 1; pos < y; pos++ ) {
menu.next_item();
}
}
}
jmrsrc-0.7.23/src/message_display.cc 100664 764 764 101623 6623366026 17063 0 ustar jvuokko jvuokko // $Id: message_display.cc,v 1.26 1998/09/27 09:53:57 jvuokko Exp $
/****************************************************************************
* *
* * MODULE : message_display.cc
* *
* * Copyright (c) 1997 Jukka Vuokko
* * See file COPYING for more information about copyrights.
* *
****************************************************************************
* *
* *
* * Functions for article viewing level.
* *
* *
* *
* *
***************************************************************************/
#include <fstream.h>
#include <stdlib.h> // abs()
#ifdef __WATCOMC__
# include <io.h> // F_OK
#else
# include <unistd.h>
#endif
#include "jmr.hh"
#include "terminal_io.hh"
#include "menu.hh"
#include "mail.hh"
#include "menubar.hh"
static void save_thread_to_file (Msgthread *thread);
static void save_articles (Msgthread *thread, int flag, char *fname,
int filemode);
extern Terminal_screen Screen;
extern settings_t Settings;
extern Mail* mail;
extern Window* Wscript;
static const menuinfo article_menu_array[] = {
{"[jmr]", 0 },
{"Exit from menu", 0},
{"Return to thread selection", QUIT_CMD},
{"Quit jmr", FORCED_QUIT_CMD},
{"@NEXT", 0 },
{"[Moving]", 0},
{"Next Unread", NEXT_UNREAD_CMD },
{"First of thread", '<' },
{"Last of thread", '>' },
{"@NEXT", 0 },
{"Followup", FOLLOWUP_CMD },
{"@NEXT", 0 },
{"Reply private", REPLY_CMD },
{"@NEXT", 0 },
{"@END", 0 }
};
/***************************************************************************
* FUNCTION : save_thread_to_file
***************************************************************************
*
* description : Saves current thread or article to file.
*
*
* in: Settings.extractdir, Screen
* out:
*
* return :
***************************************************************************/
static void
save_thread_to_file (Msgthread *thread)
{
ofstream file;
String fullname;
static char fname[MAXPATH];
bool quit;
int c;
int flag = DEFAULT_FL;
int filemode = 0;
if ( !*fname ) {
strcpy( fname, Settings.default_folder.c_str() );
}
Screen.gotoxy (0, Screen.get_ymax() -4 );
for (int i = 0; i < 4; ++i ) {
Screen.down();
Screen.clr_eol();
}
if (ACCESS (Settings.extractdir.get(), F_OK)) {
handle_error ("Cannot access extract directory");
return;
}
Screen.up(1);
show_option_str( "Save [@t@]hread [@a@]rticle or [@q@]uit ? @t@" );
quit = false;
do {
c = Screen.get_ch();
switch(c) {
case 't':
case SELECT_CMD:
flag = THREAD_FL;
quit = true;
break;
case 'a':
flag = ARTICLE_FL;
Screen.addstr ("\ba" );
quit = true;
break;
case QUIT_CMD:
return;
}
} while (quit == false);
do {
c = 0;
Screen.bottom();
Screen.clr_eol();
Screen.reset_attr();
Screen.addstr( "Input file name : " );
Screen.textcolor(BOLD);
Screen.getline (fname, 40);
Screen.reset_attr();
Screen.bottom();
Screen.clr_eol();
fullname = fname;
if ( have_full_path( fullname ) == false &&
expand_path( fullname ) == false ) {
fullname = Settings.extractdir.get();
fullname += fname;
}
if ( access( fullname.c_str(), R_OK ) == 0 ) {
Screen.bottom();
show_option_str("File exists. Select: [@o@]verwrite"
" [@n@]ew file [@a@]ppend [@q@]uit ?"
" @a@" );
Screen.clr_eol();
quit = false;
do {
c = Screen.get_ch();
switch (c) {
case 'o':
filemode = ios::trunc;
quit = true;
break;
case 'n':
case QUIT_CMD:
quit = true;
break;
case 'a':
case SELECT_CMD:
filemode = ios::app;
quit = true;
break;
}
} while (quit == false);
Screen.bottom();
Screen.clr_eol();
} else {
filemode = ios::trunc;
}
} while (c == 'n');
if (c != QUIT_CMD) {
save_articles (thread, flag, fullname.get(), filemode);
}
}
/***************************************************************************
* FUNCTION : save_articles
***************************************************************************
*
* description : Saves article or thread to given file.
*
***************************************************************************/
static void
save_articles (Msgthread *thread, // thread
int flag, // flag. if THREAD_FL, then saves whole thread
char *fname, // name of file
int filemode) // mode. ios::trunc or ios::app
{
ofstream file;
Message *msg;
file.open (fname, ios::out | filemode);
if (file.fail()) {
String err = "Cannot save message to file `";
err += fname;
err += "'";
system_error ( err.c_str() );
return;
}
if (flag & THREAD_FL) {
thread->save_position();
thread->first();
}
// write contents of thread to file
do {
msg = thread->get();
assert( msg );
msg->save_to_file (file, mail->get_bbs_name(),
mail->get_group_name(),
mail->get_group_number());
if (!(flag & THREAD_FL)) {
break;
}
} while (thread->next() == true);
if (flag & THREAD_FL) {
thread->restore_position();
}
file.close();
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: Msgdisplay
//
// description: Constructor for this class.
//
//
//**************************************************************************/
Msgdisplay::Msgdisplay ( Grpthread &groups_threads ) :
threads( groups_threads), hist_ring(READ_HISTORY_SIZE)
{
orig_article = 0;
ymax = Screen.get_ymax() - 2;
ymin = 6;
ypos = ymin;
lines_per_page = ymax - ymin;
pages = 0;
lines_in_article = 0;
xmax = Screen.get_xmax();
modefl = 0;
if (Settings.is_wrapmode == true ) {
modefl |= WRAPMODE;
}
menubar.init( article_menu_array );
menubar.set_text( MENUBAR_TEXT );
for (int i=0; i <= xmax; i++) {
dashline += '-';
}
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: enter
//
// description: Main function for class. Gets events from user.
//
// in: Screen, Settings.is_wrapmode
// out: Settings.is_wrapmode
//
//
// returns: DEFAULT_FL or TAGGED_FL.
//
//**************************************************************************/
int
Msgdisplay::enter (
// pointer to flag of viewing mode
bool *showflag,
// if NEXT_UNREAD_FL, then search for next unread.
int flag)
{
int c;
int rc = DEFAULT_FL;
bool quit = false;
bool first_pass = false;
show_all_articles = showflag;
is_bottom = false;
// go to next unread, or first message of thread
if (flag & NEXT_UNREAD_FL) {
threads.next_unread();
} else {
threads.get_thread()->first();
}
sline.init( Screen.get_xmax() + 1);
update ();
hist_ring.clear();
hist_ring.add( orig_article );
display (REFRESH_ALL_FL);
iline.show_default();
do {
c = Screen.get_ch();
iline.reset();
switch (c) {
case ESCAPE:
if ( Screen.get_xch() == ESCAPE ) {
iline.set( MENU_MSG );
iline.show();
c = menubar.select();
draw_header();
iline.reset();
} else if ( Screen.get_xch() == 'r' ) {
modefl ^= ROT13MODE;
display();
} else if ( Screen.get_xch() == 'w' ) {
modefl ^= WRAPMODE;
Settings.is_wrapmode =
Settings.is_wrapmode ? false : true;
update();
display();
}
break;
case MESSAGEWIN_CMD:
Wscript->enable();
iline.set ( MESSAGES_MSG );
iline.show();
Screen.get_ch();
iline.reset();
Wscript->disable();
break;
case QUIT_CMD:
case CODE_LEFT:
quit = true;
break;
case CODE_DOWN:
down();
break;
case CODE_NPAGE:
case PAGE_DOWN_CMD:
if (is_bottom == true && first_pass == false) {
first_pass = true;
iline.set(AT_END_OF_ARTICLE_MSG);
} else if (is_bottom == true && first_pass == true) {
first_pass = false;
if (page_down(NEXT_FL) == false) {
iline.set( NO_UNREAD_MSG, ERROR_FL );
}
} else {
page_down();
}
break;
case CODE_PPAGE:
case PAGE_UP_CMD:
page_up();
break;
case CODE_UP:
up();
break;
case CODE_HOME:
display (REFRESH_ALL_FL);
break;
case CODE_END:
go_bottom();
break;
case NEXT_UNREAD_CMD:
case SELECT_CMD:
if (true == threads.next_unread()) {
update();
hist_ring.add( orig_article );
display (REFRESH_ALL_FL);
} else {
iline.set( NO_UNREAD_MSG, ERROR_FL );
}
break;
case NEXT_CMD:
//case CODE_RIGHT:
if (true == next()) {
update();
hist_ring.add( orig_article );
display (REFRESH_ALL_FL);
} else {
quit = true;
}
break;
case PREV_CMD:
if (true == prev()) {
update();
hist_ring.add( orig_article );
display (REFRESH_ALL_FL);
}
break;
case EDIT_REPLY_CMD:
if(mail->get_group_number() == NEW_REPLIES_NUMBER) {
mail->edit( orig_article );
update();
display( REFRESH_ALL_FL );
}
break;
case 'E':
if(mail->get_group_number() == NEW_REPLIES_NUMBER) {
mail->edit_header( orig_article );
update();
display( REFRESH_ALL_FL );
}
break;
case REPLY_CMD:
mail->reply( orig_article );
display(REFRESH_ALL_FL);
break;
case FOLLOWUP_CMD:
mail->followup( orig_article );
display(REFRESH_ALL_FL);
break;
case SAVE_TO_FILE_CMD:
save_thread_to_file (threads.get_thread());
display(REFRESH_ALL_FL);
break;
case TAG_CMD:
if (mail->get_group_number() < BASEGROUP_NUMBER) {
mail->tag_article( orig_article );
draw_header_status();
rc = TAGGED_FL;
}
break;
case KILL_CMD:
if (mail->get_group_number() > PERSONAL_NUMBER) {
orig_article->invert_kill_flag();
draw_header_status();
}
break;
case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
throw quit_exception();
#else
Quit_flag = true;
break;
#endif
case '<':
case CODE_CTRL_A:
if (true == begin_of_thread()) {
update();
hist_ring.add( orig_article );
display( REFRESH_ALL_FL );
}
break;
case '>':
case CODE_CTRL_E:
if (true == end_of_thread()) {
update();
hist_ring.add( orig_article );
display( REFRESH_ALL_FL );
}
break;
case HELP_CMD:
show_help();
display( REFRESH_ALL_FL );
break;
case PREV_IN_HIST_CMD:
if (true == --hist_ring ) {
threads.go_message( hist_ring.get() );
update();
display( REFRESH_ALL_FL );
}
break;
case NEXT_IN_HIST_CMD:
if (true == ++hist_ring) {
threads.go_message( hist_ring.get() );
update();
display( REFRESH_ALL_FL );
}
break;
case 0:
break;
default:
iline.set( INVALID_COMMAND_MSG );
break;
}
iline.show();
} while (quit == false
#ifdef NO_EXCEPTIONS
&& Quit_flag == false
#endif
);
Screen.bottom();
Screen.clr_eol();
return rc;
}
//**************************************************************************/
// CLASS: Msgdisplay
// MEMBER FUNCTION: begin_of_thread
//**************************************************************************/
//
// Moves current position to first article of current thread.
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
//
// RETURN: true if success
//**************************************************************************/
bool
Msgdisplay::begin_of_thread()
{
return threads.get_thread()->first();
}
//**************************************************************************/
// CLASS: Msgdisplay
// MEMBER FUNCTION: end_of_thread
//**************************************************************************/
//
// Moves position within current thread to thread's last article
//
// EXTERNAL VARIABLE REFERENCES
// IN :
// OUT:
//
//
// RETURN: true if success
//**************************************************************************/
bool
Msgdisplay::end_of_thread()
{
return threads.get_thread()->last();
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: next
//
// description: Moves position to next article in a current thread.
// If thread ends, then switch to next thread.
//
// in:
// out:
//
// returns: true if success.
//
//**************************************************************************/
bool
Msgdisplay::next ()
{
if ( threads.get_thread()->next() == true) {
return true;
}
// uusi kokeilu, vanhassa ei ehkä toiminut threadin vaihto oikein???
if ( threads.next() == true ) {
return threads.get_thread()->first();
}
return false;
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: prev
//
// description: Moves position to previous message of a current thread.
// If already at start of thread, then moves to last message of
// previous thread.
// in:
// out:
//
// returns: true if success.
//
//**************************************************************************/
bool
Msgdisplay::prev()
{
bool rc = threads.get_thread()->prev();
if (rc == false) {
if (threads.prev() == true) {
rc = threads.get_thread()->last();
}
}
return rc;
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: update
//
// description: Counts some necessary values and makes sure that lines
// are read from file to memory. (Message::first_line()).
// FIXME: lisää maininnat orig_articlen assosionnista...
//
// NOTE: This should be called only when position in thread is changed!!!!
//
//**************************************************************************/
void
Msgdisplay::update()
{
xmax = Screen.get_xmax();
orig_article = threads.get_article();
orig_article->first_line();
article = *orig_article;
if ( modefl & WRAPMODE ) {
article.wrap( xmax+1 );
}
lines_in_article = article.get_num_of_lines();
first_of_page = 0;
pages = lines_in_article / lines_per_page + 1;
}
//**************************************************************************/
// CLASS: Msgdisplay
// MEMBER FUNCTION: go_bottom
//
//
// DESCRIPTION: Moves position to bottom of article
//
//
//**************************************************************************/
void
Msgdisplay::go_bottom()
{
for (;;) {
article.save_position();
int i = lines_per_page - 1;
while (i) {
article.next_line();
i--;
}
if (article.next_line() == true) {
first_of_page += lines_per_page;
} else {
article.restore_position();
break;
}
}
display();
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: display
//
// description: Displays everyhting. If flag is REFRESH_ALL_FL, then
// moves top of article and redraws header and footer.
//
//
// in: threadlist (number of articles)
// out:
//
// returns:
//
//**************************************************************************/
void
Msgdisplay::display (int flag)
{
String str;
int i;
int y;
int pros;
int lastline = lines_in_article;
int width = xmax + 1;
Screen.reset_attr();
if (flag & REFRESH_ALL_FL) {
if (!(orig_article->get_status() & READ_ST) &&
mail->get_group_number() != PERSONAL_NUMBER &&
mail->get_group_number() != NEW_REPLIES_NUMBER) {
// Set articles status to readed
orig_article->set_status_bits (READ_ST);
threads.get_thread()->dec_unread();
}
Screen.erase();
draw_header();
if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
iline.set_default( MSG_DISPLAY_NEW_REPLIES_KEYS_MSG);
} else if (mail->get_group_number() >= BASEGROUP_NUMBER) {
iline.set_default( MSG_DISPLAY_BASEGROUP_KEYS_MSG);
} else {
iline.set_default( MSG_DISPLAY_KEYS_MSG);
}
iline.show_default();
first_of_page = 0;
article.first_line();
}
// show group on status bar. If current group is some of the
// basegroups, then display original group of the article
str = "Group: [";
str += orig_article->get_group_number();
str += "] ";
if (mail->get_group_number() >= BASEGROUP_NUMBER) {
mail->save_group_pos();
mail->go_group_number( orig_article->get_group_number());
str += mail->get_group_name();
mail->restore_group_pos();
} else {
str += mail->get_group_name();
}
if ((modefl & (ROT13MODE | WRAPMODE)) == (ROT13MODE | WRAPMODE)) {
str += " (mode:ROT13,WRAP)";
} else if ( modefl & ROT13MODE ) {
str += " (mode:ROT13)";
} else if ( modefl & WRAPMODE ) {
str += " (mode:WRAP)";
}
DEBUG( "modefl = " << modefl << " " + str);
sline.set_infotext( str.get() );
// print message lines
y = ymin;
if (article.check_lines() == true) {
do {
Screen.gotoxy (0,y);
Screen.clr_eol();
if ( article.getline().length() > width ) {
str.copy (article.getline(), 0, xmax);
} else {
str.copy (article.getline(), 0, width);
}
// if (str.find_any_ch (">", 6) == true) {
// Screen.textcolor (Settings.quote_color);
// } else
if (str.find_any_ch (Settings.quotechars.get(),
Settings.quotedepth)) {
Screen.textcolor (Settings.quote_color);
} else {
Screen.textcolor (Settings.article_color);
}
if ( modefl & ROT13MODE ) {
str.rot13();
}
Screen.addstr( str );
if (article.getline().length() > width) {
Screen.textcolor (RED | BOLD);
if ( modefl & WRAPMODE ) {
Screen.addch( Settings.wrap_indicator);
} else {
Screen.addch ('$');
}
}
Screen.reset_attr();
if (++y > ymax)
break;
} while (article.next_line() == true);
lastline = y - ymin + first_of_page;
pros = 100 * lastline;
pros /= lines_in_article;
} else {
pros = 100;
}
i = y-ymin-1;
// clear bottom of screen
while (y <= ymax) {
Screen.gotoxy(0,y);
//Screen.empty_line();
Screen.clr_eol();
y++;
}
// Test, if current line is last line of message
is_bottom = false;
if (article.next_line() == true) {
sline.set_percent( pros );
article.prev_line();
str = "L";
str += lastline;
str += "/";
str += lines_in_article;
sline.set_extra_info( str.get() );
} else {
Msgthread *tmp = threads.get_thread();
is_bottom = true;
if (tmp->next() == true) {
tmp->prev();
sline.set_extra_info( "Next Response" );
} else {
sline.set_extra_info("Last Response");
}
if (lines_in_article <= ymax - ymin) {
sline.set_percent( ALL_PERCENT );
} else {
sline.set_percent( BOT_PERCENT );
}
}
// move pointer to first line of page
while (i>0) {
article.prev_line();
i--;
}
sline.show();
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: draw_header
//
// description: Displays some interesting data at top of screen.
//
// NOTE: this displays data of original message 'orig_article' not
// wrapped one!
//
//**************************************************************************/
void
Msgdisplay::draw_header()
{
String str;
menubar.show();
Screen.reset_attr();
for (int i=1; i <= 3; i++) {
Screen.gotoxy( 0, i );
Screen.empty_line();
}
Screen.gotoxy(0,1);
Screen.print(" Date: %s, %s ", orig_article->get_date().c_str(),
orig_article->get_time().c_str() );
draw_header_status();
Screen.gotoxy (0,2);
Screen.addstr( " From: " );
Screen.textcolor (Settings.header_author_color);
Screen.addstr( str.copy (orig_article->get_writer(), 0, 40) );
if (orig_article->get_writer().length() > 40) {
Screen.addch( '$' );
}
Screen.reset_attr();
Screen.gotoxy( 0, 3 );
Screen.addstr( " To: " );
if (orig_article->get_receiver() == Settings.username) {
Screen.textcolor (Settings.personal_color | BLINK);
}
Screen.addstr( str.copy (orig_article->get_receiver(), 0, 40) );
if (orig_article->get_receiver().length() > 40) {
Screen.addch( '$' );
}
Screen.reset_attr();
Screen.gotoxy( Screen.get_xmax() - 30,2);
Screen.print( "Msgno : %d", orig_article->get_number() );
Screen.print( " Ref: %d", orig_article->get_reference_num() );
Screen.gotoxy( Screen.get_xmax() - 30, 3);
Screen.print( "Respno: %d/%d", orig_article->get_number_in_thread(),
threads.count_threads_articles() );
Screen.gotoxy( 0, 4 );
Screen.addstr( "Subject: " );
if (Screen.is_color_term() == true) {
Screen.textcolor ( Settings.header_subject_color );
} else {
Screen.textcolor ( BOLD );
}
Screen.addstr( str.copy (orig_article->get_subject(), 0, 72) );
if (orig_article->get_subject().length() > 72) {
Screen.addch( '$' );
}
Screen.gotoxy( 0, 5 );
set_dashline_color();
Screen.addstr( dashline );
Screen.reset_attr();
}
//**************************************************************************/
// CLASS: Msgdisplay
// MEMBER FUNCTION: draw_header_status
//**************************************************************************/
//
// Display status information of current article.
// This also queries information directly from original message (orig_article)
// not from wrapped message, 'article'.
//
//**************************************************************************/
void
Msgdisplay::draw_header_status()
{
Screen.gotoxy( Screen.get_xmax() - 30, 1);
Screen.reset_attr();
Screen.clr_eol();
Screen.addstr( "Status: " );
if (orig_article->get_status() & REPLIED_ST) {
Screen.textcolor (CYAN | BOLD | BLINK);
Screen.addstr( "Replied" );
Screen.reset_attr();
} else if (orig_article->get_status() & PRIVATE_ST) {
Screen.textcolor(MAGENTA);
Screen.addstr( "Private" );
} else {
Screen.addstr( "Public " );
}
Screen.gotoxy( Screen.get_xmax() - 14, 1);
if (orig_article->is_killed() == true) {
Screen.textcolor (RED | BOLD | BLINK);
Screen.addstr( "! Killed !" );
} else if (orig_article->get_status() & TAGGED_ST) {
Screen.textcolor (RED | BOLD);
Screen.addstr( "- Tagged -" );
}
Screen.reset_attr();
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: down
//
// description: Scrolls one line down
//
//
//**************************************************************************/
void
Msgdisplay::down()
{
article.save_position();
int i = lines_per_page - 1;
while (i) {
article.next_line();
i--;
}
if (article.next_line() == true) {
article.restore_position();
article.next_line();
first_of_page++;
display();
} else {
article.restore_position();
}
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: Up
//
// description: Scrolls one line up.
//
//
//**************************************************************************/
void
Msgdisplay::up()
{
if (first_of_page > 0) {
first_of_page--;
article.prev_line();
display();
}
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: page_down
//
// description: Moves one page down, or if end of article and flag is
// NEXT_FL then moves to next article.
//
//
// returns: false if no more articles
//
//**************************************************************************/
bool
Msgdisplay::page_down (int flag)
{
bool rc = true;
article.save_position();
int i = lines_per_page - 1;
while (i) {
article.next_line();
i--;
}
if (article.next_line() == true) {
first_of_page += lines_per_page;
display();
} else if (flag & NEXT_FL) {
// if position is bottom of article, then switch to next
// article
article.restore_position();
if (true == (rc = threads.next_unread()) ) {
update();
display (REFRESH_ALL_FL);
}
}
return rc;
}
//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: page_up
//
// description: Moves one page up within article.
//
//
//**************************************************************************/
void
Msgdisplay::page_up()
{
int i = lines_per_page;
while (i && first_of_page > 0) {
first_of_page--;
i--;
article.prev_line();
}
display();
}
jmrsrc-0.7.23/src/message_menu.cc 100664 764 764 104007 6623366026 16361 0 ustar jvuokko jvuokko // $Id: message_menu.cc,v 1.15 1997/07/17 15:35:03 jvuokko Exp $
/****************************************************************************
* *
* * MODULE : message_menu.cc
* *
* * Copyright (c) 1997 Jukka Vuokko
* * See file COPYING for more information about copyrights.
* *
****************************************************************************
* *
* *
* * Functions for thread selection level.
* *
* *
* *
* *
***************************************************************************/
#include <fstream.h>
#include <stdlib.h> // abs()
#ifdef __WATCOMC__
# include <io.h> // F_OK
#else
# include <unistd.h>
#endif
#include "jmr.hh"
#include "terminal_io.hh"
#include "menu.hh"
#include "mail.hh"
#include "menubar.hh"
extern Terminal_screen Screen;
extern settings_t Settings;
extern Mail* mail;
extern Window* Wscript;
static const menuinfo thread_menu_array[] = {
{"[jmr]", 0 },
{"Exit from menu", 0},
{"Return to group selection", QUIT_CMD},
{"Quit jmr", FORCED_QUIT_CMD},
{"@NEXT", 0 },
{"Write", WRITE_CMD },
{"@NEXT", 0 },
{"Yank in/out", YANK_IN_OUT_CMD },
{"@NEXT", 0 },
{"@END", 0 }
};
//**************************************************************************/
//
// CLASS: Msgmenu
// MEMBER FUNCTION: Msgmenu
//
// description: Constructor
//
//
// in: Screen
// out:
//
// returns:
//
//**************************************************************************/
Msgmenu::Msgmenu ()
{
show_all_articles = false;
is_status_msg = false;
thread_mode = true;
thread_cnt = 0;
ymin = 6;
ymax = Screen.get_ymax() - 2;
lines_per_page = ymax - ymin + 1;
pages = 0;
first_of_page = 0;
current = 0;
ypos = ymin;
menubar.init( thread_menu_array );
menubar.set_text( MENUBAR_TEXT );
for (int i=Screen.get_xmax(); i >= 0; i--) {
dashline += '-';
}
}
//**************************************************************************/
//
// CLASS: Msgmenu
// MEMBER FUNCTION: enter
//
// description: Main procedure for class. Handles events from input.
//
// NOTE: Position in ListGroup-list must be in right place
//
// in: mail, Screen, Quit_flag
// out: Quit_flag
//
// returns:
//
//**************************************************************************/
void
Msgmenu::enter ()
{
int c;
int flag;
bool quit = false;
bool rc = false;
show_all_articles = false;
c = mail->get_group_number();
if (mail->count_articles() > 0 && mail->count_unread() == 0) {
show_all_articles = true;
}
mail->first_article();
threads.update( show_all_articles );
sline.init( Screen.get_xmax() + 1);
Msgdisplay msgdisp( threads );
update();
display();
do {
c = Screen.get_ch();
flag = DEFAULT_FL;
iline.reset();
if (c == ESCAPE) {
iline.set( MENU_MSG );
iline.show();
c = menubar.select();
display();
iline.reset();
}
switch (c) {
case MESSAGEWIN_CMD:
Wscript->enable();
iline.set ( MESSAGES_MSG );
iline.show();
Screen.get_ch();
iline.reset();
Wscript->disable();
break;
case NEXT_UNREAD_CMD:
if (go_next_article() == true) {
flag = msgdisp.enter(&show_all_articles,
NEXT_UNREAD_FL);
update_msg_counts();
set_selector_to_current_thread();
display ();
} else {
iline.set( NO_UNREAD_MSG, ERROR_FL );
}
break;
case SELECT_CMD:
case CODE_RIGHT:
if (threads.check() == true) {
flag = msgdisp.enter(&show_all_articles,
DEFAULT_FL);
update_msg_counts();
set_selector_to_current_thread();
display ();
}
break;
case REPLY_CMD:
if (threads.check() == false) {
break;
}
mail->reply(threads.get_article());
if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
threads.update( show_all_articles );
update();
}
update_msg_counts();
display();
break;
case FOLLOWUP_CMD:
if (threads.check() == false) {
break;
}
mail->followup(threads.get_article());
if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
threads.update( show_all_articles );
update();
}
update_msg_counts();
display();
break;
case EDIT_REPLY_CMD:
if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
mail->edit(threads.get_article());
threads.update( show_all_articles );
update();
update_msg_counts();
display();
}
break;
case 'E':
if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
mail->edit_header(threads.get_article());
threads.update( show_all_articles );
update();
update_msg_counts();
display();
}
break;
case KILL_CMD:
if (mail->get_group_number() > PERSONAL_NUMBER) {
threads.get_article()->invert_kill_flag();
Screen.gotoxy (0, ypos);
draw_article_info (EMPHASIZED_FL);
}
break;
case WRITE_CMD:
if (mail->get_group_number() < BASEGROUP_NUMBER) {
mail->write();
update_msg_counts();
display();
}
break;
case QUIT_CMD:
case CODE_LEFT:
quit = true;
break;
case CODE_DOWN:
case NEXT_CMD:
if (false == down()) {
iline.set( AT_BOTTOM_MSG );
}
break;
case CODE_UP:
case PREV_CMD:
if (false == up()) {
iline.set( AT_TOP_MSG );
}
break;
case CODE_HOME:
update();
//display();
draw_list();
break;
case CODE_END:
go_last_thread();
draw_list();
break;
case CODE_PPAGE: // same as PAGE_UP_CMD
case PAGE_UP_CMD:
if (false ==page_up()) {
iline.set( AT_FIRST_PAGE_MSG );
}
break;
case CODE_NPAGE:
case PAGE_DOWN_CMD:
if (false ==page_down()) {
iline.set( AT_LAST_PAGE_MSG );
}
break;
case CATCHUP_CMD:
quit = catchup();
break;
case FORCED_CATCHUP_CMD:
mail->catchup();
quit = true;
break;
case YANK_IN_OUT_CMD:
show_all_articles = show_all_articles == false ?
true : false;
threads.update( show_all_articles );
update();
display();
break;
case SORT_CMD:
if (true == sort()) {
update();
display();
iline.set("Articles sorted!");
} else {
display();
iline.show( REFRESH_ALL_FL );
}
break;
case TAG_CMD:
if (mail->get_sort_order() == THREAD_SORTED) {
iline.set(TAGGING_NOT_ALLOWED_MSG, ERROR_FL);
break;
}
if (mail->get_group_number() != REPLYLOG_NUMBER &&
mail->get_group_number() != NEW_REPLIES_NUMBER) {
if (!threads.check()) {
iline.set("No articles to tag!",
ERROR_FL);
break;
}
mail->tag_article (threads.get_article());
flag = TAGGED_FL;
Screen.gotoxy (0, ypos);
draw_article_info (EMPHASIZED_FL);
}
break;
case TAG_ALL_CMD:
if (mail->get_sort_order() == THREAD_SORTED) {
iline.set( TAGGING_NOT_ALLOWED_MSG, ERROR_FL );
break;
}
if (mail->get_group_number() != REPLYLOG_NUMBER &&
mail->get_group_number() != NEW_REPLIES_NUMBER) {
rc = threads.tag_all();
flag = TAGGED_FL;
//update();
display();
if (rc == true) {
iline.set("All articles tagged!");
} else {
iline.set("All tags removed");
}
}
break;
case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
throw quit_exception();
#else
Quit_flag = true;
break;
#endif
case HELP_CMD:
show_help();
display();
break;
case 0:
break;
default:
iline.set( INVALID_COMMAND_MSG );
break;
}; // end of switch-case
if ( mail->get_group_number() == TAGGED_ARTICLES_NUMBER &&
( flag & TAGGED_FL ) ){
threads.update( show_all_articles );
update();
update_msg_counts();
display();
}
iline.show();
} while (quit == false
#ifdef NO_EXCEPTIONS
&& Quit_flag == false
#endif
);
mail->unload_articles_of_group();
// update read-counts in personal mail group
mail->save_group_pos();
mail->go_group_number (PERSONAL_NUMBER);
mail->update_groups_counts();
mail->restore_group_pos();
Screen.bottom();
Screen.clr_eol();
}
//**************************************************************************/
//
// CLASS: Msgmenu
// MEMBER FUNCTION:
//
// description: Inits some variables.
//
//
//**************************************************************************/
void
Msgmenu::update()
{
thread_cnt = threads.count_threads();
pages = thread_cnt / lines_per_page;
pages++;
current_page = 0;
first_of_page = 0;
current = 0;
ypos = ymin;
if (mail->get_sort_order() == THREAD_SORTED) {
thread_mode = true;
} else {
thread_mode = false;
}
if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
iline.set_default(THREAD_MENU_NEW_REPLIES_KEYS_MSG);
} else if (mail->get_group_number() == REPLYLOG_NUMBER ||
mail->get_group_number() == TAGGED_ARTICLES_NUMBER) {
iline.set_default( THREAD_MENU_BASEGROUP_KEYS_MSG );
} else {
iline.set_default( THREAD_MENU_KEYS_MSG );
}
}
//**************************************************************************/
// CLASS: Msgmenu
// MEMBER FUNCTION: catchup
//**************************************************************************/
//
// Mark all articles of current group as read if user answers 'y' to
// question.
//
// EXTERNAL VARIABLE REFERENCES
// IN : Screen, mail
// OUT:
//
// RETURN: true if articles are marked as read
//**************************************************************************/
bool
Msgmenu::catchup()
{
bool rc = false;
Screen.reset_attr();
Screen.