/*
 * $Id: file_transfer.cpp 13894 2011-10-23 19:32:35Z borkra $
 *
 * myYahoo Miranda Plugin 
 *
 * Authors: Gennady Feldman (aka Gena01) 
 *          Laurent Marechal (aka Peorth)
 *
 * This code is under GPL and is based on AIM, MSN and Miranda source code.
 * I want to thank Robert Rainwater and George Hazan for their code and support
 * and for answering some of my questions during development of this plugin.
 */
#include <time.h>
#include <sys/stat.h>

#include "yahoo.h"
#include <m_protosvc.h>
#include "file_transfer.h"

YList *file_transfers=NULL;

static y_filetransfer* new_ft(CYahooProto* ppro, int id, HANDLE hContact, const char *who, const char *msg,  
					const char *url, const char *ft_token, int y7, YList *fs, int sending)
{
	yahoo_file_info * fi;
	int i=0;
	YList *l=fs;

	LOG(("[new_ft] id: %d, who: %s, msg: %s, ft_token: %s, y7: %d, sending: %d", id, who, msg, ft_token, y7, sending));

	y_filetransfer* ft = (y_filetransfer*) calloc(1, sizeof(y_filetransfer));
	ft->ppro = ppro;
	ft->id  = id;
	ft->who = strdup(who);
	ft->hWaitEvent = INVALID_HANDLE_VALUE;

	ft->hContact = hContact;
	ft->files = fs;

	ft->url = (url == NULL) ? NULL : strdup(url);
	ft->ftoken = (ft_token == NULL) ? NULL : strdup(ft_token);
	ft->msg = (msg != NULL) ? strdup(msg) : strdup("[no description given]");

	ft->cancel = 0;
	ft->y7 = y7;
	
	ft->hWaitEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

	ft->pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS);
	ft->pfts.hContact = hContact;
	ft->pfts.flags =  PFTS_TCHAR; 
	ft->pfts.flags |= (sending != 0 ) ? PFTS_SENDING : PFTS_RECEIVING;
	
	ft->pfts.tszWorkingDir = NULL;
	ft->pfts.currentFileTime = 0;

	ft->pfts.totalFiles = y_list_length(fs);

	ft->pfts.ptszFiles = (TCHAR**) mir_calloc(ft->pfts.totalFiles * sizeof(TCHAR *));
	ft->pfts.totalBytes = 0;

	while(l) {
		fi = ( yahoo_file_info* )l->data;

		ft->pfts.ptszFiles[i++] = mir_utf8decodeT(fi->filename);
		ft->pfts.totalBytes += fi->filesize;

		l=l->next;
	}

	ft->pfts.currentFileNumber = 0;

	fi = ( yahoo_file_info* )fs->data; 
	ft->pfts.tszCurrentFile = _tcsdup(ft->pfts.ptszFiles[ft->pfts.currentFileNumber]);
	ft->pfts.currentFileSize = fi->filesize; 

	file_transfers = y_list_prepend(file_transfers, ft);

	LOG(("[/new_ft]"));
	
	return ft;
}

y_filetransfer* find_ft(const char *ft_token, const char *who) 
{
	YList *l;
	y_filetransfer* f;
	LOG(("[find_ft] Searching for: %s", ft_token));
	
	for(l = file_transfers; l; l = y_list_next(l)) {
		f = (y_filetransfer* )l->data;
		if (lstrcmpA(f->ftoken, ft_token) == 0 && lstrcmpA(f->who, who) == 0) {
			LOG(("[find_ft] Got it!"));
			return f;
		}
	}

	LOG(("[find_ft] FT not found?"));
	return NULL;
}

static void free_ft(y_filetransfer* ft)
{
	YList *l;
	int i;
	
	LOG(("[free_ft] token: %s", ft->ftoken));
	
	for(l = file_transfers; l; l = y_list_next(l)) {
		if (l->data == ft){
			LOG(("[free_ft] Ft found and removed from the list"));
			file_transfers = y_list_remove_link(file_transfers, l);
			y_list_free_1(l);
			break;
		}
	}

	if ( ft->hWaitEvent != INVALID_HANDLE_VALUE )
		CloseHandle( ft->hWaitEvent );
	
	FREE(ft->who);
	FREE(ft->msg);
	FREE(ft->url);
	FREE(ft->ftoken);
	FREE(ft->relay);
	
	LOG(("[free_ft] About to free the File List."));
	
	while(ft->files) {
		YList *tmp = ft->files;
		yahoo_file_info * c = ( yahoo_file_info* )ft->files->data;
		FREE(c->filename);
		FREE(c);
		ft->files = y_list_remove_link(ft->files, ft->files);
		y_list_free_1(tmp);
	}
	
	LOG(("[free_ft] About to free PFTS."));
	
	for (i=0; i< ft->pfts.totalFiles; i++)
		mir_free(ft->pfts.ptszFiles[i]);
	
	mir_free(ft->pfts.ptszFiles);
	FREE(ft->pfts.tszCurrentFile);
	FREE(ft->pfts.tszWorkingDir);
	FREE(ft);
	
	LOG(("[/free_ft]"));
}

