1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 15:33:03 +08:00

Compare commits

..

19 Commits

Author SHA1 Message Date
github-actions[bot] db7a868598 chore(release): v1.0.3 [auto release commit] 2026-01-17 17:52:03 +00:00
KenanZhu f1e0334ce3 docs(MsgBase, LibOperator): 添加并完善类文档注释 2026-01-16 23:41:25 +08:00
KenanZhu b9411261ea style(ALMainWorkers): 一些格式更改 2026-01-16 23:25:42 +08:00
KenanZhu fa737711d4 optimize(ConfigReader, ConfigWriter): 优化配置文件读写类逻辑,完善异常处理,添加注释文档 2026-01-16 23:23:03 +08:00
KenanZhu 79e2128fca style(operators.*): 显式指定浏览器驱动类型为 WebDriver 2026-01-16 23:21:36 +08:00
KenanZhu 128c8e7a83 style(*): 移除未使用的 import 语句 2026-01-16 22:37:26 +08:00
KenanZhu 6474f6e3bb style(*): 格式化一些界面类的构造函数 2026-01-16 22:33:01 +08:00
KenanZhu ba60a5d884 style(comment): 修改一些注释格式 2026-01-15 17:08:54 +08:00
KenanZhu 4d8f8130dc chore(operators.__init__): 添加 LibChecker 类的简介 2026-01-13 22:40:45 +08:00
KenanZhu eba99cab9f fix(ALSeatMapWidget): 修复座位图选择的确定取消逻辑 2026-01-13 22:01:16 +08:00
KenanZhu aa7a806ff7 fix(gui): 修复一些界面问题 2026-01-12 14:22:20 +08:00
KenanZhu bb180f8c8e fix(ALConfigWidget, LibReserve): 修改二楼楼层区域名称
将 二层外环 改为 二层西区
2026-01-09 14:06:36 +08:00
KenanZhu 107ed41b58 chore(*): 更新 license 和版权信息为 2025 - 2026 年 2026-01-09 14:00:25 +08:00
github-actions[bot] 43b87db4eb chore(release): v1.0.2 [auto release commit] 2026-01-05 04:05:04 +00:00
KenanZhu ae23f65e5a fix(AutoLib): 修复并完善对不同浏览器驱动的支持,目前支持的浏览器驱动为 Edge、Chrome、Firefox
之前的代码只支持 Edge 浏览器驱动,现在完善了对 Chrome、Firefox 浏览器驱动的支持
2026-01-05 11:59:33 +08:00
KenanZhu a7b9c340ae refactor(ALConfigWidget): 初始化的默认浏览器驱动路径改为空 2026-01-05 11:58:15 +08:00
KenanZhu 96d733d2ed fix(ALConfigWidget): 修复配置界面错误字符 2026-01-05 11:43:16 +08:00
KenanZhu 65cb951ada ci(workflow): 优化 update-version.yml 中的版本信息更新逻辑
对 ALVersionInfo.py 文件的更新逻辑进行差异化处理,分别为 commit-release 和 build 阶段生成不同的 artifact:

* 针对 commit-release 阶段:将所有与构建(build)相关的字段值设置为 'local' 或 'null'
* 针对 build 阶段:保留完整的版本信息字段

