/* copyright 	Maher Awamy <muhri@muhri.net>		
 * Skipdownload, a front end to wget
 *  released under the GPL				
 */


#include "skipdownload.h"
#include "intl.h"

GtkWidget *clist = NULL;
GSList *file_list = NULL;
gchar *home = NULL;

static void select_location(gchar *url, gint type)
{
     	DownloadData *data;
     	data = g_new0(DownloadData,1);     	      	
	data->type = type;     	
     	data->url = g_strdup(url);
     	
     	data->fs = gtk_file_selection_new(_("Select Directory or File"));
     	
     	g_signal_connect(G_OBJECT(data->fs), "destroy", G_CALLBACK(destroy_fs), data);
     	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(data->fs)->cancel_button), "clicked", G_CALLBACK(cancel_fs), data);
     	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(data->fs)->ok_button), "clicked", G_CALLBACK(get_location), data);
     	gtk_widget_show(data->fs);
     	return;
}

static void destroy_fs(GtkWidget *fs, DownloadData *data)
{
     	if (file_list == NULL) {
	     	g_free(data->url);
	     	g_free(data);
	     	quit_download();
	}
     	return;
}

static void cancel_fs(GtkWidget *button, DownloadData *data)
{
     	g_free(data->url);
     	gtk_widget_destroy(data->fs);
     	g_free(data);
     	return;
}

static void get_location(GtkWidget *button, DownloadData *data)
{
     	G_CONST_RETURN gchar *location;
        gchar *locdir;
     	struct stat buf;
     	
     	location = gtk_file_selection_get_filename(GTK_FILE_SELECTION(data->fs));

		/* strategy:
		 * if the file exists and is a dir, then is_dir should be TRUE
		 * else, that's ok. it must not be a dir. but we make sure that the
		 * parent dir to that path name exists. */

		
		if (stat (location, &buf) == 0 && S_ISDIR(buf.st_mode))
		{
			data->is_dir = TRUE;
		}
		else
		{
			int i;
			data->is_dir = FALSE;
			/* Make sure parent dir exists */
			locdir = strdup (location);
			for (i=strlen(locdir)-1; i >= 0; --i)
				if (locdir[i] == '/')
				{
					locdir[i] = '\0';
					break;
				}

			if (stat (locdir, &buf) == -1)
			{
				/* It doesn't !?!?!? Shame. */
				g_print (_("Sorry, you need to specify a path that does not contain nonexistent directories."));
				return;
			}
		}

     	data->location = g_strdup(location);
	file_list = g_slist_append(file_list, data);

     	gtk_widget_destroy(data->fs);
     	
     	if (data->type == 0) 
     		progress_window(data);
        else 
	     	refresh_list();	
       	return;
}

static void quit_download(void)
{
     	gchar *file;
        file = g_strconcat(home,"/.skipstone/skipdownload_running",NULL);
     	unlink(file);
     	g_free(file);
     	gtk_main_quit();
}


/* Thanks to Boris, stolen from Ferite the next perl :)*/

static gint find_string( gchar *haystack, gchar *needle )
{

	gchar *where = NULL;
     	where = strstr(haystack, needle);
     	
     	if (where != NULL) {
	     	return (where - haystack);
	}

       return -1;
}


/* If there is a line in the gstring ('\n' delimited) copy it to the
|  line buffer with the newline stripped and erase it from the gstring.
*/
static gboolean
getline_from_gstring(GString **gstring, gchar *line, gint size)
{
	gchar	*s;
	gint	len;

	if ((s = strchr((*gstring)->str, '\n')) != NULL)
		{
		if ((len = s - (*gstring)->str) < size)
			{
			strncpy(line, (*gstring)->str, len);
			line[len] = '\0';	/*  Strips the newline */
			*gstring = g_string_erase(*gstring, 0, len + 1);
			return TRUE;
			}
		else
			printf("getline_from_gstring: line buffer is too small.\n");
		}
	return FALSE;
}