static void upload_file(int id, int fd, int error, void *data) 
{
	y_filetransfer *sf = (y_filetransfer*) data;
	struct yahoo_file_info *fi = (struct yahoo_file_info *)sf->files->data;
	char buf[1024];
	unsigned long size = 0;
	DWORD dw = 0;
	int   rw = 0;

	if (fd < 0) {
		LOG(("[get_fd] Connect Failed!"));
		error = 1;
	}

	if(!error) {
		HANDLE myhFile = CreateFile(sf->pfts.tszCurrentFile,
			GENERIC_READ,
			FILE_SHARE_READ|FILE_SHARE_WRITE,
			NULL,
			OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
			0);

		if(myhFile !=INVALID_HANDLE_VALUE) {
			DWORD lNotify = GetTickCount();

			LOG(("proto: %s, hContact: %p", sf->ppro->m_szModuleName, sf->hContact));

			LOG(("Sending file: %s", fi->filename));
			//ProtoBroadcastAck(m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, sf, 0);
			//ProtoBroadcastAck(m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, sf, 0);
			ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, sf, 0);
			//ProtoBroadcastAck(m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_SENTREQUEST, sf, 0);
			//ProtoBroadcastAck(m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, sf, 0);

			do {
				ReadFile(myhFile, buf, sizeof(buf), &dw, NULL);

				if (dw) {
					rw = Netlib_Send((HANDLE)fd, buf, dw, MSG_NODUMP);

					if (rw < 1) {
						LOG(("Upload Failed. Send error? Got: %d", rw));
						error = 1;
						break;
					} else 
						size += rw;

					if(GetTickCount() >= lNotify + 500 || rw < 1024 || size == fi->filesize) {
						LOG(("DOING UI Notify. Got %lu/%lu", size, fi->filesize));
						sf->pfts.totalProgress = size;
						sf->pfts.currentFileProgress = size;

						ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_DATA, sf, (LPARAM) & sf->pfts);
						lNotify = GetTickCount();
					}

				}

				if (sf->cancel) {
					LOG(("Upload Cancelled! "));
					error = 1;
					break;
				}
			} while ( rw > 0 && dw > 0 && !error);

			CloseHandle(myhFile);

			sf->pfts.totalProgress = size;
			sf->pfts.currentFileProgress = size;

			ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_DATA, sf, (LPARAM) & sf->pfts);

		}
	}	

	if (fd > 0) {
		int tr = 0;

		do {
			rw = Netlib_Recv((HANDLE)fd, buf, sizeof(buf), 0);
			LOG(("Got: %d bytes", rw));

			if (tr == 0) {
				//"HTTP/1.1 999" 12
				// 012345678901
				if (rw > 12) {
					if (buf[9] != '2' || buf[10] != '0' || buf[11] != '0') {
						LOG(("File Transfer Failed: %c%c%c", buf[9], buf[10], buf[11]));
						error=1;
					}
				}
			}
			tr +=rw;
		} while (rw > 0);

		Netlib_CloseHandle((HANDLE)fd);
	}

	LOG(("File send complete!"));

	if (! error) {
		sf->pfts.currentFileNumber++;

		if (sf->pfts.currentFileNumber >= sf->pfts.totalFiles) {
			ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, sf, 0);
		} else {
			YList *l;
			struct yahoo_file_info * fi;
			
			// Do Next file
			FREE(sf->pfts.tszCurrentFile);
			
			l = sf->files;
			
			fi = ( yahoo_file_info* )l->data;
			FREE(fi->filename);
			FREE(fi);
			
			sf->files = y_list_remove_link(sf->files, l);
			y_list_free_1(l);
			
			// need to move to the next file on the list and fill the file information
			fi = ( yahoo_file_info* )sf->files->data; 
			sf->pfts.tszCurrentFile = _tcsdup(sf->pfts.ptszFiles[sf->pfts.currentFileNumber]);
			sf->pfts.currentFileSize = fi->filesize; 
			sf->pfts.currentFileProgress = 0;
			
			ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, sf, 0);
			LOG(("Waiting for next file request packet..."));
		}

	} else {
		ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, sf, 0);
	}
}

