/*
    nget - command line nntp client
    Copyright (C) 1999-2004  Matthew Mueller <donut AT dakotacom.net>

    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.
*/


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <signal.h>
#include <unistd.h>
#ifdef HAVE_LIBPOPT
#include "argparser.h" //only needed for with-popt build
extern "C" {
#include <popt.h>
}
#else
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include "_fileconf.h"

#include "path.h"
#include "misc.h"
#include "termstuff.h"
#include "strreps.h"
#include "sockstuff.h"
#include "prot_nntp.h"
#include "log.h"
#include "nget.h"
#include "cfgfile.h"
#include "myregex.h"
#include "status.h"


#define NUM_OPTIONS 43
#ifndef HAVE_LIBPOPT

#ifndef HAVE_GETOPT_LONG
struct option {
	const char *name;
	int has_arg;
	int *flag;
	int val;
};
#endif

static struct option long_options[NUM_OPTIONS+1];
static string getopt_options="-";//lets generate this in addoption, to avoid forgeting to update a define. (like in v0.8, oops)
#define OPTIONS getopt_options.c_str()

#else //!HAVE_LIBPOPT

class c_poptContext {
	protected:
		poptContext optCon;
	public:
		int GetNextOpt(void) {return poptGetNextOpt(optCon);}
		const char * GetOptArg(void) {return poptGetOptArg(optCon);}
		const char * const * GetArgs(void) {return poptGetArgs(optCon);}
		const char * BadOption(int flags) {return poptBadOption(optCon, flags);}
		c_poptContext(const char *name, int argc, const char **argv, const struct poptOption *options, int flags) {
			optCon = poptGetContext(POPT_NAME_T name, argc, POPT_ARGV_T argv, options, flags);
			poptReadDefaultConfig(optCon, 0);
		}
		~c_poptContext() {
			poptFreeContext(optCon);
		}
};

static struct poptOption optionsTable[NUM_OPTIONS+1];
#endif //HAVE_LIBPOPT

struct opt_help {
	int namelen;
	char *arg;
	char *desc;
};
static opt_help ohelp[NUM_OPTIONS+1];
static int olongestlen=0;

enum {
	OPT_TEST_MULTI=2,
	OPT_TEXT_HANDLING,
	OPT_SAVE_TEXT_FOR_BINARIES,
	OPT_DECODE,
	OPT_TIMEOUT,
	OPT_DUPEPATH,
	OPT_AUTOPAR,
	OPT_NOAUTOPAR,
	OPT_FULLXOVER,
	OPT_HELP,
	OPT_MIN_SHORTNAME //sentinel, must be last element.
};

