/*	WebDownloader for X-Window
 *	Copyright (C) 1999-2002 Koshelev Maxim
 *	This Program is free but not GPL!!! You can't modify it
 *	without agreement with author. You can't distribute modified
 *	program but you can distribute unmodified program.
 *
 *	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.
 */

#include "dlist.h"
#include "ftpd.h"
#include "locstr.h"
#include "string.h"
#include "html.h"

#include "face/lod.h"
#include "face/edit.h"
#include "var.h"
#include "ntlocale.h"
#include "main.h"
#include "httpd.h"
#include "savedvar.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
#include <unistd.h>
#include <strings.h>
#include "signal.h"
#include "ping.h"
#include "filter.h"
#include "face/themes.h"
#include "hproxy.h"

using namespace d4x;

extern tMain aa;

void tTriger::reset() {
	old=curent;
};

void tTriger::clear() {
	old=-1;
	curent=0;
};

void tTriger::set(fsize_t a ) {
	curent=a;
};

void tTriger::update() {
	old=curent-1;
};

int tTriger::change() {
	return old==curent?0:1;
};

/* file locking functions */
/* return zero id all ok */
int d4x_f_lock(int fd){
	struct flock a;
	a.l_type=F_WRLCK;
	a.l_whence=SEEK_SET;
	a.l_start=0;
	a.l_len=1;
	if (fcntl(fd,F_SETLK,&a)==-1){
		switch(errno){
		case EINVAL: //not supported by fs
		case ENOLCK: //too many locks
			return(-1);
		};
		return(1);
	};
	return(0);
};

void d4x_f_unlock(int fd){
	struct flock a;
	a.l_type=F_UNLCK;
	a.l_whence=SEEK_SET;
	a.l_start=0;
	a.l_len=1;
	fcntl(fd,F_SETLK,&a);
};

/*-------------------------------------------------------------
  tDefaultWL
 --------------------------------------------------------------*/

tDefaultWL::tDefaultWL(){
	fd=-1;
	LOG=NULL;
	segments=NULL;
	fdlock=0;
	overlap_flag=0;
};

fsize_t tDefaultWL::read(void *dst,fsize_t len){
	if (fd>=0){
		fsize_t loaded_size=::read(fd,dst,len);
		return(loaded_size);
	};
	return(0);
};

int tDefaultWL::is_overlaped(){
	return(overlap_flag);
};

fsize_t tDefaultWL::write(const void *buff, fsize_t len){
	DBC_RETVAL_IF_FAIL(buff!=NULL,0);
	if (fd>=0){
		fsize_t cur=(fsize_t)(lseek(fd,0,SEEK_CUR));
		fsize_t saved_size=::write(fd,buff,len);
		if (saved_size<len)
			log(LOG_ERROR,_("Can't write to file"));
		
		if (segments && saved_size){
			overlap_flag=segments->insert(cur,cur+saved_size);
		};
		return (saved_size);
	};
	return 0;
};

void tDefaultWL::set_log(tLog *log){
	LOG=log;
};

void tDefaultWL::unlock_fd(){
	if (fdlock)
		d4x_f_unlock(fd);
	fdlock=0;
};

int tDefaultWL::lock_fd(){
	/*trying to lock*/
	switch(d4x_f_lock(fd)){
	case 0:
		fdlock=1;
		break;
	case 1:
		log(LOG_ERROR,_("File is already opened by another download!"));
		return(-1);
	case -1:
		log(LOG_WARNING,_("Filesystem do not support advisory record locking!"));
		log(LOG_WARNING,_("Will proceed without it but beware that you might have problems."));
	};
	return(0);
	/*end of trying */
};

void tDefaultWL::set_segments(tSegmentator *newseg){
	segments=newseg;
};

void tDefaultWL::fd_close(){
	if (fd>=0){
		unlock_fd();
		close(fd);		
	};
};

void tDefaultWL::set_fd(int newfd,int lockstate){
	if (fd>=0) fd_close();
	fdlock=lockstate;
	fd=newfd;
};


int tDefaultWL::get_fd(){
	return(fd);
};

fsize_t tDefaultWL::shift(fsize_t len,int mode){
	if (fd>=0){
//		printf("Shift to %i\n",shift);
		return(lseek(fd,len,mode));
	};
	return 0;
};

void tDefaultWL::truncate(){
	if (fd>=0){
		off_t a=lseek(fd,0,SEEK_CUR);
		ftruncate(fd,a);
		if (segments)
			segments->truncate(a);
//		log_printf(LOG_WARNING,"truncate file to %i",a);
	};
};

void tDefaultWL::log(int type,const char *str){
	DBC_RETURN_IF_FAIL(str!=NULL);
	if (LOG){
		LOG->add(str,type);
	};
};

void tDefaultWL::cookie_set(tCookie *cookie){
	COOKIES->lock.lock();
	tCookie *temp=COOKIES->find_exact(cookie);
	if (temp){
		if (cookie->get_time()<time(NULL)){
			COOKIES->del(temp);
			delete(temp);
		}else{
			temp->value=cookie->value;
			temp->set_time(cookie->get_time());
			temp->myown=1;
		};
		COOKIES->lock.unlock();
		delete(cookie);
		return;
	};
	cookie->myown=1;
	COOKIES->add(cookie);
	COOKIES->lock.unlock();
};


std::string tDefaultWL::cookie(const char *host, const char *path){
	DBC_RETVAL_IF_FAIL(host!=NULL,NULL);
	DBC_RETVAL_IF_FAIL(path!=NULL,NULL);
	COOKIES->lock.lock();
	tCookie *temp=COOKIES->find(host);
	std::string request_string;
	tDownload **dwn=my_pthread_key_get();
	if (dwn && *dwn && (*dwn)->config->cookie.get()){
		COOKIES->lock.unlock();
		return(copy_string((*dwn)->config->cookie.get()));
	};
	while (temp){
//		temp->print();
		if (begin_string(path,temp->path.c_str()))
			request_string+=temp->name+"="+temp->value+";";
		if (temp->get_time()<time(NULL)){
			COOKIES->del(temp);
			delete(temp);
			temp=COOKIES->find(host);
		}else
			temp=COOKIES->find(temp,host);
	};
	COOKIES->lock.unlock();
	return(request_string);
};


tDefaultWL::~tDefaultWL(){
	fd_close();
};

/*************Split Information****************/

d4xCondition::d4xCondition(){
	pthread_mutex_init(&my_mutex,NULL);
};

void d4xCondition::set_value(int val){
	pthread_mutex_lock(&my_mutex);
	value=val;
	pthread_mutex_unlock(&my_mutex);
};

int d4xCondition::get_value(){
	pthread_mutex_lock(&my_mutex);
	int val=value;
	pthread_mutex_unlock(&my_mutex);
	return(val);
};

int d4xCondition::dec(){
	pthread_mutex_lock(&my_mutex);
	value-=1;
	int val=value;
	pthread_mutex_unlock(&my_mutex);
	return(val);
};

int d4xCondition::inc(){
	pthread_mutex_lock(&my_mutex);
	value+=1;
	int val=value;
	pthread_mutex_unlock(&my_mutex);
	return(val);	
};

d4xCondition::~d4xCondition(){
	pthread_mutex_destroy(&my_mutex);
};


tSplitInfo::tSplitInfo(){
	FirstByte=LastByte=-1;
	next_part=parent=grandparent=NULL;
	thread_num=1;
	cond=NULL;
	reset();
};

void tSplitInfo::reset(){
	failed=prepared=run=0;
	stopcount=runcount=0;
};

tSplitInfo::~tSplitInfo(){
	if (next_part)	delete(next_part);
};

/**********************************************/
tDownload::tDownload() {
	sizequery=0;
	list_iter=NULL;
	fsearch=0;
	restart_from_begin=0;
	regex_match=NULL;
	config=NULL;
	next2stop=prev2update=next2update=NULL;
	protect=0;
	next=prev=NULL;
	split=NULL;
	who=NULL;
	CurrentLog=LOG=NULL;
	WL=NULL;
	editor=NULL;
//	config->ftp_recurse_depth=config->http_recurse_depth=1;
	SpeedLimit=NULL;
	finfo.size=-1;
	finfo.type=T_NONE;
	DIR=NULL;
	finfo.perm=S_IWUSR | S_IRUSR;
	Start=Pause=Difference=HistoryTime=0;
	Percent=0;
	myowner=NULL;
	thread_id=0;
	DIR=NULL;
	action=ACTION_NONE;
	ScheduleTime=0;
	segments=NULL;
	ALTS=NULL;
};

