/*
** Amster - Download
** Copyright © 1999-2000 by Gürer Özen
** Copyright © 2000-2001 by Jacob Laursen
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "amster.h"

#include <proto/dos.h>
#include <proto/asl.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/asl.h>

#include "network.h"

#include <MUI/NListview_mcc.h>
#include <MUI/NFloattext_mcc.h>
#include "md5.h"
#include "amster_Cat.h"


int dl_count = 0;
int QueueCount = 0;
int WaitingCount = 0;

/* Private functions */

ULONG dl_new(struct IClass *cl, Object *obj, struct opSet *msg);
void dl_startq(struct TransferData *data, char *title, char *user, u_long ip, int port);
void dl_startqi(struct TransferData *data, long key);
void dl_checkqueue(struct TransferData *data);
void DownloadRetry(struct TransferData *data, Object *obj, char *title, char *user, int limit);
void PollWaiting(struct TransferData *data, Object *obj);
int dl_filename(songtrans sd);
char *chkmd5_fromlock(BPTR lock);
int dl_askfname(char *fname);
void dl_handlemsg(thread t, int com, APTR data);
void dl_resume(struct TransferData *data);
void dl_cps(struct TransferData *data);
THREAD_DECL(dl_sucker);


MUI_DISPATCH(dl_dispatch)
{
	struct TransferData *data;

	switch(msg->MethodID) {
		case OM_NEW:
			return(dl_new(cl, obj, (APTR)msg));
		case MUIM_Window_Setup:
			return(dl_setup(cl, obj, (APTR)msg));
		case MUIM_Window_Cleanup:
			return(dl_muicleanup(cl, obj, (APTR)msg));
		case DL_ADD:
			data = INST_DATA(cl, obj);
			DoMethod(data->list, MUIM_NList_InsertSingle, (songtrans)(((muimsg)msg)->arg1), MUIV_NList_Insert_Bottom);
			return(NULL);
		case DL_START:
			data = INST_DATA(cl, obj);
			dl_startq(data, (char *)(((muimsg)msg)->arg1), (char *)(((muimsg)msg)->arg2), (u_long)(((muimsg)msg)->arg3), (int)(((muimsg)msg)->arg4));
			return(NULL);
		case DL_STARTINCOMING:
			data = INST_DATA(cl, obj);
			dl_startqi(data, (long)(((muimsg)msg)->arg1));
			return(NULL);
		case DL_UPDATE:
			{
			long pos = MUIV_NList_GetPos_Start;
			data = INST_DATA(cl, obj);
			DoMethod(data->list, MUIM_NList_GetPos, (((muimsg)msg)->arg1), &pos);
			DoMethod(data->list, MUIM_NList_Redraw, pos);
			return(NULL);
			}
		case DL_CPS:
			data = INST_DATA(cl, obj);
			dl_cps(data);
			return(NULL);
		case DL_CLEANUP:
			data = INST_DATA(cl, obj);
			TransferCleanup(data);
			return(NULL);
		case DL_CLEANUP_SINGLE:
			data = INST_DATA(cl, obj);
			TransferCleanupSingle(data, (songtrans)(((muimsg)msg)->arg1));
			return NULL;
		case DL_ABORT:
			data = INST_DATA(cl, obj);
			TransferAbort(data);
			return(NULL);
		case DL_RESUME:
			data = INST_DATA(cl, obj);
			dl_resume(data);
			return(NULL);
		case DL_INFO:
			data = INST_DATA(cl, obj);
			TransferInfo(data);
			return(NULL);
		case DL_RESUME_UPDATE:
			{
			songtrans st;
			data = INST_DATA(cl, obj);

			DoMethod(data->list, MUIM_NList_GetEntry, MUIV_NList_GetEntry_Active, &st);
			if (st) {
				if (st->state >= DLS_ABORT) set(data->BT_Resume, MUIA_Disabled, FALSE);
			}

			return(NULL);
			}
		case DL_PLAY:
			{
			songtrans st;
			data = INST_DATA(cl, obj);

			DoMethod(data->list, MUIM_NList_GetEntry, MUIV_NList_GetEntry_Active, &st);
			if (!st) return(NULL);
			if (st->state < DLS_DOWN || !(st->fname)) return(NULL);
			prf_event((enum Event)(((muimsg)msg)->arg1), st->fname);

			return(NULL);
			}
		case DL_SETERROR:
			data = INST_DATA(cl, obj);
			TransferSetError(data, (char *)(((muimsg)msg)->arg1), (char *)(((muimsg)msg)->arg2), (int)(((muimsg)msg)->arg3));
			return(NULL);
		case DL_RETRY:
			data = INST_DATA(cl, obj);
			DownloadRetry(data, obj, (char *)(((muimsg)msg)->arg1), (char *)(((muimsg)msg)->arg2), (int)(((muimsg)msg)->arg3));
			return(NULL);
		case DL_POLLWAIT:
			data = INST_DATA(cl, obj);
			PollWaiting(data, obj);
			return(NULL);
		case DL_REMWAITING:
			data = INST_DATA(cl, obj);
			if (((songtrans)(((muimsg)msg)->arg1))->state == DLS_WAIT) {
				if (--WaitingCount == 0) DoMethod(_app(obj), MUIM_Application_RemInputHandler, &data->waitnode);
				/* This was the last waiting entry - remove input handler */
#ifdef AMSTER_DEBUG
gui_debugf("removed waiting - dl_count: %d, QueueCount: %d, waiting: %d", dl_count, QueueCount, WaitingCount);
#endif
			}
			return(NULL);
		case DL_SETDELAY:
			data = INST_DATA(cl, obj);
			data->waitnode.ihn_Millis = (int)(((muimsg)msg)->arg1)*1000;
			return(NULL);
		case DL_WATCHER:
			data = INST_DATA(cl, obj);
			TransferWatcher(data);
			return(NULL);
		case DL_COUNTDECREMENT:
			data = INST_DATA(cl, obj);
			if (--dl_count == 0) DoMethod(_app(obj), MUIM_Application_RemInputHandler, &data->watchnode);
			dl_checkqueue(data);
			return(NULL);
		case DL_COUNTINCREMENT:
			data = INST_DATA(cl, obj);
			if (dl_count++ == 0) DoMethod(_app(obj), MUIM_Application_AddInputHandler, &data->watchnode);
			return(NULL);
		case DL_CHECKQUEUE:
			data = INST_DATA(cl, obj);
			dl_checkqueue(data);
			return(NULL);
	}
	return(DoSuperMethodA(cl, obj, msg));
}


