//ftp 文件传输协议
import inet
import fsys;
namespace inet  
 
ftp = class {

	ctor(server,user,password,port = 21,pasv,agent = "Mozilla/4.0", proxy,proxyBypass="",flags=0 ){ 
		port = tonumber(port);
		
	    if( #proxy ){
	   		this.session = ::InternetOpen(agent,0x3/*_INTERNET_OPEN_TYPE_PROXY*/,proxy,proxyBypass,flags);	
	    }
	    else{ 
	    	this.session = ::InternetOpen(agent,(!proxy)? 0x1/*_INTERNET_OPEN_TYPE_DIRECT*/:0x0/*_INTERNET_OPEN_TYPE_PRECONFIG*/,null,null,flags);
	    } 
	    if(!this.session)
			error("inet.ftp初始化失败",2) 
	       
    	//连接
    	this.connect = ::InternetConnect(this.session 
    		,server
    		,port 
    		,user
    		,password
    		, 1
    		, pasv?0x8000000:0 //是否被动模式
    		, 0); 
    		
		if( not this.connect )
			return null,"连接FTP服务器失败"; 
			
		..table.add_gc(this,"close");
    };  
 	close = function(){ 
 		if(this.connect)
 			::InternetCloseHandle(this.connect);
 			
    	if(this.session)
    		::InternetCloseHandle(this.session);
    };
    command = function(cmd,flags=2/*_INTERNET_FLAG_TRANSFER_BINARY*/, expectResponse){
    	return FtpCommand(this.connect,expectResponse,flags,cmd );
    };
    upload=function(path,remotePath,flags=0){ 
    	if( !..io.exist(path) ){
    		return false,"上传出错:本地文件不存在",path
    	}
   		return FtpPutFile(this.connect,..io.fullpath(path),remotePath,flags,0)  
    };
    download=function(local,remote,failIfExists,flags=0){
    	return FtpGetFile(this.connect,remote,..io.fullpath(local),failIfExists,0x80/*_FILE_ATTRIBUTE_NORMAL*/,flags,0) 
    }; 
    delete = function(path){
    	path = ..string.replace(path,"\\","/");
    	FtpDeleteFile(this.connect,path);
    }
    deleteDir = function(path){ 
    	return FtpRemoveDirectory(this.connect,canonicalizeDirectory(path) );
    };
    createDir = function(path){
    	if(!path)error("目录不能为空",2);
    	path = canonicalizeDirectory(path);
    	var parent,last = ..string.match(path,"(.+)/([^/]+/)")
    	if( parent && last ){
    		owner.createDir(parent)
    	};
    	return FtpCreateDirectory(this.connect,canonicalizeDirectory(path));
    };
    rename = function(path,new){
    	return FtpRenameFile(this.connect,..string.replace(path,"\\","/"),..string.replace(new,"\\","/") );
    };
    getCurDir = function(){
    	var ok,path,len = FtpGetCurrentDirectory(this.connect,0x104/*_MAX_PATH*/,0x104/*_MAX_PATH*/);
    	if(ok)return path;
    };
    setCurDir = function(path){
    	if(!path)error("目录不能为空",2);
    	return FtpSetCurrentDirectory(this.connect,canonicalizeDirectory(path) )
    };
    eachDir = function(dir,p="*.*"){ 
     	dir = canonicalizeDirectory(dir);
     	FtpSetCurrentDirectory(this.connect,dir)
     	var hfile;
     	var findData = ..fsys.WIN32_FIND_DATA() 
     	var freeItor = function(){  
   			if(hfile){ 
   				::InternetCloseHandle(hfile); 
   				hfile = null;
   			}
   		}   
   		var gcFile;
   		
     	return function(){
     	
     		if(hfile){
     			if(!InternetFindNextFile(gcFile,findData))
     				return;
     		}
     		else{
     			hfile = FtpFindFirstFile(this.connect,p,findData,0,0);
     			if(!hfile)
     				return; 

     			gcFile = ..gcdata( _gc = freeItor ;_topointer =  hfile ) 
     		}
     	  
     		filename =..string.str( findData.cFileName );
     		if( findData.dwFileAttributes & 0x10/*_FILE_ATTRIBUTE_DIRECTORY*/ )
     			return filename, ,findData
     		else
     			return dir,filename,findData 
     	},freeItor 
    }; 
    open = function( path,mode,flag){
    	var f = file( this.connect,path,mode,flag )
    	return f;
    };	
 }
 