fsize_t tDownload::filesize(){
	if (finfo.size==0 && who)
		finfo.size=who->another_way_get_size();
	return(finfo.size>0?finfo.size:0);
};

fsize_t tDownload::get_loaded(){
	if (segments){
		return(segments->get_total());
	};
	return(0);
};

int tDownload::cmp(tAbstractSortNode *b){
	DBC_RETVAL_IF_FAIL(b!=NULL,0);
	tDownload *bb=(tDownload*)b;
	int r=strcmp(info.file.c_str(),bb->info.file.c_str());
	if (r)
		return r;
	r=strcmp(info.path.c_str(),bb->info.path.c_str());
	if (r)
		return r;
	r=strcmp(info.file.c_str(),bb->info.file.c_str());
	if (r)
		return r;
	return strcmp(info.params.c_str(),bb->info.params.c_str());
};

void tDownload::delete_editor() {
	if (editor)
		delete editor;
};

void tDownload::print_error(int err){
	switch(err){
	case ERROR_ACCESS:
		WL->log_printf(LOG_ERROR,
			      _("You have no permissions to create file at path %s"),
			      config->save_path.get());
		break;
	case ERROR_NO_SPACE:{
		WL->log_printf(LOG_ERROR,
			      _("You have no space at path %s for creating file"),
			      config->save_path.get());
		break;
	};
	case ERROR_DIRECTORY:{
		WL->log(LOG_ERROR,_("Directory already created!:))"));
		break;
	};
	};
};

void tDownload::print() {
	//do nothing
};

int tDownload::owner(){
	if (myowner)
		return(myowner->get_key());
	return(DL_ALONE);
};

void tDownload::remove_tmp_files(){
	d4x::Path path=make_path_to_file();
	if (!Name2Save.empty()){
		path/=Name2Save;
	}else{
		if (info.file.empty())
			path/=std::string(".")+CFG.DEFAULT_NAME;
		else
			path/=std::string(".")+info.file;
	};
	if (!info.params.empty()){
		path+=std::string("?")+info.params;
	};
	remove(path.c_str());
	path+=std::string(".segments");
	remove(path.c_str());
};

int tDownload::delete_file() {
	if (who==NULL) return(0);
	int rvalue=0;
	tFileInfo *D_FILE=who->get_file_info();
	if (D_FILE->type==T_FILE) {
		char *name,*guess;
		make_file_names(&name,&guess);
		if (remove(guess) && remove(name))
			rvalue=-1;
		delete[] name;
		delete[] guess;
	};
	return rvalue;
};

void tDownload::make_file_names(char **name, char **guess){
	DBC_RETURN_IF_FAIL(name!=NULL);
	DBC_RETURN_IF_FAIL(guess!=NULL);
	char *real_path=parse_save_path(config->save_path.get(),info.file.c_str());
	if (!Name2Save.empty())
		who->make_full_pathes(real_path,
				      Name2Save.c_str(),
				      name,guess);
	else
		who->make_full_pathes(real_path,name,guess);
	delete[] real_path;
};

int tDownload::try_to_lock_fdesc(){
	if (im_first) return(((tDefaultWL*)(WL))->lock_fd());
	return(0);
};

fsize_t tDownload::init_segmentator(int fdesc,fsize_t cursize,char *name){
	fsize_t rvalue=cursize;
	im_first=0;
	((tDefaultWL*)(WL))->set_fd(fdesc);
	if (segments==NULL){
		im_first=1;
		if (try_to_lock_fdesc()) return(-1);
		segments=new tSegmentator;
		char *segname=sum_strings(name,".segments",NULL);
		segments->init(segname);
		delete[] segname;
		tSegment *first_seg=segments->get_first();
		if (first_seg && first_seg->next!=NULL &&
		    (unsigned long int)rvalue<first_seg->end){
			WL->log(LOG_WARNING,"Segmentation info is wrong!");
			rvalue=0;
			ftruncate(fdesc,0);
			segments->truncate(0);
		}else{
			if ((first_seg==NULL || first_seg->next==NULL)){
				if (rvalue>0)
					segments->insert(0,rvalue);
				segments->truncate(rvalue);
			};
		};
		first_seg=segments->get_first();
		rvalue=first_seg?first_seg->end:0;
	};
	((tDefaultWL*)(WL))->set_segments(segments);
	return rvalue;
};

fsize_t tDownload::create_file() {
	if (!who) return -1;
	tFileInfo *D_FILE=who->get_file_info();
	int fdesc=-1;
	fsize_t rvalue=0;
	char *name,*guess;
	make_file_names(&name,&guess);

	make_dir_hier_without_last(name);
	switch (D_FILE->type) {
	case T_LINK:{ //this is a link
		WL->log(LOG_WARNING,_("Trying to create a link"));
		int err=symlink(D_FILE->body.get(),guess);
		if (err) {
			switch (errno){
			case EEXIST:
				WL->log(LOG_ERROR,_("Link already created!:))"));
				break;
			case EACCES:
				print_error(ERROR_ACCESS);
				break;
			default:
				WL->log(LOG_ERROR,_("Can't create link"));
				rvalue=-1;
				break;
			};
		};
		chmod(guess,D_FILE->perm  | S_IWUSR);
		break;
	};
	case T_FILE:{ //this is a file
		WL->log(LOG_WARNING,_("Trying to create a file"));
		if (restart_from_begin){
			fdesc=open(guess,O_RDWR|O_TRUNC,S_IRUSR | S_IWUSR );
		}else
			fdesc=open(guess,O_RDWR,S_IRUSR | S_IWUSR );
		if (fdesc<0) {
			if (restart_from_begin){
				fdesc=open(name,O_RDWR|O_CREAT|O_TRUNC,S_IRUSR | S_IWUSR );
			}else
				fdesc=open(name,O_RDWR|O_CREAT,S_IRUSR | S_IWUSR );
			if (fdesc<0) {
				switch (errno){
				case EACCES:
					print_error(ERROR_ACCESS);
					rvalue=-1;
					break;
				case ENOSPC:
					print_error(ERROR_NO_SPACE);
					rvalue=-1;
					break;
				default:
					WL->log(LOG_ERROR,_("Can't create file at the path:"));
					WL->log(LOG_ERROR,config->save_path.get());
					WL->log(LOG_ERROR,_("which has name:"));
					WL->log(LOG_ERROR,name);
					rvalue=-1;
				};
				break;
			};
			need_to_rename=1;
		}else{
			need_to_rename=0;
		};
		restart_from_begin=0;
		WL->log(LOG_OK,_("File was created!"));
		rvalue=init_segmentator(fdesc,lseek(fdesc,0,SEEK_END),name);
		break;
	};
	case T_DIR:{ //this is a directory
		WL->log(LOG_WARNING,_("Trying to create a dir"));
		if (info.file.empty()){
			print_error(ERROR_DIRECTORY);
			break;
		};
		int temp=0;
		if (strlen(guess))
			temp=mkdir(guess,S_IRWXU);
		if (temp) {
			if (errno!=EEXIST) {
				WL->log(LOG_ERROR,_("Can't create directory!"));
				rvalue=-1;
				break;
			};
			print_error(ERROR_DIRECTORY);
		};
		chmod(guess,D_FILE->perm | S_IWUSR |S_IXUSR);
		break;
	};
	case T_DEVICE:{ //this is device
		WL->log(LOG_WARNING,_("Downloader can't create devices..."));
		break;
	};
	default:{
		who->print_error(ERROR_UNKNOWN);
	};
	};
	delete[] name;
	delete[] guess;
	return rvalue;
};

int tDownload::file_type() {
	if (!who) return(T_FILE);
	tFileInfo *D_FILE=who->get_file_info();
	return (D_FILE->type);
};