ULONG dl_new(struct IClass *cl, Object *obj, struct opSet *msg)
{
	static struct Hook downlistdispHook = { {0,0}, &translistdisp, NULL, NULL };
	struct TransferData *data;
	Object *list, *info, *playbut, *abortbut, *resbut, *cleanbut;

	if (obj = (Object *)DoSuperNew(cl, obj,
		MUIA_HelpNode, "download",
		MUIA_Window_Title, MSG_DL_TITLE,
		MUIA_Window_ID, MAKE_ID('D','O','W','N'),
		WindowContents, VGroup,
			Child, list = NListviewObject,
				MUIA_NListview_NList, NListObject,
					InputListFrame,
					MUIA_Font, MUIV_Font_Tiny,
					MUIA_NList_Title, TRUE,
					MUIA_NList_Format, "BAR, BAR, BAR, BAR, BAR",
					MUIA_NList_DisplayHook, &downlistdispHook,
					MUIA_NList_DragSortable, TRUE,
					MUIA_CycleChain, 1,
				End,
			End,
			Child, info = TextObject,
				TextFrame,
				MUIA_Background, MUII_TextBack,
				MUIA_Text_PreParse, "\33c",
			End,
			Child, HGroup,
				Child, HGroup,
					Child, playbut = SimpleButton(MSG_DL_PLAY_GAD),
					MUIA_ShortHelp, MSG_PLAY_HELP,
				End,
				Child, HSpace(4),
				Child, HGroup,
					Child, abortbut = SimpleButton(MSG_DL_ABORT_GAD),
					MUIA_ShortHelp, MSG_ABORT_HELP,
				End,
				Child, HGroup,
					Child, resbut = SimpleButton(MSG_DL_RESUME_GAD),
					MUIA_ShortHelp, MSG_RESUME_HELP,
				End,
				Child, HSpace(4),
				Child, HGroup,
					Child, cleanbut = SimpleButton(MSG_DL_CLEANUP_GAD),
					MUIA_ShortHelp, MSG_CLEANUP_HELP,
				End,
			End,
		End,
		TAG_MORE, msg->ops_AttrList))
	{
		data = INST_DATA(cl,obj);
		data->list = list;
		data->info = info;
		data->BT_Resume = resbut;

		data->ihnode.ihn_Object = obj;
		data->ihnode.ihn_Millis = 1000;
		data->ihnode.ihn_Method = DL_CPS;
		data->ihnode.ihn_Flags  = MUIIHNF_TIMER;

		data->waitnode.ihn_Object = obj;
		data->waitnode.ihn_Millis = prf->QueueDelay*1000;	/* Poll interval in milliseconds */
		data->waitnode.ihn_Method = DL_POLLWAIT;
		data->waitnode.ihn_Flags  = MUIIHNF_TIMER;

		data->watchnode.ihn_Object = obj;
		data->watchnode.ihn_Millis = 10000;
		data->watchnode.ihn_Method = DL_WATCHER;
		data->watchnode.ihn_Flags  = MUIIHNF_TIMER;

		DoMethod(playbut,  MUIM_Notify, MUIA_Pressed, FALSE, obj, 2, DL_PLAY, PRFE_PLAYMP3 );
		DoMethod(resbut,   MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, DL_RESUME );
		DoMethod(abortbut, MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, DL_ABORT  );
		DoMethod(cleanbut, MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, DL_CLEANUP);

		DoMethod(list, MUIM_Notify, MUIA_NList_EntryClick,  MUIV_EveryTime, obj, 1, DL_INFO);
		DoMethod(list, MUIM_Notify, MUIA_NList_DoubleClick, MUIV_EveryTime, obj, 2, DL_PLAY, PRFE_DL_DCLICK);

		DoMethod(obj, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, gui->iconpanel, 1, PANEL_CLOSEDL);

		return((ULONG)obj);
	}

	return(0);
}


