/*
** Download
*/

#include "include/config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <proto/exec.h>
#include <proto/socket.h>
#include <proto/dos.h>
#include <proto/asl.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/asl.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h>
#include <bsdsocket/socketbasetags.h>
#include <error.h>
#include <time.h>

#include "include/mui.h"
#include <MUI/NListview_mcc.h>
#include <MUI/NFloattext_mcc.h>
#include "include/gui.h"
#include "include/rexx.h"
#include "include/panel.h"
#include "include/download.h"
#include "include/prefs.h"
#include "include/share.h"
#include "md5.h"
#include "amster_Cat.h"
#include "include/protos.h"

#define MUIM_Window_Cleanup       0x8042ab26 /* Custom Class */ /* V18 */
#define MUIM_Window_Setup         0x8042c34c /* Custom Class */ /* V18 */

typedef struct songdl_struct {
	struct songdl_struct *next;
	song song;
	char *mynick;
	u_long ip;
	int port;
	char *fname;
	u_long size, cur, resumestart;
	int state;
	thread t;
	int ts;
	int cps,stalltick;
	u_long oldsize;
	time_t starttime,dltime,timeleft;
	/* thread data */
	struct Library *DosBase;
	struct Library *SocketBase;
	long s;
	char *buffer;
	BPTR f;
	u_long nsig,nsigm,msigm;
	struct sockaddr_in sin;
	struct fd_set fds;
	struct timeval tv;
} *songdl, _songdl;

#define DLS_PREP 0
#define DLS_CON 1
#define DLS_REQ 2
#define DLS_DOWN 3
#define DLS_FIN 4
#define DLS_ABORT 5
#define DLS_ERROR 6

#define DLC_STATE 0
#define DLC_SIZE 1

int dl_count = 0;

ULONG dl_new(struct IClass *cl, Object *obj, struct opSet *msg);
ULONG dl_setup(struct IClass *cl, Object *obj, Msg msg);
ULONG dl_muicleanup(struct IClass *cl, Object *obj, Msg msg);
MUIF listdisp(REG(a2) char **array, REG(a1) songdl sd);
void dl_startq2(struct dldata *data, char *title, char *user, u_long ip, int port);
void dl_info(struct dldata *data);
int dl_filename(songdl sd);
char *chkmd5_fromlock(BPTR lock);
int dl_askfname(char *fname);
void dl_handlemsg(thread t, int com, APTR data);
void dl_abort(struct dldata *data);
void dl_resume(struct dldata *data);
void dl_cleanup(struct dldata *data);
void dl_cps(struct dldata *data);
__asm __saveds void dl_sucker(void);
void dl_sucker_cleanup(songdl sd, int ret);


