/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	Save (or print) articles
 */

#include <signal.h>
#include <errno.h>
#include "config.h"
#include "keymap.h"
#include "nn_term.h"
#include "news.h"

/* save.c */

static char *ckdir_path __APROTO((char *name));
static mkdirs_in_path __APROTO((char *name, char *start));

export char *default_save_file = "+$F";
export char *folder_save_file = NULL;
export int suggest_save_file = 1;
export char *unshar_header_file = "Unshar.Headers";
export int  use_mail_folders = 0;
export int  use_path_in_from = 0;
export int  use_mmdf_folders = 0;
export int  save_report = 1;
export int  quick_save = 0;
export int  conf_append = 0;
export int  conf_create = 1;
export int  folder_format_check = 1;

export char *saved_header_escape = "~";

export char *print_header_lines = "FDGS";
export char *save_header_lines = "FDNS";
static char *short_header_lines;

export char *save_counter_format = "%d";	/* format of save counter */
export int  save_counter_offset = 0;

export char printer[FILENAME] = DEFAULT_PRINTER;
export int edit_print_command = 1;

export char patch_command[FILENAME] = "patch -p0";
export int edit_patch_command = 1;

export char unshar_command[FILENAME] = SHELL;
export int edit_unshar_command = 0;

extern int file_completion();

import char *temp_file;
import int  shell_restrictions;
import char delayed_msg[];

import int current_folder_type;

import int rot13_active;

extern char *ctime();

static int save_mode;
static char *unshar_cmd;

#define	HEADER_HANDLING	0x0f	/* what should we do with the header */

#define NO_HEADER	0	/* save without a header */
#define SHORT_HEADER	1	/* save with partial header */
#define	FULL_HEADER	2	/* save with full header */
#define	SHORT_HEADER_DG	3	/* save with partial header (digest) */

export int print_header_type = SHORT_HEADER;

#define	SEPARATE_FILES	0x0100	/* save as separate files */
#define UNIQUE_FILES	0x0200	/* save in unique files */
#define	FILE_IS_NEW	0x0400	/* this is a new file */
#define APPEND_ARTNUM	0x0800	/* append article number to file name */
#define	IS_PIPE		0x1000	/* output is on pipe */
#define	DO_UNSHAR	0x2000	/* unshar article (or patch) */
#define	DO_PATCH	0x4000	/* patch article */
#define	DO_DECODE	0x8000	/* uudecode article */

/* open modes for open_news_article for the various HEADER_HANDLINGs */

static int open_mode[] = {
    SKIP_HEADER,
    FILL_NEWS_HEADER | SKIP_HEADER | GET_ALL_FIELDS,
    0,
    FILL_NEWS_HEADER | FILL_DIGEST_HEADER | SKIP_HEADER | GET_ALL_FIELDS
};

static FILE *save_file;			/* output stream for saved files */
static char *save_name;			/* save file name */

static int uniq_counter;		/* separate files counter */
static char uniq_format[FILENAME]; 	/* sprintf format for '*' expansion */

static char last_dir[FILENAME] = "";

/*
 * return pointer to first path name component, that does not exist
 */

static char *ckdir_path(name)
char *name;
{
    char *slash;
    char *component;

    component = name;

    while ((slash = strchr(component, '/'))) {
	if (slash == component) {
	    /*  ...//...  */
	    component++;
	    continue;
	}
	*slash = NUL;
	if (file_exist(name, "drx")) {
	    *slash++ = '/';
	    component = slash;
	    continue;
	}
	if (file_exist(name, (char *)NULL)) {
	    *slash = '/';
	    return NULL;
	}
	*slash = '/';
	break;
    }
    return component;
}

/*
 * create directories in path name, starting from start
 */

static int
mkdirs_in_path(name, start)
char *name, *start;
{
    char *slash;
    char *component;

    component = start;

    while ((slash = strchr(component, '/'))) {
	if (slash == component) {
	    /*  ...//...  */
	    component++;
	    continue;
	}
	*slash = NUL;
	if (mkdir(name, 0755)) {
	    msg("Cannot make %s/", name);
	    *slash = '/';
	    return 0;
	}
	*slash++ = '/';
	component = slash;
    }
    return 1;
}

void
set_folder_type(name)
char *name;
{		
    FILE *f;

    current_folder_type = -1;
    if (!folder_format_check) return;

    if ((f = open_file(name, OPEN_READ)) != NULL) {
	get_folder_type(f);
	fclose(f);
    }
}

