Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

prntfile.cpp 8.95 KB
Newer Older
1 2 3 4 5 6
/* Synchronet file print/display routines */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *																			*
 * 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.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
23
#include "utf8.h"
24
#include "petdefs.h"
25

26
#ifndef PRINTFILE_MAX_LINE_LEN
27
#define PRINTFILE_MAX_LINE_LEN (8*1024)
28
#endif
29
#ifndef PRINTFILE_MAX_FILE_LEN
30
#define PRINTFILE_MAX_FILE_LEN (2*1024*1024)
31
#endif
32

33 34 35 36 37
/****************************************************************************/
/* Prints a file remotely and locally, interpreting ^A sequences, checks    */
/* for pauses, aborts and ANSI. 'str' is the path of the file to print      */
/* Called from functions menu and text_sec                                  */
/****************************************************************************/
38
bool sbbs_t::printfile(const char* fname, long mode, long org_cols, JSObject* obj)
39
{
40
	char* buf;
41
	char fpath[MAX_PATH+1];
42
	char* p;
rswindell's avatar
rswindell committed
43
	int file;
44
	BOOL rip=FALSE;
rswindell's avatar
rswindell committed
45
	long l,length,savcon=console;
46 47
	FILE *stream;

48 49 50
	SAFECOPY(fpath, fname);
	fexistcase(fpath);
	p=getfext(fpath);
51
	if(p!=NULL) {
52
		if(stricmp(p,".rip")==0) {
rswindell's avatar
rswindell committed
53
			rip=TRUE;
rswindell's avatar
rswindell committed
54
			mode|=P_NOPAUSE;
55 56
		} else if(stricmp(p, ".seq") == 0) {
			mode |= P_PETSCII;
57 58
		} else if(stricmp(p, ".utf8") == 0) {
			mode |= P_UTF8;
rswindell's avatar
rswindell committed
59
		}
60
	}
61

62
	if(mode&P_NOABORT || rip) {
63 64
		if(online==ON_REMOTE && console&CON_R_ECHO) {
			rioctl(IOCM|ABORT);
rswindell's avatar
rswindell committed
65 66 67 68
			rioctl(IOCS|ABORT); 
		}
		sys_status&=~SS_ABORT; 
	}
69

70
	if((stream=fnopen(&file,fpath,O_RDONLY|O_DENYNONE))==NULL) {
71 72
		if(!(mode&P_NOERROR)) {
			lprintf(LOG_NOTICE,"!Error %d (%s) opening: %s"
73
				,errno,strerror(errno),fpath);
74
			bputs(text[FileNotFound]);
75
			if(SYSOP) bputs(fpath);
76 77 78
			CRLF;
		}
		return false; 
rswindell's avatar
rswindell committed
79
	}
80

81
	length=(long)filelength(file);
82
	if(length < 1) {
rswindell's avatar
rswindell committed
83
		fclose(stream);
84 85 86 87 88
		if(length < 0) {
			errormsg(WHERE,ERR_CHK,fpath,length);
			return false;
		}
		return true;
rswindell's avatar
rswindell committed
89
	}
90

91 92 93 94
	if(!(mode&P_NOCRLF) && row > 0 && !rip) {
		newline();
	}

95
	if((mode&P_OPENCLOSE) && length <= PRINTFILE_MAX_FILE_LEN) {
96 97 98 99 100 101 102 103 104 105 106 107 108
		if((buf=(char*)malloc(length+1L))==NULL) {
			fclose(stream);
			errormsg(WHERE,ERR_ALLOC,fpath,length+1L);
			return false; 
		}
		l=lread(file,buf,length);
		fclose(stream);
		if(l!=length)
			errormsg(WHERE,ERR_READ,fpath,length);
		else {
			buf[l]=0;
			if((mode&P_UTF8) && !term_supports(UTF8))
				utf8_normalize_str(buf);
109
			putmsg(buf,mode,org_cols, obj);
110 111 112
		}
		free(buf);
	} else {	// Line-at-a-time mode
113 114
		ulong sys_status_sav = sys_status;
		enum output_rate output_rate = cur_output_rate;
115
		uint tmpatr = curatr;
116 117
		ulong orgcon = console;
		attr_sp = 0;	/* clear any saved attributes */
118 119
		if(!(mode&P_SAVEATR))
			attr(LIGHTGRAY);
120 121
		if(mode&P_NOPAUSE)
			sys_status |= SS_PAUSEOFF;
122 123 124 125 126 127 128 129
		if(length > PRINTFILE_MAX_LINE_LEN)
			length = PRINTFILE_MAX_LINE_LEN;
		if((buf=(char*)malloc(length+1L))==NULL) {
			fclose(stream);
			errormsg(WHERE,ERR_ALLOC,fpath,length+1L);
			return false; 
		}
		while(!feof(stream) && !msgabort()) {
130
			if(fgets(buf, length + 1, stream) == NULL)
131 132 133
				break;
			if((mode&P_UTF8) && !term_supports(UTF8))
				utf8_normalize_str(buf);
134
			if(putmsgfrag(buf, &mode, org_cols, obj) != '\0') // early-EOF?
135
				break;
136 137
		}
		free(buf);
rswindell's avatar
rswindell committed
138
		fclose(stream);
139 140
		if(!(mode&P_SAVEATR)) {
			console = orgcon;
141
			attr(tmpatr);
142 143 144 145 146 147 148 149 150 151
		}
		if(!(mode&P_NOATCODES) && cur_output_rate != output_rate)
			set_output_rate(output_rate);
		if(mode&P_PETSCII)
			outcom(PETSCII_UPPERLOWER);
		attr_sp=0;	/* clear any saved attributes */

		/* Restore original settings of Forced Pause On/Off */
		sys_status &= ~(SS_PAUSEOFF|SS_PAUSEON);
		sys_status |= (sys_status_sav&(SS_PAUSEOFF|SS_PAUSEON));
rswindell's avatar
rswindell committed
152
	}
153

154
	if((mode&P_NOABORT || rip) && online==ON_REMOTE) {
155
		SYNC;
rswindell's avatar
rswindell committed
156 157
		rioctl(IOSM|ABORT); 
	}
158 159 160
	if(rip)
		ansi_getlines();
	console=savcon;
161
	return true;
rswindell's avatar
rswindell committed
162
}
163

