init database expect db_node

This commit is contained in:
Burgess Leo
2025-05-15 16:33:30 +08:00
parent c6800de871
commit 6af6fedc8e
24 changed files with 1232 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

20
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="db_ntfs_info" uuid="e357eca1-8d5c-49d9-a100-79497b7a51f0">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:E:\PythonWorkSpace\fastcopy\src\db_ntfs_info.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
</libraries>
</data-source>
</component>
</project>

10
.idea/fastcopy.iml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,24 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="801" name="Python" />
</Languages>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N806" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="downloadWenAnBa.extend" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (fastcopy)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (fastcopy)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fastcopy.iml" filepath="$PROJECT_DIR$/.idea/fastcopy.iml" />
</modules>
</component>
</project>

6
.idea/sqldialects.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.12

0
db_manage/__init__.py Normal file
View File

378
db_manage/create_tables.py Normal file
View File

@@ -0,0 +1,378 @@
import os
import sqlite3
def CreateDBConfigTable(db_path='../src/db_ntfs_info.db', table_name='db_config'):
"""
在指定路径下创建 SQLite 数据库,并在其中创建配置表。
:param db_path: str, 数据库文件的路径
:param table_name: str, 要创建的表名
:return: None
"""
# 确保目录存在
directory = os.path.dirname(db_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 连接到SQLite数据库如果文件不存在会自动创建
conn = sqlite3.connect(db_path)
# 创建一个游标对象
cursor = conn.cursor()
# 动态构建创建表的SQL语句
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Key TEXT UNIQUE NOT NULL,
Value TEXT NOT NULL
);
"""
# 执行SQL语句
cursor.execute(create_table_sql)
# 提交更改
conn.commit()
# 关闭连接
conn.close()
print(f"表 [{table_name}] 已在数据库 [{db_path}] 中创建成功")
def CreateDBDeviceTable(db_path='../src/db_ntfs_info.db', table_name='db_device'):
"""
在指定路径下创建 SQLite 数据库,并在其中创建设备信息表。
:param db_path: str, 数据库文件的路径
:param table_name: str, 要创建的表名
:return: None
"""
# 确保目录存在
directory = os.path.dirname(db_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 连接到SQLite数据库如果文件不存在会自动创建
conn = sqlite3.connect(db_path)
# 创建一个游标对象
cursor = conn.cursor()
# 动态构建创建表的SQL语句
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Path TEXT NOT NULL UNIQUE, -- 添加 UNIQUE 约束
Type TEXT NOT NULL CHECK(Type IN ('磁盘', '文件', '文件夹')),
Option TEXT
);
"""
# 执行SQL语句
cursor.execute(create_table_sql)
# 提交更改
conn.commit()
# 关闭连接
conn.close()
print(f"表 [{table_name}] 已在数据库 [{db_path}] 中创建成功")
def CreateDBNodeTable(db_path='../src/db_ntfs_info.db', table_name='db_node'):
"""
在指定路径下创建 SQLite 数据库,并在其中创建节点信息表。
:param db_path: str, 数据库文件的路径
:param table_name: str, 要创建的表名
:return: None
"""
# 确保目录存在
directory = os.path.dirname(db_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 连接到SQLite数据库如果文件不存在会自动创建
conn = sqlite3.connect(db_path)
# 创建一个游标对象
cursor = conn.cursor()
# 动态构建创建表的SQL语句
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
PathID INTEGER,
ParentID INTEGER,
NameHash TEXT,
PathHash TEXT,
ExtendNameID INTEGER,
DirLayer INTEGER,
GroupID INTEGER,
UserID INTEGER,
FileCreateTime TEXT,
FileModifyTime TEXT,
FileAccessTime TEXT,
FileAuthTime TEXT,
FileSize INTEGER,
FileMode INTEGER,
FileHash TEXT,
ExtentCount INTEGER,
extent1_DeviceID INTEGER,
extent1_Location INTEGER,
extent1_Length INTEGER,
extent2_DeviceID INTEGER,
extent2_Location INTEGER,
extent2_Length INTEGER,
extent3_DeviceID INTEGER,
extent3_Location INTEGER,
extent3_Length INTEGER,
extent4_DeviceID INTEGER,
extent4_Location INTEGER,
extent4_Length INTEGER,
-- 外键约束(可选)
FOREIGN KEY(PathID) REFERENCES path_table(ID),
FOREIGN KEY(ExtendNameID) REFERENCES extname_table(ID),
FOREIGN KEY(GroupID) REFERENCES groups(ID),
FOREIGN KEY(UserID) REFERENCES users(ID)
);
"""
# 执行SQL语句
cursor.execute(create_table_sql)
# 提交更改
conn.commit()
# 关闭连接
conn.close()
print(f"表 [{table_name}] 已在数据库 [{db_path}] 中创建成功")
def CreateDBUserTable(db_path='../src/db_ntfs_info.db', table_name='db_user'):
"""
在指定路径下创建 SQLite 数据库,并在其中创建用户表。
:param db_path: str, 数据库文件的路径
:param table_name: str, 要创建的表名
:return: None
"""
# 确保目录存在
directory = os.path.dirname(db_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 连接到SQLite数据库如果文件不存在会自动创建
conn = sqlite3.connect(db_path)
# 创建一个游标对象
cursor = conn.cursor()
# 动态构建创建表的SQL语句
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
UserName TEXT UNIQUE NOT NULL
);
"""
# 执行SQL语句
cursor.execute(create_table_sql)
# 提交更改
conn.commit()
# 关闭连接
conn.close()
print(f"表 [{table_name}] 已在数据库 [{db_path}] 中创建成功")
def CreateDBGroupTable(db_path='../src/db_ntfs_info.db', table_name='db_group'):
"""
在指定路径下创建 SQLite 数据库,并在其中创建组表。
:param db_path: str, 数据库文件的路径
:param table_name: str, 要创建的表名
:return: None
"""
# 确保目录存在
directory = os.path.dirname(db_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 连接到SQLite数据库如果文件不存在会自动创建
conn = sqlite3.connect(db_path)
# 创建一个游标对象
cursor = conn.cursor()
# 动态构建创建表的SQL语句
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
GroupName TEXT UNIQUE NOT NULL
);
"""
# 执行SQL语句
cursor.execute(create_table_sql)
# 提交更改
conn.commit()
# 关闭连接
conn.close()
print(f"表 [{table_name}] 已在数据库 [{db_path}] 中创建成功")
def CreateDBExtendSnippetTable(db_path='../src/db_ntfs_info.db', table_name='db_extend_extent'):
"""
在指定路径下创建 SQLite 数据库,并在其中创建扩展片段表。
:param db_path: str, 数据库文件的路径
:param table_name: str, 要创建的表名
:return: None
"""
# 确保目录存在
directory = os.path.dirname(db_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 连接到SQLite数据库如果文件不存在会自动创建
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 动态构建创建表的SQL语句
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
NodeID INTEGER NOT NULL,
ExtentNumber INTEGER NOT NULL,
extent_DeviceID INTEGER,
extent_Location INTEGER,
extent_Length INTEGER,
-- 外键约束(可选)
FOREIGN KEY(NodeID) REFERENCES db_node(ID)
);
"""
# 执行SQL语句
cursor.execute(create_table_sql)
# 提交更改
conn.commit()
conn.close()
print(f"表 [{table_name}] 已在数据库 [{db_path}] 中创建成功")
def CreateDBPathTable(db_path='../src/db_path.db', table_name='db_path'):
"""
在指定路径下创建 SQLite 数据库,并在其中创建路径信息表,
包含 DeviceID 字段,用于标记文件所属设备(磁盘)。
:param db_path: str, 数据库文件的路径
:param table_name: str, 要创建的表名
:return: None
"""
# 确保目录存在
directory = os.path.dirname(db_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 连接到SQLite数据库如果文件不存在会自动创建
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 动态构建创建表的SQL语句包含 DeviceID 外键)
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
-- DeviceID TEXT NOT NULL,
Path TEXT NOT NULL,
Name TEXT NOT NULL,
PathHash TEXT UNIQUE NOT NULL,
IsDir INTEGER NOT NULL CHECK(IsDir IN (0, 1)),
ParentID INTEGER,
ContentSize INTEGER,
-- 外键约束
-- FOREIGN KEY(DeviceID) REFERENCES db_device(ID),
FOREIGN KEY(ParentID) REFERENCES {table_name}(ID)
);
"""
# 执行SQL语句
cursor.execute(create_table_sql)
# 提交更改
conn.commit()
conn.close()
print(f"表 [{table_name}] 已在数据库 [{db_path}] 中创建成功")
def CreateDBExtendNameTable(db_path='../src/db_extend_name.db', table_name='db_extend_name'):
"""
在指定路径下创建 SQLite 数据库,并在其中创建扩展名表。
:param db_path: str, 数据库文件的路径
:param table_name: str, 要创建的表名
:return: None
"""
# 确保目录存在
directory = os.path.dirname(db_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 连接到SQLite数据库如果文件不存在会自动创建
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 动态构建创建表的SQL语句
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
ExtendName TEXT UNIQUE NOT NULL
);
"""
# 执行SQL语句
cursor.execute(create_table_sql)
# 提交更改
conn.commit()
conn.close()
print(f"表 [{table_name}] 已在数据库 [{db_path}] 中创建成功")
def main():
CreateDBDeviceTable(db_path='../src/db_ntfs_info.db', table_name='db_device')
CreateDBConfigTable(db_path='../src/db_ntfs_info.db', table_name='db_config')
CreateDBNodeTable(db_path='../src/db_ntfs_info.db', table_name='db_node')
CreateDBUserTable(db_path='../src/db_ntfs_info.db', table_name='db_user')
CreateDBGroupTable(db_path='../src/db_ntfs_info.db', table_name='db_group')
CreateDBExtendSnippetTable(db_path='../src/db_ntfs_info.db', table_name='db_extend_extent')
CreateDBPathTable(db_path='../src/db_ntfs_info.db', table_name='db_path')
CreateDBExtendNameTable(db_path='../src/db_ntfs_info.db', table_name='db_extend_name')
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,45 @@
import sqlite3
def DropAllTables(db_path):
"""
删除指定数据库中的所有8张表如果存在
:param db_path: str, SQLite 数据库文件的路径
:return: None
"""
# 表名列表根据你之前创建的8张表
tables = [
'db_config', # 表1配置表
'db_device', # 表2设备表
'db_node', # 表3节点信息表
'db_group', # 表4组表
'db_user', # 表5用户表
'db_extend_extent', # 表6扩展片段表
'db_path', # 表7路径表
'db_extend_name' # 表8扩展名表
]
# 连接到SQLite数据库
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 遍历并删除每张表
for table in tables:
cursor.execute(f"DROP TABLE IF EXISTS {table};")
print(f"表 [{table}] 已删除")
# 提交更改
conn.commit()
conn.close()
print("✅ 所有预定义表已删除完成")
def main():
DropAllTables(db_path='../src/db_ntfs_info.db')
if __name__ == '__main__':
main()