char *init_save(command, mode_textp)
char command;
char **mode_textp;
{
    char *mode_text;
    static char last_input[FILENAME] = "";
    static char name_buf[512];	/* buffer for file name & command expansion */
    char *start, *last, *np;

    uniq_counter = 0;
    short_header_lines = save_header_lines;

    switch (command) {

     case K_SAVE_FULL_HEADER:
	save_mode = FULL_HEADER;
	mode_text = "Save";
	goto cont_save;

     case K_SAVE_SHORT_HEADER:
	save_mode = SHORT_HEADER;
	mode_text = "Output";
	goto cont_save;

     case K_SAVE_NO_HEADER:
	save_mode = NO_HEADER;
	mode_text = "Write";

     cont_save:
	if (quick_save && (current_group->group_flag & G_FOLDER) == 0) {
	    if (current_group->save_file)
		save_name = current_group->save_file;
	    else
		save_name = default_save_file;
	    strcpy(last_input, save_name);
	    save_name = last_input;
	} else {
	    prompt("\1%s on\1 (+~|) ", mode_text);

	    save_name = current_group->save_file;
	    if (save_name == NULL && suggest_save_file)
		save_name = (current_group->group_flag & G_FOLDER) ?
		    folder_save_file : default_save_file;
	    if (save_name != NULL) {
		if (!expand_file_name(name_buf, save_name, 2)) return NULL;
		save_name = name_buf;
	    }

	    save_name = get_s(last_input, save_name, NONE, file_completion);
	    if (save_name == NULL || *save_name == NUL) return NULL;

	    if (save_name[1] == NUL && save_name[0] == '+')
		save_name = (current_group->group_flag & G_FOLDER) ?
		    folder_save_file : default_save_file;
	    else
		if (current_group->save_file == NULL ||
		    strcmp(save_name, current_group->save_file))
		    strcpy(last_input, save_name);
	    if (save_name == NULL || *save_name == NUL) return NULL;
	}

	if (*save_name == '|') {
	    if (shell_restrictions) {
		msg("Restricted operation - pipes not allowed");
		return NULL;
	    }
	    mode_text = "Pipe";
	    save_name++;
	    save_mode |= IS_PIPE;
	    if (*save_name == '|') {
		save_mode |= SEPARATE_FILES;
		save_name++;
	    }
	}

	if (!expand_file_name(name_buf, save_name, (save_mode & IS_PIPE) ? 11 : 3))
	    return NULL;
	save_name = name_buf;

	if ((save_mode & IS_PIPE) == 0) {
	    np = strrchr(save_name, '*');
	    if (np != NULL) {
		*np = NUL;
		sprintf(uniq_format, "%s%s%s",
			save_name, save_counter_format, np + 1);
		*np = '*';
		save_mode |= SEPARATE_FILES | UNIQUE_FILES;
	    } else {
		np = strrchr(save_name, '$');
		if (np != NULL && np[1] == 'N') {
		    if (current_group->group_flag & G_FOLDER) {
			msg("$N is not defined in a folder");
			return NULL;
		    }
		    *np = NUL;
		    sprintf(uniq_format, "%s%%ld%s", save_name, np + 2);
		    *np = '$';
		    save_mode |= SEPARATE_FILES | APPEND_ARTNUM;
		}
	    }
	}
	break;

     case K_UUDECODE:
	save_mode = NO_HEADER | DO_UNSHAR | DO_DECODE;
	mode_text = "Decode";
	goto unshar1;

     case K_PATCH:
	save_mode = NO_HEADER | SEPARATE_FILES | DO_UNSHAR | DO_PATCH;
	mode_text = "Patch";
	if (!shell_restrictions && edit_patch_command) {
	    prompt("\1Patch command:\1 ");
	    save_name = get_s(NONE, patch_command, NONE, NULL_FCT);
	    if (save_name == NULL || *save_name == NUL) return NULL;
	    strcpy(patch_command, save_name);
	}
	unshar_cmd = patch_command;
	goto unshar1;

     case K_UNSHAR:
	save_mode = NO_HEADER | SEPARATE_FILES | DO_UNSHAR;
	mode_text = "Unshar";
	if (!shell_restrictions && edit_unshar_command) {
	    prompt("\1Unshar command:\1 ");
	    save_name = get_s(NONE, unshar_command, NONE, NULL_FCT);
	    if (save_name == NULL || *save_name == NUL) return NULL;
	    strcpy(unshar_command, save_name);
	}
	unshar_cmd = unshar_command;

     unshar1:
	prompt("\1%s Directory:\1 ", mode_text);
	save_name = current_group->save_file;
	if (save_name != NULL) {
	    if (!expand_file_name(name_buf, save_name, 10)) return NULL;
	    save_name = name_buf;
	}

	save_name = get_s(last_dir, save_name, NONE, file_completion);
	if (save_name == NULL || *save_name == NUL) return NULL;
	strcpy(last_dir, save_name);
	if (!expand_file_name(name_buf, save_name, 1))
	    return NULL;
	save_name = name_buf;
	break;

     case K_PRINT:
	if (print_header_type < NO_HEADER || print_header_type > FULL_HEADER) {
	    msg("Invalid 'print-header-type' value %d", print_header_type);
	    print_header_type = NO_HEADER;
	}

	short_header_lines = print_header_lines;
	save_mode = print_header_type | IS_PIPE;
	
	if (!shell_restrictions && edit_print_command) {
	    prompt("\1Print command:\1 ");
	    save_name = get_s(NONE, printer, NONE, NULL_FCT);
	    if (save_name == NULL || *save_name == NUL) return NULL;
	    strcpy(printer, save_name);
	}
	if (!expand_file_name(name_buf, printer, 1))
	    return NULL;
	save_name = name_buf;
	mode_text = "Print";
	break;

     default:
	msg("Illegal save command: %d", command);
	return NULL;
    }

    if (save_name == NULL) return NULL;

    if (!(save_mode & IS_PIPE)) {
	if (file_exist(save_name, (save_mode & DO_UNSHAR) ? "wd" : "wf")) {
	    if (save_mode & DO_UNSHAR) {
		int len = strlen(save_name);
		if (save_name[len - 1] != '/')
		    strcpy(save_name + len, "/");
	    } else
		if (conf_append) {
		    tprintf("\rAppend to: %s ? ", save_name);
		    clrline();
		    if (!yes(0)) return NULL;
		}
	} else {
	    if (errno != ENOENT) {
		msg("Cannot access %s", save_name);
		return NULL;
	    }

	    if (save_mode & DO_UNSHAR) {
		int len = strlen(save_name);
		if (save_name[len - 1] != '/')
		    strcpy(save_name + len, "/");
	    }

	    start = ckdir_path(save_name);
	    if (start == NULL) {
		msg("No permission");
		return NULL;
	    }

	    last = strrchr(start, '/');
	    /* last != NULL => non-existing directories */

	    if (conf_create && (!(save_mode & SEPARATE_FILES) || last)) {
		tprintf("\rCreate ");
		for (np = save_name; *np; np++) {
		    if (np == start) tputc('\"');
		    tputc(*np);
		    if ((save_mode & SEPARATE_FILES) && np == last) break;
		}
		tprintf("\" ?");
		clrline();
		if (yes(last != NULL) <= 0) return NULL;
	    }

	    if (last && !mkdirs_in_path(save_name, start))
		return NULL;
	}
    }

    if (mode_textp) *mode_textp = mode_text;

    save_mode |= FILE_IS_NEW;	/* so save() will open it */

    if (save_mode & DO_DECODE) {
	uud_start(save_name);
	save_mode &= ~DO_UNSHAR;
    }

    return save_name;
}