164
bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols, JSObject* obj)
165
{
rswindell's avatar
rswindell committed
166
	char*	buf;
167
	char	fpath[MAX_PATH+1];
rswindell's avatar
rswindell committed
168
	char*	p;
169
	FILE*	fp;
rswindell's avatar
rswindell committed
170 171
	int		file,cur=0;
	long	length,l;
172

173 174
	SAFECOPY(fpath, fname);
	fexistcase(fpath);
175 176 177
	if(mode&P_NOABORT) {
		if(online==ON_REMOTE) {
			rioctl(IOCM|ABORT);
rswindell's avatar
rswindell committed
178 179 180 181
			rioctl(IOCS|ABORT); 
		}
		sys_status&=~SS_ABORT; 
	}
182
	if((fp=fnopen(&file,fpath,O_RDONLY|O_DENYNONE))==NULL) {
183 184
		if(!(mode&P_NOERROR)) {
			lprintf(LOG_NOTICE,"!Error %d (%s) opening: %s"
185
				,errno,strerror(errno),fpath);
186
			bputs(text[FileNotFound]);
187
			if(SYSOP) bputs(fpath);
188 189 190
			CRLF;
		}
		return false; 
rswindell's avatar
rswindell committed
191
	}
192 193 194
	if(!(mode&P_NOCRLF) && row > 0) {
		newline();
	}
195
	length=(long)filelength(file);
rswindell's avatar
rswindell committed
196
	if(length<0) {
197
		fclose(fp);
198
		errormsg(WHERE,ERR_CHK,fpath,length);
199
		return false;
rswindell's avatar
rswindell committed
200
	}
201 202
	if(length > lines * PRINTFILE_MAX_LINE_LEN) {
		length = lines * PRINTFILE_MAX_LINE_LEN; 
Rob Swindell's avatar
Rob Swindell committed
203
		(void)fseek(fp, -length, SEEK_END);
204
	}
deuce's avatar
deuce committed
205
	if((buf=(char*)malloc(length+1L))==NULL) {
206
		fclose(fp);
207
		errormsg(WHERE,ERR_ALLOC,fpath,length+1L);
208
		return false; 
rswindell's avatar
rswindell committed
209
	}
210 211
	l=fread(buf, sizeof(char), length, fp);
	fclose(fp);
rswindell's avatar
rswindell committed
212
	if(l!=length)
213
		errormsg(WHERE,ERR_READ,fpath,length);
rswindell's avatar
rswindell committed
214 215 216 217 218 219 220 221 222 223 224 225
	else {
		buf[l]=0;
		p=(buf+l)-1;
		if(*p==LF) p--;
		while(*p && p>buf) {
			if(*p==LF)
				cur++;
			if(cur>=lines) {
				p++;
				break; 
			}
			p--; 
rswindell's avatar
rswindell committed
226
		}
227
		putmsg(p,mode,org_cols, obj);
rswindell's avatar
rswindell committed
228
	}
229 230
	if(mode&P_NOABORT && online==ON_REMOTE) {
		SYNC;
rswindell's avatar
rswindell committed
231 232
		rioctl(IOSM|ABORT); 
	}
deuce's avatar
deuce committed
233
	free(buf);
234
	return true;
235 236 237
}