这种差异化处理确保其后续提交的 release commit 不会包含构建相关的版本信息。
2026-01-04 10:05:57 +08:00
KenanZhu 94ce3433a3 ci(workflows): 重构 CI/CD 工作流执行配置 2026-01-03 14:33:49 +08:00
30 changed files with 561 additions and 262 deletions
@@ -1,48 +1,58 @@
name: Build and Release
name: Build
# This workflow compiles the application for Windows platform using PyInstaller, and
# archives the built artifacts as 'AutoLibrary.<tag_name>-windows-x86_64.zip'.
#
# It is triggered when called by the release workflow.
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
workflow_call:
inputs:
version:
description: 'Version number'
required: true
type: string
tag_name:
description: 'Tag name'
required: true
type: string
outputs:
version:
description: 'The version number'
value: ${{ jobs.build-windows.outputs.version }}
tag_name:
description: 'The tag name'
value: ${{ jobs.build-windows.outputs.tag_name }}
#
# Build Windows
#
jobs:
update-version-info:
uses: ./.github/workflows/update-version-info.yml
permissions:
contents: write
with:
tag_name: ${{ github.ref_name }}
ref: ${{ github.ref }}
commit-and-move-tag:
needs: update-version-info
if: ${{ needs.update-version-info.outputs.has_changes == 'true' }}
uses: ./.github/workflows/commit-and-move-tag.yml
permissions:
contents: write
with:
tag_name: ${{ needs.update-version-info.outputs.tag_name }}
version: ${{ needs.update-version-info.outputs.version }}
file_path: src/gui/ALVersionInfo.py
build-and-release:
build-windows:
runs-on: windows-latest
needs: [update-version-info, commit-and-move-tag]
if: always() && needs.update-version-info.result == 'success'
permissions:
contents: write
outputs:
version: ${{ steps.get_version.outputs.VERSION }}
tag_name: ${{ steps.get_version.outputs.TAG_NAME }}
steps:
- name: Checkout code with updated version info
- name: Checkout code
uses: actions/checkout@v4
with:
ref: main
- name: Get version info from previous job
id: get_tag
# here we download the build version of ALVersionInfo.py from artifacts
# and replace the committed version
- name: Download build version of ALVersionInfo.py
uses: actions/download-artifact@v4
with:
name: updated-version-info-for-build
path: src/gui/
- name: Get version info
id: get_version
run: |
$tagName = "${{ needs.update-version-info.outputs.tag_name }}"
$version = "${{ needs.update-version-info.outputs.version }}"
$version = "${{ inputs.version }}"
$tagName = "${{ inputs.tag_name }}"
echo "TAG_NAME=$tagName" >> $env:GITHUB_OUTPUT
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
@@ -50,17 +60,17 @@ jobs:
Write-Host "✓ Version: $version"
shell: pwsh
- name: Verify ALVersionInfo.py was updated
- name: Verify 'ALVersionInfo.py' was updated
run: |
$versionInfoFile = "src/gui/ALVersionInfo.py"
Write-Host "Verifying $versionInfoFile content:"
Write-Host "================================"
Write-Host "=================================="
Get-Content $versionInfoFile | Write-Host
Write-Host "================================"
Write-Host "=================================="
shell: pwsh
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v4
with:
python-version: '3.13'
@@ -69,7 +79,7 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirement.txt
- name: Fix ddddocr compatibility and copy model files
- name: Solve ddddocr compatibility and copy model files
run: |
$ddddocrPath = python -c "import ddddocr, os; print(os.path.dirname(ddddocr.__file__))"
Write-Host "ddddocr package location: $ddddocrPath"
@@ -121,9 +131,9 @@ jobs:
./compile_rc.bat
shell: cmd
- name: Generate Main.spec dynamically
- name: Generate 'Main.spec'
run: |
$version = "${{ steps.get_tag.outputs.VERSION }}"
$version = "${{ steps.get_version.outputs.VERSION }}"
$exeName = "AutoLibrary-$version"
Write-Host "Generating Main.spec for version: $version"
@@ -173,28 +183,29 @@ jobs:
" icon=['src\\gui\\icons\\AutoLibrary_32x32.ico'],"
")"
)
$specLines | Out-File -FilePath "Main.spec" -Encoding UTF8
Write-Host "✓ Main.spec generated successfully"
Write-Host "`n=== Generated Main.spec ==="
Write-Host "`nGenerated Main.spec ============"
Get-Content "Main.spec" | Write-Host
Write-Host "==========================`n"
Write-Host "==================================`n"
shell: pwsh
- name: Build with PyInstaller
run: |
pyinstaller Main.spec
- name: Create Release Archive
- name: Zip windows release
id: zip_release
run: |
$tagName = "${{ steps.get_tag.outputs.TAG_NAME }}"
$version = "${{ steps.get_tag.outputs.VERSION }}"
$tagName = "${{ steps.get_version.outputs.TAG_NAME }}"
$version = "${{ steps.get_version.outputs.VERSION }}"
$exeName = "AutoLibrary-$version.exe"
$zipName = "AutoLibrary.$tagName-windows-x86_64.zip"
Write-Host "Looking for executable: dist/$exeName"
echo "ZIP_PATH=$zipName" >> $env:GITHUB_OUTPUT
Write-Host "Looking for executable: dist/$exeName"
if (Test-Path "dist/$exeName") {
Compress-Archive -Path "dist/$exeName" -DestinationPath $zipName
Write-Host "✓ Created release archive: $zipName"
@@ -206,31 +217,9 @@ jobs:
}
shell: pwsh
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2
- name: Archive artifacts
uses: actions/upload-artifact@v4
with:
tag_name: ${{ steps.get_tag.outputs.TAG_NAME }}
name: AutoLibrary ${{ steps.get_tag.outputs.TAG_NAME }}
files: |
AutoLibrary.${{ steps.get_tag.outputs.TAG_NAME }}-windows-x86_64.zip
draft: false
prerelease: false
generate_release_notes: true
body: |
### 下载获取
- **Windows x86_64**: `AutoLibrary.${{ steps.get_tag.outputs.TAG_NAME }}-windows-x86_64.zip`
### 如何使用
1. 下载 `AutoLibrary.${{ steps.get_tag.outputs.TAG_NAME }}-windows-x86_64.zip` 文件
2. 解压到任意目录
3. 下载对应浏览器的驱动文件
4. 运行 `AutoLibrary-${{ steps.get_tag.outputs.VERSION }}.exe` (首次运行会初始化配置文件)
5. 按照提示操作即可
更多详情请访问 [AutoLibrary 网站](http://autolibrary.cv) 和查看 [帮助手册](https://autolibrary.cv/docs/manual_lists.html)
---
**完整更新日志见下方自动生成的 Release Notes**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: AutoLibrary.${{ steps.get_version.outputs.TAG_NAME }}-windows-x86_64
path: |
${{ steps.zip_release.outputs.ZIP_PATH }}
@@ -1,4 +1,9 @@
name: Commit and Move Tag
name: Commit Release
# This workflow commits version changes in 'ALVersionInfo.py' (get from artifacts) and
# moves the release tag to this new release commit.
#
# It is triggered when called by the release workflow.
on:
workflow_call:
@@ -18,10 +23,10 @@ on:
outputs:
new_commit_sha:
description: 'The new commit SHA after moving the tag'
value: ${{ jobs.commit-and-move-tag.outputs.new_commit_sha }}
value: ${{ jobs.commit-release.outputs.new_commit_sha }}
jobs:
commit-and-move-tag:
commit-release:
runs-on: ubuntu-latest
permissions:
contents: write
@@ -35,10 +40,12 @@ jobs:
ref: main
fetch-depth: 0
- name: Download modified file
# here we download the commit version of ALVersionInfo.py from artifacts
# and replace the original file with it.
- name: Download commit version of ALVersionInfo.py
uses: actions/download-artifact@v4
with:
name: updated-version-info
name: updated-version-info-for-commit
path: downloaded-file/
- name: Replace file with updated version
@@ -52,9 +59,9 @@ jobs:
echo "✓ File replaced: $FILE_PATH"
echo ""
echo "=== Updated file content ==="
cat "$FILE_PATH"
echo "============================"
echo "Updated file content ==================="
cat "$FILE_PATH"
echo "========================================"
- name: Commit changes
id: commit_changes
@@ -85,7 +92,7 @@ jobs:
git push origin HEAD:${MAIN_BRANCH}
echo "✓ Changes pushed to ${MAIN_BRANCH}"
- name: Move tag to new commit
- name: Move tag to new release commit
run: |
TAG_NAME="${{ inputs.tag_name }}"
+156
View File
@@ -0,0 +1,156 @@
name: Release
# This workflow automates the complete release process for AutoLibrary application
# It is triggered when a new version tag (vX.Y.Z) is pushed to the repository
#
# Workflow Steps:
# START >
# 1. Update Version:
# Updates version information in 'ALVersionInfo.py' with build metadata and archives
# the updated version file as an artifact.
#
# for more information, please refer to the comment in the workflow 'update-version.yml'
# 2. Commit Release:
# Commits version changes and moves the release tag to this new release commit.
# 3. Build:
# Compiles the application for Windows platform using PyInstaller, and
# archives the built artifacts as 'AutoLibrary.<tag_name>-windows-x86_64.zip'.
# 4. Release:
# Creates GitHub release with generated artifacts and release notes
# < END
#
# The workflow ensures version consistency between source code, built artifacts, and GitHub releases
# while maintaining proper commit history and tag management.
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
#
# Start :
# virtual job that indacates the start of the release process
#
start:
runs-on: ubuntu-latest
steps:
- name: Start release
run: |
echo "✓ Starting release"
#
# Update version :
# this job updates the version in the file 'ALVersionInfo.py'
#
update-version:
needs:
- start
uses: ./.github/workflows/update-version.yml
permissions:
contents: write
with:
tag_name: ${{ github.ref_name }}
ref: ${{ github.ref }}
#
# Commit release :
# this job commits the updated version file and move the release
# tag to this new commit
#
commit-release:
needs:
- update-version
if: ${{ needs.update-version.outputs.has_changes == 'true' }}
uses: ./.github/workflows/commit-release.yml
permissions:
contents: write
with:
tag_name: ${{ needs.update-version.outputs.tag_name }}
version: ${{ needs.update-version.outputs.version }}
file_path: src/gui/ALVersionInfo.py
#
# Build :
# this job builds the application artifacts and archives them
build:
needs:
- update-version
- commit-release
if: always() && needs.update-version.result == 'success' && needs.commit-release.result == 'success'
uses: ./.github/workflows/build.yml
permissions:
contents: write
with:
version: ${{ needs.update-version.outputs.version }}
tag_name: ${{ needs.update-version.outputs.tag_name }}
#
# Release :
# this job creates a GitHub release and uploads the archive files
release:
runs-on: ubuntu-latest
needs:
- build
if: always() && needs.build.result == 'success'
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: AutoLibrary.${{ needs.build.outputs.tag_name }}-windows-x86_64
path: artifacts/
- name: Create release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.build.outputs.tag_name }}
name: AutoLibrary ${{ needs.build.outputs.tag_name }}
files: |
artifacts/AutoLibrary.${{ needs.build.outputs.tag_name }}-windows-x86_64.zip
draft: false
prerelease: false
generate_release_notes: true
body: |
### 下载获取
- **Windows x86_64**: `AutoLibrary.${{ needs.build.outputs.tag_name }}-windows-x86_64.zip`
### 如何使用
1. 下载 `AutoLibrary.${{ needs.build.outputs.tag_name }}-windows-x86_64.zip` 文件
2. 解压到任意目录
3. 下载对应浏览器的驱动文件
4. 运行 `AutoLibrary-${{ needs.build.outputs.version }}.exe` (首次运行会初始化配置文件)
5. 按照提示操作即可
更多详情请访问 [AutoLibrary 网站](http://autolibrary.cv) 和查看 [帮助手册](https://autolibrary.cv/docs/manual_lists.html)
---
**完整更新日志见下方自动生成的 Release Notes**
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# End :
# virtual job that indacates the end of the release process
#
end:
needs:
- release
runs-on: ubuntu-latest
steps:
- name: End release
run: |
echo "✓ Ending release"
@@ -1,10 +1,18 @@
name: Update Version Info
name: Update Version
# This workflow updates version information in 'ALVersionInfo.py' with build metadata.
# In progress, it will generate two version files, the first one is locate in 'src/gui/ALVersionInfo.py',
# and the second one is locate in 'src/gui/temp/ALVersionInfo.py'. The first one is use
# in the release process, it only update the version and tag name. The commit and build infomation
# is 'local' or 'null'. All of them will finally archive as artifacts.
#
# It is triggered when called by the release workflow.
on:
workflow_call:
inputs:
tag_name:
description: 'Tag name (e.g., v1.0.0)'
description: 'Tag name'
required: true
type: string
ref:
@@ -14,16 +22,16 @@ on:
outputs:
tag_name:
description: 'The tag name'
value: ${{ jobs.update-version-info.outputs.tag_name }}
value: ${{ jobs.update-version.outputs.tag_name }}
version:
description: 'The version number'
value: ${{ jobs.update-version-info.outputs.version }}
value: ${{ jobs.update-version.outputs.version }}
has_changes:
description: 'Whether ALVersionInfo.py was modified'
value: ${{ jobs.update-version-info.outputs.has_changes }}
value: ${{ jobs.update-version.outputs.has_changes }}
jobs:
update-version-info:
update-version:
runs-on: ubuntu-latest
permissions:
contents: write
@@ -59,24 +67,30 @@ jobs:
echo "✓ Commit SHA: $COMMIT_SHA"
echo "✓ Commit Date: $COMMIT_DATE"
- name: Create 'temp' directory
run: |
echo "Creating temp directory..."
mkdir -p "src/gui/temp"
echo "✓ temp directory created successfully"
- name: Update ALVersionInfo.py with version info
run: |
VERSION="${{ steps.get_version.outputs.VERSION }}"
TAG_NAME="${{ steps.get_version.outputs.TAG_NAME }}"
COMMIT_SHA="${{ steps.get_version.outputs.COMMIT_SHA }}"
COMMIT_DATE="${{ steps.get_version.outputs.COMMIT_DATE }}"
APP_INFO_FILE="src/gui/ALVersionInfo.py"
VER_INFO_BUILDFILE="src/gui/temp/ALVersionInfo.py"
VER_INFO_COMMITFILE="src/gui/ALVersionInfo.py"
BUILD_DATE=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
echo "Updating $APP_INFO_FILE with build information..."
echo "Updating ALVersionInfo.py files with build information..."
{
echo '# -*- coding: utf-8 -*-'
echo ''
echo '"""'
echo ' The contents of this file will automatically be updated by the'
echo ' workflow process. Do not edit manually.'
echo ' '
echo ''
echo ' This file is auto-generated during the workflow process.'
echo " Last updated: ${BUILD_DATE}"
echo '"""'
@@ -87,13 +101,39 @@ jobs:
echo "AL_COMMIT_DATE = \"${COMMIT_DATE}\" # time zone : UTC"
echo "AL_BUILD_DATE = \"${BUILD_DATE}\" # time zone : UTC"
echo 'AL_VERSION_FULL = f"{AL_VERSION} ({AL_COMMIT_SHA})"'
} > "$APP_INFO_FILE"
} > "$VER_INFO_BUILDFILE"
echo " ALVersionInfo.py updated successfully"
echo "Updating ALVersionInfo.py for release commit..."
{
echo '# -*- coding: utf-8 -*-'
echo ''
echo '"""'
echo ' The contents of this file will automatically be updated by the'
echo ' workflow process. Do not edit manually.'
echo ''
echo ' This file is auto-generated during the workflow process.'
echo " Last updated: ${BUILD_DATE}"
echo '"""'
echo ''
echo "AL_VERSION = \"${VERSION}\""
echo "AL_TAG = \"${TAG_NAME}\""
echo "AL_COMMIT_SHA = \"local\""
echo "AL_COMMIT_DATE = \"null\" # time zone : UTC"
echo "AL_BUILD_DATE = \"null\" # time zone : UTC"
echo 'AL_VERSION_FULL = f"{AL_VERSION} ({AL_COMMIT_SHA})"'
} > "$VER_INFO_COMMITFILE"
echo "✓ ALVersionInfo.py files updated successfully"
echo ""
echo "=== Updated ALVersionInfo.py ==="
cat "$APP_INFO_FILE"
echo "=========================="
echo "Build version file location: $VER_INFO_BUILDFILE"
echo "Commit version file location: $VER_INFO_COMMITFILE"
echo ""
echo "Build version ALVersionInfo.py content ="
cat "$VER_INFO_BUILDFILE"
echo ""
echo "Commit version ALVersionInfo.py content "
cat "$VER_INFO_COMMITFILE"
echo "========================================"
- name: Check if ALVersionInfo.py was modified
id: check_changes
@@ -106,10 +146,18 @@ jobs:
echo "✓ ALVersionInfo.py has been modified"
fi
- name: Upload modified ALVersionInfo.py
- name: Upload modified ALVersionInfo.py ready for build
if: steps.check_changes.outputs.has_changes == 'true'
uses: actions/upload-artifact@v4
with:
name: updated-version-info
path: src/gui/ALVersionInfo.py
name: updated-version-info-for-build
path: src/gui/temp/ALVersionInfo.py
retention-days: 1
- name: Upload modified ALVersionInfo.py ready for commit
if: steps.check_changes.outputs.has_changes == 'true'
uses: actions/upload-artifact@v4
with:
name: updated-version-info-for-commit
path: src/gui/ALVersionInfo.py
retention-days: 1
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2025 KenanZhu
Copyright 2025 - 2026 KenanZhu
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
+8 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -13,6 +13,13 @@ from base.MsgBase import MsgBase
class LibOperator(MsgBase):
"""
Base abstract class for library operation.
This class provides the foundation for library-related operations, inheriting
message handling and tracing abilities from MsgBase. It serves as an abstract
base class that must be subclassed to implement specific library functionality.
"""
def __init__(
self,
+17 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -12,6 +12,22 @@ import queue
class MsgBase:
"""
Base class for message and trace abilities (thread-safe).
This class provides the foundation for message handling and tracing
abilities based on the provided input and output queues. It enables
thread-safe communication between components using queue-based messaging.
Args:
input_queue (queue.Queue): The input queue for receiving messages.
output_queue (queue.Queue): The output queue for sending messages.
Usage:
This class must be initialized with input and output queues. The queue
provider (the caller of this class or its subclasses) must explicitly
implement queue polling to retrieve and process messages.
"""
def __init__(
self,
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
+3 -10
View File
@@ -1,31 +1,24 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import os
import sys
import time
import uuid
import queue
from enum import Enum
from datetime import datetime, timedelta
from PySide6.QtCore import (
Qt, Signal, Slot, QDateTime
Slot, QDateTime
)
from PySide6.QtWidgets import (
QLabel, QDialog, QWidget, QSpinBox, QVBoxLayout,
QLabel, QDialog, QWidget, QSpinBox,
QHBoxLayout, QGridLayout, QDateTimeEdit
)
from PySide6.QtGui import (
QCloseEvent
)
from gui.Ui_ALAddTimerTaskDialog import Ui_ALAddTimerTaskDialog
+12 -9
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -45,12 +45,11 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
):
super().__init__(parent)
self.setupUi(self)
self.__config_paths = config_paths
self.__config_data = {"run": {}, "user": {}}
self.__seat_map_widget = None
self.setupUi(self)
self.modifyUi()
self.connectSignals()
self.initlizeFloorRoomMap()
@@ -143,12 +142,12 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
}
self.__room_map = {
"1": "二层内环",
"2": "二层外环",
"2": "二层西区",
"3": "三层内环",
"4": "三层外环",
"5": "四层内环",
"6": "四层外环",
"7": "四层期刊",
"7": "四层期刊",
"8": "五层考研"
}
self.__floor_rmap = {
@@ -158,9 +157,9 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
v: k for k, v in self.__room_map.items()
}
self.__floor_room_map = {
"二层": ["二层内环", "二层外环"],
"二层": ["二层内环", "二层西区"],
"三层": ["三层内环", "三层外环"],
"四层": ["四层内环", "四层外环", "四层期刊"],
"四层": ["四层内环", "四层外环", "四层期刊"],
"五层": ["五层考研"]
}
@@ -264,7 +263,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
},
"web_driver": {
"driver_type": "edge",
"driver_path": "msedgedriver.exe",
"driver_path": "",
"headless": False
},
"mode": {
@@ -334,7 +333,10 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.AutoCaptchaCheckBox.setChecked(run_config["login"]["auto_captcha"])
self.LoginAttemptSpinBox.setValue(run_config["login"]["max_attempt"])
self.BrowserTypeComboBox.setCurrentText(run_config["web_driver"]["driver_type"])
driver_path = os.path.abspath(run_config["web_driver"]["driver_path"])
if run_config["web_driver"]["driver_path"]:
driver_path = os.path.abspath(run_config["web_driver"]["driver_path"])
else:
driver_path = ""
self.BrowseBrowserDriverEdit.setText(QDir.toNativeSeparators(driver_path))
self.HeadlessCheckBox.setChecked(run_config["web_driver"]["headless"])
run_mode = run_config["mode"]["run_mode"]
@@ -800,6 +802,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.__seat_map_widget.deleteLater()
self.__seat_map_widget = None
if len(selected_seats) == 0:
self.SeatIDEdit.clear() # no selected seat, we clear the edit
return
self.SeatIDEdit.setText(",".join(selected_seats))
+18 -5
View File
@@ -63,7 +63,7 @@
</property>
<widget class="QWidget" name="UserConfigWidget">
<attribute name="title">
<string>用户置</string>
<string>用户置</string>
</attribute>
<layout class="QHBoxLayout" name="UserConfigWidgetLayout">
<property name="spacing">
@@ -905,7 +905,7 @@
<widget class="QSpinBox" name="MaxBeginTimeDiffSpinBox">
<property name="minimumSize">
<size>
<width>65</width>
<width>55</width>
<height>25</height>
</size>
</property>
@@ -1052,7 +1052,20 @@
<number>0</number>
</property>
<item>
<widget class="QSpinBox" name="MaxRenewTimeDiffSpinBox"/>
<widget class="QSpinBox" name="MaxRenewTimeDiffSpinBox">
<property name="minimumSize">
<size>
<width>55</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="PreferLateRenewTimeCheckBox">
@@ -1107,7 +1120,7 @@
</widget>
<widget class="QWidget" name="RunConfigWidget">
<attribute name="title">
<string>运行置</string>
<string>运行置</string>
</attribute>
<layout class="QGridLayout" name="SystemConfigWidgetLayout">
<property name="leftMargin">
@@ -1730,7 +1743,7 @@
</size>
</property>
<property name="text">
<string>;;;</string>
<string>...</string>
</property>
</widget>
</item>
+2 -7
View File
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import os
import sys
import time
import queue
@@ -30,9 +29,6 @@ from gui.ALMainWorkers import TimerTaskWorker, AutoLibWorker
from gui import AutoLibraryResource
from utils.ConfigReader import ConfigReader
from utils.ConfigWriter import ConfigWriter
class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@@ -46,8 +42,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
super().__init__()
self.__class_name = self.__class__.__name__
self.setupUi(self)
self.__input_queue = queue.Queue()
self.__output_queue = queue.Queue()
self.__timer_task_queue = queue.Queue()
@@ -64,6 +58,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__current_timer_task_thread = None
self.__is_running_timer_task = False
self.setupUi(self)
self.modifyUi()
self.setupTray()
self.connectSignals()
+4 -8
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -32,7 +32,7 @@ class AutoLibWorker(QThread, MsgBase):
config_paths: dict
):
super().__init__(input_queue = input_queue, output_queue = output_queue)
super().__init__(input_queue=input_queue, output_queue=output_queue)
self.__config_paths = config_paths
@@ -69,15 +69,11 @@ class AutoLibWorker(QThread, MsgBase):
self._showTrace(
f"正在加载配置文件, 运行配置文件路径: {self.__config_paths["run"]}"
)
self.__run_config = ConfigReader(
self.__config_paths["run"]
).getConfigs()
self.__run_config = ConfigReader(self.__config_paths["run"]).getConfigs()
self._showTrace(
f"正在加载配置文件, 用户配置文件路径: {self.__config_paths["user"]}"
)
self.__user_config = ConfigReader(
self.__config_paths["user"]
).getConfigs()
self.__user_config = ConfigReader(self.__config_paths["user"]).getConfigs()
if self.__run_config is None or self.__user_config is None:
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
+2 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -28,6 +28,7 @@ class ALSeatFrame(QFrame):
super().__init__(parent)
self.__seat_number = seat_number
self.__is_selected = False
self.setupUi()
def setupUi(
+7 -3
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -34,12 +34,13 @@ class ALSeatMapWidget(QWidget):
):
super().__init__(parent)
self.__floor = floor
self.__room = room
self.__seats_data = seats_data
self.__selected_seats = []
self.__seat_frames = {}
self.__confirmed = False
self.setupUi()
self.connectSignals()
@@ -144,6 +145,8 @@ class ALSeatMapWidget(QWidget):
event: QCloseEvent
):
if not self.__confirmed:
self.clearSelections()
self.seatMapWidgetClosed.emit(self.__selected_seats)
super().closeEvent(event)
@@ -265,6 +268,7 @@ class ALSeatMapWidget(QWidget):
self
):
self.__confirmed = True
self.close()
@Slot()
@@ -272,5 +276,5 @@ class ALSeatMapWidget(QWidget):
self
):
self.clearSelections()
self.__confirmed = False
self.close()
+3 -6
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -8,10 +8,7 @@ You may use, modify, and distribute this file under the terms of the MIT License
See the LICENSE file for details.
"""
import os
import sys
import time
import copy
import queue
from enum import Enum
from datetime import datetime, timedelta
@@ -24,7 +21,7 @@ from PySide6.QtWidgets import (
QHBoxLayout, QVBoxLayout, QLabel, QPushButton
)
from PySide6.QtGui import (
QCloseEvent, QScreen
QCloseEvent
)
from gui.Ui_ALTimerTaskWidget import Ui_ALTimerTaskWidget
@@ -50,8 +47,8 @@ class TimerTaskItemWidget(QWidget):
):
super().__init__(parent)
self.__timer_task = timer_task
self.modifyUi()
+1 -2
View File
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
from enum import Enum
from PySide6.QtCore import (
+7 -7
View File
@@ -3,14 +3,14 @@
"""
The contents of this file will automatically be updated by the
workflow process. Do not edit manually.
This file is auto-generated during the workflow process.
Last updated: 2026-01-02 16:38:53 UTC
Last updated: 2026-01-17 17:51:55 UTC
"""
AL_VERSION = "1.0.1"
AL_TAG = "v1.0.1"
AL_COMMIT_SHA = "924db3b"
AL_COMMIT_DATE = "2026-01-02 16:35:16 UTC" # time zone : UTC
AL_BUILD_DATE = "2026-01-02 16:38:53 UTC" # time zone : UTC
AL_VERSION = "1.0.3"
AL_TAG = "v1.0.3"
AL_COMMIT_SHA = "local"
AL_COMMIT_DATE = "null" # time zone : UTC
AL_BUILD_DATE = "null" # time zone : UTC
AL_VERSION_FULL = f"{AL_VERSION} ({AL_COMMIT_SHA})"
+55 -34
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -14,7 +14,9 @@ from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.edge.service import Service
from selenium.webdriver.edge.service import Service as EdgeService
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.firefox.service import Service as FirefoxService
from base.MsgBase import MsgBase
from operators.LibChecker import LibChecker
@@ -24,8 +26,6 @@ from operators.LibReserve import LibReserve
from operators.LibCheckin import LibCheckin
from operators.LibRenew import LibRenew
from utils.ConfigReader import ConfigReader
class AutoLib(MsgBase):
@@ -53,54 +53,75 @@ class AutoLib(MsgBase):
) -> bool:
self._showTrace("正在初始化浏览器驱动......")
edge_options = webdriver.EdgeOptions()
web_driver_config = self.__run_config.get("web_driver", None)
self.__driver_type = web_driver_config.get("driver_type")
match self.__driver_type.lower():
case "edge":
driver_options = webdriver.EdgeOptions()
case "chrome":
driver_options = webdriver.ChromeOptions()
case "firefox":
driver_options = webdriver.FirefoxOptions()
case _:
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type} !")
if not web_driver_config:
self._showTrace("未配置浏览器驱动参数 !")
return False
if web_driver_config.get("headless"):
edge_options.add_argument("--headless")
edge_options.add_argument("--disable-gpu")
edge_options.add_argument("--no-sandbox")
edge_options.add_argument("--disable-dev-shm-usage")
driver_options.add_argument("--headless")
driver_options.add_argument("--disable-gpu")
driver_options.add_argument("--no-sandbox")
driver_options.add_argument("--disable-dev-shm-usage")
# must be 1920x1080, otherwise the page will cause some elements not accessible
edge_options.add_argument("--window-size=1920,1080")
edge_options.add_argument("--remote-allow-origins=*")
driver_options.add_argument("--window-size=1920,1080")
# omit ssl errors and verbose log level
edge_options.add_argument("--ignore-certificate-errors")
edge_options.add_argument("--ignore-ssl-errors")
edge_options.add_argument("--log-level=OFF")
edge_options.add_argument("--silent")
driver_options.add_argument("--ignore-certificate-errors")
driver_options.add_argument("--ignore-ssl-errors")
driver_options.add_argument("--log-level=OFF")
driver_options.add_argument("--silent")
edge_options.add_experimental_option("excludeSwitches", ["enable-automation"])
edge_options.add_experimental_option("useAutomationExtension", False)
edge_options.add_argument("--disable-blink-features=AutomationControlled")
edge_options.add_argument(
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "\
"AppleWebKit/537.36 (KHTML, like Gecko) "\
"Chrome/120.0.0.0 "\
"Safari/537.36 "\
"Edg/120.0.0.0"
)
# set options for chrome and edge
if self.__driver_type.lower() in ["edge", "chrome"]:
driver_options.add_argument("--remote-allow-origins=*")
driver_options.add_experimental_option("excludeSwitches", ["enable-automation"])
driver_options.add_experimental_option("useAutomationExtension", False)
driver_options.add_argument("--disable-blink-features=AutomationControlled")
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "\
"AppleWebKit/537.36 (KHTML, like Gecko) "\
"Chrome/120.0.0.0 "\
"Safari/537.36"
if self.__driver_type.lower() == "edge":
user_agent += " Edg/120.0.0.0"
# set options for firefox
elif self.__driver_type.lower() == "firefox":
driver_options.set_preference("dom.webdriver.enabled", False)
driver_options.set_preference("useAutomationExtension", False)
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) "\
"Gecko/20100101 Firefox/120.0"
driver_options.add_argument(f"user-agent={user_agent}")
# init browser driver
self.__driver_path = web_driver_config.get("driver_path")
self.__driver_type = web_driver_config.get("driver_type")
if not self.__driver_path:
raise Exception(f"未配置浏览器驱动路径 !")
self.__driver_path = os.path.abspath(self.__driver_path)
try:
service = None
if self.__driver_path:
service = Service(executable_path=self.__driver_path)
match self.__driver_type.lower():
case "edge":
self.__driver = webdriver.Edge(service=service, options=edge_options)
service = EdgeService(executable_path=self.__driver_path)
self.__driver = webdriver.Edge(service=service, options=driver_options)
case "chrome":
self.__driver = webdriver.Chrome(service=service, options=edge_options)
service = ChromeService(executable_path=self.__driver_path)
self.__driver = webdriver.Chrome(service=service, options=driver_options)
case "firefox":
self.__driver = webdriver.Firefox(service=service, options=edge_options)
self._showTrace(f"Firefox 浏览器驱动初始化略慢, 请耐心等待...")
service = FirefoxService(executable_path=self.__driver_path)
self.__driver = webdriver.Firefox(service=service, options=driver_options)
case _:
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}")
self.__driver.implicitly_wait(1)
@@ -191,9 +212,7 @@ class AutoLib(MsgBase):
login_config.get("auto_captcha", True),
):
return 1
"""
Here, we collect the run mode from the run config.
"""
# Here, we collect the run mode from the run config.
run_mode = run_mode_config.get("run_mode", 0)
run_mode = {
"auto_reserve": run_mode&0x1,
@@ -294,6 +313,8 @@ class AutoLib(MsgBase):
) -> bool:
if self.__driver:
if self.__driver_type.lower() == "firefox":
self._showTrace(f"Firefox 浏览器驱动关闭略慢, 请耐心等待...")
self.__driver.quit()
self.__driver = None
self._showTrace(f"浏览器驱动已关闭")
+9 -8
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -13,6 +13,7 @@ import queue
from datetime import datetime, timedelta
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +26,7 @@ class LibChecker(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
@@ -336,15 +337,15 @@ class LibChecker(LibOperator):
def postRenewCheck(
self,
record: dict
):
) -> bool:
"""
Check if the renew operation is successful
Check if the renew operation is successful
Args:
record (dict): The expected record after renewal
Args:
record (dict): The expected record after renewal
Returns:
bool: True if the renew operation is successful, False otherwise
Returns:
bool: True if the renew operation is successful, False otherwise
"""
# because the special circumstance that the renew operation
# do not show the success message or anything else,
+3 -4
View File
@@ -1,18 +1,17 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import re
import time
import queue
from datetime import datetime, timedelta
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +24,7 @@ class LibCheckin(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+3 -2
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -13,6 +13,7 @@ import queue
from datetime import datetime, timedelta
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +26,7 @@ class LibCheckout(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+3 -3
View File
@@ -1,19 +1,19 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import time
import queue
import base64
import ddddocr
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -26,7 +26,7 @@ class LibLogin(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+3 -4
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -10,8 +10,7 @@ See the LICENSE file for details.
import queue
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.webdriver import WebDriver
from base.LibOperator import LibOperator
@@ -22,7 +21,7 @@ class LibLogout(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+3 -5
View File
@@ -1,18 +1,16 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import os
import time
import queue
from datetime import datetime, timedelta
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +23,7 @@ class LibRenew(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+6 -6
View File
@@ -1,18 +1,18 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
import re
import time
import queue
from datetime import datetime, timedelta
from datetime import datetime
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +25,7 @@ class LibReserve(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
@@ -40,12 +40,12 @@ class LibReserve(LibOperator):
}
self.__room_map = {
"1": "二层内环",
"2": "二层外环",
"2": "二层西区",
"3": "三层内环",
"4": "三层外环",
"5": "四层内环",
"6": "四层外环",
"7": "四层期刊",
"7": "四层期刊",
"8": "五层考研"
}
+1
View File
@@ -8,5 +8,6 @@
- LibReserve: Library operator for reserving seat.
- LibCheckin: Library operator for checking in seat.
- LibCheckout: Library operator for checking out seat.
- LibChecker: Library operator for checking record status.
- LibRenew: Library operator for renewing seat.
"""
+45 -19
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -8,63 +8,89 @@ You may use, modify, and distribute this file under the terms of the MIT License
See the LICENSE file for details.
"""
import json
import copy
from typing import Any
class ConfigReader:
"""
Config reader class.
This class is used to read config file in JSON format.
Args:
config_path (str): The path of config file.
Examples:
>>> print(open("config.json", "r", encoding="utf-8").read())
{
"key1": {
"key2": "value1"
}
}
>>> config_reader = ConfigReader("config.json")
>>> config_reader.get("key1/key2")
"value1"
"""
def __init__(
self,
config_path: str
):
self._config_path = config_path
self._config_data = {}
if not self.__readConfig():
return None
self.__config_path = config_path
self.__config_data = None
self.__readConfig()
def __readConfig(
self
) -> bool:
):
try:
with open(self._config_path, 'r', encoding='utf-8') as file:
self._config_data = json.load(file)
return True
with open(self.__config_path, 'r', encoding='utf-8') as file:
self.__config_data = json.load(file)
except FileNotFoundError as e:
raise Exception(f"Config file not found: {self.__config_path}") from e
except PermissionError as e:
raise Exception(f"Without enough permission to read config file: {self.__config_path}") from e
except json.JSONDecodeError as e:
raise Exception(f"JSON decode error in config file: {self.__config_path}") from e
except Exception as e:
print(f"Error reading config file: {e}")
return False
raise Exception(f"Unknown error occurred while reading config file: {e}") from e
def getConfigs(
self
) -> dict:
return self._config_data.copy()
return self.__config_data.copy()
def getConfig(
self,
key: str
) -> dict:
) -> Any:
return self._config_data.get(key, {})
config = self.__config_data.get(key, {})
return copy.deepcopy(config)
def get(
self,
key: str,
default: any = None
) -> any:
default: Any = None
) -> Any:
keys = key.split('/')
current = self._config_data
current = self.__config_data
for k in keys:
if isinstance(current, dict) and k in current:
current = current[k]
else:
return default
return current
return copy.deepcopy(current)
def hasConfig(
@@ -86,4 +112,4 @@ class ConfigReader:
self
) -> str:
return self._config_path
return self.__config_path
+41 -12
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -9,8 +9,35 @@ See the LICENSE file for details.
"""
import json
from typing import Any
class ConfigWriter:
"""
Config writer class.
This class is used to write config file in JSON format.
Args:
config_path (str): The path of config file.
config_data (dict): The config data to be written.
Examples:
>>> config_data = {
... "key1": {
... "key2": "value1"
... }
... }
>>> config_writer = ConfigWriter("config.json", config_data)
>>> config_writer.set("key1/key2", "value1")
True
>>> print(open("config.json", "r", encoding="utf-8").read())
{
"key1": {
"key2": "value1"
}
}
"""
def __init__(
self,
@@ -19,23 +46,25 @@ class ConfigWriter:
):
self.__config_path = config_path
self.__config_data = config_data if config_data is not None else {}
if config_data is None:
return None
if not self.__writeConfig():
return None
self.__config_data = config_data.copy() if config_data is not None else {}
self.__writeConfig()
def __writeConfig(
self
) -> bool:
):
try:
with open(self.__config_path, "w") as f:
with open(self.__config_path, "w", encoding="utf-8") as f:
json.dump(self.__config_data, f, indent=4, sort_keys=False)
return True
except:
return False
except PermissionError as e:
raise Exception(f"Without enough permission to write config file: {self.__config_path}") from e
except IOError as e:
raise Exception(f"IO error occurred while writing config file: {self.__config_path}") from e
except TypeError as e:
raise Exception(f"Config data contains invalid type that can not be JSON serialized: {e}") from e
except Exception as e:
raise Exception(f"Unknown error occurred while writing config file: {e}") from e
def setConfigs(
@@ -60,7 +89,7 @@ class ConfigWriter:
def set(
self,
key: str,
value: dict
value: Any
) -> bool:
keys = key.replace("\\", "/").split("/")