int
save(ah)
article_header *ah;
{
    register FILE *art;
    register c, lcount = 0, mode;
    news_header_buffer hdrbuf;
    news_header_buffer dghdrbuf;
    int was_raw = 0, set_visual = 0;
    char copybuf[FILENAME * 4], uniqbuf[FILENAME];
    flag_type nn_st_flag = A_ST_FILED;
    int with_header;

    if (ah->a_group) init_group(ah->a_group);

    mode = save_mode & HEADER_HANDLING;
    if (mode == SHORT_HEADER && ah->flag & A_DIGEST)
	mode = SHORT_HEADER_DG;

    c = open_mode[mode];
    if (use_mail_folders && use_path_in_from && (c & FILL_NEWS_HEADER) == 0)
	c |= FILL_NEWS_HEADER | GET_ALL_FIELDS;

    art = open_news_article(ah, c, hdrbuf, dghdrbuf);
    if (art == NULL) {
	msg("Cannot read %s", group_path_name);
	return 0;
    }

    if (save_mode & DO_DECODE) {
	save_file = NULL;
	c = uudecode(ah, art);
	fclose(art);
	return (c < 0) ? 0 : 1;
    }

    if (save_mode & UNIQUE_FILES) {
	uniqbuf[0] = NUL;
	do {
	    strcpy(copybuf, uniqbuf);
	    uniq_counter++;
	    sprintf(uniqbuf, uniq_format, uniq_counter + save_counter_offset);
	    if (strcmp(uniqbuf, copybuf) == 0) {
		msg("save-counter \"%s\" does not generate unique file names",
		    save_counter_format);
		goto fatal;
	    }
	} while (file_exist(uniqbuf, (char *)NULL));
	save_name = uniqbuf;
	save_mode |= FILE_IS_NEW;
    } else
	if (save_mode & APPEND_ARTNUM) {
	    sprintf(uniqbuf, uniq_format, (long)(ah->a_number));
	    save_name = uniqbuf;
	}

    if (save_mode & FILE_IS_NEW) {
	if (save_mode & (IS_PIPE | DO_UNSHAR)) {
	    set_visual = 1;
	    was_raw = visual_off();
	}

	if (save_mode & IS_PIPE) {
	    if ((save_file = popen(save_name, "w")) == NULL) {
		msg("Cannot pipe to %s", save_name);
		goto fatal;
	    }
	} else
	if (save_mode & DO_UNSHAR) {
	    if ((save_mode & DO_PATCH) == 0) {
		if (!unshar_position(art))
		    goto fatal;
		if (unshar_header_file)
		    store_header(ah, art, save_name, unshar_header_file);
	    }

	    new_temp_file();
	    sprintf(copybuf,
		    "cd %s && { %s 2>&1 ; } | tee %s ; cat %s >> %s.Result ; rm %s",
		    save_name != NULL ? save_name : ".", unshar_cmd,
		    temp_file, temp_file,
		    (save_mode & DO_PATCH) ? "Patch" : "Unshar",
		    temp_file);

	    save_file = popen(copybuf, "w");
	    if (save_file == NULL) {
		msg("Cannot exec: '%s'", copybuf);
		goto fatal;
	    }
	    tprintf("\r\n%s %s\r\n",
		   save_mode & DO_PATCH ? "PATCHING FROM" : "UNPACKING",
		   ah->subject ? ah->subject : "ARTICLE"); fl;
	} else {
	    if ((save_file = open_file(save_name, OPEN_APPEND)) == NULL) {
		msg("Cannot write %s", save_name);
		fclose(art);
		return 0;
	    }
	    current_folder_type = -1;
	    if (ftell(save_file) != (off_t)0) {
		if (mode != NO_HEADER) set_folder_type(save_name);
		save_mode &= ~FILE_IS_NEW;
	    }
	}
    }

    clrline();
    s_pipe = 0;

    with_header = mode != NO_HEADER &&
	(save_mode & (IS_PIPE | DO_UNSHAR)) == 0;

    if (with_header)
	mailbox_format(save_file, 1);

    if (mode == FULL_HEADER) {
	off_t cnt = ah->fpos - ah->hpos;
	while (--cnt >= 0) {
	    if ((c = getc(art)) == EOF) break;
	    putc(c, save_file);
	}
    } else
    if (mode == SHORT_HEADER || mode == SHORT_HEADER_DG) {
	char *name, *val;
	scan_header_fields(short_header_lines, ah);
	while (next_header_field(&name, &val, (fct_type *)NULL)) {
	    if (name == NULL) continue;
	    fprintf(save_file, "%s: %s\n", name, val);
	}
	fputc(NL, save_file);
    }

    fflush(save_file);
    if (s_pipe) goto broken_pipe;

    lcount = 0;
    while (ftell(art) < ah->lpos && fgets(copybuf, 512, art)) {
	lcount++;
	if (rot13_active) rot13_line(copybuf);
	if (saved_header_escape && with_header && is_header_line(copybuf))
	    fputs(saved_header_escape, save_file);
	fputs(copybuf, save_file);
	if (s_pipe) goto broken_pipe;
    }

    if (with_header)
	lcount += mailbox_format(save_file, 0);

broken_pipe:
    fclose(art);

    if (save_mode & DO_UNSHAR) {
	if ((c = pclose(save_file)) != 0) {
	    sprintf(delayed_msg, "Save command failed; exit = %d", c);
	    nn_st_flag = 0;
	}
	save_file = NULL;
    } else {
	if (s_pipe)
	    msg("Command did not read complete article!");
	else
    	if (save_report)
	    msg((save_mode & IS_PIPE)     ? "%s: %d lines piped" :
		(save_mode & FILE_IS_NEW) ? "%s created: %d lines written" :
		"%s: %d lines appended", save_name, lcount);

	if (s_pipe || (save_mode & SEPARATE_FILES)) {
	    if (end_save() == 0) goto err;
	} else
	    save_mode &= ~FILE_IS_NEW;
    }

#ifdef MAIL_READING
    if (mail_auto_delete && (save_mode & IS_PIPE) == 0)
	if (current_group->group_flag & G_MAILBOX)
	    if (ah->attr != A_CANCEL)
		fcancel(ah);
#endif
    if (set_visual) visual_on();
    if (was_raw) nn_raw();

    ah->flag |= nn_st_flag;

    return !s_pipe || (save_mode & SEPARATE_FILES);

 fatal:
    fclose(art);
 err:
    if (set_visual) visual_on();
    if (was_raw) nn_raw();
    return 0;
}

