• Day(24-25):FTP项目实战


    FTP上传下载服务器

    要求:

    1、多用户认证

    2、每个用户有自己的家目录

    3、ls 查看当前目录

    4、get file 下载文件

    5、put File 上传文件

    6、del file 删除文件或文件夹

    7、mkdir dir 创建文件夹

    8、cd 切换目录

    9、日志记录

    目录结构

    import optparse
    import socket
    import json,os
    import shelve
    
    class FtpClient(object):
        """ftp客户端"""
        MSG_SIZE = 1024  # 消息最长1024
    
        def __init__(self):
            self.username = None            #用户名
            self.terminal_display = None
            self.shelve_obj = shelve.open(".luffy_db")      #存放断点续传信息
            self.current_dir = None         #当前所在路径
    
            parser = optparse.OptionParser()
            parser.add_option("-s","--server", dest="server", help="ftp server ip_addr")
            parser.add_option("-P","--port",type="int", dest="port", help="ftp server port")
            parser.add_option("-u","--username", dest="username", help="username info")
            parser.add_option("-p","--password", dest="password", help="password info")
            self.options , self.args = parser.parse_args()      #拿到用户输入参数
    
            # print(self.options,self.args,type(self.options),self.options.server)
            self.argv_verification()
    
            self.make_connection()
    
    
        def argv_verification(self):
            """检查参数合法性"""
            if not self.options.server or not self.options.port:    #IP和端口不能为空
                exit("Error: must supply server and port parameters")
    
    
        def make_connection(self):
            """建立socket链接"""
            self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)    #创建socket对象
            self.sock.connect((self.options.server,self.options.port))      #连接服务端
    
        def get_response(self):
            """获取服务器端返回"""
            data = self.sock.recv(self.MSG_SIZE)    #接受信息
            return json.loads(data.decode())        #返回信息
    
    
        def auth(self):
            """用户认证"""
            count = 0
            while count < 3:        #3次认证
                username = input("username:").strip()
                if not username:continue
                password = input("password:").strip()
    
                cmd = {
                    'action_type':'auth',
                    'username':username,
                    'password':password,
                }
    
                self.sock.send(json.dumps(cmd).encode("utf-8"))     #发送服务端进行验证
                response = self.get_response()      #拿到返回结果
                print("response:",response)
                if response.get('status_code') == 200:      #pass auth
                    self.username = username        #记录当前用户名
                    self.terminal_display = "[%s]>>:" % self.username    #命令前显示
                    self.current_dir = "\"         #当前路径为根目录
                    return True
                else:
                    print(response.get("status_msg"))   #打印错误信息
                count += 1
    
        def unfinished_file_check(self):
            """检查shelve db ,把为正常传完的文件列表打印,按用户的指令决定是否重传"""
            if list(self.shelve_obj.keys()):    #判断断点续传信息中是否有信息
                print("-------Unfinished file list -------------")
                for index,abs_file in enumerate(self.shelve_obj.keys()):  #拿到索引和文件绝对路径
                    # del self.shelve_obj[abs_file]
                    received_file_size = os.path.getsize(self.shelve_obj[abs_file][1])  #拿到在本地的文件名,拿到已下载的大小
                    print("%s. %s    %s    %s   %s" %(index,abs_file,
                                                      self.shelve_obj[abs_file][0],
                                                      received_file_size,
                                                      received_file_size/self.shelve_obj[abs_file][0]*100
                                                      ))            #打印索引、文件路径、文件大小、已经下载大小,百分比
    
                while True:
                    choice = input("[select file index to re-download]").strip()  #等待用输入索引
                    if not choice:continue
                    if choice == 'back':break       #跳过续传
                    if choice.isdigit():
                        choice = int(choice)        #将选择的数字字符串转化为整形
                        if choice >= 0 and choice <= index:     #判断索引存在
                            selected_file = list(self.shelve_obj.keys())[choice]    #拿到选择续传的文件的文件路径
                            already_received_size = os.path.getsize(self.shelve_obj[selected_file][1])  #拿到文件大小
    
                            print("tell server to resend file ", selected_file)
                            #abs_filename + size +received_size
                            self.send_msg('re_get', file_size=self.shelve_obj[selected_file][0],        #发送消息头,包括文件名,大小,已下载大小
                                          received_size=already_received_size,
                                          abs_filename=selected_file)
    
                            response = self.get_response()          #拿到返回消息
                            if response.get('status_code') == 401:      #"File exist ,ready to re-send !",
                                local_filename = self.shelve_obj[selected_file][1]  #拿到本地已下载的文件名
    
    
    
                                f = open(local_filename,'ab')           #打开文件追加写
                                total_size = self.shelve_obj[selected_file][0]
                                recv_size = already_received_size
                                current_percent = int(recv_size /total_size *100)
                                progress_generator = self.progress_bar(total_size,current_percent,current_percent)
                                progress_generator.__next__()
                                while recv_size < total_size:
                                    if total_size - recv_size < 8192:  # last recv
                                        data = self.sock.recv(total_size - recv_size)
                                    else:
                                        data = self.sock.recv(8192)
                                    recv_size += len(data)
                                    f.write(data)
                                    progress_generator.send(recv_size)
    
                                    print("file re-get done")
                            else:
                                print(response.get("status_msg"))   #打印错误信息
    
        def interactive(self):
            """处理与Ftpserver的所有交互"""
            if self.auth():     #验证成功
                self.unfinished_file_check()        #进入断点续传检查
    
                while True:
                    user_input  = input(self.terminal_display).strip()  #输入指令
                    if not user_input:continue
    
                    cmd_list = user_input.split()   #字符串指令分割
                    if hasattr(self,"_%s"%cmd_list[0]):    #判断类时候有指令方法
                        func = getattr(self,"_%s"%cmd_list[0])      #拿到指令对应的方法
                        func(cmd_list[1:])      #传入指令并执行
    
    
        def parameter_check(self,args,min_args=None,max_args=None,exact_args=None):
            """参数个数合法性检查"""
            if min_args:    #最少参数个数
                if len(args) < min_args:
                    print("must provide at least %s parameters but %s received." %(min_args,len(args)))
                    return False
            if max_args:    #最多参数个数
                if len(args) > max_args:
                    print("need at most %s parameters but %s received." %(max_args,len(args)))
                    return False
    
            if exact_args:  #特定参数个数
                if len(args) != exact_args:
                    print("need exactly %s parameters but %s received." % (exact_args, len(args)))
                    return False
    
            return True
    
        def send_msg(self,action_type,**kwargs ):
            """打包消息并发送到远程"""
            msg_data = {
                'action_type': action_type,
                'fill':''
            }       #消息头初始内容
            msg_data.update(kwargs)     #将传入的关键字参数更新到消息头
    
            bytes_msg = json.dumps(msg_data).encode()   #序列化并转成bytes
            if self.MSG_SIZE > len(bytes_msg):      #消息头定长
                msg_data['fill'] = msg_data['fill'].zfill( self.MSG_SIZE - len(bytes_msg))
                bytes_msg = json.dumps(msg_data).encode()
    
            self.sock.send(bytes_msg)   #发送消息头
    
        def _ls(self,cmd_args):
            """
            display current dir's file list
            :param cmd_args:
            :return:
            """
            self.send_msg(action_type='ls')     #发送ls的消息头
            response = self.get_response()
            print(response)
            if response.get('status_code') == 302: #ready to send long msg
                cmd_result_size = response.get('cmd_result_size')  #信息的大小
                received_size = 0
                cmd_result = b''
                while received_size < cmd_result_size:  #循环接受消息
                    if cmd_result_size - received_size < 8192:      #last receive
                        data = self.sock.recv( cmd_result_size -  received_size)
                    else:
                        data = self.sock.recv(8192)
                    cmd_result += data
                    received_size += len(data)
                else:
                    print(cmd_result.decode("gbk"))     #打印结果
    
        def _cd(self,cmd_args):
            """change to target dir"""
            if self.parameter_check(cmd_args, exact_args=1): #只有一个参数
                target_dir = cmd_args[0]        #拿到目标目录
                self.send_msg('cd',target_dir=target_dir)   #发送消息头,包括目标目录
                response = self.get_response()
                print(response)
                if response.get("status_code") == 350:#dir changed
                    self.terminal_display = "[\%s & %s]" % (response.get('current_dir'),self.username)     #显示目录和用户名
                    self.current_dir = response.get('current_dir')      #修改当前目录
    
        def _get(self,cmd_args):
            """download file from ftp server
            1.拿到文件名
            2.发送到远程
            3.等待服务器返回消息
                3.1 如果文件存在, 拿到文件大小
                    3.1.1 循环接收
                3.2 文件如果不存在
                    print status_msg
    
            """
            if self.parameter_check(cmd_args,min_args=1):
                filename = cmd_args[0]
                self.send_msg(action_type='get',filename=filename)  #发送get指令和文件名
                response = self.get_response()
                if response.get('status_code') == 301:# file exist ,ready to receive
                    file_size = response.get('file_size')       #文件大小
                    received_size = 0       #已接收大小
    
                    progress_generator = self.progress_bar(file_size)   #初始化进度条生成器
                    progress_generator.__next__()       #先next才能send
    
                    #save to shelve db
                    file_abs_path = os.path.join(self.current_dir,filename)     #拿到文件在服务器的绝对路径
                    self.shelve_obj[file_abs_path] = [file_size,"%s.download" %filename]    #将文件信息存在本地
    
                    f = open("%s.download" %filename,"wb")  #打开文件,就收数据
                    while received_size < file_size:
                        if file_size - received_size < 8192:#last recv
                            data = self.sock.recv(  file_size - received_size )
                        else:
                            data = self.sock.recv(8192)
                        received_size += len(data)
                        f.write(data)   #写数据
                        progress_generator.send(received_size)      #打印进度条
    
                    else:
                        print('
    ')
                        print("---file [%s] recv done,received size [%s]----"%( filename,file_size))
                        del self.shelve_obj[file_abs_path]      #文件传输完毕,删除本地信息
                        f.close()
                        os.rename("%s.download"%filename,filename)      #改文件名
    
                else:
                    print(response.get('status_msg'))       #打印错误信息
    
    
        def progress_bar(self,total_size,current_percent=0,last_percent=0):
    
            while True:
                received_size = yield current_percent   #收到当前接受到的文件大小,循环完返回当前的百分比
                current_percent = int(received_size / total_size *100)
    
                if current_percent > last_percent:
                    print("#" * int(current_percent / 2) + "{percent}%".format(percent=current_percent), end='
    ',
                          flush=True)
                    last_percent = current_percent  # 把本次循环的percent赋值给last
    
        def _put(self,cmd_args):
            """上传本地文件到服务器
            1. 确保本地文件存在
            2. 拿到文件名+大小,放到消息头里发给远程
            3. 打开文件,发送内容
            """
    
    
            if self.parameter_check(cmd_args, exact_args=1):     #检查参数个数
                local_file = cmd_args[0]        #拿到要上传的文件名
                if os.path.isfile(local_file):     #判断本地文件是否存在
                    total_size = os.path.getsize(local_file)    #拿到文件大小
                    self.send_msg('put',file_size=total_size,filename=local_file)       #发送put指令,包括文件名、大小
                    f = open(local_file,'rb')
                    uploaded_size = 0
                    #last_percent = 0
    
                    progress_generator = self.progress_bar(total_size)      #进度条
                    progress_generator.__next__()
                    for line in f:      #发送数据
                        self.sock.send(line)
                        uploaded_size += len(line)
                        progress_generator.send(uploaded_size)      #打印进度条
    
                    else:
                        print('
    ')
                        print('file upload done'.center(50,'-'))
                        f.close()
    
        def _mkdir(self,cmd_args):
            """创建文件夹"""
            if self.parameter_check(cmd_args, exact_args=1):     #检查参数个数
                dir_name = cmd_args[0]        #拿到要创建的目录
                self.send_msg('mkdir', dir_name=dir_name)  # 发送mkdir指令,包括目录
                response = self.get_response()
                print(response.get('status_msg'))
    
        def _del(self,cmd_args):
            """创建文件夹"""
            if self.parameter_check(cmd_args, exact_args=1):     #检查参数个数
                target = cmd_args[0]        #拿到要删除的目录或文件
                self.send_msg('del', target=target)  # 发送del指令,包括目标
                response = self.get_response()
                print(response.get('status_msg'))
    
    if __name__ == "__main__":
        client = FtpClient()
        client.interactive() #交互
    
    luffy_client.py
    luffy_client.py
    import os,sys
    
    #拿到server的绝对路径,添加到搜索模块的路径集
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    
    if __name__ == "__main__":
        from core import management
    
        argv_parser =  management.ManagementTool(sys.argv)  #将用户输入指令传入管理工具,检验合法性
        argv_parser.execute()   #指令合法后解析执行
    
    luffy_server.py
    luffy_server.py
    [alex]
    name = alex Li
    password = e99a18c428cb38d5f260853678922e03
    expire = 2017-09-20
    
    
    [egon]
    name = egg lin
    password = e99a18c428cb38d5f260853678922e03
    expire = 2018-01-01
    
    accounts.ini
    accounts.ini
    import os
    
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    HOST =  "0.0.0.0"
    PORT = 9999
    
    USER_HOME_DIR = os.path.join(BASE_DIR,'home')
    
    LOG_DIR=os.path.join(BASE_DIR,'log')
    
    ACCOUNT_FILE = "%s/conf/accounts.ini" % BASE_DIR
    
    MAX_SOCKET_LISTEN = 5
    
    settings.py
    settings.py
    import os
    import logging.config
    import time
    from conf.settings import LOG_DIR
    
    # 定义三种日志输出格式 开始
    
    standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' 
                      '[%(levelname)s][%(message)s]'
    
    simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
    
    # id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'
    id_simple_format = '%(message)s'
    
    # 定义日志输出格式 结束
    
    # logfile_dir = os.path.dirname(os.path.abspath(__file__))  # log文件的目录
    
    logfile_name = '%s.log'%time.strftime('%Y-%m-%d',time.localtime())  # log文件名
    
    # 如果不存在定义的日志目录就创建一个
    if not os.path.isdir(LOG_DIR):
        os.mkdir(LOG_DIR)
    
    
    # log文件的全路径
    # logfile_path = os.path.join(logfile_dir, logfile_name)
    logfile_path = "%s%s%s" % (LOG_DIR, os.path.sep, logfile_name)
    
    
    
    
    # log配置字典
    LOGGING_DIC = {
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'standard': {
                'format': standard_format
            },
            'simple': {
                'format': id_simple_format
            },
        },
        'filters': {},
        'handlers': {
            'console': {
                'level': 'DEBUG',
                'class': 'logging.StreamHandler',  # 打印到屏幕
                'formatter': 'simple'
            },
            'default': {
                'level': 'DEBUG',
                'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
                'filename': logfile_path,  # 日志文件
                'maxBytes': 1024*1024*5,  # 日志大小 5M
                'backupCount': 5,
                'formatter': 'standard',
                'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
            },
        },
        'loggers': {
            '': {
                'handlers': ['default', 'console'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
                'level': 'DEBUG',
                'propagate': True,  # 向上(更高level的logger)传递
            },
        },
    }
    
    
    def load_logging_cfg():
        logging.config.dictConfig(LOGGING_DIC)  # 导入上面定义的logging配置
        logger = logging.getLogger(__name__)  # 生成一个log实例
        # logger.info('log 配置文件没有问题!')  # 记录该文件的运行状态
        return logger
    
    if __name__ == '__main__':
        load_my_logging_cfg()
    
    log_conf.py
    log_conf.py
    import socket
    from conf import settings
    import json,hashlib,os,time
    import configparser
    import subprocess
    from  core import log_conf
    
    class FTPServer(object):
        """处理与客户端所有的交互的socket server"""
    
        STATUS_CODE ={
            200 : "Passed authentication!",
            201 : "Wrong username or password!",
            300 : "File does not exist !",
            301 : "File exist , and this msg include the file size- !",
            302 : "This msg include the msg size!",
            350 : "Dir changed !",
            351 : "Dir doesn't exist !",
            401 : "File exist ,ready to re-send !",
            402 : "File exist ,but file size doesn't match!",
            501 : "directory create success!",
            502 : "directory exist!",
            503 : "File delete successful!",
            504 : "The folder is not empty and cannot be deleted!",
        }       #状态码集合
    
        MSG_SIZE = 1024 #消息最长1024
    
        def __init__(self,management_instance):
            self.management_instance = management_instance  #将ManagementTool对象本身传入
            self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)    #建立socket对象
            self.sock.bind((settings.HOST,settings.PORT))                   #绑定IP和端口
            self.sock.listen(settings.MAX_SOCKET_LISTEN)                    #开始监听
            self.accounts = self.load_accounts()                            #加载账户信息
            self.user_obj = None                #保存当前用户
            self.user_current_dir = None        #当前用户目录
            self.log=log_conf.load_logging_cfg()
    
    
        def run_forever(self):
            """启动socket server"""
            print('starting LuffyFtp server on %s:%s'.center(50,'-') %(settings.HOST,settings.PORT))
    
            while True:     #循环接受连接
                self.request,self.addr = self.sock.accept()     #拿到链接和地址
                # print("got a new connection from %s....." %(self.addr,))
                self.log.info("got a new connection from %s....." %(self.addr,))
                try:
                    self.handle()       #进入用户交互
                except Exception as e:
                    # print("Error happend with client,close connection.",e)
                    self.log.info("Error happend with client,close connection.",e)
                    self.request.close()        #打印错误信息,关闭连接
    
    
        def handle(self):
            """处理与用户的所有指令交互"""
            while True:
    
                raw_data = self.request.recv(self.MSG_SIZE)     #接受客户端发送的消息
                print('------->',raw_data)
                if not raw_data:        #用户断了连接,打印消息并删除链接和地址
                    # print("connection %s is lost ...." % (self.addr,))
                    self.log.info("connection %s is lost ...." % (self.addr,))
                    del self.request,self.addr
                    break
    
                data = json.loads(raw_data.decode("utf-8"))     #先转化为utf—8再反序列化
                action_type = data.get('action_type')           #拿到指令
                if action_type:
                    if hasattr(self,"_%s" % action_type):       #查找类是否有对应方法
                        func = getattr(self,"_%s" % action_type)    #拿到对应方法并执行
                        func(data)
    
                else:
                    print("invalid command,")
    
    
        def load_accounts(self):
            """加载所有账号信息"""
            config_obj = configparser.ConfigParser()        #拿到信息对象
            config_obj.read(settings.ACCOUNT_FILE)          #读取本地信息
    
            print(config_obj.sections())                    #打印信息标头
            return config_obj
    
    
        def authenticate(self,username,password):
            """用户认证方法"""
            if username in self.accounts:       #判断用户是否存在
                _password = self.accounts[username]['password']     #拿到用户密码
                md5_obj = hashlib.md5()
                md5_obj.update(password.encode())
                md5_password = md5_obj.hexdigest()              #拿到密码的MD5值
                # print("passwd:",_password,md5_password)
                if md5_password == _password:           #判断密码是否相等
    
                    self.user_obj = self.accounts[username]         #保存用户信息
                    self.user_obj['home']= os.path.join(settings.USER_HOME_DIR,username)    #服务端用户家目录的的绝对路径
                    #set user home directory
                    self.user_current_dir = self.user_obj['home']   #保存当前路径
                    return True     #认证成功返回TRUE
                else:
                    return  False   #密码错误返回FALSE
            else:
                return False        #用户名错误返回FALSE
    
        def send_response(self,status_code,*args,**kwargs):
            """
            打包发送消息给客户端
            :param status_code:
            :param args:
            :param kwargs: {filename:ddd,filesize:222}
            :return:
            """
            data = kwargs
            data['status_code'] = status_code       #返回状态码
            data['status_msg'] = self.STATUS_CODE[status_code]      #返回信息
            data['fill'] = ''
    
            bytes_data = json.dumps(data).encode()      #序列化并转bytes
    
            if len(bytes_data) < self.MSG_SIZE:     #定长
                data['fill'] = data['fill'].zfill(  self.MSG_SIZE - len(bytes_data))
                bytes_data = json.dumps(data).encode()
    
            self.request.send(bytes_data)       #发送到客户端
    
        def _auth(self,data):
            """处理用户认证请求"""
            # print("auth ",data )
            if self.authenticate(data.get('username'),data.get('password')): #进行认证
                print('pass auth....')
    
                #1. 消息内容,状态码
                #2. json.dumps
                #3 . encode
                self.send_response(status_code=200)     #认证成功返回200
    
            else:
                self.send_response(status_code=201)     #认证失败返回201
    
    
        def _get(self,data):
            """client downloads file through this method
                1. 拿到文件名
                2. 判断文件是否存在
                    2.1 如果存在, 返回状态码+文件大小
                        2.1.1打开文件,发送文件内容
                    2.2 如果不存在, 返回状态码
                3.
            """
            filename = data.get('filename')     #拿到文件名
            #full_path = os.path.join(self.user_obj['home'],filename)
            full_path = os.path.join(self.user_current_dir,filename)        #拿到文件路径
            if os.path.isfile(full_path):       #判断文件是否存在
                filesize = os.stat(full_path).st_size       #拿到文件大小
                self.send_response(301,file_size=filesize)  #返回状态码和文件大小
                print("ready to send file ")
                f = open(full_path,'rb')        #打开文件,发送数据
                for line in f:
                    self.request.send(line)
                else:
                    # print('file send done..',full_path)
                    self.log.info('file send done..',full_path)
    
                f.close()
    
    
            else:
                self.send_response(300)     #文件不存在,返回状态码
    
        def _re_get(self,data):
            """re-send file to client
            1. 拼接文件路径
            2. 判断文件是否存在
                2.1 如果存在,判断文件大小是否与客户端发过来的一致
                    2.1.1 如果不一致,返回错误消息
                    2.1.2 如果一致,告诉客户端,准备续传吧
                    2.1.3 打开文件,Seek到指定位置,循环发送
                2.2 文件不存在,返回错误
    
    
            """
            print("_re_get",data)
            abs_filename = data.get('abs_filename')     #拿到文件路径
            full_path = os.path.join(self.user_obj['home'],abs_filename.strip("\"))    #拼接家目录和文件名
            print("reget fullpath", full_path)
            # print("user home",self.user_obj['home'])
            if os.path.isfile(full_path):   #判断文件是否存在
                if os.path.getsize(full_path) == data.get('file_size'):     #判断文件大是否一致
                    self.send_response(401)         #返回状态码,让客户端准备接收数据
                    f = open(full_path,'rb')        #打开文件
                    f.seek(data.get("received_size"))       #光标移动到断点处
                    for line in f:
                        self.request.send(line)
                    else:
                        # print("-----file re-send done------")
                        self.log.info('file re-send done..', full_path)
    
                        f.close()
                else:#2.1.1
                    self.send_response(402,file_size_on_server=os.path.getsize(full_path))  #文件大小不一致,返回错误信息
            else:
                self.send_response(300)     #文件不存在,返回错误信息
    
        def _put(self,data):
            """client uploads file to server
            1. 拿到local文件名+大小
            2. 检查本地是否已经有相应的文件。self.user_cuurent_dir/local_file
                2.1 if file exist , create a new file with file.timestamp suffix.
                2.2 if not , create a new file named local_file name
            3. start to receive data
            """
            local_file = data.get("filename")       #拿到文件名
            full_path = os.path.join(self.user_current_dir,local_file) #文件路径
            if os.path.isfile(full_path): #代表文件已存在,不能覆盖
                filename = "%s.%s" %(full_path,time.time())     #保存新的文件名
            else:
                filename = full_path
    
            f = open(filename,"wb")     #打开文件,准备写数据
            total_size = data.get('file_size')
            received_size = 0
    
            while received_size < total_size:
                if total_size - received_size < 8192:  # last recv
                    data = self.request.recv(total_size - received_size)
                else:
                    data = self.request.recv(8192)
                received_size += len(data)
                f.write(data)
                print(received_size, total_size)
            else:
                # print('file %s recv done'% local_file)
                self.log.info('file %s recv done'% local_file)
    
                f.close()
    
    
        def _ls(self,data):
            """run dir command and send result to client"""
            cmd_obj = subprocess.Popen('dir %s' %self.user_current_dir,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)    #执行Windows上的dir命令
            stdout = cmd_obj.stdout.read()      #拿到执行结果
            stderr = cmd_obj.stderr.read()      #拿到错误信息
    
            cmd_result = stdout + stderr        #拼接结果信息
    
            if not  cmd_result:     #没有结果返回结果,定义返回结果
                cmd_result = b'current dir has no file at all.'
    
            self.send_response(302,cmd_result_size=len(cmd_result))     #发送状态码和信息长度
            self.request.sendall(cmd_result)                            #发送结果
    
        def _cd(self,data):
            """根据用户的target_dir改变self.user_current_dir 的值
            1. 把target_dir 跟user_current_dir 拼接
            2. 检测 要切换的目录是否存在
                2.1 如果存在 , 改变self.user_current_dir的值到新路径
                2.2 如果不存在,返回错误消息
    
            """
            #/home/alex/FuckHomework/  cfd
            target_dir = data.get('target_dir')     #拿到客户端发送来的目标路径
            full_path = os.path.abspath(os.path.join(self.user_current_dir,target_dir) ) #拼接当前路径和目标路径,再拿到服务端的绝对路径
            print("full path:",full_path)
            if os.path.isdir(full_path):    #判断路径是否存在
    
                if full_path.startswith(self.user_obj['home']):     #判断路径是否是用户家目录下的路径
                    self.user_current_dir = full_path               #是的话,将目标路径赋值给当前路径
                    relative_current_dir = self.user_current_dir.replace(self.user_obj['home'], '')     #用户拿到相对路径
                    self.send_response(350, current_dir=relative_current_dir)       #发送给用户状态码和相对路径
    
                else:
                    self.send_response(351)     #目标路径不是用户允许访问的路径,返回状态码
    
            else:
                self.send_response(351)         #目标路径不存在,返回状态码
    
        def _mkdir(self,data):
            """
            创建文件夹目录
            1、将用户发送的dir_name和self.user_current_dir进行拼接
            2、判断文件夹是否存在
                2.1不存在就创建 并返回信息
                2.2存在就返回错误信息
            :param data:
            :return:
            """
            dir_name = data.get('dir_name')     #拿到目录名字
            full_path = os.path.abspath(os.path.join(self.user_current_dir,dir_name))       #拼接路径
            if not os.path.isdir(full_path):        #判断目录是否存在
                os.makedirs(full_path)      #不存在则创建
                self.log.info('%s directory create success!' % full_path)       #日志记录并打印
                self.send_response(501)     #返回状态码
            else:
                self.send_response(502)
        def _del(self,data):
            """
            创建文件夹目录
            1、将用户发送的target和self.user_current_dir进行拼接
            2、判断文件或者文件夹是否存在
                2.1文件存在,删除文件
                2.2文件夹存在,判断是否为空
                    2.2.1空文件夹可删除
                    2.2.2文件夹不为空,不可删除
                2.3路径不存在,返回错误信息
            :param data:
            :return:
            """
            target = data.get('target')
            full_path = os.path.abspath(os.path.join(self.user_current_dir,target)) #拼接路径
            if os.path.isfile(full_path):       #判断是否为文件
                os.remove(full_path)            #删除文件
                self.log.info('%s file delete success!' % full_path)
                self.send_response(503)
            elif os.path.isdir(full_path):      #判断路径为文件夹
                if not os.listdir(full_path):   #文件夹为空,可以删除
                    os.rmdir(full_path)
                    self.log.info('%s directory delete success!' % full_path)
                    self.send_response(503)
    
                else:
                    self.send_response(504)     #不为空,不能删除,并发送结果
            else:
                self.send_response(300)         #文件不存在
    
    main.py
    main.py
    from core import main
    
    class ManagementTool(object):
        """负责对用户输入的指令进行解析并调用相应模块处理"""
        def __init__(self,sys_argv):
            self.sys_argv = sys_argv    #接受用户输入指令
            print(self.sys_argv)
            self.verify_argv()      #检验指令合法性
    
        def verify_argv(self):
            """验证指令合法性"""
            if len(self.sys_argv) < 2:  #至少输入一个参数
                self.help_msg()     #输出帮助信息
            cmd = self.sys_argv[1]  #拿到指令
            if not hasattr(self,cmd):   #判断类是否存在对应方法
                print("invalid argument!")
                self.help_msg()
    
    
        def help_msg(self):
            """帮助信息"""
            msg = '''
            start       start FTP server
            stop        stop  FTP server
            restart     restart FTP server
            createuser  username create a ftp user
    
            '''
            exit(msg)
    
        def execute(self):
            """解析并执行指令"""
            cmd = self.sys_argv[1]
            func = getattr(self,cmd )
            func()
    
    
        def start(self):
            """start ftp server"""
            server = main.FTPServer(self)       #建立socket对象
            server.run_forever()                #启动
    
    
    
    
        def creteuser(self):
            """
            创建用户
            :return:
            """
            print(self.sys_argv)
    
    management.py
    management.py
    [2017-07-17 16:48:35,798][MainThread:1952][task_id:core.log_conf][main.py:47][INFO][got a new connection from ('127.0.0.1', 49629).....]
    [2017-07-17 16:49:14,426][MainThread:1952][task_id:core.log_conf][main.py:294][INFO][E:PycharmProjectsqz5Day28LuffyFTPserverhomealexabcabcd directory create success!]
    [2017-07-17 16:49:42,910][MainThread:1952][task_id:core.log_conf][main.py:320][INFO][E:PycharmProjectsqz5Day28LuffyFTPserverhomealexabcabcd directory delete success!]
    [2017-07-17 16:49:56,433][MainThread:1952][task_id:core.log_conf][main.py:320][INFO][E:PycharmProjectsqz5Day28LuffyFTPserverhomealexabcab directory delete success!]
    [2017-07-17 16:50:00,880][MainThread:1952][task_id:core.log_conf][main.py:64][INFO][connection ('127.0.0.1', 49629) is lost ....]
    
    2017-07-17.log
    2017-07-17.log
  • 相关阅读:
    软件测试的重要性
    软件测试二三事
    白盒测试总结
    黑盒测试总结
    闰年测试以及非法输入的处理方法
    软件测试(3)-基于等价类划分的一个小例子
    第二周课堂小结以及习题思考
    软件测试人员的自身修养
    软件测试的目的和方法
    第七周学习笔记之灰盒测试
  • 原文地址:https://www.cnblogs.com/Vee-Wang/p/7201606.html
Copyright © 2020-2023  润新知