void tDownload::set_date_file() {
	if (!who) return;
	tFileInfo *D_FILE=who->get_file_info();
	if (config->get_date) {
		char *name,*guess;
		make_file_names(&name,&guess);
		struct utimbuf dates;
		dates.actime=dates.modtime=D_FILE->date;
		utime(name,&dates);
		utime(guess,&dates);
		delete[] name;
		delete[] guess;
	};
	if (config->permisions)
		fchmod(((tDefaultWL *)(WL))->get_fd(),D_FILE->perm);
	else
		fchmod(((tDefaultWL *)(WL))->get_fd(),get_permisions_from_int(CFG.DEFAULT_PERMISIONS));
};

void tDownload::update_trigers() {
	Speed.update();
	ActStatus.update();
	Size.update();
	Attempt.update();
	Remain.update();
	finfo.oldtype=finfo.type-1;
};

void tDownload::make_file_visible(){
	if (who && need_to_rename){
		tFileInfo *D_FILE=who->get_file_info();
		if (D_FILE->type==T_FILE) {
			char *oldname,*newname;
			make_file_names(&oldname,&newname);
			rename(oldname,newname);
			delete[] oldname;
			delete[] newname;
		};
	};
};

void tDownload::set_default_cfg(){
	config->copy_ints(&(CFG.DEFAULT_CFG));
	config->http_recursing=config->http_recurse_depth==1?0:1;
	config->user_agent.set(CFG.USER_AGENT);
	config->Filter.set(CFG.DEFAULT_FILTER);
	if (myowner && myowner->PAPA)
		config->save_path.set(myowner->PAPA->save_path.get());
	else
		config->save_path.set(CFG.GLOBAL_SAVE_PATH);
	if (CFG.SOCKS_HOST){
		config->socks_host.set(CFG.SOCKS_HOST);
		config->socks_port=CFG.SOCKS_PORT;
		if (CFG.SOCKS_USER && CFG.SOCKS_PASS){
			config->socks_user.set(CFG.SOCKS_USER);
			config->socks_pass.set(CFG.SOCKS_PASS);
		};
	};
	config->proxy.type=CFG.FTP_PROXY_TYPE;
	config->proxy.no_cache=CFG.PROXY_NO_CACHE;
	if (CFG.USE_PROXY_FOR_FTP) {
		config->proxy.ftp_host.set(CFG.FTP_PROXY_HOST);
		config->proxy.ftp_port=CFG.FTP_PROXY_PORT;
		if (CFG.NEED_PASS_FTP_PROXY) {
			config->proxy.ftp_user.set(CFG.FTP_PROXY_USER);
			config->proxy.ftp_pass.set(CFG.FTP_PROXY_PASS);
		};
	};
	if (CFG.USE_PROXY_FOR_HTTP) {
		config->proxy.http_host.set(CFG.HTTP_PROXY_HOST);
		config->proxy.http_port=CFG.HTTP_PROXY_PORT;
		if (CFG.NEED_PASS_HTTP_PROXY) {
			config->proxy.http_user.set(CFG.HTTP_PROXY_USER);
			config->proxy.http_pass.set(CFG.HTTP_PROXY_PASS);
		};
	};
	if (CFG.NUMBER_OF_PARTS>1 && split==NULL){
		split=new tSplitInfo;
		split->NumOfParts=CFG.NUMBER_OF_PARTS;
	};
};

void tDownload::copy(tDownload *dwn){
	info=dwn->info;
	Description.set(dwn->Description.get());
	if (dwn->config){
		if (config==NULL) config=new tCfg;
		config->copy(dwn->config);
		restart_from_begin=dwn->restart_from_begin;
		config->referer.set(dwn->config->referer.get());
		Name2Save=dwn->Name2Save;
		config->save_path.set(dwn->config->save_path.get());
		config->log_save_path.set(dwn->config->log_save_path.get());
	}else{
		if (config) delete(config);
		config=NULL;
	};
	if (dwn->split==NULL && split)
		delete(split);
	if (dwn->split){
		if (split==NULL) split=new tSplitInfo;
		split->NumOfParts=dwn->split->NumOfParts;
	};
};

d4x::Path tDownload::make_path_to_file(){
	int noconfig=0;
	if (config==NULL){
	    noconfig=1;
	    config=new tCfg;
	    set_default_cfg();
	};
	
	char *real_path=parse_save_path(config->save_path.get(),info.file.c_str());
	d4x::Path rval(real_path);
	delete [] real_path;
	
	if (info.proto==D_PROTO_HTTP && config->http_recursing){
		if (config->leave_server)
			rval/=d4x::Path(info.host)/info.path;
		else
			rval/=info.path;
	};
	if (noconfig){
	    delete(config);
	    config=NULL;
	};
	return(rval);
};

d4x::Path tDownload::create_new_file_path(){
	if (info.mask) 
		return(info.path);
	return (info.path/info.file);
};

d4x::Path tDownload::create_new_save_path(){
	if (info.mask)
		return d4x::Path(config->save_path.get());

	if (config->save_path.get()) {
		return(d4x::Path(config->save_path.get())/(Name2Save.empty()?info.file:Name2Save));
	};
	return(d4x::Path(Name2Save.empty()?info.file:Name2Save));
};

void tDownload::convert_list_to_dir() {
	if (who==NULL) {
		return;
	};
	tFtpDownload *tmp=(tFtpDownload *)(who);
	tStringList *dir=tmp->dir();
	if (DIR) {
		DIR->done();
	} else {
		DIR=new tDList(DL_TEMP);
		DIR->init(0);
	};
	if (dir==NULL || dir->first()==NULL) {
		return;
	};
	d4x::Path path=create_new_file_path();
	d4x::Path savepath=create_new_save_path();
	tString *temp=dir->last();
	tFileInfo *prom=new tFileInfo;
	while (temp) {
		ftp_cut_string_list(temp->body,prom,1);
		if (prom->name.get() && !equal(prom->name.get(),".")
		    && !equal(prom->name.get(),"..")
		    && (prom->type!=T_DIR || config->ftp_recurse_depth!=2)
		    && (prom->type==T_DIR || !info.mask || check_mask(prom->name.get(),info.file.c_str()))) {
			tDownload *onenew=new tDownload;
			d4x::URL *addrnew=&(onenew->info);
			onenew->config=new tCfg;
			onenew->config->isdefault=0;
			addrnew->path=path;
			if (prom->type==T_DIR){
				if (info.mask){
					addrnew->path/=std::string(prom->name.get());
					addrnew->file=info.file;
				}else{
					addrnew->path/=std::string(prom->name.get());
				};
				d4x::Path SavePath=savepath/std::string(prom->name.get());
				onenew->config->save_path.set(SavePath.c_str());
				addrnew->mask=info.mask;
			} else {
				addrnew->file=prom->name.get();
				onenew->config->save_path.set(savepath.c_str());
			};
			addrnew->copy_host(info);

			onenew->config->copy(config);
			onenew->config->ftp_recurse_depth = config->ftp_recurse_depth ? config->ftp_recurse_depth-1 : 0;
			onenew->config->http_recurse_depth = config->http_recurse_depth;
			onenew->set_split_count(split?split->NumOfParts:0);
			if (CFG.RECURSIVE_OPTIMIZE) {
				onenew->finfo.type=prom->type;
				onenew->finfo.size=prom->size;
				onenew->finfo.date=prom->date;
				if (config->permisions) onenew->finfo.perm=prom->perm;
				if (onenew->finfo.type==T_LINK) {
					if (config->follow_link==1){
						onenew->finfo.type=T_NONE;
						d4x::Path tmppath=onenew->info.path/std::string(prom->body.get());
						std::string::size_type a=tmppath.rfind('/');
						if (a!=std::string::npos){
							onenew->info.file=tmppath.substr(a+1);
							onenew->info.path=tmppath.substr(0,a);
						}else{
							onenew->info.file=tmppath;
							onenew->info.path="";
						};
						onenew->finfo.size=0; //follow symbolik link size is unknown yet
						onenew->finfo.date=0; //date is unknown too :-(
					}else{
						onenew->finfo.body.set(prom->body.get());
					};
				};
			};
			if (addrnew->is_valid()){
				DIR->insert(onenew);
			}else
				delete(onenew);
		};
		dir->del(temp);
		delete(temp);
		temp=dir->last();
		prom->name.set(NULL);
		prom->body.set(NULL);
	};
	delete (prom);
};


