ftp客服端实现自动更新文件(带更新完自动启动功能)-python

2023-11-18

ftp客服端实现自动更新文件(带自动启动功能并封装为带配置文件的工具)-python


前言

由于工位机不可能做到实时看守,当更新程序的时候我们还得手动去工位机上安装程序并运行,实属麻烦,因此笔者就研究了一下ftp自动更新并启动程序,最后封装为带一个配置文件的exe,所有人都可以使用的工具。
功能:通过配置文件从ftp服务端更新某个指定文件夹里的所有文件和目录(已经有了的不会下载,只下载更新的),可指定是否启动更新后自启动程序(启动的程序可自定义)功能,

一、项目环境和结构

环境

  1. python3.7_32位 (anaconda实现)
  2. 在这里插入图片描述
  3. 本地模拟ftp服务端(下一篇文章介绍服务器上部署公网ftp服务端
# -*- coding:utf-8 -*-
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers  import FTPHandler
from pyftpdlib.servers import FTPServer
# 实例化DummyAuthorizer来创建ftp用户
authorizer = DummyAuthorizer()
# 参数:用户名,密码,目录,权限
authorizer.add_user('admin', '12345', r'E:\project_code\ftp\ftpmyserver\File', perm='elradfmwMT')
# E:\project_code\ftp\ftpmyserver\File
# 匿名登录
# authorizer.add_anonymous('/home/nobody')
handler = FTPHandler
handler.authorizer = authorizer

# #添加被动端口范围
# handler.passive_ports = range(21212, 21213)

# 参数:IP,端口,handler
server = FTPServer(('0.0.0.0', 21), handler)           #设置为0.0.0.0为本机的IP地址
server.serve_forever()
结构
administrator.py(以管理员身份执行某端代码封装,它是为了我后续自动启动电脑里某个文件的程序时候以管理员去运行,没有这块需求可以去掉,)
ftpclient.py
configuration.txt (配置文件)

代码

  1. configuration.txt
HOST = 127.0.0.1     #远程ftp服务器的ip地址
FTPDir = /      #需要下载的ftp服务端目录路径
LocalDir = E:\project_code\ftp\ftpclient\down   # 本地存贮路径
flag = True  #是否需要打开更新后自动启动程序功能
dir = E:\project_code\companyId\dist\start_service.bat       #需要自启动的文件路径
  1. administrator.py
# coding:utf8
# -*- coding: utf-8 -*-
"""
Created on

@author: tql

Function:以管理员身份执行代码块

"""
from __future__ import print_function
import os
import sys
import ctypes
import inspect

if sys.version_info[0] == 3:
    import winreg as winreg
else:
    import _winreg as winreg

CMD = r"C:\Windows\System32\cmd.exe"
FOD_HELPER = r'C:\Windows\System32\fodhelper.exe'
PYTHON_CMD = "python"
REG_PATH = 'Software\Classes\ms-settings\shell\open\command'
DELEGATE_EXEC_REG_KEY = 'DelegateExecute'


def is_admin():
    '''
    Checks if the script is running with administrative privileges.
    Returns True if is running as admin, False otherwise.
    '''
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False


def create_reg_key(key, value):
    '''
    Creates a reg key
    '''
    try:
        winreg.CreateKey(winreg.HKEY_CURRENT_USER, REG_PATH)
        registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_PATH, 0, winreg.KEY_WRITE)
        winreg.SetValueEx(registry_key, key, 0, winreg.REG_SZ, value)
        winreg.CloseKey(registry_key)
    except WindowsError:
        raise


def bypass_uac(cmd):
    '''
    Tries to bypass the UAC
    '''
    try:
        create_reg_key(DELEGATE_EXEC_REG_KEY, '')
        create_reg_key(None, cmd)
    except WindowsError:
        raise