static void addoption(char *longo,int needarg,char shorto,char *adesc,char *desc){
	static int cur=0;
	assert(cur<NUM_OPTIONS);
#ifdef HAVE_LIBPOPT
	optionsTable[cur].longName=longo;
	optionsTable[cur].shortName=(shorto>OPT_MIN_SHORTNAME)?shorto:0;
	optionsTable[cur].argInfo=needarg?POPT_ARG_STRING:POPT_ARG_NONE;
	optionsTable[cur].val=shorto;
	optionsTable[cur].arg=NULL;
#else //HAVE_LIBPOPT
	long_options[cur].name=longo;
	long_options[cur].has_arg=needarg;
	long_options[cur].flag=0;
	long_options[cur].val=shorto;
	if (shorto>OPT_MIN_SHORTNAME){
		getopt_options+=shorto;
		if (needarg)
			getopt_options+=':';
	}
#endif //!HAVE_LIBPOPT
	ohelp[cur].namelen=longo?strlen(longo):0;
	ohelp[cur].arg=adesc;
	ohelp[cur].desc=desc;
	int l=(adesc?strlen(adesc):0)+ohelp[cur].namelen+2;
	if (l>olongestlen)
		olongestlen=l;
	cur++;
}
static void addoptions(void)
{
	addoption("quiet",0,'q',0,"supress extra info");
	addoption("host",1,'h',"HOSTALIAS","force nntp host to use (must be configured in .ngetrc)");
	addoption("available",0,'a',0,"update/load available newsgroups list");
	addoption("quickavailable",0,'A',0,"load available newsgroups list");
	addoption("xavailable",0,'X',0,"search available newsgroups list without using cache files");
	addoption("group",1,'g',"GROUP(s)","update and use newsgroups (comma seperated)");
	addoption("quickgroup",1,'G',"GROUP(s)","use group(s) without checking for new headers");
	addoption("xgroup",1,'x',"GROUP(s)","use group(s) without using cache files (requires XPAT)");
	addoption("flushserver",1,'F',"HOSTALIAS","flush server from current group(s) or newsgroup list");
	addoption("expretrieve",1,'R',"EXPRESSION","retrieve files matching expression(see man page)");
	addoption("retrieve",1,'r',"REGEX","retrieve files matching regex");
	addoption("list",1,'@',"LISTFILE","read commands from listfile");
	addoption("path",1,'p',"DIRECTORY","path to store subsequent retrieves");
	addoption("temppath",1,'P',"DIRECTORY","path to store tempfiles");
	addoption("dupepath",1,OPT_DUPEPATH,"DIRECTORY","extra path to check for dupe files");
	addoption("makedirs",1,'m',"no,yes,ask,#","make dirs specified by -p and -P");
//	addoption("mark",1,'m',"MARKNAME","name of high water mark to test files against");
//	addoption("testretrieve",1,'R',"REGEX","test what would have been retrieved");
	addoption("testmode",0,'T',0,"test what would have been retrieved");
	addoption("test-multiserver",1,OPT_TEST_MULTI,"OPT","make testmode display per-server completion info (no(default)/long/short)");
	addoption("fullxover",1,OPT_FULLXOVER,"OPT","override fullxover setting (-1..2, default -1)");
	addoption("text",1,OPT_TEXT_HANDLING,"OPT","how to handle text posts (files(default)/mbox[:filename]/ignore)");
	addoption("save-binary-info",1,OPT_SAVE_TEXT_FOR_BINARIES,"OPT","save text files for posts that contained only binaries (yes/no(default))");
	addoption("tries",1,'t',"INT","set max retries (-1 unlimits, default 20)");
	addoption("delay",1,'s',"INT","seconds to wait between retry attempts(default 1)");
	addoption("timeout",1,OPT_TIMEOUT,"INT","seconds to wait for data from server(default 180)");
	addoption("limit",1,'l',"INT","min # of lines a 'file' must have(default 0)");
	addoption("maxlines",1,'L',"INT","max # of lines a 'file' must have(default -1)");
	addoption("incomplete",0,'i',0,"retrieve files with missing parts");
	addoption("complete",0,'I',0,"retrieve only files with all parts(default)");
	addoption("decode",0,OPT_DECODE,0,"decode and delete temp files (default)");
	addoption("keep",0,'k',0,"decode, but keep temp files");
	addoption("no-decode",0,'K',0,"keep temp files and don't even try to decode them");
	addoption("case",0,'c',0,"match casesensitively");
	addoption("nocase",0,'C',0,"match incasesensitively(default)");
	addoption("dupecheck",1,'d',"FLAGS","check to make sure you haven't already downloaded files(default -dfiM)");
	addoption("nodupecheck",0,'D',0,"don't check if you already have files(shortcut for -dFIM)");
	addoption("autopar",0,OPT_AUTOPAR,0,"only download as many par files as needed (default)");
	addoption("no-autopar",0,OPT_NOAUTOPAR,0,"disable special par file handling");
	addoption("mark",0,'M',0,"mark matching articles as retrieved");
	addoption("unmark",0,'U',0,"mark matching articles as not retrieved (implies -dI)");
	addoption("writelite",1,'w',"LITEFILE","write out a ngetlite list file");
	addoption("noconnect",0,'N',0,"don't connect, only try to decode what we have");
	addoption("help",0,OPT_HELP,0,"this help");
	addoption(NULL,0,0,NULL,NULL);
};
static void print_help(void){
	printf("nget v"PACKAGE_VERSION" - nntp command line fetcher\n");
	printf("Copyright 1999-2004 Matthew Mueller <donut AT dakotacom.net>\n");
	printf("\n\
This program is free software; you can redistribute it and/or modify\n\
it under the terms of the GNU General Public License as published by\n\
the Free Software Foundation; either version 2 of the License, or\n\
(at your option) any later version.\n\n\
This program is distributed in the hope that it will be useful,\n\
but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n\
GNU General Public License for more details.\n\n\
You should have received a copy of the GNU General Public License\n\
along with this program; if not, write to the Free Software\n\
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n");
	printf("\nUSAGE: nget -g group [-r file [-r file] [-g group [-r ...]]]\n");
	//this is kinda ugly, but older versions of popt don't have the poptPrintHelp stuff, and it seemed to print out a bit of garbage for me anyway...
	for (int i=0;
#ifdef HAVE_LIBPOPT
			optionsTable[i].longName;
#else
			long_options[i].name;
#endif
			i++){
		if(
#ifdef HAVE_LIBPOPT
				optionsTable[i].shortName
#else
				long_options[i].val
#endif
				>=OPT_MIN_SHORTNAME)
			printf("-%c  ",
#ifdef HAVE_LIBPOPT
				optionsTable[i].shortName
#else
				long_options[i].val
#endif
			);
		else
			printf("    ");
		if (
#ifdef HAVE_LIBPOPT
				optionsTable[i].argInfo!=POPT_ARG_NONE
#else
				long_options[i].has_arg
#endif
		)
			printf("--%s=%-*s",
#ifdef HAVE_LIBPOPT
					optionsTable[i].longName,
#else
					long_options[i].name,
#endif
					olongestlen-ohelp[i].namelen-1,ohelp[i].arg);
		else
			printf("--%-*s",
					olongestlen,
#ifdef HAVE_LIBPOPT
					optionsTable[i].longName
#else
					long_options[i].name
#endif
			);
		printf("  %s\n",ohelp[i].desc);
	}
#ifndef HAVE_LIBPOPT
#ifndef HAVE_GETOPT_LONG
	printf("Note: long options not supported by this compile\n");
#endif
#endif //!HAVE_LIBPOPT
	//cache_dbginfo();
//	getchar();
}


c_prot_nntp nntp;
SockPool sockpool;