MUIF dl_dispatch(REG(a0) struct IClass *cl,REG(a2) Object *obj,REG(a1) Msg msg)
{
	struct dldata *data = INST_DATA(cl,obj);

	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:
			DoMethod(data->list,MUIM_NList_InsertSingle,(songdl)(((muimsg)msg)->arg1),MUIV_NList_Insert_Bottom);
			return(NULL);
		case DL_START:
			dl_startq2(data,(char*)(((muimsg)msg)->arg1),(char*)(((muimsg)msg)->arg2),(u_long)(((muimsg)msg)->arg3),(int)(((muimsg)msg)->arg4));
			return(NULL);
		case DL_UPDATE:
			{
			long pos = MUIV_NList_GetPos_Start;
			DoMethod(data->list,MUIM_NList_GetPos,(((muimsg)msg)->arg1),&pos);
			DoMethod(data->list,MUIM_NList_Redraw,pos);
			return(NULL);
			}
		case DL_CPS:
			dl_cps(data);
			return(NULL);
		case DL_CLEANUP:
			dl_cleanup(data);
			return(NULL);
		case DL_ABORT:
			dl_abort(data);
			return(NULL);
		case DL_RESUME:
			dl_resume(data);
			return(NULL);
		case DL_INFO:
			dl_info(data);
			return(0);
		case DL_PLAY:
			{
			u_long palpa;
			DoMethod(data->list,MUIM_NList_GetEntry,MUIV_NList_GetEntry_Active,&palpa);
			if(!palpa) return(NULL);
			if(((songdl)palpa)->state < DLS_DOWN) return(NULL);
			if(prf->scripts[PRFE_PLAYMP3-1])
				rexx_execute(prf->scripts[PRFE_PLAYMP3-1],"\"%s\"",((songdl)palpa)->fname);
			return(NULL);
			}
	}
	return(DoSuperMethodA(cl,obj,msg));
}


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

	if (obj = (Object *)DoSuperNew(cl,obj,
		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_NList_Title, TRUE,
					MUIA_NList_Format, "BAR, BAR, BAR, BAR, BAR",
					MUIA_NList_DisplayHook, &listdispHook,
					MUIA_CycleChain, 1,
				End,
			End,
			Child, info = TextObject,
				TextFrame,
				MUIA_Background, MUII_TextBack,
				MUIA_Text_PreParse, "\33c",
			End,
			Child, HGroup,
				Child, playbut = SimpleButton(MSG_DL_PLAY_GAD),
				Child, HSpace(4),
				Child, abortbut = SimpleButton(MSG_DL_ABORT_GAD),
				Child, resbut = SimpleButton(MSG_DL_RESUME_GAD),
				Child, HSpace(4),
				Child, cleanbut = SimpleButton(MSG_DL_CLEANUP_GAD),
			End,
		End,
		TAG_MORE, msg->ops_AttrList))
	{
		data = INST_DATA(cl,obj);
		data->list = list;
		data->info = info;

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

		DoMethod(playbut,  MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, DL_PLAY   );
		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(obj, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, gui->iconpanel, 1, PANEL_CLOSEDL);

		return((ULONG)obj);
	}
	return(0);
}


ULONG dl_setup(struct IClass *cl, Object *obj, Msg msg)
{
	struct dldata *data = INST_DATA(cl,obj);

	if (!DoSuperMethodA(cl,obj,msg))
		return(FALSE);

	DoMethod(_app(obj), MUIM_Application_AddInputHandler, &data->ihnode);
	return(TRUE);
}


ULONG dl_muicleanup(struct IClass *cl, Object *obj, Msg msg)
{
	struct dldata *data = INST_DATA(cl,obj);

	DoMethod(_app(obj), MUIM_Application_RemInputHandler, &data->ihnode);
	return(DoSuperMethodA(cl,obj,msg));
}


MUIF listdisp(REG(a2) char **array, REG(a1) songdl sd)
{
	static char buf2[50],buf3[50],buf4[50];
	static char *states[] = {
		(char *)_MSG_DL_STAT_PREPARE,
		(char *)_MSG_DL_STAT_CONN,
		(char *)_MSG_DL_STAT_REQ,
		(char *)_MSG_DL_STAT_DL,
		(char *)_MSG_DL_STAT_FIN,
		(char *)_MSG_DL_STAT_ABORT,
		(char *)_MSG_DL_STAT_ERROR,
		NULL
	};

	if(states[0] == (char *)_MSG_DL_STAT_PREPARE)
		localize_array(states);

	if(sd) {
		*array++ = nap_strippath(sd->song->title);
		*array++ = states[sd->state];
		if (sd->size > 0)
			sprintf(buf2,"\33r%ld / %ld (%d%%)", sd->cur, sd->size, (sd->cur*100+50)/sd->size);
		else sprintf(buf2, "\33r0");	/* Can this happen? It shouldn't! */
		*array++ = buf2;

		if (sd->cps > 0) {
			if (sd->stalltick<5) {	/* 5 seconds */
				sprintf(buf3,"%d",sd->cps);
				*array++ = buf3;
			}
			else {
				*array++ = (char *)MSG_DL_STALLED;
			}
			sprintf(buf4,"%ld:%02ld / %ld:%02ld",sd->dltime/60,sd->dltime%60,sd->timeleft/60,sd->timeleft%60);
			*array = buf4;
		}
		else {
			*array++ = "-";
			*array = "-";
		}

	}
	else {
		*array++ = (char *)MSG_LH_FILE;
		*array++ = (char *)MSG_LH_STATE;
		*array++ = (char *)MSG_LH_SIZE;
		*array++ = (char *)MSG_LH_CPS;
		*array = (char *)MSG_LH_ETIME;
	}
	return(0);
}