def execute():
    if not is_admin():
        print('[!] The script is NOT running with administrative privileges')
        print('[+] Trying to bypass the UAC')
        try:
            current_dir = __file__
            cmd = '{} /k {} {}'.format(CMD, PYTHON_CMD, current_dir)
            bypass_uac(cmd)
            os.system(FOD_HELPER)
            sys.exit(0)
        except WindowsError:
            sys.exit(1)
    else:
        # 这里添加我们需要管理员权限的代码
        with open("C:\Windows\System32\configuration.txt", "r", encoding='utf-8') as f:  # 设置文件对象
            conf = f.readlines()
        return conf

def start_file(dir):
    if not is_admin():
        print('[!] The script is NOT running with administrative privileges')
        print('[+] Trying to bypass the UAC')
        try:
            current_dir = __file__
            cmd = '{} /k {} {}'.format(CMD, PYTHON_CMD, current_dir)
            bypass_uac(cmd)
            os.system(FOD_HELPER)
            sys.exit(0)
        except WindowsError:
            sys.exit(1)
    else:
        # 这里添加我们需要管理员权限的代码
        os.startfile(dir)


if __name__ == '__main__':
    execute()

  1. ftpclient.py
# -*- coding: utf-8 -*-
"""
Created on

@author: tql

Function:定时更新ftp服务器中指定目录中的所有文件

"""

import ftplib
import os, sys
import socket
import re
from apscheduler.schedulers.background import BlockingScheduler


def getmyPath():
    sap = '/'
    if sys.argv[0].find(sap) == -1:
        sap = '\\'
    indx = sys.argv[0].rfind(sap)
    path = sys.argv[0][:indx] + sap
    return path


config_valid = {}
lujing = getmyPath()
with open(lujing + "configuration.txt", "r", encoding='utf-8') as f:  # 设置文件对象
    conf = f.readlines()

for key,line in enumerate(conf):
    content = line.split('#', 1)
    content[0] = re.sub('\n$', "", content[0])         # 去掉文本里的 #
    content[0] = re.sub('\s+', '', content[0]).strip()   # 去掉所有空格
    if len(content[0])>0:
        temp = content[0].split('=',1)
        config_valid[temp[0]]=temp[1]

try:
    HOST = config_valid["HOST"]  # ftp地址
except:
    HOST = config_valid["\ufeffHOST"]  # ftp地址



# USER = config_valid["USER"]  # 用户名
# PASSWD = config_valid["PASSWD"]  # 用户密码
USER = "admin"  # 用户名
PASSWD = "12345"  # 用户密码


LocalDir = config_valid["LocalDir"]  # 本地存贮路径
FTPDir = config_valid["FTPDir"]  # 需要下载的ftp目录路径


local_fname = 'checkfile.txt'  # 用本地存放已下载过的文件名和文件信息

local_files = []  # 存放从checkfile.txt中读回的文件名
appendFiles = []  # 存放服务器上是否有新文件需要更新






def FtpConnect(host, username, passwd):
    '''
    连接并登录ftp服务器

    host:ftp地址
    username:用户名
    passwd:用户密码
    '''
    try:
        ftp = ftplib.FTP(host)
        # ftp.encoding = 'utf-8' #解决中文乱码问题
        # ftp.set_debuglevel(0)  #不开启调试模式
    except (socket.error, socket.gaierror):
        print('Error, cannot reach ' + host)
        return None
    else:
        print('Connect To Host Success...')

    try:
        ftp.login(username, passwd)
    except ftplib.error_perm:
        print('Username or Passwd Error')
        # ftp.quit()
        return None
    else:
        print('Login Success...')
    return ftp