static void dl_file(int id, int fd, int error,	const char *filename, unsigned long size, void *data) 
{
    y_filetransfer *sf = (y_filetransfer*) data;
	struct yahoo_file_info *fi = (struct yahoo_file_info *)sf->files->data;
    char buf[1024];
    unsigned long rsize = 0;
	DWORD dw, c;

	if (fd < 0) {
		LOG(("[get_url] Connect Failed!"));
		
		if (sf->ftoken != NULL) {
			LOG(("[get_url] DC Detected: asking sender to upload to Yahoo FileServers!"));
			yahoo_ftdc_deny(id, sf->who, fi->filename, sf->ftoken, 3);	
		}
		
		error = 1;
	}
	
    if(!error) {
		HANDLE myhFile;
		TCHAR filefull[MAX_PATH];
		
		/*
		 * We need FULL Path for File Resume to work properly!!!
		 *
		 * Don't rely on workingDir to be right, since it's not used to check if file exists.
		 */
		mir_sntprintf(filefull, MAX_PATH, _T("%s\\%s"), sf->pfts.tszWorkingDir, sf->pfts.tszCurrentFile);
		FREE(sf->pfts.tszCurrentFile);
		sf->pfts.tszCurrentFile = _tcsdup(filefull);		
		
		ResetEvent(sf->hWaitEvent);
		
		if ( sf->ppro->SendBroadcast( sf->hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, sf, ( LPARAM )&sf->pfts )) {
			WaitForSingleObject( sf->hWaitEvent, INFINITE );
			
			LOG(("[dl_file] Got action: %ld", sf->action));
			
			switch(sf->action){
				case FILERESUME_RENAME:
				case FILERESUME_OVERWRITE:	
				case FILERESUME_RESUME:	
					// no action needed at this point, just break out of the switch statement
					break;

				case FILERESUME_CANCEL	:
					sf->cancel = 1;
					break;

				case FILERESUME_SKIP	:
				default:
					sf->cancel = 2;
					break;
				}
		}

		
		
		if (! sf->cancel) {
			
			myhFile    = CreateFile(sf->pfts.tszCurrentFile,
									GENERIC_WRITE,
									FILE_SHARE_WRITE,
									NULL, OPEN_ALWAYS,  FILE_ATTRIBUTE_NORMAL,  0);
	
			if(myhFile !=INVALID_HANDLE_VALUE) {
				DWORD lNotify = GetTickCount();
				
				SetEndOfFile(myhFile);
				
				LOG(("proto: %s, hContact: %p", sf->ppro->m_szModuleName, sf->hContact));
				
				ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, sf, 0);
				
				do {
					dw = Netlib_Recv((HANDLE)fd, buf, 1024, MSG_NODUMP);
				
					if (dw > 0) {
						WriteFile(myhFile, buf, dw, &c, NULL);
						rsize += dw;
						sf->pfts.totalProgress += dw;
						sf->pfts.currentFileProgress += dw;
						
						if(GetTickCount() >= lNotify + 500 || dw <= 0 || rsize == size) {
							
							LOG(("DOING UI Notify. Got %lu/%lu", rsize, size));
							
							ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_DATA, sf, (LPARAM) & sf->pfts);
							lNotify = GetTickCount();
						}
						
					} else {
						LOG(("Recv Failed! Socket Error?"));
						error = 1;
						break;
					}
					
					if (sf->cancel) {
						LOG(("Recv Cancelled! "));
						error = 1;
						break;
					}
				} while ( dw > 0 && rsize < size);
				
				while (dw > 0 && ! sf->cancel && ! error) {
					dw = Netlib_Recv((HANDLE)fd, buf, 1024, MSG_NODUMP);
					LOG(("Ack."));
				}
				
				ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_DATA, sf, (LPARAM) & sf->pfts);
				
				LOG(("[Finished DL] Got %lu/%lu", rsize, size));
				CloseHandle(myhFile);
				
			} else {
				LOG(("Can not open file for writing: %s", buf));
				error = 1;
			}
			
		} 
    }
	
	if (fd > 0) {
		LOG(("Closing connection: %d", fd));
		Netlib_CloseHandle((HANDLE)fd);
	
		if (sf->cancel || error) {
			/* abort FT transfer */
			yahoo_ft7dc_abort(id, sf->who, sf->ftoken);
		}
	}
	
    if (! error) {
		sf->pfts.currentFileNumber++;
		
		LOG(("File %d/%d download complete!", sf->pfts.currentFileNumber, sf->pfts.totalFiles));
		
		if (sf->pfts.currentFileNumber >= sf->pfts.totalFiles) {
			ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, sf, 0);
		} else {
			YList *l;
			struct yahoo_file_info * fi;
			
			// Do Next file
			yahoo_ft7dc_nextfile(id, sf->who, sf->ftoken);
			FREE(sf->pfts.tszCurrentFile);
			
			l = sf->files;
			
			fi = ( yahoo_file_info* )l->data;
			FREE(fi->filename);
			FREE(fi);
			
			sf->files = y_list_remove_link(sf->files, l);
			y_list_free_1(l);
			
			// need to move to the next file on the list and fill the file information
			fi = ( yahoo_file_info* )sf->files->data; 
			sf->pfts.tszCurrentFile = _tcsdup(sf->pfts.ptszFiles[sf->pfts.currentFileNumber]);
			sf->pfts.currentFileSize = fi->filesize; 
			sf->pfts.currentFileProgress = 0;
			
			ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, sf, 0);
		}
	} else {
		LOG(("File download failed!"));
		
		ProtoBroadcastAck(sf->ppro->m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, sf, 0);
	}
}

