/*   See backup for volume directory information
 * 
 *   Args
 * 
 *        -b ttt  = name of both rewinding & non-rewinding tape device
 *        -d      = prints directory list
 *        -f ttt  = name of rewinding tape device
 *        -i x    = where the archive information files are
 *        -l fff  = name of log file
 *        -m num  = level of logging (0=none...3=verbose)
 *        -n ttt  = name of non-rewinding tape device
 *        +/-o    = overwrite mode on/off
 *        -p xx   = name of preferences file (default = .taper in $HOME/.taper)
 *        -r path = path to restore files to
 *        -s num  = number of leading paths to strip (default 0)
 *        -v      = prints version
 *        -w num  = limit to volume num
 *        -x num  = maximum # of bytes to transfer to/from device in one hit
 *        +/-y    = turns on/off alpha sort of directory list (default on)
 *        +/-z    = turns on/off confirmation of directory selection (default on)
 *        -?      = prints command line help
 *        
 *  
*/ 
#include "common.h"
#include "restore.h"

/* Window stuff */
WINDOW    *win_main, *files, *selection;
int       screen_ylen, screen_xlen;		 /* physical dims of screen */
int       screen_ylen_files, screen_ylen_selection;/* length of screens */
int       left_width, right_width;


void usage()
{
    fprintf(stderr, "\n\n");
    fprintf(stderr, "Restore command line options\n");
    fprintf(stderr, "----------------------------\n");
    common_usage();
    fprintf(stderr, "-d      (--directory-list)        prints directory list only\n");
    fprintf(stderr, "-o num  (--overwrite num)         overwrite level (0=none..2=unconditional)\n");
    fprintf(stderr, "-r path (--relative-path)         restores to path instead of current directory\n");
    fprintf(stderr, "\n\n");
    do_exit(ERROR_USAGE);
}

						 
void parse_args(int argc, char *argv[])
{
    int c=0;
    char z;
    
    while (++c<argc) {
	z = common_parse_args(argc,argv, &c);	 /* look for common args */
	if (z==-1) usage();
	if (z==0) {
	    if (argv[c][1] == '-') {
		if (!strcasecmp(&argv[c][2], "overwrite"))
		  argv[c][1] = 'o';
		if (!strcasecmp(&argv[c][2], "relative-path"))
		  argv[c][1] = 'r';
		if (!strcasecmp(&argv[c][2], "directory-list"))
		  argv[c][1] = 'd';
		if (!strcasecmp(&argv[c][2], "limit-volume"))
		  argv[c][1] = 'w';
	    }
	    switch(argv[c][1]) {
	     case 'w':
	     case 'W':
		c++;
		if (c==argc)			 /* make sure that they've supplied */
		  usage();			 /* another arg */
		limit_vols = atoi(argv[c]);
		break;
	     case 'R':
	     case 'r':
		c++;
		if (c==argc)			 /* make sure that they've supplied */
		  usage();		 /* another arg */
		strcpy(rel_path, argv[c]);
		break;
	     case 'o':
	     case 'O':
		c++;
		if (c==argc)
		  usage();
		ovrwrite=atoi(argv[c]);
		if ((ovrwrite < 0) || (ovrwrite > 2))
		  usage();
		break;
	     case 'D':
	     case 'd':
		pr_dir = 1;
		break;
	     default:
		usage();
		break;
	    }
	}
    }
}


int find_min_vol()
{
/* Looks through the currently selected files and works out
 * which is the first volume it needs to read
*/
    int vol;
    long cur_pos=0;
    struct file_entry *fe;
    
    fe=vol_files;
    vol=0;
    while (cur_pos++ <  no_in_archive) {
	if (fe->selected)
	  vol = (vol==0) ? fe->i.volume : min(vol, fe->i.volume);
	fe++;
    }
    return vol;
}