int
end_save()
{
    int c;
    FILE *sf;
    sf = save_file;
    save_file = NULL;

    if (sf) {
	if (save_mode & (IS_PIPE | DO_UNSHAR)) {
	    if ((c = pclose(sf)) != 0) {
		if (c >= 256) c >>= 8;	/* HACK */
		sprintf(delayed_msg, "Save command failed; exit = %d", c);
		return 0;
	    }
	} else
	    if (fclose(sf) == EOF) {
		sprintf(delayed_msg, "Save failed (disk full?)");
		return 0;
	    }
    }

    if (save_mode & DO_DECODE) {
	uud_end();
    }
    if (save_mode & DO_UNSHAR)
	sprintf(delayed_msg, "Output is saved in %s/%s.Result",
		save_name != NULL ? save_name : ".",
		(save_mode & DO_PATCH) ? "Patch" : "Unshar");
    return 1;
}

void
store_header(ah, f, dir, file)
article_header *ah;
FILE *f;
char *dir, *file;
{
    register int c;
    off_t endpos;
    FILE *h;

    if (dir != (char *)NULL && file[0] != '/')
	file = relative(dir, file);
    if ((h = open_file(file, OPEN_APPEND)) == NULL) {
	msg("Cannot open %s", file);
	return;
    }
    fseek(h, (off_t)0, 2);
    if (ftell(h) > 0)
	set_folder_type(file);
    else
	current_folder_type = -1;
    if (!use_mmdf_folders && ftell(h) > 0) putc(NL, h);  /* just in case */
    mailbox_format(h, 1);
    endpos = ftell(f) - ah->hpos;
    fseek(f, ah->hpos, 0);
    while (--endpos >= 0 && (c = getc(f)) != EOF)
	putc(c, h);

    mailbox_format(h, 0);
    fclose(h);
}