//=======================================================
//File Transfer
//=======================================================
void __cdecl CYahooProto::recv_filethread(void *psf) 
{
	y_filetransfer *sf = ( y_filetransfer* )psf;
	struct yahoo_file_info *fi = (struct yahoo_file_info *)sf->files->data;
	
	ProtoBroadcastAck(m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, sf, 0);
	
	DebugLog("[yahoo_recv_filethread] who: %s, msg: %s, filename: %s ", sf->who, sf->msg, fi->filename);
	
	
	yahoo_get_url_handle(m_id, sf->url, &dl_file, sf);
	
	if (sf->pfts.currentFileNumber >= sf->pfts.totalFiles)
		free_ft(sf);
	else
		DebugLog("[yahoo_recv_filethread] More files coming?");
}

void CYahooProto::ext_got_file(const char *me, const char *who, const char *url, long expires, const char *msg, const char *fname, unsigned long fesize, const char *ft_token, int y7)
{
	HANDLE hContact;
	char *szBlob;
	y_filetransfer *ft;
	char fn[1024];
	struct yahoo_file_info *fi;
	YList *files=NULL;

	LOG(("[ext_yahoo_got_file] ident:%s, who: %s, url: %s, expires: %lu, msg: %s, fname: %s, fsize: %lu ftoken: %s y7: %d", me, who, url, expires, msg, fname, fesize, ft_token == NULL ? "NULL" : ft_token, y7));

	hContact = getbuddyH(who);
	if (hContact == NULL) 
		hContact = add_buddy(who, who, 0 /* NO FT for other IMs */, PALF_TEMPORARY);

	ZeroMemory(fn, 1024);

	if (fname != NULL)
		lstrcpynA(fn, fname, 1024);
	else {
		char *start, *end;

		/* based on how gaim does this */
		start = ( char* )strrchr(url, '/');
		if (start)
			start++;

		end = ( char* )strrchr(url, '?');

		if (start && *start && end) {
			lstrcpynA(fn, start, end-start+1);
		} else 
			lstrcpyA(fn, "filename.ext");
	}

	fi = y_new(struct yahoo_file_info,1);
	fi->filename = strdup(fn);
	fi->filesize = fesize;

	files = y_list_append(files, fi);

	ft = new_ft(this, m_id, hContact, who, msg,	url, ft_token, y7, files, 0 /* downloading */);
	if (ft == NULL) {
		DebugLog("SF IS NULL!!!");
		return;
	}

	// blob is DWORD(*ft), ASCIIZ(filenames), ASCIIZ(description)
	szBlob = (char *) malloc(sizeof(DWORD) + lstrlenA(fn) + lstrlenA(ft->msg) + 2);
	*((PDWORD) szBlob) = 0;
	strcpy(szBlob + sizeof(DWORD), fn);
	strcpy(szBlob + sizeof(DWORD) + lstrlenA(fn) + 1, ft->msg);

	PROTORECVEVENT pre;
	pre.flags = PREF_UTF;
	pre.timestamp = (DWORD)time(NULL);
	pre.szMessage = szBlob;
	pre.lParam = (LPARAM)ft;

	CCSDATA ccs;
	ccs.szProtoService = PSR_FILE;
	ccs.hContact = hContact;
	ccs.wParam = 0;
	ccs.lParam = (LPARAM) & pre;
	CallService(MS_PROTO_CHAINRECV, 0, (LPARAM) & ccs);
	free(szBlob);
}