void dl_addq(song s)
/* This is the initial function, called from resultview.c */
{
	songtrans sd;

#ifdef AMSTER_DEBUG
gui_debug("dl_addq()");
#endif
	sd = malloc(sizeof(_songtrans));
	if (!sd) return;
	memset(sd, 0, sizeof(_songtrans));

	sd->song = nap_songdup(s);
	if (!sd->song) {
		free(sd);
		return;
	}

	sd->size = s->size;
	sd->mynick = prf->user;
	sd->type = TYPE_DOWNLOAD_OUT;
	sd->reqtime = GetDateStamp();

	if (dl_count >= prf->DownloadQueueLimit && prf->DownloadQueueLimit < 26) {
		sd->state = DLS_QUEUE;
		QueueCount++;
	}
	else {
		sprintf(nap_buf, "\"%s\" \"%s\"", s->user, s->title);
		nap_send(NAPC_FILEINFOREQ);
		sd->state = DLS_PREP;
		DoMethod(gui->dwin, DL_COUNTINCREMENT);
	}

#ifdef AMSTER_DEBUG
gui_debugf("new download count: %d (concurrent setting: %d), queue count: %d", dl_count, prf->DownloadQueueLimit, QueueCount);
#endif

	DoMethod(gui->dwin, DL_ADD, sd);
}


void dl_startq(struct TransferData *data, char *title, char *user, u_long ip, int port)
/* This is the initial function, when the actual download is about to
   take place (acknowledged by server) - called from napster.c (via DL_START) */
{
	u_long tmp;
	songtrans sd;
	long i;

#ifdef AMSTER_DEBUG
gui_debugf("port : %d (file: %s, user: %s)", port, title, user);
#endif

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &tmp);
		if (!tmp) return;
		sd = (songtrans)tmp;
		if ((sd->state == DLS_PREP || sd->state == DLS_WAIT || sd->state == DLS_QUEUE) && stricmp(user,sd->song->user)==0 && strcmp(title,sd->song->title)==0) break;
	}

	if (sd->t) {
#ifdef AMSTER_DEBUG
gui_debug("this shouldn't happen (1)!");
#endif
		return;	/* Already in a thread */
	}

	if (!dl_filename(sd)) {
		if (sd->state == DLS_WAIT) DoMethod(gui->dwin, DL_REMWAITING, sd);
		else if (sd->state == DLS_QUEUE) QueueCount--;
		else {
			sd->state = DLS_ABORT;
			DoMethod(gui->dwin, DL_COUNTDECREMENT);
		}
		sd->state = DLS_ABORT;
		DoMethod(gui->dwin, DL_UPDATE, sd);
		return;
	}

	prf_event(PRFE_DLSTART, sd->fname);

#ifdef AMSTER_DEBUG
gui_debugf("ack [%s] - IP: %ld", title, ip);
#endif

	sd->song->ip = ip;	/* In case of browse result, the IP isn't already there */

	sd->ip = ip;
	sd->port = port;
	sd->s = -1;
	sd->oldsize = sd->cur;

	if (sd->state == DLS_WAIT) DoMethod(gui->dwin, DL_REMWAITING, sd);

