docs: update CLAUDE.md, README, and Vietnamese guide for migration step

- CLAUDE.md: document migrator.py algorithm and junction-removal rationale
- README.md: pipeline now 5 steps, add --skip-migrate flag, add migrator.py
  to folder structure, update test count 142 -> 158
- docs/huong-dan-su-dung.md: 5-step pipeline table, new glossary entry,
  updated footer version note
This commit is contained in:
Tran G. (Revernomad) Khoa
2026-04-14 15:11:39 +07:00
parent 48637ffe90
commit b24828ac68
3 changed files with 45 additions and 20 deletions

View File

@@ -117,6 +117,20 @@ Pass 2 builds `ok_disk_names` — the set of disk names that already match the s
**`build_server_index` progress callback:** Accepts an optional `progress_fn(current, total, name)` callback. `step_fetch` in `run.py` uses this to print `Indexing N/M: @FolderName` every 25 folders so the log never goes silent during the server scan phase. The library itself never calls `print` — the caller owns the I/O. **`build_server_index` progress callback:** Accepts an optional `progress_fn(current, total, name)` callback. `step_fetch` in `run.py` uses this to print `Indexing N/M: @FolderName` every 25 folders so the log never goes silent during the server scan phase. The library itself never calls `print` — the caller owns the I/O.
### `migrator.py` — mod group migration
Before `step_fetch` runs, `step_migrate` moves locally-downloaded mod folders to match the group assignments in the new `comparison.json`. This avoids re-downloading mods that already exist on disk under a different group when presets are switched (e.g. `A``A_v1`).
**Algorithm:**
1. `_build_local_index(downloads)` — scans every `downloads/{group}/@Folder`, reads `meta.cpp` to extract `publishedid`, builds `{steam_id → (group, path)}` and `{norm_name → (group, path)}` maps.
2. `_build_target_list(comparison)` — flattens `comparison.json` into `[(new_group, steam_id, mod_name)]`.
3. For each target: locate mod on disk (steam_id first, normalised name fallback); skip if already in correct group or destination exists; remove stale junction from `arma_dir` if present; move folder with `shutil.move`.
**Junction removal is critical:** a stale junction (target moved away) still has the reparse point attribute, so `_is_junction()` returns `True` and `link_group` would skip it as `already_linked` without recreating it at the new path. Removing the junction before the move lets `step_link` recreate it correctly.
**CLI:** `python run.py --skip-migrate` bypasses the step if needed.
### `update_mods.py` — orphan file removal ### `update_mods.py` — orphan file removal
After downloading updated files, `update_mods.py` compares every file in the local mod folder against the server's file list and **deletes any local files that no longer exist on the server**. This prevents stale `.pbo` or `.bisign` files from accumulating when a mod's content changes upstream. Each removed file is logged as `[-] orphan removed: <rel_path>` and the final summary line includes an orphan count. The orphan check runs even when no files need downloading (e.g. timestamps match but the local folder has extras). After downloading updated files, `update_mods.py` compares every file in the local mod folder against the server's file list and **deletes any local files that no longer exist on the server**. This prevents stale `.pbo` or `.bisign` files from accumulating when a mod's content changes upstream. Each removed file is logged as `[-] orphan removed: <rel_path>` and the final summary line includes an orphan count. The orphan check runs even when no files need downloading (e.g. timestamps match but the local folder has extras).

View File