void CYahooProto::ext_got_files(const char *me, const char *who, const char *ft_token, int y7, YList* files)
{
	HANDLE hContact;
	char *szBlob;
	y_filetransfer *ft;
	YList *f;
	char fn[4096];
	int fc = 0;

	LOG(("[ext_yahoo_got_files] ident:%s, who: %s, ftoken: %s ", me, who, ft_token == NULL ? "NULL" : ft_token));

	hContact = getbuddyH(who);
	if (hContact == NULL) 
		hContact = add_buddy(who, who, 0 /* NO FT for other IMs */, PALF_TEMPORARY);

	ft = new_ft(this, m_id, hContact, who, NULL, NULL, ft_token, y7, files, 0 /* downloading */);
	if (ft == NULL) {
		DebugLog("SF IS NULL!!!");
		return;
	}

	fn[0] = '\0';
	for (f=files; f; f = y_list_next(f)) {
		char z[1024];
		struct yahoo_file_info *fi = (struct yahoo_file_info *) f->data;

		snprintf(z, 1024, "%s (%lu)\r\n", fi->filename, fi->filesize);
		lstrcatA(fn, z);
		fc++;
	}

	if (fc > 1) {
		/* multiple files */

	}

	// blob is DWORD(*ft), ASCIIZ(filenames), ASCIIZ(description)
	szBlob = (char *) malloc(sizeof(DWORD) + lstrlenA(fn) + 2);
	*((PDWORD) szBlob) = 0;
	strcpy(szBlob + sizeof(DWORD), fn);
	strcpy(szBlob + sizeof(DWORD) + lstrlenA(fn) + 1, "");

	PROTORECVEVENT pre;
	pre.flags = PREF_UTF;
	pre.timestamp = (DWORD)time(NULL);
	pre.szMessage = szBlob;
	pre.lParam = (LPARAM)ft;

	CCSDATA ccs;
	ccs.szProtoService = PSR_FILE;
	ccs.hContact = ft->hContact;
	ccs.wParam = 0;
	ccs.lParam = (LPARAM)&pre;
	CallService(MS_PROTO_CHAINRECV, 0, (LPARAM) & ccs);
	free(szBlob);
}

void CYahooProto::ext_got_file7info(const char *me, const char *who, const char *url, const char *fname, const char *ft_token)
{
	y_filetransfer *ft;

	LOG(("[ext_yahoo_got_file7info] ident:%s, who: %s, url: %s, fname: %s, ft_token: %s", me, who, url, fname, ft_token));
	
	ft = find_ft(ft_token, who);
	
	if (ft == NULL) {
		LOG(("ERROR: Can't find the token: %s in my file transfers list...", ft_token));
		return;
	}
	
	ProtoBroadcastAck(m_szModuleName, ft->hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE) 1, 0);
	
	FREE(ft->url);
	
	ft->url = strdup(url);
	
	YForkThread(&CYahooProto::recv_filethread, ft);
}

void ext_yahoo_send_file7info(int id, const char *me, const char *who, const char *ft_token)
{
	y_filetransfer *ft;
	yahoo_file_info *fi;
	
	char *c;
	LOG(("[ext_yahoo_send_file7info] id: %i, ident:%s, who: %s, ft_token: %s", id, me, who, ft_token));
	
	ft = find_ft(ft_token, who);
	
	if (ft == NULL) {
		LOG(("ERROR: Can't find the token: %s in my file transfers list...", ft_token));
		return;
	}
	
	fi = (yahoo_file_info *) ft->files->data;
	
	c = strrchr(fi->filename, '\\');
	if (c != NULL ) {
		c++;
	} else {
		c = fi->filename;
	}
	
	LOG(("Resolving relay.msg.yahoo.com..."));
	PHOSTENT he = gethostbyname("relay.msg.yahoo.com");
	
	if (he) {
		ft->relay = strdup( inet_ntoa(*( PIN_ADDR )he->h_addr_list[0]) );
		LOG(("Got Relay IP: %s", ft->relay));
	} else {
		ft->relay = strdup( "98.136.112.33" );
		LOG(("DNS Lookup failed. Using Relay IP: %s", ft->relay));
	}
	
	yahoo_send_file7info(id, me, who, ft_token, c, ft->relay );
}

