//
// Copyright (c) 1997 Jean-Marc Zucconi
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer
//    in this position and unchanged.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. The name of the author may not be used to endorse or promote products
//    derived from this software without specific prior written permission
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/errno.h>
#include <c++tk.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <camlib.h>

#include "cd-write.h"
#include "copy.h"

static char *help_copy = "{\n\
\n\
}";

static struct dev_struct dev[] = {
    {"cd0", 0},
    {"cd1", 0},
    {"cd2", 0},
    {"cd3", 0},
    {0, 0}
};

static Label label;
static LabelEntry E_out,E_prefix;
static ScrolledHList hlist;
static ButtonBox bx;
static Button bbrowse, btrack, tcopy;
static ButtonBox bbox;
static struct cd_op *ops;
static int speed;
static int speeds[8] = {1, 2, 4, 6, 8, 10, 12, 16}; 
static Radiobutton r[8];
static Button bsel;
static char device[20];
static struct toc *t = 0;
static int copying = 0;
static char filename[1024];
static char dir[1024] = {0};
static char prefix[30] = "track";
static int bytes_copied;

struct TRK {
    int track;
    LabelEntry *start, *count, *file;
    Button *cancel;
    char *sel;
};

static void do_copy (int, char **);
static int copy_audio (FD, int, int, int, int, char * = 0);
static int copy_blocks (FD, int, int, int, int, char * = 0);

static void
disable ()
{
    int i;
    char x[3] = "b?";
    bsel.configure ("-state disabled");
    for (i = 0; dev[i].name; i++) {
	x[1] = i + '0';
	bbox.subwidget (x, "configure -state disabled");
    }
    for (i = 0; i < 8; i++)
	r[i].configure ("-state disabled");
    E_out.subwidget ("entry", "configure -state disabled");
    bbrowse.configure ("-state disabled");
}
static void
enable ()
{
    int i;
    char x[3] = "b?";
    bsel.configure ("-state normal");
    for (i = 0; dev[i].name; i++) {
	x[1] = i + '0';
	if (dev[i].there)
	    bbox.subwidget (x, "configure -state normal");
    }
    int *sp = ops->speeds ();
    for (i = 0; sp[i]; i++) {
	int n ;
	for (n = 0; n < 8; n++)
	    if (speeds[n] == sp[i])
		break;
	if (n != 8)
	    r[n].configure ("-state normal");
    }
    E_out.subwidget ("entry", "configure -state normal");
    bbrowse.configure ("-state normal");
}
void
check_dev (dev_struct * dev)
{
    char s[20];
    int i, n;
    FD fd;
    for (i = n = 0; dev[i].name; i++) {
	fd = cam_open_device (dev[i].name, O_RDWR);
	if (fd) {
	    cam_close_device (fd);
	    dev[i].there = 1;
	    n++;
	}
    }
    if (!n && geteuid ())
	Error ("You don't have access permission to the device files (/dev/cd?)");
}
static void
select_dir (int argc, char **argv)
{
    char *s = strdup (*argv);
    E_out.subwidget ("entry", "delete 0 end");
    E_out.subwidget ("entry insert end", s);
    free (s);
}
static void
select_track_file (int argc, char **argv)
{
    TRK *e;
    char *s = strdup (argv[1]);
    e = (TRK *) atoi (argv[0]);
    e->file->subwidget ("entry", "delete 0 end");
    e->file->subwidget ("entry insert end", s);
    free (s);
}

static void
browse (int argc, char **argv)
{
    DirSelectDialog dirsel;
    if (*TkWinfo ("exists", ".dirselc") == '0')
	dirsel = tixDirSelectDialog (".dirselc");
    dirsel.command (select_dir);
    dirsel.popup ();
}