/*
	if (dl_count >= prf->DownloadQueueLimit && prf->DownloadQueueLimit < 26) return;
*/
	/* Simply do not spawn the thread if the download is queued. This shouldn't
	   be possible though. */

	if (sd->state == DLS_QUEUE) {
		QueueCount--;
		DoMethod(gui->dwin, DL_COUNTINCREMENT);
	}
	else if (sd->state == DLS_WAIT) {
		DoMethod(gui->dwin, DL_COUNTINCREMENT);
	}

	sd->state = DLS_PREP;

	if (port == 0) {
		/* The other user is behind firewall, so we'll wait for him to make the connection.
		   After that we spawn the thread. */
		sd->type = TYPE_DOWNLOAD_IN;
		gui_debugf("User %s is behind firewall, so we send alternate download request.", user);
		sprintf(nap_buf, "%s \"%s\"", user, title);
		nap_send(NAPC_ALTDLREQ);
	}
	else {
		sd->t = th_spawn(dl_handlemsg, "Amster downloader", &dl_sucker, prf->DownloadTaskPri, sd);
		if (!sd->t) {
			sd->state = DLS_ERROR;
			DoMethod(gui->dwin, DL_UPDATE, sd);
		}
	}
}


void dl_startqi(struct TransferData *data, long key)
/* This is the initial function, when a firewalled download is about to
   take place (acknowledged by other client) - called from napster.c (via DL_STARTINCOMING) */
{
	u_long tmp;
	long len;
	long s;
	songtrans sd;
	long i;
	char *user, *title;
	int size;
	char *buffer;
	char *test;

	buffer = malloc(512);
	test = buffer;
	/* Add error handling */

len = sprintf(buffer, "%ld", 0);
gui_debugf("result: %d, buffer(addr: %x/len: %d): %s", len, &buffer, strlen(buffer), buffer);

	s = ObtainSocket(key, AF_INET, SOCK_STREAM, 0);

gui_debugf("dl_startqi (socket: %ld)", s);

	/* Make sure we have bsdsocket.library open etc. */

	len = recv(s, buffer, 511, 0);
	if (len < 1) {
		/* Clean up here! */
gui_debugf("net error: %ld/%ld", len, Errno());
		CloseSocket(s);
		free(buffer);
        DoMethod(gui->dwin, DL_COUNTDECREMENT);
		return;
	}
	buffer[len] = '\0';

gui_debugf("from listener thread (len: %ld): %s\n", len, buffer);

	user = nap_token(&buffer);
	title = nap_token(&buffer);
	size = nap_ltoken(&buffer);

gui_debugf("parsed: %s/%s/%ld", user, title, size);

free(buffer);
buffer=malloc(512);

/*
len = sprintf(buffer, "%ld", 0);
gui_debugf("result: %d, buffer(addr: %x/len: %d): %s", len, &buffer, strlen(buffer), buffer);
*/

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &tmp);
		if (!tmp) {
			/* "INVALID REQUEST" etc. + cleanup here */
			CloseSocket(s);
			free(buffer);
			return;
		}
		sd = (songtrans)tmp;
		if ((sd->state == DLS_PREP) && stricmp(user,sd->song->user)==0 && strcmp(title,sd->song->title)==0) break;
	}

gui_debugf("found! - cur: %ld", sd->cur);

	len = sprintf(buffer, "%ld", sd->cur);
gui_debugf("result: %d, buffer(addr: %x/len: %d): %s", len, &buffer, strlen(buffer), buffer);

	len = send(s, buffer, strlen(buffer), 0);
	if (len < 1) {
gui_debugf("result: %d/%d", len, Errno());
		CloseSocket(s);
		free(buffer);
		sd->state = DLS_ERROR;
		sd->error = ERROR_UNKNOWN;
		DoMethod(gui->dwin, DL_UPDATE, sd);
        DoMethod(gui->dwin, DL_COUNTDECREMENT);
		return;
	}

	free(buffer);

	if (sd->t) {
gui_debug("this shouldn't happen (1)!");
		CloseSocket(s);
		sd->state = DLS_ERROR;
		DoMethod(gui->dwin, DL_UPDATE, sd);
        DoMethod(gui->dwin, DL_COUNTDECREMENT);
		return;	/* Already in a thread */
	}

	/* Spawn thread etc. */

	sd->s = ReleaseSocket(s, GetUniqueID());
	/* This is NOT the socket descriptor, but a key we can use to get it in the download thread */

	sd->t = th_spawn(dl_handlemsg, "Amster downloader (firewall)", &dl_sucker, prf->DownloadTaskPri, sd);
	if (!sd->t) {
		sd->state = DLS_ERROR;
		DoMethod(gui->dwin, DL_UPDATE, sd);
        DoMethod(gui->dwin, DL_COUNTDECREMENT);
	}
}