void nget_cleanup(void) {
	try {
		nntp.cleanup();
		sockpool.expire_connections(true);
	}catch(baseEx &e){
		printCaughtEx(e);
	}catch(exception &e){
		PERROR("nget_cleanup: caught std exception %s",e.what());
	}catch(...){
		PERROR("nget_cleanup: caught unknown exception");
	}
}

static void term_handler(int s){
	PERROR("\nterm_handler: signal %i, shutting down.",s);
	nget_cleanup();
	exit(get_exit_status());
}


string nghome;
string ngcachehome;

//c_server_list servers;
//c_group_info_list groups;
//c_server_priority_grouping_list priogroups;

c_nget_config nconfig;

#define BAD_HOST 1
#define BAD_PATH 2
#define BAD_TEMPPATH 4
void nget_options::do_get_path(string &s){char *p=goodgetcwd();s=p;free(p);}

nget_options::nget_options(void){
	do_get_path(startpath);
	get_path();
	get_temppath();

	makedirs=0;
	maxretry=20;
	retrydelay=1;
	badskip=0;
	linelimit=0;
	maxlinelimit=ULONG_MAX;
	gflags=0;
	test_multi=NO_SHOW_MULTI;
	retr_show_multi=SHOW_MULTI_LONG;//always display the multi-server info when retrieving, just because I think thats better
	cmdmode=RETRIEVE_MODE;
	host=NULL;
	texthandling=TEXT_FILES;
	save_text_for_binaries=false;
	mboxfname="nget.mbox";
	fullxover=-1;
}
void nget_options::get_path(void){do_get_path(path);}
void nget_options::get_temppath(void){
	do_get_path(temppath);
	path_append(temppath,"");//ensure temppath ends with seperator
	assert(is_pathsep(temppath[temppath.size()-1]));
}
void nget_options::parse_dupe_flags(const char *opt){
	while(*opt){
		switch (*opt){
			case 'i':gflags&= ~GETFILES_NODUPEIDCHECK;break;
			case 'I':gflags|= GETFILES_NODUPEIDCHECK;break;
			case 'f':gflags&= ~GETFILES_NODUPEFILECHECK;break;
			case 'F':gflags|= GETFILES_NODUPEFILECHECK;break;
			case 'm':gflags|= GETFILES_DUPEFILEMARK;break;
			case 'M':gflags&= ~GETFILES_DUPEFILEMARK;break;
			default:
				PERROR("unknown dupe flag %c",*opt);
				set_user_error_status_and_do_fatal_user_error();
		}
		opt++;
	}
}
int nget_options::set_save_text_for_binaries(const char *s){
	if (!s) {
		return 0;
	}
	if (strcasecmp(s,"yes")==0)
		save_text_for_binaries=true;
	else if (strcasecmp(s,"no")==0)
		save_text_for_binaries=false;
	else{
		PERROR("set_save_text_for_binaries invalid option %s",s);
		set_user_error_status_and_do_fatal_user_error();
		return 0;
	}
	return 1;
}
int nget_options::set_text_handling(const char *s){
	if (!s) {
		return 0;
	}
	if (strcasecmp(s,"files")==0)
		texthandling=TEXT_FILES;
	else if (strcasecmp(s,"mbox")==0)
		texthandling=TEXT_MBOX;
	else if (strncasecmp(s,"mbox:",5)==0) {
		texthandling=TEXT_MBOX;
		mboxfname=s+5;
	}
	else if (strcasecmp(s,"ignore")==0)
		texthandling=TEXT_IGNORE;
	else{
		PERROR("set_text_handling invalid option %s",s);
		set_user_error_status_and_do_fatal_user_error();
		return 0;
	}
	return 1;
}
int nget_options::set_test_multi(const char *s){
	if (!s) {
		//printf("set_makedirs s=NULL\n");
		return 0;
	}
	if (strcasecmp(s,"short")==0)
		test_multi=SHOW_MULTI_SHORT;
	else if (strcasecmp(s,"long")==0)
		test_multi=SHOW_MULTI_LONG;
	else if (strcasecmp(s,"no")==0)
		test_multi=NO_SHOW_MULTI;
	else{
		PERROR("set_test_multi invalid option %s",s);
		set_user_error_status_and_do_fatal_user_error();
		return 0;
	}
	return 1;
}
#define MAKEDIRS_YES -1
#define MAKEDIRS_ASK -2
int nget_options::set_makedirs(const char *s){
	if (!s) {
		//printf("set_makedirs s=NULL\n");
		return 0;
	}
	if (strcasecmp(s,"yes")==0)
		makedirs=MAKEDIRS_YES;
	else if (strcasecmp(s,"ask")==0)
		makedirs=MAKEDIRS_ASK;
	else if (strcasecmp(s,"no")==0)
		makedirs=0;
	else{
		char *erp;
		int numcreate = strtol(s,&erp,10);
		if (*s=='\0' || *erp!='\0' || numcreate < 0) {
			PERROR("set_makedirs invalid option %s",s);
			set_user_error_status_and_do_fatal_user_error();
			return 0;
		}
		makedirs = numcreate;
	}
	return 1;
}
//	~nget_options(){del_path();}