def filelist(ftp):
    '''
    获取ftp当前目录下的所有文件及目录信息
    '''
    flist = []
    ftp.dir('.', flist.append)  # 将目录中的内容存进flist列表
    files = [f.split()[-1] for f in flist if f.startswith('-')]  # 读取flist列表中的信息,以-开头的是常规文件,将该信息以空字符分割成列表,取最后的元素即为文件名
    fids = [f.split(None, 4)[-1] for f in flist if f.startswith('-')]  # 读取flist列表中的信息,以-开头的是常规文件,将该信息以前4个空字符分割成列表,
    # 最后的元素包括了文件的大小,修改日期时间,可作为文件的标识
    dictf = dict(zip(files, fids))  # 将文件名与对应的标识合成字典
    dirs = [f.split()[-1] for f in flist if f.startswith('d')]  # 读取flist列表中的信息,以d开头的是目录,将该信息以空字符分割成列表,取最后的元素即为目录名
    # print(dirs)
    return (dictf, dirs)


def FtpDownloadDir(ftp, ftp_dir, local_dir):
    '''
    递归下载ftp指定目录下的所有文件及目录
    '''
    print(f'Walking to {ftp_dir}')
    print(f'Walking to {local_dir}')
    if ftp_dir == "/":
        dirname = "AllDir"  # 用于本地创建新目录,如果下载的是FTP根目录,则在目录名为AllDir
    else:
        dirname = os.path.basename(ftp_dir)  # 否则本地目录名与FTP目录名一样

    ftp.cwd(ftp_dir)  # 进入ftp对应目录
    os.chdir(local_dir)  # 进入本地下载目录

    if os.path.exists(dirname):  # 如果本地dirname目录已存在
        os.chdir(dirname)  # 则直接进入该目录
    else:
        try:
            os.mkdir(dirname)  # 否则在本地创建该目录
        except OSError:
            print('OSError!')
        else:
            os.chdir(dirname)  # 创建完后进入该目录

    ftp_curr_dir = ftp.pwd()  # 获取FTP当前目录路径
    local_curr_dir = os.getcwd()  # 获取本地当前目录路径
    # print(f'Changing to {ftp_curr_dir}')
    # print(f'Changing to {local_curr_dir}')

    dictf, dirs = filelist(ftp)  # 调用filelist函数,递归ftp当前目录下的所有文件及目录

    for f, k in dictf.items():  # 获取到的文件信息的键值对
        # print(k.split(' ')[-1])
        if k not in local_files:  # 文件标识与本地存储的已下载过的文件标识做对比

            FtpDownloadFile(ftp, f, f)  # k不在local_files中说明该文件未下载过,则下载该文件

            appendFiles.append(k)  # 同时将该文件的标识存储到appenFiles列表中,用于下载完成后更新本地的checkfile.txt文件


    for d in dirs:  # 对子目录进行处理
        FtpDownloadDir(ftp, d, local_curr_dir)  # 调用自身,递归下载子目录中的文件
        ftp.cwd('..')
        os.chdir('..')  # 每次递归完成后,ftp及本地都返回上一层目录,继续其他子目录的处理
    os.chdir(local_dir)


def FtpDownloadFile(ftp, remotefile, localfile):
    '''
    下载ftp当前目录的文件到本地的当前目录中
    '''
    buffer_size = 10240  # 默认是8192
    try:
        f = open(localfile, 'wb')
        ftp.retrbinary(f'RETR {remotefile}', f.write, buffer_size)
    except ftplib.error_perm:
        print(f'File:{f} Download Error')
        # os.unlink(localpath)
    else:
        print(f'File:{f} Download Success...')
    finally:
        f.close()