namespace ftp{
	canonicalizeDirectory = function(path){
		if(!path)error("目录不能为空",2); 
		path = ..string.replace(path,"\\","/");
     	if( not ..string.endWith( path,"/") )
     		path = path + "/";  
     	return path;
	};
	
	FtpGetFile = ::Wininet.api("FtpGetFileA","bool(POINTER hConnect,STRING remoteFile,STRING newFile,bool failIfExists,INT dwFlagsAndAttributes,INT dwFlags,INT dwContext)")
	FtpPutFile = Wininet.api("FtpPutFileA","bool(POINTER hConnect,STRING localFile,STRING remoteFile, INT dwFlags,INT dwContext)")	
	FtpRenameFile = Wininet.api("FtpRenameFile","bool(POINTER hConnect,STRING lpszExisting,STRING lpszNew)")
	FtpDeleteFile  = Wininet.api("FtpDeleteFileA","bool(POINTER hConnect,STRING lpszFileName)")
	FtpRemoveDirectory  = Wininet.api("FtpRemoveDirectoryA","bool(POINTER hConnect,STRING lpszFileName)")
	FtpFindFirstFile = Wininet.api("FtpFindFirstFileA","pointer(POINTER hFtpSession,STRING lpszSearchFile,struct& lpFindFileData,int dwFlags,int dwContent)");
	InternetFindNextFile = ::Wininet.api("InternetFindNextFileA","int(pointer hFind,struct& lpvFindData)");
	FtpSetCurrentDirectory = ::Wininet.api("FtpSetCurrentDirectoryA","bool(POINTER hFtpSession,str dir)");
	FtpGetCurrentDirectory = ::Wininet.api("FtpGetCurrentDirectoryA","bool(POINTER hConnect,str &dir,INT &len)");
	FtpCommand = ::Wininet.api("FtpCommandA","bool(POINTER hConnect,bool expectResponse,INT flags,str command,pointer context,pointer &phFtpCommand)");
 	FtpCreateDirectory  = ::Wininet.api("FtpCreateDirectoryA","bool(POINTER hConnect,STRING dir)")
   
   	
	class file{
		ctor( hConnect,filename,mode="rb",flags = 0){
			flags = flags | (  ..string.find(mode,"b") ? 2/*_FTP_TRANSFER_TYPE_BINARY*/ : 1/*_FTP_TRANSFER_TYPE_ASCII*/ );
	
			mode = mode[1] == 'r'#  ? 0x80000000/*_GENERIC_READ*/ : 0x40000000/*_GENERIC_WRITE*/;
			var hFile = FtpOpenFile( hConnect,filename,mode,flags);
			if(!hFile){
				return null;
			};
			..table.add_gc(this,"close");
		};
		write = function(...){
			return wirteFile(hFile,...);
		}; 
		read = function(bufferSize){
			return readFile(hFile,bufferSize);  
		};
		eachRead = function(bufferSize){
			return function(){
				return readFile(hFile,bufferSize); 
			};
		}; 
		close = function(){
			if(hFile){
				::InternetCloseHandle(hFile);
				hFile = null;
			}
		}; 
	}
	namespace file{
		import inet.file;
		readFile = inet.file.read;
		wirteFile = inet.file.write; 
		FtpOpenFile = ::Wininet.api("FtpOpenFile","pointer(POINTER hConnect,STRING fileName,INT dwAccess,INT flags,pointer contect)")
	};

}
 