int makedirs(const char *dir, int mode){
	string head(dir), tail;
	list<string> parts;
	while (!head.empty() && !direxists(head)) {
		path_split(head, tail);
		parts.push_front(tail);
	}
	while (!parts.empty()) {
		path_append(head, parts.front());
		parts.pop_front();
		if (mkdir(head.c_str(),mode))
			return -1;
	}
	return 0;
}

static int missing_path_tail_count(const char *dir) {
	string head(dir), tail;
	int count=0;
	while (!head.empty() && !direxists(head)) {
		path_split(head, tail);
		count++;
	}
	return count;
}

int maybe_mkdir_chdir(const char *dir, int makedir){
	if (chdir(dir)){
		if (errno==ENOENT && makedir){
			int missing_count = missing_path_tail_count(dir);
			assert(missing_count>0);
			bool doit=0;
			if (makedir==MAKEDIRS_ASK){
				char buf[40],*p;
				if (!is_abspath(dir)){
					p=goodgetcwd();
					printf("in %s, ",p);
					free(p);
				}
				printf("do you want to make dir %s?",dir);
				if (missing_count>1)
					printf(" (%i non-existant parts) ",missing_count);
				fflush(stdout);
				while (1){
					if (fgets(buf,39,stdin)){
						if ((p=strpbrk(buf,"\r\n"))) *p=0;
						if (strcasecmp(buf,"yes")==0 || strcasecmp(buf,"y")==0){
							doit=1;break;
						}
						if (strcasecmp(buf,"no")==0 || strcasecmp(buf,"n")==0){
							break;
						}
						char *erp;
						int numcreate = strtol(buf,&erp,10);
						if (*buf!='\0' && *erp=='\0') {
							if (numcreate >= missing_count)
								doit=1;
							break;
						}
						printf("%s?? enter y[es], n[o], or max # of dirs to create.\n",buf);
					}else{
						perror("fgets");
						return -1;
					}
				}
			}else if(makedir==MAKEDIRS_YES)
				doit=1;
			else if (makedir>=missing_count)
				doit=1;
			if (doit){
				if (makedirs(dir,PUBXMODE)){
					perror("mkdir");
					return -1;
				}
				if (chdir(dir)){
					perror("chdir");
					return -1;
				}
				return 0;
			}
		}
		return -1;
	}
	return 0;
}
struct s_argv {
	int argc;
	const char **argv;
};
static int do_args(int argc, const char **argv,nget_options options,int sub){
	int c=0;
	const char * loptarg=NULL;
	t_nntp_getinfo_list getinfos;
	t_xpat_list patinfos;
	t_grouplist_getinfo_list grouplistgetinfos;

#ifdef HAVE_LIBPOPT
#ifndef POPT_CONTEXT_ARG_OPTS
#define POPT_CONTEXT_ARG_OPTS 0
#endif
	c_poptContext optCon("nget", argc, argv, optionsTable, (sub?POPT_CONTEXT_KEEP_FIRST:0) | POPT_CONTEXT_ARG_OPTS);
#else
#ifdef HAVE_GETOPT_LONG
	int opt_idx;
#endif
#endif
	while (1){
		c=
#ifdef HAVE_LIBPOPT
					optCon.GetNextOpt()
#else //HAVE_LIBPOPT
#ifdef HAVE_GETOPT_LONG
					getopt_long(argc,GETOPT_ARGV_T argv,OPTIONS,long_options,&opt_idx)
#else
					getopt(argc,GETOPT_ARGV_T argv,OPTIONS)
#endif
#endif //!HAVE_LIBPOPT
					;
#ifdef HAVE_LIBPOPT
		loptarg=optCon.GetOptArg();
#else
		loptarg=optarg;
#endif //HAVE_LIBPOPT
//		printf("arg:%c(%i)=%s(%p)\n",isprint(c)?c:'.',c,loptarg,loptarg);
#if (HAVE_LIBPOPT && !POPT_CONTEXT_ARG_OPTS)
		if (optCon.GetArgs()) { //hack for older version of popt without POPT_CONTEXT_ARG_OPTS
			c = 0;
			loptarg = *optCon.GetArgs();
		}
#endif
		switch (c){
			case 'T':
				options.gflags|=GETFILES_TESTMODE;
				PDEBUG(DEBUG_MIN,"testmode now %i",options.gflags&GETFILES_TESTMODE > 0);
				break;
			case OPT_TEST_MULTI:
				options.set_test_multi(loptarg);
				break;
			case OPT_TEXT_HANDLING:
				options.set_text_handling(loptarg);
				break;
			case OPT_SAVE_TEXT_FOR_BINARIES:
				options.set_save_text_for_binaries(loptarg);
				break;
			case 'M':
				options.gflags|= GETFILES_MARK;
				options.gflags&= ~GETFILES_UNMARK;
				break;
			case 'U':
				options.gflags|= GETFILES_UNMARK;
				options.gflags&= ~GETFILES_MARK;
				options.parse_dupe_flags("I");
				break;
			case 'R':
				if (options.cmdmode==NOCACHE_GROUPLIST_MODE) {
					PERROR("-R is not yet supported with -X");
					set_user_error_status_and_do_fatal_user_error();
					break;
				}
				if (options.cmdmode==GROUPLIST_MODE) {
					try {
						nntp_grouplist_pred *p=make_grouplist_pred(loptarg, options.gflags);
						grouplistgetinfos.push_back(new c_grouplist_getinfo(p, options.gflags));
					}catch(RegexEx &e){
						printCaughtEx(e);
						set_user_error_status_and_do_fatal_user_error();
					}catch(UserEx &e){
						printCaughtEx(e);
						set_user_error_status_and_do_fatal_user_error();
					}
					break;
				}
				if (!options.badskip){
					if (options.cmdmode==NOCACHE_RETRIEVE_MODE) {
						PERROR("-R is not yet supported with -x");
						set_user_error_status_and_do_fatal_user_error();
						break;
					}
					if(options.groups.empty()){
						PERROR("no group specified");
						set_user_error_status_and_do_fatal_user_error();
					}else{
						try {
							nntp_file_pred *p=make_nntpfile_pred(loptarg, options.gflags);
							getinfos.push_back(new c_nntp_getinfo(options.path, options.temppath, options.dupepaths, p, options.gflags));
						}catch(RegexEx &e){
							printCaughtEx(e);
							set_user_error_status_and_do_fatal_user_error();
						}catch(UserEx &e){
							printCaughtEx(e);
							set_user_error_status_and_do_fatal_user_error();
						}
					}
				}
				break;
			case 'r':
				if (options.cmdmode==GROUPLIST_MODE || options.cmdmode==NOCACHE_GROUPLIST_MODE) {
					arglist_t e_parts;
					e_parts.push_back("group");
					e_parts.push_back(loptarg);
					e_parts.push_back("=~");

					e_parts.push_back("desc");
					e_parts.push_back(loptarg);
					e_parts.push_back("=~");

					e_parts.push_back("||");
					try {
						if (options.cmdmode==NOCACHE_GROUPLIST_MODE) {
							patinfos.push_back(new c_xpat("", regex2wildmat(loptarg, !(options.gflags&GETFILES_CASESENSITIVE))));
						}
						nntp_grouplist_pred *p=make_grouplist_pred(e_parts, options.gflags);
						grouplistgetinfos.push_back(new c_grouplist_getinfo(p, options.gflags));
					}catch(RegexEx &e){
						printCaughtEx(e);
						set_user_error_status_and_do_fatal_user_error();
					}
					//if make_pred breaks during -r, it can't be the users fault, since they only supply the regex, so let UserEx be be caught by the main func
					break;
				}
				if (!options.badskip){
					if(options.groups.empty()){
						PERROR("no group specified");
						set_user_error_status_and_do_fatal_user_error();
					}else{
						arglist_t e_parts;
						e_parts.push_back("subject");
						e_parts.push_back(loptarg);
						e_parts.push_back("=~");
						//use push_front for lines tests, to exploit short circuit evaluation (since comparing integers is faster than regexs)
						if (options.linelimit > 0) {
							e_parts.push_front(">=");
							e_parts.push_front(tostr(options.linelimit));
							e_parts.push_front("lines");
							e_parts.push_back("&&");
						}
						if (options.maxlinelimit < ULONG_MAX) {
							e_parts.push_front("<=");
							e_parts.push_front(tostr(options.maxlinelimit));
							e_parts.push_front("lines");
							e_parts.push_back("&&");
						}
						try {
							if (options.cmdmode==NOCACHE_RETRIEVE_MODE) {
								patinfos.push_back(new c_xpat("Subject", regex2wildmat(loptarg, !(options.gflags&GETFILES_CASESENSITIVE))));
							}
							nntp_file_pred *p=make_nntpfile_pred(e_parts, options.gflags);
							getinfos.push_back(new c_nntp_getinfo(options.path, options.temppath, options.dupepaths, p, options.gflags));
						}catch(RegexEx &e){
							printCaughtEx(e);
							set_user_error_status_and_do_fatal_user_error();
						}
						//if make_pred breaks during -r, it can't be the users fault, since they only supply the regex, so let UserEx be be caught by the main func
					}
				}
				break;
			case OPT_AUTOPAR:
				options.gflags&= ~GETFILES_NOAUTOPAR;
				break;
			case OPT_NOAUTOPAR:
				options.gflags|= GETFILES_NOAUTOPAR;
				break;
			case 'i':
				options.gflags|= GETFILES_GETINCOMPLETE;
				break;
			case 'I':
				options.gflags&= ~GETFILES_GETINCOMPLETE;
				break;
			case 'k':
				options.gflags|= GETFILES_KEEPTEMP;
				break;
			case 'K':
				options.gflags|= GETFILES_NODECODE;
				break;
			case OPT_DECODE:
				options.gflags&= ~(GETFILES_NODECODE|GETFILES_KEEPTEMP);
				break;
			case 'c':
				options.gflags|= GETFILES_CASESENSITIVE;
				break;
			case 'C':
				options.gflags&= ~GETFILES_CASESENSITIVE;
				break;
			case 'd':
				options.parse_dupe_flags(loptarg);
				break;
			case 'D':
				options.parse_dupe_flags("FIM");
				break;
			case 'N':
				options.gflags|= GETFILES_NOCONNECT;
				break;
			case 'q':
				quiet++;
				break;
			case 's':
				if (parsestr(loptarg, options.retrydelay, 0, INT_MAX, "--delay"))
					PMSG("retry delay set to %i",options.retrydelay);
				break;
			case OPT_TIMEOUT:
				if (parsestr(loptarg, sock_timeout, 1, INT_MAX, "--timeout"))
					PMSG("sock timeout set to %i",sock_timeout);
				break;
			case 't':
				if (parsestr(loptarg, options.maxretry, -1, INT_MAX, "--tries")) {
					if (options.maxretry<=0)
						options.maxretry=INT_MAX-1;
					PMSG("max retries set to %i",options.maxretry);
				}
				break;
			case 'L':
				if (strcmp(loptarg,"-1")==0)
					options.maxlinelimit=ULONG_MAX;
				else if (!parsestr(loptarg, options.maxlinelimit, "--maxlines"))
					break;
				PMSG("maximum line limit set to %lu",options.maxlinelimit);
				break;
			case 'l':
				if (parsestr(loptarg, options.linelimit, "--limit"))
					PMSG("minimum line limit set to %lu",options.linelimit);
				break;
			case 'w':
				options.writelite=loptarg;
				PMSG("writelite to %s",options.writelite.c_str());
				break;
			case 'm':
				options.set_makedirs(loptarg);
				break;
			case 'P':
				if (!maybe_mkdir_chdir(loptarg,options.makedirs)){
					options.get_temppath();
					PMSG("temppath:%s",options.temppath.c_str());
					options.badskip &= ~BAD_TEMPPATH;
					if (chdir(options.startpath.c_str())){
						set_path_error_status();
						throw ApplicationExFatal(Ex_INIT, "could not change to startpath: %s",options.startpath.c_str());
					}
				}else{
					PERROR("could not change temppath to %s",loptarg);
					set_path_error_status_and_do_fatal_user_error();
					options.badskip |= BAD_TEMPPATH;
				}
				break;
			case 'p':
				if (!maybe_mkdir_chdir(loptarg,options.makedirs)){
					options.dupepaths.clear();
					options.get_path();
					options.get_temppath();
					PMSG("(temp)path:%s",options.path.c_str());
					options.badskip &= ~(BAD_TEMPPATH | BAD_PATH);
					if (chdir(options.startpath.c_str())){
						set_path_error_status();
						throw ApplicationExFatal(Ex_INIT, "could not change to startpath: %s",options.startpath.c_str());
					}
				}else{
					PERROR("could not change to %s",loptarg);
					set_path_error_status_and_do_fatal_user_error();
					options.badskip |= (BAD_TEMPPATH | BAD_PATH);
				}
				break;
			case OPT_DUPEPATH:
				if (direxists(loptarg)) {
					options.dupepaths.push_back(loptarg);
				} else {
					PERROR("dupepath %s does not exist",loptarg);
					set_path_error_status_and_do_fatal_user_error();
				}
				break;
			case OPT_FULLXOVER:
				parsestr(loptarg, options.fullxover, -1, 2, "--fullxover");
				break;
			case 'F':
				{
					c_server::ptr server=nconfig.getserver(loptarg);
					if (!server) {PERROR("no such server %s",loptarg);set_user_error_status_and_do_fatal_user_error();break;}
					if (options.cmdmode==NOCACHE_RETRIEVE_MODE || options.cmdmode==NOCACHE_GROUPLIST_MODE) {
						PERROR("nothing to flush in nocache mode");
						set_user_error_status_and_do_fatal_user_error();
						break;
					}
					if (options.cmdmode==GROUPLIST_MODE) {
						nntp.nntp_grouplist(0, options);
						nntp.glist->flushserver(server->serverid);
						break;
					}
					if (options.groups.empty()){
						PERROR("specify group before -F");
						set_user_error_status_and_do_fatal_user_error();
						break;
					}
					for (vector<c_group_info::ptr>::const_iterator gi=options.groups.begin(); gi!=options.groups.end(); gi++) {
						nntp.nntp_group(*gi, false, options);
						c_nntp_server_info* servinfo=nntp.gcache->getserverinfo(server->serverid);
						nntp.gcache->flushlow(servinfo,ULONG_MAX,nntp.midinfo);
						servinfo->reset();
					}
					break;
				}
#ifdef HAVE_LIBPOPT
#define POPT_ERR_CASE(a) case a: PERROR("%s: %s",#a,optCon.BadOption(0)); set_user_error_status_and_do_fatal_user_error(); break;
			POPT_ERR_CASE(POPT_ERROR_NOARG);
			POPT_ERR_CASE(POPT_ERROR_BADOPT);
			POPT_ERR_CASE(POPT_ERROR_OPTSTOODEEP);
			POPT_ERR_CASE(POPT_ERROR_BADQUOTE);
			POPT_ERR_CASE(POPT_ERROR_ERRNO);
			POPT_ERR_CASE(POPT_ERROR_BADNUMBER);
			POPT_ERR_CASE(POPT_ERROR_OVERFLOW);
#endif
			case ':':
			case '?':
				//getopt prints the error message itself.
				set_user_error_status_and_do_fatal_user_error();
				break;
			case OPT_HELP:
				print_help();
				return 1;
			case 0://POPT_CONTEXT_ARG_OPTS
			case 1://getopt arg
				PERROR("invalid command line arg: %s", loptarg);
				set_user_error_status_and_do_fatal_user_error();
				return 1;
			default:
				if (!grouplistgetinfos.empty()) {
					if(!(options.gflags&GETFILES_TESTMODE)){
						PERROR("testmode required for grouplist");
						set_user_error_status_and_do_fatal_user_error();
					}else if (!patinfos.empty()){
						nntp.nntp_grouplist_search(grouplistgetinfos, patinfos, options);
					}else{
						nntp.nntp_grouplist_search(grouplistgetinfos, options);
					}
					grouplistgetinfos.clear();
					patinfos.clear();
				}
				if (!patinfos.empty()){
					nntp.nntp_retrieve(options.groups, getinfos, patinfos, options);
					getinfos.clear();
					patinfos.clear();
				}
				else if (!getinfos.empty()){
					nntp.nntp_retrieve(options.groups, getinfos, options);
					getinfos.clear();
				}
				switch (c){
					case '@':
#ifdef HAVE_LIBPOPT
						{
							string filename=fcheckpath(loptarg,path_join(nghome,"lists"));
							s_argv larg;
							arglist_t arglist;
							try {
								c_file_fd f(filename.c_str(),O_RDONLY);
								f.initrbuf();
								while (f.bgets()>0){
									try{
										parseargs(arglist, f.rbufp(), true);
									} catch(UserExFatal &e) {
										printCaughtEx(e);
										set_user_error_status_and_do_fatal_user_error();
										break;
									}
								}
								f.close();
							}catch (FileNOENTEx &e){
								PERROR("error: %s",e.getExStr());
								set_user_error_status_and_do_fatal_user_error();
								break;
							}
							if (!arglist.empty()){
								int tc=0;
								larg.argc=arglist.size();
								larg.argv=(const char**)malloc((larg.argc+1)*sizeof(char**));
								arglist_t::iterator it=arglist.begin();
								for (;it!=arglist.end();++it,++tc){
									larg.argv[tc]=it->c_str();
								}

								do_args(larg.argc,larg.argv,options,1);
								//here we reset the stuff that may have been screwed in our recursiveness.  Perhaps it should reset it before returning, or something.. but I guess this'll do for now, since its the only place its called recursively.
								if (options.host)
									nntp.nntp_open(options.host);
								if (!chdir(options.startpath.c_str())){
									PMSG("path:%s",options.path.c_str());
								}else{
									set_path_error_status();
									throw ApplicationExFatal(Ex_INIT, "could not change to startpath: %s",options.startpath.c_str());
								}

								free(larg.argv);
							}
						}
#else
						PERROR("This option is only available when libpopt is used.");
#endif
						break;
					case 'a':
						//if BAD_HOST, don't try to -a, fall through to -A instead
						if (!(options.badskip & BAD_HOST)){
							options.cmdmode=GROUPLIST_MODE;
							nntp.nntp_grouplist(1, options);
							break;
						}
					case 'A':
						options.cmdmode=GROUPLIST_MODE;
						break;
					case 'X':
						options.cmdmode=NOCACHE_GROUPLIST_MODE;
						break;
					case 'g':
						//if BAD_HOST, don't try to -g, fall through to -G instead
						if (!(options.badskip & BAD_HOST)){
							options.cmdmode=RETRIEVE_MODE;
							nconfig.getgroups(options.groups, loptarg);
							for (vector<c_group_info::ptr>::const_iterator gi=options.groups.begin(); gi!=options.groups.end(); gi++)
								nntp.nntp_group(*gi, true, options);
							break;
						}
					case 'G':
						options.cmdmode=RETRIEVE_MODE;
						nconfig.getgroups(options.groups, loptarg);
						break;
					case 'x':
						options.cmdmode=NOCACHE_RETRIEVE_MODE;
						nconfig.getgroups(options.groups, loptarg);
						break;
					case 'h':{
							if (*loptarg){
								options.host=nconfig.getserver(loptarg);
								if (options.host.isnull()){
									options.badskip |= BAD_HOST;
									PERROR("invalid host %s (must be configured in .ngetrc first)",loptarg);
									set_user_error_status_and_do_fatal_user_error();
								}
								else
									options.badskip &= ~BAD_HOST;
							}else{
								options.host=NULL;
								options.badskip &= ~BAD_HOST;
							}
							nntp.nntp_open(options.host);
						}
						break;
					case -1://end of args.
						return 0;
					default:
						print_help();
						return 1;
				}
		}
	}
}

