吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1233|回复: 5
收起左侧

[Python 原创] Python+tkinter+pywin32 实现的用于创建和管理磁盘中符号链接的工具

[复制链接]
lq2007 发表于 2023-7-29 23:46
本帖最后由 lq2007 于 2023-7-29 23:47 编辑

由于 C 盘空间不足,我有把默认存放在 C 盘的目录通过符号链接转移出去的习惯(不改默认配置主要是多个程序都要改麻烦)。于是写了一个扫描和建立链接的工具以防万一

file.py 主要创建 File 类用于获取文件类型、是否为符号链接或快捷方式,并取得其真实地址

"""
获取文件信息
:param p: 文件路径
:return: 文件信息
"""
self.path = p
# 判断文件 or 文件夹
self.is_directory = os.path.isdir(p)
self.is_file = os.path.isfile(p)
self.is_exist = self.is_file or self.is_directory
if self.is_exist:
    s = os.stat(p)
    # 文件基本信息
    self.hard_link_count = s.st_nlink
    self.size = s.st_size
    # 判断链接
    self.is_windows_link = self.is_file and os.path.basename(p).endswith('.lnk')
    if self.is_windows_link:
        # 获取 lnk 目标
        shell = win32com.client.Dispatch('WScript.Shell')
        shortcut = shell.CreateShortCut(p)
        real_path = str(shortcut.Targetpath)
        target = File(real_path)
        self.real_path = target.real_path
        self.is_symbol_link = False
        # 快捷方式可能无效或为网站等
        self.is_exist = target.is_exist
    else:
        # 检查符号链接
        attr = find_first_file(p)
        self.is_symbol_link = not (not attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) \
                              and IsReparseTagNameSurrogate(attr.dwReserved0)
        self.real_path = os.path.realpath(p)
else:
    self.real_path = None
    self.is_symbol_link = False
    self.is_windows_link = False
    self.hard_link_count = 0
    self.size = 0
self.basename = os.path.basename(self.real_path) if self.is_exist else None

componenet/link_manager.py 文件主要实现查找目录中的所有符号链接及其真实地址

def _select_dir(self):
    """
    选择一个目录,遍历所有文件并将符号链接及其源文件插入列表
    :return: None
    """
    # 检查是否有正在进行的任务
    if self.is_working:
        messagebox.showerror('任务进行中', '当前存在正在进行的任务,无法进行新任务')
        return
    # 选择目录
    directory = filedialog.askdirectory()
    if directory is None or directory == "" or not os.path.isdir(directory):
        return
    self.lst_log.delete(0, 'end')
    self.lst_log.insert('end', f'loading {directory}')
    self.lst_log.insert('end', '...')
    last_idx = self.lst_log.size() - 1
    # 插入文件到记录
    def insert_to_data(f: File):
        if f.real_path in self.data:
            self.data[f.real_path].append(f)
        else:
            self.data[f.real_path] = [f]
        self.fresh()
    # 遍历目录,递归查找符号链接
    # 返回 True 表示查询中断
    def walk_dir(file_dir: File) -> bool:
        if self.working_stop:
            self.is_working = False
            self.working_stop = False
            return True
        self.lst_log.delete(last_idx, last_idx)
        self.lst_log.insert(last_idx, f'...{file_dir.path}')
        # 目录本身是符号链接
        if file_dir.is_symbol_link:
            insert_to_data(file_dir)
            return False
        # 遍历目录内容
        try:
            for entry in os.scandir(file_dir.real_path):
                if self.working_stop:
                    self.is_working = False
                    self.working_stop = False
                    return True
                self.lst_log.delete(last_idx, last_idx)
                self.lst_log.insert(last_idx, f'...{file_dir.path}')
                file = File(entry.path)
                if file.is_symbol_link:
                    insert_to_data(file)
                elif file.is_directory:
                    if walk_dir(file):
                        return True
            return False
        except PermissionError:
            self.lst_log.insert('end', f'  -{file_dir.real_path} 权限不足')
            return False
    self.is_working = True
    self.working_stop = False
    td = Thread(target=lambda: walk_dir(File(directory)))
    td.start()

componenet/link_create.py 可以一次为一组文件创建符号链接,也可以将文件或目录转移到新位置后在原位置创建符号链接。在执行前会检查是否有重名文件、如果要移动也会检查是否被占用。在这里需要管理员权限

"""
创建符号链接
:return: None
"""
lst_proc = self._process
lst_proc.delete(0, 'end')
# 检查文件名是否冲突
file_map_by_name = {}  # type: dict[str, list[File]]
for file in self._src.files:
    if file.basename in file_map_by_name:
        file_map_by_name[file.basename].append(file)
    else:
        file_map_by_name[file.basename] = [file]