static void progress(GtkWidget *bar, GtkWidget *entry, GtkWidget *size_lbl, GtkWidget *bps_lbl, DownloadData *data)
{
     	FILE *pipe;
     	GString *gstring;
     	gchar *line;
     	gint value = 0;
     	gint got_length = 0; /* so we dont call g_strncasecmp for each line */
	struct timeval tv, prev_tv;
     	struct timezone tz;
     	gint n, t, bps, bytes, prev_bytes = -1;
	gchar *buf;
	gchar *cmd;

	if (data->is_dir)
	{
		chdir (data->location);
		cmd = g_strdup_printf("wget -v -c --progress=dot  '%s' 2>&1", 
				      data->url);
	}
	else
		cmd = g_strdup_printf("wget -v -c --progress=dots -O '%s' '%s' 2>&1",
				      data->location, data->url);

     	gtk_entry_set_text(GTK_ENTRY(entry),data->url);
     	pipe = popen(cmd,"r");
     	
     	if (pipe == NULL) {
	  	printf("null output\n");
	  	return;
     	}
    
       	fcntl(fileno(pipe), F_SETFL, O_NONBLOCK);
     	gstring = g_string_new("");
     	
     	line = (gchar *)g_malloc(1024);
    	 
     	while(!feof(pipe)) {
		n = fread(line, 1, 1023, pipe);
		if (n > 0) {
	       		line[n] = '\0';
       			gstring = g_string_append(gstring, line);
		}
		while (getline_from_gstring(&gstring, line, 1024)) {
		     	if (!got_length) {		     
		     		if (!g_strncasecmp(line,"Length:",7) || !g_strncasecmp(line,"[ skipping",10)) {
				     	gtk_label_set_text(GTK_LABEL(size_lbl),line+8);
				     	got_length = 1;
				        continue;
				}
			}
			if (got_length && (n = find_string(line, "%")) > 0) {
       				/* whitespace in format matches 0 or more whitespace in input */
				if (sscanf(line + n -2 , " %d%%", &value) != 1){
	   				     value = -1;
				}
				if (value >= 0 && value <= 100) {
		  			gtk_progress_set_value(GTK_PROGRESS(bar), value);
					gettimeofday(&tv,&tz);
				     	n = sscanf(line,"%dk", &bytes);
				     	if (n != 1)
						n = sscanf(line,"%dK", &bytes);
				     	if (n == 1) {
					     	t = 1000000 * (tv.tv_sec - prev_tv.tv_sec);
					     	t += tv.tv_usec - prev_tv.tv_usec;
					     	t /= 10000;  /* get to .01 sec precision */
						if (t > 0 && prev_bytes >= 0) {
						     	bps = 100000 * (bytes - prev_bytes) / t;
						     	/* printf("t=%d, bytes=%dK bps = %d\n", t,					     //bytes - prev_bytes, bps);*/
	   						buf = format_bps(bps);
							gtk_label_set_text(GTK_LABEL(bps_lbl), buf);
							g_free(buf);     

						}
						prev_bytes = bytes;
					}
				     	prev_tv = tv;
				}
			}
		}
		usleep(50000);
		while(gtk_events_pending()) {
		     gtk_main_iteration();
		}
	}

	g_string_free(gstring, TRUE);
	pclose(pipe);
     	
     	g_free(cmd);
     	g_free(line);
     	g_free(data->url);
     	g_free(data->location);
	file_list = g_slist_remove(file_list,data);
       	g_free(data);	
       	
     	if (g_slist_length(file_list) == 0) {	     
     		quit_download();
       	} else {
     	
     		DownloadData *nextdata = g_slist_nth_data(file_list,0);     	     	
	     	refresh_list();
     		progress(bar,entry,size_lbl,bps_lbl,nextdata);
	}
     	return;
}

/* Thaks Bill */
static gchar *format_bps(gint n)
{
         gchar   *buf;
     
         if (n < 1000)
       		buf = g_strdup_printf("%d Bps", n);
         else if (n < 19950)
       {
	    	n += 50;
	    	buf = g_strdup_printf("%d.%d KBps", n / 1000, (n % 1000) / 100);
       }
         else if (n < 999500)
       {
	    	n += 500;
	    	buf = g_strdup_printf("%d KBps", n / 1000);
       }
         else
       {
	    	n += 50000;
	    	buf = g_strdup_printf("%d.%d MBps",
      		      		    n / 1000000, (n % 1000000) / 100000);
       }
         return buf;
}