def operfile(ftp,fileTxt, op):
    '''
    操作下载目录中的文件
    op为'r'时读取该文件,如文件不存在则忽略
    op为'w'时追加写入文件
    '''
    fp = os.path.join(LocalDir, fileTxt)


    if op == 'r':
        print(f'从 {fp} 中读取本地文件列表')
        try:
            with open(fp, 'r')as ft:
                for line in ft:
                    line = line.strip()
                    local_files.append(line)
        except Exception as e:
            print(e)
    elif op == 'w':
        print(f'更新 {fileTxt} 中文件列表')
        try:
            file = open(fp, 'w').close()  # 先清除掉checkfile文本里的内容
            get_all_file(ftp, FTPDir, fp)
            # for f, k in dictf.items():  # 获取到的文件信息的键值对
            #     with open(fp, 'a') as ft:
            #         ft.writelines([f'{k}\n'])
            # for d in dirs:  # 对子目录进行处理
            #     ftp.cwd(d)  # 进入ftp对应目录
            #     dictf, dirs = filelist(ftp)  # 调用filelist函数,递归ftp当前目录下的所有文件及目录
            #     for f, k in dictf.items():  # 获取到的文件信息的键值对
            #         with open(fp, 'a') as ft:
            #             ft.writelines([f'{k}\n'])

        except Exception as e:
            print(e)

def get_all_file(ftp, ftp_dir, fp):
    '''
       获取当前目录下所有的文件和子目录下所有文件的信息并存入checkfile
     '''
    ftp.cwd(ftp_dir)  # 进入ftp对应目录
    dictf, dirs = filelist(ftp)  # 调用filelist函数,递归ftp当前目录下的所有文件及目录
    for f, k in dictf.items():  # 获取到的文件信息的键值对
        with open(fp, 'a') as ft:
            ft.writelines([f'{k}\n'])
    for d in dirs:  # 对子目录进行处理
        get_all_file(ftp, d, fp)
        ftp.cwd('..')  # ftp返回上一级



def ftpmain():
    global appendFiles
    ftp = FtpConnect(HOST, USER, PASSWD)  # 连接并登录ftp服务器
    if ftp:  # 如果登录成功
        operfile(ftp,local_fname, 'r')  # 从checkfile.txt中获取已下载过的文件
        FtpDownloadDir(ftp, FTPDir, LocalDir)  # 将ftp指定目录下的文件更新到本地目录中
        # if config_valid["flag"] == "1":     # 判断是否启动更新重启功能
        #     for i in range(len(appendFiles)):
        #         if appendFiles[i].split(' ')[-1] == config_valid["dir"].split("\\")[-1]:  # config_valid["dir"].split("\\")[-1] 表示文件名,如果这个文件更新了就重新运行
        #             os.system("start explorer " + config_valid["dir"])
        if appendFiles:  # 如果有新文件更新到本地
            operfile(ftp, local_fname, 'w')  # 则将其追加到checkfile.txt中
            if config_valid["flag"]:  # 判断是否启动更新重启功能

                os.startfile(config_valid["dir"])  # 以管理员身份运行程序


            appendFiles = []  # 清空列表
        else:
            print(f'无需更新{local_fname}')
        ftp.quit()



def timer():
    scheduler = BlockingScheduler()
    scheduler.add_job(ftpmain, 'cron', hour='*/1', misfire_grace_time=300)
    scheduler.start()

if __name__ == '__main__':
    # timer()   #定时执行,如每小时执行一次

     ftpmain()   #手动执行,只执行一次

二、使用介绍

先配置configurantion.txt文件(注意配置文件必须和程序放在同目录下,否则读不到配置文件里的信息),用户名和密码也可以放到配置文件里,笔者自己的需求是把用户名和密码封装到程序里了,读者可自行调整,在这里插入图片描述
在ftpclient.py里第47行把下面代码注释掉然后打开上面2行,然后在配置文件里面添加对应数据就好了,flag为自启动功能,不用可以设置false(或者0和1),dir配置自启动的文件或者程序,可以是打开记事本来测试一下。
讲完配置文件来到我们的ftpclient.py,程序里的代码其实没必要深究,我已经给你们写好了,只需要看主程序使用就好了,主程序有2个,ftpmain函数和timer函数,ftpmain函数是只运行一次,timer函数是定时任务的意思,间隔可以自己调,具体需求读者需要使用哪个函数。
最后到administrator.py,这是一个获取管理员权限然后去执行代码,我们只需要去里面加上我们想要执行的代码即可在这里插入图片描述
这里是以管理员权限打开dir文件,然后在ftpclient.py里去调用该函数即可在这里插入图片描述
以上就是所有代码执行流程,是不是很简单呢。

