1    | /*
2    |  * $Log: history.c,v $
3    |  * Revision 1.9  2002/08/27 22:27:50  millis
4    |  * Updated for automake/autoconf functionality
5    |  *
6    |  * Revision 1.8  2002/05/11 09:15:32  greg
7    |  * Minor bug fixes
8    |  * - fixed subjectline for absence requests
9    |  * - fixed phase length, so it's no longer hard coded for responses
10   |  * - partial fix for unusable builds, players with only unusable builds
11   |  *    will no longer be flagged as having orders due, however players
12   |  *    with some usable builds will need to waive any unusable builds,
13   |  *    also, if one or more players have unusable builds, but no
14   |  *    player has usable builds, the build phase will process after
15   |  *    a short delay
16   |  *
17   |  * Revision 1.7  2001/08/30 03:40:12  greg
18   |  * fix "history exclstart" to work with both old and new subject lines
19   |  *
20   |  * Revision 1.6  2001/07/15 09:15:19  greg
21   |  * added support for game directories in a sub directory
22   |  *
23   |  * Revision 1.5  2001/07/14 07:25:41  greg
24   |  * fix "history exclstart" command with new subjectlines
25   |  *
26   |  * Revision 1.4  2001/02/23 00:19:33  miller
27   |  * un-DOSify
28   |  *
29   |  * Revision 1.3  2001/02/03 10:36:16  miller
30   |  * properly hide history in blind game
31   |  *
32   |  * Revision 1.2  2000/11/14 14:27:37  miller
33   |  * Restrict history for blind games (so that players cannot find out more than they should!)
34   |  *
35   |  * Revision 1.1  1998/02/28 17:49:42  david
36   |  * Initial revision
37   |  *
38   |  * Revision 1.3  1996/11/10 14:22:47  rpaar
39   |  * changed the "broad#" to "broad" in the keys[] array.
40   |  * modified the return text to include newlines.
41   |  *
42   |  * Revision 1.2  1996/11/05 23:06:12  rpaar
43   |  * Added function to process history as turns not dates
44   |  */
45   | 
46   | /*  history.c -- Extract selected portions of the history archives.
47   |  *  Copyright 1987, Lowe.
48   |  *
49   |  *  Diplomacy is a trademark of the Avalon Hill Game Company, Baltimore,
50   |  *  Maryland, all rights reserved; used with permission.
51   |  *
52   |  *  Redistribution and use in source and binary forms are permitted
53   |  *  provided that it is for non-profit purposes, that this and the 
54   |  *  above notices are preserved and that due credit is given to Mr.
55   |  *  Lowe.
56   |  */
57   | 
58   | #include <stdlib.h>
59   | #include <string.h>
60   | #include <sys/stat.h>
61   | #include <sys/types.h>
62   | #include <time.h>
63   | 
64   | #include "config.h"
65   | #include "dip.h"
66   | #include "functions.h"
67   | #include "mail.h"
68   | 
69   | static FILE *fp;
70   | static int lcnt;
71   | static struct stat sbuf;
72   | 
73   | /***************************************************************************/
74   | 
75   | /* Moded by R.Paar 27 May 1996
76   |  */
77   | 
78   | /* Function: doturns
79   | 
80   |  * Input params: 
81   |  *       int nbflag - flag whether notices should be included (1), or Not (0)
82   |  *       char * against - the starting turn for exclusion zone
83   |  *       char * lastturn - ending turn for exlusion zone
84   |  *       char * GameName - name of the game we are currently processing.
85   |  *
86   |  * Output: 1 on success
87   |  *
88   |  * Error Checking: None.  
89   |  *                 To keep it simple:
90   |  *                   nbflag cannot be incorrectly passed.  (Should be a BOOL)
91   |  *                   on incorrect 'against' parameter, copies whole archive (beware!)
92   |  *                   on incorrect 'lastturn' parameter, copies only to 'against' turn.
93   |  *
94   |  * Objective: Copy a history archive such that starting on turn 'against'
95   |  *   the archive is not copied.  Ends on turn 'lastturn'.
96   |  *   If nbflag set, exclude _ALL_ notices.  (N.B. this includes draw notices)
97   |  *
98   |  * N.B:  I've hacked it so it will include draws in the returned history.  
99   |  *
100  |  * Q: Shouldn't Draws be given a "Diplomacy results" subject line?
101  |  */
102  | 
103  | static int doturns(nbflag, against, lastturn, GameName)
104  | int nbflag;
105  | char *against;
106  | char *lastturn;
107  | char *GameName;
108  | /* Should probably extract the const char *'s to somewhere more useful.
109  |  * flag variable may be overkill, however, better than executing a strstr several
110  |  * times per line. Def of flag values:
111  |  *              0 - ok to print to output file.
112  |  *              1 - Do not print to output file.
113  |  *              2 - Found the exclusion zone start, used to determine looking for exclusion
114  |  *                      end.
115  |  */
116  | {
117  | 	FILE *out = rfp;
118  | 	int flag = 0;
119  | 	static char Input[150];
120  | 
121  | 	while (fgets(Input, sizeof(Input), fp) != NULL) {
122  | 		if (flag == 0) {
123  | 			if (strstr(Input, "Subject:") != NULL) {
124  | 				if ((!nbflag) && (strstr(Input, "Results") == NULL)
125  | 					&& (strstr(Input, "Game Starting") == NULL)
126  | 					&& (strstr(Input, "starting") == NULL)
127  | 					&& (strstr(Input, "results") == NULL)) {
128  | 					flag = 1;
129  | 				} else if (strstr(Input, against) != NULL) {
130  | 					flag = 2;
131  | 				}
132  | 			}
133  | 		}
134  | 		if ((flag == 2) && strstr(Input, lastturn) != NULL) {
135  | 			flag = 0;
136  | 		}
137  | 		if ((flag == 1) && (strstr(Input, "declared a draw between") != NULL)) {
138  | 			fprintf(out, "Subject: Diplomacy notice: %s\n\n", GameName);
139  | 			flag = 0;
140  | 		}
141  | 		if (flag == 0) {
142  | 			fprintf(out, "%s", Input);
143  | 		}
144  | 		if ((flag < 2) && (strstr(Input, "Date:") != NULL) &&
145  | 		    (strstr(Input, "\(") != NULL)) {
146  | 			flag = 0;
147  | 		}
148  | 	}
149  | 
150  | 	return 1;
151  | }
152  | 
153  | /***************************************************************************/
154  | 
155  | static void hist_date(long *date, long *pos, int print)
156  | {
157  | 
158  | 	static char line[150];
159  | 	char *s;
160  | 
161  | 	for (;;) {
162  | 
163  | 		if (print) {
164  | 			fputs(line, rfp);
165  | 			if (!--lcnt) {
166  | 				return;
167  | 			}
168  | 		}
169  | 		*pos = ftell(fp);
170  | 		if (!fgets(line, sizeof(line), fp)) {
171  | 			if (print)
172  | 				lcnt = -1;
173  | 			*date = sbuf.st_mtime;
174  | 			return;
175  | 		}
176  | 		if (!strncmp(line, "Date: ", 6) && (s = strchr(line, '('))) {
177  | 			*date = atol(s + 1);
178  | 			return;
179  | 		}
180  | 	}
181  | }
182  | 
183  | /***************************************************************************/
184  | 
185  | int history(char *line, int power_type)
186  | {
187  | 
188  | 	/*
189  | 	 * power_type is the type of the plyer requesting history
190  |          * Set to OBSERVER if power type is unknown
191  |          */
192  | 
193  | 	/*
194  | 	 *  The input line consists of [name] [from date] [to date] [lines n].
195  | 	 *            or: [name] exclstart turn [exclend turn] [broad].
196  | 	 *
197  | 	 *            'broad' option is default for exstart parameter to
198  | 	 *                        eliminate potentially long histories from being
199  | 	 *                        returned.  Users must specifically ask for notices to
200  | 	 *                        to be included if they so desire.
201  | 	 */
202  | 
203  | 	/* Rp - added int flagb to flag incl/excl Broadcasts.  flagt = time/turns */
204  | 	int i, not_eof, flagb, flagt;
205  | 	char *s, name[sizeof(dipent.name)], file[sizeof(name) + 31];
206  | 	char exstart[10], exend[10];	/* 10 characters should be plenty */
207  | 	long s_date, s_pos, e_date, e_pos, n_date, n_pos, from, pos,
208  | 	 to;
209  | 
210  | 	/* Rp - added exclstart#, exclend#, nb# */
211  | 	static char *keys[] =
212  | 	{"", "from#", "to#", "until#", "lines#", "for#",
213  | 	 "exclstart#", "exclend#", "broad"};
214  | 	static char value[] =
215  | 	{'x', 'f', 't', 't', 'l', 'l', 's', 'e', 'n'};
216  | 
217  | 	*name = '\0';
218  | 	/* Rp - init char *s */
219  | 	*exstart = '\0';
220  | 	*exend = '\0';
221  | 
222  | 	s = line;
223  | 
224  | 	time(&to);
225  | 	from = to - 168 * 60 * 60;
226  | 	lcnt = 1000;
227  | 	flagb = 0;		/* Rp - define flagb = 1 include broadcast, 0 excluded */
228  | 	flagt = 1;		/* Rp - define flagt = 1 do time-wise, define flagt = 0 as exclude */
229  | 
230  | 
231  | 	while (isspace(*s))
232  | 		s++;
233  | 
234  | 	while (*s) {
235  | 		s = lookfor(s, keys, nentry(keys), &i);
236  | 
237  | 		switch (value[i]) {
238  | 		case 'x':
239  | 			if (*name) {
240  | 				if ((!msg_header_done) && (!signedon))
241  | 					msg_header(rfp);
242  | 				fprintf(rfp, "history %s", line);
243  | 				fprintf(rfp, "Unrecognized keyword: %s", s);
244  | 				return 1;
245  | 			}
246  | 			while (isspace(*s))
247  | 				s++;
248  | 			for (i = 0; *s && !isspace(*s) && i < sizeof(name) - 1; i++) {
249  | 				/* TODO this should allow case insensitive history commands */
250  | 				/* double check that the tolower has that effect */
251  | 				name[i] = tolower(*s);
252  | 				s++;
253  | 			}
254  | 			name[i] = '\0';
255  | 			break;
256  | 
257  | 		case 'f':
258  | 			if (mail_date(&s, &from, 1, rfp)) {
259  | 				if ((!msg_header_done) && (!signedon))
260  | 					msg_header(rfp);
261  | 				fprintf(rfp, "history %s", line);
262  | 				fprintf(rfp, "Unparsable from date: %s", s);
263  | 				return 1;
264  | 			}
265  | 			break;
266  | 
267  | 		case 't':
268  | 			if (mail_date(&s, &to, 1, rfp)) {
269  | 				if ((!msg_header_done) && (!signedon))
270  | 					msg_header(rfp);
271  | 				fprintf(rfp, "history %s", line);
272  | 				fprintf(rfp, "Unparsable to date: %s", s);
273  | 				return 1;
274  | 			}
275  | 			break;
276  | 
277  | 		case 'l':
278  | 			while (isspace(*s))
279  | 				s++;
280  | 			for (lcnt = 0; isdigit(*s); lcnt = lcnt * 10 + *s++ - '0');
281  | 			if (lcnt == 0) {
282  | 				if ((!msg_header_done) && (!signedon))
283  | 					msg_header(rfp);
284  | 				fprintf(rfp, "history %s", line);
285  | 				fprintf(rfp, "Unparsable line count: %s", s);
286  | 				return 1;
287  | 			}
288  | 			break;
289  | 			/* Rp - init vars if found on History line */
290  | 		case 's':{
291  | 				flagt = 0;
292  | 				if ((!msg_header_done) && (!signedon))
293  | 					msg_header(rfp);
294  | 				while (isspace(*s))
295  | 					s++;
296  | 				/* if (copycheck( s, exstart)) {} */
297  | 				for (i = 0; *s && !isspace(*s) && i < sizeof(exstart) - 1; i++) {
298  | 					exstart[i] = *s++;
299  | 				}
300  | 				exstart[i] = '\0';
301  | 			}
302  | 			break;
303  | 
304  | 		case 'e':{
305  | 				if ((!msg_header_done) && (!signedon))
306  | 					msg_header(rfp);
307  | 				while (isspace(*s))
308  | 					s++;
309  | 				/* if (copycheck( s, exend)) {} */
310  | 				for (i = 0; *s && !isspace(*s) && i < sizeof(exend) - 1; i++) {
311  | 					exend[i] = *s++;
312  | 				}
313  | 				exend[i] = '\0';
314  | 			}
315  | 			break;
316  | 
317  | 		case 'n':
318  | 			if ((!msg_header_done) && (!signedon))
319  | 				msg_header(rfp);
320  | 			flagt = 0;
321  | 			flagb = 1;
322  | 			break;
323  | 		}
324  | 
325  | 		while (isspace(*s))
326  | 			s++;
327  | 
328  | 	}
329  | 
330  | 	/*
331  | 	 *  Now read that archive file.
332  | 	 */
333  | 
334  | 	if (!*name) {
335  | 		if (!signedon) {
336  | 			if (!msg_header_done)
337  | 				msg_header(rfp);
338  | 			fprintf(rfp, "history %s", line);
339  | 			fprintf(rfp, "Game name not specified.\n");
340  | 			return 1;
341  | 		}
342  | 		strcpy(name, dipent.name);
343  | 	} else {
344  | 		if (!signedon) {
345  | 			if ((mfp = fopen(MASTER_FILE, "r")) == NULL) {
346  | 				if (!msg_header_done)
347  | 					msg_header(rfp);
348  | 				fprintf(rfp, "Error opening master file %s.\n", MASTER_FILE);
349  | 				return 1;
350  | 			}
351  | 			while ((not_eof = getdipent(mfp))) {
352  | 				if (!strcmp(dipent.name, name)) {
353  | 					break;
354  | 				}
355  | 			}
356  | 			fclose(mfp);
357  | 		}
358  | 	}
359  | 
360  | 	sprintf(file, "%s%s/archive", GAME_DIR, name);
361  | 	if (stat(file, &sbuf)) {
362  | 		if ((!msg_header_done) && (!signedon))
363  | 			msg_header(rfp);
364  | 		fprintf(rfp, "history %s", line);
365  | 		fprintf(rfp, "No history available for game '%s'.\n", name);
366  | 		return 1;
367  | 	}
368  | 
369  |         /* Check if a blind game - if so, only the master can do this */
370  |         if (dipent.flags & F_BLIND ) {
371  |                 if (dipent.phase[6] != 'X' ) {
372  |                     /* Non-finished blind games have restrictions on history */
373  |                     switch (power_type)
374  |                     {
375  | 			case 0: /* not signed on */
376  |                         case OBSERVER:
377  |                                 fprintf(rfp,"Observers are not allowed history command in blind games.\n");
378  |                                 return 0;
379  |                         case MASTER:
380  |                                 break; /* No problem for the master! */
381  | 
382  |                         default:
383  |                                 fprintf(rfp, "Players are not allowed history command in blind games.\nContact the master for help in this.\n");
384  |                                 return 0;
385  |                     }
386  |                }
387  |         }
388  | 
389  | 	if (!msg_header_done)
390  | 		msg_header(rfp);
391  | 	fprintf(rfp, "History information for game '%s' ", name);
392  | 	if (flagt) {
393  | 		fprintf(rfp, "from %s\n", ptime(&from));
394  | 		fprintf(rfp, "through %s:\n", ptime(&to));
395  | 	} else {
396  | 		fprintf(rfp, "excluding turn '%s'\n", exstart);
397  | 		fprintf(rfp, "to turn '%s'.", ((*exend != '\0') ? exend : "end of game"));
398  | 	}
399  | 	fprintf(rfp, "\n");
400  | 
401  | 	/*
402  | 	 *  Open the archive file and determine its start time.
403  | 	 */
404  | 
405  | 	if (!(fp = fopen(file, "r"))) {
406  | 		fprintf(rfp, "Unable to open history file for '%s'.\n", name);
407  | 		return 1;
408  | 	}
409  | 	/* Rp - if (flagt) do search by date */
410  | 	if (flagt) {
411  | 		hist_date(&s_date, &s_pos, 0);
412  | 
413  | 		e_date = sbuf.st_mtime;
414  | 		e_pos = sbuf.st_size;
415  | 
416  | 		/*
417  | 		 *  Now do a binary search to find the intended start time.
418  | 		 */
419  | 
420  | 		while (s_date < from && from < e_date && e_pos - s_pos > 2048) {
421  | 			if (Dflg)
422  | 				printf("s = %24.24s / %ld.\n", ctime(&s_date), s_pos);
423  | 			if (Dflg)
424  | 				printf("e = %24.24s / %ld.\n", ctime(&e_date), e_pos);
425  | 			pos = s_pos + (long) (((float) (e_pos - s_pos) *
426  | 					       (float) (from - s_date)) /
427  | 					      (float) (e_date - s_date));
428  | 			fseek(fp, pos, 0);
429  | 			hist_date(&n_date, &n_pos, 0);
430  | 			if (Dflg)
431  | 				printf("n = %24.24s / %ld from %ld.\n", ctime(&n_date), n_pos, pos);
432  | 			if (n_date >= from) {
433  | 				e_date = n_date;
434  | 				e_pos = pos;
435  | 			} else {
436  | 				s_date = n_date;
437  | 				s_pos = n_pos;
438  | 			}
439  | 		}
440  | 
441  | 		/*
442  | 		 *  Okay, close enuf.
443  | 		 */
444  | 
445  | 		if (from > e_date) {
446  | 			fprintf(rfp, "No history for specified period.\n");
447  | 		} else {
448  | 
449  | 			fseek(fp, s_pos, 0);
450  | 			do {
451  | 				hist_date(&s_date, &s_pos, 0);
452  | 				if (Dflg)
453  | 					printf("Skipped to %24.24s at %ld.\n", ctime(&s_date), s_pos);
454  | 			} while (s_date < from);
455  | 
456  | 			do {
457  | 				hist_date(&s_date, &s_pos, 1);
458  | 				if (Dflg)
459  | 					printf("Printed to %24.24s at %ld.\n", ctime(&s_date), s_pos);
460  | 			} while (s_date < to && lcnt > 0);
461  | 
462  | 			fprintf(rfp, "\n---- History terminated due to %s.\n",
463  | 				lcnt == 0 ? "line limit exceeded" :
464  | 				lcnt == -1 ? "end of information" :
465  | 				"time range exhausted");
466  | 		}
467  | 	}
468  | 	/* end if (flagt) */
469  | 	else {
470  | 		if (doturns(flagb, exstart, exend, name)) {
471  | 			fprintf(rfp, "\n---- History terminated due to end of infomation.\n");
472  | 		}
473  | 	}
474  | 
475  | 	fclose(fp);
476  | 	return 0;
477  | }