/**intellisense()
inet.ftp = FTP文件传输支持库
inet.ftp(.(主机,用户名,密码,端口号,是否被动模式,UserAgent,代理,绕过代理,选项) = 除第一个参数以外,其他参数可选
!ftp.eachDir = @for(dir,file,findData in ??.eachDir("\")){
	 if( !file ){
	 	io.print("目录",dir)
	 }
	 else{
	 	io.print("文件",file)
	 } 
}
!ftp.setCurDir(.(目录) = 设置服务器上的当前目录
!ftp.getCurDir() = 返回服务器上的当前目录
!ftp.deleteDir(.(目录) = 删除目录
!ftp.createDir(.(目录) = 创建目录,支持创建多级目录\n可以斜杠开头的的路径表示相对于根目录的完整路径\n也可使用setCurDir()设定当前目录
!ftp.delete(.(文件) = 删除文件
!ftp.rename(.(文件名,新名字) = 文件改名
!ftp.download(.("/本地路径","/远程路径") = 下载前可使用setCurDir()设定当前目录\n也可以斜杠开头的的路径表示相对于根目录的完整路径
!ftp.upload(.("/本地路径","远程路径") = 上传前可使用setCurDir()设定当前目录\n也可以斜杠开头的的路径表示相对于根目录的完整路径
!ftp.download(.(loadfile,remotefile,1/*_INTERNET_FLAG_TRANSFER_ASCII*/) = ASCII模式下载
!ftp.download(.(loadfile,remotefile,2/*_INTERNET_FLAG_TRANSFER_BINARY*/) = BINARY模式下载
!ftp.upload(.(formfile,todir,1/*_INTERNET_FLAG_TRANSFER_ASCII*/) = ASCII模式上传
!ftp.upload(.(formfile,todir,2/*_INTERNET_FLAG_TRANSFER_BINARY*/) = BINARY模式上传
!ftp.command("OPTS UTF8 off") = 发送命令通知服务器关闭UTF8编码模式\n可解决中文文件名乱码问题
!ftp.command(.(命令,选项,是否返回句柄) = 仅指定第一个参数即可,\n参数2指定传送模式,默认为BINARY模式\n也可指定为_INTERNET_FLAG_TRANSFER_ASCII\n参数3默认为false,如果为true则第二个返回值为返回句柄
!ftp.close() = 关闭连接
!ftp.open("/目录/文件路径","rb") = 二进制只读模式打开ftp文件流\n参数2为"rt"则使用文本模式,\n打开文件后仅允许读文件,其他所有FTP操作被禁用,\n直到调用close()关闭文件
!ftp.open("/目录/文件路径","wb") = 二进制只写模式打开ftp文件流\n参数2为"wt"则使用文本模式,\n打开文件后仅允许写文件,其他所有FTP操作被禁用,\n直到调用close()关闭文件
!ftp.open() = !ftp_file.
!ftp_file.read() = 读文件,返回文件数据,\n可选指定缓冲区长度
!ftp_file.write(.(写入字符串,不定个数字符串) = 写文件,支持多参数,\n返回写入数据的总长度,失败返回空或0
!ftp_file.close() = 关闭FTP文件
!ftp_file.eachRead(缓冲区长度) = @for(str in ??.eachRead() ){
	__
}
?inet.ftp = !ftp.
end intellisense**/

/**intellisense()
_INTERNET_FLAG_TRANSFER_ASCII=@1/*_INTERNET_FLAG_TRANSFER_ASCII*/
_INTERNET_FLAG_TRANSFER_BINARY=@2/*_INTERNET_FLAG_TRANSFER_BINARY*/
_FTP_TRANSFER_TYPE_ASCII=@1/*_FTP_TRANSFER_TYPE_ASCII*/
_FTP_TRANSFER_TYPE_BINARY=@2/*_FTP_TRANSFER_TYPE_BINARY*/
end intellisense**/