void make_dir_hier(char *path) {
	DBC_RETURN_IF_FAIL(path!=NULL);
	char *temp=path;
	while (temp) {
		temp=index(temp+1,'/');
		if (temp) *temp=0;
		mkdir(path,S_IRWXU);
		if (temp) *temp='/';
	};
};

void make_dir_hier_without_last(char *path) {
	DBC_RETURN_IF_FAIL(path!=NULL);
	char *temp=rindex(path,'/');
	if (temp){
		*temp=0;
		make_dir_hier(path);
		*temp='/';
	};
};

static int is_subdir(const char *path, const char *subdir){
	if (begin_string(subdir,path)){
		int len=strlen(path);
		if (subdir[len]=='/' || subdir[len]==0)
			return(1);
	};
	return(0);
};

int tDownload::http_check_settings(const d4x::URL &what){
	if (what.host!=info.host){
		return (config->leave_server);
	};
	if (config->dont_leave_dir==0 || is_subdir(info.path.c_str(),what.path.c_str()))
		return 1;
	return 0;
};

void tDownload::convert_list_to_dir2(tQueue *dir) {
	if (!dir) return;
	if (DIR) {
		DIR->done();
		DIR->init(0);
	} else {
		DIR=new tDList(DL_TEMP);
		DIR->init(0);
	};
	tHtmlUrl *temp=(tHtmlUrl *)dir->last();
	d4x::Filter filter;
	if (config->Filter.get())
		filter=FILTERS_DB.find(config->Filter.get());
	while (temp) {
		tDownload *onenew=new tDownload;
		onenew->config=new tCfg;
		onenew->config->isdefault=0;
		onenew->config->save_path.set(config->save_path.get());
		if (onenew->config->save_path.get())
			normalize_path(onenew->config->save_path.get());
		
		onenew->config->http_recursing=1;
		onenew->config->copy(config);
		onenew->set_split_count(split?split->NumOfParts:0);
		onenew->config->http_recurse_depth = config->http_recurse_depth ? config->http_recurse_depth-1 : 0;
		onenew->config->ftp_recurse_depth = config->ftp_recurse_depth;
		onenew->config->referer.set(std::string(info).c_str());
		onenew->Description.set(temp->descr);
		if (temp->info.is_valid() && http_check_settings(temp->info)){
			onenew->info=temp->info;
			if (!filter.empty()){
				if (filter.match(onenew->info)){
					onenew->info.tag.clear(); //this info is not needed any more
					DIR->insert(onenew);
				}else{
					delete(onenew);
				};
			}else{
				DIR->insert(onenew);
			};
		}else{
			delete(onenew);
		};
		dir->del(temp);
		delete(temp);
		temp=(tHtmlUrl *)dir->last();
	};
};

void tDownload::save_to_config(int fd){
	f_wstr_lf(fd,"Download:");
	f_wstr_lf(fd,"URL:");
	f_wstr_lf(fd,std::string(info).c_str());
	if (split)
		write_named_integer(fd,"SplitTo:",split->NumOfParts);
	if (config && config->isdefault==0)
		config->save_to_config(fd);
	if (ScheduleTime)
		write_named_time(fd,"Time:",ScheduleTime);
	if (HistoryTime)
		write_named_time(fd,"HTime:",HistoryTime);
	int tmpid=owner();
	if (tmpid==DL_SIZEQUERY)
		write_named_integer(fd,"State:",action);
	else
		write_named_integer(fd,"State:",tmpid);
	if (finfo.size>0){
		write_named_fsize(fd,"size:",finfo.size);
		if (Size>0)
			write_named_fsize(fd,"loaded:",Size);
	};
	if (Description.get())
		write_named_string(fd,"Description:",Description.get());
	if (!Name2Save.empty())
		write_named_string(fd,"SaveName:",Name2Save.c_str());
	if (protect)
		write_named_integer(fd,"protect:",protect);
	if (ALTS)
		ALTS->save_to_config(fd);
	if (restart_from_begin)
		write_named_integer(fd,"restart_from_begin:",restart_from_begin);
	f_wstr_lf(fd,"EndDownload:");
};

int tDownload::load_from_config(int fd){
	tSavedVar table_of_fields[]={
		{"Url:",	SV_TYPE_URL,	&(info)},
		{"State:",	SV_TYPE_INT,	&status},
		{"Time:",	SV_TYPE_TIME,	&ScheduleTime},
		{"HTime:",	SV_TYPE_TIME,	&HistoryTime},
		{"Cfg:",	SV_TYPE_CFG,	&config},
		{"SavePath:",	SV_TYPE_PSTR,	&(config->save_path)},
		{"SaveName:",	SV_TYPE_STDSTR,	&(Name2Save)},
		{"SplitTo:",	SV_TYPE_SPLIT,	&(split)},
		{"size:",	SV_TYPE_LINT,	&(finfo.size)},
		{"loaded:",	SV_TYPE_FSIZE_TRIGER,	&(Size)},
		{"protect:",	SV_TYPE_INT,	&(protect)},
		{"Description:",SV_TYPE_PSTR,	&(Description)},
		{"Alt:",	SV_TYPE_ALT,	&(ALTS)},
		{"restart_from_begin:",SV_TYPE_INT,&restart_from_begin},
		{"EndDownload:",SV_TYPE_END,	NULL}
	};
	char buf[MAX_LEN];
	while(f_rstr(fd,buf,MAX_LEN)>0){
		unsigned int i;
		for (i=0;i<sizeof(table_of_fields)/sizeof(tSavedVar);i++){
			if (equal_uncase(buf,table_of_fields[i].name)){
				if (table_of_fields[i].type==SV_TYPE_END){
					if (finfo.size>0)
						Percent=(fsize_t(Size)*float(100))/float(finfo.size);
					return(0);
				}else{
					if (sv_parse_file(fd,&(table_of_fields[i]),buf,MAX_LEN))
						return(-1);
				};
			};
		};
	};
	return -1;
};

void tDownload::check_local_file_time(){
	if (split==NULL){
		struct stat tmpstat;
		fstat(((tDefaultWL *)WL)->get_fd(),&tmpstat);
		who->set_local_filetime(tmpstat.st_mtime);
	};
};

void tDownload::http_postload(){
	d4xContentDisposition *cd=((tHttpDownload *)who)->get_content_disp();
	if (cd){
		if (cd->filename.get()){
			char *buf=new char[1000];
			char *oldname,*newname;
			make_file_names(&oldname,&newname);
			char *tmp=rindex(newname,'/');
			if (tmp){
				*tmp=0;
				char *a=tmp;
				tmp=sum_strings(newname,"/",cd->filename.get(),NULL);
				*a='/';
			}else{
				tmp=copy_string(cd->filename.get());
			};
			char *cpfrom;
			if (need_to_rename){
				cpfrom=oldname;
			}else
				cpfrom=newname;
				 
			WL->log_printf(LOG_WARNING,_("Trying to copy %s to %s due 'Content-Disposition'"),
				       cpfrom,tmp);
			if (file_copy(cpfrom,tmp,buf,1000))
				WL->log(LOG_ERROR,_("Error during copying!"));
			delete[] buf;
			delete[] tmp;
			delete[] newname;
			delete[] oldname;
		};
	};
};