void dl_checkqueue(struct TransferData *data)
{
	u_long tmp;
	songtrans sd;
	long i;

	if (dl_count >= prf->DownloadQueueLimit && prf->DownloadQueueLimit < 26) return;

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &tmp);
		if (!tmp) return;
		sd = (songtrans)tmp;
		if (sd->state == DLS_QUEUE) break;
	}

	sd->reqtime = GetDateStamp();	/* Reset time to avoid watcher timeouts */

	sprintf(nap_buf, "\"%s\" \"%s\"", sd->song->user, sd->song->title);
	nap_send(NAPC_FILEINFOREQ);
	DoMethod(gui->dwin, DL_COUNTINCREMENT);
	sd->state = DLS_PREP;
	QueueCount--;

#ifdef AMSTER_DEBUG
gui_debugf("queue spawn - new dl_count: %d/QueueCount: %d (%s)", dl_count, QueueCount, sd->song->title);
#endif

	DoMethod(gui->dwin, DL_UPDATE, sd);
}


void DownloadRetry(struct TransferData *data, Object *obj, char *title, char *user, int limit)
/* Called from napster.c when a file cannot be downloaded yet (busy) */
{
	u_long tmp;
	songtrans sd;
	long i;

#ifdef AMSTER_DEBUG
gui_debugf("queue full (server rejection): %s", title);
#endif
	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &tmp);
		if (!tmp) return;
		sd = (songtrans)tmp;
		if (sd->state == DLS_PREP && strcmp(sd->song->title, title) == 0 && stricmp(sd->song->user, user) == 0) break;
	}

	if (sd->RetryCount >= prf->QueueRetries) return;	/* We've already given up */
	else if (limit == 0) {
		sd->state = DLS_ERROR;
		sd->error = ERROR_TEASER;	/* Queue limit is 0 = file can't be downloaded by anyone */

		DoMethod(gui->dwin, DL_COUNTDECREMENT);
	}
	else {
		sd->state = DLS_WAIT;
		sd->ErrorCode = limit;
		WaitingCount++;

		if (WaitingCount == 1) DoMethod(_app(obj), MUIM_Application_AddInputHandler, &data->waitnode);
		/* This is the first waiting entry - add input handler */

		DoMethod(gui->dwin, DL_COUNTDECREMENT);
		/* We consider this queued and let other downloads start */
	}

	DoMethod(gui->dwin, DL_UPDATE, sd);
}


void PollWaiting(struct TransferData *data, Object *obj)
{
	u_long tmp;
	songtrans sd;
	long i;

#ifdef AMSTER_DEBUG
gui_debug("PollWaiting()");
#endif

	if (dl_count >= prf->DownloadQueueLimit && prf->DownloadQueueLimit < 26) {
#ifdef AMSTER_DEBUG
gui_debug("- cancelled due to local download count limit!");
#endif
		return;
	}

	if (WaitingCount == 0) return;	/* Don't waste time! */

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &tmp);
		if (!tmp) return;
		sd = (songtrans)tmp;
		if (sd->state == DLS_WAIT && QueueCount == 0) {
			if (sd->RetryCount < prf->QueueRetries) {
#ifdef AMSTER_DEBUG
gui_debugf("Repeating request for '%s'", sd->song->title);
#endif
				sprintf(nap_buf,"\"%s\" \"%s\"", sd->song->user, sd->song->title);
				nap_send(NAPC_FILEINFOREQ);
				sd->RetryCount++;
			}
			else {	/* We give up! */
				sd->state = DLS_ERROR;
				sd->error = ERROR_BUSY;
				DoMethod(gui->dwin, DL_REMWAITING, sd);
				DoMethod(gui->dwin, DL_UPDATE, sd);
			}
		}
	}
}