@@ -12,7 +12,7 @@ cp config.template.json config.json # fill in server URL + credentials + arma_
python check_deps.py # verify dependencies python check_deps.py # verify dependencies
# Day-to-day: full pipeline # Day-to-day: full pipeline
python run.py # parse → compare → download → link python run.py # parse → compare → migrate → download → link
# GUI (recommended) # GUI (recommended)
python gui.py python gui.py
@@ -24,7 +24,7 @@ python sync_missing.py # retry mods that were absent from server
python check_names.py --fix # fix folder name mismatches python check_names.py --fix # fix folder name mismatches
# Testing # Testing
python test_suite.py # 142 tests (network tests auto-skip if offline) python test_suite.py # 158 tests (network tests auto-skip if offline)
``` ```
--- ---
@@ -133,29 +133,34 @@ Place your Arma 3 Launcher preset exports (`.html`) into the `modlist_html/` fol
python run.py python run.py
``` ```
This runs all four steps in sequence: This runs all five steps in sequence:
``` ```
Step 1/4: Parse presets — modlist_html/*.html -> modlist_json/*.json Step 1/5: Parse presets — modlist_html/*.html -> modlist_json/*.json
Step 2/4: Compare presets — produces modlist_json/comparison.json Step 2/5: Compare presets — produces modlist_json/comparison.json
Step 3/4: Fetch mods — downloads from server -> downloads/ Step 3/5: Migrate mod groups — moves existing folders to match new group assignments
Step 4/4: Link mods creates junctions/symlinks in Arma 3 Server dir Step 4/5: Fetch mods — downloads from server -> downloads/
Step 5/5: Link mods — creates junctions/symlinks in Arma 3 Server dir
``` ```
The **migrate step** avoids re-downloading mods that already exist on disk when you switch preset versions (e.g. `A``A_v1`). It matches mods by steam ID (via `meta.cpp`) and moves the folder to the correct group, removing any stale junction first so the link step can re-create it at the new path.
### Skip flags ### Skip flags
```bash ```bash
python run.py --skip-fetch --skip-link # parse + compare only python run.py --skip-fetch --skip-link # parse + compare + migrate only
python run.py --skip-parse --skip-compare --skip-fetch # link only python run.py --skip-parse --skip-compare --skip-fetch # link only
python run.py --skip-parse --skip-compare --skip-fetch --group shared python run.py --skip-parse --skip-compare --skip-fetch --group shared
python run.py --skip-migrate # skip auto-migration
``` ```
| Flag | Skips | | Flag | Skips |
|------|-------| |------|-------|
| `--skip-parse` | Step 1 (HTML parsing) | | `--skip-parse` | Step 1 (HTML parsing) |
| `--skip-compare` | Step 2 (preset comparison) | | `--skip-compare` | Step 2 (preset comparison) |
| `--skip-fetch` | Step 3 (downloading) | | `--skip-migrate` | Step 3 (mod group migration) |
| `--skip-link` | Step 4 (linking) | | `--skip-fetch` | Step 4 (downloading) |
| `--skip-link` | Step 5 (linking) |
| `--group GROUP` | Link step: only link this one group (e.g. `shared`) | | `--group GROUP` | Link step: only link this one group (e.g. `shared`) |
> **Safe to re-run.** Every step is idempotent — existing files are skipped, already-linked mods are skipped. > **Safe to re-run.** Every step is idempotent — existing files are skipped, already-linked mods are skipped.
@@ -526,7 +531,7 @@ The same functionality is available as the **Clean Orphans** tab in the GUI.
### run.py ### run.py
Orchestrator that chains all four pipeline steps. Described in [Quick Start](#quick-start--full-pipeline) above. Orchestrator that chains all five pipeline steps. Described in [Quick Start](#quick-start--full-pipeline) above.
--- ---
@@ -602,6 +607,7 @@ arma-modlist-tools/
| |- linker.py # Junction/symlink manager | |- linker.py # Junction/symlink manager
| |- reporter.py # Missing-mod report builder | |- reporter.py # Missing-mod report builder
| |- cleaner.py # Orphan folder detection | |- cleaner.py # Orphan folder detection
| |- migrator.py # Mod group migration (move folders to match comparison.json)
| |- config.py # config.json loader | |- config.py # config.json loader
| |- compat.py # OS detection + encoding fix | |- compat.py # OS detection + encoding fix
| |
@@ -652,7 +658,7 @@ arma-modlist-tools/
|- check_names.py # Diagnose and fix folder name / steam_id issues |- check_names.py # Diagnose and fix folder name / steam_id issues
|- clean_orphans.py # Find and delete orphaned mod folders |- clean_orphans.py # Find and delete orphaned mod folders
|- check_deps.py # Dependency checker |- check_deps.py # Dependency checker
|- test_suite.py # Test suite (142 tests) |- test_suite.py # Test suite (158 tests)
``` ```
--- ---
@@ -691,7 +697,7 @@ arma-modlist-tools/
## Running Tests ## Running Tests
The test suite covers all modules with 142 tests. Network tests (section 15) auto-skip when the server is unreachable. The test suite covers all modules with 158 tests. Network tests auto-skip when the server is unreachable.
```bash ```bash
python test_suite.py python test_suite.py
@@ -713,7 +719,9 @@ python test_suite.py
cleaner 8 tests cleaner 8 tests
e2e — clean_orphans 6 tests (subprocess CLI) e2e — clean_orphans 6 tests (subprocess CLI)
coverage gaps 23 tests (mocked platform branches) coverage gaps 23 tests (mocked platform branches)
gui.views.mods 8 tests (_find_folder matching)
migrator 7 tests (group migration logic)
live server 9 tests (skipped if server unreachable) live server 9 tests (skipped if server unreachable)
------------------------------------------------------------ ------------------------------------------------------------
Results: 142 passed, 0 failed, 0 skipped (142 total) Results: 158 passed, 0 failed, 0 skipped (158 total)
``` ```

View File

@@ -37,7 +37,8 @@ Tài liệu này dành cho người dùng **chưa biết gì** về dự án. B
Ứng dụng sẽ tự động: Ứng dụng sẽ tự động:
- Đọc danh sách mod từ các preset - Đọc danh sách mod từ các preset
- So sánh, tìm mod dùng chung và mod riêng giữa các preset - So sánh, tìm mod dùng chung và mod riêng giữa các preset
- Tải mod từ máy chủ về máy tính của bạn - **Di chuyển** các thư mục mod đã tải về sang đúng nhóm mới (tránh tải lại khi đổi phiên bản preset)
- Tải mod từ máy chủ về máy tính của bạn (chỉ những mod thực sự chưa có)
- Tạo liên kết (junction/symlink) để Arma 3 Server nhận ra các mod - Tạo liên kết (junction/symlink) để Arma 3 Server nhận ra các mod
--- ---
@@ -153,13 +154,14 @@ Giao diện gồm thanh điều hướng bên trái và khu vực nội dung bê
### 5.3 Trạng thái Pipeline ### 5.3 Trạng thái Pipeline
Cột bên phải hiển thị 4 bước của quy trình: Cột bên phải hiển thị 5 bước của quy trình:
| Bước | Mô tả | Dấu hiệu hoàn thành | | Bước | Mô tả | Dấu hiệu hoàn thành |
|------|-------|-------------------| |------|-------|-------------------|
| Phân tích preset | Đọc danh sách mod từ tệp HTML | Có ≥ 2 preset được chọn | | Phân tích preset | Đọc danh sách mod từ tệp HTML | Có ≥ 2 preset được chọn |
| So sánh preset | Tìm mod chung và riêng | Tệp `comparison.json` tồn tại | | So sánh preset | Tìm mod chung và riêng | Tệp `comparison.json` tồn tại |
| Tải mod | Tải tệp mod từ máy chủ | Có thư mục mod trong `downloads/` | | Di chuyển nhóm mod | Chuyển thư mục mod sẵn có sang đúng nhóm mới | Tự động (không cần tải lại) |
| Tải mod | Tải tệp mod thực sự còn thiếu từ máy chủ | Có thư mục mod trong `downloads/` |
| Liên kết với Arma | Tạo junction tới thư mục Arma 3 | Thư mục Arma 3 Server tồn tại | | Liên kết với Arma | Tạo junction tới thư mục Arma 3 | Thư mục Arma 3 Server tồn tại |
Biểu tượng `✓` (xanh) = đã xong, `○` (xám) = chưa xong. Biểu tượng `✓` (xanh) = đã xong, `○` (xám) = chưa xong.
@@ -391,7 +393,7 @@ Hiển thị các đường dẫn đang dùng: URL máy chủ, thư mục Arma,
| Thuật ngữ | Giải thích | | Thuật ngữ | Giải thích |
|-----------|------------| |-----------|------------|
| **Preset** | Tệp HTML xuất từ Arma 3 Launcher chứa danh sách mod | | **Preset** | Tệp HTML xuất từ Arma 3 Launcher chứa danh sách mod |
| **Pipeline** | Chuỗi 4 bước tự động: phân tích → so sánh → tải → liên kết | | **Pipeline** | Chuỗi 5 bước tự động: phân tích → so sánh → di chuyển nhóm → tải → liên kết |
| **Junction / Symlink** | Liên kết thư mục ảo — Arma 3 thấy mod trong thư mục của mình nhưng tệp thực sự nằm ở `downloads/` | | **Junction / Symlink** | Liên kết thư mục ảo — Arma 3 thấy mod trong thư mục của mình nhưng tệp thực sự nằm ở `downloads/` |
| **Shared mods** | Mod xuất hiện trong tất cả preset đã chọn | | **Shared mods** | Mod xuất hiện trong tất cả preset đã chọn |
| **Unique mods** | Mod chỉ có trong một preset cụ thể | | **Unique mods** | Mod chỉ có trong một preset cụ thể |
@@ -401,9 +403,10 @@ Hiển thị các đường dẫn đang dùng: URL máy chủ, thư mục Arma,
| **comparison.json** | Tệp kết quả so sánh preset, lưu danh sách mod theo nhóm | | **comparison.json** | Tệp kết quả so sánh preset, lưu danh sách mod theo nhóm |
| **missing_report.json** | Báo cáo mod có trong preset nhưng chưa có trên máy chủ | | **missing_report.json** | Báo cáo mod có trong preset nhưng chưa có trên máy chủ |
| **downloads/** | Thư mục chứa tệp mod đã tải về | | **downloads/** | Thư mục chứa tệp mod đã tải về |
| **Di chuyển nhóm mod** | Bước tự động chuyển thư mục mod từ nhóm cũ sang nhóm mới theo `comparison.json` — tránh tải lại khi đổi phiên bản preset |
| **Mod thừa (Orphan)** | Thư mục mod còn trong `downloads/` nhưng không còn trong preset nào đang dùng | | **Mod thừa (Orphan)** | Thư mục mod còn trong `downloads/` nhưng không còn trong preset nào đang dùng |
| **config.json** | Tệp cấu hình lưu thông tin máy chủ và đường dẫn | | **config.json** | Tệp cấu hình lưu thông tin máy chủ và đường dẫn |
--- ---
*Phiên bản tài liệu: 2026-04 (cập nhật: thêm Clean Orphans). Nếu có vấn đề, liên hệ người quản trị máy chủ.* *Phiên bản tài liệu: 2026-04 (cập nhật: thêm bước Di chuyển nhóm mod, pipeline 5 bước). Nếu có vấn đề, liên hệ người quản trị máy chủ.*