void tDownload::download_completed(int type) {
	who->done();
	im_last=1;
	if (split && split->cond && split->cond->dec()!=0)
		im_last=0;
	if (split==NULL)
		WL->truncate();
	switch (type){
	case D_PROTO_HTTP:{
		if (im_last){
			http_recurse();
			http_postload();
			if (split && split->grandparent!=this){
				if (split->grandparent->DIR) delete(split->grandparent->DIR);
				split->grandparent->DIR=DIR;
				DIR=NULL;
			};
		};
		break;
	};
	case D_PROTO_FTP:{
		if (finfo.type==T_DIR && config->ftp_recurse_depth!=1)
			convert_list_to_dir();
	};
	};
	WL->log(LOG_OK,_("Downloading was successefully completed!"));
	
	if (im_last && CFG.WRITE_DESCRIPTION && info.proto!=D_PROTO_SEARCH){
		/* add string into Descript.ion file */
		d4x::Path path=make_path_to_file();
		path/=std::string("Descript.ion");
		int fd=open(path.c_str(),O_WRONLY|O_CREAT,S_IRUSR | S_IWUSR );
		lseek(fd,0,SEEK_END);
		/* locking file exclusively */
		struct flock a;
		a.l_type=F_WRLCK;
		a.l_whence=SEEK_SET;
		a.l_start=0;
		a.l_len=1;
		fcntl(fd,F_SETLKW,&a);
		/* writing file */
		if (Name2Save.empty())
			f_wstr(fd,info.file.c_str());
		else
			f_wstr(fd,Name2Save.c_str());
		f_wstr(fd," - ");
		if (Description.get()) {
			f_wstr(fd,Description.get());
			f_wstr(fd," [");
			f_wstr(fd,std::string(info).c_str());
			f_wchar(fd,']');
		} else {
			f_wstr(fd,std::string(info).c_str());
		};
		f_wchar(fd,'\n');
		/* unlocking file */
		a.l_type=F_UNLCK;
		fcntl(fd,F_SETLK,&a);
		close(fd);
	};
	if (im_last){
		make_file_visible();
		set_date_file();
	};
	if (config->sleep_before_complete)
		sleep(config->time_for_sleep);
	D4X_UPDATE.add(this,DOWNLOAD_COMPLETE);
};

void tDownload::download_failed() {
	if (who)
		who->done();
	if (segments)
		segments->save();
	WL->log(LOG_ERROR,_("Downloading was failed..."));
	D4X_UPDATE.add(this,DOWNLOAD_FATAL);
};

static void _html_parser_destroy_(void *a){
	tHtmlParser *b=(tHtmlParser *)a;
	if (b) delete(b);
};

static void _html_parser_dir_destroy_(void *a){
	tStringList *b=(tStringList *)a;
	if (b) delete(b);
};

void tDownload::http_recurse() {
	tHttpDownload *httpd=(tHttpDownload *)(who);
	char *type=httpd->get_content_type();
	if ((config->change_links || config->http_recurse_depth!=1) &&
	    type && begin_string_uncase(type,"audio/x-pn-realaudio")){
		d4x::Path a(make_path_to_file());
		a/=info.file;
		if (!info.params.empty())
			a+=std::string("?")+info.params;
		int fd=open(a.c_str(),O_RDWR,S_IRUSR | S_IWUSR);
		if (fd>=0){
			char *buf=new char[MAX_LEN];
			*buf=0;
			f_rstr(fd,buf,MAX_LEN);
			tQueue *dir=new tQueue;
			pthread_cleanup_push(_html_parser_dir_destroy_,dir);
			tHtmlUrl *node=new tHtmlUrl;
			info.tag.clear();
			node->info=std::string(buf);
			delete[] buf;
			dir->insert(node);
			if (config->http_recurse_depth!=1)
				convert_list_to_dir2(dir);
			/* FIXME: what about changing link? */
			pthread_cleanup_pop(1);
		};
		close(fd);
	};
	if ((config->change_links || config->http_recurse_depth!=1) &&
	    type && begin_string_uncase(type,"text/html")){
		tQueue *dir=new tQueue;
		tHtmlParser *html=new tHtmlParser;
		if (who)
			html->set_content_type(((tHttpDownload*)who)->get_content_type());
		pthread_cleanup_push(_html_parser_dir_destroy_,dir);
		pthread_cleanup_push(_html_parser_destroy_,html);
		dir->init(0);
		if (config->change_links){
			d4x::Path a(make_path_to_file());
			a/=info.file;
			if (info.params.empty())
				a+=std::string(".fl");
			else
				a+=std::string("?")+info.params;
			html->out_fd=open(a.c_str(),O_RDWR|O_CREAT|O_TRUNC,S_IRUSR | S_IWUSR );
			html->leave=config->leave_server;
			html->parse(WL,dir,info,config->quest_sign_replace);
			if (html->out_fd){
				char *name,*guess;
				make_file_names(&name,&guess);
				if (need_to_rename){
					delete[] guess;
					remove(name);
					rename(a.c_str(),name);
					delete[] name;
				}else{
					delete[] name;
					remove(guess);
					rename(a.c_str(),guess);
					delete[] guess;
				};
				((tDefaultWL*)(WL))->set_fd(html->out_fd,0);
			};
		}else{
			html->out_fd=-1;
			html->parse(WL,dir,info,config->quest_sign_replace);
		};
		pthread_cleanup_pop(1);
		if (config->http_recurse_depth!=1){
			convert_list_to_dir2(dir);
		};
		pthread_cleanup_pop(1);
	}else if(type && begin_string_uncase(type,"text/html")){
		tQueue *dir=new tQueue;
		pthread_cleanup_push(_html_parser_dir_destroy_,dir);
		tHtmlParser *html=new tHtmlParser;
		pthread_cleanup_push(_html_parser_destroy_,html);
		if (who)
			html->set_content_type(((tHttpDownload*)who)->get_content_type());
		dir->init(0);
		html->out_fd=-1;
		html->parse(WL,dir,info,0);
		pthread_cleanup_pop(1);

//look for meta tag to redirect
		
		tHtmlUrl *temp=(tHtmlUrl *)dir->last();
		while(temp){
			if (strcasecmp(temp->info.tag.c_str(),"meta")==0){
				finfo.type=T_REDIRECT;
				RedirectURL=temp->info;
				break;
			};
			temp=(tHtmlUrl *)dir->next();
		};
		pthread_cleanup_pop(1);
	};
};

void tDownload::export_socket(tDownloader *what){
	if (WL->is_overlaped()) return;
	SocketPtr sock=what->export_ctrl_socket();
	if (sock.get()){
		GVARS.SOCKETS->insert(info,sock);
	};
};

void tDownload::http_check_redirect(bool removefiles){
	char *newurl=newurl=who->get_new_url();
	if (config->change_links && (config->http_recurse_depth!=1 ||
	    config->http_recursing)){
		/* wtrite simply HTML file for redirection */
		WL->shift(0);
		static char *redirect_html="<HTML><HEAD><TITLE>"
			"D4X Redirect page</TITLE><META http-equiv=\"Refresh\" "
			"CONTENT=\"1;url=";
		static char *redirect_html_end="\"></HEAD></HTML>";
		WL->write(redirect_html,strlen(redirect_html));
		d4x::URL tmpadr=fix_url_global(newurl,
					     info,
					     ((tDefaultWL *)(WL))->get_fd(),
					     config->leave_server);
		WL->write(redirect_html_end,strlen(redirect_html_end));
		who->done();
		make_file_visible();
	}else{
		who->done();
		if (removefiles)
			delete_file();
	};
	delete[] newurl;
	d4x::URL addr=redirect_url();
	finfo.type=T_REDIRECT;
	if (addr.is_valid()){
		if (config->http_recursing ||config->http_recurse_depth!=1){
			if (equal_uncase(addr.host.c_str(),info.host.c_str()) ||
			    config->leave_server){
				finfo.type=T_REDIRECT;
			}else{
				finfo.type=T_FILE;
			};
		};
		d4x::Filter filter;
		if (config->Filter.get())
			filter=FILTERS_DB.find(config->Filter.get());
		if (!filter.empty() && finfo.type==T_REDIRECT){
			if (filter.match(addr))
				finfo.type=T_REDIRECT;
			else{
				finfo.type=T_FILE;
				WL->log(LOG_WARNING,_("Redirection blocked by filter"));
			};
		};
	};
	WL->log(LOG_WARNING,_("Redirect detected..."));
};

void tDownload::download_http_size(){
	WL->log(LOG_WARNING,_("Size detection only!"));
	SocketPtr s=GVARS.SOCKETS->find_and_remove(info);
	if (who->init(info,config,s)==0) {
		who->init_download(info.path,info.file);
		finfo.size=who->get_size_only();
		finfo.type=T_FILE;
		if (((tHttpDownload*)who)->persistent())
			export_socket(who);
	};
	D4X_UPDATE.add(this,DOWNLOAD_COMPLETE);
};