void dl_resume(struct TransferData *data)
{
	u_long item;
	songtrans sd;

#ifdef AMSTER_DEBUG
gui_debug("dl_resume()");
#endif
	DoMethod(data->list, MUIM_NList_GetEntry, MUIV_NList_GetEntry_Active, &item);
	if (!item) return;

	sd = (songtrans)item;
	if (sd->state < DLS_ABORT) return;	/* Can't resume files that's already in progress */
#ifdef AMSTER_DEBUG
gui_debug("1");
#endif
	if (sd->t) return;
#ifdef AMSTER_DEBUG
gui_debug("2");
#endif

	if (dl_count >= prf->DownloadQueueLimit && prf->DownloadQueueLimit < 26) {
		sd->state = DLS_QUEUE;
		QueueCount++;
	}
	else {
#ifdef AMSTER_DEBUG
gui_debug("3");
#endif
		sd->reqtime = GetDateStamp();
		sprintf(nap_buf, "\"%s\" \"%s\"", sd->song->user, sd->song->title);
		nap_send(NAPC_FILEINFOREQ);
		sd->state = DLS_PREP;
		DoMethod(gui->dwin, DL_COUNTINCREMENT);
	}

#ifdef AMSTER_DEBUG
gui_debugf("download count (resume): %d", dl_count);
#endif

	set(data->BT_Resume, MUIA_Disabled, TRUE);
	DoMethod(gui->dwin, DL_UPDATE, sd);
}


int dl_filename(songtrans sd)
{
	BPTR lock;
	ALIGNED struct FileInfoBlock fib;
	char md5[80] = "";
	char *fname, temp[108];
	long oldsize=0;
	int way, reqrc;
	char *expl, *choice;

	fname = malloc(512);
	if (!fname) return 0;
	strcpy(fname, prf->dlpath);
	strncpy(temp, nap_strippath(sd->song->title), 107);
	temp[107] = '\0';
	AddPart(fname, temp, 511);

	if (prf->askfile) {
		if (!dl_askfname(fname)) return 0;
	}
	else {	/* Needs optimization and elegance */
		if (strlen(nap_strippath(sd->song->title)) > prf->NameLength) {
			strcpy(fname+strlen(fname)-strlen(nap_strippath(sd->song->title))-4+prf->NameLength, fname+strlen(fname)-4);
		}
	}

	lock = Lock(fname, ACCESS_READ);
	if (!lock) {
		sd->fname = fname;
		sd->cur = 0;
		return(1);
	}

	if (Examine(lock, &fib)) {
		oldsize = fib.fib_Size;
		sscanf(fib.fib_Comment, "md5: %[^;]", &md5);
		if (md5[0] == 0 && oldsize >= 300032) strcpy(md5, chkmd5_fromlock(lock));
			else UnLock(lock);
	}
	else UnLock(lock);

	if (sd->size <= oldsize) {
		way = 1;
		expl = (char *)MSG_DL_RESBIGSIZE;
		choice = (char *)MSG_DL_OVERCANCEL_GAD;
	}
	else if (md5[0] == 0) {
		way = 0;
		expl = (char *)MSG_DL_NOMD5INFO;
		choice = (char *)MSG_DL_RESUMECANCEL_GAD;
	}
	else if (strcmp(md5,sd->song->md5)!=0) {
		way = 1;
		expl = (char *)MSG_DL_NOMATCH;
		choice = (char *)MSG_DL_OVERCANCEL_GAD;
	}
	else {
		way = 2;
		expl = (char *)MSG_DL_RESMATCH;
		choice = (char *)MSG_DL_RESUMEOVER_GAD;
	}

	reqrc = MUI_Request(gui->app, gui->win, 0L,
	                    (char *)MSG_DL_RESUME_INFO,
	                    choice,
	                    (char *)MSG_DL_RESUME_MSG,
	                    fname,
	                    oldsize,
	                    sd->size,
	                    md5[0]==0 ? (char*)MSG_DL_NOMD5 : md5,
	                    sd->song->md5,
	                    expl);

	if (reqrc == 0) return(0);

	if ((way==0 || way==2) && reqrc == 1) {
		sd->fname = fname;
		sd->cur = oldsize;
		return(1);
	}

	if (reqrc == 2) {
		if (dl_askfname(fname)) {
			sd->fname = fname;
			sd->cur = 0;
			return(1);
		}
		else return(0);
	}

	sd->cur = 0;
	sd->fname = fname;
	return(1);
}


char *chkmd5_fromlock(BPTR lock)
{
    APTR buffer;
    BPTR fh;
    int len;
    char md5[33] = "";

    md5_state_t state;
    md5_byte_t digest[16];
    int di;

    md5_init(&state);

    if (fh = OpenFromLock(lock)) {
        if (buffer = AllocMem(300032, MEMF_ANY)) {

            len = Read(fh, buffer, 300032);
            if (len > 0) {
                md5_append(&state, (const md5_byte_t *)buffer, len);
                md5_finish(&state, digest);
                for (di = 0; di < 16; ++di)
                    sprintf(md5+di*2, "%02x", digest[di]);
            }
            FreeMem(buffer, 300032);
        }
        Close(fh);
    }
    else UnLock(lock);

    return md5;
}