static void handle_usr1_signal(int sig) 
{
     
     FILE *url_file;
     gchar *file;
     gchar *url = NULL;
     gchar *line;
     
     file = g_strconcat(home,"/.skipstone/skipdownload_url",NULL);

     url_file = fopen(file,"r");
     if (url_file == NULL) { /* No argument was passed .. */
		return;
     } else {
		line = (gchar *)g_malloc(1024);
	  	while(fgets(line,1024,url_file) != NULL) {
		     line[strlen(line)-1] = '\0';
		     if (g_strncasecmp(line,"url=",4) == 0) {
			  	url = g_strdup(line+4);
		     }
		}
	  	fclose(url_file);
		select_location(url,1);
  		unlink(file);
	  	g_free(url);
     		g_free(line);
     }
     g_free(file);
     return;
}

static void set_running(void)
{
     	FILE *pidfile;
     	gchar *file;
     	gint  my_pid;
     	my_pid = getpid();
     	file = g_strconcat(home,"/.skipstone/skipdownload_running",NULL);
     	pidfile = fopen(file,"w");
     	if (pidfile == NULL) {
	     	printf("could not open pid file\n");
	     	return;
	}
     	fprintf(pidfile,"pid=%d\n",my_pid);
     	fclose(pidfile);
     	g_free(file);
     	return;
}

static void check_running(gchar *url)
{
     	FILE *pidfile;
     	FILE *url_file;
     	gchar *file;
     	gchar *line;
     	gchar *arg;
     	gint  pid = 0;
     	file = g_strconcat(home,"/.skipstone/skipdownload_running",NULL);
     	
     	pidfile = fopen(file, "r");
     	if (pidfile == NULL) {
	  	return; /* no file means we want to start a new process immediately */
     	}
     	line = (gchar *)g_malloc(1024);
     	while(fgets(line,1024,pidfile) != NULL) {
	  	line[strlen(line)-1] = '\0';
	  	if (g_strncasecmp(line,"pid=",4) == 0) {
	       		pid = atoi(line+4);
	  	}
	}
     	fclose(pidfile);
     	if (kill(pid,0) == 0) { /* its alive */
		if (!url) { /* No url, just a new window */
		     	kill(pid,10);
		} else { /* Url, print it to file so we can read it when we launch the new window */
		    	arg = g_strconcat(home,"/.skipstone/skipdownload_url",NULL);
      		     	url_file = fopen(arg,"w");
		     	if (url_file == NULL) {
			     	printf("erm could not open the url arg file for some reason\n");
			     	kill(pid,10);
			} else {
			     	fprintf(url_file,"url=%s\n",url);
				fclose(url_file);			     	
				g_free(arg);
			     	kill(pid,10);
			}
		}

	g_free(line); /* clean up after yourself before you exit this processs */	
	g_free(file);
	exit(0);
	}
     	g_free(line); /* other wise clean up those only */
     	g_free(file);
     	return;
}


static void make_clist(void)
{
	GtkWidget *vbox;
     	GtkWidget *sw;
     	GtkWidget *btnbox;
     	GtkWidget *insert;
     	GtkWidget *remove;
     	GtkWidget *main_win;
     
     	gchar *titles[2];
	
     	titles[0] = _("Url");
     	titles[1] = _("Location");
     
     	main_win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
     	g_signal_connect(G_OBJECT(main_win), "destroy", G_CALLBACK(on_clist_window_destroy), NULL);
     	gtk_window_set_title(GTK_WINDOW(main_win), _("Skipdownload Queue"));
     	gtk_window_set_default_size(GTK_WINDOW(main_win), 600, 0);
     	sw = gtk_scrolled_window_new(NULL,NULL);
     	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);     	
     	clist = gtk_clist_new_with_titles(2,titles);	
	gtk_clist_set_column_width(GTK_CLIST(clist), 0, 400);
     	gtk_container_add(GTK_CONTAINER(sw), clist);

     	vbox = gtk_vbox_new(0,0);
     	
     	gtk_container_add(GTK_CONTAINER(main_win), vbox);
     	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, FALSE);
     	
     	insert = gtk_button_new_from_stock(GTK_STOCK_ADD);
     	remove = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
     	g_signal_connect(G_OBJECT(remove), "clicked", G_CALLBACK(remove_item_from_clist), NULL);
     	g_signal_connect(G_OBJECT(insert), "clicked", G_CALLBACK(insert_in_clist), NULL);
     	btnbox = gtk_hbox_new(0,0);
     	
     	gtk_box_pack_start(GTK_BOX(vbox), btnbox, FALSE, FALSE, 0);
     	gtk_box_pack_start(GTK_BOX(btnbox), insert, TRUE, TRUE, 0);
     	gtk_box_pack_start(GTK_BOX(btnbox), remove, TRUE, TRUE, 0);
     	gtk_widget_show_all(main_win);
     	return;
}