long rtotal_read;
int restore_action(struct file_info *fi, char *fn)
/* Acts on the restore loop finding filename fn
 * Action returns 1 if it advanced past file (ie. it read it in)
 * or 0 if it didn't read it in
 * 
 * Returns -1 if no more files to be restored after this
*/ 
{
    char   s[MAXNAMLEN], l[MAXNAMLEN*2], tmpf[MAXNAMLEN];
    struct file_entry *fe;
    long   c1, x, sz, tr;
    int    of, ret;
    struct utimbuf ut;
    struct stat statbuf;
    
    c1 = 0;
    fe = vol_files;
    while (c1++ < no_in_archive) {		 /* look for this file in volume dir */
	if (fi->pos_in_archive == fe->i.pos_in_archive)
	  break;
	fe++;
    }
	
    if (!fe->selected) 				 /* not selected */
      return 0;					 /* tell loop that we didn't touch it */

    strcpy(fn, stripped_cf);			 /* make filename as user */
    if (*stripped_cf)
      strcat(fn, "/");				 /* wants it */
    strcat(fn, fe->name);
    
    fe->selected = 0;				 /* mark as unselected now that it's read */
    ret = (find_min_vol() == fe->i.volume) ? 0 : -2;   /* any more to restore from this vol */

    if (*rel_path) {				 /* make pathname */
	strcpy(s, rel_path);			 /* taking into account */
	if (!((s[strlen(s)-1] == '/') ||	 /* the relative path supplied by */
	      (fn[0] == '/')))			 /* the user */
	  strcat(s, "/");
	    strcat(s, fn);
    }
    else 
      strcpy(s, fn);
    
    rtotal_read += fi->act_size + strlen(s)+1+sizeof(struct file_info);
    if (!make_dirs(s))				 /* make directories */
      return ret;				 /* failed in making directories */
    
    if (S_ISLNK(fi->mode)) {			 /* soft link */
	sprintf(l, "Reading in link info for %s", s);
	if (log_level == 3) write_log(l);
	if (tape_read((char *) &x, sizeof(x)) != sizeof(x)) {/* a link */
	    write_fatal_log(l);
	    do_exit(ERROR_READING);
	}
	if (log_level == 3) write_log(l);	 
	if (tape_read(tmpf, x) != x) {		 /* who do we point to */
	    write_fatal_log(l);
	    do_exit(ERROR_READING);
	}
	sprintf(l, "Creating symbolic link file %s", s);
	if (log_level == 3) write_log(l);
	if (symlink(tmpf, s) == -1) 
	  write_error_log(l);
	of = 0;
	goto setowners;
    }

    if (S_ISDIR(fi->mode))  {			 /* directory */
	if (s[strlen(s)-1] == '/')
	  s[strlen(s)-1] = 0;
	sprintf(l, "Creating directory %s", s);
	if (log_level == 3) write_log(l);
	if (mkdir(s, 493) == -1)
	  if (errno != EEXIST)			 /* directory exists is not an error */
	  write_error_log(l); 
	of = 0;
	goto setowners;
    }

    if (!S_ISREG(fi->mode)) {			 /* device */
	of = 0;
	sprintf(l, "Creating device %s", s);
	if (log_level == 3) write_log(l);
	if (mknod(s, fi->mode, fi->dev) == -1)
	  write_error_log(l);
	goto setowners;
    }
    
    if (fi->checksum == -1) {		 /* checksum -1 - therefore file not backed up */
	sprintf(l, "There was a problem when backing up file %s, therefore file not restored", s);
	write_warning_log(l);
	return ret;
    }

    sprintf(l, "Getting file info for file %s", s);
    if (log_level == 3) write_log(l);
    if ((of = lstat(s, &statbuf)) == -1)	 /* try and get info about file */
      if (errno != ENOENT) {			 /* not an error is couldn't */
	  write_error_log(l);
	  return ret;
      }
    if ((of == -1) || (ovrwrite == 2))		 /* file doesn't exist or unconditional overwrite */
	of = open(s, O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
    else
      switch (ovrwrite) {
       case 0:				 /* no overwrite */
	  if (of != -1) {			 /* file existed */
	      sprintf(l, "File %s exists. Not overwriting", s);
	      write_warning_log(l);
	      return ret;
	  }
	  of = open(s, O_RDWR|O_EXCL|O_CREAT, S_IREAD|S_IWRITE);
	  break;
       case 1:				 /* only overwrite if backup file more recent */
	  if (fi->ctime > statbuf.st_ctime)
	    of = open(s, O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
	  else {
	      sprintf(l, "File %s not overwritten because not as recent", s);
	      write_warning_log(l);
	      return ret;
	  }
	  break;
      }
    
    sprintf(l, "Creating file %s\n", s);
    if (log_level == 3) write_log(l);
    if (of == -1) {
	write_error_log(l);
	return ret;
    }
    
    sz = fi->act_size;				 /* read in regular file */
    if (fi->compressed) {
	close(of);
	if (log_level == 3) write_log("Opening temporary file to compress");
	tmpnam(tmpf);
	of = open(tmpf, O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);/* open temp file */
	if (of == -1) {
	    write_error_log("Opening temporary file to compress to");
	    return ret;				 /* couldn't decompress so didn't read */
	}
    }

    sprintf(l,"Reading file %s", s);
    if (log_level > 1) write_log(l);
    c1 = 0;
    while (1) {					 /* copy file accross */
	tr = min(sz, max_tr_size); 
	x = tape_read(tr_buffer, tr);		 /* from device file   */
	if (!x) break;
	if (x == -1) {
	    write_fatal_log(l);
	    do_exit(ERROR_READING);
	}
	sz -= x;
	if (c1 != -1) {				 /* if there have been no previous write errors */
	    c1 = write(of, tr_buffer, x);	 /* if there has been a prev write error */
	    if (c1 == -1)			 /* still continue reading to pass by file on device  */
		write_error_log(l);
	}
	if (!sz) break;				 /* read all we need to */
    }
	
    if (calc_checksum(of) != fi->checksum) {
	sprintf(l, "Checksum error for file %s", s);
	write_log(l);
    }

    if (fi->compressed) {			 /* now decompress */
	sprintf(fn, "%s -d %s > \"%s\"", COMPRESS_PROG, tmpf, s);
	change_dollar(fn);			 /* fixes so that $ are changed to \$ */
	sprintf(l, "Compression file %s", s);
	if (log_level == 3) write_log(l);
	if (system(fn) != 0)			 /* do decompress */
	  write_error_log(l);
	close(of);
	unlink(tmpf);				 /* delete temp file */
    }

    setowners:;					 
    
    sprintf(l, "Setting permissions, modes & times for %s", s);
    if (log_level == 3) write_log(l);
    sz = 0;
    if ((of) && (!fi->compressed)) {
	sz += fchmod(of, fi->mode);		 /* now restore permissions/owners etc... */
	sz += fchown(of, fi->uid, fi->gid);
	    close(of);
    }
    else {
	sz += chmod(s, fi->mode);		 /* directories/links */
	sz += chown(s, fi->uid, fi->gid);
    }
    ut.actime = fi->atime;			 /* fix up times */
    ut.modtime = fi->mtime;
    sz += utime(s, &ut);
    if (sz)
      write_error_log(l);
    return (ret == -2) ? -1 : 1;		 /* we passed file */
}


void dumb_restore_print_status(WINDOW *mes_box, long cur_in_vol,
			  long file_size, char *fn,
			  time_t t_start, time_t t_current)
{
    char scr[MAXNAMLEN], num1[50], num2[50];
    
    sprintf(scr, "Passing %s", fn);
    status_box(mes_box, scr, 0, FALSE, 1);
    sprintf(scr, "File %ld of %ld", cur_in_vol, ifd.no_in_archive);
    status_box(mes_box, scr, 2, FALSE, 1);
    sprintf(scr, "Byte %s of %s", convert(num1, rtotal_read), 
	    convert(num2, total_compressed));
    status_box(mes_box, scr, 3, FALSE, 1);
    if (total_compressed) 
      sprintf(scr, "%.0f%% done, time elapsed %s",  (float) rtotal_read/(float) total_compressed*100,
	      convtime(t_start, t_current));
    else				 /* if total bytes=0 then base on # files */
      sprintf(scr, "%ld%% done, time elapsed %s",  cur_in_vol/ifd.no_in_archive*100,
	      convtime(t_start, t_current));
    status_box(mes_box, scr, 5, FALSE, 1);
    sprintf(scr, "Average restore/mkinfo rate %ld K/sec",
	    ((t_current-t_start) ? (rtotal_read/1024)/(t_current-t_start) : 0));
    status_box(mes_box, scr, 6, FALSE, 1);
}
    

long get_selected();				 /* is in sel_restore */
long rno_in_vol;
int  v;
void smart_restore_print_status(WINDOW *mes_box, long cur_in_vol,
			  long file_size, char *fn,
			  time_t t_start, time_t t_current)
{
    char scr[MAXNAMLEN], num1[50], num2[50];
    
    sprintf(scr, "Passing %s", fn);
    status_box(mes_box, scr, 0, FALSE, 1);
    sprintf(scr, "Volume %d of %d. File %ld of %ld", v, ifd.number_volumes,
	    cur_in_vol, rno_in_vol);
    status_box(mes_box, scr, 2, FALSE, 1);
    sprintf(scr, "Read byte %s of %s", convert(num1, rtotal_read), 
	    convert(num2, get_selected()));
    status_box(mes_box, scr, 3, FALSE, 1);
    if (get_selected()) 
      sprintf(scr, "%.0f%% done, time elapsed %s",  (float) rtotal_read/(float) get_selected()*100,
	      convtime(t_start, t_current));
    else				 /* if total bytes=0 then base on # files */
      sprintf(scr, "Time elapsed %s", convtime(t_start, t_current));
    status_box(mes_box, scr, 5, FALSE, 1);
    sprintf(scr, "Average restore/mkinfo rate %ld K/sec",
	    ((t_current-t_start) ? (rtotal_read/1024)/(t_current-t_start) : 0));
    status_box(mes_box, scr, 6, FALSE, 1);

}


void do_restore() {
    WINDOW *mes_box=NULL;
    int tape, pos;
    char l[200];
    time_t t_start;
    
/* An error reading from the backup device is a fatal error */
    if (!no_sel)
      return;
    rtotal_read = 0;
    mes_box = status_box(mes_box, "", 2, TRUE, 4);/* create window */
    nodelay(mes_box, TRUE);
    status_box(mes_box, "Identifying tape", 2, FALSE, 1);
    dv = tape_open(O_RDONLY);			 /* open rewinding (assume at beginning of tape) */
    if (dv == -1)
      do_exit(ERROR_OPENING_BACKUP);
    tape_readheader(&tdh, 0);			 /* read in tape header */
    if (tdh.magic != TAPER_MAGIC_NUMBER)
      do_exit(ERROR_MAGIC);
    if (is_regfile(dv)) {			 /* regular file - must traverse whole thing */
	close_statusbox(mes_box);
	common_restore_loop(restore_action, 0, dumb_restore_print_status);
	return;
    }
    t_start = time(NULL);
    while ((v=find_min_vol()) != 0) {
	status_box(mes_box, "", 0, FALSE, 1);
	status_box(mes_box, "", 3, FALSE, 1);
	status_box(mes_box, "", 5, FALSE, 1);
	status_box(mes_box, "", 6, FALSE, 1);
	calc_tape_pos(v, &tape, &pos);		 /* work out which tape we want */
	sprintf(l, "Advancing to volume %d", v);
	status_box(mes_box, l, 2, FALSE, 1);
	if (check_tape(tape) == -1)		 /* make sure we have right tape */
	  break;
	if (v != 1) {				 /* no need to advance for vol #1 */
	    tape_close();			 /* will be rewinding device */
	    dv = ntape_open(O_RDWR);
	    tape_fsf(pos);			 /* advance to right position */
	    tape_close();
	    dv = tape_open(O_RDWR);
	    if (dv == -1) {
		write_fatal_log("Advancing to correct volume");
		do_exit(ERROR_OPENING_BACKUP);
	    }
	}
	read_volheader(&rno_in_vol);		 /* read in volume header */
        if (traverse_volume(restore_action, rno_in_vol, t_start, mes_box, 
			    0, 0, smart_restore_print_status) == -1)
	  break;
    }
}


    
void kill_windows() {
    delwin(selection); delwin(files);
    werase(win_main); wrefresh(win_main);
    endwin();
    curs_set(1);
}

    
int read_vol_dir() 
{
    if (do_read_vol_dir(tape, !pr_dir, O_RDONLY, TRUE, TRUE) ==  BAD_MAGIC) 
	do_exit(ERROR_MAGIC);
    return 1;
}


void print_voldir() {
    long c;
    struct file_entry *fe;
    char   s[MAXNAMLEN], s1[20], s2[20];
    long   c_sz=0, u_sz =0;
    struct tm t;
    
    printf("%-25s %3s %5s %14s %12s %12s\n", "Pathname", "Vol", "Pos", "Backup", "File Size", "Compressed");
    printf("----------------------------------------------------------------------------\n");
    fe = vol_files;
    for (c=0; c<ifd.no_in_archive; c++) {
	strcpy(s, fe->name); s[22] = 0;
	t = *localtime(&fe->i.backup_time);
	printf("%-25s %3d %5ld %2d/%2d/%2d %2d:%02d %12s %12s\n", s, fe->i.volume, fe->i.pos_in_archive,t.tm_mday,
	       t.tm_mon+1, t.tm_year, t.tm_hour, t.tm_min, convert(s1, fe->i.size),
	       convert(s1, fe->i.act_size));
	c_sz += fe->i.act_size;
	u_sz += fe->i.size;
	fe++;
    }
    printf("\n%-47s    %12s %12s\n", "TOTALS", convert(s2, u_sz), convert(s2, c_sz));
}

	
void init_windows() {
/*  Initialise windows and print screen edges etc..      */

    win_main = initscr();			 /* Initialize the cuwrsor library */    
    raw(); noecho(); cbreak();
    start_color(); setup_colors();

    screen_xlen=win_main->_maxx;		 /* Dimensions of screen */    
    screen_ylen=win_main->_maxy;

    left_width = screen_xlen; 
    right_width = 0;
    
    screen_ylen_files = screen_ylen/2 +4;
    screen_ylen_selection = screen_ylen - screen_ylen_files -1;
    
    title = newwin(1, screen_xlen, 0, 0);	 /* title bar */
    files = newwin(screen_ylen_files, screen_xlen,
		  1, 0);			 
    selection = newwin(screen_ylen_selection, screen_xlen,
		   screen_ylen_files+1, 0);
    bottom = newwin(1, screen_xlen, screen_ylen, 0);
    keypad(selection, TRUE); 			 
    keypad(files, TRUE);			 /* set input modes */

    my_werase(files, COLOR_DIRECTORY); my_werase(selection, COLOR_SELECTED);
    my_werase(bottom, COLOR_BOTTOM);
    wrefresh(files); wrefresh(selection); wrefresh(bottom);
    curs_set(0);
}


int main(int argc, char *argv[]) {

    int  x;

    *rel_path = 0;
    init_memory();
    vol_files = my_malloc(1);			  
    sel_files = my_malloc(1);
    vt_info = my_malloc(1);
    dirs = my_malloc(1);
    read_environment();
    read_prefs_file(argc, argv);
    parse_args(argc, argv);
    fixup_device_names();
    init_buffers();
    if (!pr_dir) init_windows();		 /* Draw windows */ 
    open_logfile("Restore");
    read_vol_dir();				 /*  Read volume dir */
    if (pr_dir) {
	print_voldir();
	tape_close(dv);
    }
    else
    {
	print_title_line();
	if (ifd.no_in_archive)
	  x = select_restore_files();				 /* get files user wants */
	else {
	    message_box("This archive is empty", MB_OK);
	    x = 0;
	}
	delwin(selection); delwin(files);
	my_werase(win_main, COLOR_MAIN); wrefresh(win_main);
	  
	if (x) do_restore();

	tape_close(dv);
       	my_werase(win_main, COLOR_MAIN); wrefresh(win_main);
	final_message("Restore");
	delwin(title);
	werase(win_main); wrefresh(win_main);
	endwin();
      }

    my_free_all();
    close_logfile("Restore");
    return 0;			                 /* succesful return */
}

    