/****************************************************************************/
238
/* Displays a menu file (e.g. from the text/menu directory)                 */
239 240
/* Pass a code including wildcards (* or ?) to display a randomly-chosen	*/
/* file matching the pattern in 'code'										*/
241
/****************************************************************************/
242
bool sbbs_t::menu(const char *code, long mode, JSObject* obj)
243
{
244 245 246
    char path[MAX_PATH+1];
	const char *next= "msg";
	const char *last = "asc";
247

248 249 250
	if(strcspn(code, "*?") != strlen(code))
		return random_menu(code, mode, obj);

251 252
	sys_status&=~SS_ABORT;
	if(menu_file[0])
253
		SAFECOPY(path,menu_file);
254
	else {
rswindell's avatar
rswindell committed
255
		long term = term_supports();
256 257 258 259 260 261 262 263 264 265 266 267
		do {
			if((term&RIP) && menu_exists(code, "rip", path))
				break;
			if((term&(ANSI|COLOR)) == ANSI && menu_exists(code, "mon", path))
				break;
			if((term&ANSI) && menu_exists(code, "ans", path))
				break;
			if((term&PETSCII) && menu_exists(code, "seq", path))
				break;
			if(term&NO_EXASCII) {
				next = "asc";
				last = "msg";
rswindell's avatar
rswindell committed
268
			}
269 270
			if(menu_exists(code, next, path))
				break;
271 272
			if(!menu_exists(code, last, path))
				return false;
273
		} while(0);
274
	}
275

276
	mode |= P_OPENCLOSE | P_CPM_EOF;
277 278
	if(column == 0)
		mode |= P_NOCRLF;
279
	return printfile(path, mode, /* org_cols: */0, obj);
280 281
}

282
bool sbbs_t::menu_exists(const char *code, const char* ext, char* path)
283
{
284 285 286
	char pathbuf[MAX_PATH+1];
	if(path == NULL)
		path = pathbuf;
287

288 289 290 291 292 293 294 295 296
	if(menu_file[0]) {
		strncpy(path, menu_file, MAX_PATH);
		return fexistcase(path) ? true : false;
	}

	/* Either <menu>.asc or <menu>.msg is required */
	if(ext == NULL)
		return menu_exists(code, "asc", path)
			|| menu_exists(code, "msg", path);
297

298 299 300 301 302 303
	char prefix[MAX_PATH];
	if(isfullpath(code))
		SAFECOPY(prefix, code);
	else {
		backslash(menu_dir);
		SAFEPRINTF3(prefix, "%smenu/%s%s", cfg.text_dir, menu_dir, code);
304 305
		FULLPATH(path, prefix, MAX_PATH);
		SAFECOPY(prefix, path);
306
	}
307
	safe_snprintf(path, MAX_PATH, "%s.%lucol.%s", prefix, cols, ext);
rswindell's avatar
rswindell committed
308 309
	if(fexistcase(path))
		return true;
310
	safe_snprintf(path, MAX_PATH, "%s.%s", prefix, ext);
deuce's avatar
deuce committed
311 312
	return fexistcase(path) ? true : false;
}
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343

/****************************************************************************/
/* Displays a random menu file (e.g. from the text/menu directory)          */
/****************************************************************************/
bool sbbs_t::random_menu(const char *name, long mode, JSObject* obj)
{
	char path[MAX_PATH + 1];
	glob_t g = {0};
	str_list_t names = NULL;

	SAFEPRINTF2(path, "%smenu/%s", cfg.text_dir, name);
	if(glob(path, GLOB_NOESCAPE|GLOB_MARK, NULL, &g) != 0) {
		return false;
	}
	for(size_t i = 0; i < g.gl_pathc; i++) {
		char* ext = getfext(g.gl_pathv[i]);
		if(ext == NULL)
			continue;
		*ext = 0;
		strListPush(&names, g.gl_pathv[i]);
	}
	globfree(&g);
	strListDedupe(&names, /* case_sensitive: */true);
	bool result = false;
	size_t i = sbbs_random(strListCount(names));
	if(menu_exists(names[i], NULL, path)) {
		result = menu(names[i], mode, obj);
	}
	strListFree(&names);
	return result;
}