static void on_clist_window_destroy(GtkWidget *window)
{
     	clist = NULL;
     	return;
}

static void insert_in_clist(void)
{
     	GtkWidget *hbox, *hbox2;
     	GtkWidget *lbl, *lbl2;
     	GtkWidget *ok, *cancel, *select;
	InsertData *idata;
	gchar *home_dir = g_strconcat(home,"/",NULL);
       
     	idata = g_new0(InsertData,1);
     
     	idata->dialog = gtk_dialog_new();
     	gtk_window_set_title(GTK_WINDOW(idata->dialog), _("Insert a new url"));
     	lbl = gtk_label_new(_("Url"));
     	lbl2 = gtk_label_new(_("Location"));
     	select = gtk_button_new_with_label(_("Select"));
     	idata->entry = gtk_entry_new();
     	idata->location = gtk_entry_new();
     	hbox = gtk_hbox_new(0,0);
     	hbox2 = gtk_hbox_new(0,0);
     	ok = gtk_button_new_from_stock(GTK_STOCK_OK);
     	cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
     	
     	gtk_entry_set_text(GTK_ENTRY(idata->location), home_dir);
     	
     	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(idata->dialog)->vbox), hbox, FALSE, FALSE, 0);
     	gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 0);
     	gtk_box_pack_start(GTK_BOX(hbox), idata->entry, TRUE, TRUE, 0);
     	
     	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(idata->dialog)->vbox), hbox2, FALSE, FALSE, 0);
     	gtk_box_pack_start(GTK_BOX(hbox2), lbl2, FALSE, FALSE, 0);
     	gtk_box_pack_start(GTK_BOX(hbox2), idata->location, TRUE, TRUE, 0);
     	gtk_box_pack_start(GTK_BOX(hbox2), select, FALSE, FALSE, 0);
     	
     	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(idata->dialog)->action_area), ok, TRUE, TRUE, 0);
     	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(idata->dialog)->action_area), cancel, TRUE, TRUE, 0);
	
     	g_signal_connect(G_OBJECT(idata->dialog), "destroy", G_CALLBACK(on_insert_destroy), idata);
 	g_signal_connect(G_OBJECT(cancel), "clicked", G_CALLBACK(on_insert_cancel), idata);
     	g_signal_connect(G_OBJECT(ok), "clicked", G_CALLBACK(on_insert_ok), idata);
     	g_signal_connect(G_OBJECT(select), "clicked", G_CALLBACK(on_select_clicked), idata);
     	gtk_widget_show_all(idata->dialog);
	
     	g_free(home_dir);
     	return;
}

static void on_select_clicked(GtkWidget *button, InsertData *idata)
{
     	
     	idata->fs = gtk_file_selection_new(_("Select Directory"));
     	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(idata->fs)->cancel_button), "clicked", G_CALLBACK(close_fs_from_insert), idata);
	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(idata->fs)->ok_button), "clicked", G_CALLBACK(ok_from_insert_fs), idata);
     	gtk_widget_show_all(idata->fs);
     	return;
}

static void ok_from_insert_fs(GtkWidget *button, InsertData *idata)
{
     	G_CONST_RETURN gchar *filename = gtk_file_selection_get_filename(GTK_FILE_SELECTION(idata->fs));
     	if (filename) {
	     	gtk_entry_set_text(GTK_ENTRY(idata->location), filename);
	     	gtk_widget_destroy(idata->fs);
	}
     	return;
}

static void close_fs_from_insert(GtkWidget *button, InsertData *idata)
{
     	gtk_widget_destroy(idata->fs);
     	return;
}
     