void tDownload::download_ftp_size(){
	WL->log(LOG_WARNING,_("Size detection only!"));
	SocketPtr s=GVARS.SOCKETS->find_and_remove(info);
	if (who->init(info,config,s)==0) {
		who->init_download(info.path,info.file);
		status=DOWNLOAD_SIZE_WAIT;
		fsize_t size=who->get_size();
		if (size>=0) {
			finfo.size=size;
			finfo.type=file_type();
		};
	};
	export_socket(who);
	D4X_UPDATE.add(this,DOWNLOAD_COMPLETE);
};

void tDownload::download_http() {
	if (!who) who=new tHttpDownload(WL);
	if (sizequery){
		sizequery=0;
		download_http_size();
		return;
	};
	config->split=split?1:0;
	SocketPtr s=GVARS.SOCKETS->find_and_remove(info);
	if (who->init(info,config,s)) {
		download_failed();
		return;
	};
	who->init_download(info.path,info.file);
	/* We need to know size of already loaded file
	 * but I think if file not found we need to delete it
	 * because in http name of file may be specify 
	 * in http answer
	 */
	fsize_t CurentSize=create_file();
	if (CurentSize<0) {
		download_failed();
		return;
	};
	((tDefaultWL*)(WL))->unlock_fd();
	if (split && !im_first)
		CurentSize=split->FirstByte;
	fsize_t SizeDecrement=CurentSize>0 && segments->one_segment()?1:0;
	if (SizeDecrement) ((tHttpDownload*)who)->pass_first_segment();
	who->set_loaded(CurentSize-SizeDecrement);
	CurentSize=who->rollback();
	if (split) split->FirstByte=CurentSize;
	
	fsize_t size=who->get_size();
	if (!im_first && split && split->FirstByte>0 && who->reget()==0){
		WL->log(LOG_WARNING,_("Multithreaded downloading is not possible due to server limitations (resuming not supported)"));
		download_completed(D_PROTO_HTTP);
		return;
	};
	/* In the case if file already loaded
	 */
	if (size==CurentSize+SizeDecrement && size>0 && config->rollback==0) {
		check_local_file_time();
		if (!who->remote_file_changed()){
			finfo.size=size;
			finfo.type=T_FILE;
			WL->log(LOG_OK,_("Local file is seems to be equal to remote one"));
			download_completed(D_PROTO_HTTP);
			return;
		};
	};
	/* There are must be procedure for removing file
	 * wich execute if CurentSize==0
	 */
	if (size==-1) {
		http_check_redirect(CurentSize<=0);
		D4X_UPDATE.add(this,DOWNLOAD_COMPLETE);
		return;
	};
	if (im_first && ((tDefaultWL*)(WL))->lock_fd()){
		download_failed();
		return;
	};

	if (size<0 && split==NULL && CurentSize==0) {
		if (CurentSize==0 && segments) segments->complete();
		if (delete_file())
			WL->log(LOG_WARNING,_("It is strange that we can't delete file which just created..."));
	};

	if (size<-1) {
		WL->log(LOG_WARNING,_("File not found"));
		download_failed();
		return;
	};
	finfo.size=size;
	finfo.type=T_FILE;
	/* there we need to create file again
	 * if CurentSize==0
	 */
	if (split && im_first){
		if (who->reget())
			prepare_splits();
		else
			split->LastByte=size;
	};
	check_local_file_time();
	fsize_t SIZE_FOR_DOWNLOAD=who->reget()?size-CurentSize:size;
	SIZE_FOR_DOWNLOAD=(split && split->LastByte>0)?split->LastByte-split->FirstByte:SIZE_FOR_DOWNLOAD;
	Difference=0;
	status=DOWNLOAD_GO;
	if (who->download(SIZE_FOR_DOWNLOAD)) {
		download_failed();
		return;
	};
	if (!split && ((tHttpDownload*)who)->persistent())
		export_socket(who);
	download_completed(D_PROTO_HTTP);
};

void tDownload::remove_links(d4xSearchEngine *engine){
	d4xFtpRegex ftpr;
	regex_t regs[2];
	ftpr.compile(engine->match.get(),info.file.c_str());
	ftpr.compile_regexes(regs);
	tDownload *tmp=DIR->last();
	tDList *nDIR=new tDList;
	while(tmp){
		DIR->del(tmp);
		char *a=ftpr.cut(std::string(tmp->info).c_str(),regs);
		if (a){
//			printf("%s\n",a);
			tmp->info=std::string(a);
			delete[] a;
			if (nDIR->find(tmp->info))
				delete(tmp);
			else{
				nDIR->insert(tmp);
			};
		}else{
			delete(tmp);
		};
		tmp=DIR->last();
	};
	delete(DIR);
	DIR=nDIR;
};

static void _tmp_sort_free_(void *buf){
	d4xPing *tmp=(d4xPing *)buf;
	delete(tmp);
};

static int _cmp_pinged_hosts_(tNode *a,tNode *b){
	tDownload *aa=(tDownload *)a;
	tDownload *bb=(tDownload *)b;
	float rval=(aa->Percent/aa->Attempt)-(bb->Percent/bb->Attempt);
	if (rval==0) return(0);
	return(rval>0?1:-1);
};

void tDownload::sort_links(){
	if (DIR==NULL || DIR->count()<=0) return;
	WL->log(LOG_OK,_("Sorting started"));
	int i=0;
	while (i<CFG.SEARCH_PING_TIMES){
		WL->log_printf(LOG_OK,_("Pinging (atempt %i of %i)"),i+1,CFG.SEARCH_PING_TIMES);
		if (ActStatus==0){ //clear previous percentage for non comulative ping
			tDownload *a=DIR->last();
			while(a){
				a->Percent=0;
				a->Attempt=0;
				a=DIR->next();
			};
		};
		d4xPing *tmp=new d4xPing;
		pthread_cleanup_push(_tmp_sort_free_,tmp);
		tmp->run(DIR,WL);
		pthread_cleanup_pop(1);
		DIR->sort(_cmp_pinged_hosts_);
		if (!i)
			ActStatus=1;
		i+=1;
	};
};

static void _tmp_info_remove_(void *addr){
	d4x::URL *info=(d4x::URL *)addr;
	delete(info);
};

void tDownload::ftp_search_sizes(){
	WL->log(LOG_WARNING,_("Trying to determine filesizes"));
	delete(who);
	who=NULL;

	tDownload *tmp=DIR->last();
	config->number_of_attempts=5;
	while(tmp){
		tDownload *nexttmp=DIR->next();
		if (config->proxy.ftp_host.get() && config->proxy.type)
			who=new tProxyDownload(WL);
		else
			who=new tFtpDownload(WL);
		if (who->init(tmp->info,config)){
			WL->log(LOG_ERROR,"Can't determine filesize");
			tmp->finfo.size=-1;
		}else{
			who->init_download(tmp->info.path,
					   tmp->info.file);
			tmp->finfo.size=who->get_size_only();
		};
		who->done();
		delete(who);
		who=NULL;
		tmp=nexttmp;
	};
};