err_msg = ''
for name in file_map_by_name:
    arr = file_map_by_name[name]
    if len(arr) > 1:
        err_msg += f'文件重复:{name}'
        for file in arr:
            err_msg += f'\n{file.real_path}'
if len(err_msg) > 0:
    messagebox.showerror('文件重复', err_msg)
    return
lst_proc.insert('end', '文件重复性检查完成')
# 检查文件是否已存在
for file in self._dst.files:
    for pp in os.scandir(file.real_path):
        name = os.path.basename(pp)
        if name in file_map_by_name:
            if len(err_msg) > 0:
                err_msg += '\n'
            err_msg += f'{file.real_path}: {name} 已存在'
            continue
if len(err_msg) > 0:
    messagebox.showerror('文件已存在', err_msg)
    return
lst_proc.insert('end', '文件存在性检查完成')
# 检查管理员权限
winapi.require_admin()
while not winapi.is_admin():
    if messagebox.askretrycancel('权限不足', '需要管理员权限运行'):
        winapi.require_admin()
    else:
        return
# 创建软连接
count = len(self._src.files)
for index, file in enumerate(self._src.files):
    for dp in self._dst.files:
        winapi.make_symbol_in(file.real_path, dp.real_path, file.basename, file.is_directory)
        lst_proc.insert('end', f'{index + 1}/{count} {file.real_path} <- {dp.real_path}/{file.basename}')

winapi.py 主要都是涉及到与 Windows 系统相关的函数,多会使用系统的库函数,用于获取文件属性、创建符号链接、检查文件占用、管理员权限等

def find_first_file(p: str) -> WIN32_FIND_DATAW:
    """
    查找文件(夹)的文件属性
    :param p: 文件路径
    :return: 文件属性,详见 https://learn.microsoft.com/zh-cn/windows/win32/fileio/file-attribute-constants
    """
    find_result = wintypes.WIN32_FIND_DATAW()
    find_handle = kernel32.FindFirstFileW(p, ctypes.byref(find_result))
    kernel32.FindClose(find_handle)
    return find_result

def make_symbol_link(src: str, dst: str, is_directory: bool):
    """
    创建符号链接
    :param src: 源文件/目录
    :param dst: 目标文件/目录
    :param is_directory: 是否为目录
    :return: None
    """
    # return kernel32.CreateSymbolicLinkW(src, dst, 0x1 if is_directory else 0x0)
    print(f"link {src} <= {dst}")
    win32file.CreateSymbolicLink(dst, src, 1 if is_directory else 0)

def is_admin() -> bool:
    """
    检查是否为管理员权限
    :return: 是否以管理员权限执行
    """
    try:
        return shell32.IsUserAnAdmin()
    except:
        return False

def require_admin():
    """
    请求管理员权限
    :return: None
    """
    if not is_admin():
        shell32.ShellExecuteW(None, 'runas', sys.executable, __file__, None, 1)

def is_file_used(p):
    """
    检查文件是否被占用
    :param p: 文件
    :return: 是否被占用
    """
    handle = None
    try:
        handle = win32file.CreateFile(p, win32file.GENERIC_READ,
                                      0, None, win32file.OPEN_EXISTING, win32file.FILE_ATTRIBUTE_NORMAL, None)
        return int(handle) == win32file.INVALID_HANDLE_VALUE
    except:
        return True
    finally:
        try:
            win32file.CloseHandle(handle)
        except:
            pass

def is_directory_open(p):
    """
    检查目录是否被占用
    :param p: 目录
    :return: 是否被占用
    """
    try:
        os.listdir(p)
        return False
    except:
        return True

目前已知的问题主要是:

  1. 使用 Listbox 作为信息输出感觉还是不太方便
  2. 扫描出的符号链接需要多点一下才能切换过去
  3. 导出的文件重新导入还没有测试

以及,在 Windows 11 的情况下,尝试了多种方式也没能实现将文件拖拽进程序中的操作,windnd 和 pydnd 好像都不行了?

完整的源代码位于Github,或者从论坛附件下载,附件下载的解压密码 52pojie



device_manager-master.rar (11.97 KB, 下载次数: 1)

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
angxi6 + 1 + 1 热心回复!
hrh123 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

xiatongxue 发表于 2023-7-30 14:01
看下 学习ing....
angxi6 发表于 2023-7-30 14:30
数码小叶 发表于 2023-7-30 15:13
258239234 发表于 2023-7-30 15:55
很专业,高手!
cageforawalk666 发表于 2023-7-31 15:16
有学到新知识!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 09:41

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表