1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 #Author calmyan
4 importsocket,os,json,getpass,hashlib5 importos ,sys,optparse6
7 STATUS_CODE={8 240:'格式出错,格式:{"action":"get","filename":"filename","size":100}',9 241:'指令错误',10 242:'用户密码出错',11 243:'用户或密码出错',12 244:'用户密码通过校验',13 }14 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
15 sys.path.append(BASE_DIR)#增加环境变量
16 from cfg importconfig17 classFTPClient(object):18 def __init__(self):19 paresr=optparse.OptionParser()20 paresr.add_option('-s','--server',dest='server',help='服务器地址')21 paresr.add_option('-P','--port',type="int",dest='port',help='服务器端口')22 paresr.add_option('-u','--username',dest='username',help='用户名')23 paresr.add_option('-p','--password',dest='password',help='密码')24 (self.options,self.args)=paresr.parse_args()#返回一个字典与列表的元组
25 self.verify_args(self.options,self.args)#判断参数
26 self.ser_connect()#连接服务端
27 self.cmd_list=config.CMD_LIST28 self.rat=0#文件断点
29
30 #实例化一个连接端
31 defser_connect(self):32 self.c=socket.socket()#实例化一个连接端
33 self.c.connect((self.options.server,self.options.port))#进行连接
34
35 #判断用户与密码是否成对出现
36 defverify_args(self,options,args):37 if (options.username is None and options.password is None) or (options.username is not None and options.password is not None):#判断用户与密码是否成对出现
38 pass##判断用户与密码单个出现
39 else:40 exit('出错:请输入用户与密码!')#退出
41 if options.server and options.port:#端口判断
42 if options.port>0 and options.port<65535:43 returnTrue44 else:45 print('端口号:[%s]错误,端口范围:0-65535'%options.port)46
47 #登陆方法
48 def landing(self):#登陆方法
49 '''用户验证'''
50 if self.options.username is not None:#判断用户名已经输入
51 #print(self.options.username,self.options.password)
52 return self.get_user_pwd(self.options.username,self.options.password)#返回结果
53 else:54 print('用户登陆'.center(60,'='))55 ret_count=0#验证次数
56 while ret_count<5:57 username=input('用户名:').strip()58 password=getpass.getpass('密码:').strip()59 ifself.get_user_pwd(username,password):60 return self.get_user_pwd(username,password)#调用远程验证用户 返回结果
61 else:62 ret_count+=1#次数加一
63 print('认证出错次数[%s]'%ret_count)64 else:65 print('密码出错次数过多!')66 exit()67
68 #'''用户名与密码检验'''
69 defget_user_pwd(self,username,password):70 '''用户名与密码检验'''
71 #发送 头文件
72 data={73 'action':'auth',74 'username':username,75 'password':password76 }77 self.c.send(json.dumps(data).encode())#发送到服务器
78 response = self.get_response()#得到服务端的回复
79 if response.get('status_code') == 244:80 print(STATUS_CODE[244])81 self.user = username#存下用户名
82 self.user_dir=response.get('dir')#目录
83 returnTrue84 else:85 print(response.get("status_msg") )86
87 #服务器回复
88 def get_response(self):#服务器回复
89 '''服务器回复信息'''
90 data=self.c.recv(1024)#接收回复
91 data =json.loads(data.decode())92 returndata93
94 #指令帮助
95 def help(self):#指令帮助
96 attr='''
97 help 指令帮助98 ----------------------------------99 info 个人信息100 ----------------------------------101 ls 查看当前目录(linux/windows)102 ----------------------------------103 pwd 查看当前路径(linux/windows)104 ----------------------------------105 cd 目录 切换目录(linux/windows)106 ----------------------------------107 get filename 下载文件108 ----------------------------------109 put filename 上传文件110 ----------------------------------111 --md5 使用md5 在get/put 后112 ----------------------------------113 mkdir name 创建目录(linux/windows)114 ----------------------------------115 rmdir name 删除目录(linux/windows)116 ----------------------------------117 rm filename 删除文件 (linux/windows)118 ----------------------------------119 exit 退出120 ----------------------------------121 '''.format()122 print(attr)123
124 ##交互
125 def inter(self):#交互
126 if self.landing():#通过用户密码认证
127 print('指令界面'.center(60,'='))128 self.help()129 whileTrue:130 cmd = input('[%s]-->指令>>>:'%self.user_dir).strip()131 if len(cmd)==0:continue#输入空跳过
132 if cmd=='exit':exit()#退出指令
133 cmd_str=cmd.split()#用空格分割 取命令到列表
134 #print(cmd_str)
135 #print(len(cmd_str))
136 if len(cmd_str)==1 and cmd_str[0] in self.cmd_list:#如果是单个命令 并且在命令列表中
137 #if len(cmd_str)==1:#如果是单个命令 并且在命令列表中
138 if cmd_str[0]==config.HELP:139 self.help()140 continue
141 func=getattr(self,'cmd_compr')#调用此方法
142 ret=func(cmd_str)143 ifret:144 continue
145 else:146 pass
147 elif len(cmd_str)>1:148 if hasattr(self,'cmd_%s'%cmd_str[0]):#判断类中是否有此方法
149 func=getattr(self,'cmd_%s'%cmd_str[0])#调用此方法
150 func(cmd_str)#执行
151 continue
152 else:153 print('指令出错!')154 self.help()#155
156 #'''是否要md5'''
157 defcmd_md5_(self,cmd_list):158 '''是否要md5'''
159 if '--md5' incmd_list:160 returnTrue161
162 #进度条
163 def show_pr(self,total):#进度条
164 received_size = 0 #发送的大小
165 current_percent = 0 #166 while received_size <167 if int total>current_percent :168 print("#",end="",flush=True)#进度显示167>
169 current_percent = int((received_size / total) * 100)170 new_size = yield #断点跳转 传入的大小
171 received_size +=new_size172
173 #单个命令
174 def cmd_compr(self,cmd_str,**kwargs):175 mag_dict={176 "action":"compr",177 'actionname':cmd_str[0]178 }179 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据
180 cmd_res_attr=self.get_response()#得到服务器的回复
181 if type(cmd_res_attr) is not int:#如果不int 类型
182 if cmd_res_attr["status_code"] ==241:#命令不对
183 print(cmd_res_attr['status_msg'])184 return
185 if cmd_res_attr["status_code"] ==240:#命令不对
186 print(cmd_res_attr['status_msg'])187 return
188 size_l=0#收数据当前大小
189 self.c.send('准备好接收了,可以发了'.encode('utf-8'))190 receive_data= ''.encode()191 while size_l<192 data="self.c.recv(1024)#开始接收数据</p">
193 size_l+=len(data)#加上
194 receive_data +=data195 else:196 receive_data=receive_data.decode()197 try:198 receive_data=eval(receive_data)#转为列表 或字典
199 exceptException as e:200 pass
201 if type(receive_data) is dict:#如果是字典
202 for i inreceive_data:203 print(i,receive_data[i])204 return 1
205 if type(receive_data) is list:#如果是列表
206 for i inreceive_data:207 print(i)208 return 1
209 print(receive_data)210 return 1
211
212 #切换目录
213 def cmd_cd(self,cmd_list,**kwargs):214 '''切换目录'''
215 mag_dict={216 "action":"cd",217 'actionname':cmd_list[1]218 }219 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据
220 msg_l=self.c.recv(1024)#接收数据 消息
221 data=json.loads(msg_l.decode())222 if data["status_code"] ==251:#目录不可切换
223 print(data['status_msg'])224 return
225 elif data["status_code"] ==252:#目录可以换
226 print(data['status_msg'])227 self.c.send(b'1')#发送到服务器,表示可以了
228 data=self.c.recv(1024)229 print(data.decode())230 user_dir=data.decode()231 print(user_dir)232 self.user_dir=user_dir233 return
234 elif data["status_code"] ==256:#目录不存在
235 print(data['status_msg'])236 return
237
238 #删除文件
239 def cmd_rm(self,cmd_list,**kwargs):240 mag_dict={241 "action":"rm",242 'filename':cmd_list[1]243 }244 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
245 data=self.get_response()#得到服务器的回复
246 if data["status_code"] ==245:#文件不存在
247 print(data['status_msg'])248 #print('删除前空间:',data['剩余空间'])
249 return
250 elif data["status_code"] ==254:#文件删除完成
251 print(data['status_msg'])252 print('删除前空间:',data['剩余空间'])253 pass
254 self.c.send(b'1')#发送到服务器,表示可以
255 data=self.get_response()#得到服务器的回复
256 if data["status_code"] ==255:#文件删除完成
257 print(data['status_msg'])258 print('删除后空间:',data['剩余空间'])259 return
260
261 #创建目录
262 def cmd_mkdir(self,cmd_list,**kwargs):263 mag_dict={264 "action":"mkdir",265 'filename':cmd_list[1]266 }267 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
268 data=self.get_response()#得到服务器的回复
269 if data["status_code"] ==257:#目录已经存在
270 print(data['status_msg'])271 return
272 elif data["status_code"] ==256:#目录创建中
273 print(data['目录'])274 pass
275 self.c.send(b'1')#发送到服务器,表示可以
276 data=self.get_response()#得到服务器的回复
277 if data["status_code"] ==258:#目录创建中完成
278 print(data['status_msg'])279 return
280 pass
281
282 #删除目录
283 def cmd_rmdir(self,cmd_list,**kwargs):284 mag_dict={285 "action":"rmdir",286 'filename':cmd_list[1]287 }288 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
289 data=self.get_response()#得到服务器的回复
290 if data["status_code"] ==256:#目录不存在
291 print(data['status_msg'])292 return
293 elif data["status_code"] ==260:#目录不为空
294 print(data['status_msg'])295 print(data['目录'])296 return
297 elif data["status_code"] ==257:#目录删除中
298 print(data['目录'])299 pass
300 self.c.send(b'1')#发送到服务器,表示可以
301 data=self.get_response()#得到服务器的回复
302 if data["status_code"] ==259:#目录删除完成
303 print(data['status_msg'])304 return
305 pass
306
307 #上传方法
308 def cmd_put(self,cmd_list,**kwargs):#上传方法
309 if len(cmd_list) > 1:310 filename=cmd_list[1]#取文件名
311 filename_dir=config.PUT_DIR+filename#拼接文件名路径
312
313 if os.path.isfile(filename_dir):#是否是一个文件
314 filesize=os.stat(filename_dir).st_size#获取文件大小
315 #执行行为 名字,大小,是否
316 mag_dict={317 "action":"put",318 'filename':filename,319 'size':filesize,320 'overridden':True,321 'md5':False322 }323 if self.cmd_md5_(cmd_list):#判断是否进行MD5
324 mag_dict['md5'] =True325 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
326 data=self.get_response()#得到服务器的回复
327 if data["status_code"] ==250:#磁盘空间不足
328 print(data['status_msg'])329 print(mag_dict['size'])330 return
331 if data["status_code"] ==249:#磁盘空间足够
332 print(data['status_msg'])333 print('剩余空间',data['剩余空间'])334 self.c.send(b'1')#发送到服务器,表示可以上传文件了
335 data=self.get_response()#得到服务器的回复
336 if data["status_code"] ==230:#断点续传
337 print(data['status_msg'])338 print(data['文件大小'])339 self.rat=data['文件大小']#文件指针位置
340 pass
341 elif data["status_code"] ==231:#非断点续传
342 print(data['status_msg'])343 self.rat=0#文件指针位置
344 pass
345 f=open(filename_dir,'rb')#打开文件
346 f.seek(self.rat)#移动到位置
347 print(mag_dict['md5'])348 self.c.send(b'1')#发送到服务器,表示可以上传文件了
349 if mag_dict['md5']==True:350 md5_obj = hashlib.md5()#定义MD5
351 progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小
352 progress.__next__()353 while self.rat<354 line="f.read(1024)355" self.c.send try:357 progress.send>
358 exceptStopIteration as e:359 print("100%")360 break
361 md5_obj.update(line)#计算MD5
362
363 else:364 print(filename,'发送完成!')365 f.close()366 md5_val =md5_obj.hexdigest()367 md5_from_server = self.get_response()#服务端的MD5
368 if md5_from_server['status_code'] == 248:369 if md5_from_server['md5'] ==md5_val:370 print("%s 文件一致性校验成功!" %filename)371 return
372 else:373 progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小
374 progress.__next__()375 #for line in f:
376 while self.rat<377 line="f.read(1024)378" self.c.send try:380 progress.send>
381 exceptStopIteration as e:382 print("100%")383 break
384 #print(line)
385 else:386 print(filename,'发送完成!')387 f.close()388 return
389 else:390 print(filename,'文件不存在!')391
392 #下载方法
393 def cmd_get(self,cmd_list,**kwargs):#下载方法
394 #cmd_split= args[0].split()#指令解析
395 #if len(cmd_list) == 1:
396 #print("没有输入文件名.")
397 #return
398 #down_filename = cmd_list[1].split('/')[-1]#文件名
399 down_filename=cmd_list[1]#取文件名
400 file_path='%s/%s'%(config.GET_DIR,down_filename)#拼接文件路径 用户down目录
401 if os.path.isfile(file_path):#文件是否存
402 filesize=os.stat(file_path).st_size#获取文件大小
403 name_down=True404 else:405 filesize=0406 name_down=False407 mag_dict={408 "action":"get",409 'filename':cmd_list[1],410 'name_down':name_down,411 'size':filesize412 }413 if self.cmd_md5_(cmd_list):#判断是否进行MD5
414 mag_dict['md5'] =True415 self.c.send(json.dumps(mag_dict).encode())#发送
416 self.c.send(b'1')#发送到服务器,防粘包
417
418 response = self.get_response()#服务器返回文件 的信息
419 if response["status_code"] ==247:#如文件存在
420 if name_down==True and response['file_size']==filesize:421 print('文件已经下载完成')422 self.c.send(b'2')423 return
424 self.c.send(b'1')#发送到服务器,表示可以接收文件了
425 #if name_down:
426 received_size = filesize#当前接收的数据大小
427 #else:
428 #received_size = 0#当前接收的数据大小
429
430 file_obj = open(file_path,"ab")#打开文件
431 ifself.cmd_md5_(cmd_list):432 md5_obj =hashlib.md5()433 progress = self.show_pr(response['file_size']) #进度条 传入文件大小
434 progress.__next__()435 while received_size< response['file_size']:436 if response['file_size'] - received_size>1024:#表示接收不止一次
437 size=1024
438 else:#最后一次
439 size=response['file_size'] -received_size440 #print('最后一个大小',size)
441 data= self.c.recv(size)#接收数据
442
443 try:444 progress.send(len(data))#传入当前数据大小
445 exceptStopIteration as e:446 print("100%")447 received_size+=len(data)#接收数据大小累加
448 file_obj.write(data)#写入文件
449 md5_obj.update(data)#进行MD5验证
450 else:451 print("下载完成".center(60,'-'))452 file_obj.close()453 md5_val = md5_obj.hexdigest()#获取MD5
454 #print(md5_val)
455 md5_from_server = self.get_response()#服务端的MD5
456 #print(md5_from_server['md5'])
457 if md5_from_server['status_code'] == 248:458 if md5_from_server['md5'] ==md5_val:459 print("%s 文件一致性校验成功!" %down_filename)460 pass
461 else:462 progress = self.show_pr(response['file_size']) #进度条 传入文件大小
463 progress.__next__()464 while received_size< response['file_size']:465 if response['file_size'] - received_size>1024:#表示接收不止一次
466 size=1024
467 else:#最后一次
468 size=response['file_size'] -received_size469 #print('最后一个大小',size)
470 data= self.c.recv(size)#接收数据
471
472 try:473 progress.send(len(data))#传入当前数据大小
474 exceptStopIteration as e:475 print("100%")476 received_size+=len(data)#接收数据大小累加
477 file_obj.write(data)#写入文件
478 pass
479
480 else:481 print("下载完成".center(60,'-'))482 file_obj.close()483 pass
484 self.c.send(b'1')#发送到服务器,表示可以接收文件了
485
486 if __name__=='__main__':487
488 c=FTPClient()489 c.inter()
377>354>192>