static void
fbrowse (int argc, char **argv)
{
    char s[20];
    TRK *trk = (TRK *) atoi (argv[1]);
    strcpy (s, ".filesel");
    strcat (s, argv[0]);
    if (*TkWinfo ("exists", s) == '1') 
	return;
    FileSelectDialog  f = tixFileSelectDialog (s);
    trk->sel = (char *) f.pathname ();
    f.command (select_track_file, argv[1]);
    f.popup ();
}
static void
trackcancel (int argc, char **argv)
{
    TRK *e;
    int q;
    char s[20], *p;
    e = (TRK *) atoi (argv[1]);
    p = e->cancel->configure ("-text");
    q = 0;
    while (*p) {
	if (*p++ == ' ')
	    q++;
	if (q == 4)
	    break;
    }
    if (*p == 'C') {
	copying++;
	return;
    }
    strcpy (s, ".track");
    strcat (s, argv[0]);
    TkDestroy (s);
    if (e->sel && *TkWinfo ("exists", e->sel) == '1')
	TkDestroy (e->sel);
    delete e->start;
    delete e->count;
    delete e->file;
    delete e->cancel;
    delete e;
}
static void
trackcopy (int argc, char **argv)
{
    int addr1, addr2;
    int track = atoi (argv[0]);
    TRK *e = (TRK *) atoi (argv[1]);
    if (copying)
	return;
    addr1 = atoi (e->start->subwidget ("entry", "get"));
    addr2 = atoi (e->count->subwidget ("entry", "get"));
    if (addr1 < 0 || addr1 > addr2 || addr1 > t[track].addr) {
	Error ("Invalid value for first block");
	return;
    }
    addr1 += t[track-1].addr;
    addr2 += addr1;
    if (addr2 > t[track].addr) {
	Error ("Invalid value for length");
	return;
    }
    char msg[80];
    char *file = strdup (e->file->subwidget ("entry", "get"));
    time_t t1, t2;
    FD fd = cam_open_device (device, O_RDWR);
    copying = 1;
    disable ();
    hlist.configure ("-cursor watch");
    start_info ();
    prevent_removable (fd, 1);
    sprintf (msg, "Copying track %s", argv[0]);
    Info (msg);
    copy_progress_start (addr2-addr1);
    t1 = time (0);
    bytes_copied = 0;
    e->cancel->configure ("-text Cancel");
    bx.subwidget ("cancel", "configure -state disabled");
    if (t[track-1].type & T_DATA || ops->perfect_dae || (addr2-addr1+1) <= FRAME_COUNT)
	copy_blocks (fd, t[track-1].track, addr1, addr2, t[track-1].type, file);
    else
	copy_audio (fd, t[track-1].track, addr1, addr2, t[track-1].type, file);
    bx.subwidget ("cancel", "configure -state normal");
    e->cancel->configure ("-text Dismiss");
    t2 = time (0);
    if (bytes_copied && t2 != t1) {
	if (bytes_copied >= 1024*1024)
	    sprintf (msg, "%.2f Mbytes copied in %d seconds (%.1fK/s)\n",
		     bytes_copied/1024./1024., t2-t1, bytes_copied/1024./(t2-t1));
	else
	    sprintf (msg, "%.2f Kbytes copied in %d seconds (%.1fK/s)\n",
		     bytes_copied/1024., t2-t1, bytes_copied/1024./(t2-t1));
	Info (msg);
    }
    prevent_removable (fd, 0);
    hlist.configure ("-cursor {}");
    enable ();
    copying = 0;
    CLOSE (fd);
    end_info ();
    free (file);
}
static void
dbclick (int argc, char **argv)
{
    char s[100];
    int track = atoi (argv[0]) + 1;
    sprintf (s, ".track%d", track);
    if (*TkWinfo ("exists", s) == '1')
	return;
    struct TRK *trk = new TRK;

    trk->sel = 0;
    TkToplevel (s);
    sprintf (s, ".track%d {track %d}", track, track);
    TkWm ("title", s); 
    sprintf (s, ".track%d.left", track);
    TkFrame (s);
    sprintf (s, ".track%d.left.start", track);
    trk->start = new LabelEntry;
    bcopy (&tixLabelEntry (s, "-label {first block:} -labelside left \
                    -options {entry.width 8 label.width 15 label.anchor e}"),
	   trk->start, sizeof(LabelEntry));
    trk->start->subwidget ("entry", "insert 0 0");
    TkPack (s, "-side top -padx 10m -pady 2m -fill x");
    sprintf (s, ".track%d.left.count", track);
    trk->count = new LabelEntry;
    bcopy (&tixLabelEntry (s, "-label length: -labelside left \
                    -options {entry.width 8 label.width 15 label.anchor e}"),
	   trk->count, sizeof(LabelEntry));
    TkPack (s, "-side top -padx 10m -pady 2m -fill x");
    sprintf (s, "%d", t[track].addr-t[track-1].addr)+1;
    trk->count->subwidget ("entry insert 0", s);
    sprintf (s, ".track%d.left.file", track);
    trk->file = new LabelEntry;
    bcopy (&tixLabelEntry (s, "-label {File:} \
            -labelside top -options { entry.width 22  label.anchor w }"),
	   trk->file, sizeof(LabelEntry));
    TkPack (s, "-side top -padx 10m -pady 2m -fill x");

    sprintf (s, ".track%d.right", track);
    TkFrame (s);
    sprintf (s, ".track%d.right.ok", track);
    tcopy = TkButton (s, "-text Copy");
    TkPack (s, "-side top -anchor s -padx 4 -pady 1 -fill x");
    sprintf (s, ".track%d.right.cancel", track);
    trk->cancel = new Button;
    bcopy (&TkButton (s, "-text Dismiss"), trk->cancel, sizeof (Button));
    TkPack (s, "-side top -anchor s -padx 4 -pady 1 -fill x");
    sprintf (s, ".track%d.right.fill", track);
    TkLabel (s, "-text {}");
    TkPack (s, "-side top -anchor s -padx 4 -pady 4 -fill both");

    sprintf (s, ".track%d.right.browse", track);
    btrack = TkButton (s, "-text {Browse...}");
    TkPack (s, "-side top -anchor s -padx 4 -pady 1 -fill x");
    sprintf (s, "%d %d", track, trk);
    btrack.command (fbrowse, s);
    tcopy.command (trackcopy, s);
    trk->cancel->command (trackcancel, s);
    sprintf (s, ".track%d.left .track%d.right", track, track);
    TkPack (s, "-side left -pady 2m  -fill both");
}
static void
set_button (int argc, char **argv)
{
    char s[200], *state;
    struct dev_info *inf;
    int i = **argv - '0';

    hlist.subwidget ("hlist", "delete all");

    if (t)
	free (t);
    t = 0;

    strcpy (device, dev[i].name);
    FD fd = cam_open_device (device, O_RDWR);
    rezero_unit (fd);

    inf = inquiry (fd);
    if (!inf) {
	CLOSE (fd);
	hlist.subwidget ("hlist", "add 0 -itemtype text -text {INQUIRE command failed}");
	return;
    }
    sprintf (s, "-text {%s             %s             %s}", inf->vendor, inf->product, inf->revision);
    label.configure (s);
    i = test_unit_ready (fd);
    if (i < 0) {
	hlist.subwidget ("hlist", "add 0 -itemtype text -text {TEST UNIT READY command failed}");
	CLOSE (fd);
	return;
    }
    if (i == 0) {
	hlist.subwidget ("hlist", "add 0 -itemtype text -text {Drive empty}");
	CLOSE (fd);
	return;
    }
    t = cd_toc (fd);
    i = 3;
    while (i-- && !t) {
	sleep (1);
	t = cd_toc (fd);
    }
    CLOSE (fd);
    if (!t) {
	hlist.subwidget ("hlist", "add 0 -itemtype text -text {TOC command failed}");
	return;
    }
    ops = cd_cap (inf->vendor, inf->product, inf->revision);
    int *sp = ops->speeds ();
    speed = ops->default_speed ();
    for (i = 0; i < 8; i++)
	r[i].configure ("-state disabled");
    for (i = 0; sp[i]; i++) {
	int n ;
	for (n = 0; n < 8; n++)
	    if (speeds[n] == sp[i])
		break;
	r[n].configure ("-state normal");
	if (sp[i] == speed)
	    r[i].invoke ();
    }
    bx.subwidget ("ok", "configure -state normal");
    bsel.configure ("-state normal");
    hlist.subwidget ("hlist", "delete all");
    for (i = 0; t[i].track != 0xaa; i++) {
	state = (t[i].type & T_AUDIO && !ops->read_audio_blocks) ? 
	    (char *)"disabled" : (char *)"normal";
	sprintf (s, "add %d -itemtype imagetext -image %s -state %s", i, 
		 t[i].type & T_AUDIO ? (t[i].type & T_PREEMP ? "note_p" : "note") : "data",
		 state);
	hlist.subwidget ("hlist", s);
	sprintf (s, "item create %d 1 -itemtype text -text {     track %2d     }", i,
		 t[i].track);
	hlist.subwidget ("hlist", s);
	if (t[i].type & T_AUDIO) {
	    int min, sec;
	    min = (t[i+1].addr-t[i].addr)/(60*75);
	    sec = (t[i+1].addr-t[i].addr -min*60*75)/75;
	    sprintf (s, "item create %d 2 -itemtype text -text {duration:  %02d:%02d}",
		     i, min, sec);
	} else
	    sprintf (s, "item create %d 2 -itemtype text -text {%d  blocks}", i,
		     t[i+1].addr-t[i].addr);
	hlist.subwidget ("hlist", s);
    }
    hlist.subwidget ("hlist yview", "0");
}
static void
select_all (int argc, char **argv)
{
    char s[40];
    int i;
    for (i = 0; t[i].track != 0xaa; i++)
	if (t[i].type & T_DATA || ops->read_audio_blocks) {
	    sprintf (s, "selection set %d", i);
	    hlist.subwidget ("hlist", s);
	}
}
static void
ret (int argc, char **argv)
{
    if (copying) 
	copying++;
    else
	TkDestroy (".copy");
}
void
copy_cd (int argc, char **argv)
{
    int i;
    Button b;
    char s[180], x[4];

    if (*TkWinfo ("exists", ".copy") == '1')
	return;

    check_dev (dev);

    TkToplevel (".copy");
    Balloon balloon = tixBalloon (".copy.balloon");
    
    bbox = tixButtonBox (".copy.b -orientation horizontal");
    x[0] = 'b'; x[2] = 0;
    for (i = 0; dev[i].name; i++) {
	sprintf (s, "-text %s -state %s", 
		 dev[i].name, dev[i].there ? "normal" : "disabled");
	x[1] = i + '0';
	bbox.add (x, s);
	bbox.subwidget (x)->command (set_button, x+1);
    }

    TkPack (".copy.b", "-side top -pady 2 -fill x");

    label = TkLabel (".copy.label -text { } -bd 2 -width 25 -height 1 -relief sunken");
    TkPack (".copy.label", "-side top -pady 8 -fill x -expand 1");
    
    hlist = tixScrolledHList (".copy.f", "-relief flat -bd 2 -options {\n\
                    hlist.columns 3\n\
                    hlist.gap 0\n\
                    hlist.selectMode extended\n\
                    hlist.header false\n\
                    hlist.width 40 hlist.height 15\n\
             }");
    TkPack (".copy.f" , "-side top -pady 10");
    hlist.subwidget ("hlist")->command (dbclick);

    bsel = TkButton (".copy.sel -text {Select all tracks} -state disabled");
    bsel.command (select_all);
    balloon.bind (".copy.sel -balloonmsg", "{Use Ctrl-Button1 to make disjoint selections}");
    TkPack (".copy.sel", "-side top");

    TkFrame (".copy.rb");
    speed = 0;
    TkLabel (".copy.rb.l", "-text speed:");
    TkPack (".copy.rb.l", "-side left -padx 2m -anchor w");
    for (i = 0; i < 8; i++) {
	sprintf (s, ".copy.rb.b%d -text %dx -relief flat -value %d \
                   -state disabled", speeds[i], speeds[i], speeds[i]);
	r[i] = TkRadiobutton (s);
	r[i].variable (&speed);
	*strchr (s, ' ') = 0;
	TkPack (s, "-side left -padx 2m -anchor w");
    }
    TkPack (".copy.rb", "-side top");

    TkFrame (".copy.d");
    E_out = tixLabelEntry (".copy.d.e", "-label {Destination directory:} \
            -labelside top -options {\n\
	    entry.width 35\n\
	    label.anchor w }");
    if (!*dir)
	getcwd (dir, 1024);
    E_out.subwidget ("entry insert 0", dir);
    bbrowse = TkButton (".copy.d.browse", "-text {Browse...}");
    bbrowse.command (browse);
    TkPack (".copy.d.e  -side left -expand yes -fill x -anchor s -padx 4 -pady 1");
    TkPack (".copy.d.browse -side left -anchor s -padx 4 -pady 1");
    TkPack (".copy.d", "-side top -pady 2m -fill x");

    E_prefix = tixLabelEntry (".copy.p", "-label {file prefix: } \
            -labelside left -options {\n\
	    entry.width 20\n\
	    label.anchor w }");
    balloon.bind (".copy.p -balloonmsg", "{File names will be \n\
   <file prefix>_NN.data,\n\
   <file prefix>_NN.audio,\n\
or <file prefix>_NN.audio+}");
    E_prefix.subwidget ("entry insert 0", prefix);
    TkPack (".copy.p", "-side top -pady 2m ");
    bx = tixButtonBox (".copy.b2 -orientation horizontal");
    bx.add ("ok", "-text  Copy -state disabled");
    bx.add ("cancel", "-text Dismiss");
//    bx.add ("help", "-text Help");
    bx.subwidget ("ok")->command (do_copy);
    bx.subwidget ("cancel")->command (ret);
//    bx.subwidget ("help")->command (help, ADDR (help_copy));
    TkPack (".copy.b2", "-side top -fill x");
}