0
main.py Normal file
View File

45
ntfs_utils/__init__.py Normal file
View File

@@ -0,0 +1,45 @@
from db_config import GetNTFSBootInfo, InsertInfoToDBConfig
from db_device import ScanSpecialVolumes, InsertVolumesToDB
from db_extend_name import InsertExtensionsToDB
from db_group import InsertGroupToDB
from db_path import GenerateHash, ShouldSkipPath, ScanVolume, InsertPathDataToDB
from db_user import InsertUserToDB
def main():
volume_letter = 'Z'
# 初始化 db_config 表
config_data = GetNTFSBootInfo(volume_letter)
InsertInfoToDBConfig(config_data)
# 初始化 db_device 表
device_data = ScanSpecialVolumes(volume_letter)
InsertVolumesToDB([device_data])
# 初始化 db_user 表
user_list = ["Copier"]
InsertUserToDB(user_list)
# 初始化 db_group 表
group_name_list = ["Copier"]
InsertGroupToDB(group_name_list)
# 初始化 db_path 表
scanned_data = ScanVolume(volume_letter)
InsertPathDataToDB(scanned_data)
# 初始化 db_extend_name 表
common_extensions = [
"txt", "log", "csv", "xls", "xlsx", "doc", "docx",
"ppt", "pptx", "pdf", "jpg", "jpeg", "png", "gif",
"bmp", "mp3", "wav", "mp4", "avi", "mkv", "mov",
"exe", "dll", "bat", "ini", "reg", "zip", "rar", "7z",
"json", "xml", "html", "css", "js", "py", "java", "cpp"
]
count = InsertExtensionsToDB(common_extensions)
print(f"共插入 {count} 个新扩展名。")
if __name__ == '__main__':
main()