int main(int argc, const char ** argv){
#ifdef HAVE_SETLINEBUF
	setlinebuf(stdout); //force stdout to be line buffered, useful if redirecting both stdout and err to a file, to keep them from getting out of sync.
#endif
	atexit(print_error_status);
	try {
		sockstuff_init();
		//	atexit(cache_dbginfo);
		addoptions();
		signal(SIGTERM,term_handler);
#ifdef SIGHUP
		signal(SIGHUP,term_handler);
#endif
		signal(SIGINT,term_handler);
#ifdef SIGQUIT
		signal(SIGQUIT,term_handler);
#endif
#ifdef SIGPIPE
		signal(SIGPIPE,SIG_IGN); //don't die on broken connections (some platforms don't support MSG_NOSIGNAL)
#endif
		{
			char *home;
			home=getenv("NGETHOME");
			if (home && *home) {
				nghome=path_join(home,"");
			} else {
				home=getenv("HOME");
				if (!home || !*home)
					throw ConfigExFatal(Ex_INIT,"HOME or NGETHOME environment var not set.");
				nghome = home;
				if (direxists(path_join(home,".nget5","")))
					nghome=path_join(home,".nget5","");
				else if (direxists(path_join(home,"_nget5","")))
					nghome=path_join(home,"_nget5","");
				else
					throw ConfigExFatal(Ex_INIT,"neither %s nor %s exist", path_join(home,".nget5","").c_str(), path_join(home,"_nget5","").c_str());
			}
		}
		ngcachehome = nghome;

		srand(time(NULL));
		if (argc<2){
			print_help();
		}
		else {
			nget_options options;
			{
				char *cp;
				cp = getenv("NGETRC");
				string ngetrcfn;
				if (cp && *cp) {
					ngetrcfn = cp;
					if (!fexists(ngetrcfn))
						throw ConfigExFatal(Ex_INIT,"NGETRC %s: not found", ngetrcfn.c_str());
				}
				else {
					ngetrcfn = nghome + ".ngetrc";
					if (!fexists(ngetrcfn)) {
						ngetrcfn = nghome + "_ngetrc";
						if (!fexists(ngetrcfn))
							throw ConfigExFatal(Ex_INIT,"neither %s nor %s exist", (nghome + ".ngetrc").c_str(), ngetrcfn.c_str());
					}
				}
				CfgSection cfg(ngetrcfn);
				const CfgSection *galias,*halias,*hpriority;
				halias=cfg.getsection("halias");
				hpriority=cfg.getsection("hpriority");
				galias=cfg.getsection("galias");
				if (!halias)
					throw ConfigExFatal(Ex_INIT,"no halias section");
				nconfig.setlist(&cfg,halias,hpriority,galias);
				int t;
				/*			if (!halias || !(options.host=halias->getitema("default")))
							options.host=getenv("NNTPSERVER")?:"";
							nntp.nntp_open(options.host,NULL,NULL);*/
				/*			options.host=halias->getsection("default");
							nntp.nntp_open(options.host);*/
				cfg.get("timeout",sock_timeout,1,INT_MAX);
				cfg.get("debug",debug,0,DEBUG_ALL);
				cfg.get("quiet",quiet,0,2);
				cfg.get("limit",options.linelimit,0UL,ULONG_MAX);
				cfg.get("tries",options.maxretry,1,INT_MAX);
				cfg.get("delay",options.retrydelay,0,INT_MAX);
				if (cfg.get("case",t,0,1) && t==1)
					options.gflags|= GETFILES_CASESENSITIVE;
				if (cfg.get("complete",t,0,1) && t==0)
					options.gflags|= GETFILES_GETINCOMPLETE;
				if (cfg.get("dupeidcheck",t,0,1) && t==0)
					options.gflags|= GETFILES_NODUPEIDCHECK;
				if (cfg.get("dupefilecheck",t,0,1) && t==0)
					options.gflags|= GETFILES_NODUPEFILECHECK;
				if (cfg.get("dupefilemark",t,0,1) && t==1)
					options.gflags|= GETFILES_DUPEFILEMARK;
				if (cfg.get("tempshortnames",t,0,1) && t==1)
					options.gflags|= GETFILES_TEMPSHORTNAMES;
				if (cfg.get("save_binary_info",t,0,1) && t==1)
					options.save_text_for_binaries=true;
				if (cfg.get("autopar",t,0,1) && t==0)
					options.gflags|= GETFILES_NOAUTOPAR;
				options.set_test_multi(cfg.geta("test_multiserver"));
				options.set_text_handling(cfg.geta("text"));
				options.set_makedirs(cfg.geta("makedirs"));
				
				cfg.get("cachedir",ngcachehome);//.ngetrc setting overrides default
				cp=getenv("NGETCACHE");//environment var overrides .ngetrc
				if (cp && *cp)
					ngcachehome=cp;
				ngcachehome = path_join(ngcachehome, "");
				if (!direxists(ngcachehome))
					throw ConfigExFatal(Ex_INIT,"cache dir %s does not exist", ngcachehome.c_str());
				cfg.check_unused();
			}
			//check for user errors here rather than using set_user_error_status_and_do_fatal_user_error, so that all config entries can be checked before exiting.
			if (get_user_error_status() && nconfig.fatal_user_errors)
				throw FatalUserException();
			init_term_stuff();
			options.get_path();
			nntp.initready();
			do_args(argc,argv,options,0);
		}
	}catch(FatalUserException &e){
		PERROR("fatal_user_errors enabled, exiting");
	}catch(ConfigEx &e){
		set_fatal_error_status();
		printCaughtEx(e);
		PERROR("(see man nget for configuration info)");
	}catch(baseEx &e){
		set_fatal_error_status();
		PERROR_nnl("main():");printCaughtEx(e);
	}catch(exception &e){
		set_fatal_error_status();
		PERROR("caught std exception %s",e.what());
	}catch(...){
		set_fatal_error_status();
		PERROR("caught unknown exception");
	}
	nget_cleanup();

	return get_exit_status();
}



syntax highlighted by Code2HTML, v. 0.9.1