void tDownload::ftp_search() {
	/* FIXME: prepare new url for ftp search */
	if (action!=ACTION_REPING){
		Size=0;
		d4xSearchEngine *engine=D4X_SEARCH_ENGINES.get_next_used_engine(NULL);
		tDList *TMP_DIR=NULL;
		while(engine){
			d4x::URL *tmpinfo=new d4x::URL;
			fsize_t size=0;
			int who_download_status=0;
			pthread_cleanup_push(_tmp_info_remove_,tmpinfo);
			config->change_links=0;
			engine->prepare_url(*tmpinfo,finfo.size,info.file.c_str(),CFG.SEARCH_PERSERVER);
			pthread_cleanup_pop(0);
			if (who->init(*tmpinfo,config)) {
				delete(tmpinfo);
				download_failed();
				return;
			};
			pthread_cleanup_push(_tmp_info_remove_,tmpinfo);
			who->init_download(tmpinfo->path,tmpinfo->file);
			who->set_loaded(0);
			size=who->get_size();
			pthread_cleanup_pop(0);
			if (size<=-1) {
				WL->log(LOG_WARNING,_("Searching failed"));
				delete(tmpinfo);
				download_failed();
				return;
			};
			pthread_cleanup_push(_tmp_info_remove_,tmpinfo);
			finfo.type=T_FILE;
			Start=Pause=time(NULL);
			Difference=0;
			status=DOWNLOAD_GO;
			who_download_status=who->download(0);
			pthread_cleanup_pop(0);
			if (who_download_status) {
				delete(tmpinfo);
				download_failed();
				return;
			};
			pthread_cleanup_push(_tmp_info_remove_,tmpinfo);
			config->http_recurse_depth=2;
			config->leave_server=1;
			d4x::URL *a=new d4x::URL(info);
			info=*tmpinfo;
			pthread_cleanup_push(_tmp_info_remove_,a);
			http_recurse();
			info=*a;
			pthread_cleanup_pop(1);
			who->done();
			remove_links(engine);
			if (TMP_DIR){
				if (DIR){
					tDownload *dwn=DIR->first();
					while(dwn){
						DIR->del(dwn);
						TMP_DIR->insert_if_absent(dwn);
						dwn=DIR->first();
					};
					delete(DIR);
					DIR=NULL;
				};
			}else{
				TMP_DIR=DIR;
				DIR=NULL;
			};
			Size=TMP_DIR->count();
			engine=D4X_SEARCH_ENGINES.get_next_used_engine(engine);
			pthread_cleanup_pop(1);
			if (Size>=CFG.SEARCH_ENTRIES)
				break;
		};
		if (TMP_DIR && !DIR)
			DIR=TMP_DIR;
	}else{
		Size=DIR->count();
	};
	if (finfo.size<0 && DIR!=NULL && DIR->count()>0)
		ftp_search_sizes();
	sort_links();
	WL->log(LOG_OK,_("Search had been completed!"));
	D4X_UPDATE.add(this,DOWNLOAD_COMPLETE);
};


void tDownload::download_ftp(){
	WL->log(LOG_WARNING,_("Was Started!"));
	if (!who) who=new tFtpDownload(WL);
	if (sizequery){
		sizequery=0;
		download_ftp_size();
		return;
	};

	if (finfo.type==T_LINK && config->follow_link!=2) {
		WL->log(LOG_WARNING,_("It is a link and we already load it"));
		who->init_download("",info.file);
		who->set_file_info(&finfo);
		create_file();
		set_date_file();
		download_completed(D_PROTO_FTP);
		return;
	};

	config->split=split?1:0;
	SocketPtr s=GVARS.SOCKETS->find_and_remove(info);
	if (who->init(info,config,s)) {
		download_failed();
		return;
	};
	who->init_download(info.path,info.file);
	if (finfo.size<0 || finfo.type==T_NONE) {
		status=DOWNLOAD_SIZE_WAIT;
		fsize_t size=who->get_size();
		if (size<0) {
			WL->log(LOG_ERROR,_("File not found"));
			if (info.mask){
				download_failed();
				return;
			};
			WL->log(LOG_ERROR,_("Trying to work without CWD"));
			((tFtpDownload *)(who))->dont_cwd();
			finfo.type=T_FILE;
		}else{
			finfo.size=size;
			finfo.type=file_type();
		};
	} else {
		who->set_file_info(&(finfo));
	};
	if (finfo.type==T_LINK){
		finfo.size=0;
		if (config->follow_link==2){
			tFileInfo *i=who->get_file_info();
			i->type=finfo.type=T_FILE;
		};
	};
	fsize_t CurentSize=0;
	if (info.mask==0){
		CurentSize=create_file();
		//if it was link
		finfo.type=file_type();
	};

	if (finfo.type==T_DEVICE) {
		download_completed(D_PROTO_FTP);
		return;
	};
	if (CurentSize<0) {
		download_failed();
		return;
	};
	if (finfo.size && CurentSize>finfo.size)
		CurentSize=finfo.size;
	
	if (split && finfo.type==T_FILE){
		if (im_first){
			if (who->reget())
				prepare_splits();
			else
				split->LastByte=CurentSize;
		}else{
			CurentSize=split->FirstByte;
		};
	};
	check_local_file_time();
	who->set_loaded(CurentSize);
	if (split) WL->shift(CurentSize);
	fsize_t SIZE_FOR_DOWNLOAD=(split && split->LastByte>0)?split->LastByte-split->FirstByte:0;
	Difference=0;
	status=DOWNLOAD_GO;
	if (who->download(SIZE_FOR_DOWNLOAD)) {
		download_failed();
		return;
	};
	if (config->dont_send_quit) export_socket(who);
	if (config->follow_link==1 && finfo.type==T_LINK)
		finfo.type=T_REDIRECT;
	download_completed(D_PROTO_FTP);
};

#define SPLIT_MINIMUM_PART 5120

int tDownload::find_best_split(){
	tSegment *holes=segments->to_holes(finfo.size);
	tSegment *tmp=holes->next;
	tSegment *best=holes;
	fsize_t maxlen=holes->end-holes->begin;
	while(tmp){
		fsize_t l=tmp->end-tmp->begin;
		if (l>maxlen){
			maxlen=l;
			best=tmp;
		};
		tmp=tmp->next;
	};
	split->FirstByte=split->LastByte=0;
	// if this part is already loading by another thread then we
	// need to load only part of this chunk
	tDownload *gp=split->grandparent;
	while (gp){
		if (gp!=this &&
		    ((gp->split->LastByte>=best->begin && gp->split->LastByte<=best->end) ||
		     (gp->split->FirstByte>=best->begin && gp->split->FirstByte<=best->end)||
		     (gp->split->FirstByte<=best->begin && gp->split->LastByte>=best->end)))
			break;
		gp=gp->split->next_part;
	};
	if (gp){
		split->FirstByte=best->begin+maxlen/2;
	}else{
		split->FirstByte=best->begin;
	};
	split->LastByte=best->end;
	int completed=0;
	if (best->begin==0) completed=1;
	while(holes){
		tmp=holes->next;
		delete(holes);
		holes=tmp;
	};
	int k=(info.proto==D_PROTO_FTP && (config==NULL || config->proxy.ftp_host.get()==NULL || config->proxy.type==0))?12:8;
	if (split->LastByte-split->FirstByte>SPLIT_MINIMUM_PART*k && completed==0){
		return 1;
	};
	split->FirstByte=split->LastByte=0;
	return 0;
};