struct _sfs{
	char *me;
	char *token;
	y_filetransfer *sf;
};

void CYahooProto::ext_ft7_send_file(const char *me, const char *who, const char *filename, const char *token, const char *ft_token)
{
	y_filetransfer *sf;
	struct _sfs *s;
	
	LOG(("[ext_yahoo_send_file7info] ident:%s, who: %s, ft_token: %s", me, who, ft_token));
	
	sf = find_ft(ft_token, who);
	
	if (sf == NULL) {
		LOG(("ERROR: Can't find the token: %s in my file transfers list...", ft_token));
		return;
	}

	s = (struct _sfs *) malloc( sizeof( struct _sfs ));
	
	s->me = strdup(me);
	s->token = strdup(token);
	s->sf = sf;
	
	YForkThread(&CYahooProto::send_filethread, s);
}

/**************** Send File ********************/

void __cdecl CYahooProto::send_filethread(void *psf) 
{
	struct _sfs *s = ( struct _sfs * )psf;
	y_filetransfer *sf = s->sf;
	struct yahoo_file_info *fi = (struct yahoo_file_info *)sf->files->data;
	
	ProtoBroadcastAck(m_szModuleName, sf->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, sf, 0);
	
	LOG(("who %s, msg: %s, filename: %s filesize: %ld", sf->who, sf->msg, fi->filename, fi->filesize));
	
	yahoo_send_file_y7(sf->id, s->me, sf->who, sf->relay, fi->filesize, s->token,  &upload_file, sf);
	
	FREE(s->me);
	FREE(s->token);
	FREE(s);
	
	if (sf->pfts.currentFileNumber >= sf->pfts.totalFiles) {
		free_ft(sf);
	} else {
		DebugLog("[yahoo_send_filethread] More files coming?");
	}

}


////////////////////////////////////////////////////////////////////////////////////////
// SendFile - sends a file

HANDLE __cdecl CYahooProto::SendFile( HANDLE hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles )
{
	DBVARIANT dbv;
	y_filetransfer *sf;
	
	LOG(("[YahooSendFile]"));
	
	if ( !m_bLoggedIn )
		return 0;

	/*DebugLog("Getting Files");
	
	if ( ppszFiles[1] != NULL ){
		MessageBoxA(NULL, "YAHOO protocol allows only one file to be sent at a time", "Yahoo", MB_OK | MB_ICONINFORMATION);
		return 0;
 	}
	
	DebugLog("Getting Yahoo ID");
	*/
	
	if (!DBGetContactSettingString(hContact, m_szModuleName, YAHOO_LOGINID, &dbv)) {
		long tFileSize = 0;
		struct _stat statbuf;
		struct yahoo_file_info *fi;
		YList *fs=NULL;
		int i=0;
		char *s;
	
		while (ppszFiles[i] != NULL) {
			if ( _tstat( ppszFiles[i], &statbuf ) == 0 )
				tFileSize = statbuf.st_size;
	
			fi = y_new(struct yahoo_file_info,1);
			
			/**
			 * Need to use regular memory allocator/deallocator, since this is how things are build w/ libyahoo2
			 */
			s = mir_utf8encodeT(ppszFiles[i]);
			fi->filename = strdup(s);
			mir_free(s);
			
			fi->filesize = tFileSize;
		
			fs = y_list_append(fs, fi);
			i++;
		}
	
		sf = new_ft(this, m_id, hContact, dbv.pszVal, ( char* )szDescription,
					NULL, NULL, 0, fs, 1 /* sending */);
					
		DBFreeVariant(&dbv);
		
		if (sf == NULL) {
			DebugLog("SF IS NULL!!!");
			return 0;
		}

		LOG(("who: %s, msg: %s, # files: %d", sf->who, sf->msg, i));
		//mir_forkthread(yahoo_send_filethread, sf);
		
		sf->ftoken=yahoo_ft7dc_send(m_id, sf->who, fs);
		
		LOG(("Exiting SendRequest..."));
		
		return sf;
	}
	
	LOG(("[/YahooSendFile]"));
	
	return 0;
}