int dl_askfname(char *fname)
{
	struct FileRequester *freq;
	u_long win;

	freq = AllocAslRequestTags(ASL_FileRequest, TAG_DONE);
	if (!freq) return(0);

	get(gui->dwin, MUIA_Window_Window, &win);
	if (AslRequestTags(freq,
					ASLFR_Window, win,
					ASLFR_TitleText, MSG_DL_SELECTFILE,
					ASLFR_InitialFile, nap_strippath(fname),
					ASLFR_InitialDrawer, prf->dlpath,
					ASLFR_DoSaveMode, TRUE,
					TAG_DONE)) {
		strcpy(fname, freq->fr_Drawer);
		AddPart(fname, freq->fr_File, 511);
		FreeAslRequest(freq);
		return(1);
	}
	else {
		FreeAslRequest(freq);
		return(0);
	}
}


void dl_handlemsg(thread t, int com, APTR data)
/* This is the function that receives messages from the download
   thread. It's called when something needs to be done in the
   main thread (i.e. updating the window). */
{
	songtrans sd=(songtrans)t->data;

	switch (com) {
		case THC_STARTUP:
#ifdef AMSTER_DEBUG
gui_debug("thread start");
#endif
			sd->ts = 1;
			nap_sendbuf(NAPC_DLINC, "");
			break;
		case THC_EXIT:
#ifdef AMSTER_DEBUG
gui_debugf("thread exit = %ld, file: %s", data, sd->fname);
#endif
			sd->error = (int)data;
			TransferHandleError(sd);
			sd->ts = 0;
			sd->t = NULL;
			DoMethod(gui->dwin, DL_COUNTDECREMENT);
			nap_sendbuf(NAPC_DLCOMPLETE, "");
			if (sd->state == DLS_FIN && (prf->AutoCleanup == 1 || prf->AutoCleanup == 3)) {
				DoMethod(gui->dwin, DL_CLEANUP_SINGLE, sd);
				break;
			}
			break;
		case DLC_STATE:
			sd->state = (int)data;
		case DLC_UPDATE:
			DoMethod(gui->dwin, DL_UPDATE, sd);
			break;
		case DLC_ADDSHARE:
			DoMethod(gui->shwin, SHARE_ADDFILE, sd->song, sd->fname);
			break;
#ifdef AMSTER_DEBUG
		default:
gui_debugf("thread c: %d, d: %ld", com, data);
#endif
	}
}


void dl_cps(struct TransferData *data)
{
	songtrans sd;
	u_long item;
	int i;

	for (i=0; ; i++) {
		DoMethod(data->list, MUIM_NList_GetEntry, i, &item);
		if (!item) return;
		sd = (songtrans)item;
		if (sd->state == DLS_DOWN) {
			CalculateCps(sd);
			DoMethod(gui->dwin, DL_UPDATE, sd);
		}
	}
}


/* thread code */

