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
原文地址:https://www.cnblogs.com/Vee-Wang/p/7201606.html