////////////////////////////////////////////////////////////////////////////////////////
// FileAllow - starts a file transfer

HANDLE __cdecl CYahooProto::FileAllow( HANDLE /*hContact*/, HANDLE hTransfer, const PROTOCHAR* szPath )
{
	y_filetransfer *ft = (y_filetransfer *)hTransfer;
	size_t len;

	DebugLog("[YahooFileAllow]");

	//LOG(LOG_INFO, "[%s] Requesting file from %s", ft->cookie, ft->user);
	ft->pfts.tszWorkingDir = _tcsdup( szPath );

	len = _tcslen(ft->pfts.tszWorkingDir) - 1;
	if (ft->pfts.tszWorkingDir[len] == '\\')
		ft->pfts.tszWorkingDir[len] = 0;
		
	if (ft->y7) {
		DebugLog("[YahooFileAllow] Trying to relay Y7 transfer.");
		//void yahoo_ft7dc_accept(int id, const char *buddy, const char *ft_token);
		yahoo_ft7dc_accept(ft->id, ft->who, ft->ftoken);

		return hTransfer;
	}

	YForkThread(&CYahooProto::recv_filethread, ft);
	return hTransfer;
}

////////////////////////////////////////////////////////////////////////////////////////
// FileCancel - cancels a file transfer

int __cdecl CYahooProto::FileCancel( HANDLE /*hContact*/, HANDLE hTransfer )
{
	DebugLog("[YahooFileCancel]");

	y_filetransfer* ft = (y_filetransfer*)hTransfer;
	
	if (! (ft->pfts.flags & PFTS_SENDING)  && ! ft->cancel) {
		/* abort FT transfer */
		yahoo_ft7dc_abort(ft->id, ft->who, ft->ftoken);
	}
	
	if ( ft->hWaitEvent != INVALID_HANDLE_VALUE )
		SetEvent( ft->hWaitEvent );

	ft->action = FILERESUME_CANCEL;
	ft->cancel = 1;
	
	return 0;
}

////////////////////////////////////////////////////////////////////////////////////////
// FileDeny - denies a file transfer

int __cdecl CYahooProto::FileDeny( HANDLE /*hContact*/, HANDLE hTransfer, const PROTOCHAR* )
{
	/* deny file receive request.. just ignore it! */
	y_filetransfer *ft = (y_filetransfer *)hTransfer;

	DebugLog("[YahooFileDeny]");

	if ( !m_bLoggedIn || ft == NULL ) {
		DebugLog("[YahooFileDeny] Not logged-in or some other error!");
		return 1;
	}

	if (ft->y7) {
		DebugLog("[YahooFileDeny] Y7 Transfer detected.");
		//void yahoo_ft7dc_accept(int id, const char *buddy, const char *ft_token);
		yahoo_ft7dc_deny(ft->id, ft->who, ft->ftoken);
		return 0;
	}

	if (ft->ftoken != NULL) {
		struct yahoo_file_info *fi = (struct yahoo_file_info *)ft->files->data;

		DebugLog("[YahooFileDeny] DC Detected: Denying File Transfer!");
		yahoo_ftdc_deny(m_id, ft->who, fi->filename, ft->ftoken, 2);	
	}
	return 0;
}

////////////////////////////////////////////////////////////////////////////////////////
// FileResume - processes file renaming etc

int __cdecl CYahooProto::FileResume( HANDLE hTransfer, int* action, const PROTOCHAR** szFilename )
{
	y_filetransfer *ft = (y_filetransfer *)hTransfer;

	DebugLog("[YahooFileResume] Action: %d", *action);

	if ( !m_bLoggedIn || ft == NULL ) {
		DebugLog("[YahooFileResume] Not loggedin or some other error!");
		return 1;
	}

	ft->action = *action;

	DebugLog("[YahooFileResume] Action: %d", *action);

	if ( *action == FILERESUME_RENAME ) {
		DebugLog("[YahooFileResume] Renamed file!");
		
		FREE(ft->pfts.tszCurrentFile);
		ft->pfts.tszCurrentFile = _tcsdup( *szFilename );
	}	

	SetEvent( ft->hWaitEvent );
	return 0;
}