static FILE * 
open_file (int n, int type)
{
    char s[30];
    FILE *f;
    char *p;

    strncpy (prefix, E_prefix.subwidget ("entry", "get"), 20);
    prefix[20] = 0;
    if (!*prefix)
	strcpy (prefix, "track");
    if (type == T_DATA)
	p = "data";
    else {
	if (type & T_PREEMP)
	    p = "audio+";
	else
	    p = "audio";
    }
    sprintf (s, "%s_%02d.%s", prefix, n, p);
    strcpy (dir, E_out.subwidget ("entry", "get"));
    strcpy (filename, dir);
    if (*filename == 0)
	strcpy (filename, "./");
    else if (filename[strlen(filename)-1] != '/')
	strcat (filename, "/");
    strcat (filename, s);
    f = fopen (filename, "w");
    return f;
}
static int
copy_blocks (FD fd, int track, int start_block, int end_block, int type, char *file)
{
    char *buf;
    FILE *f;
    int n, size, r;
    int bsize, nblk;
    int status = STATUS_OK;

    status = start_unit (fd);
    if (status)
	return STATUS_ERROR;
    status = ops->set_mode (fd, type);
    if (!status) {
	if (type & T_DATA)
	    Info ("can't set device in data mode");
	else
	    Info ("can't set device in audio mode");
	return STATUS_ERROR;
    }
    status = ops->set_speed (fd, speed);
    if (!status) {
	Info ("can't set speed");
	return STATUS_ERROR;
    }
    if (type & T_DATA ) {
	nblk = 32;
	bsize = 2048;
    } else {
	nblk = FRAME_COUNT;
	bsize = 2352;
    }
    buf = (char *) malloc (nblk*bsize);

    if (!file)
	f = open_file (track, type);
    else
	f = fopen (file, "w");
    if (!f) {
	sprintf (buf, "can't create file %s", file ? file : filename);
	Info (buf);
	Info (strerror (errno));
	free (buf);
	return STATUS_ERROR;
    }
    n = end_block - start_block + 1;
    w_active |= COPY_ACTIVE;
    while (n) {
	if (n > nblk) {
	    size = nblk*bsize;
	    n -= nblk;
	} else {
	    size = bsize;
	    n--;
	}
	status = type & T_DATA ? 
	    ops->read_data_blocks (fd, buf, size, start_block)
	    :
	    ops->read_audio_blocks (fd, buf, size, start_block);
	if (!status) {
	    sprintf (buf, "can't read blocks %d-%d", start_block, start_block+size/2048);
	    Info (buf);
	    status = STATUS_ERROR;
	    break;
	}
	if ((r = fwrite (buf, 1, size, f)) != size) {
	    sprintf (buf, "%s: write error (write %d instead of %d)", 
		     filename, r, size);
	    Info (strerror (errno));
	    Info (buf);
	    status = STATUS_ERROR;
	    break;
	}
	bytes_copied += size;
	if (n >= nblk)
	    start_block += nblk;
	else
	    start_block++;
	if (copying > 1) {
	    status = STATUS_CANCEL;
	    break;
	}
	copy_progress_msg (n);
    }
    if (status == STATUS_CANCEL)
	Info ("copy interrupted");
    else if (status == STATUS_ERROR)
	Info ("copy failed");
    else
	Info ("");

    fclose (f);
    free (buf);
    w_active &= ~COPY_ACTIVE;
    return status;
}
static int
over (char *a, char *b, int n, int s)
{
    int i;
    char *p;
    long l = *(long *)b;
    // lets see if b starts at or after a+FRAME_SIZE*FRAME_COUNT-n
    i = n-200;
    p = a + FRAME_SIZE*FRAME_COUNT-n;
    while (i--) {
	if ((l == *(long *) p) && (memcmp (p, b, i+200) == 0))
	    return i+200+1;
	p++;
    }
    // hope b starts before a+FRAME_SIZE*FRAME_COUNT-n
    i = FRAME_SIZE*FRAME_COUNT-n;
    p = a + i;
    while (i--) {
	if ((l == *(long *) p) && (memcmp (p, b, FRAME_SIZE) == 0))
	    return FRAME_SIZE*FRAME_COUNT-i-1;
	p--;
    }
    // bad news
    return 0;
}
static int
copy_audio (FD fd, int track, int start_block, int end_block, int type, char *file)
{
    char *buf1, *buf2;
    FILE *f;
    int n, nb, size, r, c;
    int status;
    int overlap = 1, max_overlap = 1;
    int badness = 0;

    status = start_unit (fd);
    if (status)
	return STATUS_ERROR;
    status = ops->set_mode (fd, T_AUDIO);
    if (!status) {
	Info ("can't set device in audio mode");
	return STATUS_ERROR;
    }
    status = ops->set_speed (fd, speed);
    if (!status) {
	Info ("can't set speed");
	return STATUS_ERROR;
    }
    size = FRAME_SIZE*FRAME_COUNT;
    buf1 = new char[size];
    buf2 = new char[size];
    if (!file)
	f = open_file (track, type);
    else
	f = fopen (file, "w");
    if (!f) {
	sprintf (buf1, "can't create file %s", file ? file : filename);
	Info (strerror (errno));
	Info (buf1);
	delete [] buf1;
	delete [] buf2;
	return STATUS_ERROR;
    }
    status = ops->read_audio_blocks (fd, buf1, size, start_block);
    if (!status) {
	sprintf (buf1, "can't read blocks %d-%d", start_block, start_block+size/FRAME_SIZE);
	Info (buf1);
	return STATUS_ERROR;
    }
    if ((nb = fwrite (buf1, 1, size, f)) != size) {
	sprintf (buf1, "%s: write error (write %d instead of %d)", 
		 filename, nb, size);
	Info (strerror (errno));
	Info (buf1);
	return STATUS_ERROR;
    }
    bytes_copied += size;
    start_block += FRAME_COUNT;

    w_active |= COPY_ACTIVE;

    while (1) {
	// buf1 contains blocks #start_block-FRAME_COUNT, #start_block-FRAME_COUNT+1,...  #start_block-1
	// read nb frames in buf2, from block #start_block-overlap
	if ((start_block-overlap+FRAME_COUNT) > end_block)
	    nb = end_block-start_block+overlap+1;
	else
	    nb = FRAME_COUNT;
	size = FRAME_SIZE*nb;
	status = ops->read_audio_blocks (fd, buf2, size, start_block-overlap);
	if (!status) {
	    sprintf (buf1, "can't read blocks %d-%d", 
		     start_block-overlap,
		     start_block-overlap+nb-1);
	    Info (buf1);
	    status = STATUS_ERROR;
	    break;
	}
	status = over (buf1, buf2, overlap*FRAME_SIZE, size);
        if (status < FRAME_SIZE)
	    status = 0;
	if (status > overlap*FRAME_SIZE)
	    badness += status - overlap*FRAME_SIZE;
	if (status == 0) {
	    overlap ++;
	    if (overlap >= FRAME_COUNT) {
		Info ("Impossible to extract audio");
		status = STATUS_ERROR;
		break;
	    }
	    continue;
	}
	n = status/FRAME_SIZE;
	r = status-FRAME_SIZE*n;
        if (r) // the last block in buf2 is incomplete
	    nb--;
        size = FRAME_SIZE*(nb-n); // new data
	if (size > 0)
	    if ((c = fwrite (buf2+status, 1, size, f)) != size) {
		sprintf (buf1, "%s: write error (write %d instead of %d)", 
			 filename, c,  size);
		Info (strerror (errno));
		Info (buf1);
		status = STATUS_ERROR;
		break;
	    }
	bytes_copied += size;
	if (copying > 1) {
	    status = STATUS_CANCEL;
	    break;
	}
	copy_progress_msg (end_block-start_block);
	if (n == overlap)
	    overlap --;
	else
	    overlap = n;
	if (overlap < 1)
	    overlap = 1;
	if (overlap > max_overlap)
	    max_overlap = overlap;
	start_block += nb - n;
	if (start_block > end_block) {
	    status = STATUS_OK;
	    copy_progress_msg (0);
	    break;
	}
	c = FRAME_SIZE*FRAME_COUNT-size;
	memcpy (buf1, buf1+size, c); 
	memcpy (buf1 + c, buf2 + status, size);
    }
    if (status == STATUS_CANCEL)
	Info ("copy interrupted");
    else if (status == STATUS_ERROR)
	Info ("copy failed");
    else
	Info ("");

    fclose (f);
    sprintf (buf1, "badness = %d max overlap = %d", badness, max_overlap);
    Info (buf1);
    delete [] buf1;
    delete [] buf2;
    w_active &= ~COPY_ACTIVE;
    return status;
}
static int
copy_track (FD fd, int n)
{
    int start_block = t[n].addr;
    int end_block = t[n+1].addr-1;
    if (t[n].type & T_DATA || ops->perfect_dae)
	return copy_blocks (fd, t[n].track, start_block, end_block, t[n].type);
    else
	return copy_audio (fd, t[n].track, start_block, end_block, t[n].type);
}
static int
bad_dir ()
{
    struct stat st;
    strcpy (dir, E_out.subwidget ("entry", "get"));
    if (!*dir || (stat (dir, &st) < 0) || !(st.st_mode & S_IFDIR)) {
	Error ("Bad destination directory");
	return 1;
    }
    return 0;
}
static void
do_copy (int argc, char **argv)
{
    int status;
    char *s, *p, *q;
    int n;
    char msg[80];
    time_t t1, t2;

    if (copying)
	return;

    if (bad_dir ())
	return;
    FD fd = cam_open_device (device, O_RDWR);
    p = s = strdup (hlist.subwidget ("hlist", "info selection"));
    copying = 1;
    disable ();
    hlist.configure ("-cursor watch");
    start_info ();
    prevent_removable (fd, 1);
    bx.subwidget("cancel", "configure -text Cancel");
    while (q = strtok (p, " \t")) {
	hlist.subwidget ("hlist yview", q);
	TkUpdate ();
	n = atoi (q);
	sprintf (msg, "Copying track %d", t[n].track);
	Info (msg);
	copy_progress_start (t[n+1].addr-t[n].addr);
	t1 = time (0);
	bytes_copied = 0;
	status = copy_track (fd, n);
	t2 = time (0);
	if (bytes_copied && t2 != t1) {
	    if (bytes_copied >= 1024*1024)
		sprintf (msg, "%.2f Mbytes copied in %d seconds (%.1fK/s)\n",
			     bytes_copied/1024./1024., t2-t1, bytes_copied/1024./(t2-t1));
	    else
		sprintf (msg, "%.2f Kbytes copied in %d seconds (%.1fK/s)\n",
			     bytes_copied/1024., t2-t1, bytes_copied/1024./(t2-t1));
	    Info (msg);
	}
	if (status != STATUS_OK)
	    break;
	hlist.subwidget ("hlist selection clear", q);
	p = 0;
    }
    bx.subwidget("cancel", "configure -text Dismiss");
    prevent_removable (fd, 0);
    hlist.configure ("-cursor {}");
    enable ();
    copying = 0;
    free (s);
    CLOSE (fd);
    end_info ();
}
