mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-18 23:43:02 +08:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95a3ae2a24 | |||
| 896242a1e3 | |||
| fd96fc235e | |||
| 25aab588a8 | |||
| 6e1b8e6b10 | |||
| 5f2327cf61 | |||
| 96e7adabb0 | |||
| 42afbbe694 | |||
| 3777970332 | |||
| 9fb28e1368 | |||
| 4aeca08ce8 | |||
| a1ff85256a | |||
| 169de92d5b | |||
| 5ca4a14a14 | |||
| 155b3fe3ca | |||
| 99d454a566 | |||
| 3963b3f2e6 | |||
| f2a05809bd | |||
| b55a0c06a5 | |||
| 2496c4e367 | |||
| de30559af1 | |||
| e1c2efc8c0 | |||
| 26a70cdceb | |||
| ce14be2555 | |||
| eda16f01f1 | |||
| 22f806bfb0 | |||
| d26852eaaf | |||
| 2ffe620532 | |||
| fe42d3cd98 | |||
| 0795939aa3 | |||
| 8b6baf9b6a | |||
| 7098d7075f | |||
| be3942ea2f | |||
| 7e3a089e21 | |||
| f3d68c40cb | |||
| 0ceff677e4 | |||
| 6f6b415bff | |||
| 735f31830d | |||
| 7be5afeae1 | |||
| 3d6978c9c2 |
+32
-25
@@ -94,13 +94,13 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not (Test-Path "model")) {
|
if (-not (Test-Path "models")) {
|
||||||
New-Item -ItemType Directory -Path "model" | Out-Null
|
New-Item -ItemType Directory -Path "models" | Out-Null
|
||||||
Write-Host "✓ Created model directory"
|
Write-Host "✓ Created models directory"
|
||||||
}
|
}
|
||||||
|
|
||||||
$onnxSource = Join-Path $ddddocrPath "common.onnx"
|
$onnxSource = Join-Path $ddddocrPath "common.onnx"
|
||||||
$onnxDest = "model/common.onnx"
|
$onnxDest = "models/common.onnx"
|
||||||
if (Test-Path $onnxSource) {
|
if (Test-Path $onnxSource) {
|
||||||
Copy-Item $onnxSource $onnxDest -Force
|
Copy-Item $onnxSource $onnxDest -Force
|
||||||
Write-Host "✓ Copied ONNX model from: $onnxSource"
|
Write-Host "✓ Copied ONNX model from: $onnxSource"
|
||||||
@@ -119,18 +119,18 @@ jobs:
|
|||||||
}
|
}
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Compile Qt UI files
|
|
||||||
run: |
|
|
||||||
cd src/gui/batchs
|
|
||||||
./compile_ui.bat
|
|
||||||
shell: cmd
|
|
||||||
|
|
||||||
- name: Compile Qt Resource files
|
- name: Compile Qt Resource files
|
||||||
run: |
|
run: |
|
||||||
cd src/gui/batchs
|
cd batchs
|
||||||
./compile_rc.bat
|
./compile_rc.bat
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
|
- name: Compile Qt UI files
|
||||||
|
run: |
|
||||||
|
cd batchs
|
||||||
|
./compile_ui.bat
|
||||||
|
shell: cmd
|
||||||
|
|
||||||
- name: Generate 'Main.spec'
|
- name: Generate 'Main.spec'
|
||||||
run: |
|
run: |
|
||||||
$version = "${{ steps.get_version.outputs.VERSION }}"
|
$version = "${{ steps.get_version.outputs.VERSION }}"
|
||||||
@@ -148,8 +148,8 @@ jobs:
|
|||||||
" pathex=[],"
|
" pathex=[],"
|
||||||
" binaries=[],"
|
" binaries=[],"
|
||||||
" datas=["
|
" datas=["
|
||||||
" ('model\\common.onnx', 'ddddocr'),"
|
" ('models\\common.onnx', 'ddddocr'),"
|
||||||
" ('src\\gui\\icons\\AutoLibrary_32x32.ico', 'gui\\icons'),"
|
" ('src\\gui\\resources\\icons\\AutoLibrary_32x32.ico', 'gui\\resources\\icons'),"
|
||||||
" ],"
|
" ],"
|
||||||
" hiddenimports=[],"
|
" hiddenimports=[],"
|
||||||
" hookspath=[],"
|
" hookspath=[],"
|
||||||
@@ -164,10 +164,7 @@ jobs:
|
|||||||
"exe = EXE("
|
"exe = EXE("
|
||||||
" pyz,"
|
" pyz,"
|
||||||
" a.scripts,"
|
" a.scripts,"
|
||||||
" a.binaries,"
|
" name='AutoLibrary',"
|
||||||
" a.datas,"
|
|
||||||
" [],"
|
|
||||||
" name='$exeName',"
|
|
||||||
" debug=False,"
|
" debug=False,"
|
||||||
" bootloader_ignore_signals=False,"
|
" bootloader_ignore_signals=False,"
|
||||||
" strip=False,"
|
" strip=False,"
|
||||||
@@ -180,12 +177,22 @@ jobs:
|
|||||||
" target_arch=None,"
|
" target_arch=None,"
|
||||||
" codesign_identity=None,"
|
" codesign_identity=None,"
|
||||||
" entitlements_file=None,"
|
" entitlements_file=None,"
|
||||||
" icon=['src\\gui\\icons\\AutoLibrary_32x32.ico'],"
|
" icon=['src\\gui\\resources\\icons\\AutoLibrary_32x32.ico'],"
|
||||||
|
")"
|
||||||
|
""
|
||||||
|
"coll = COLLECT("
|
||||||
|
" exe,"
|
||||||
|
" a.binaries,"
|
||||||
|
" a.datas,"
|
||||||
|
" strip=False,"
|
||||||
|
" upx=True,"
|
||||||
|
" upx_exclude=[],"
|
||||||
|
" name='$exeName'"
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
$specLines | Out-File -FilePath "Main.spec" -Encoding UTF8
|
$specLines | Out-File -FilePath "Main.spec" -Encoding UTF8
|
||||||
|
|
||||||
Write-Host "✓ Main.spec generated successfully"
|
Write-Host "✓ Main.spec (non-single file) generated successfully"
|
||||||
Write-Host "`nGenerated Main.spec ============"
|
Write-Host "`nGenerated Main.spec ============"
|
||||||
Get-Content "Main.spec" | Write-Host
|
Get-Content "Main.spec" | Write-Host
|
||||||
Write-Host "==================================`n"
|
Write-Host "==================================`n"
|
||||||
@@ -200,17 +207,17 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
$tagName = "${{ steps.get_version.outputs.TAG_NAME }}"
|
$tagName = "${{ steps.get_version.outputs.TAG_NAME }}"
|
||||||
$version = "${{ steps.get_version.outputs.VERSION }}"
|
$version = "${{ steps.get_version.outputs.VERSION }}"
|
||||||
$exeName = "AutoLibrary-$version.exe"
|
$distDir = "dist/AutoLibrary-$version"
|
||||||
$zipName = "AutoLibrary.$tagName-windows-x86_64.zip"
|
$zipName = "AutoLibrary.$tagName-windows-x86_64.zip"
|
||||||
|
|
||||||
echo "ZIP_PATH=$zipName" >> $env:GITHUB_OUTPUT
|
echo "ZIP_PATH=$zipName" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
Write-Host "Looking for executable: dist/$exeName"
|
Write-Host "Looking for distribution directory: $distDir"
|
||||||
if (Test-Path "dist/$exeName") {
|
if (Test-Path $distDir) {
|
||||||
Compress-Archive -Path "dist/$exeName" -DestinationPath $zipName
|
Compress-Archive -Path "$distDir/*" -DestinationPath $zipName
|
||||||
Write-Host "✓ Created release archive: $zipName"
|
Write-Host "✓ Created release archive (directory mode): $zipName"
|
||||||
} else {
|
} else {
|
||||||
Write-Error "✗ Executable not found: dist/$exeName"
|
Write-Error "✗ Distribution directory not found: $distDir"
|
||||||
Write-Host "Files in dist directory:"
|
Write-Host "Files in dist directory:"
|
||||||
Get-ChildItem "dist" | ForEach-Object { Write-Host " - $($_.Name)" }
|
Get-ChildItem "dist" | ForEach-Object { Write-Host " - $($_.Name)" }
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -125,18 +125,6 @@ jobs:
|
|||||||
prerelease: false
|
prerelease: false
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
body: |
|
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**
|
**完整更新日志见下方自动生成的 Release Notes**
|
||||||
env:
|
env:
|
||||||
|
|||||||
+11
-11
@@ -6,16 +6,16 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
model/*.onnx
|
|
||||||
driver/*.exe
|
models/*.*
|
||||||
src/gui/configs/*.json
|
drivers/*.*
|
||||||
src/gui/translators/qtbase_zh_CN.qm
|
!models/*.md
|
||||||
src/gui/AutoLibraryResources.py
|
!drivers/*.md
|
||||||
src/gui/AutoLibraryResource.py
|
!templates/*.md
|
||||||
src/gui/Ui_ALMainWindow.py
|
!templates/configs/*.md
|
||||||
src/gui/Ui_ALConfigWidget.py
|
|
||||||
src/gui/Ui_ALTimerTaskWidget.py
|
src/gui/resources/ui/Ui_*.py
|
||||||
src/gui/Ui_ALAddTimerTaskDialog.py
|
src/gui/resources/translators/qtbase_zh_CN.qm
|
||||||
src/gui/Ui_ALAboutDialog.py
|
src/gui/resources/ALResource.py
|
||||||
|
|
||||||
Main.spec
|
Main.spec
|
||||||
@@ -3,6 +3,7 @@ chcp 65001 >nul
|
|||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
cd /d "%~dp0.."
|
cd /d "%~dp0.."
|
||||||
|
cd src/gui/resources
|
||||||
|
|
||||||
echo [AutoLibrary compile] 检查翻译文件...
|
echo [AutoLibrary compile] 检查翻译文件...
|
||||||
if exist translators (
|
if exist translators (
|
||||||
@@ -18,12 +19,11 @@ if exist translators (
|
|||||||
|
|
||||||
pyside6-lrelease "%%f"
|
pyside6-lrelease "%%f"
|
||||||
if !errorlevel! equ 0 (
|
if !errorlevel! equ 0 (
|
||||||
echo [AutoLibrary compile] 翻译文件 "%%f" ✓ 编译成功,输出文件: "!qm_filename!"
|
echo [AutoLibrary compile] 翻译文件 "%%f" 编译成功,输出文件: "!qm_filename!"
|
||||||
) else (
|
) else (
|
||||||
echo [AutoLibrary compile] 翻译文件 "%%f" ✗ 编译失败
|
echo [AutoLibrary compile] 翻译文件 "%%f" 编译失败
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
echo.
|
|
||||||
) else (
|
) else (
|
||||||
echo [AutoLibrary compile] 未找到任何 .ts 翻译文件
|
echo [AutoLibrary compile] 未找到任何 .ts 翻译文件
|
||||||
)
|
)
|
||||||
@@ -52,11 +52,10 @@ for %%f in (*.qrc) do (
|
|||||||
|
|
||||||
pyside6-rcc "%%f" -o "!output_file!"
|
pyside6-rcc "%%f" -o "!output_file!"
|
||||||
if !errorlevel! equ 0 (
|
if !errorlevel! equ 0 (
|
||||||
echo [AutoLibrary compile] 文件 "%%f" ✓ 编译成功,输出文件: "!output_file!"
|
echo [AutoLibrary compile] 文件 "%%f" 编译成功,输出文件: "!output_file!"
|
||||||
) else (
|
) else (
|
||||||
echo [AutoLibrary compile] 文件 "%%f" ✗ 编译失败
|
echo [AutoLibrary compile] 文件 "%%f" 编译失败
|
||||||
)
|
)
|
||||||
echo.
|
|
||||||
)
|
)
|
||||||
|
|
||||||
echo [AutoLibrary compile] 所有操作完成。
|
echo [AutoLibrary compile] 所有操作完成。
|
||||||
Regular → Executable
+7
-11
@@ -1,8 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
PRJECT_DIR="$SCRIPT_DIR/.."
|
||||||
cd "$PARENT_DIR"
|
|
||||||
|
cd "$PRJECT_DIR/src/gui/resources"
|
||||||
|
|
||||||
echo "[AutoLibrary compile] 检查翻译文件..."
|
echo "[AutoLibrary compile] 检查翻译文件..."
|
||||||
if [ -d "translators" ]; then
|
if [ -d "translators" ]; then
|
||||||
@@ -10,7 +11,6 @@ if [ -d "translators" ]; then
|
|||||||
ts_files=(*.ts)
|
ts_files=(*.ts)
|
||||||
ts_count=${#ts_files[@]}
|
ts_count=${#ts_files[@]}
|
||||||
|
|
||||||
# 如果第一个元素是"*.ts"(表示没有匹配),则数量为0
|
|
||||||
if [ "$ts_count" -eq 1 ] && [ "${ts_files[0]}" = "*.ts" ]; then
|
if [ "$ts_count" -eq 1 ] && [ "${ts_files[0]}" = "*.ts" ]; then
|
||||||
ts_count=0
|
ts_count=0
|
||||||
fi
|
fi
|
||||||
@@ -23,12 +23,11 @@ if [ -d "translators" ]; then
|
|||||||
echo "[AutoLibrary compile] 正在编译翻译文件: \"$file\" -> \"$qm_file\""
|
echo "[AutoLibrary compile] 正在编译翻译文件: \"$file\" -> \"$qm_file\""
|
||||||
|
|
||||||
if pyside6-lrelease "$file"; then
|
if pyside6-lrelease "$file"; then
|
||||||
echo "[AutoLibrary compile] 翻译文件 \"$file\" ✓ 编译成功,输出文件: \"$qm_file\""
|
echo "[AutoLibrary compile] 翻译文件 \"$file\" 编译成功,输出文件: \"$qm_file\""
|
||||||
else
|
else
|
||||||
echo "[AutoLibrary compile] 翻译文件 \"$file\" ✗ 编译失败"
|
echo "[AutoLibrary compile] 翻译文件 \"$file\" 编译失败"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo
|
|
||||||
else
|
else
|
||||||
echo "[AutoLibrary compile] 未找到任何 .ts 翻译文件"
|
echo "[AutoLibrary compile] 未找到任何 .ts 翻译文件"
|
||||||
fi
|
fi
|
||||||
@@ -36,7 +35,6 @@ if [ -d "translators" ]; then
|
|||||||
else
|
else
|
||||||
echo "[AutoLibrary compile] 未找到 translators 目录"
|
echo "[AutoLibrary compile] 未找到 translators 目录"
|
||||||
fi
|
fi
|
||||||
echo
|
|
||||||
|
|
||||||
file_count=$(ls *.qrc 2>/dev/null | wc -l)
|
file_count=$(ls *.qrc 2>/dev/null | wc -l)
|
||||||
|
|
||||||
@@ -46,7 +44,6 @@ if [ $file_count -eq 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[AutoLibrary compile] 找到 $file_count 个 .qrc 文件,开始编译..."
|
echo "[AutoLibrary compile] 找到 $file_count 个 .qrc 文件,开始编译..."
|
||||||
echo
|
|
||||||
|
|
||||||
for file in *.qrc; do
|
for file in *.qrc; do
|
||||||
base_name=$(basename "$file" .qrc)
|
base_name=$(basename "$file" .qrc)
|
||||||
@@ -54,11 +51,10 @@ for file in *.qrc; do
|
|||||||
echo "[AutoLibrary compile] 正在编译: \"$file\" -> \"$output_file\""
|
echo "[AutoLibrary compile] 正在编译: \"$file\" -> \"$output_file\""
|
||||||
|
|
||||||
if pyside6-rcc "$file" -o "$output_file"; then
|
if pyside6-rcc "$file" -o "$output_file"; then
|
||||||
echo "[AutoLibrary compile] 文件 \"$file\" ✓ 编译成功,输出文件: \"$output_file\""
|
echo "[AutoLibrary compile] 文件 \"$file\" 编译成功,输出文件: \"$output_file\""
|
||||||
else
|
else
|
||||||
echo "[AutoLibrary compile] 文件 \"$file\" ✗ 编译失败"
|
echo "[AutoLibrary compile] 文件 \"$file\" 编译失败"
|
||||||
fi
|
fi
|
||||||
echo
|
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "[AutoLibrary compile] 所有操作完成。"
|
echo "[AutoLibrary compile] 所有操作完成。"
|
||||||
@@ -3,6 +3,7 @@ chcp 65001 >nul
|
|||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
cd /d "%~dp0.."
|
cd /d "%~dp0.."
|
||||||
|
cd src/gui/resources/ui
|
||||||
|
|
||||||
set count=0
|
set count=0
|
||||||
for %%f in (*.ui) do set /a count+=1
|
for %%f in (*.ui) do set /a count+=1
|
||||||
@@ -23,11 +24,10 @@ for %%f in (*.ui) do (
|
|||||||
|
|
||||||
pyside6-uic "%%f" -o "!output_file!"
|
pyside6-uic "%%f" -o "!output_file!"
|
||||||
if !errorlevel! equ 0 (
|
if !errorlevel! equ 0 (
|
||||||
echo [AutoLibrary compile] 文件 "%%f" ✓ 编译成功,输出文件: "!output_file!"
|
echo [AutoLibrary compile] 文件 "%%f" 编译成功,输出文件: "!output_file!"
|
||||||
) else (
|
) else (
|
||||||
echo [AutoLibrary compile] 文件 "%%f" ✗ 编译失败
|
echo [AutoLibrary compile] 文件 "%%f" 编译失败
|
||||||
)
|
)
|
||||||
echo.
|
|
||||||
)
|
)
|
||||||
|
|
||||||
echo [AutoLibrary compile] 所有操作完成。
|
echo [AutoLibrary compile] 所有操作完成。
|
||||||
Regular → Executable
+5
-6
@@ -1,8 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
PRJECT_DIR="$SCRIPT_DIR/.."
|
||||||
cd "$PARENT_DIR"
|
|
||||||
|
cd "$PRJECT_DIR/src/gui/resources/ui"
|
||||||
|
|
||||||
file_count=$(ls *.ui 2>/dev/null | wc -l)
|
file_count=$(ls *.ui 2>/dev/null | wc -l)
|
||||||
|
|
||||||
@@ -12,7 +13,6 @@ if [ $file_count -eq 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[AutoLibrary compile] 找到 $file_count 个 .ui 文件,开始编译..."
|
echo "[AutoLibrary compile] 找到 $file_count 个 .ui 文件,开始编译..."
|
||||||
echo
|
|
||||||
|
|
||||||
for file in *.ui; do
|
for file in *.ui; do
|
||||||
base_name=$(basename "$file" .ui)
|
base_name=$(basename "$file" .ui)
|
||||||
@@ -20,11 +20,10 @@ for file in *.ui; do
|
|||||||
echo "[AutoLibrary compile] 正在编译: \"$file\" -> \"$output_file\""
|
echo "[AutoLibrary compile] 正在编译: \"$file\" -> \"$output_file\""
|
||||||
|
|
||||||
if pyside6-uic "$file" -o "$output_file"; then
|
if pyside6-uic "$file" -o "$output_file"; then
|
||||||
echo "[AutoLibrary compile] 文件 \"$file\" ✓ 编译成功,输出文件: \"$output_file\""
|
echo "[AutoLibrary compile] 文件 \"$file\" 编译成功,输出文件: \"$output_file\""
|
||||||
else
|
else
|
||||||
echo "[AutoLibrary compile] 文件 \"$file\" ✗ 编译失败"
|
echo "[AutoLibrary compile] 文件 \"$file\" 编译失败"
|
||||||
fi
|
fi
|
||||||
echo
|
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "[AutoLibrary compile] 所有操作完成。"
|
echo "[AutoLibrary compile] 所有操作完成。"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This folder is used to store the batch scripts.
|
||||||
@@ -1 +0,0 @@
|
|||||||
For more infomation, please visit our website: https://www.autolibrary.cv
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
This folder is used to store the browser driver using by selenium.
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This folder is used to store the browser drivers using by selenium.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
This folder is used to store the manuals.
|
||||||
|
|
||||||
|
Our manuals are available at https://www.autolibrary.top/manuals
|
||||||
@@ -1 +0,0 @@
|
|||||||
This folder is used to store the model using by ddddocr.
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This folder is used to store the models using by ddddocr.
|
||||||
@@ -2,14 +2,15 @@
|
|||||||
# AutoLibrary
|
# AutoLibrary
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|
[](https://github.com/KenanZhu/AutoLibrary)
|
||||||

|

|
||||||

|
[](https://github.com/KenanZhu/AutoLibrary/actions/workflows/release.yml)
|
||||||

|
[](https://github.com/KenanZhu/AutoLibrary/releases/latest)
|
||||||
|

|
||||||
|
|
||||||
了解更多请访问 [_AutoLibrary 网站_](http://autolibrary.cv)
|
了解更多请访问 [_AutoLibrary 网站_](http://www.autolibrary.top)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -21,36 +22,18 @@
|
|||||||
4. 批量操作 - 支持同时预约多个用户,可以指定当前需要跳过的用户,并将用户分成多个组
|
4. 批量操作 - 支持同时预约多个用户,可以指定当前需要跳过的用户,并将用户分成多个组
|
||||||
5. 定时任务 - 使用内置定时任务管理,添加定时任务,指定时间后按当前预约信息自动运行
|
5. 定时任务 - 使用内置定时任务管理,添加定时任务,指定时间后按当前预约信息自动运行
|
||||||
|
|
||||||
*1,2,3 的具体操作方法和注意事项请访问我们的 [帮助手册](https://autolibrary.cv/docs/manual_lists.html)*
|
*1,2,3 的具体操作方法和注意事项请访问我们的 [帮助手册](https://www.autolibrary.top/manuals)*
|
||||||
|
|
||||||
### 特点
|
|
||||||
|
|
||||||
#### 关于预约等操作的注意事项
|
|
||||||
|
|
||||||
工具会自动处理登录过程的验证码识别过程,正常情况下单次识别准确率在 90% 以上,如遇验证码识别错误,大概率是校园网网络环境不佳导致的。
|
|
||||||
|
|
||||||
只要确保处于校园网网络环境内,工具都是可以正常运行的。操作处理速度基本上取决于校园网的网络环境,一般情况下在 3-4 秒(不考虑硬件差异)左右即可完成一个用户的操作,完全满足正常使用需求。
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> 工具仅作为正常的预约,签到和续约的图书馆辅助工具,请勿干扰图书馆的正常运行(如故意预约多个座位,或同时预约大量的用户等,对此影响图书馆正常运行本工具概不负责,请在善用工具方便自己的情况下尽量不用影响其他同学的使用)。
|
|
||||||
|
|
||||||
#### 关于批量操作的注意事项
|
|
||||||
|
|
||||||
批量操作时,建议将需要操作的用户分成多个组,每个组的用户数量不要超过 4 人(即一整张桌子的数量),否则会影响操作效率,大量用户同时预约会一定程度上增加图书馆服务器的压力,影响正常使用。根据需要在用户管理界面中可以勾选本次操作是否跳过该用户,以提高运行效率。
|
|
||||||
|
|
||||||
#### 关于定时任务的注意事项
|
|
||||||
|
|
||||||
定时任务会在指定的时间自动运行,运行时会根据当前预约信息进行操作。一般情况下不建议设置两个运行开始时间比较接近的定时任务,否则后一个任务会等待前一个任务完成后才会运行,按照队列的顺序执行。
|
|
||||||
|
|
||||||
### 如何使用
|
### 如何使用
|
||||||
|
|
||||||
1. 下载最新版本的 [AutoLibrary 压缩包](https://github.com/KenanZhu/AutoLibrary/releases)。
|
1. 下载最新版本的 [AutoLibrary 压缩包](https://github.com/KenanZhu/AutoLibrary/releases/latest)。
|
||||||
2. 解压下载的文件到任意目录。
|
2. 解压下载的文件到任意目录。
|
||||||
3. 下载对应浏览器的驱动文件,并在配置界面的运行配置选项卡对应位置选择你下载好的浏览器驱动
|
3. 下载对应浏览器类型和版本(具体操作请参考适用软件版本的 [帮助手册](https://www.autolibrary.top/manuals))的驱动文件,并在配置界面的运行配置选项卡对应位置选择你下载好的浏览器驱动。
|
||||||
4. 运行 `AutoLibrary.exe` 文件。
|
4. 运行 `AutoLibrary-[主版本号].[次版本号].[修订版本号].Z.exe` 文件 (如 `AutoLibrary-1.0.0.exe`)。
|
||||||
5. 按照提示操作即可。
|
5. 点击 [配置] 按钮,在配置界面填写好预约信息和运行配置后,点击 [确认] 按钮。
|
||||||
|
6. 点击 [启动脚本] 按钮,即可开始自动预约、续约、签到等操作。
|
||||||
|
|
||||||
*注意 1*: 关于浏览器驱动的下载和其它相关问题,请参考我们的 [帮助手册](https://autolibrary.cv/docs/manual_lists.html) 中对应软件版本的内容。
|
*注意 1*: 关于浏览器驱动的下载和其它相关问题,请参考我们的 [帮助手册](https://www.autolibrary.top/manuals) 中对应软件版本的内容。
|
||||||
|
|
||||||
#### 平台支持 & 编译步骤
|
#### 平台支持 & 编译步骤
|
||||||
|
|
||||||
@@ -58,7 +41,7 @@
|
|||||||
|
|
||||||
1. 确保系统安装了 Python 3.13 版本 (推荐,过低或高版本会导致兼容问题)。
|
1. 确保系统安装了 Python 3.13 版本 (推荐,过低或高版本会导致兼容问题)。
|
||||||
2. 安装 pyside6 selenium ddddocr 库,命令为 `pip install pyside6 selenium ddddocr`。
|
2. 安装 pyside6 selenium ddddocr 库,命令为 `pip install pyside6 selenium ddddocr`。
|
||||||
3. 在 `src/gui/batchs` 目录下运行 `compile_ui.bat` (linux 和 macOS 系统使用 `compile_ui.sh`) 文件来编译 Qt 的 UI 文件。
|
3. 在 `batchs` 目录下运行 `compile_ui.bat` (linux 和 macOS 系统使用 `compile_ui.sh`) 文件来编译 Qt 的 UI 文件。
|
||||||
4. 在上一步相同目录内运行 `compile_rc.bat` (linux 和 macOS 系统使用 `compile_rc.sh`) 文件来编译 Qt 的资源文件。
|
4. 在上一步相同目录内运行 `compile_rc.bat` (linux 和 macOS 系统使用 `compile_rc.sh`) 文件来编译 Qt 的资源文件。
|
||||||
5. 待上述步骤完成后,运行 `src/Main.py` 文件即可。
|
5. 待上述步骤完成后,运行 `src/Main.py` 文件即可。
|
||||||
|
|
||||||
@@ -77,6 +60,25 @@ def classification(self, img: bytes):
|
|||||||
|
|
||||||
[1](@ref):[pillow 中已经删除或已经弃用的常量](https://pillow.ac.cn/en/stable/deprecations.html#constants)
|
[1](@ref):[pillow 中已经删除或已经弃用的常量](https://pillow.ac.cn/en/stable/deprecations.html#constants)
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
|
||||||
|
#### 关于预约等操作
|
||||||
|
|
||||||
|
工具会自动处理登录过程的验证码识别过程,正常情况下单次识别准确率在 90% 以上,如遇验证码识别错误,大概率是校园网网络环境不佳导致的。
|
||||||
|
|
||||||
|
只要确保处于校园网网络环境内,工具都是可以正常运行的。操作处理速度基本上取决于校园网的网络环境,一般情况下在 3-4 秒(不考虑硬件差异)左右即可完成一个用户的操作,完全满足正常使用需求。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 工具仅作为正常的预约,签到和续约的图书馆辅助工具,请勿干扰图书馆的正常运行(如故意预约多个座位,或同时预约大量的用户等,对此影响图书馆正常运行本工具概不负责,请在善用工具方便自己的情况下尽量不用影响其他同学的使用)。
|
||||||
|
|
||||||
|
#### 关于批量操作
|
||||||
|
|
||||||
|
批量操作时,建议将需要操作的用户分成多个组,每个组的用户数量不要超过 4 人(即一整张桌子的数量),否则会影响操作效率,大量用户同时预约会一定程度上增加图书馆服务器的压力,影响正常使用。根据需要在用户管理界面中可以勾选本次操作是否跳过该用户,以提高运行效率。
|
||||||
|
|
||||||
|
#### 关于定时任务
|
||||||
|
|
||||||
|
定时任务会在指定的时间自动运行,运行时会根据当前预约信息进行操作。一般情况下不建议设置两个运行开始时间比较接近的定时任务,否则后一个任务会等待前一个任务完成后才会运行,按照队列的顺序执行。
|
||||||
|
|
||||||
### Q&A
|
### Q&A
|
||||||
|
|
||||||
#### 为什么开发这个工具?
|
#### 为什么开发这个工具?
|
||||||
@@ -97,11 +99,11 @@ def classification(self, img: bytes):
|
|||||||
|
|
||||||
#### 后续会有哪些功能?
|
#### 后续会有哪些功能?
|
||||||
|
|
||||||
当前 v1.0.0 版本的功能对于正常使用已经足够,不过后续会着重考虑完善 2-4 人预约时的使用体验,暂时有以下构想:
|
当前版本的功能对于正常使用已经足够,不过后续会着重完善预约时的使用体验,暂时有以下构想:
|
||||||
|
|
||||||
1. 2-4 人一起预约时,往往会偏向于预约并排或对面的整个空座位,这时候工具会按照一定策略查询搜索符合条件的座位,并预约并排或对面的整个座位,而不是各自独立预约。
|
- 引入交互预约面板功能,预约时直接在座位分布图中选择可用座位,并按用户分配,无需事先配置预约信息。
|
||||||
2. 预约时会考虑到组内用户的预约时间是否冲突,若冲突则会提示用户是否继续预约,若用户选择继续预约,则会按需要调整预约时间,再进行预约。
|
- 优化定时任务管理功能,用户可以在定时任务管理界面设置重复运行的定时任务,如每日预约、每周预约等。
|
||||||
3. 对于比较固定的用户,会考虑在定时任务管理中添加如 ‘每日任务’ ‘每周任务’ 等选项,用户可以根据需要设置定时任务重复的日期范围,自动完成预约,签到,续约等操作。
|
- 软件的自动更新以及公告栏功能,用户可以自动更新最新版本并获取最新公告事项。
|
||||||
|
|
||||||
不过由于本人的时间和能力有限,也需要考虑到图书馆的正常运行,所以后续功能会有所取舍,但也许会进行一些小的功能验证。
|
不过由于本人的时间和能力有限,也需要考虑到图书馆的正常运行,所以后续功能会有所取舍,但也许会进行一些小的功能验证。
|
||||||
|
|
||||||
|
|||||||
+15
-2
@@ -7,14 +7,25 @@ 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.
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||||
See the LICENSE file for details.
|
See the LICENSE file for details.
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PySide6.QtCore import QTranslator
|
from PySide6.QtCore import QTranslator, QStandardPaths, QDir
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
|
|
||||||
from gui.ALMainWindow import ALMainWindow
|
from gui.ALMainWindow import ALMainWindow
|
||||||
from gui import AutoLibraryResource
|
from gui.resources import ALResource
|
||||||
|
|
||||||
|
from utils.ConfigManager import instance
|
||||||
|
|
||||||
|
|
||||||
|
def initializeConfigManager():
|
||||||
|
|
||||||
|
app_dir = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppDataLocation)
|
||||||
|
config_dir = os.path.join(app_dir, "config")
|
||||||
|
if not QDir(config_dir).exists():
|
||||||
|
QDir().mkpath(config_dir)
|
||||||
|
instance(config_dir)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
@@ -23,6 +34,8 @@ def main():
|
|||||||
if translator.load(":/res/trans/translators/qtbase_zh_CN.ts"):
|
if translator.load(":/res/trans/translators/qtbase_zh_CN.ts"):
|
||||||
app.installTranslator(translator)
|
app.installTranslator(translator)
|
||||||
app.setStyle('Fusion')
|
app.setStyle('Fusion')
|
||||||
|
app.setApplicationName("AutoLibrary")
|
||||||
|
initializeConfigManager()
|
||||||
window = ALMainWindow()
|
window = ALMainWindow()
|
||||||
window.show()
|
window.show()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
||||||
|
|||||||
+2
-14
@@ -7,8 +7,8 @@ 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.
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||||
See the LICENSE file for details.
|
See the LICENSE file for details.
|
||||||
"""
|
"""
|
||||||
import time
|
|
||||||
import queue
|
import queue
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
class MsgBase:
|
class MsgBase:
|
||||||
@@ -53,7 +53,7 @@ class MsgBase:
|
|||||||
msg: str
|
msg: str
|
||||||
):
|
):
|
||||||
|
|
||||||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
||||||
self._output_queue.put(f"{timestamp}-[{self._class_name:<15}] : {msg}")
|
self._output_queue.put(f"{timestamp}-[{self._class_name:<15}] : {msg}")
|
||||||
|
|
||||||
|
|
||||||
@@ -67,15 +67,3 @@ class MsgBase:
|
|||||||
return msg
|
return msg
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _inputMsg(
|
|
||||||
self,
|
|
||||||
timeout: float = 1.0
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._input_queue.get(timeout=timeout)
|
|
||||||
return True
|
|
||||||
except queue.Empty:
|
|
||||||
return False
|
|
||||||
|
|||||||
+11
-10
@@ -7,11 +7,10 @@ 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.
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||||
See the LICENSE file for details.
|
See the LICENSE file for details.
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
from PySide6.QtGui import (
|
from PySide6.QtGui import (
|
||||||
QIcon
|
QIcon, QFont
|
||||||
)
|
)
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QDialog, QApplication
|
QDialog, QApplication
|
||||||
@@ -23,9 +22,8 @@ from PySide6.QtCore import (
|
|||||||
from gui.ALVersionInfo import (
|
from gui.ALVersionInfo import (
|
||||||
AL_VERSION, AL_COMMIT_SHA, AL_COMMIT_DATE, AL_BUILD_DATE
|
AL_VERSION, AL_COMMIT_SHA, AL_COMMIT_DATE, AL_BUILD_DATE
|
||||||
)
|
)
|
||||||
from gui.Ui_ALAboutDialog import Ui_ALAboutDialog
|
from gui.resources.ui.Ui_ALAboutDialog import Ui_ALAboutDialog
|
||||||
|
from gui.resources import ALResource
|
||||||
from gui import AutoLibraryResource
|
|
||||||
|
|
||||||
|
|
||||||
class ALAboutDialog(QDialog, Ui_ALAboutDialog):
|
class ALAboutDialog(QDialog, Ui_ALAboutDialog):
|
||||||
@@ -47,8 +45,11 @@ class ALAboutDialog(QDialog, Ui_ALAboutDialog):
|
|||||||
|
|
||||||
self.LogoIconLabel.setPixmap(QIcon(":/res/icon/icons/AutoLibrary_32x32.ico").pixmap(48, 48))
|
self.LogoIconLabel.setPixmap(QIcon(":/res/icon/icons/AutoLibrary_32x32.ico").pixmap(48, 48))
|
||||||
info_text = self.generateAboutText()
|
info_text = self.generateAboutText()
|
||||||
self.AboutInfoEdit.setHtml(info_text)
|
self.AboutInfoBrowser.setHtml(info_text)
|
||||||
self.AboutInfoEdit.setTextInteractionFlags(Qt.TextBrowserInteraction)
|
browser_font = self.AboutInfoBrowser.font()
|
||||||
|
browser_font.setFamily("Courier New")
|
||||||
|
self.AboutInfoBrowser.setFont(browser_font)
|
||||||
|
self.AboutInfoBrowser.setTextInteractionFlags(Qt.TextBrowserInteraction)
|
||||||
|
|
||||||
|
|
||||||
def connectSignals(
|
def connectSignals(
|
||||||
@@ -60,7 +61,7 @@ class ALAboutDialog(QDialog, Ui_ALAboutDialog):
|
|||||||
|
|
||||||
def generateAboutText(
|
def generateAboutText(
|
||||||
self
|
self
|
||||||
):
|
) -> str:
|
||||||
|
|
||||||
os_info = self.getOSInfo()
|
os_info = self.getOSInfo()
|
||||||
about_text = f"""
|
about_text = f"""
|
||||||
@@ -81,7 +82,7 @@ System architecture: {os_info['architecture']}<br>
|
|||||||
<h4>Project Information:</h4>
|
<h4>Project Information:</h4>
|
||||||
License: MIT License<br>
|
License: MIT License<br>
|
||||||
Project repository: <a href="https://www.github.com/KenanZhu/AutoLibrary" style="text-decoration: none;">https://www.github.com/KenanZhu/AutoLibrary</a><br>
|
Project repository: <a href="https://www.github.com/KenanZhu/AutoLibrary" style="text-decoration: none;">https://www.github.com/KenanZhu/AutoLibrary</a><br>
|
||||||
Project website: <a href="https://www.autolibrary.cv/" style="text-decoration: none;">https://www.autolibrary.cv/</a><br>
|
Project website: <a href="https://www.autolibrary.top" style="text-decoration: none;">https://www.autolibrary.top</a><br>
|
||||||
|
|
||||||
<h4>Author Information:</h4>
|
<h4>Author Information:</h4>
|
||||||
Developer: KenanZhu<br>
|
Developer: KenanZhu<br>
|
||||||
@@ -138,7 +139,7 @@ GitHub: <a href="https://www.github.com/KenanZhu" style="text-decoration: none;"
|
|||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
about_text = self.AboutInfoEdit.toPlainText()
|
about_text = self.AboutInfoBrowser.toPlainText()
|
||||||
clipboard = QApplication.clipboard()
|
clipboard = QApplication.clipboard()
|
||||||
clipboard.setText(about_text)
|
clipboard.setText(about_text)
|
||||||
original_text = self.CopyButton.text()
|
original_text = self.CopyButton.text()
|
||||||
|
|||||||
+214
-198
@@ -14,47 +14,42 @@ from PySide6.QtCore import (
|
|||||||
Qt, Signal, Slot, QTime, QDate, QDir, QFileInfo
|
Qt, Signal, Slot, QTime, QDate, QDir, QFileInfo
|
||||||
)
|
)
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QWidget, QLineEdit, QMessageBox, QFileDialog,
|
QDialog, QWidget, QLineEdit, QMessageBox, QFileDialog,
|
||||||
QTreeWidgetItem, QMenu, QInputDialog
|
QTreeWidgetItem, QMenu, QInputDialog
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import (
|
from PySide6.QtGui import (
|
||||||
QCloseEvent, QAction
|
QCloseEvent, QAction
|
||||||
)
|
)
|
||||||
|
|
||||||
from gui.Ui_ALConfigWidget import Ui_ALConfigWidget
|
from utils.JSONReader import JSONReader
|
||||||
from gui.ALSeatMapWidget import ALSeatMapWidget
|
from utils.JSONWriter import JSONWriter
|
||||||
from gui.ALSeatMapTable import seats_maps
|
from utils.ConfigManager import ConfigType, instance
|
||||||
from gui.ALUserTreeWidget import TreeItemType
|
from utils.ConfigManager import getValidateAutomationConfigPaths
|
||||||
from gui.ALUserTreeWidget import ALUserTreeWidget
|
|
||||||
|
|
||||||
from utils.ConfigReader import ConfigReader
|
from gui.resources.ui.Ui_ALConfigWidget import Ui_ALConfigWidget
|
||||||
from utils.ConfigWriter import ConfigWriter
|
from gui.ALSeatMapSelectDialog import ALSeatMapSelectDialog
|
||||||
|
from gui.ALSeatMapTable import ALSeatMapTable
|
||||||
|
from gui.ALUserTreeWidget import ALUserTreeWidget, ALUserTreeItemType
|
||||||
|
|
||||||
|
|
||||||
class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
||||||
|
|
||||||
configWidgetCloseSingal = Signal(dict)
|
configWidgetIsClosed = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent = None,
|
parent = None,
|
||||||
config_paths = {
|
|
||||||
"run": "",
|
|
||||||
"user": ""
|
|
||||||
}
|
|
||||||
):
|
):
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.__config_paths = config_paths
|
self.__cfg_mgr = instance()
|
||||||
|
self.__config_paths = getValidateAutomationConfigPaths()
|
||||||
self.__config_data = {"run": {}, "user": {}}
|
self.__config_data = {"run": {}, "user": {}}
|
||||||
self.__seat_map_widget = None
|
|
||||||
|
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.modifyUi()
|
self.modifyUi()
|
||||||
self.connectSignals()
|
self.connectSignals()
|
||||||
self.initlizeFloorRoomMap()
|
if not self.initializeConfigs():
|
||||||
self.initlizeDefaultConfigPaths()
|
|
||||||
if not self.initlizeConfigs():
|
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
@@ -70,8 +65,8 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
self.UserListLayout.insertWidget(0, self.UserTreeWidget)
|
self.UserListLayout.insertWidget(0, self.UserTreeWidget)
|
||||||
self.UserTreeWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
self.UserTreeWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self.UserTreeWidget.customContextMenuRequested.connect(self.onUserTreeWidgetContextMenu)
|
self.UserTreeWidget.customContextMenuRequested.connect(self.onUserTreeWidgetContextMenu)
|
||||||
self.initlizeFloorRoomMap()
|
self.initializeFloorRoomMap()
|
||||||
self.initilizeUserInfoWidget()
|
self.initializeUserInfoWidget()
|
||||||
|
|
||||||
|
|
||||||
def connectSignals(
|
def connectSignals(
|
||||||
@@ -126,11 +121,11 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
event: QCloseEvent
|
event: QCloseEvent
|
||||||
):
|
):
|
||||||
|
|
||||||
self.configWidgetCloseSingal.emit(self.__config_paths)
|
self.configWidgetIsClosed.emit()
|
||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
|
||||||
def initlizeFloorRoomMap(
|
def initializeFloorRoomMap(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
@@ -164,19 +159,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def initlizeDefaultConfigPaths(
|
def initializeConfigToWidget(
|
||||||
self
|
|
||||||
):
|
|
||||||
|
|
||||||
script_path = sys.executable
|
|
||||||
script_dir = QFileInfo(script_path).absoluteDir()
|
|
||||||
self.__default_config_paths = {
|
|
||||||
"user": QDir.toNativeSeparators(script_dir.absoluteFilePath("user.json")),
|
|
||||||
"run": QDir.toNativeSeparators(script_dir.absoluteFilePath("run.json"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def initlizeConfigToWidget(
|
|
||||||
self,
|
self,
|
||||||
which: str,
|
which: str,
|
||||||
config_data: dict
|
config_data: dict
|
||||||
@@ -186,23 +169,22 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
self.setRunConfigToWidget(config_data)
|
self.setRunConfigToWidget(config_data)
|
||||||
self.CurrentRunConfigEdit.setText(self.__config_paths["run"])
|
self.CurrentRunConfigEdit.setText(self.__config_paths["run"])
|
||||||
elif which == "user":
|
elif which == "user":
|
||||||
self.initilizeUserInfoWidget()
|
self.initializeUserInfoWidget()
|
||||||
self.fillUserTree(config_data)
|
self.setUsersToTreeWidget(config_data)
|
||||||
self.CurrentUserConfigEdit.setText(self.__config_paths["user"])
|
self.CurrentUserConfigEdit.setText(self.__config_paths["user"])
|
||||||
|
|
||||||
|
|
||||||
def initlizeConfig(
|
def initializeConfig(
|
||||||
self,
|
self,
|
||||||
which: str
|
which: str
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
msg = ""
|
msg = "" # no use for now
|
||||||
is_success = True
|
is_success = True
|
||||||
if which == "run":
|
if which == "run":
|
||||||
run_config_path = self.__config_paths[which]
|
run_config_path = self.__config_paths[which]
|
||||||
if not os.path.exists(run_config_path):
|
if not os.path.exists(run_config_path):
|
||||||
self.__config_data[which] = self.defaultRunConfig()
|
self.__config_data[which] = self.defaultRunConfig()
|
||||||
self.__config_paths[which] = self.__default_config_paths[which]
|
|
||||||
if self.saveRunConfig(self.__config_paths[which], self.__config_data[which]):
|
if self.saveRunConfig(self.__config_paths[which], self.__config_data[which]):
|
||||||
msg += f"运行配置文件已初始化, 文件路径: \n{self.__config_paths[which]}\n"
|
msg += f"运行配置文件已初始化, 文件路径: \n{self.__config_paths[which]}\n"
|
||||||
else:
|
else:
|
||||||
@@ -215,7 +197,6 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
user_config_path = self.__config_paths[which]
|
user_config_path = self.__config_paths[which]
|
||||||
if not os.path.exists(user_config_path):
|
if not os.path.exists(user_config_path):
|
||||||
self.__config_data[which] = self.defaultUserConfig()
|
self.__config_data[which] = self.defaultUserConfig()
|
||||||
self.__config_paths[which] = self.__default_config_paths[which]
|
|
||||||
if self.saveUserConfig(self.__config_paths[which], self.__config_data[which]):
|
if self.saveUserConfig(self.__config_paths[which], self.__config_data[which]):
|
||||||
msg += f"用户配置文件已初始化, 文件路径: \n{self.__config_paths[which]}\n"
|
msg += f"用户配置文件已初始化, 文件路径: \n{self.__config_paths[which]}\n"
|
||||||
else:
|
else:
|
||||||
@@ -224,27 +205,19 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
self.__config_data[which] = self.loadUserConfig(user_config_path)
|
self.__config_data[which] = self.loadUserConfig(user_config_path)
|
||||||
if self.__config_data[which] is None:
|
if self.__config_data[which] is None:
|
||||||
is_success = False
|
is_success = False
|
||||||
if msg:
|
|
||||||
QMessageBox.information(
|
|
||||||
self,
|
|
||||||
"提示 - AutoLibrary",
|
|
||||||
f"配置文件初始化完成: \n{msg}"
|
|
||||||
)
|
|
||||||
return is_success
|
return is_success
|
||||||
|
|
||||||
|
|
||||||
def initlizeConfigs(
|
def initializeConfigs(
|
||||||
self
|
self
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
is_success = True
|
is_success = True
|
||||||
for which in ["run", "user"]:
|
for which in ["run", "user"]:
|
||||||
if not self.__config_paths[which]:
|
if not self.initializeConfig(which):
|
||||||
self.__config_paths[which] = self.__default_config_paths[which]
|
|
||||||
if not self.initlizeConfig(which):
|
|
||||||
is_success = False
|
is_success = False
|
||||||
break
|
break
|
||||||
self.initlizeConfigToWidget(which, self.__config_data[which])
|
self.initializeConfigToWidget(which, self.__config_data[which])
|
||||||
return is_success
|
return is_success
|
||||||
|
|
||||||
|
|
||||||
@@ -277,27 +250,8 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"groups": []
|
"groups": [
|
||||||
}
|
]
|
||||||
|
|
||||||
|
|
||||||
def defaultGroup(
|
|
||||||
self
|
|
||||||
) -> dict:
|
|
||||||
|
|
||||||
return {
|
|
||||||
"name": "默认分组",
|
|
||||||
"enabled": True,
|
|
||||||
"users": []
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def defaultUsers(
|
|
||||||
self
|
|
||||||
) -> dict:
|
|
||||||
|
|
||||||
return {
|
|
||||||
"users": []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -328,24 +282,41 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
run_config: dict
|
run_config: dict
|
||||||
):
|
):
|
||||||
|
|
||||||
self.HostUrlEdit.setText(run_config["library"]["host_url"])
|
try:
|
||||||
self.LoginUrlEdit.setText(run_config["library"]["login_url"])
|
self.HostUrlEdit.setText(run_config["library"]["host_url"])
|
||||||
self.AutoCaptchaCheckBox.setChecked(run_config["login"]["auto_captcha"])
|
self.LoginUrlEdit.setText(run_config["library"]["login_url"])
|
||||||
self.LoginAttemptSpinBox.setValue(run_config["login"]["max_attempt"])
|
self.AutoCaptchaCheckBox.setChecked(run_config["login"]["auto_captcha"])
|
||||||
self.BrowserTypeComboBox.setCurrentText(run_config["web_driver"]["driver_type"])
|
self.LoginAttemptSpinBox.setValue(run_config["login"]["max_attempt"])
|
||||||
if run_config["web_driver"]["driver_path"]:
|
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"]:
|
||||||
else:
|
driver_path = os.path.abspath(run_config["web_driver"]["driver_path"])
|
||||||
driver_path = ""
|
else:
|
||||||
self.BrowseBrowserDriverEdit.setText(QDir.toNativeSeparators(driver_path))
|
driver_path = ""
|
||||||
self.HeadlessCheckBox.setChecked(run_config["web_driver"]["headless"])
|
self.BrowseBrowserDriverEdit.setText(QDir.toNativeSeparators(driver_path))
|
||||||
run_mode = run_config["mode"]["run_mode"]
|
self.HeadlessCheckBox.setChecked(run_config["web_driver"]["headless"])
|
||||||
self.AutoReserveCheckBox.setChecked(run_mode&0x01)
|
run_mode = run_config["mode"]["run_mode"]
|
||||||
self.AutoCheckinCheckBox.setChecked(run_mode&0x02)
|
self.AutoReserveCheckBox.setChecked(run_mode&0x01)
|
||||||
self.AutoRenewalCheckBox.setChecked(run_mode&0x04)
|
self.AutoCheckinCheckBox.setChecked(run_mode&0x02)
|
||||||
|
self.AutoRenewalCheckBox.setChecked(run_mode&0x04)
|
||||||
|
except KeyError as e:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"警告 - AutoLibrary",
|
||||||
|
f"运行配置文件读取键 '{e}' 时发生错误 ! :\n"
|
||||||
|
f"文件路径: {self.__config_paths['run']}\n"
|
||||||
|
"文件可能被意外修改或已经损坏\n"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"警告 - AutoLibrary",
|
||||||
|
f"运行配置文件读取键 '{e}' 时发生未知错误 ! :\n"
|
||||||
|
f"文件路径: {self.__config_paths['run']}\n"
|
||||||
|
"文件可能被意外修改或已经损坏\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def initilizeUserInfoWidget(
|
def initializeUserInfoWidget(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
@@ -370,7 +341,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
self.PreferLateRenewTimeCheckBox.setChecked(False)
|
self.PreferLateRenewTimeCheckBox.setChecked(False)
|
||||||
|
|
||||||
|
|
||||||
def collectUserFromUserInfoWidget(
|
def collectUserFromWidget(
|
||||||
self
|
self
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
||||||
@@ -403,7 +374,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def collectUserConfigFromUserTreeWidget(
|
def collectUsersFromTreeWidget(
|
||||||
self
|
self
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
||||||
@@ -450,13 +421,64 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
self.ExpectRenewDurationSpinBox.setValue(user["reserve_info"]["renew_time"]["expect_duration"])
|
self.ExpectRenewDurationSpinBox.setValue(user["reserve_info"]["renew_time"]["expect_duration"])
|
||||||
self.MaxRenewTimeDiffSpinBox.setValue(user["reserve_info"]["renew_time"]["max_diff"])
|
self.MaxRenewTimeDiffSpinBox.setValue(user["reserve_info"]["renew_time"]["max_diff"])
|
||||||
self.PreferLateRenewTimeCheckBox.setChecked(not user["reserve_info"]["renew_time"]["prefer_early"])
|
self.PreferLateRenewTimeCheckBox.setChecked(not user["reserve_info"]["renew_time"]["prefer_early"])
|
||||||
except:
|
except KeyError as e:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"警告 - AutoLibrary",
|
"警告 - AutoLibrary",
|
||||||
"用户配置文件读取发生错误 !\n"\
|
f"用户配置文件读取键 '{e}' 时发生错误 ! :\n"
|
||||||
f"用户: {user['username']} 配置文件可能已损坏"
|
f"文件路径: {self.__config_paths['user']}\n"
|
||||||
|
"文件可能被意外修改或已经损坏\n"
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"警告 - AutoLibrary",
|
||||||
|
f"用户配置文件读取键 '{e}' 时发生未知错误 ! :\n"
|
||||||
|
f"文件路径: {self.__config_paths['user']}\n"
|
||||||
|
"文件可能被意外修改或已经损坏\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setUsersToTreeWidget(
|
||||||
|
self,
|
||||||
|
users: dict
|
||||||
|
):
|
||||||
|
|
||||||
|
self.UserTreeWidget.clear()
|
||||||
|
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
|
||||||
|
try:
|
||||||
|
if "groups" in users:
|
||||||
|
for group_config in users["groups"]:
|
||||||
|
group_item = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value)
|
||||||
|
group_item.setText(0, group_config["name"])
|
||||||
|
group_item.setFlags(group_item.flags() | Qt.ItemIsEditable)
|
||||||
|
group_item.setCheckState(1, Qt.Checked if group_config.get("enabled", True) else Qt.Unchecked)
|
||||||
|
for user_config in group_config["users"]:
|
||||||
|
user_item = QTreeWidgetItem(group_item, ALUserTreeItemType.USER.value)
|
||||||
|
user_item.setText(0, user_config["username"])
|
||||||
|
user_item.setText(1, "" if user_config.get("enabled", True) else "跳过")
|
||||||
|
user_item.setData(0, Qt.UserRole, user_config)
|
||||||
|
user_item.setCheckState(1, Qt.Checked if user_config.get("enabled", True) else Qt.Unchecked)
|
||||||
|
user_item.setDisabled(not group_config.get("enabled", True))
|
||||||
|
group_item.setExpanded(True)
|
||||||
|
except KeyError as e:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"警告 - AutoLibrary",
|
||||||
|
f"用户配置文件读取键 '{e}' 时发生错误 ! :\n"
|
||||||
|
f"文件路径: {self.__config_paths['user']}\n"
|
||||||
|
"文件可能被意外修改或已经损坏\n"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"警告 - AutoLibrary",
|
||||||
|
f"用户配置文件读取键 '{e}' 时发生未知错误 ! :\n"
|
||||||
|
f"文件路径: {self.__config_paths['user']}\n"
|
||||||
|
"文件可能被意外修改或已经损坏\n"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged)
|
||||||
|
|
||||||
|
|
||||||
def loadRunConfig(
|
def loadRunConfig(
|
||||||
@@ -467,18 +489,18 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
try:
|
try:
|
||||||
if not run_config_path or not os.path.exists(run_config_path):
|
if not run_config_path or not os.path.exists(run_config_path):
|
||||||
raise Exception("文件路径不存在")
|
raise Exception("文件路径不存在")
|
||||||
run_config = ConfigReader(run_config_path).getConfigs()
|
run_config = JSONReader(run_config_path).data()
|
||||||
if run_config and "library" in run_config\
|
if run_config and "library" in run_config\
|
||||||
and "web_driver" in run_config\
|
and "web_driver" in run_config\
|
||||||
and "login" in run_config:
|
and "login" in run_config:
|
||||||
return run_config
|
return run_config
|
||||||
return None
|
else:
|
||||||
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"警告 - AutoLibrary",
|
"警告 - AutoLibrary",
|
||||||
f"运行配置文件读取发生错误 ! : {e}\n"\
|
f"运行配置文件读取发生错误 ! :\n{e}"
|
||||||
f"文件路径: {run_config_path}"
|
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -494,14 +516,13 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
raise Exception("文件路径为空")
|
raise Exception("文件路径为空")
|
||||||
if not run_config_data or not isinstance(run_config_data, dict):
|
if not run_config_data or not isinstance(run_config_data, dict):
|
||||||
raise Exception("运行配置数据为空或类型错误")
|
raise Exception("运行配置数据为空或类型错误")
|
||||||
ConfigWriter(run_config_path, run_config_data)
|
JSONWriter(run_config_path, run_config_data)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"警告 - AutoLibrary",
|
"警告 - AutoLibrary",
|
||||||
f"配置文件写入发生错误 ! : {e}\n"\
|
f"配置文件写入发生错误 ! : \n{e}"
|
||||||
f"文件路径: {run_config_path}"
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -514,11 +535,11 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
try:
|
try:
|
||||||
if not user_config_path or not os.path.exists(user_config_path):
|
if not user_config_path or not os.path.exists(user_config_path):
|
||||||
raise Exception("文件路径不存在")
|
raise Exception("文件路径不存在")
|
||||||
user_config = ConfigReader(user_config_path).getConfigs()
|
user_config = JSONReader(user_config_path).data()
|
||||||
if user_config and "groups" in user_config:
|
if user_config and "groups" in user_config:
|
||||||
return user_config
|
return user_config
|
||||||
# compatibility with old version config format
|
# compatibility with old version config format
|
||||||
if user_config and "users" in user_config:
|
elif user_config and "users" in user_config:
|
||||||
user_config = {
|
user_config = {
|
||||||
"groups": [
|
"groups": [
|
||||||
{
|
{
|
||||||
@@ -529,13 +550,13 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
return user_config
|
return user_config
|
||||||
return None
|
else:
|
||||||
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"警告 - AutoLibrary",
|
"警告 - AutoLibrary",
|
||||||
f"用户配置文件读取发生错误 ! : {e}\n"\
|
f"用户配置文件读取发生错误 ! :\n{e}"
|
||||||
f"文件路径: {user_config_path}"
|
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -551,14 +572,13 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
raise Exception("文件路径为空")
|
raise Exception("文件路径为空")
|
||||||
if not user_config_data or not isinstance(user_config_data, dict):
|
if not user_config_data or not isinstance(user_config_data, dict):
|
||||||
raise Exception("用户配置数据为空或类型错误")
|
raise Exception("用户配置数据为空或类型错误")
|
||||||
ConfigWriter(user_config_path, user_config_data)
|
JSONWriter(user_config_path, user_config_data)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"警告 - AutoLibrary",
|
"警告 - AutoLibrary",
|
||||||
f"用户配置文件写入发生错误 ! : {e}\n"\
|
f"用户配置文件写入发生错误 ! :\n{e}"
|
||||||
f"文件路径: \n{user_config_path}"
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -570,7 +590,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
if user_config_path:
|
if user_config_path:
|
||||||
self.__config_data["user"] = self.collectUserConfigFromUserTreeWidget()
|
self.__config_data["user"] = self.collectUsersFromTreeWidget()
|
||||||
if not self.saveUserConfig(
|
if not self.saveUserConfig(
|
||||||
user_config_path,
|
user_config_path,
|
||||||
self.__config_data["user"]
|
self.__config_data["user"]
|
||||||
@@ -609,45 +629,19 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
return True
|
return True
|
||||||
if user_config is not None:
|
if user_config is not None:
|
||||||
self.__config_data["user"].update(user_config)
|
self.__config_data["user"].update(user_config)
|
||||||
self.fillUserTree(self.__config_data["user"])
|
self.setUsersToTreeWidget(self.__config_data["user"])
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def fillUserTree(
|
|
||||||
self,
|
|
||||||
user_config_data: dict
|
|
||||||
):
|
|
||||||
|
|
||||||
self.UserTreeWidget.clear()
|
|
||||||
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
|
|
||||||
try:
|
|
||||||
if "groups" in user_config_data:
|
|
||||||
for group_config in user_config_data["groups"]:
|
|
||||||
group_item = QTreeWidgetItem(self.UserTreeWidget, TreeItemType.GROUP.value)
|
|
||||||
group_item.setText(0, group_config["name"])
|
|
||||||
group_item.setFlags(group_item.flags() | Qt.ItemIsEditable)
|
|
||||||
group_item.setCheckState(1, Qt.Checked if group_config.get("enabled", True) else Qt.Unchecked)
|
|
||||||
for user_config in group_config["users"]:
|
|
||||||
user_item = QTreeWidgetItem(group_item, TreeItemType.USER.value)
|
|
||||||
user_item.setText(0, user_config["username"])
|
|
||||||
user_item.setText(1, "" if user_config.get("enabled", True) else "跳过")
|
|
||||||
user_item.setData(0, Qt.UserRole, user_config)
|
|
||||||
user_item.setCheckState(1, Qt.Checked if user_config.get("enabled", True) else Qt.Unchecked)
|
|
||||||
user_item.setDisabled(not group_config.get("enabled", True))
|
|
||||||
group_item.setExpanded(True)
|
|
||||||
finally:
|
|
||||||
self.UserTreeWidget.itemChanged.connect(self.onUserTreeWidgetItemChanged)
|
|
||||||
|
|
||||||
|
|
||||||
def addGroup(
|
def addGroup(
|
||||||
self,
|
self,
|
||||||
group_name: str = ""
|
group_name: str = ""
|
||||||
) -> QTreeWidgetItem:
|
) -> QTreeWidgetItem:
|
||||||
|
|
||||||
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
|
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
|
||||||
group_item = QTreeWidgetItem(self.UserTreeWidget, TreeItemType.GROUP.value)
|
group_item = QTreeWidgetItem(self.UserTreeWidget, ALUserTreeItemType.GROUP.value)
|
||||||
if not group_name:
|
if not group_name:
|
||||||
group_name = f"新分组-{self.UserTreeWidget.topLevelItemCount()}"
|
group_name = f"新分组-{self.UserTreeWidget.topLevelItemCount()}"
|
||||||
group_item.setText(0, group_name)
|
group_item.setText(0, group_name)
|
||||||
@@ -658,6 +652,19 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
return group_item
|
return group_item
|
||||||
|
|
||||||
|
|
||||||
|
def delGroup(
|
||||||
|
self,
|
||||||
|
group_item: QTreeWidgetItem = None
|
||||||
|
):
|
||||||
|
|
||||||
|
if group_item is None:
|
||||||
|
return
|
||||||
|
if group_item.type() != ALUserTreeItemType.GROUP.value:
|
||||||
|
return
|
||||||
|
index = self.UserTreeWidget.indexOfTopLevelItem(group_item)
|
||||||
|
self.UserTreeWidget.takeTopLevelItem(index)
|
||||||
|
|
||||||
|
|
||||||
def addUser(
|
def addUser(
|
||||||
self,
|
self,
|
||||||
group_item: QTreeWidgetItem = None
|
group_item: QTreeWidgetItem = None
|
||||||
@@ -667,7 +674,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
current_item = self.UserTreeWidget.currentItem()
|
current_item = self.UserTreeWidget.currentItem()
|
||||||
if current_item is None:
|
if current_item is None:
|
||||||
group_item = self.addGroup()
|
group_item = self.addGroup()
|
||||||
if group_item.type() == TreeItemType.USER.value:
|
if group_item.type() == ALUserTreeItemType.USER.value:
|
||||||
group_item = group_item.parent()
|
group_item = group_item.parent()
|
||||||
if group_item.checkState(1) == Qt.CheckState.Unchecked:
|
if group_item.checkState(1) == Qt.CheckState.Unchecked:
|
||||||
return None
|
return None
|
||||||
@@ -701,7 +708,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
|
self.UserTreeWidget.itemChanged.disconnect(self.onUserTreeWidgetItemChanged)
|
||||||
user_item = QTreeWidgetItem(group_item, TreeItemType.USER.value)
|
user_item = QTreeWidgetItem(group_item, ALUserTreeItemType.USER.value)
|
||||||
user_item.setText(0, new_user["username"])
|
user_item.setText(0, new_user["username"])
|
||||||
user_item.setText(1, "")
|
user_item.setText(1, "")
|
||||||
user_item.setData(0, Qt.UserRole, new_user)
|
user_item.setData(0, Qt.UserRole, new_user)
|
||||||
@@ -720,7 +727,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
|
|
||||||
if user_item is None:
|
if user_item is None:
|
||||||
return
|
return
|
||||||
if user_item.type() != TreeItemType.USER.value:
|
if user_item.type() != ALUserTreeItemType.USER.value:
|
||||||
return
|
return
|
||||||
parent_item = user_item.parent()
|
parent_item = user_item.parent()
|
||||||
index = parent_item.indexOfChild(user_item)
|
index = parent_item.indexOfChild(user_item)
|
||||||
@@ -729,19 +736,6 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
self.UserTreeWidget.setCurrentItem(None)
|
self.UserTreeWidget.setCurrentItem(None)
|
||||||
|
|
||||||
|
|
||||||
def delGroup(
|
|
||||||
self,
|
|
||||||
group_item: QTreeWidgetItem = None
|
|
||||||
):
|
|
||||||
|
|
||||||
if group_item is None:
|
|
||||||
return
|
|
||||||
if group_item.type() != TreeItemType.GROUP.value:
|
|
||||||
return
|
|
||||||
index = self.UserTreeWidget.indexOfTopLevelItem(group_item)
|
|
||||||
self.UserTreeWidget.takeTopLevelItem(index)
|
|
||||||
|
|
||||||
|
|
||||||
def renameItem(
|
def renameItem(
|
||||||
self,
|
self,
|
||||||
item: QTreeWidgetItem,
|
item: QTreeWidgetItem,
|
||||||
@@ -761,7 +755,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
if not ok or not new_name:
|
if not ok or not new_name:
|
||||||
return
|
return
|
||||||
item.setText(0, new_name)
|
item.setText(0, new_name)
|
||||||
if item.type() == TreeItemType.GROUP.value:
|
if item.type() == ALUserTreeItemType.GROUP.value:
|
||||||
item.setText(0, new_name)
|
item.setText(0, new_name)
|
||||||
else:
|
else:
|
||||||
user = item.data(0, Qt.UserRole)
|
user = item.data(0, Qt.UserRole)
|
||||||
@@ -770,7 +764,6 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
item.setData(0, Qt.UserRole, user)
|
item.setData(0, Qt.UserRole, user)
|
||||||
self.setUserToWidget(user)
|
self.setUserToWidget(user)
|
||||||
|
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onShowPasswordCheckBoxChecked(
|
def onShowPasswordCheckBoxChecked(
|
||||||
self,
|
self,
|
||||||
@@ -792,20 +785,6 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
self.RoomComboBox.addItems(self.__floor_room_map[floor])
|
self.RoomComboBox.addItems(self.__floor_room_map[floor])
|
||||||
self.RoomComboBox.setCurrentIndex(0)
|
self.RoomComboBox.setCurrentIndex(0)
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def onSeatMapWidgetClosed(
|
|
||||||
self,
|
|
||||||
selected_seats: list[str]
|
|
||||||
):
|
|
||||||
|
|
||||||
self.__seat_map_widget.seatMapWidgetClosed.disconnect(self.onSeatMapWidgetClosed)
|
|
||||||
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))
|
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onSelectSeatsButtonClicked(
|
def onSelectSeatsButtonClicked(
|
||||||
self
|
self
|
||||||
@@ -815,18 +794,19 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
room = self.RoomComboBox.currentText()
|
room = self.RoomComboBox.currentText()
|
||||||
floor_idx = self.__floor_rmap[floor]
|
floor_idx = self.__floor_rmap[floor]
|
||||||
room_idx = self.__room_rmap[room]
|
room_idx = self.__room_rmap[room]
|
||||||
if self.__seat_map_widget is None:
|
dialog = ALSeatMapSelectDialog(
|
||||||
self.__seat_map_widget = ALSeatMapWidget(
|
self,
|
||||||
self,
|
floor,
|
||||||
floor,
|
room,
|
||||||
room,
|
ALSeatMapTable[floor_idx][room_idx]
|
||||||
seats_maps[floor_idx][room_idx]
|
)
|
||||||
)
|
dialog.selectSeats(self.SeatIDEdit.text().split(","))
|
||||||
self.__seat_map_widget.seatMapWidgetClosed.connect(self.onSeatMapWidgetClosed)
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
self.__seat_map_widget.show()
|
selected_seats = dialog.getSelectedSeats()
|
||||||
self.__seat_map_widget.raise_()
|
if len(selected_seats) == 0:
|
||||||
self.__seat_map_widget.activateWindow()
|
self.SeatIDEdit.clear()
|
||||||
self.__seat_map_widget.selectSeats(self.SeatIDEdit.text().split(","))
|
return
|
||||||
|
self.SeatIDEdit.setText(",".join(dialog.getSelectedSeats()))
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onUserTreeWidgetCurrentItemChanged(
|
def onUserTreeWidgetCurrentItemChanged(
|
||||||
@@ -838,8 +818,8 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
# cant effectively update the data of each user, due to the
|
# cant effectively update the data of each user, due to the
|
||||||
# possiblity of frequency edit. we just let the QListWidget
|
# possiblity of frequency edit. we just let the QListWidget
|
||||||
# help us.
|
# help us.
|
||||||
if previous and previous.type() == TreeItemType.USER.value:
|
if previous and previous.type() == ALUserTreeItemType.USER.value:
|
||||||
user = self.collectUserFromUserInfoWidget()
|
user = self.collectUserFromWidget()
|
||||||
if user:
|
if user:
|
||||||
self.UsernameEdit.textEdited.disconnect()
|
self.UsernameEdit.textEdited.disconnect()
|
||||||
user["enabled"] = previous.checkState(1) == Qt.Checked
|
user["enabled"] = previous.checkState(1) == Qt.Checked
|
||||||
@@ -847,15 +827,15 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
previous.setText(1, "" if user.get("enabled", True) else "跳过")
|
previous.setText(1, "" if user.get("enabled", True) else "跳过")
|
||||||
previous.setData(0, Qt.UserRole, user)
|
previous.setData(0, Qt.UserRole, user)
|
||||||
if current is None:
|
if current is None:
|
||||||
self.initilizeUserInfoWidget()
|
self.initializeUserInfoWidget()
|
||||||
return
|
return
|
||||||
if current.type() == TreeItemType.USER.value:
|
if current.type() == ALUserTreeItemType.USER.value:
|
||||||
user = current.data(0, Qt.UserRole)
|
user = current.data(0, Qt.UserRole)
|
||||||
if user:
|
if user:
|
||||||
self.setUserToWidget(user)
|
self.setUserToWidget(user)
|
||||||
self.UsernameEdit.textEdited.connect(lambda text: current.setText(0, text))
|
self.UsernameEdit.textEdited.connect(lambda text: current.setText(0, text))
|
||||||
else:
|
else:
|
||||||
self.initilizeUserInfoWidget()
|
self.initializeUserInfoWidget()
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onUserTreeWidgetItemChanged(
|
def onUserTreeWidgetItemChanged(
|
||||||
@@ -868,7 +848,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
return
|
return
|
||||||
if column != 1:
|
if column != 1:
|
||||||
return
|
return
|
||||||
if item.type() == TreeItemType.GROUP.value:
|
if item.type() == ALUserTreeItemType.GROUP.value:
|
||||||
is_checked = item.checkState(1) == Qt.CheckState.Checked
|
is_checked = item.checkState(1) == Qt.CheckState.Checked
|
||||||
for i in range(item.childCount()):
|
for i in range(item.childCount()):
|
||||||
child = item.child(i)
|
child = item.child(i)
|
||||||
@@ -933,7 +913,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
menu = QMenu(self.UserTreeWidget)
|
menu = QMenu(self.UserTreeWidget)
|
||||||
if current_item is None:
|
if current_item is None:
|
||||||
self.showTreeMenu(menu)
|
self.showTreeMenu(menu)
|
||||||
elif current_item.type() == TreeItemType.GROUP.value:
|
elif current_item.type() == ALUserTreeItemType.GROUP.value:
|
||||||
self.showGroupMenu(menu, current_item)
|
self.showGroupMenu(menu, current_item)
|
||||||
else:
|
else:
|
||||||
self.showUserMenu(menu, current_item)
|
self.showUserMenu(menu, current_item)
|
||||||
@@ -982,9 +962,27 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
)[0]
|
)[0]
|
||||||
if run_config_path:
|
if run_config_path:
|
||||||
run_config_path = QDir.toNativeSeparators(run_config_path)
|
run_config_path = QDir.toNativeSeparators(run_config_path)
|
||||||
if self.loadConfig(run_config_path):
|
data = self.loadRunConfig(run_config_path)
|
||||||
|
if data is not None:
|
||||||
|
self.__config_data["run"].update(data)
|
||||||
|
self.setRunConfigToWidget(data)
|
||||||
self.__config_paths["run"] = run_config_path
|
self.__config_paths["run"] = run_config_path
|
||||||
self.CurrentRunConfigEdit.setText(run_config_path)
|
self.CurrentRunConfigEdit.setText(run_config_path)
|
||||||
|
paths = self.__cfg_mgr.get(ConfigType.GLOBAL, "automation.run_path.paths", [])
|
||||||
|
if run_config_path not in paths:
|
||||||
|
paths.append(run_config_path)
|
||||||
|
index = len(paths) - 1
|
||||||
|
else:
|
||||||
|
index = paths.index(run_config_path)
|
||||||
|
self.__cfg_mgr.set(ConfigType.GLOBAL, "automation.run_path", {"current": index, "paths": paths})
|
||||||
|
else:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"警告 - AutoLibrary",
|
||||||
|
"运行配置文件读取发生错误 ! :\n"\
|
||||||
|
"无法从选择的运行配置文件中加载数据 ! :\n"\
|
||||||
|
"可能选择了错误的配置文件类型"
|
||||||
|
)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onBrowseCurrentUserConfigButtonClicked(
|
def onBrowseCurrentUserConfigButtonClicked(
|
||||||
@@ -999,9 +997,27 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
)[0]
|
)[0]
|
||||||
if user_config_path:
|
if user_config_path:
|
||||||
user_config_path = QDir.toNativeSeparators(user_config_path)
|
user_config_path = QDir.toNativeSeparators(user_config_path)
|
||||||
if self.loadConfig(user_config_path):
|
data = self.loadUserConfig(user_config_path)
|
||||||
|
if data is not None:
|
||||||
|
self.__config_data["user"].update(data)
|
||||||
|
self.setUsersToTreeWidget(data)
|
||||||
self.__config_paths["user"] = user_config_path
|
self.__config_paths["user"] = user_config_path
|
||||||
self.CurrentUserConfigEdit.setText(user_config_path)
|
self.CurrentUserConfigEdit.setText(user_config_path)
|
||||||
|
paths = self.__cfg_mgr.get(ConfigType.GLOBAL, "automation.user_path.paths", [])
|
||||||
|
if user_config_path not in paths:
|
||||||
|
paths.append(user_config_path)
|
||||||
|
index = len(paths) - 1
|
||||||
|
else:
|
||||||
|
index = paths.index(user_config_path)
|
||||||
|
self.__cfg_mgr.set(ConfigType.GLOBAL, "automation.user_path", {"current": index, "paths": paths})
|
||||||
|
else:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"警告 - AutoLibrary",
|
||||||
|
"用户配置文件读取发生错误 ! :\n"\
|
||||||
|
"无法从选择的用户配置文件中加载数据 ! :\n"\
|
||||||
|
"可能选择了错误的配置文件类型"
|
||||||
|
)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onBrowseExportRunConfigButtonClicked(
|
def onBrowseExportRunConfigButtonClicked(
|
||||||
@@ -1088,9 +1104,9 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
if run_exists or user_exists:
|
if run_exists or user_exists:
|
||||||
exist_files = []
|
exist_files = []
|
||||||
if run_exists:
|
if run_exists:
|
||||||
exist_files.append(run_config_path)
|
exist_files.append(f"运行配置文件: \n{run_config_path}")
|
||||||
if user_exists:
|
if user_exists:
|
||||||
exist_files.append(user_config_path)
|
exist_files.append(f"用户配置文件: \n{user_config_path}")
|
||||||
reply = QMessageBox.information(
|
reply = QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
"提示 - AutoLibrary",
|
"提示 - AutoLibrary",
|
||||||
@@ -1106,8 +1122,8 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
"run": run_config_path,
|
"run": run_config_path,
|
||||||
"user": user_config_path
|
"user": user_config_path
|
||||||
}
|
}
|
||||||
self.initlizeConfigToWidget("run", self.__config_data["run"])
|
self.initializeConfigToWidget("run", self.__config_data["run"])
|
||||||
self.initlizeConfigToWidget("user", self.__config_data["user"])
|
self.initializeConfigToWidget("user", self.__config_data["user"])
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onConfirmButtonClicked(
|
def onConfirmButtonClicked(
|
||||||
@@ -1115,7 +1131,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
):
|
):
|
||||||
|
|
||||||
current_item = self.UserTreeWidget.currentItem()
|
current_item = self.UserTreeWidget.currentItem()
|
||||||
if current_item and current_item.type() == TreeItemType.USER.value:
|
if current_item and current_item.type() == ALUserTreeItemType.USER.value:
|
||||||
self.UserTreeWidget.setCurrentItem(None)
|
self.UserTreeWidget.setCurrentItem(None)
|
||||||
if self.saveConfigs(
|
if self.saveConfigs(
|
||||||
self.__config_paths["run"],
|
self.__config_paths["run"],
|
||||||
@@ -1124,7 +1140,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
"提示 - AutoLibrary",
|
"提示 - AutoLibrary",
|
||||||
"配置文件保存成功 !\n"
|
"配置文件保存成功 ! :\n"
|
||||||
f"运行配置文件路径: \n{self.__config_paths['run']}\n"\
|
f"运行配置文件路径: \n{self.__config_paths['run']}\n"\
|
||||||
f"用户配置文件路径: \n{self.__config_paths['user']}"
|
f"用户配置文件路径: \n{self.__config_paths['user']}"
|
||||||
)
|
)
|
||||||
@@ -1132,7 +1148,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
|
|||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"警告 - AutoLibrary",
|
"警告 - AutoLibrary",
|
||||||
"配置文件保存失败, 请检查文件路径权限"
|
"配置文件保存失败 !\n"
|
||||||
)
|
)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|||||||
+70
-84
@@ -7,31 +7,35 @@ 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.
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||||
See the LICENSE file for details.
|
See the LICENSE file for details.
|
||||||
"""
|
"""
|
||||||
import sys
|
import os
|
||||||
import time
|
|
||||||
import queue
|
import queue
|
||||||
|
|
||||||
from PySide6.QtCore import (
|
from PySide6.QtCore import (
|
||||||
Qt, Signal, Slot, QDir, QFileInfo, QTimer, QUrl,
|
Qt, Signal, Slot, QTimer, QDir, QUrl,
|
||||||
)
|
)
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QMainWindow, QMenu, QSystemTrayIcon
|
QMainWindow, QMenu, QSystemTrayIcon, QMessageBox
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import (
|
from PySide6.QtGui import (
|
||||||
QTextCursor, QCloseEvent, QFont, QIcon, QDesktopServices
|
QTextCursor, QCloseEvent, QFont, QIcon, QDesktopServices
|
||||||
)
|
)
|
||||||
|
|
||||||
from gui.Ui_ALMainWindow import Ui_ALMainWindow
|
from base.MsgBase import MsgBase
|
||||||
|
|
||||||
|
from utils.ConfigManager import ConfigType, instance
|
||||||
|
from utils.ConfigManager import getValidateAutomationConfigPaths
|
||||||
|
|
||||||
|
from gui.resources.ui.Ui_ALMainWindow import Ui_ALMainWindow
|
||||||
|
from gui.resources import ALResource
|
||||||
from gui.ALConfigWidget import ALConfigWidget
|
from gui.ALConfigWidget import ALConfigWidget
|
||||||
from gui.ALTimerTaskWidget import ALTimerTaskWidget
|
from gui.ALTimerTaskManageWidget import ALTimerTaskManageWidget
|
||||||
from gui.ALAboutDialog import ALAboutDialog
|
from gui.ALAboutDialog import ALAboutDialog
|
||||||
from gui.ALMainWorkers import TimerTaskWorker, AutoLibWorker
|
from gui.ALMainWorkers import TimerTaskWorker, AutoLibWorker
|
||||||
|
|
||||||
from gui import AutoLibraryResource
|
|
||||||
|
|
||||||
|
class ALMainWindow(MsgBase, QMainWindow, Ui_ALMainWindow):
|
||||||
|
|
||||||
class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
# signal : timer task
|
||||||
|
|
||||||
timerTaskIsRunning = Signal(dict)
|
timerTaskIsRunning = Signal(dict)
|
||||||
timerTaskIsExecuted = Signal(dict)
|
timerTaskIsExecuted = Signal(dict)
|
||||||
timerTaskIsError = Signal(dict)
|
timerTaskIsError = Signal(dict)
|
||||||
@@ -40,19 +44,12 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
super().__init__()
|
MsgBase.__init__(self, queue.Queue(), queue.Queue())
|
||||||
self.__class_name = self.__class__.__name__
|
QMainWindow.__init__(self)
|
||||||
self.__input_queue = queue.Queue()
|
self.__cfg_mgr = instance()
|
||||||
self.__output_queue = queue.Queue()
|
|
||||||
self.__timer_task_queue = queue.Queue()
|
self.__timer_task_queue = queue.Queue()
|
||||||
script_path = sys.executable
|
self.__config_paths = getValidateAutomationConfigPaths()
|
||||||
script_dir = QFileInfo(script_path).absoluteDir()
|
self.__alTimerTaskManageWidget = None
|
||||||
self.__config_paths = {
|
|
||||||
"run": QDir.toNativeSeparators(script_dir.absoluteFilePath("run.json")),
|
|
||||||
"user": QDir.toNativeSeparators(script_dir.absoluteFilePath("user.json")),
|
|
||||||
"timer_task": QDir.toNativeSeparators(script_dir.absoluteFilePath("timer_task.json")),
|
|
||||||
}
|
|
||||||
self.__alTimerTaskWidget = None
|
|
||||||
self.__alConfigWidget = None
|
self.__alConfigWidget = None
|
||||||
self.__auto_lib_thread = None
|
self.__auto_lib_thread = None
|
||||||
self.__current_timer_task_thread = None
|
self.__current_timer_task_thread = None
|
||||||
@@ -77,13 +74,24 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
self.AboutAction.triggered.connect(self.onAboutActionTriggered)
|
self.AboutAction.triggered.connect(self.onAboutActionTriggered)
|
||||||
|
|
||||||
# initialize timer task widget, but not show it
|
# initialize timer task widget, but not show it
|
||||||
self.__alTimerTaskWidget = ALTimerTaskWidget(self, self.__config_paths["timer_task"])
|
try:
|
||||||
self.timerTaskIsRunning.connect(self.__alTimerTaskWidget.onTimerTaskIsRunning)
|
self.__alTimerTaskManageWidget = ALTimerTaskManageWidget(self)
|
||||||
self.timerTaskIsExecuted.connect(self.__alTimerTaskWidget.onTimerTaskIsExecuted)
|
except Exception as e:
|
||||||
self.timerTaskIsError.connect(self.__alTimerTaskWidget.onTimerTaskIsError)
|
QMessageBox.critical(
|
||||||
self.__alTimerTaskWidget.timerTaskIsReady.connect(self.onTimerTaskIsReady)
|
self,
|
||||||
self.__alTimerTaskWidget.timerTaskWidgetClosed.connect(self.onTimerTaskWidgetClosed)
|
"错误 - AutoLibrary",
|
||||||
self.__alTimerTaskWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint)
|
f"初始化定时任务功能失败: \n{e}"
|
||||||
|
)
|
||||||
|
self.__alTimerTaskManageWidget = None
|
||||||
|
self.TimerTaskManageWidgetButton.setEnabled(False)
|
||||||
|
self.TimerTaskManageWidgetButton.setToolTip("定时任务功能初始化失败, 请检查配置文件。")
|
||||||
|
return
|
||||||
|
self.timerTaskIsRunning.connect(self.__alTimerTaskManageWidget.onTimerTaskIsRunning)
|
||||||
|
self.timerTaskIsExecuted.connect(self.__alTimerTaskManageWidget.onTimerTaskIsExecuted)
|
||||||
|
self.timerTaskIsError.connect(self.__alTimerTaskManageWidget.onTimerTaskIsError)
|
||||||
|
self.__alTimerTaskManageWidget.timerTaskIsReady.connect(self.onTimerTaskIsReady)
|
||||||
|
self.__alTimerTaskManageWidget.timerTaskManageWidgetIsClosed.connect(self.onTimerTaskManageWidgetClosed)
|
||||||
|
self.__alTimerTaskManageWidget.setWindowFlags(Qt.WindowType.Window|Qt.WindowType.WindowCloseButtonHint)
|
||||||
|
|
||||||
|
|
||||||
def onAboutActionTriggered(
|
def onAboutActionTriggered(
|
||||||
@@ -98,7 +106,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
url = QUrl("https://www.autolibrary.cv/docs/manual_lists.html")
|
url = QUrl("https://www.autolibrary.top/manuals")
|
||||||
QDesktopServices.openUrl(url)
|
QDesktopServices.openUrl(url)
|
||||||
|
|
||||||
|
|
||||||
@@ -116,7 +124,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
|
|
||||||
self.TrayMenu = QMenu()
|
self.TrayMenu = QMenu()
|
||||||
self.TrayMenu.addAction("显示主窗口", self.showNormal)
|
self.TrayMenu.addAction("显示主窗口", self.showNormal)
|
||||||
self.TrayMenu.addAction("显示定时窗口", self.onTimerTaskWidgetButtonClicked)
|
self.TrayMenu.addAction("显示定时窗口", self.onTimerTaskManageWidgetButtonClicked)
|
||||||
self.TrayMenu.addAction("最小化到托盘", self.hideToTray)
|
self.TrayMenu.addAction("最小化到托盘", self.hideToTray)
|
||||||
self.TrayMenu.addSeparator()
|
self.TrayMenu.addSeparator()
|
||||||
self.TrayMenu.addAction("退出", self.close)
|
self.TrayMenu.addAction("退出", self.close)
|
||||||
@@ -154,7 +162,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
):
|
):
|
||||||
|
|
||||||
self.ConfigButton.clicked.connect(self.onConfigButtonClicked)
|
self.ConfigButton.clicked.connect(self.onConfigButtonClicked)
|
||||||
self.TimerTaskWidgetButton.clicked.connect(self.onTimerTaskWidgetButtonClicked)
|
self.TimerTaskManageWidgetButton.clicked.connect(self.onTimerTaskManageWidgetButtonClicked)
|
||||||
self.StartButton.clicked.connect(self.onStartButtonClicked)
|
self.StartButton.clicked.connect(self.onStartButtonClicked)
|
||||||
self.StopButton.clicked.connect(self.onStopButtonClicked)
|
self.StopButton.clicked.connect(self.onStopButtonClicked)
|
||||||
self.SendButton.clicked.connect(self.onSendButtonClicked)
|
self.SendButton.clicked.connect(self.onSendButtonClicked)
|
||||||
@@ -173,9 +181,9 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
if self.__is_running_timer_task:
|
if self.__is_running_timer_task:
|
||||||
self.__current_timer_task_thread.wait(2000)
|
self.__current_timer_task_thread.wait(2000)
|
||||||
self.__current_timer_task_thread.deleteLater()
|
self.__current_timer_task_thread.deleteLater()
|
||||||
if self.__alTimerTaskWidget:
|
if self.__alTimerTaskManageWidget:
|
||||||
self.__alTimerTaskWidget.close()
|
self.__alTimerTaskManageWidget.close()
|
||||||
self.__alTimerTaskWidget.deleteLater()
|
self.__alTimerTaskManageWidget.deleteLater()
|
||||||
if self.__alConfigWidget:
|
if self.__alConfigWidget:
|
||||||
self.__alConfigWidget.close()
|
self.__alConfigWidget.close()
|
||||||
# the config widget is already deleted in the 'self.onConfigWidgetClosed'
|
# the config widget is already deleted in the 'self.onConfigWidgetClosed'
|
||||||
@@ -226,7 +234,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
self.timerTaskIsRunning.emit(timer_task)
|
self.timerTaskIsRunning.emit(timer_task)
|
||||||
self.__timer_task_timer.stop()
|
self.__timer_task_timer.stop()
|
||||||
self.__is_running_timer_task = True
|
self.__is_running_timer_task = True
|
||||||
self.setControlButtons(True, True, False)
|
self.setControlButtons(None, True, False)
|
||||||
if not timer_task["silent"]:
|
if not timer_task["silent"]:
|
||||||
self.TrayIcon.showMessage(
|
self.TrayIcon.showMessage(
|
||||||
"定时任务 - AutoLibrary",
|
"定时任务 - AutoLibrary",
|
||||||
@@ -237,11 +245,11 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
self.showNormal()
|
self.showNormal()
|
||||||
self.__current_timer_task_thread = TimerTaskWorker(
|
self.__current_timer_task_thread = TimerTaskWorker(
|
||||||
timer_task,
|
timer_task,
|
||||||
self.__input_queue,
|
self._input_queue,
|
||||||
self.__output_queue,
|
self._output_queue,
|
||||||
self.__config_paths
|
self.__config_paths
|
||||||
)
|
)
|
||||||
self.__current_timer_task_thread.finishedSignal_TimerWorker.connect(self.onTimerTaskFinished)
|
self.__current_timer_task_thread.timerTaskWorkerIsFinished.connect(self.onTimerTaskFinished)
|
||||||
self.__current_timer_task_thread.start()
|
self.__current_timer_task_thread.start()
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
self.__is_running_timer_task = False
|
self.__is_running_timer_task = False
|
||||||
@@ -263,23 +271,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
if start_button_enabled is not None:
|
if start_button_enabled is not None:
|
||||||
self.StartButton.setEnabled(start_button_enabled)
|
self.StartButton.setEnabled(start_button_enabled)
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def showMsg(
|
|
||||||
self,
|
|
||||||
msg: str
|
|
||||||
):
|
|
||||||
|
|
||||||
self.__output_queue.put(f"[{self.__class_name:<15}] >>> : {msg}")
|
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def showTrace(
|
|
||||||
self,
|
|
||||||
msg: str
|
|
||||||
):
|
|
||||||
|
|
||||||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
|
||||||
self.__output_queue.put(f"{timestamp}-[{self.__class_name:<15}] : {msg}")
|
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def pollMsgQueue(
|
def pollMsgQueue(
|
||||||
self
|
self
|
||||||
@@ -287,30 +278,28 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
msg = self.__output_queue.get_nowait()
|
msg = self._output_queue.get_nowait()
|
||||||
self.appendToTextEdit(msg)
|
self.appendToTextEdit(msg)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onTimerTaskWidgetClosed(
|
def onTimerTaskManageWidgetClosed(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
self.TimerTaskWidgetButton.setEnabled(True)
|
self.TimerTaskManageWidgetButton.setEnabled(True)
|
||||||
|
|
||||||
@Slot(dict)
|
@Slot(dict)
|
||||||
def onConfigWidgetClosed(
|
def onConfigWidgetClosed(
|
||||||
self,
|
self
|
||||||
config_paths: dict
|
|
||||||
):
|
):
|
||||||
|
|
||||||
if self.__alConfigWidget:
|
if self.__alConfigWidget:
|
||||||
self.__alConfigWidget.configWidgetCloseSingal.disconnect(self.onConfigWidgetClosed)
|
self.__alConfigWidget.configWidgetIsClosed.disconnect(self.onConfigWidgetClosed)
|
||||||
self.__alConfigWidget.deleteLater()
|
self.__alConfigWidget.deleteLater()
|
||||||
self.__alConfigWidget = None
|
self.__alConfigWidget = None
|
||||||
self.setControlButtons(True, None, None)
|
self.setControlButtons(True, None, None)
|
||||||
self.__config_paths = config_paths
|
|
||||||
|
|
||||||
@Slot(dict)
|
@Slot(dict)
|
||||||
def onTimerTaskIsReady(
|
def onTimerTaskIsReady(
|
||||||
@@ -328,7 +317,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
):
|
):
|
||||||
|
|
||||||
self.__current_timer_task_thread.wait(1000)
|
self.__current_timer_task_thread.wait(1000)
|
||||||
self.__current_timer_task_thread.finishedSignal_TimerWorker.disconnect(self.onTimerTaskFinished)
|
self.__current_timer_task_thread.timerTaskWorkerIsFinished.disconnect(self.onTimerTaskFinished)
|
||||||
self.__current_timer_task_thread.deleteLater()
|
self.__current_timer_task_thread.deleteLater()
|
||||||
self.__current_timer_task_thread = None
|
self.__current_timer_task_thread = None
|
||||||
self.setControlButtons(None, False, True)
|
self.setControlButtons(None, False, True)
|
||||||
@@ -341,7 +330,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
QSystemTrayIcon.MessageIcon.Information,
|
QSystemTrayIcon.MessageIcon.Information,
|
||||||
1000
|
1000
|
||||||
)
|
)
|
||||||
self.showTrace(
|
self._showTrace(
|
||||||
f"定时任务 {timer_task['name']} 执行{'失败' if is_error else '完成'}, uuid: {timer_task['task_uuid']}"
|
f"定时任务 {timer_task['name']} 执行{'失败' if is_error else '完成'}, uuid: {timer_task['task_uuid']}"
|
||||||
)
|
)
|
||||||
if not is_error:
|
if not is_error:
|
||||||
@@ -350,14 +339,14 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
self.timerTaskIsError.emit(timer_task)
|
self.timerTaskIsError.emit(timer_task)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onTimerTaskWidgetButtonClicked(
|
def onTimerTaskManageWidgetButtonClicked(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
self.__alTimerTaskWidget.show()
|
self.__alTimerTaskManageWidget.show()
|
||||||
self.__alTimerTaskWidget.raise_()
|
self.__alTimerTaskManageWidget.raise_()
|
||||||
self.__alTimerTaskWidget.activateWindow()
|
self.__alTimerTaskManageWidget.activateWindow()
|
||||||
self.TimerTaskWidgetButton.setEnabled(False)
|
self.TimerTaskManageWidgetButton.setEnabled(False)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onConfigButtonClicked(
|
def onConfigButtonClicked(
|
||||||
@@ -365,11 +354,8 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
):
|
):
|
||||||
|
|
||||||
if self.__alConfigWidget is None:
|
if self.__alConfigWidget is None:
|
||||||
self.__alConfigWidget = ALConfigWidget(
|
self.__alConfigWidget = ALConfigWidget(self)
|
||||||
self,
|
self.__alConfigWidget.configWidgetIsClosed.connect(self.onConfigWidgetClosed)
|
||||||
self.__config_paths
|
|
||||||
)
|
|
||||||
self.__alConfigWidget.configWidgetCloseSingal.connect(self.onConfigWidgetClosed)
|
|
||||||
self.__alConfigWidget.show()
|
self.__alConfigWidget.show()
|
||||||
self.__alConfigWidget.raise_()
|
self.__alConfigWidget.raise_()
|
||||||
self.__alConfigWidget.activateWindow()
|
self.__alConfigWidget.activateWindow()
|
||||||
@@ -383,12 +369,12 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
self.setControlButtons(None, True, False)
|
self.setControlButtons(None, True, False)
|
||||||
if self.__auto_lib_thread is None:
|
if self.__auto_lib_thread is None:
|
||||||
self.__auto_lib_thread = AutoLibWorker(
|
self.__auto_lib_thread = AutoLibWorker(
|
||||||
self.__input_queue,
|
self._input_queue,
|
||||||
self.__output_queue,
|
self._output_queue,
|
||||||
self.__config_paths
|
self.__config_paths
|
||||||
)
|
)
|
||||||
self.__auto_lib_thread.finishedSignal.connect(self.onStopButtonClicked)
|
self.__auto_lib_thread.autoLibWorkerIsFinished.connect(self.onStopButtonClicked)
|
||||||
self.__auto_lib_thread.finishedWithErrorSignal.connect(self.onStopButtonClicked)
|
self.__auto_lib_thread.autoLibWorkerFinishedWithError.connect(self.onStopButtonClicked)
|
||||||
self.__auto_lib_thread.start()
|
self.__auto_lib_thread.start()
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
@@ -397,11 +383,11 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
):
|
):
|
||||||
|
|
||||||
if self.__auto_lib_thread:
|
if self.__auto_lib_thread:
|
||||||
self.showTrace("正在停止操作......")
|
self._showTrace("正在停止操作......")
|
||||||
self.__auto_lib_thread.wait(2000)
|
self.__auto_lib_thread.wait(2000)
|
||||||
self.showTrace("操作已停止")
|
self._showTrace("操作已停止")
|
||||||
self.__auto_lib_thread.finishedSignal.disconnect(self.onStopButtonClicked)
|
self.__auto_lib_thread.autoLibWorkerIsFinished.disconnect(self.onStopButtonClicked)
|
||||||
self.__auto_lib_thread.finishedWithErrorSignal.disconnect(self.onStopButtonClicked)
|
self.__auto_lib_thread.autoLibWorkerFinishedWithError.disconnect(self.onStopButtonClicked)
|
||||||
self.__auto_lib_thread.deleteLater()
|
self.__auto_lib_thread.deleteLater()
|
||||||
self.__auto_lib_thread = None
|
self.__auto_lib_thread = None
|
||||||
self.setControlButtons(None, False, True)
|
self.setControlButtons(None, False, True)
|
||||||
@@ -414,6 +400,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
|
|||||||
msg = self.MessageEdit.text().strip()
|
msg = self.MessageEdit.text().strip()
|
||||||
if not msg:
|
if not msg:
|
||||||
return
|
return
|
||||||
self.showMsg(msg)
|
self._showMsg(msg)
|
||||||
self.__input_queue.put(msg) # put message to input queue
|
self._input_queue.put(msg) # put message to input queue
|
||||||
self.MessageEdit.clear()
|
self.MessageEdit.clear()
|
||||||
+19
-19
@@ -17,13 +17,13 @@ from PySide6.QtCore import (
|
|||||||
|
|
||||||
from base.MsgBase import MsgBase
|
from base.MsgBase import MsgBase
|
||||||
from operators.AutoLib import AutoLib
|
from operators.AutoLib import AutoLib
|
||||||
from utils.ConfigReader import ConfigReader
|
from utils.JSONReader import JSONReader
|
||||||
|
|
||||||
|
|
||||||
class AutoLibWorker(QThread, MsgBase):
|
class AutoLibWorker(MsgBase, QThread):
|
||||||
|
|
||||||
finishedSignal = Signal()
|
autoLibWorkerIsFinished = Signal()
|
||||||
finishedWithErrorSignal = Signal()
|
autoLibWorkerFinishedWithError = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -32,8 +32,8 @@ class AutoLibWorker(QThread, MsgBase):
|
|||||||
config_paths: dict
|
config_paths: dict
|
||||||
):
|
):
|
||||||
|
|
||||||
super().__init__(input_queue=input_queue, output_queue=output_queue)
|
MsgBase.__init__(self, input_queue, output_queue)
|
||||||
|
QThread.__init__(self)
|
||||||
self.__config_paths = config_paths
|
self.__config_paths = config_paths
|
||||||
|
|
||||||
|
|
||||||
@@ -69,11 +69,11 @@ class AutoLibWorker(QThread, MsgBase):
|
|||||||
self._showTrace(
|
self._showTrace(
|
||||||
f"正在加载配置文件, 运行配置文件路径: {self.__config_paths["run"]}"
|
f"正在加载配置文件, 运行配置文件路径: {self.__config_paths["run"]}"
|
||||||
)
|
)
|
||||||
self.__run_config = ConfigReader(self.__config_paths["run"]).getConfigs()
|
self.__run_config = JSONReader(self.__config_paths["run"]).data()
|
||||||
self._showTrace(
|
self._showTrace(
|
||||||
f"正在加载配置文件, 用户配置文件路径: {self.__config_paths["user"]}"
|
f"正在加载配置文件, 用户配置文件路径: {self.__config_paths["user"]}"
|
||||||
)
|
)
|
||||||
self.__user_config = ConfigReader(self.__config_paths["user"]).getConfigs()
|
self.__user_config = JSONReader(self.__config_paths["user"]).data()
|
||||||
if self.__run_config is None or self.__user_config is None:
|
if self.__run_config is None or self.__user_config is None:
|
||||||
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
|
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
|
||||||
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
|
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
|
||||||
@@ -116,17 +116,17 @@ class AutoLibWorker(QThread, MsgBase):
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._showTrace(f"AutoLibrary 运行时发生异常 : {e}")
|
self._showTrace(f"AutoLibrary 运行时发生异常 : {e}")
|
||||||
self.finishedWithErrorSignal.emit()
|
self.autoLibWorkerFinishedWithError.emit()
|
||||||
return
|
return
|
||||||
if auto_lib:
|
if auto_lib:
|
||||||
auto_lib.close()
|
auto_lib.close()
|
||||||
self._showTrace("AutoLibrary 运行结束")
|
self._showTrace("AutoLibrary 运行结束")
|
||||||
self.finishedSignal.emit()
|
self.autoLibWorkerIsFinished.emit()
|
||||||
|
|
||||||
|
|
||||||
class TimerTaskWorker(AutoLibWorker):
|
class TimerTaskWorker(AutoLibWorker):
|
||||||
|
|
||||||
finishedSignal_TimerWorker = Signal(bool, dict)
|
timerTaskWorkerIsFinished = Signal(bool, dict)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -137,10 +137,10 @@ class TimerTaskWorker(AutoLibWorker):
|
|||||||
):
|
):
|
||||||
|
|
||||||
super().__init__(input_queue, output_queue, config_paths)
|
super().__init__(input_queue, output_queue, config_paths)
|
||||||
|
|
||||||
self.__timer_task = timer_task
|
self.__timer_task = timer_task
|
||||||
self.finishedSignal.connect(self.onTimerTaskIsFinished)
|
|
||||||
self.finishedWithErrorSignal.connect(self.onTimerTaskIsError)
|
self.autoLibWorkerIsFinished.connect(self.onTimerTaskIsFinished)
|
||||||
|
self.autoLibWorkerFinishedWithError.connect(self.onTimerTaskFinishedWithError)
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
self
|
self
|
||||||
@@ -149,18 +149,18 @@ class TimerTaskWorker(AutoLibWorker):
|
|||||||
self._showTrace(f"定时任务 {self.__timer_task['name']} 开始运行")
|
self._showTrace(f"定时任务 {self.__timer_task['name']} 开始运行")
|
||||||
super().run()
|
super().run()
|
||||||
|
|
||||||
@Slot(dict)
|
@Slot()
|
||||||
def onTimerTaskIsError(
|
def onTimerTaskFinishedWithError(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
self._showTrace(f"定时任务 {self.__timer_task['name']} 运行时发生异常")
|
self._showTrace(f"定时任务 {self.__timer_task['name']} 运行时发生异常")
|
||||||
self.finishedSignal_TimerWorker.emit(True, self.__timer_task)
|
self.timerTaskWorkerIsFinished.emit(True, self.__timer_task)
|
||||||
|
|
||||||
@Slot(dict)
|
@Slot()
|
||||||
def onTimerTaskIsFinished(
|
def onTimerTaskIsFinished(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
self._showTrace(f"定时任务 {self.__timer_task['name']} 运行结束")
|
self._showTrace(f"定时任务 {self.__timer_task['name']} 运行结束")
|
||||||
self.finishedSignal_TimerWorker.emit(False, self.__timer_task)
|
self.timerTaskWorkerIsFinished.emit(False, self.__timer_task)
|
||||||
|
|||||||
+13
-12
@@ -22,7 +22,7 @@ class ALSeatFrame(QFrame):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
seat_number,
|
seat_number,
|
||||||
parent=None
|
parent = None
|
||||||
):
|
):
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -40,18 +40,19 @@ class ALSeatFrame(QFrame):
|
|||||||
self.setLineWidth(2)
|
self.setLineWidth(2)
|
||||||
self.setStyleSheet("""
|
self.setStyleSheet("""
|
||||||
QFrame {
|
QFrame {
|
||||||
background-color: #4196EB;
|
background-color: #2294FF;
|
||||||
border: 2px solid #4196EB;
|
border: 2px solid #2294FF;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #F0F0F0;
|
color: #FFFFFF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
self.label = QLabel(self.__seat_number, self)
|
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
self.label.setAlignment(Qt.AlignCenter)
|
self.Label = QLabel(self.__seat_number, self)
|
||||||
self.label.setGeometry(0, 0, 60, 40)
|
self.Label.setAlignment(Qt.AlignCenter)
|
||||||
|
self.Label.setGeometry(0, 0, 60, 40)
|
||||||
|
|
||||||
def mousePressEvent(
|
def mousePressEvent(
|
||||||
self,
|
self,
|
||||||
@@ -77,24 +78,24 @@ class ALSeatFrame(QFrame):
|
|||||||
self.setStyleSheet("""
|
self.setStyleSheet("""
|
||||||
QFrame {
|
QFrame {
|
||||||
background-color: #4CAF50;
|
background-color: #4CAF50;
|
||||||
border: 2px solid #388E3C;
|
border: 2px solid #4CAF50;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #F0F0F0;
|
color: #FFFFFF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
else:
|
else:
|
||||||
self.setStyleSheet("""
|
self.setStyleSheet("""
|
||||||
QFrame {
|
QFrame {
|
||||||
background-color: #4196EB;
|
background-color: #2294FF;
|
||||||
border: 2px solid #4196EB;
|
border: 2px solid #2294FF;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
QLabel {
|
QLabel {
|
||||||
color: #F0F0F0;
|
color: #FFFFFF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
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 PySide6.QtCore import (
|
||||||
|
Qt, Slot, Signal
|
||||||
|
)
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QDialog, QLabel, QHBoxLayout, QVBoxLayout,
|
||||||
|
QPushButton,
|
||||||
|
)
|
||||||
|
from PySide6.QtGui import (
|
||||||
|
QCloseEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
from gui.ALSeatMapView import ALSeatMapView
|
||||||
|
|
||||||
|
|
||||||
|
class ALSeatMapSelectDialog(QDialog):
|
||||||
|
|
||||||
|
seatMapSelectDialogIsClosed = Signal(list)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent: QDialog = None,
|
||||||
|
floor: str = "",
|
||||||
|
room: str = "",
|
||||||
|
seats_data: str = ""
|
||||||
|
):
|
||||||
|
|
||||||
|
super().__init__(parent)
|
||||||
|
self.__floor = floor
|
||||||
|
self.__room = room
|
||||||
|
self.__seats_data = seats_data
|
||||||
|
self.__confirmed = False
|
||||||
|
|
||||||
|
self.setupUi()
|
||||||
|
self.connectSignals()
|
||||||
|
|
||||||
|
|
||||||
|
def setupUi(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
self.setModal(True)
|
||||||
|
self.setMinimumSize(800, 600)
|
||||||
|
self.resize(800, 600)
|
||||||
|
self.setWindowTitle(f"选择楼层座位 - AutoLibrary")
|
||||||
|
|
||||||
|
self.SeatMapWidgetMainLayout = QVBoxLayout(self)
|
||||||
|
self.SeatMapWidgetMainLayout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
self.SeatMapWidgetMainLayout.setSpacing(5)
|
||||||
|
self.TitleLabel = QLabel(f"楼层座位分布图: {self.__floor}-{self.__room}")
|
||||||
|
self.TitleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.TitleLabel.setStyleSheet("font-size: 16px; font-weight: bold; margin: 10px;")
|
||||||
|
self.SeatMapWidgetMainLayout.addWidget(self.TitleLabel)
|
||||||
|
|
||||||
|
self.SeatMapGraphicsView = ALSeatMapView(None, self.__seats_data)
|
||||||
|
self.SeatMapWidgetMainLayout.addWidget(self.SeatMapGraphicsView)
|
||||||
|
|
||||||
|
self.TipsLabel = QLabel(
|
||||||
|
" 点击座位进行选择/取消选择, 最多选择1个座位 \n"
|
||||||
|
" [操作方法: Ctrl+鼠标滚轮缩放 | 滚轮/拖拽/方向键 移动]"
|
||||||
|
)
|
||||||
|
self.TipsLabel.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||||
|
self.TipsLabel.setStyleSheet("color: #666; margin: 5px;")
|
||||||
|
self.SeatMapWidgetMainLayout.addWidget(self.TipsLabel)
|
||||||
|
|
||||||
|
self.ConfirmButton = QPushButton("确认")
|
||||||
|
self.ConfirmButton.setFixedSize(80, 25)
|
||||||
|
self.ConfirmButton.setAutoDefault(True)
|
||||||
|
self.ConfirmButton.setDefault(True)
|
||||||
|
self.CancelButton = QPushButton("取消")
|
||||||
|
self.CancelButton.setFixedSize(80, 25)
|
||||||
|
self.SeatMapWidgetControlLayout = QHBoxLayout()
|
||||||
|
self.SeatMapWidgetControlLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.SeatMapWidgetControlLayout.setSpacing(5)
|
||||||
|
self.SeatMapWidgetControlLayout.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||||
|
self.SeatMapWidgetControlLayout.addWidget(self.CancelButton)
|
||||||
|
self.SeatMapWidgetControlLayout.addWidget(self.ConfirmButton)
|
||||||
|
self.SeatMapWidgetMainLayout.addLayout(self.SeatMapWidgetControlLayout)
|
||||||
|
|
||||||
|
|
||||||
|
def connectSignals(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked)
|
||||||
|
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
|
||||||
|
|
||||||
|
|
||||||
|
def showEvent(
|
||||||
|
self,
|
||||||
|
event
|
||||||
|
):
|
||||||
|
|
||||||
|
result = super().showEvent(event)
|
||||||
|
|
||||||
|
screen_rect = self.screen().geometry()
|
||||||
|
target_pos = self.parent().geometry().center()
|
||||||
|
target_pos.setX(target_pos.x() - self.width()//2)
|
||||||
|
target_pos.setY(target_pos.y() - self.height()//2)
|
||||||
|
if target_pos.x() < 0:
|
||||||
|
target_pos.setX(0)
|
||||||
|
if target_pos.x() + self.width() > screen_rect.width():
|
||||||
|
target_pos.setX(screen_rect.width() - self.width())
|
||||||
|
if target_pos.y() < 0:
|
||||||
|
target_pos.setY(0)
|
||||||
|
if target_pos.y() + self.height() > screen_rect.height():
|
||||||
|
target_pos.setY(screen_rect.height() - self.height())
|
||||||
|
self.move(target_pos)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def closeEvent(
|
||||||
|
self,
|
||||||
|
event: QCloseEvent
|
||||||
|
):
|
||||||
|
|
||||||
|
if not self.__confirmed:
|
||||||
|
self.clearSelections()
|
||||||
|
self.reject()
|
||||||
|
else:
|
||||||
|
self.accept()
|
||||||
|
self.seatMapSelectDialogIsClosed.emit(self.getSelectedSeats())
|
||||||
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
|
||||||
|
def selectSeat(
|
||||||
|
self,
|
||||||
|
seat_number: str
|
||||||
|
):
|
||||||
|
|
||||||
|
self.SeatMapGraphicsView.selectSeat(seat_number)
|
||||||
|
|
||||||
|
|
||||||
|
def selectSeats(
|
||||||
|
self,
|
||||||
|
seat_numbers: list[str]
|
||||||
|
) -> bool:
|
||||||
|
|
||||||
|
return self.SeatMapGraphicsView.selectSeats(seat_numbers)
|
||||||
|
|
||||||
|
|
||||||
|
def getSelectedSeats(
|
||||||
|
self
|
||||||
|
) -> list[str]:
|
||||||
|
|
||||||
|
return self.SeatMapGraphicsView.getSelectedSeats()
|
||||||
|
|
||||||
|
|
||||||
|
def clearSelections(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
self.SeatMapGraphicsView.clearSelections()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def onConfirmButtonClicked(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
self.__confirmed = True
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def onCancelButtonClicked(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
self.__confirmed = False
|
||||||
|
self.reject()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
seats_maps = {
|
ALSeatMapTable = {
|
||||||
"2": {
|
"2": {
|
||||||
"1": """
|
"1": """
|
||||||
,,,,,,,,,,,039A,039B,,040A,040B,,041A,041B,,042A,042B,,043A,043B,,044A,044B,,,,,,,,,
|
,,,,,,,,,,,039A,039B,,040A,040B,,041A,041B,,042A,042B,,043A,043B,,044A,044B,,,,,,,,,
|
||||||
|
|||||||
@@ -8,41 +8,32 @@ You may use, modify, and distribute this file under the terms of the MIT License
|
|||||||
See the LICENSE file for details.
|
See the LICENSE file for details.
|
||||||
"""
|
"""
|
||||||
from PySide6.QtCore import (
|
from PySide6.QtCore import (
|
||||||
Qt, Slot, Signal, QEvent
|
Qt, Slot, QEvent
|
||||||
)
|
)
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QFrame, QWidget, QLabel, QHBoxLayout, QVBoxLayout,
|
QFrame, QWidget,
|
||||||
QGridLayout, QGraphicsView, QGraphicsScene, QGraphicsItem,
|
QGridLayout, QGraphicsView, QGraphicsScene, QGraphicsItem
|
||||||
QPushButton,
|
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import (
|
from PySide6.QtGui import (
|
||||||
QPainter, QWheelEvent, QCloseEvent
|
QPainter, QWheelEvent
|
||||||
)
|
)
|
||||||
|
|
||||||
from gui.ALSeatFrame import ALSeatFrame
|
from gui.ALSeatFrame import ALSeatFrame
|
||||||
|
|
||||||
|
|
||||||
class ALSeatMapWidget(QWidget):
|
class ALSeatMapView(QGraphicsView):
|
||||||
|
|
||||||
seatMapWidgetClosed = Signal(list)
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent: QWidget = None,
|
parent: QWidget = None,
|
||||||
floor: str = "",
|
|
||||||
room: str = "",
|
|
||||||
seats_data: dict = {},
|
seats_data: dict = {},
|
||||||
):
|
):
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.__floor = floor
|
|
||||||
self.__room = room
|
|
||||||
self.__seats_data = seats_data
|
self.__seats_data = seats_data
|
||||||
self.__selected_seats = []
|
self.__selected_seats = []
|
||||||
self.__seat_frames = {}
|
self.__seat_frames = {}
|
||||||
self.__confirmed = False
|
|
||||||
|
|
||||||
self.setupUi()
|
self.setupUi()
|
||||||
self.connectSignals()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def formatSeatNumber(
|
def formatSeatNumber(
|
||||||
@@ -56,108 +47,13 @@ class ALSeatMapWidget(QWidget):
|
|||||||
return seat_number.zfill(3)
|
return seat_number.zfill(3)
|
||||||
|
|
||||||
|
|
||||||
def setupUi(
|
|
||||||
self
|
|
||||||
):
|
|
||||||
|
|
||||||
self.setWindowFlags(Qt.WindowType.Window)
|
|
||||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
|
||||||
self.setMinimumSize(800, 600)
|
|
||||||
self.resize(800, 600)
|
|
||||||
self.setWindowTitle(f"选择楼层座位 - AutoLibrary")
|
|
||||||
|
|
||||||
self.SeatMapWidgetMainLayout = QVBoxLayout(self)
|
|
||||||
self.TitleLabel = QLabel(f"楼层座位分布图: {self.__floor}-{self.__room}")
|
|
||||||
self.TitleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
||||||
self.TitleLabel.setStyleSheet("font-size: 16px; font-weight: bold; margin: 10px;")
|
|
||||||
self.SeatMapWidgetMainLayout.addWidget(self.TitleLabel)
|
|
||||||
|
|
||||||
self.SeatMapGraphicsView = QGraphicsView(self)
|
|
||||||
self.SeatMapGraphicsScene = QGraphicsScene(self)
|
|
||||||
self.SeatMapGraphicsView.setScene(self.SeatMapGraphicsScene)
|
|
||||||
self.SeatMapGraphicsView.setRenderHint(QPainter.RenderHint.LosslessImageRendering)
|
|
||||||
self.SeatMapGraphicsView.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
|
|
||||||
self.SeatMapGraphicsView.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
||||||
self.SeatMapGraphicsView.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
||||||
self.SeatMapGraphicsView.viewport().installEventFilter(self)
|
|
||||||
|
|
||||||
self.SeatsContainerWidget = QWidget()
|
|
||||||
self.SeatsContainerLayout = QGridLayout(self.SeatsContainerWidget)
|
|
||||||
self.createSeatMap()
|
|
||||||
|
|
||||||
self.ContainerProxy = self.SeatMapGraphicsScene.addWidget(self.SeatsContainerWidget)
|
|
||||||
self.ContainerProxy.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
|
|
||||||
self.SeatMapWidgetMainLayout.addWidget(self.SeatMapGraphicsView)
|
|
||||||
|
|
||||||
self.TipsLabel = QLabel(
|
|
||||||
" 点击座位进行选择/取消选择, 最多选择1个座位 \n"
|
|
||||||
" [操作方法: Ctrl+鼠标滚轮缩放 | 滚轮/拖拽/方向键 移动]"
|
|
||||||
)
|
|
||||||
self.TipsLabel.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
||||||
self.TipsLabel.setStyleSheet("color: #666; margin: 5px;")
|
|
||||||
self.SeatMapWidgetMainLayout.addWidget(self.TipsLabel)
|
|
||||||
|
|
||||||
self.ConfirmButton = QPushButton("确认")
|
|
||||||
self.ConfirmButton.setFixedSize(80, 25)
|
|
||||||
self.CancelButton = QPushButton("取消")
|
|
||||||
self.CancelButton.setFixedSize(80, 25)
|
|
||||||
self.SeatMapWidgetControlLayout = QHBoxLayout()
|
|
||||||
self.SeatMapWidgetControlLayout.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
||||||
self.SeatMapWidgetControlLayout.addWidget(self.CancelButton)
|
|
||||||
self.SeatMapWidgetControlLayout.addWidget(self.ConfirmButton)
|
|
||||||
self.SeatMapWidgetMainLayout.addLayout(self.SeatMapWidgetControlLayout)
|
|
||||||
|
|
||||||
|
|
||||||
def connectSignals(
|
|
||||||
self
|
|
||||||
):
|
|
||||||
|
|
||||||
self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked)
|
|
||||||
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
|
|
||||||
|
|
||||||
|
|
||||||
def showEvent(
|
|
||||||
self,
|
|
||||||
event
|
|
||||||
):
|
|
||||||
|
|
||||||
result = super().showEvent(event)
|
|
||||||
|
|
||||||
screen_rect = self.screen().geometry()
|
|
||||||
target_pos = self.parent().geometry().center()
|
|
||||||
target_pos.setX(target_pos.x() - self.width()//2)
|
|
||||||
target_pos.setY(target_pos.y() - self.height()//2)
|
|
||||||
if target_pos.x() < 0:
|
|
||||||
target_pos.setX(0)
|
|
||||||
if target_pos.x() + self.width() > screen_rect.width():
|
|
||||||
target_pos.setX(screen_rect.width() - self.width())
|
|
||||||
if target_pos.y() < 0:
|
|
||||||
target_pos.setY(0)
|
|
||||||
if target_pos.y() + self.height() > screen_rect.height():
|
|
||||||
target_pos.setY(screen_rect.height() - self.height())
|
|
||||||
self.move(target_pos)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def closeEvent(
|
|
||||||
self,
|
|
||||||
event: QCloseEvent
|
|
||||||
):
|
|
||||||
|
|
||||||
if not self.__confirmed:
|
|
||||||
self.clearSelections()
|
|
||||||
self.seatMapWidgetClosed.emit(self.__selected_seats)
|
|
||||||
super().closeEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
def eventFilter(
|
def eventFilter(
|
||||||
self,
|
self,
|
||||||
watched,
|
watched,
|
||||||
event
|
event
|
||||||
):
|
):
|
||||||
|
|
||||||
if (watched is self.SeatMapGraphicsView.viewport() and
|
if (watched is self.viewport() and
|
||||||
event.type() == QEvent.Type.Wheel and
|
event.type() == QEvent.Type.Wheel and
|
||||||
event.modifiers() == Qt.KeyboardModifier.ControlModifier
|
event.modifiers() == Qt.KeyboardModifier.ControlModifier
|
||||||
):
|
):
|
||||||
@@ -172,12 +68,40 @@ class ALSeatMapWidget(QWidget):
|
|||||||
):
|
):
|
||||||
|
|
||||||
delta = event.angleDelta().y()
|
delta = event.angleDelta().y()
|
||||||
|
min_scale = 0.1
|
||||||
|
max_scale = 4.0
|
||||||
|
current_scale = self.transform().m11()
|
||||||
zoom_factor = 1.2 if delta > 0 else 1/1.2
|
zoom_factor = 1.2 if delta > 0 else 1/1.2
|
||||||
self.SeatMapGraphicsView.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
target_scale = current_scale*zoom_factor
|
||||||
self.SeatMapGraphicsView.scale(zoom_factor, zoom_factor)
|
if target_scale < min_scale and delta < 0:
|
||||||
|
return
|
||||||
|
if target_scale > max_scale and delta > 0:
|
||||||
|
return
|
||||||
|
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
||||||
|
self.scale(zoom_factor, zoom_factor)
|
||||||
|
|
||||||
|
|
||||||
def createSeatMap(
|
def setupUi(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
self.SeatMapGraphicsScene = QGraphicsScene(self)
|
||||||
|
self.setScene(self.SeatMapGraphicsScene)
|
||||||
|
self.setRenderHint(QPainter.RenderHint.LosslessImageRendering)
|
||||||
|
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
|
||||||
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
|
self.viewport().installEventFilter(self)
|
||||||
|
|
||||||
|
self.SeatsContainerWidget = QWidget()
|
||||||
|
self.SeatsContainerLayout = QGridLayout(self.SeatsContainerWidget)
|
||||||
|
self.setupSeatMap()
|
||||||
|
|
||||||
|
self.ContainerProxy = self.SeatMapGraphicsScene.addWidget(self.SeatsContainerWidget)
|
||||||
|
self.ContainerProxy.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
|
||||||
|
|
||||||
|
|
||||||
|
def setupSeatMap(
|
||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
@@ -262,19 +186,3 @@ class ALSeatMapWidget(QWidget):
|
|||||||
self.__selected_seats.append(seat_number)
|
self.__selected_seats.append(seat_number)
|
||||||
else:
|
else:
|
||||||
self.__seat_frames[seat_number].toggleSelection()
|
self.__seat_frames[seat_number].toggleSelection()
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def onConfirmButtonClicked(
|
|
||||||
self
|
|
||||||
):
|
|
||||||
|
|
||||||
self.__confirmed = True
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def onCancelButtonClicked(
|
|
||||||
self
|
|
||||||
):
|
|
||||||
|
|
||||||
self.__confirmed = False
|
|
||||||
self.close()
|
|
||||||
@@ -20,10 +20,10 @@ from PySide6.QtWidgets import (
|
|||||||
QHBoxLayout, QGridLayout, QDateTimeEdit
|
QHBoxLayout, QGridLayout, QDateTimeEdit
|
||||||
)
|
)
|
||||||
|
|
||||||
from gui.Ui_ALAddTimerTaskDialog import Ui_ALAddTimerTaskDialog
|
from gui.resources.ui.Ui_ALTimerTaskAddDialog import Ui_ALTimerTaskAddDialog
|
||||||
|
|
||||||
|
|
||||||
class TimerTaskStatus(Enum):
|
class ALTimerTaskStatus(Enum):
|
||||||
|
|
||||||
PENDING = "等待中"
|
PENDING = "等待中"
|
||||||
READY = "已就绪"
|
READY = "已就绪"
|
||||||
@@ -33,7 +33,7 @@ class TimerTaskStatus(Enum):
|
|||||||
OUTDATED = "已过期"
|
OUTDATED = "已过期"
|
||||||
|
|
||||||
|
|
||||||
class ALAddTimerTaskWidget(QDialog, Ui_ALAddTimerTaskDialog):
|
class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -128,7 +128,7 @@ class ALAddTimerTaskWidget(QDialog, Ui_ALAddTimerTaskDialog):
|
|||||||
"execute_time": execute_time,
|
"execute_time": execute_time,
|
||||||
"silent": silent,
|
"silent": silent,
|
||||||
"add_time": added_time,
|
"add_time": added_time,
|
||||||
"status": TimerTaskStatus.PENDING,
|
"status": ALTimerTaskStatus.PENDING,
|
||||||
"executed": False
|
"executed": False
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,13 +8,14 @@ You may use, modify, and distribute this file under the terms of the MIT License
|
|||||||
See the LICENSE file for details.
|
See the LICENSE file for details.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from PySide6.QtCore import (
|
from PySide6.QtCore import (
|
||||||
Qt, Signal, Slot, QTimer
|
Qt, Signal, Slot, QTimer, QFileInfo, QDir
|
||||||
)
|
)
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QDialog, QWidget, QListWidgetItem, QMessageBox,
|
QDialog, QWidget, QListWidgetItem, QMessageBox,
|
||||||
@@ -24,21 +25,13 @@ from PySide6.QtGui import (
|
|||||||
QCloseEvent
|
QCloseEvent
|
||||||
)
|
)
|
||||||
|
|
||||||
from gui.Ui_ALTimerTaskWidget import Ui_ALTimerTaskWidget
|
from utils.ConfigManager import ConfigType, instance
|
||||||
from gui.ALAddTimerTaskDialog import ALAddTimerTaskWidget, TimerTaskStatus
|
|
||||||
|
|
||||||
from utils.ConfigReader import ConfigReader
|
from gui.resources.ui.Ui_ALTimerTaskManageWidget import Ui_ALTimerTaskManageWidget
|
||||||
from utils.ConfigWriter import ConfigWriter
|
from gui.ALTimerTaskAddDialog import ALTimerTaskAddDialog, ALTimerTaskStatus
|
||||||
|
|
||||||
|
|
||||||
class SortPolicy(Enum):
|
class ALTimerTaskItemWidget(QWidget):
|
||||||
|
|
||||||
BY_NAME = "按名称"
|
|
||||||
BY_ADD_TIME = "按添加时间"
|
|
||||||
BY_EXECUTE_TIME = "按执行时间"
|
|
||||||
|
|
||||||
|
|
||||||
class TimerTaskItemWidget(QWidget):
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -71,7 +64,7 @@ class TimerTaskItemWidget(QWidget):
|
|||||||
|
|
||||||
ExecuteTimeStr = self.__timer_task["execute_time"].strftime("%Y-%m-%d %H:%M:%S")
|
ExecuteTimeStr = self.__timer_task["execute_time"].strftime("%Y-%m-%d %H:%M:%S")
|
||||||
ExecuteTimeLabel = QLabel(f"执行时间: {ExecuteTimeStr}")
|
ExecuteTimeLabel = QLabel(f"执行时间: {ExecuteTimeStr}")
|
||||||
ExecuteTimeLabel.setStyleSheet("color: gray;")
|
ExecuteTimeLabel.setStyleSheet("color: #969696;")
|
||||||
ExecuteTimeLabel.setFixedHeight(20)
|
ExecuteTimeLabel.setFixedHeight(20)
|
||||||
self.TaskInfoLayout.addWidget(ExecuteTimeLabel)
|
self.TaskInfoLayout.addWidget(ExecuteTimeLabel)
|
||||||
|
|
||||||
@@ -79,29 +72,29 @@ class TimerTaskItemWidget(QWidget):
|
|||||||
self.ItemWidgetLayout.addStretch()
|
self.ItemWidgetLayout.addStretch()
|
||||||
|
|
||||||
match self.__timer_task["status"]:
|
match self.__timer_task["status"]:
|
||||||
case TimerTaskStatus.PENDING:
|
case ALTimerTaskStatus.PENDING:
|
||||||
TaskStatusText = "等待中"
|
TaskStatusText = "等待中"
|
||||||
TaskStatusColor = "#FF9800"
|
TaskStatusColor = "#FF9800"
|
||||||
case TimerTaskStatus.READY:
|
case ALTimerTaskStatus.READY:
|
||||||
TaskStatusText = "已就绪"
|
TaskStatusText = "已就绪"
|
||||||
TaskStatusColor = "#316BFF"
|
TaskStatusColor = "#316BFF"
|
||||||
case TimerTaskStatus.RUNNING:
|
case ALTimerTaskStatus.RUNNING:
|
||||||
TaskStatusText = "执行中"
|
TaskStatusText = "执行中"
|
||||||
TaskStatusColor = "#2294FF"
|
TaskStatusColor = "#2294FF"
|
||||||
case TimerTaskStatus.EXECUTED:
|
case ALTimerTaskStatus.EXECUTED:
|
||||||
TaskStatusText = "已执行"
|
TaskStatusText = "已执行"
|
||||||
TaskStatusColor = "#4CAF50"
|
TaskStatusColor = "#4CAF50"
|
||||||
case TimerTaskStatus.ERROR:
|
case ALTimerTaskStatus.ERROR:
|
||||||
TaskStatusText = "执行失败"
|
TaskStatusText = "执行失败"
|
||||||
TaskStatusColor = "#FF5722"
|
TaskStatusColor = "#DC0000"
|
||||||
case TimerTaskStatus.OUTDATED:
|
case ALTimerTaskStatus.OUTDATED:
|
||||||
TaskStatusText = "已过期"
|
TaskStatusText = "已过期"
|
||||||
TaskStatusColor = "#FF5722"
|
TaskStatusColor = "#DC0000"
|
||||||
TaskStatusLabel = QLabel(TaskStatusText)
|
TaskStatusLabel = QLabel(TaskStatusText)
|
||||||
TaskStatusLabel.setStyleSheet(f"""
|
TaskStatusLabel.setStyleSheet(f"""
|
||||||
QLabel {{
|
QLabel {{
|
||||||
background-color: {TaskStatusColor};
|
background-color: {TaskStatusColor};
|
||||||
color: white;
|
color: #FFFFFF;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}}
|
}}
|
||||||
@@ -116,7 +109,7 @@ class TimerTaskItemWidget(QWidget):
|
|||||||
TaskModeLabel.setStyleSheet(f"""
|
TaskModeLabel.setStyleSheet(f"""
|
||||||
QLabel {{
|
QLabel {{
|
||||||
background-color: {TaskModeColor};
|
background-color: {TaskModeColor};
|
||||||
color: white;
|
color: #FFFFFF;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}}
|
}}
|
||||||
@@ -128,37 +121,41 @@ class TimerTaskItemWidget(QWidget):
|
|||||||
self.DeleteButton = QPushButton("删除")
|
self.DeleteButton = QPushButton("删除")
|
||||||
self.DeleteButton.setFixedSize(80, 25)
|
self.DeleteButton.setFixedSize(80, 25)
|
||||||
self.ItemWidgetLayout.addWidget(self.DeleteButton)
|
self.ItemWidgetLayout.addWidget(self.DeleteButton)
|
||||||
if self.__timer_task["status"] == TimerTaskStatus.READY\
|
if self.__timer_task["status"] == ALTimerTaskStatus.READY\
|
||||||
or self.__timer_task["status"] == TimerTaskStatus.RUNNING:
|
or self.__timer_task["status"] == ALTimerTaskStatus.RUNNING:
|
||||||
self.DeleteButton.setEnabled(False)
|
self.DeleteButton.setEnabled(False)
|
||||||
self.setFixedHeight(55)
|
self.setFixedHeight(55)
|
||||||
|
|
||||||
|
|
||||||
class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
class ALTimerTaskManageWidget(QWidget, Ui_ALTimerTaskManageWidget):
|
||||||
|
|
||||||
|
class SortPolicy(Enum):
|
||||||
|
|
||||||
|
BY_NAME = "按名称"
|
||||||
|
BY_ADD_TIME = "按添加时间"
|
||||||
|
BY_EXECUTE_TIME = "按执行时间"
|
||||||
|
|
||||||
timerTasksChanged = Signal()
|
|
||||||
timerTaskIsReady = Signal(dict)
|
timerTaskIsReady = Signal(dict)
|
||||||
timerTaskWidgetClosed = Signal()
|
timerTasksChanged = Signal()
|
||||||
|
timerTaskManageWidgetIsClosed = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parent = None,
|
parent = None
|
||||||
timer_tasks_config_path: str = ""
|
|
||||||
):
|
):
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
self.__cfg_mgr = instance()
|
||||||
self.__timer_tasks = []
|
self.__timer_tasks = []
|
||||||
self.__check_timer = None
|
self.__check_timer = None
|
||||||
self.__sort_policy = SortPolicy.BY_EXECUTE_TIME
|
self.__sort_policy = self.SortPolicy.BY_EXECUTE_TIME
|
||||||
self.__sort_order = Qt.SortOrder.AscendingOrder
|
self.__sort_order = Qt.SortOrder.AscendingOrder
|
||||||
self.__timer_tasks_config_path = timer_tasks_config_path
|
|
||||||
|
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.connectSignals()
|
self.connectSignals()
|
||||||
self.setupTimer()
|
self.setupTimer()
|
||||||
if not self.initializeTimerTasks():
|
if not self.initializeTimerTasks():
|
||||||
return
|
raise Exception("定时任务配置文件初始化失败 !")
|
||||||
|
|
||||||
|
|
||||||
def connectSignals(
|
def connectSignals(
|
||||||
@@ -185,74 +182,57 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
self
|
self
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
timer_tasks = self.loadTimerTasks(self.__timer_tasks_config_path)
|
timer_tasks = self.getTimerTasks()
|
||||||
if timer_tasks is not None:
|
if timer_tasks is not None:
|
||||||
self.__timer_tasks = timer_tasks
|
self.__timer_tasks = timer_tasks
|
||||||
self.timerTasksChanged.emit()
|
self.timerTasksChanged.emit()
|
||||||
return True
|
return True
|
||||||
timer_tasks = []
|
timer_tasks = []
|
||||||
if self.saveTimerTasks(self.__timer_tasks_config_path, copy.deepcopy(timer_tasks)):
|
if self.setTimerTasks(copy.deepcopy(timer_tasks)):
|
||||||
QMessageBox.information(
|
|
||||||
self,
|
|
||||||
"信息 - AutoLibrary",
|
|
||||||
f"定时任务配置文件初始化完成: \n{self.__timer_tasks_config_path}"
|
|
||||||
)
|
|
||||||
self.__timer_tasks = timer_tasks
|
self.__timer_tasks = timer_tasks
|
||||||
self.updateTimerTaskList()
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def loadTimerTasks(
|
def getTimerTasks(
|
||||||
self,
|
self
|
||||||
timer_tasks_config_path: str
|
|
||||||
) -> list:
|
) -> list:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not timer_tasks_config_path or not os.path.exists(timer_tasks_config_path):
|
timer_tasks = self.__cfg_mgr.get(ConfigType.TIMERTASK)
|
||||||
raise Exception("定时任务配置文件不存在")
|
|
||||||
timer_tasks = ConfigReader(timer_tasks_config_path).getConfigs()
|
|
||||||
if timer_tasks and "timer_tasks" in timer_tasks:
|
if timer_tasks and "timer_tasks" in timer_tasks:
|
||||||
for task in timer_tasks["timer_tasks"]:
|
for task in timer_tasks["timer_tasks"]:
|
||||||
task["add_time"] = datetime.strptime(task["add_time"], "%Y-%m-%d %H:%M:%S")
|
task["add_time"] = datetime.strptime(task["add_time"], "%Y-%m-%d %H:%M:%S")
|
||||||
task["execute_time"] = datetime.strptime(task["execute_time"], "%Y-%m-%d %H:%M:%S")
|
task["execute_time"] = datetime.strptime(task["execute_time"], "%Y-%m-%d %H:%M:%S")
|
||||||
task["status"] = TimerTaskStatus(task["status"])
|
task["status"] = ALTimerTaskStatus(task["status"])
|
||||||
return timer_tasks["timer_tasks"]
|
return timer_tasks["timer_tasks"]
|
||||||
raise Exception("定时任务配置文件格式错误")
|
raise Exception("定时任务配置文件格式错误")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"警告 - AutoLibrary",
|
"警告 - AutoLibrary",
|
||||||
f"加载定时任务配置发生错误 ! : {e}\n"\
|
f"加载定时任务配置发生错误 ! : \n{e}"
|
||||||
f"文件路径: {timer_tasks_config_path}"
|
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def saveTimerTasks(
|
def setTimerTasks(
|
||||||
self,
|
self,
|
||||||
timer_tasks_config_path: str,
|
|
||||||
timer_tasks: list
|
timer_tasks: list
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not timer_tasks_config_path:
|
|
||||||
raise Exception("配置文件路径为空")
|
|
||||||
for task in timer_tasks:
|
for task in timer_tasks:
|
||||||
task["add_time"] = task["add_time"].strftime("%Y-%m-%d %H:%M:%S")
|
task["add_time"] = task["add_time"].strftime("%Y-%m-%d %H:%M:%S")
|
||||||
task["execute_time"] = task["execute_time"].strftime("%Y-%m-%d %H:%M:%S")
|
task["execute_time"] = task["execute_time"].strftime("%Y-%m-%d %H:%M:%S")
|
||||||
task["status"] = task["status"].value
|
task["status"] = task["status"].value
|
||||||
ConfigWriter(
|
self.__cfg_mgr.set(ConfigType.TIMERTASK, "", { "timer_tasks": timer_tasks })
|
||||||
timer_tasks_config_path,
|
|
||||||
{ "timer_tasks": timer_tasks }
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
"警告 - AutoLibrary",
|
"警告 - AutoLibrary",
|
||||||
f"保存定时任务配置发生错误 ! : {e}\n"\
|
f"保存定时任务配置发生错误 ! : \n{e}"
|
||||||
f"文件路径: {timer_tasks_config_path}"
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -287,7 +267,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
):
|
):
|
||||||
|
|
||||||
self.hide()
|
self.hide()
|
||||||
self.timerTaskWidgetClosed.emit()
|
self.timerTaskManageWidgetIsClosed.emit()
|
||||||
event.ignore()
|
event.ignore()
|
||||||
|
|
||||||
|
|
||||||
@@ -297,17 +277,17 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
order: Qt.SortOrder = Qt.SortOrder.AscendingOrder
|
order: Qt.SortOrder = Qt.SortOrder.AscendingOrder
|
||||||
):
|
):
|
||||||
|
|
||||||
if policy == SortPolicy.BY_NAME:
|
if policy == self.SortPolicy.BY_NAME:
|
||||||
self.__timer_tasks.sort(
|
self.__timer_tasks.sort(
|
||||||
key = lambda x: x["name"],
|
key = lambda x: x["name"],
|
||||||
reverse = order is Qt.SortOrder.DescendingOrder
|
reverse = order is Qt.SortOrder.DescendingOrder
|
||||||
)
|
)
|
||||||
elif policy == SortPolicy.BY_ADD_TIME:
|
elif policy == self.SortPolicy.BY_ADD_TIME:
|
||||||
self.__timer_tasks.sort(
|
self.__timer_tasks.sort(
|
||||||
key = lambda x: x["add_time"],
|
key = lambda x: x["add_time"],
|
||||||
reverse = order is Qt.SortOrder.DescendingOrder
|
reverse = order is Qt.SortOrder.DescendingOrder
|
||||||
)
|
)
|
||||||
elif policy == SortPolicy.BY_EXECUTE_TIME:
|
elif policy == self.SortPolicy.BY_EXECUTE_TIME:
|
||||||
self.__timer_tasks.sort(
|
self.__timer_tasks.sort(
|
||||||
key = lambda x: x["execute_time"],
|
key = lambda x: x["execute_time"],
|
||||||
reverse = order is Qt.SortOrder.DescendingOrder
|
reverse = order is Qt.SortOrder.DescendingOrder
|
||||||
@@ -324,15 +304,15 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
invalid = 0
|
invalid = 0
|
||||||
total = len(self.__timer_tasks)
|
total = len(self.__timer_tasks)
|
||||||
for timer_task in self.__timer_tasks:
|
for timer_task in self.__timer_tasks:
|
||||||
if timer_task["status"] == TimerTaskStatus.PENDING:
|
if timer_task["status"] == ALTimerTaskStatus.PENDING:
|
||||||
pending += 1
|
pending += 1
|
||||||
elif timer_task["status"] == TimerTaskStatus.READY\
|
elif timer_task["status"] == ALTimerTaskStatus.READY\
|
||||||
or timer_task["status"] == TimerTaskStatus.RUNNING:
|
or timer_task["status"] == ALTimerTaskStatus.RUNNING:
|
||||||
in_queue += 1
|
in_queue += 1
|
||||||
elif timer_task["status"] == TimerTaskStatus.EXECUTED:
|
elif timer_task["status"] == ALTimerTaskStatus.EXECUTED:
|
||||||
executed += 1
|
executed += 1
|
||||||
elif timer_task["status"] == TimerTaskStatus.ERROR\
|
elif timer_task["status"] == ALTimerTaskStatus.ERROR\
|
||||||
or timer_task["status"] == TimerTaskStatus.OUTDATED:
|
or timer_task["status"] == ALTimerTaskStatus.OUTDATED:
|
||||||
invalid += 1
|
invalid += 1
|
||||||
self.TotalTaskLabel.setText(f"总任务:{total}")
|
self.TotalTaskLabel.setText(f"总任务:{total}")
|
||||||
self.PendingTaskLabel.setText(f"待执行:{pending}")
|
self.PendingTaskLabel.setText(f"待执行:{pending}")
|
||||||
@@ -350,7 +330,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
for timer_task in self.__timer_tasks:
|
for timer_task in self.__timer_tasks:
|
||||||
item = QListWidgetItem()
|
item = QListWidgetItem()
|
||||||
item.setData(Qt.UserRole, timer_task)
|
item.setData(Qt.UserRole, timer_task)
|
||||||
widget = TimerTaskItemWidget(self, timer_task)
|
widget = ALTimerTaskItemWidget(self, timer_task)
|
||||||
widget.DeleteButton.clicked.connect(
|
widget.DeleteButton.clicked.connect(
|
||||||
lambda _, uuid = timer_task["task_uuid"]: self.deleteTask(uuid)
|
lambda _, uuid = timer_task["task_uuid"]: self.deleteTask(uuid)
|
||||||
)
|
)
|
||||||
@@ -363,7 +343,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
dialog = ALAddTimerTaskWidget(self)
|
dialog = ALTimerTaskAddDialog(self)
|
||||||
if dialog.exec() == QDialog.DialogCode.Accepted:
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
timer_task = dialog.getTimerTask()
|
timer_task = dialog.getTimerTask()
|
||||||
self.__timer_tasks.append(timer_task)
|
self.__timer_tasks.append(timer_task)
|
||||||
@@ -398,8 +378,8 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
return
|
return
|
||||||
in_queue_tasks = [
|
in_queue_tasks = [
|
||||||
x for x in self.__timer_tasks
|
x for x in self.__timer_tasks
|
||||||
if x["status"] == TimerTaskStatus.READY
|
if x["status"] == ALTimerTaskStatus.READY
|
||||||
or x["status"] == TimerTaskStatus.RUNNING
|
or x["status"] == ALTimerTaskStatus.RUNNING
|
||||||
]
|
]
|
||||||
in_queue_count = len(in_queue_tasks)
|
in_queue_count = len(in_queue_tasks)
|
||||||
if in_queue_count > 0:
|
if in_queue_count > 0:
|
||||||
@@ -422,13 +402,13 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
for timer_task in self.__timer_tasks:
|
for timer_task in self.__timer_tasks:
|
||||||
if timer_task["execute_time"] > now:
|
if timer_task["execute_time"] > now:
|
||||||
continue
|
continue
|
||||||
if timer_task["status"] is not TimerTaskStatus.PENDING:
|
if timer_task["status"] is not ALTimerTaskStatus.PENDING:
|
||||||
continue
|
continue
|
||||||
if timer_task["execute_time"] <= now + timedelta(seconds = -5):
|
if timer_task["execute_time"] <= now + timedelta(seconds = -5):
|
||||||
timer_task["status"] = TimerTaskStatus.OUTDATED
|
timer_task["status"] = ALTimerTaskStatus.OUTDATED
|
||||||
need_update = True
|
need_update = True
|
||||||
else:
|
else:
|
||||||
timer_task["status"] = TimerTaskStatus.READY
|
timer_task["status"] = ALTimerTaskStatus.READY
|
||||||
self.timerTaskIsReady.emit(timer_task)
|
self.timerTaskIsReady.emit(timer_task)
|
||||||
need_update = True
|
need_update = True
|
||||||
if need_update:
|
if need_update:
|
||||||
@@ -441,9 +421,9 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
):
|
):
|
||||||
|
|
||||||
mapping = {
|
mapping = {
|
||||||
0: SortPolicy.BY_NAME,
|
0: self.SortPolicy.BY_NAME,
|
||||||
1: SortPolicy.BY_ADD_TIME,
|
1: self.SortPolicy.BY_ADD_TIME,
|
||||||
2: SortPolicy.BY_EXECUTE_TIME
|
2: self.SortPolicy.BY_EXECUTE_TIME
|
||||||
}
|
}
|
||||||
self.__sort_policy = mapping[policy]
|
self.__sort_policy = mapping[policy]
|
||||||
self.updateTimerTaskList()
|
self.updateTimerTaskList()
|
||||||
@@ -466,7 +446,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
self
|
self
|
||||||
):
|
):
|
||||||
|
|
||||||
self.saveTimerTasks(self.__timer_tasks_config_path, copy.deepcopy(self.__timer_tasks))
|
self.setTimerTasks(copy.deepcopy(self.__timer_tasks))
|
||||||
self.updateTimerTaskList()
|
self.updateTimerTaskList()
|
||||||
self.updateStat()
|
self.updateStat()
|
||||||
|
|
||||||
@@ -479,7 +459,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
|
|
||||||
for task in self.__timer_tasks:
|
for task in self.__timer_tasks:
|
||||||
if task["task_uuid"] == timer_task["task_uuid"]:
|
if task["task_uuid"] == timer_task["task_uuid"]:
|
||||||
task["status"] = TimerTaskStatus.RUNNING
|
task["status"] = ALTimerTaskStatus.RUNNING
|
||||||
self.timerTasksChanged.emit()
|
self.timerTasksChanged.emit()
|
||||||
|
|
||||||
|
|
||||||
@@ -491,7 +471,7 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
|
|
||||||
for task in self.__timer_tasks:
|
for task in self.__timer_tasks:
|
||||||
if task["task_uuid"] == timer_task["task_uuid"]:
|
if task["task_uuid"] == timer_task["task_uuid"]:
|
||||||
task["status"] = TimerTaskStatus.EXECUTED
|
task["status"] = ALTimerTaskStatus.EXECUTED
|
||||||
self.timerTasksChanged.emit()
|
self.timerTasksChanged.emit()
|
||||||
|
|
||||||
@Slot(dict)
|
@Slot(dict)
|
||||||
@@ -502,5 +482,5 @@ class ALTimerTaskWidget(QWidget, Ui_ALTimerTaskWidget):
|
|||||||
|
|
||||||
for task in self.__timer_tasks:
|
for task in self.__timer_tasks:
|
||||||
if task["task_uuid"] == timer_task["task_uuid"]:
|
if task["task_uuid"] == timer_task["task_uuid"]:
|
||||||
task["status"] = TimerTaskStatus.ERROR
|
task["status"] = ALTimerTaskStatus.ERROR
|
||||||
self.timerTasksChanged.emit()
|
self.timerTasksChanged.emit()
|
||||||
@@ -21,7 +21,7 @@ from PySide6.QtGui import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TreeItemType(Enum):
|
class ALUserTreeItemType(Enum):
|
||||||
|
|
||||||
GROUP = 0
|
GROUP = 0
|
||||||
USER = 1
|
USER = 1
|
||||||
@@ -111,15 +111,15 @@ class ALUserTreeWidget(QTreeWidget):
|
|||||||
if source_item is None:
|
if source_item is None:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
return
|
return
|
||||||
if source_item.type() == TreeItemType.GROUP.value:
|
if source_item.type() == ALUserTreeItemType.GROUP.value:
|
||||||
if target_item is not None:
|
if target_item is not None:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
return
|
return
|
||||||
elif source_item.type() == TreeItemType.USER.value:
|
elif source_item.type() == ALUserTreeItemType.USER.value:
|
||||||
if target_item is None:
|
if target_item is None:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
return
|
return
|
||||||
if target_item.type() != TreeItemType.GROUP.value:
|
if target_item.type() != ALUserTreeItemType.GROUP.value:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
return
|
return
|
||||||
if target_item.checkState(1) == Qt.CheckState.Unchecked:
|
if target_item.checkState(1) == Qt.CheckState.Unchecked:
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
workflow process. Do not edit manually.
|
workflow process. Do not edit manually.
|
||||||
|
|
||||||
This file is auto-generated during the workflow process.
|
This file is auto-generated during the workflow process.
|
||||||
Last updated: 2026-01-17 17:51:55 UTC
|
Last updated: 2026-02-26 15:04:28 UTC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
AL_VERSION = "1.0.3"
|
AL_VERSION = "1.1.0"
|
||||||
AL_TAG = "v1.0.3"
|
AL_TAG = "v1.1.0"
|
||||||
AL_COMMIT_SHA = "local"
|
AL_COMMIT_SHA = "local"
|
||||||
AL_COMMIT_DATE = "null" # time zone : UTC
|
AL_COMMIT_DATE = "null" # time zone : UTC
|
||||||
AL_BUILD_DATE = "null" # time zone : UTC
|
AL_BUILD_DATE = "null" # time zone : UTC
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
this folder is used to store the batch scripts.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
this folder is used to store the config files.
|
|
||||||
|
Before Width: | Height: | Size: 785 KiB After Width: | Height: | Size: 785 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -104,7 +104,7 @@
|
|||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QFrame" name="frame">
|
<widget class="QFrame" name="AboutInfoSpaceFrame">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>56</width>
|
<width>56</width>
|
||||||
@@ -129,21 +129,24 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTextEdit" name="AboutInfoEdit">
|
<widget class="QTextBrowser" name="AboutInfoBrowser">
|
||||||
<property name="font">
|
<property name="frameShadow">
|
||||||
<font>
|
<enum>QFrame::Shadow::Plain</enum>
|
||||||
<family>Courier New</family>
|
</property>
|
||||||
<bold>false</bold>
|
<property name="verticalScrollBarPolicy">
|
||||||
</font>
|
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="lineWrapMode">
|
<property name="lineWrapMode">
|
||||||
<enum>QTextEdit::LineWrapMode::NoWrap</enum>
|
<enum>QTextEdit::LineWrapMode::NoWrap</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="readOnly">
|
<property name="openExternalLinks">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="textInteractionFlags">
|
<property name="openLinks">
|
||||||
<set>Qt::TextInteractionFlag::TextBrowserInteraction</set>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -94,19 +94,19 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="UserListLayout">
|
<layout class="QVBoxLayout" name="UserListLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="rightMargin">
|
<property name="rightMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeWidget" name="UserTreeWidget">
|
<widget class="QTreeWidget" name="UserTreeWidget">
|
||||||
@@ -178,6 +178,9 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="UserListControlLayout">
|
<layout class="QHBoxLayout" name="UserListControlLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="DelUserButton">
|
<widget class="QPushButton" name="DelUserButton">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
@@ -309,7 +312,7 @@
|
|||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<layout class="QHBoxLayout" name="PasswordLayout">
|
<layout class="QHBoxLayout" name="PasswordLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="PasswordEdit">
|
<widget class="QLineEdit" name="PasswordEdit">
|
||||||
@@ -546,7 +549,7 @@
|
|||||||
<item row="4" column="4">
|
<item row="4" column="4">
|
||||||
<layout class="QHBoxLayout" name="SeatIDLayout">
|
<layout class="QHBoxLayout" name="SeatIDLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="SeatIDEdit">
|
<widget class="QLineEdit" name="SeatIDEdit">
|
||||||
@@ -703,7 +706,7 @@
|
|||||||
<item row="10" column="4">
|
<item row="10" column="4">
|
||||||
<layout class="QHBoxLayout" name="EndTimeDiffLayout">
|
<layout class="QHBoxLayout" name="EndTimeDiffLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSpinBox" name="MaxEndTimeDiffSpinBox">
|
<widget class="QSpinBox" name="MaxEndTimeDiffSpinBox">
|
||||||
@@ -899,7 +902,7 @@
|
|||||||
<item row="7" column="4">
|
<item row="7" column="4">
|
||||||
<layout class="QHBoxLayout" name="BeginTimeDiffLayout">
|
<layout class="QHBoxLayout" name="BeginTimeDiffLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSpinBox" name="MaxBeginTimeDiffSpinBox">
|
<widget class="QSpinBox" name="MaxBeginTimeDiffSpinBox">
|
||||||
@@ -1049,7 +1052,7 @@
|
|||||||
<item row="15" column="4">
|
<item row="15" column="4">
|
||||||
<layout class="QHBoxLayout" name="RenewTimeDiffLayout">
|
<layout class="QHBoxLayout" name="RenewTimeDiffLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSpinBox" name="MaxRenewTimeDiffSpinBox">
|
<widget class="QSpinBox" name="MaxRenewTimeDiffSpinBox">
|
||||||
@@ -1092,7 +1095,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="15" column="1">
|
<item row="15" column="1">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="MaxRenewTimeDiffLabel">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>80</width>
|
<width>80</width>
|
||||||
@@ -1153,10 +1156,10 @@
|
|||||||
<property name="rightMargin">
|
<property name="rightMargin">
|
||||||
<number>3</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="horizontalSpacing">
|
<property name="bottomMargin">
|
||||||
<number>3</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="verticalSpacing">
|
<property name="spacing">
|
||||||
<number>5</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="0" column="0" colspan="2">
|
<item row="0" column="0" colspan="2">
|
||||||
@@ -1353,7 +1356,7 @@
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>详情请参阅 <a href="https://www.autolibrary.cv/docs/manual_lists.html"><span style=" text-decoration: underline; color:#69fcff;">用户手册</span></a></p></body></html></string>
|
<string><html><head/><body><p>详情请参阅 <a href="https://www.autolibrary.top/manuals"><span style=" text-decoration: underline; color:#69fcff;">用户手册</span></a></p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="whatsThis">
|
<property name="whatsThis">
|
||||||
<string><html><head/><body><p><br/></p></body></html></string>
|
<string><html><head/><body><p><br/></p></body></html></string>
|
||||||
@@ -1649,6 +1652,21 @@
|
|||||||
<string>当前配置</string>
|
<string>当前配置</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="CurrentConfigLayout">
|
<layout class="QGridLayout" name="CurrentConfigLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLineEdit" name="CurrentRunConfigEdit">
|
<widget class="QLineEdit" name="CurrentRunConfigEdit">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
@@ -1775,6 +1793,21 @@
|
|||||||
<string>导出路径</string>
|
<string>导出路径</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="ExportConfigLayout">
|
<layout class="QGridLayout" name="ExportConfigLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLineEdit" name="ExportUserConfigEdit">
|
<widget class="QLineEdit" name="ExportUserConfigEdit">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
@@ -34,13 +34,13 @@
|
|||||||
<number>5</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>3</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="rightMargin">
|
<property name="rightMargin">
|
||||||
<number>3</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
<number>5</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="TimerTaskWidgetButton">
|
<widget class="QPushButton" name="TimerTaskManageWidgetButton">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>25</width>
|
<width>25</width>
|
||||||
@@ -156,11 +156,9 @@
|
|||||||
<string><html><head/><body><p><br/></p></body></html></string>
|
<string><html><head/><body><p><br/></p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">background-color: rgb(10, 170, 10);
|
<string notr="true">background-color: #0AAA0A;
|
||||||
font: 12pt "Microsoft YaHei UI";
|
color: #FFFFFF;
|
||||||
color: rgb(255, 255, 255);
|
font: 700 9pt;</string>
|
||||||
font: 9pt "Segoe UI";
|
|
||||||
font: 700 9pt "Microsoft YaHei UI";</string>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>启动脚本</string>
|
<string>启动脚本</string>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>ALAddTimerTaskDialog</class>
|
<class>ALTimerTaskAddDialog</class>
|
||||||
<widget class="QDialog" name="ALAddTimerTaskDialog">
|
<widget class="QDialog" name="ALTimerTaskAddDialog">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
@@ -86,16 +86,16 @@
|
|||||||
<number>5</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="rightMargin">
|
<property name="rightMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="TimerTypeSelectLayout">
|
<layout class="QHBoxLayout" name="TimerTypeSelectLayout">
|
||||||
@@ -135,16 +135,16 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="TaskConfigLayout">
|
<layout class="QGridLayout" name="TaskConfigLayout">
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="rightMargin">
|
<property name="rightMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>5</number>
|
<number>3</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>5</number>
|
<number>5</number>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>ALTimerTaskWidget</class>
|
<class>ALTimerTaskManageWidget</class>
|
||||||
<widget class="QWidget" name="ALTimerTaskWidget">
|
<widget class="QWidget" name="ALTimerTaskManageWidget">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>定时任务 - AutoLibrary</string>
|
<string>定时任务管理 - AutoLibrary</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="ALTimerTaskWidgetLayout">
|
<layout class="QVBoxLayout" name="ALTimerTaskManageWidgetLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>5</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">QLabel {
|
<string notr="true">QLabel {
|
||||||
color: #FF5722
|
color: #DC0000
|
||||||
}</string>
|
}</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -11,6 +11,7 @@ import os
|
|||||||
import queue
|
import queue
|
||||||
|
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
|
from selenium.common.exceptions import TimeoutException
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
from selenium.webdriver.support import expected_conditions as EC
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
@@ -41,10 +42,11 @@ class AutoLib(MsgBase):
|
|||||||
self.__user_config = None
|
self.__user_config = None
|
||||||
self.__driver = None
|
self.__driver = None
|
||||||
if not self.__initBrowserDriver():
|
if not self.__initBrowserDriver():
|
||||||
raise Exception("浏览器驱动初始化失败")
|
raise Exception("浏览器驱动初始化失败 !")
|
||||||
else:
|
else:
|
||||||
if not self.__initDriverUrl():
|
if not self.__initDriverUrl():
|
||||||
raise Exception("浏览器驱动URL初始化失败")
|
self.close()
|
||||||
|
raise Exception("浏览器驱动URL初始化失败 !")
|
||||||
self.__initLibOperators()
|
self.__initLibOperators()
|
||||||
|
|
||||||
|
|
||||||
@@ -64,7 +66,8 @@ class AutoLib(MsgBase):
|
|||||||
case "firefox":
|
case "firefox":
|
||||||
driver_options = webdriver.FirefoxOptions()
|
driver_options = webdriver.FirefoxOptions()
|
||||||
case _:
|
case _:
|
||||||
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type} !")
|
self._showTrace(f"不支持的浏览器驱动类型: {self.__driver_type} !")
|
||||||
|
return False
|
||||||
|
|
||||||
if not web_driver_config:
|
if not web_driver_config:
|
||||||
self._showTrace("未配置浏览器驱动参数 !")
|
self._showTrace("未配置浏览器驱动参数 !")
|
||||||
@@ -107,7 +110,8 @@ class AutoLib(MsgBase):
|
|||||||
# init browser driver
|
# init browser driver
|
||||||
self.__driver_path = web_driver_config.get("driver_path")
|
self.__driver_path = web_driver_config.get("driver_path")
|
||||||
if not self.__driver_path:
|
if not self.__driver_path:
|
||||||
raise Exception(f"未配置浏览器驱动路径 !")
|
self._showTrace("未配置浏览器驱动路径 !")
|
||||||
|
return False
|
||||||
self.__driver_path = os.path.abspath(self.__driver_path)
|
self.__driver_path = os.path.abspath(self.__driver_path)
|
||||||
try:
|
try:
|
||||||
service = None
|
service = None
|
||||||
@@ -122,7 +126,8 @@ class AutoLib(MsgBase):
|
|||||||
self._showTrace(f"Firefox 浏览器驱动初始化略慢, 请耐心等待...")
|
self._showTrace(f"Firefox 浏览器驱动初始化略慢, 请耐心等待...")
|
||||||
service = FirefoxService(executable_path=self.__driver_path)
|
service = FirefoxService(executable_path=self.__driver_path)
|
||||||
self.__driver = webdriver.Firefox(service=service, options=driver_options)
|
self.__driver = webdriver.Firefox(service=service, options=driver_options)
|
||||||
case _:
|
case _: # actually will not happen, beacuse we have checked it at the initlization
|
||||||
|
# of 'driver_options'
|
||||||
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}")
|
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}")
|
||||||
self.__driver.implicitly_wait(1)
|
self.__driver.implicitly_wait(1)
|
||||||
self.__driver.execute_script(
|
self.__driver.execute_script(
|
||||||
@@ -183,10 +188,16 @@ class AutoLib(MsgBase):
|
|||||||
|
|
||||||
lib_config = self.__run_config.get("library", None)
|
lib_config = self.__run_config.get("library", None)
|
||||||
if not lib_config:
|
if not lib_config:
|
||||||
self._showError("未配置图书馆参数 !")
|
self._showTrace("未配置图书馆参数 !")
|
||||||
return False
|
return False
|
||||||
url = lib_config.get("host_url") + lib_config.get("login_url")
|
url = lib_config.get("host_url") + lib_config.get("login_url")
|
||||||
self.__driver.get(url)
|
self.__driver.set_page_load_timeout(5)
|
||||||
|
try:
|
||||||
|
self.__driver.get(url)
|
||||||
|
except TimeoutException:
|
||||||
|
self.__driver.execute_script("window.stop();")
|
||||||
|
self._showTrace(f"图书馆登录页面加载超时 ! 请检查网络环境是否正常")
|
||||||
|
return False
|
||||||
if not self.__waitResponseLoad():
|
if not self.__waitResponseLoad():
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -241,7 +252,8 @@ class AutoLib(MsgBase):
|
|||||||
result = 2
|
result = 2
|
||||||
# renewal
|
# renewal
|
||||||
if run_mode["auto_renewal"] and result == 2:
|
if run_mode["auto_renewal"] and result == 2:
|
||||||
if record := self.__lib_checker.canRenew():
|
can_renew, record = self.__lib_checker.canRenew()
|
||||||
|
if can_renew:
|
||||||
if self.__lib_renew.renew(username, record, reserve_info):
|
if self.__lib_renew.renew(username, record, reserve_info):
|
||||||
if self.__lib_checker.postRenewCheck(record):
|
if self.__lib_checker.postRenewCheck(record):
|
||||||
result = 0
|
result = 0
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ class LibChecker(LibOperator):
|
|||||||
|
|
||||||
def canRenew(
|
def canRenew(
|
||||||
self
|
self
|
||||||
):
|
) -> tuple[bool, dict]:
|
||||||
|
|
||||||
# only check the current date
|
# only check the current date
|
||||||
date = time.strftime("%Y-%m-%d", time.localtime())
|
date = time.strftime("%Y-%m-%d", time.localtime())
|
||||||
@@ -326,12 +326,13 @@ class LibChecker(LibOperator):
|
|||||||
)
|
)
|
||||||
if abs(time_diff_seconds) < 120*60:
|
if abs(time_diff_seconds) < 120*60:
|
||||||
self._showTrace(f"{trace_msg}, 可以续约")
|
self._showTrace(f"{trace_msg}, 可以续约")
|
||||||
return record
|
return True, record
|
||||||
else:
|
else:
|
||||||
self._showTrace(f"{trace_msg}, 无法续约")
|
self._showTrace(f"{trace_msg}, 无法续约")
|
||||||
return None
|
return False, None # we do not need to return the record, because if current
|
||||||
|
# time is not available for renewal, the record is not required
|
||||||
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约")
|
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约")
|
||||||
return None
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
def postRenewCheck(
|
def postRenewCheck(
|
||||||
|
|||||||
@@ -138,9 +138,7 @@ class LibRenew(LibOperator):
|
|||||||
abs_diff = abs(actual_diff)
|
abs_diff = abs(actual_diff)
|
||||||
if abs_diff < best_time_diff or (
|
if abs_diff < best_time_diff or (
|
||||||
abs_diff == best_time_diff and (
|
abs_diff == best_time_diff and (
|
||||||
# 优先选择更早的时间
|
|
||||||
(prefer_earlier and actual_diff <= 0) or
|
(prefer_earlier and actual_diff <= 0) or
|
||||||
# 优先选择更晚的时间
|
|
||||||
(not prefer_earlier and actual_diff >= 0)
|
(not prefer_earlier and actual_diff >= 0)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
@@ -203,7 +201,7 @@ class LibRenew(LibOperator):
|
|||||||
self._showTrace(f"用户 {username} 续约失败 !")
|
self._showTrace(f"用户 {username} 续约失败 !")
|
||||||
|
|
||||||
# After the renewal, the webpage will display a mask overlay,
|
# After the renewal, the webpage will display a mask overlay,
|
||||||
# so we need to refresh the page for subsequent operations.
|
# so we need to refresh the page for subsequent operations.
|
||||||
self.__driver.refresh()
|
self.__driver.refresh()
|
||||||
return False
|
return False
|
||||||
if not self.__selectNearstTime(record, reserve_info):
|
if not self.__selectNearstTime(record, reserve_info):
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (c) 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 threading
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from utils.JSONReader import JSONReader
|
||||||
|
from utils.JSONWriter import JSONWriter
|
||||||
|
|
||||||
|
|
||||||
|
# This config manager class only responsible for global and other
|
||||||
|
# unconfigurable config files.
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigType(Enum):
|
||||||
|
"""
|
||||||
|
Config type class. Values represent the default filename.
|
||||||
|
"""
|
||||||
|
GLOBAL = "autolibrary.json" # Global config file.
|
||||||
|
BULLETIN = "bulletin.json" # Bulletin board config file.
|
||||||
|
TIMERTASK = "timer_task.json" # Timer task config file.
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigTemplate:
|
||||||
|
"""
|
||||||
|
Config template class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config_type: ConfigType
|
||||||
|
):
|
||||||
|
|
||||||
|
self.__config_type = config_type
|
||||||
|
|
||||||
|
|
||||||
|
def template(
|
||||||
|
self
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Get config template.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Config template.
|
||||||
|
"""
|
||||||
|
match self.__config_type:
|
||||||
|
case ConfigType.GLOBAL:
|
||||||
|
return {
|
||||||
|
"automation": {
|
||||||
|
"run_path": {
|
||||||
|
"current": 0,
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
"user_path": {
|
||||||
|
"current": 0,
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ConfigType.BULLETIN:
|
||||||
|
return {
|
||||||
|
"bulletin": [],
|
||||||
|
"last_sync_time": None
|
||||||
|
}
|
||||||
|
case ConfigType.TIMERTASK:
|
||||||
|
return {
|
||||||
|
"timer_tasks": []
|
||||||
|
}
|
||||||
|
case _:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigManager:
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config_dir: str
|
||||||
|
):
|
||||||
|
|
||||||
|
self.__config_dir = os.path.abspath(config_dir)
|
||||||
|
self.__config_lock = threading.Lock()
|
||||||
|
self.__config_data = {}
|
||||||
|
|
||||||
|
self.initialize()
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
for config_type in ConfigType:
|
||||||
|
self.load(config_type)
|
||||||
|
|
||||||
|
|
||||||
|
def load(
|
||||||
|
self,
|
||||||
|
config_type: ConfigType
|
||||||
|
):
|
||||||
|
|
||||||
|
config_path = os.path.join(self.__config_dir, config_type.value)
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
try:
|
||||||
|
config_data = JSONReader(config_path).data()
|
||||||
|
self.__config_data[config_type.value] = config_data
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.__config_data[config_type.value] = ConfigTemplate(config_type).template()
|
||||||
|
JSONWriter(config_path, self.__config_data[config_type.value])
|
||||||
|
|
||||||
|
|
||||||
|
def get(
|
||||||
|
self,
|
||||||
|
config_type: ConfigType,
|
||||||
|
key: str = "",
|
||||||
|
default: Optional[Any] = None
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
|
with self.__config_lock:
|
||||||
|
config_data = self.__config_data[config_type.value]
|
||||||
|
if key == "":
|
||||||
|
return config_data
|
||||||
|
keys = key.split('.')
|
||||||
|
for k in keys[:-1]:
|
||||||
|
config_data = config_data.get(k, None)
|
||||||
|
if config_data is None:
|
||||||
|
return default
|
||||||
|
return config_data.get(keys[-1], default)
|
||||||
|
|
||||||
|
|
||||||
|
def set(
|
||||||
|
self,
|
||||||
|
config_type: ConfigType,
|
||||||
|
key: str = "",
|
||||||
|
value: Any = None
|
||||||
|
):
|
||||||
|
|
||||||
|
with self.__config_lock:
|
||||||
|
root_data = self.__config_data[config_type.value]
|
||||||
|
if key == "":
|
||||||
|
self.__config_data[config_type.value] = value
|
||||||
|
else:
|
||||||
|
keys = key.split('.')
|
||||||
|
config_data = root_data
|
||||||
|
for k in keys[:-1]:
|
||||||
|
if k not in config_data:
|
||||||
|
config_data[k] = {}
|
||||||
|
config_data = config_data[k]
|
||||||
|
config_data[keys[-1]] = value
|
||||||
|
self.save(config_type)
|
||||||
|
|
||||||
|
|
||||||
|
def save(
|
||||||
|
self,
|
||||||
|
config_type: ConfigType
|
||||||
|
):
|
||||||
|
|
||||||
|
config_path = os.path.join(self.__config_dir, config_type.value)
|
||||||
|
JSONWriter(config_path, self.__config_data[config_type.value])
|
||||||
|
|
||||||
|
|
||||||
|
def appDir(
|
||||||
|
self
|
||||||
|
) -> str:
|
||||||
|
|
||||||
|
return self.__config_dir
|
||||||
|
|
||||||
|
|
||||||
|
_config_manager_instance = None
|
||||||
|
|
||||||
|
# Utility function to get config data (thread-safe and validated) from ConfigManager instance.
|
||||||
|
def getValidateAutomationConfigPaths(
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Get validated automation config paths from ConfigManager instance.
|
||||||
|
These function will validate the config paths and return the validated paths in a dict.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Validated automation config paths.
|
||||||
|
"""
|
||||||
|
config_paths = {"run": "", "user": ""}
|
||||||
|
auto_config = _config_manager_instance.get(ConfigType.GLOBAL, "automation", {})
|
||||||
|
for cfg_type in ["run", "user"]:
|
||||||
|
paths = auto_config.get(f"{cfg_type}_path", {}).get("paths", [])
|
||||||
|
index = auto_config.get(f"{cfg_type}_path", {}).get("current", 0)
|
||||||
|
if paths == []:
|
||||||
|
paths.append(os.path.join(_config_manager_instance.appDir(), f"{cfg_type}.json"))
|
||||||
|
if index < 0:
|
||||||
|
index = 0
|
||||||
|
if index >= len(paths):
|
||||||
|
index = len(paths) - 1
|
||||||
|
config_paths[cfg_type] = paths[index]
|
||||||
|
data = {"current": index, "paths": paths}
|
||||||
|
auto_config[f"{cfg_type}_path"] = data
|
||||||
|
_config_manager_instance.set(ConfigType.GLOBAL, "automation", auto_config)
|
||||||
|
return config_paths
|
||||||
|
|
||||||
|
def getBaseConfigDir(
|
||||||
|
) -> str:
|
||||||
|
|
||||||
|
return _config_manager_instance.appDir()
|
||||||
|
|
||||||
|
# Singleton instance of ConfigManager.
|
||||||
|
_instance_lock = threading.Lock()
|
||||||
|
def instance(
|
||||||
|
config_dir: str = ""
|
||||||
|
) -> ConfigManager:
|
||||||
|
"""
|
||||||
|
Initialize ConfigManager singleton instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_dir (str): Config directory.
|
||||||
|
"""
|
||||||
|
global _config_manager_instance
|
||||||
|
with _instance_lock:
|
||||||
|
if _config_manager_instance is None:
|
||||||
|
_config_manager_instance = ConfigManager(config_dir)
|
||||||
|
else:
|
||||||
|
if config_dir == "":
|
||||||
|
return _config_manager_instance
|
||||||
|
if _config_manager_instance.appDir() != config_dir:
|
||||||
|
raise ValueError(
|
||||||
|
"ConfigManager 的实例已初始化,不能使用不同的配置目录。")
|
||||||
|
return _config_manager_instance
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
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 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 = None
|
|
||||||
self.__readConfig()
|
|
||||||
|
|
||||||
|
|
||||||
def __readConfig(
|
|
||||||
self
|
|
||||||
):
|
|
||||||
|
|
||||||
try:
|
|
||||||
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:
|
|
||||||
raise Exception(f"Unknown error occurred while reading config file: {e}") from e
|
|
||||||
|
|
||||||
|
|
||||||
def getConfigs(
|
|
||||||
self
|
|
||||||
) -> dict:
|
|
||||||
|
|
||||||
return self.__config_data.copy()
|
|
||||||
|
|
||||||
|
|
||||||
def getConfig(
|
|
||||||
self,
|
|
||||||
key: str
|
|
||||||
) -> Any:
|
|
||||||
|
|
||||||
config = self.__config_data.get(key, {})
|
|
||||||
return copy.deepcopy(config)
|
|
||||||
|
|
||||||
|
|
||||||
def get(
|
|
||||||
self,
|
|
||||||
key: str,
|
|
||||||
default: Any = None
|
|
||||||
) -> Any:
|
|
||||||
|
|
||||||
keys = key.split('/')
|
|
||||||
current = self.__config_data
|
|
||||||
for k in keys:
|
|
||||||
if isinstance(current, dict) and k in current:
|
|
||||||
current = current[k]
|
|
||||||
else:
|
|
||||||
return default
|
|
||||||
return copy.deepcopy(current)
|
|
||||||
|
|
||||||
|
|
||||||
def hasConfig(
|
|
||||||
self,
|
|
||||||
key: str
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
return self.getConfig(key) != {}
|
|
||||||
|
|
||||||
|
|
||||||
def reReadConfig(
|
|
||||||
self
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
return self.__readConfig()
|
|
||||||
|
|
||||||
|
|
||||||
def configPath(
|
|
||||||
self
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
return self.__config_path
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
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 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,
|
|
||||||
config_path: str,
|
|
||||||
config_data: dict
|
|
||||||
):
|
|
||||||
|
|
||||||
self.__config_path = config_path
|
|
||||||
self.__config_data = config_data.copy() if config_data is not None else {}
|
|
||||||
self.__writeConfig()
|
|
||||||
|
|
||||||
|
|
||||||
def __writeConfig(
|
|
||||||
self
|
|
||||||
):
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(self.__config_path, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(self.__config_data, f, indent=4, sort_keys=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(
|
|
||||||
self,
|
|
||||||
configs: dict
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
self.__config_data = configs
|
|
||||||
return self.__writeConfig()
|
|
||||||
|
|
||||||
|
|
||||||
def setConfig(
|
|
||||||
self,
|
|
||||||
key: str,
|
|
||||||
value: dict
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
self.__config_data[key] = value
|
|
||||||
return self.__writeConfig()
|
|
||||||
|
|
||||||
|
|
||||||
def set(
|
|
||||||
self,
|
|
||||||
key: str,
|
|
||||||
value: Any
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
keys = key.replace("\\", "/").split("/")
|
|
||||||
current = self.__config_data
|
|
||||||
for k in keys[:-1]:
|
|
||||||
if k not in current or not isinstance(current[k], dict):
|
|
||||||
current[k] = {}
|
|
||||||
current = current[k]
|
|
||||||
current[keys[-1]] = value
|
|
||||||
return self.__writeConfig()
|
|
||||||
|
|
||||||
|
|
||||||
def reWriteConfig(
|
|
||||||
self
|
|
||||||
) -> bool:
|
|
||||||
|
|
||||||
return self.__writeConfig()
|
|
||||||
|
|
||||||
|
|
||||||
def configPath(
|
|
||||||
self
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
return self.__config_path
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (c) 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 json
|
||||||
|
|
||||||
|
|
||||||
|
class JSONReader:
|
||||||
|
"""
|
||||||
|
JSON reader class.
|
||||||
|
|
||||||
|
This class is used to read JSON file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_path (str): The path of JSON file.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> print(open("config.json", "r", encoding="utf-8").read())
|
||||||
|
{
|
||||||
|
"key1": {
|
||||||
|
"key2": "value1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>>> json_reader = JSONReader("config.json")
|
||||||
|
>>> data = json_reader.data()
|
||||||
|
>>> data["key1"]["key2"]
|
||||||
|
"value1"
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
json_path: str
|
||||||
|
):
|
||||||
|
|
||||||
|
self.__json_path = os.path.abspath(json_path)
|
||||||
|
self.__json_data = None
|
||||||
|
self.__read()
|
||||||
|
|
||||||
|
|
||||||
|
def __read(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.__json_path, 'r', encoding='utf-8') as file:
|
||||||
|
self.__json_data = json.load(file)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise Exception(f"文件不存在: {self.__json_path}") from e
|
||||||
|
except PermissionError as e:
|
||||||
|
raise Exception(f"没有足够的权限读取文件: {self.__json_path}") from e
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise Exception(f"JSON 解析错误: {self.__json_path}") from e
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"读取文件时发生未知错误: {e}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def read(
|
||||||
|
self
|
||||||
|
) -> bool:
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.__read()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def data(
|
||||||
|
self
|
||||||
|
) -> dict:
|
||||||
|
|
||||||
|
return self.__json_data.copy()
|
||||||
|
|
||||||
|
|
||||||
|
def path(
|
||||||
|
self
|
||||||
|
) -> str:
|
||||||
|
|
||||||
|
return self.__json_path
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (c) 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 json
|
||||||
|
|
||||||
|
|
||||||
|
class JSONWriter:
|
||||||
|
"""
|
||||||
|
JSON writer class.
|
||||||
|
|
||||||
|
This class is used to write JSON file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_path (str): The path of JSON file.
|
||||||
|
json_data (dict): The JSON data to be written.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> json_data = {
|
||||||
|
... "key1": {
|
||||||
|
... "key2": "value1"
|
||||||
|
... }
|
||||||
|
... }
|
||||||
|
>>> json_writer = JSONWriter("config.json", json_data)
|
||||||
|
>>> print(open("config.json", "r", encoding="utf-8").read())
|
||||||
|
{
|
||||||
|
"key1": {
|
||||||
|
"key2": "value1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
json_path: str,
|
||||||
|
json_data: dict
|
||||||
|
):
|
||||||
|
|
||||||
|
self.__json_path = os.path.abspath(json_path)
|
||||||
|
self.__json_data = json_data.copy() if json_data is not None else {}
|
||||||
|
self.__write()
|
||||||
|
|
||||||
|
|
||||||
|
def __write(
|
||||||
|
self
|
||||||
|
):
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.__json_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(self.__json_data, f, indent=4, sort_keys=False)
|
||||||
|
except PermissionError as e:
|
||||||
|
raise Exception(f"没有足够的权限写入文件: {self.__json_path}") from e
|
||||||
|
except IOError as e:
|
||||||
|
raise Exception(f"写入文件时发生 IO 错误: {self.__json_path}") from e
|
||||||
|
except TypeError as e:
|
||||||
|
raise Exception(f"JSON 数据包含无法 JSON 序列化的类型: {e}") from e
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"写入文件时发生未知错误: {e}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def write(
|
||||||
|
self
|
||||||
|
) -> bool:
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.__write()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def path(
|
||||||
|
self
|
||||||
|
) -> str:
|
||||||
|
|
||||||
|
return self.__json_path
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
Utils module for the AutoLibrary project.
|
Utils module for the AutoLibrary project.
|
||||||
|
|
||||||
Here are the classes and modules in this package:
|
Here are the classes and modules in this package:
|
||||||
- ConfigReader: Configuration reader class for the AutoLibrary project.
|
- ConfigManager: Configuration manager class for the AutoLibrary project.
|
||||||
- ConfigWriter: Configuration writer class for the AutoLibrary project.
|
- JSONReader: JSON reader class for the AutoLibrary project.
|
||||||
|
- JSONWriter: JSON writer class for the AutoLibrary project.
|
||||||
"""
|
"""
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This folder is used to store the template config files.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"library": {
|
||||||
|
"host_url": "http://10.1.20.7",
|
||||||
|
"login_url": "/login"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"auto_captcha": true,
|
||||||
|
"max_attempt": 3
|
||||||
|
},
|
||||||
|
"web_driver": {
|
||||||
|
"driver_type": "edge",
|
||||||
|
"driver_path": "",
|
||||||
|
"headless": false
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"run_mode": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"groups": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
This folder is used to store the template files.
|
||||||
|
|
||||||
|
Directory structure:
|
||||||
|
|
||||||
|
templates
|
||||||
|
|─── configs // template config files
|
||||||
Reference in New Issue
Block a user