三、程序封装和注册服务

为了任何工位机上都可以使用该工具,所以前面笔者用了32位的python环境,然后打包封装成32位的exe可执行文件,为了方便,笔者也封装了注册服务自启动脚本。
具体方法详见我的博客:
pyinstaller打包和注册服务脚本
最后项目展示:
在这里插入图片描述

四、填坑(希望读者能用到)

  1. 配置文件txt里的每行数据处理,需要处理文本里的注释和空格在这里插入图片描述
  2. 程序里读取配置文件的路径一定要写相对路径,不要写绝对路径,绝对路径在打包后注册位服务运行的时候是读不到文本的,请读者使用一下函数去读取exe所在的路径,然后把配置文件和exe放到同目录下即可在这里插入图片描述
  3. 读文本的时候是utf-8格式,但是有些win7的电脑是utf-8 bom 格式,导致读取配置文件里的数据不对,utf-8 bom格式读出来起始数据会多一个\ufeff,笔者在这里写了异常来处理此问题在这里插入图片描述
  4. 自启动的程序如果是需要权限的,比如c盘的文件或者注册服务脚本程序什么的,就需要把代码块放到administrator.py里,然后在去调用

总结

虽然过程十分艰难,但在填了很多坑以后,还是顺利达到了最终目的,经过测试以后,已经投入使用!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ftp客服端实现自动更新文件(带更新完自动启动功能)-python 的相关文章