static void on_insert_ok(GtkWidget *button, InsertData *idata)
{
     	DownloadData *data;
       	
     	G_CONST_RETURN gchar *url = gtk_entry_get_text(GTK_ENTRY(idata->entry));
     	G_CONST_RETURN gchar *location = gtk_entry_get_text(GTK_ENTRY(idata->location));
     	if (url && strcmp(url,"") != 0) {
	     	data = g_new0(DownloadData,1);
	     	data->url = g_strdup(url);
	     	data->location = g_strdup(location);
	     	file_list = g_slist_append(file_list, data);
	     	refresh_list();
	     	gtk_widget_destroy(idata->dialog);
	}
     	return;
}
					     

static void on_insert_destroy(GtkWidget *dialog, InsertData *idata)
{
     	g_free(idata);
     	return;
}

static void on_insert_cancel(GtkWidget *button, InsertData *idata)
{
     	gtk_widget_destroy(idata->dialog);
     	return;
}

static void remove_item_from_clist(void)
{
     	GList *selection = GTK_CLIST(clist)->selection;
     	gint row;
     	DownloadData *data;
     
     	if (selection == NULL) {
		return;
	}
     	
     	row = (gint) g_list_nth_data(selection,0);
     	data = gtk_clist_get_row_data(GTK_CLIST(clist),row);
     	gtk_clist_remove(GTK_CLIST(clist), row);
     	file_list = g_slist_remove(file_list,data);
     	g_free(data->url);
     	g_free(data->location);
     	g_free(data);
     	return;
}
     

static void progress_window(DownloadData *data)
{
     	
	GtkWidget *win;
     	GtkWidget *bar;
     	GtkWidget *vbox;
     	GtkWidget *entry;
     	GtkObject *adj;	
     	GtkWidget *size_lbl;
     	GtkWidget *bps_lbl;
     
     	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
     	gtk_container_set_border_width(GTK_CONTAINER(win), 15);
     	gtk_window_set_title(GTK_WINDOW(win), _("SkipDownload"));
 	adj = gtk_adjustment_new(0,1,100,0,0,0);
     	bar = gtk_progress_bar_new_with_adjustment(GTK_ADJUSTMENT(adj));
	size_lbl = gtk_label_new("");
     	entry = gtk_entry_new();
	bps_lbl = gtk_label_new("");
     	gtk_entry_set_editable(GTK_ENTRY(entry),FALSE);
	gtk_widget_set_usize(entry,300,0);
	gtk_entry_set_text(GTK_ENTRY(entry),_("Preparing ..."));
     	vbox = gtk_vbox_new(0,0);

     	gtk_progress_set_show_text(GTK_PROGRESS(bar), TRUE);
     	gtk_progress_set_format_string(GTK_PROGRESS(bar), "%p%%");
     	gtk_container_add(GTK_CONTAINER(win), vbox);
     	gtk_box_pack_start(GTK_BOX(vbox), size_lbl, FALSE, FALSE, 0);
     	gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, TRUE, 10);
     	gtk_box_pack_start(GTK_BOX(vbox), bps_lbl, FALSE, FALSE, 0);
     	gtk_box_pack_start(GTK_BOX(vbox), bar, TRUE, TRUE, 10);
	gtk_widget_show_all(win);
     
     	refresh_list();
     	progress(bar,entry,size_lbl,bps_lbl,data);
	
     	return;
}

static void refresh_list(void)
{
	GSList *l;
     	gint row;
     
     	if (clist == NULL) {
	     	make_clist();
	}
     
     	gtk_clist_clear(GTK_CLIST(clist));
     	for (l = file_list; l; l = l->next) {
	     	DownloadData *data = l->data;		
		gchar *temp[] = { data->url, data->location };
	     	row = gtk_clist_append(GTK_CLIST(clist), temp);
		gtk_clist_set_row_data(GTK_CLIST(clist),row,data);
	}
     	return;
}

gint main(gint argc, gchar *argv[]) {	
     
     	if (argc < 2) {
	     	g_print(_("Usage: skipdownload url\n"));
	     	exit(0);
	}
	home = getenv("HOME");       	
     	check_running(argv[1]);
     	set_running();
     	
     	signal(SIGUSR1, handle_usr1_signal);
	gtk_set_locale();
     	gtk_init(&argc,&argv);     
	select_location(argv[1],0);     
     	gtk_main();
     	return 0;
}