THREAD(dl_sucker)
{
	thread t;
	songtrans sd;
	struct Library *DOSBase;
	struct Library *SocketBase;
	char *buffer;
	long tmp;
	long s;
	thmsg m;
	char cmnt[80];
	int count;

	t = thr_init();
	if (!t) return;
	sd = t->data;

#ifdef AMSTER_DEBUG
gui_debug("blah-1");
#endif
	if (!InitTransferThread(t, sd)) return;
#ifdef AMSTER_DEBUG
gui_debug("blah-2");
#endif
	sd->RetryCount = 0;
	s = sd->s;
	buffer = sd->buffer;
	DOSBase = sd->DOSBase;
	SocketBase = sd->SocketBase;

	while (1) {
		u_long sigs;

		sigs = Wait(sd->nsigm | sd->msigm);
		if (sigs&(sd->msigm)) {
			m = (thmsg)GetMsg(t->port);
			if (m) {
				if (m->com == THC_EXIT) {
					sd->state = DLS_ABORT;
					thr_message(t, DLC_UPDATE, 0);
					ExitTransferThread(sd, 0);
					return;
				}
				if (!m->isreply) {
					m->isreply=1;
					ReplyMsg((struct Message *)m);
				}
			}
		}

		if (sigs&(sd->nsigm)) {
			FD_ZERO(&sd->fds);
			FD_SET(s,&sd->fds);
			sd->tv.tv_sec = 0;
			sd->tv.tv_usec = 0;

			switch (sd->state) {
				case DLS_CON:
					if (WaitSelect(s+1,NULL,&sd->fds,NULL,&sd->tv,0) != 1) break;
					{
						sd->state = DLS_REQ;
						tmp = 0;
						IoctlSocket(s, FIONBIO, (char*)&tmp);
						/* Disable non-blocking I/O to the socket */
					}

				case DLS_REQ:
					if (WaitSelect(s+1,&sd->fds,NULL,NULL,&sd->tv,0) != 1) break;
					{
						tmp = recv(s, buffer, 1, 0);
						if (tmp != 1) {
							sd->ErrorCode = Errno();
							ExitTransferThread(sd, ERROR_NET);
							return;
						}
						if (buffer[0] != '1') {
							ExitTransferThread(sd, 29);
							return;
						}
						thr_message(t, DLC_UPDATE, 0);
						send(s, "GET", 3, 0);
						sprintf(buffer, "%s \"%s\" %ld", sd->mynick, sd->song->title, sd->cur);
						send(s, buffer, strlen(buffer), 0);

						tmp = recv(s, buffer, 32, 0);
						if (tmp < 1) {
							sd->ErrorCode = Errno();
							ExitTransferThread(sd, ERROR_NET);
							return;
						}
						buffer[tmp] = '\0';

						if (atoi(buffer) != sd->size) {
							if (strcmp(buffer, "FILE NOT FOUND") == 0 || strcmp(buffer, "FILE NOT SHARED") == 0) {
								ExitTransferThread(sd, ERROR_NOTFOUND);
								return;
							}
							else if (strcmp(buffer, "INVALID REQUEST") == 0) {
								ExitTransferThread(sd, ERROR_INVALIDREQUEST);
								return;
							}
#ifdef AMSTER_DEBUG
gui_debugf("error (please report this): %s", buffer);
#endif
							ExitTransferThread(sd, ERROR_UNKNOWN);
							return;
						}
						sd->state = DLS_INIT;
					}

				case DLS_INIT:
					sd->f = Open(sd->fname, MODE_READWRITE);
					if (!sd->f) {
						sd->ErrorCode = IoErr();
						ExitTransferThread(sd, ERROR_FILEOPEN);
						return;
					}
					sprintf(cmnt, "md5:%s; size:%ld; user:%s", sd->song->md5, sd->size, sd->song->user);
					SetComment(sd->fname, cmnt);
					Seek(sd->f, sd->cur, OFFSET_BEGINNING);
					sd->state = DLS_DOWN;
					sd->starttime = GetDateStamp();
					sd->resumestart = sd->cur;
					thr_message(t, DLC_UPDATE, 0);

				case DLS_DOWN:
					if (WaitSelect(s+1,&sd->fds,NULL,NULL,&sd->tv,0) != 1) break;
					while (sd->cur < sd->size) {

						tmp = recv(s, buffer, 4096, 0);
						if (tmp <= 0) {
							sd->ErrorCode = Errno();
							/* If recv() returns 0 the error is EINPROGRESS
							   (Net error 36) - I don't know what's causing
							   this, but if not failing the thread will busy-loop */
							ExitTransferThread(sd, ERROR_NET);
							return;
						}
						SetIoErr(0L);	/* FWrite doesn't clear IoErr() due to a bug */
						count = FWrite(sd->f, buffer, 1, tmp);
						sd->cur += count;
						if (count != tmp) {
							sd->ErrorCode = IoErr();
							ExitTransferThread(sd, ERROR_FILEWRITE);
							return;
						}

						m = (thmsg)GetMsg(t->port);
						if (m) {
							if (m->com == THC_EXIT) {
								thr_message(t, DLC_STATE, (APTR)DLS_ABORT);
								ExitTransferThread(sd, 0);
								return;
							}
							if (!m->isreply) {
								m->isreply = 1;
								ReplyMsg((struct Message *)m);
							}
						}

						FD_ZERO(&sd->fds);
						FD_SET(s,&sd->fds);
					}

					sd->state = DLS_FIN;
					thr_message(t, DLC_UPDATE, 0);

					if ((strlen(sd->song->user)+strlen(sd->host)+9) < 80) sprintf(cmnt, "from %s (@%s)", sd->song->user, sd->host);
					else sprintf(cmnt, "from %s (@%s)", sd->song->user, Inet_NtoA(sd->song->ip));
					/* If the hostname is too long for the file comment, we fall back to the IP */
					SetComment(sd->fname, cmnt);

					if (prf->autoadd) thr_message(t, DLC_ADDSHARE, 0);

					ExitTransferThread(sd, 0);
					return;

			}
		}
	}

	ExitTransferThread(sd, 0);
}