随机推荐

  • 内容管理软件——Obsidian、Zettlr学习笔记(附Typora)

    一 Obsidian 1 官网 Obsidian 2 学习教程 Obsidian 中文论坛 3 使用经验 3 1关于markdown常用格式 标题的格式 标题级数 空格 文本内容 这是一段普通的文本 这是一级标题 这是二级标题 这是三级标题
  • ChatGPT在生态保护和可持续发展中的潜在作用如何?

    ChatGPT在生态保护和可持续发展领域具有潜在的重要作用 生态保护和可持续发展是全球性的挑战 涉及到环境保护 资源管理 气候变化应对 生物多样性保护等多个方面 ChatGPT作为一种人工智能技术 可以在以下几个方面发挥积极作用 1 数据分
  • ELK(六)ElasticSearch快速入门_中文分词

    分词 分词就是指将一个文本转化成一系列单词的过程 也叫文本分析 在ElasticSearch中称之为Analysis 举例 我是中国人 gt 我 是 中国人 分词API 指定分词器进行分词 POST analyze analyzer sta
  • 【深度学习】详解 Swin Transformer (SwinT)

    目录 摘要 一 介绍 二 原理 2 1 整体架构 2 1 1 Architecture 2 1 2 Swin Transformer Block 2 2 基于移位窗口的自注意力 2 2 1 非重叠局部窗口中的自注意力 2 2 2 在连续块中
  • 图像分割必备知识点

    文章转自 微信公众号 机器学习炼丹术 文章转载或者交流联系作者微信 cyx645016617 Unet其实挺简单的 所以今天的文章并不会很长 喜欢的话可以参与文中的讨论 在文章末尾点赞 在看点一下呗 0 概述 语义分割 Semantic S
  • GoogleCast 简介

    Google Cast Function 依赖com android support mediarouter v7com google android gms play services cast frameworkCast 过程1 fra
  • 笔记本电池冲不进电或不存电的修复方法

    不少同学的本本 用不到2年电池就坏掉了不存电 几分钟就一泻千里 成了那啥 哈哈 别想多了 还有的本本因为放的太久了 几个月没充电 发现电池冲不进去电了 这个更糟进不去都 哎 这是怎么回事呢 其实电池并没有那么脆弱 电池电芯都是锂离子的 可千
  • 深度学习 训练吃显卡_深度学习为什么需要显卡计算?

    先解释一点 深度学习为什么需要显卡计算 GPU 是为大规模的并行运算而优化 GPU 上则更多的是运算单元 整数 浮点的乘加单元 特殊运算单元等等 GPU 往往拥有更大带宽的显存 因此在大吞吐量的应用中也会有很好的性能 这里有一个很有趣的解释
  • Java对象的实例化过程

    JAVA new流程 实例化过程 java对象的实例化过程
  • 主成分分析法(三):计算步骤

    主成分分析系列 主成分分析 一 基本思想与主成分估计方法 主成分分析 二 特征值因子的筛选 主成分分析法 三 计算步骤 目录 一 主成分分析简述 二 主成分分析法的步骤 1 对原始数据进行标准化处理 2 计算相关系数矩阵R 3 计算特征值和
  • pip install总是报错:ValueError: Trusted host URL must include a host part: ‘#‘

    一 问题现象 报错信息如下 Traceback most recent call last File user name anaconda3 bin pip line 11 in
  • Matlab求解基于RRT算法的自定义垛型的路径避障

    目录 背景 1 RRT搜索算法 2 基于Matlab的RRT搜索算法 3 基于Matlab的自定义垛型绘制 4 基于RRT算法的自定义垛型的路径避障 背景 在码垛机械臂路径规划过程中 需要根据现有箱子的码垛状态 给出下一个箱子的最佳码放无碰
  • 自定义一个softirq

    本文章添加自己定义一个额外的软中断 首先添加软中断种类 MY SOFTIRQ enum HI SOFTIRQ 0 TIMER SOFTIRQ NET TX SOFTIRQ NET RX SOFTIRQ BLOCK SOFTIRQ BLOCK
  • c语言之-umask()函数

    此函数的主要作用是在创建文件时设置或者屏蔽掉文件的一些权限 一般与open 函数配合使用 umask 设置建立新文件时的权限遮罩 相关函数 creat open 表头文件 sys types h sys stat h 定义函数 mode t
  • java数据类型陷阱_java学习_3.原生数据类型使用陷阱

    原生数据类型使用陷阱 Pitfall of Primitive Data Type 1 Java中的原生数据类型共有8种 1 整型 使用int表示 32位 2 字节型 使用byte表示 表示 128 127之间的256个整数 8位 3 短整
  • 8 种流行的计算机视觉应用

    计算机视觉是人工智能的一部分 它使计算机能够从计算机化的图片 视频中获取重要数据 并根据这些数据提出建议 简单地说 你可以理解 如果人工智能允许计算机思考 那么计算机视觉就会鼓励它们去看 注意到和理解 这是在深度学习和机器学习的帮助下完成的
  • 深入理解Solidity——作用域和声明

    作用域和声明 Scoping and Declarations 已声明的变量将具有其字节表示为全0的初始值 变量的初始值是任何类型的典型 零状态 zero state 例如 bool的初始值为false uint或int类型的默认值为0 对
  • FBX SDK的环境配置与FbxLine结构的输出

    FBX SDK的环境配置与FbxLine结构的输出 近期项目中 用到了FBX SDK 根据官网教程与博客等相关资料 在使用过程中主要发现了两点问题 1 FBX SDK的环境配置网上说法不一 2 FbxLine结构体官网教程没有给出具体例子
  • antd a-form-model 动态表单 自定义校验柯里化

    1 需求 前端通过后端字段遍历formItem 由于字段可能是金额 电话号码等 单独if太多了太麻烦 所以想到柯里化 2 代码 响应请求 xxx then res gt if res data list length 0 return fa
  • ftp客服端实现自动更新文件(带更新完自动启动功能)-python

    ftp客服端实现自动更新文件 带自动启动功能并封装为带配置文件的工具 python 前言 一 项目环境和结构 二 使用介绍 三 程序封装和注册服务 四 填坑 希望读者能用到 总结 前言 由于工位机不可能做到实时看守 当更新程序的时候我们还得