int
mailbox_format(f, top)
FILE *f;
int top;
{
    time_t now;
    int do_mmdf, do_mail;

    do_mmdf = do_mail = 0;
    switch (current_folder_type) {
     case 0:
	break;
     case 1:
	do_mail = 1;
	break;
     case 2:
	do_mmdf = 1;
	break;
     default:
	do_mmdf = use_mmdf_folders;
	do_mail = use_mail_folders;
	break;
    }

    if (do_mmdf) {
	fprintf(f, "\001\001\001\001\n");
	return 0;
    }

    if (top == 0) {
	fputc(NL, f);
	return 1;
    }

    if (top > 0 && do_mail) {
	now = cur_time();
	fprintf(f, "From %s %s",
		(use_path_in_from && news.ng_path) ? news.ng_path :
		current_group->group_name, ctime(&now));
	return 1;
    }

    return 0;
}

char *run_mkdir(dir, name_buf)
char *dir, *name_buf;
{
    if (dir == NULL) {
	prompt("\1Directory: \1");
	dir = get_s(last_dir, NONE, NONE, file_completion);
	if (dir == NULL || *dir == NUL) return NULL;
	strcpy(last_dir, dir);
    }

    if (*dir == '+' || *dir == '~') {
	if (!expand_file_name(name_buf, dir, 1))
	    return NULL;
	dir = name_buf;
    }

    if (file_exist(dir, (char *)NULL)) {
	msg("Directory %s already exists", dir);
	return NULL;
    }

    if (mkdir(dir, 0755)) {
	msg("Cannot make %s", dir);
	return NULL;
    }

    return dir;
}

int
change_dir(dir, in_init)
char *dir;
int in_init;
{
    char dir_buf[FILENAME];

    if (dir == NULL) {
	prompt("\1Directory: \1");
	dir = get_s(last_dir, NONE, NONE, file_completion);
    }

    if (dir == NULL || *dir == NUL) return 0;

    strcpy(last_dir, dir);

    if (*dir == '+' || *dir == '~') {
	if (!expand_file_name(dir_buf, dir, 1)) return in_init;
	dir = dir_buf;
    }

    if (chdir(dir) == 0) {
	if (!in_init) msg("Directory: %s", dir);
	return 0;
    }

    if (in_init) return 1;

    if (errno == EACCES)
	msg("Cannot access directory %s", dir);
    else {
	/* should ask and create directory here */
	msg("Directory not found: %s", dir);
    }

    return 0;
}