128
ntfs_utils/db_config.py Normal file
View File

@@ -0,0 +1,128 @@
import ctypes
import sqlite3
def GetNTFSBootInfo(volume_letter):
"""
从指定 NTFS 卷的 $Boot 元文件中提取:
- Bytes per sector
- Sectors per cluster
- Cluster size (bytes)
参数:
volume_letter: 卷标字符串,例如 'C'
返回:
dict 包含上述信息
"""
# 构造设备路径,格式为 \\.\C:
device_path = f"\\\\.\\{volume_letter}:"
# 打开卷设备(需要管理员权限)
handle = ctypes.windll.kernel32.CreateFileW(
device_path,
0x80000000, # GENERIC_READ
0x00000001 | 0x00000002, # FILE_SHARE_READ | FILE_SHARE_WRITE
None,
3, # OPEN_EXISTING
0,
None
)
if handle == -1:
raise PermissionError(f"无法打开卷 {volume_letter},请以管理员身份运行。")
try:
buffer = bytearray(512)
buffer_address = (ctypes.c_byte * len(buffer)).from_buffer(buffer)
bytes_read = ctypes.c_ulong(0)
# 读取第一个扇区BPB / $Boot 扇区)
success = ctypes.windll.kernel32.ReadFile(
handle,
buffer_address,
len(buffer),
ctypes.byref(bytes_read),
None
)
if not success or bytes_read.value != 512:
raise RuntimeError("读取卷引导扇区失败。")
finally:
ctypes.windll.kernel32.CloseHandle(handle)
# 解析 Bytes Per Sector (偏移 0x0BWORD 类型)
bytes_per_sector = int.from_bytes(buffer[0x0B:0x0D], byteorder='little')
# 解析 Sectors Per Cluster (偏移 0x0DBYTE 类型)
sectors_per_cluster = buffer[0x0D]
# 计算簇大小
cluster_size = bytes_per_sector * sectors_per_cluster
return {
"BytesPerSector": bytes_per_sector,
"SectorsPerCluster": sectors_per_cluster,
"ClusterSize": cluster_size
}
def InsertInfoToDBConfig(config_data, db_path='../src/db_ntfs_info.db', table_name='db_config'):
"""
将 NTFS 配置信息以键值对形式写入数据库的配置表中。
参数:
config_data: dict包含配置键值对
db_path: strSQLite 数据库路径
table_name: str目标表名默认为 'db_config'
返回:
None
"""
# 连接到 SQLite 数据库(如果不存在则会自动创建)
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 创建表(如果不存在)
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Key TEXT UNIQUE NOT NULL,
Value TEXT NOT NULL
);
"""
cursor.execute(create_table_sql)
# 插入或替换数据(使用 INSERT OR REPLACE
insert_sql = f"""
INSERT OR REPLACE INTO {table_name} (Key, Value)
VALUES (?, ?)
"""
for key, value in config_data.items():
cursor.execute(insert_sql, (key, str(value)))
conn.commit()
print("键值对配置已成功写入数据库")
except Exception as e:
print(f"数据库操作失败: {e}")
conn.rollback()
finally:
conn.close()
def main():
volume = "Z"
info = GetNTFSBootInfo(volume)
print(f"{volume} 的 BPB 信息:")
print(info)
InsertInfoToDBConfig(info)
if __name__ == "__main__":
main()

125
ntfs_utils/db_device.py Normal file
View File

@@ -0,0 +1,125 @@
import sqlite3
import psutil
def ScanSpecialVolumes(volume_letter):
"""
扫描指定的单个磁盘卷,返回其基本信息字典。
参数:
volume_letter: str磁盘盘符例如 "C", "Z"
返回:
dict: 包含 Path, Type, Option 字段的字典
"""
# 简单校验盘符格式(去除可能的冒号)
if len(volume_letter) >= 1 and volume_letter[-1] == ":":
volume_letter = volume_letter[:-1]
if not volume_letter or len(volume_letter) != 1 or not volume_letter.isalpha():
raise ValueError("无效的磁盘盘符,应为单个字母,如 'C''Z'")
return {
"Path": volume_letter.upper(),
"Type": "磁盘",
"Option": None
}
def ScanNTFSVolumes():
"""
扫描当前系统中所有 NTFS 格式的磁盘卷。
返回:
list of dict: 包含盘符等信息的字典列表
"""
ntfs_volumes = []
for partition in psutil.disk_partitions():
if partition.fstype.upper() == 'NTFS':
# 提取盘符(去掉冒号)
drive_letter = partition.device[0] if len(partition.device) >= 2 and partition.device[1] == ':' else None
if drive_letter:
ntfs_volumes.append({
"Path": drive_letter,
"Type": "磁盘",
"Option": None
})
return ntfs_volumes
def InsertVolumesToDB(data, db_path='../src/db_ntfs_info.db', table_name='db_device'):
"""
将 NTFS 磁盘信息写入数据库表,并防止重复插入。
参数:
data: list of dict包含 Path, Type, Option 字段的数据
db_path: strSQLite 数据库路径
table_name: str目标表名
返回:
int: 成功插入的记录数
"""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 创建表(如果不存在),并添加 Path 的唯一性约束
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Path TEXT NOT NULL UNIQUE,
Type TEXT NOT NULL CHECK(Type IN ('磁盘', '文件', '文件夹')),
Option TEXT
);
"""
cursor.execute(create_table_sql)
inserted_count = 0
insert_sql = f"""
INSERT OR IGNORE INTO {table_name} (Path, Type, Option)
VALUES (?, ?, ?)
"""
for item in data:
cursor.execute(insert_sql, (
item['Path'],
item['Type'],
item['Option']
))
if cursor.rowcount > 0:
inserted_count += 1
conn.commit()
print(f"✅ 成功插入 {inserted_count} 条 NTFS 磁盘信息")
return inserted_count
except Exception as e:
print(f"❌ 数据库操作失败: {e}")
conn.rollback()
return 0
finally:
conn.close()
def main():
# 扫描系统下所有 NTFS 磁盘
# volumes = ScanNTFSVolumes()
# print("🔍 找到以下 NTFS 磁盘:")
# for vol in volumes:
# print(vol)
#
# success_count = InsertVolumesToDB(volumes)
# print(f"共插入 {success_count} 条记录到数据库。")
# 扫描单个磁盘
volume_latter = "Z"
device_data = ScanSpecialVolumes(volume_latter)
InsertVolumesToDB([device_data])
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,68 @@
import sqlite3
def InsertExtensionsToDB(extensions, db_path='../src/db_ntfs_info.db', table_name='db_extend_name'):
"""
将扩展名列表插入到数据库中,自动忽略重复项。
参数:
extensions: list of str要插入的扩展名列表如 ["txt", "jpg"]
db_path: strSQLite 数据库路径
table_name: str扩展名表名
返回:
int: 成功插入的新记录数量
"""
if not isinstance(extensions, list):
raise TypeError("extensions 必须是一个列表")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 创建表(如果不存在)
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
ExtendName TEXT UNIQUE NOT NULL
);
"""
cursor.execute(create_table_sql)
# 插入语句(忽略重复)
insert_sql = f"""
INSERT OR IGNORE INTO {table_name} (ExtendName)
VALUES (?)
"""
# 构造插入数据格式
data_to_insert = [(ext.lower(),) for ext in extensions if ext.strip()]
# 批量插入
cursor.executemany(insert_sql, data_to_insert)
conn.commit()
inserted_count = cursor.rowcount
if inserted_count > 0:
print(f"✅ 成功插入 {inserted_count} 个扩展名")
else:
print("⚠️ 没有新的扩展名被插入(可能已存在)")
return inserted_count
except Exception as e:
print(f"❌ 插入失败: {e}")
conn.rollback()
return 0
finally:
conn.close()
# 示例调用
if __name__ == "__main__":
# 常见的文件扩展名列表(可选)
common_extensions = ["txt", "log", "csv", "xls", "xlsx", "doc", "docx"]
count = InsertExtensionsToDB(common_extensions)
print(f"共插入 {count} 个新扩展名。")

65
ntfs_utils/db_group.py Normal file
View File

@@ -0,0 +1,65 @@
import sqlite3
def InsertGroupToDB(group_name_list, db_path='../src/db_ntfs_info.db', table_name='db_group'):
"""
向用户组表中插入多个组名。
参数:
group_name_list: list of str要插入的组名列表
db_path: strSQLite 数据库路径
table_name: str用户组表名
返回:
int: 成功插入的记录数
"""
if not isinstance(group_name_list, list):
raise TypeError("group_name_list 必须是一个列表")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 创建表(如果不存在)
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
GroupName TEXT UNIQUE NOT NULL
);
"""
cursor.execute(create_table_sql)
# 构建插入数据格式
insert_sql = f"""
INSERT OR IGNORE INTO {table_name} (GroupName)
VALUES (?)
"""
data_to_insert = [(name,) for name in group_name_list]
# 批量插入
cursor.executemany(insert_sql, data_to_insert)
conn.commit()
inserted_count = cursor.rowcount
if inserted_count > 0:
print(f"✅ 成功插入 {inserted_count} 个组名")
else:
print("⚠️ 没有新的组名被插入(可能已存在)")
return inserted_count
except Exception as e:
print(f"❌ 插入失败: {e}")
conn.rollback()
return 0
finally:
conn.close()
# 示例调用
if __name__ == "__main__":
groups = ["Copier", "Admin", "Guest", "Developer"]
count = InsertGroupToDB(groups)
print(f"共插入 {count} 个新组名。")