void dl_addq(song s)
{
	songdl sd;

	sd = malloc(sizeof(_songdl));
	if(!sd) return;
	memset(sd,0,sizeof(_songdl));

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

	sd->size = s->size;
	sd->mynick = prf->user;

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


void dl_startq(char *title, char *user, u_long ip, int port)
{
/*gui_debugf("port : %d",port);*/

	DoMethod(gui->dwin,DL_START,title,user,ip,port);
}


void dl_startq2(struct dldata *data, char *title, char *user, u_long ip, int port)
{
	u_long tmp;
	songdl sd;
	long i;

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

	if(sd->t) return;

	if(!dl_filename(sd)) {
		sd->state = DLS_ABORT;
		DoMethod(gui->dwin,DL_UPDATE,sd);
		return;
	}

/*gui_debugf("ack [%s]",title);*/

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

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


void dl_info(struct dldata *data)
{
	static char buf[500];
	u_long tmp,t;
	songdl sd;

	GetAttr(MUIA_NList_EntryClick,data->list,&tmp);
	if(tmp == -1 || tmp == -2) return;
	DoMethod(data->list,MUIM_NList_GetEntry,tmp,&sd);
	if(!sd) return;

	tmp = sd->state;
	if(tmp==DLS_PREP) {
		t = sd->song->ip;
		sprintf(buf,"%s (%d.%d.%d.%d)",sd->song->user,t>>24,(t&0xFF0000)>>16,(t&0xFF00)>>8,t&0xFF);
	} else if(tmp==DLS_ERROR) {
		sprintf(buf,"hata kodu koycan buraya ;)");
	} else {
		t = sd->ip;
		sprintf(buf,"%s (%d.%d.%d.%d : %d)",sd->song->user,t>>24,(t&0xFF0000)>16,(t&0xFF00)>>8,t&0xFF,sd->port);
	}

	set(data->info,MUIA_Text_Contents,buf);
}


void dl_resume(struct dldata *data)
{
	u_long item;
	songdl sd;

	DoMethod(data->list,MUIM_NList_GetEntry,MUIV_NList_GetEntry_Active,&item);
	if(!item) return;

	sd = (songdl)item;
	if(sd->state!=DLS_ABORT && sd->state!=DLS_ERROR) return;
	if(sd->t) return;

	sd->state = 0;
	sprintf(nap_buf,"\"%s\" \"%s\"",sd->song->user,sd->song->title);
	nap_send(NAPC_FILEINFOREQ);

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


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

	fname = malloc(512);
	if(!fname) return(0);
	strcpy(fname,prf->dlpath);
	AddPart(fname,nap_strippath(sd->song->title),511);

	if (prf->askfile) dl_askfname(fname);

	lock = Lock(fname,ACCESS_READ);
	if (!lock) {
		sd->fname = fname;
		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;

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

	if(AslRequestTags(freq,
					  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_abort(struct dldata *data)
{
	u_long item;
	songdl sd;

	DoMethod(data->list,MUIM_NList_GetEntry,MUIV_NList_GetEntry_Active,&item);
	if(!item) return;

	sd = (songdl)item;
	if(!sd->t) {
		sd->state = DLS_ABORT;
		DoMethod(gui->dwin,DL_UPDATE,sd);
		return;
	} else {
		if(sd->ts) th_message(sd->t,THC_EXIT,0);
	}
}


void dl_cleanup(struct dldata *data)
{
	u_long item;
	int i=0,j;

	while(1) {
		DoMethod(data->list,MUIM_NList_GetEntry,i,&item);
		if(!item) break;
		j = ((songdl)item)->state;
		if(j == DLS_FIN || j == DLS_ERROR || j == DLS_ABORT) {
			DoMethod(data->list,MUIM_NList_Remove,i);
			nap_songfree(((songdl)item)->song);
			if(((songdl)item)->fname) free(((songdl)item)->fname);
			free((songdl)item);
		} else {
			i++;
		}
	}
}


void dl_handlemsg(thread t, int com, APTR data)
{
	songdl sd=(songdl)t->data;

	switch(com) {
		case THC_STARTUP:
/*gui_debugf("thread start");*/
			sd->ts = 1;
			dl_count++;
			break;
		case THC_EXIT:
/*gui_debugf("thread exit = %ld",data);*/
			if(data!=0) {
				sd->state = DLS_ERROR;
				DoMethod(gui->dwin,DL_UPDATE,sd);
			}
			sd->ts = 0;
			sd->t = NULL;
			dl_count--;
			break;
		case DLC_STATE:
			sd->state = (int)data;
			DoMethod(gui->dwin,DL_UPDATE,sd);
			break;
/*		default:
			gui_debugf("thread c: %d, d: %ld",com,data);*/
	}
}


void dl_cps(struct dldata *data)
{
	songdl sd;
	u_long item,size;
	int i;

	for(i=0;;i++) {
		DoMethod(data->list,MUIM_NList_GetEntry,i,&item);
		if(!item) return;
		sd = (songdl)item;
		if(sd->state == DLS_DOWN) {

			if (sd->cur != sd->oldsize) {
				sd->stalltick = 0;
				sd->oldsize = sd->cur;
			}
			else sd->stalltick++;

			sd->dltime = time(NULL) - sd->starttime;
			if (sd->dltime == 0) sd->dltime = 1;
			sd->cps = (sd->cur - sd->resumestart) / sd->dltime;
			if (sd->cps > 0)
				sd->timeleft = (sd->size - sd->cur) / sd->cps;
			else sd->timeleft = 0;
			DoMethod(gui->dwin, DL_UPDATE, sd);

/*
			sd->dltime++;
			size = sd->cur;
			if(size != sd->oldsize) {
				sd->stalltick = 0;
				sd->cps = size - sd->oldsize;
				sd->oldsize = size;
				if(sd->cps>0) sd->timeleft = (sd->size - size) / sd->cps;
				DoMethod(gui->dwin,DL_UPDATE,sd);
			}
			else {
				if(sd->stalltick>2) sd->cps=0; else sd->stalltick++;
			}
*/
		}
	}
}


/* thread code */

__asm __saveds void dl_sucker(void)
{
	thread t;
	songdl sd;
	struct Library *DosBase;
	struct Library *SocketBase;
	char *buffer;
	long tmp;
	long s;
	int state=0;
	thmsg m;
	char cmnt[80];
	struct hostent *he;
	int size;

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

	sd->nsig = AllocSignal(-1);
	if(sd->nsig == -1) {
		dl_sucker_cleanup(sd,1);
		return;
	}
	sd->nsigm = (1L << (sd->nsig));
	sd->msigm = (1L << (t->port->mp_SigBit));

	DosBase = OpenLibrary("dos.library",0);
	if(!DosBase) {
		dl_sucker_cleanup(sd,1);
		return;
	}
	sd->DosBase = DosBase;

	SocketBase = OpenLibrary("bsdsocket.library",0);
	if(!SocketBase) {
		dl_sucker_cleanup(sd,1);
		return;
	}
	sd->SocketBase = SocketBase;
	SocketBaseTags(SBTM_SETVAL(SBTC_SIGIOMASK),(char *)sd->nsigm,TAG_DONE);

	buffer = malloc(8192);
	if(!buffer) {
		dl_sucker_cleanup(sd,2);
		return;
	}
	sd->buffer = buffer;

	s = socket(AF_INET,SOCK_STREAM,0);
	if(s<0) {
		dl_sucker_cleanup(sd,3);
		return;
	}
	sd->s = s;

	sd->sin.sin_addr.s_addr = sd->ip;
	sd->sin.sin_port = sd->port;
	sd->sin.sin_family = AF_INET;
	sd->sin.sin_len = sizeof(sd->sin);
	tmp = 1;
	IoctlSocket(s,FIOASYNC,(char *)&tmp);
	IoctlSocket(s,FIONBIO,(char *)&tmp);
	tmp = connect(s,(struct sockaddr *)&sd->sin,sizeof(sd->sin));
	if(tmp== -1 && Errno()!=EINPROGRESS) {
		thr_message(t,42,(APTR)Errno());
		dl_sucker_cleanup(sd,4);
		return;
	}

	thr_message(t,DLC_STATE,(APTR)DLS_CON);
	state = DLS_CON;

	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) {
					thr_message(t,DLC_STATE,(APTR)DLS_ABORT);
					dl_sucker_cleanup(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(state) {
				case DLS_CON:
					tmp = WaitSelect(s+1,NULL,&sd->fds,NULL,&sd->tv,0);
					if(tmp==1) {
						state = DLS_REQ;
						tmp=0;
						IoctlSocket(s,FIONBIO,(char*)&tmp);
						break;
					}
					if(state==DLS_CON) break;

				case DLS_REQ:
					tmp = WaitSelect(s+1,&sd->fds,NULL,NULL,&sd->tv,0);
					if(tmp==1) {
						tmp = recv(s,buffer,1,0);
						if(tmp!=1 || (tmp==1 && buffer[0]!='1')) {
							dl_sucker_cleanup(sd,5);
							return;
						}
						thr_message(t,DLC_STATE,(APTR)DLS_REQ);
						send(s,"GET",3,0);
						sprintf(buffer,"%s \"%s\" %d",sd->mynick,sd->song->title,sd->cur);
						send(s,buffer,strlen(buffer),0);

						size = stci_d(buffer,sd->size);
						for (tmp=0;tmp<size;tmp++) {
							if(1 != recv(s, &buffer[tmp], 1, 0)) {
								dl_sucker_cleanup(sd, 6);
								return;
							}
						}
						buffer[tmp] = '\0';

						if (atoi(buffer) != sd->size) {
							if (strncmp(buffer, "FILE NOT FOUND", tmp) == 0)
								/* If we were to check the complete string, this would be the place to complicate it */
								gui_debug("File not found at the remote client.\n");
							else gui_debug("File header error.\n");
							dl_sucker_cleanup(sd,6);
							return;
						}

						sd->f = Open(sd->fname,MODE_READWRITE);
						if(!sd->f) {
							dl_sucker_cleanup(sd,7);
							return;
						}
						sprintf(cmnt,"md5:%s; size:%ld",sd->song->md5,sd->size);
						SetComment(sd->fname,cmnt);
						Seek(sd->f,sd->cur,OFFSET_BEGINNING);
						state = DLS_DOWN;
						sd->starttime = time(NULL);
						sd->resumestart = sd->cur;
						thr_message(t,DLC_STATE,(APTR)DLS_DOWN);
					}
					if(state==DLS_REQ) break;

				case DLS_DOWN:
					while(1) {
						tmp = WaitSelect(s+1,&sd->fds,NULL,NULL,&sd->tv,0);
						if(tmp<1) break;
						tmp = recv(s,buffer,4096,0);
						if(tmp<1) {
							if(tmp==0 && sd->cur>=sd->size) {
								thr_message(t,DLC_STATE,(APTR)DLS_FIN);

								he = gethostbyname(Inet_NtoA(sd->song->ip));
								he = gethostbyaddr(he->h_addr_list[0], he->h_length, AF_INET);
								if (he != 0) if ((strlen(sd->song->user)+strlen(he->h_name)+9) < 80) sprintf(cmnt, "from %s (@%s)", sd->song->user, he->h_name);
								if (cmnt[0] == '\0') 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) DoMethod(gui->app, MUIM_Application_PushMethod, gui->shwin, 3, SHARE_ADDFILE, sd->song, sd->fname);

								dl_sucker_cleanup(sd,0);
								return;
							}
							thr_message(t,12,(APTR)tmp);
							thr_message(t,13,(APTR)Errno());
							thr_message(t,DLC_STATE,(APTR)DLS_ERROR);
							dl_sucker_cleanup(sd,8);
							return;
						}
						FWrite(sd->f,buffer,1,tmp);
						sd->cur += tmp;

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

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

			}
		}
	}

	dl_sucker_cleanup(sd,0);
}


void dl_sucker_cleanup(songdl sd, int ret)
{
	struct Library *DosBase=sd->DosBase;
	struct Library *SocketBase=sd->SocketBase;

	if(sd->nsig != -1) FreeSignal(sd->nsig);
	sd->nsig = -1;
	if(sd->f) Close(sd->f);
	sd->f = 0;
	if(sd->s != -1) CloseSocket(sd->s);
	sd->s = -1;
	if(sd->buffer) free(sd->buffer);
	sd->buffer = NULL;
	if(SocketBase) CloseLibrary(SocketBase);
	sd->SocketBase = NULL;
	if(DosBase) CloseLibrary(DosBase);
	sd->DosBase = NULL;
	thr_exit(sd->t,ret);
}