void tDownload::prepare_splits(){
 	DBC_RETURN_IF_FAIL(split!=NULL);
	DBC_RETURN_IF_FAIL(segments!=NULL);
//	printf("__%Li__\n",finfo.size);
	tSegment *holes=segments->to_holes(finfo.size);
	tSegment *tmp;
/*
	printf("split to %i parts[holes->offest_in_file=%i]\n",split->NumOfParts,holes->offset_in_file);
	tmp=holes;
	while(tmp){
		printf("L[%i]:%li %li\n",holes->offset_in_file,holes->begin,holes->end);
		tmp=tmp->next;
	};
*/
	while(split->NumOfParts>holes->offset_in_file){
		tSegment *largest=holes;
		tmp=holes->next;
		while(tmp){
			if ((tmp->end-tmp->begin)>(largest->end-largest->begin))
				largest=tmp;
			tmp=tmp->next;
		};
		if (largest->end-largest->begin<SPLIT_MINIMUM_PART){
			WL->log(LOG_WARNING,_("Can't split file to specified number of parts!"));
			break;
		};
		tmp=new tSegment;
		tmp->end=largest->end;
		tmp->begin=largest->begin+(largest->end-largest->begin)/(split->NumOfParts-holes->offset_in_file+1);
		largest->end=tmp->begin;
		tmp->next=largest->next;
		largest->next=tmp;
		holes->offset_in_file+=1;
	};
	if (holes->next && holes->end==holes->next->begin){
		holes->end+=(holes->end-holes->begin)/10;
		holes->next->begin=holes->end;
	};
	split->cond=new d4xCondition;
//	if (split->NumOfParts<holes->offset_in_file)
		split->NumOfParts=holes->offset_in_file;
	split->cond->set_value(split->NumOfParts);
	split->FirstByte=holes->begin;
	split->LastByte=holes->end;
	split->thread_num=1;
//	printf("%Li %Li\n",split->FirstByte,split->LastByte);
	tmp=holes->next;
	delete(holes);
	holes=tmp;
	tSplitInfo *newsplit=split;
	tDownload *parent=this;
	char i='1';
	std::list<d4x::Alt*>::iterator alt;
	if (ALTS){
		ALTS->lock_by_download();
		alt=ALTS->LST.begin();
	};
	split->alt=0;
	int alt_num=1;
	while(holes){
		tmp=holes->next;
//		printf("H:%Li %Li\n",holes->begin,holes->end);
		if (parent->split->thread_num<newsplit->NumOfParts){
			if (newsplit->next_part==NULL)
				newsplit->next_part=new tDownload;
			tDownload *temp=newsplit->next_part;
			temp->status=DOWNLOAD_REAL_STOP;
			if (temp->split==NULL)
				temp->split=new tSplitInfo;
			else
				temp->split->reset();
			newsplit=temp->split;
			newsplit->cond=split->cond;
			newsplit->NumOfParts=split->NumOfParts;
			temp->split->grandparent=this;
			temp->split->parent=parent;
			temp->split->thread_num=parent->split->thread_num+1;
			parent=temp;
			temp->split->FirstByte=holes->begin;
			temp->split->LastByte=holes->end;
//			printf("%Li %Li\n",newsplit->FirstByte,newsplit->LastByte);
			temp->segments=segments;
			if (temp->config==NULL) temp->config=new tCfg;
			temp->config->copy(config);
			if (config->log_save_path.get()){
				char *tmppath=sum_strings(config->log_save_path.get(),"_ ",
							  NULL);
				tmppath[strlen(tmppath)-1]=i;
				temp->config->log_save_path.set(tmppath);
			};
			i+=1;
			temp->config->speed=(config->speed/split->NumOfParts)*temp->split->NumOfParts;
			temp->config->user_agent.set(config->user_agent.get());
			temp->config->referer.set(config->referer.get());
			temp->config->save_path.set(config->save_path.get());
			temp->Name2Save=Name2Save;
			temp->finfo.size=finfo.size;
			temp->finfo.type=finfo.type;
			temp->finfo.perm=finfo.perm;
			temp->finfo.date=finfo.date;
			if (ALTS && alt!=ALTS->LST.end()){
				temp->split->alt=alt_num;
				temp->info=(*alt)->info;
				(*alt)->set_proxy_settings(temp);
			}else{
				temp->split->alt=0;
				temp->info=info;
			};
			if (ALTS){
				if (alt!=ALTS->LST.end()){
					alt++;
					alt_num++;
				}else{
					alt=ALTS->LST.begin();
					alt_num=1;
				};
			};
		};
		delete(holes);
		holes=tmp;
	};
	if (ALTS)
		ALTS->unlock_by_download();
// to avoid broken downloads when we can't detect resuming support at first request
// first thread always load from begining to end
	if (split->FirstByte==0) split->LastByte=finfo.size;
	split->prepared=1;
};


void tDownload::delete_who(){
	if (who){
		delete(who);
		who=NULL;
	};
};


d4x::URL tDownload::redirect_url(){
	if (RedirectURL.is_valid()){
		return RedirectURL;
	}else if (who){
		char *newurl=NULL;
		newurl=who->get_new_url();
		if (newurl) {
			d4x::URL addr=fix_url_global(newurl,info,-1,0);
			delete[] newurl;
			return(addr);
		};
	};
	return d4x::URL();
};

void tDownload::set_split_count(int num){
	if (num>1){
		if (num>10) num=10;
		if (split==NULL)
			split=new tSplitInfo;
		split->NumOfParts=num;
		split->grandparent=this;
	}else{
		if (split)
			delete(split);
		split=NULL;
	};
};

void tDownload::set_initial_speedlimit(){
	if (CFG.SPEED_LIMIT<3 && CFG.SPEED_LIMIT>0){
		SpeedLimit->set((CFG.SPEED_LIMIT==1 ? CFG.SPEED_LIMIT_1:CFG.SPEED_LIMIT_2)/50+1);
	}else if (myowner && myowner->PAPA){
		SpeedLimit->base2=myowner->PAPA->SpdLmt/myowner->count();
	}else if (split && split->grandparent && split->grandparent->myowner){
		tDList *o=split->grandparent->myowner;
		SpeedLimit->base2=o->PAPA->SpdLmt/o->count();
	};
};

tDownload::~tDownload() {
	if (list_iter) gtk_tree_iter_free(list_iter);
	if (config) delete(config);
	if (myowner) myowner->del(this);
	if (who) delete who;
	if (editor) delete editor;
	if (LOG) LOG->ref_dec();
	if (DIR) delete DIR;
	if (WL) delete(WL);
	if (split) delete(split);
	if (ALTS) delete(ALTS);
};


//**********************************************/

tDList::tDList():tQueue(){
	Pixmap=LPE_UNKNOWN;
	empty=non_empty=NULL;
};

tDList::tDList(int key):tQueue(){
	Pixmap=LPE_UNKNOWN;
	OwnerKey=key;
	empty=non_empty=NULL;
};

void tDList::set_empty_func(void (*emp)(),void (*nonemp)()){
	empty=emp;
	non_empty=nonemp;
	if (Num && non_empty)
		non_empty();
	else if (empty)
		empty();
};

void tDList::init_pixmap(int a){
	Pixmap=a;
};

int tDList::get_key(){
	return(OwnerKey);
};

void tDList::insert(tDownload *what) {
	DBC_RETURN_IF_FAIL(what!=NULL);
	if (Num==0 && non_empty!=NULL)
		non_empty();
	tQueue::insert(what);
	what->myowner=this;
	if (Pixmap!=LPE_UNKNOWN && PAPA)
		PAPA->qv.set_pixmap(what,Pixmap);
};

void tDList::insert_before(tDownload *what,tDownload *where) {
	DBC_RETURN_IF_FAIL(where!=NULL);
	DBC_RETURN_IF_FAIL(where->myowner==this);
	tQueue::insert_before(what,where);
	what->myowner=this;
	if (Pixmap!=LPE_UNKNOWN && PAPA)
		PAPA->qv.set_pixmap(what,Pixmap);
};

void tDList::del(tDownload *what) {
	DBC_RETURN_IF_FAIL(what->myowner==this);
	if (Num==1 && empty!=NULL)
		empty();
	tQueue::del(what);
	what->myowner=NULL;
};

void tDList::forward(tDownload *what) {
	DBC_RETURN_IF_FAIL(what!=NULL);
	if (what->next) {
		tDownload *temp=(tDownload *)(what->next);
		if ((temp->prev=what->prev))
			what->prev->next=temp;
		else
			Last=what->next;
		what->prev=temp;
		if ((what->next=temp->next))
			temp->next->prev=what;
		else
			First=what;
		what->prev->next=what;
	};
};

void tDList::backward(tDownload *what) {
	DBC_RETURN_IF_FAIL(what!=NULL);
	if (what->prev) {
		tDownload *temp=(tDownload *)(what->prev);
		if ((temp->next=what->next))
			what->next->prev=temp;
		else
			First=what->prev;
		what->next=temp;
		if ((what->prev=temp->prev))
			temp->prev->next=what;
		else
			Last=what;
		what->next->prev=what;
	};
};

tDownload *tDList::find(const d4x::URL &addr){
	tNode *a=First;
	while(a){
		if (((tDownload*)a)->info==addr) return((tDownload*)a);
		a=a->prev;
	};
	return(NULL);
};

void tDList::insert_if_absent(tDownload *what){
	if (find(what->info))
		delete(what);
	else
		insert(what);
};

void tDList::dispose(){
//	ALL_DOWNLOADS->del((tDownload *)First);
	tQueue::dispose();
};

tDownload *tDList::last() {
	return (tDownload *)(Curent=Last);
};

tDownload *tDList::first() {
	return (tDownload *)(Curent=First);
};

tDownload *tDList::next() {
	return (tDownload *)tQueue::next();
};

tDownload *tDList::prev() {
	return (tDownload *)tQueue::prev();
};

tDList::~tDList() {
	done();
};