178
ntfs_utils/db_path.py Normal file
View File

@@ -0,0 +1,178 @@
import hashlib
import os
import sqlite3
def GenerateHash(s: str) -> str:
"""
对输入字符串生成 SHA-256 哈希值。
用于唯一标识一个路径PathHash
"""
return hashlib.sha256(s.encode('utf-8')).hexdigest()
def ShouldSkipPath(path: str) -> bool:
"""
判断是否应跳过该路径NTFS元文件或系统文件夹
"""
name = os.path.basename(path)
if name.startswith('$'):
return True
if name == "System Volume Information":
return True
return False
def ScanVolume(volume_letter: str):
"""
完整扫描指定磁盘的所有文件和目录,忽略 NTFS 元文件和系统文件夹,
并为每个节点分配 ParentID。
返回:
list of dict包含文件/目录信息的字典列表
"""
root_path = f"{volume_letter.upper()}:\\"
if not os.path.exists(root_path):
raise ValueError(f"磁盘 {root_path} 不存在")
result = []
path_to_id = {} # 用于记录路径到数据库 ID 的映射
counter = 1 # 模拟数据库自增 ID
for root, dirs, files in os.walk(root_path, topdown=True, onerror=None, followlinks=False):
# 过滤掉需要跳过的目录
dirs[:] = [d for d in dirs if not ShouldSkipPath(os.path.join(root, d))]
for entry in files + dirs:
full_path = os.path.join(root, entry)
if ShouldSkipPath(full_path):
continue
try:
if os.path.isdir(full_path):
is_dir = 1
bytes_size = 0
elif os.path.isfile(full_path):
is_dir = 0
bytes_size = os.path.getsize(full_path)
else:
continue
name = entry
# ✅ 修正点:对 Path 字段进行哈希
path_hash = GenerateHash(full_path)
# 计算 ContentSizeKB小文件至少显示为 1 KB
content_size = bytes_size // 1024
if content_size == 0 and bytes_size > 0:
content_size = 1
# 获取父目录路径
parent_path = os.path.dirname(full_path)
parent_id = path_to_id.get(parent_path, 0) # 默认为 0根目录可能未录入
item = {
"ID": counter,
"Path": full_path,
"Name": name,
"PathHash": path_hash,
"IsDir": is_dir,
"ParentID": parent_id,
"ContentSize": content_size
}
result.append(item)
path_to_id[full_path] = counter
counter += 1
except Exception as e:
print(f"⚠️ 跳过路径 {full_path},错误: {e}")
return result
def InsertPathDataToDB(data, db_path='../src/db_ntfs_info.db', table_name='db_path', batch_size=20):
"""
批量将扫描结果写入数据库。
"""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 创建表(如果不存在)
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Path TEXT NOT NULL,
Name TEXT NOT NULL,
PathHash TEXT UNIQUE NOT NULL,
IsDir INTEGER NOT NULL CHECK(IsDir IN (0, 1)),
ParentID INTEGER,
ContentSize INTEGER,
FOREIGN KEY(ParentID) REFERENCES {table_name}(ID)
);
"""
cursor.execute(create_table_sql)
# 插入语句(忽略重复 PathHash
insert_sql = f"""
INSERT OR IGNORE INTO {table_name}
(Path, Name, PathHash, IsDir, ParentID, ContentSize)
VALUES (?, ?, ?, ?, ?, ?)
"""
total_inserted = 0
batch = []
for item in data:
batch.append((
item['Path'],
item['Name'],
item['PathHash'],
item['IsDir'],
item['ParentID'] or 0,
item['ContentSize']
))
if len(batch) >= batch_size:
cursor.executemany(insert_sql, batch)
conn.commit()
total_inserted += cursor.rowcount
print(f"✅ 提交一批 {len(batch)} 条数据")
batch.clear()
# 插入剩余数据
if batch:
cursor.executemany(insert_sql, batch)
conn.commit()
total_inserted += cursor.rowcount
print(f"✅ 提交最后一批 {len(batch)} 条数据")
print(f"✅ 总共插入 {total_inserted} 条记录到数据库。")
except Exception as e:
print(f"❌ 插入失败: {e}")
conn.rollback()
finally:
conn.close()
# 示例主函数
def main():
volume_letter = "Z"
print(f"🔍 开始全盘扫描磁盘 {volume_letter}:\\ ...")
scanned_data = ScanVolume(volume_letter)
print(f"📊 共扫描到 {len(scanned_data)} 条有效记录,开始入库...")
InsertPathDataToDB(scanned_data)
print("✅ 全盘扫描与入库完成")
if __name__ == "__main__":
main()

66
ntfs_utils/db_user.py Normal file
View File

@@ -0,0 +1,66 @@
import sqlite3
def InsertUserToDB(username_list, db_path='../src/db_ntfs_info.db', table_name='db_user'):
"""
向用户表中插入多个用户名。
参数:
username_list: list of str要插入的用户名列表
db_path: strSQLite 数据库路径
table_name: str用户表名
返回:
int: 成功插入的新用户数量
"""
if not isinstance(username_list, list):
raise TypeError("username_list 必须是一个列表")
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 创建表(如果不存在)
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
UserName TEXT UNIQUE NOT NULL
);
"""
cursor.execute(create_table_sql)
# 插入语句(忽略重复)
insert_sql = f"""
INSERT OR IGNORE INTO {table_name} (UserName)
VALUES (?)
"""
# 构建数据格式
data_to_insert = [(name,) for name in username_list]
# 批量插入
cursor.executemany(insert_sql, data_to_insert)
conn.commit()
inserted_count = cursor.rowcount
if inserted_count > 0:
print(f"✅ 成功插入 {inserted_count} 个用户")
else:
print("⚠️ 没有新的用户被插入(可能已存在)")
return inserted_count
except Exception as e:
print(f"❌ 插入失败: {e}")
conn.rollback()
return 0
finally:
conn.close()
# 示例调用
if __name__ == "__main__":
users = ["Alice", "Bob", "Charlie", "David"]
count = InsertUserToDB(users)
print(f"共插入 {count} 个新用户。")

9
pyproject.toml Normal file
View File

@@ -0,0 +1,9 @@
[project]
name = "fastcopy"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"psutil>=7.0.0",
]

BIN
src/db_ntfs_info.db Normal file

Binary file not shown.

29
uv.lock generated Normal file
View File

@@ -0,0 +1,29 @@
version = 1
revision = 2
requires-python = ">=3.12"
[[package]]
name = "fastcopy"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "psutil" },
]
[package.metadata]
requires-dist = [{ name = "psutil", specifier = ">=7.0.0" }]
[[package]]
name = "psutil"
version = "7.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" },
{ url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" },
{ url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" },
{ url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" },
{ url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" },
{ url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" },
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
]