CI / test (pull_request) Canceled after 0s
Thư mục làm việc vừa là sandbox (agent chỉ đọc/ghi bên trong nó) vừa là kho kiến thức chung của project (file ở gốc được mọi đoạn chat tự đọc). Hai project trỏ vào cùng một thư mục là đọc lẫn dữ liệu của nhau và ghi đè lên nhau — đúng điều docstring đầu core/projects.py nói sandbox sinh ra để ngăn, nhưng không có chỗ nào thực thi: cả ba nơi đặt thư mục đều gán thẳng output_dir rồi lưu. Thêm core.projects.folder_conflict: so theo workspace_dir() chứ không theo output_dir (project chưa đặt thư mục riêng vẫn đang chiếm thư mục quản lý sẵn), chuẩn hoá đường dẫn bằng expanduser + abspath + normcase, và coi thư mục lồng nhau là trùng — đứng ở thư mục cha vẫn với tới được file của project con. Chặn tại cả ba nơi ghi output_dir: nút Đổi ở màn Project, thư mục mirror cloud, và nút chọn thư mục trong tab Cowork. Chặn lúc CHỌN chứ không lúc Lưu, vì nút Lưu chỉ ghi tên/mô tả/chỉ dẫn — chặn ở đó sẽ khoá luôn việc đổi tên một project lỡ đang trùng thư mục. Dữ liệu đã trùng sẵn không bị tự sửa: có nhãn cảnh báo ngay dưới ô Thư mục làm việc, nói rõ trùng với project nào, để người dùng tự quyết. Luật ở module riêng (presentation/workspace/project_folder_rules.py) vì nhét vào project_editing.py sẽ đẩy file đó lên 435 dòng, vượt trần 400 của scripts/check_loc.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
114 lines
5.1 KiB
Python
114 lines
5.1 KiB
Python
"""Luật "mỗi thư mục làm việc chỉ thuộc về MỘT project".
|
|
|
|
Tách khỏi ``project_editing.py`` chứ không nhét thêm vào đó: file kia đã gom
|
|
bốn tính năng và thêm luật này là chạm trần 400 dòng của
|
|
``scripts/check_loc.py``. Đây cũng là một mối quan tâm riêng — nó không nói về
|
|
việc *sửa* một project mà về việc hai project không được giẫm lên nhau.
|
|
|
|
Luật có hai nửa, cố ý không đối xứng:
|
|
|
|
* **Chặn lúc CHỌN.** Ba nơi đặt được thư mục làm việc (nút "Đổi" ở màn Project,
|
|
thư mục cloud, nút chọn thư mục trong tab Cowork) đều đi qua
|
|
:func:`folder_taken_blocked`, để cả ba chặn giống hệt nhau. Không chặn ở
|
|
"Lưu project": nút đó chỉ ghi tên/mô tả/chỉ dẫn, chặn ở đó sẽ khoá luôn việc
|
|
đổi tên một project lỡ đang trùng thư mục.
|
|
* **Cảnh báo cho cái ĐANG sai.** Dữ liệu cũ có thể đã có hai project trỏ vào
|
|
cùng một thư mục, mà nửa trên chỉ chặn từ nay trở đi. Nhãn dưới ô "Thư mục
|
|
làm việc" nói ra điều đó và để người dùng tự đổi — sửa hộ là tự ý đụng vào
|
|
dữ liệu của họ.
|
|
|
|
Phép so trùng nằm ở ``core/projects.py::folder_conflict`` (thuần, không Qt).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from PySide6.QtWidgets import QLabel, QLayout, QMessageBox, QWidget
|
|
|
|
from ...i18n import tr
|
|
from .project_editing import _row_layout_of
|
|
|
|
|
|
def _layout_chua(layout: QLayout, con: QLayout) -> "tuple | None":
|
|
"""``(layout_cha, vị_trí)`` của ``con`` bên trong ``layout``, duyệt đệ quy."""
|
|
for i in range(layout.count()):
|
|
item = layout.itemAt(i)
|
|
ben_trong = item.layout()
|
|
if ben_trong is con:
|
|
return layout, i
|
|
if ben_trong is not None:
|
|
tim = _layout_chua(ben_trong, con)
|
|
if tim is not None:
|
|
return tim
|
|
return None
|
|
|
|
|
|
def folder_taken_blocked(parent: QWidget, path: str, ignore_id: str) -> bool:
|
|
"""``True`` nếu ``path`` đã thuộc project khác — và đã báo cho người dùng.
|
|
|
|
Dùng chung cho cả ba nơi đặt được thư mục làm việc (nút "Đổi" ở màn
|
|
Project, thư mục cloud, và nút chọn thư mục trong tab Cowork), để cả ba
|
|
chặn giống hệt nhau thay vì mỗi nơi tự nghĩ ra một luật.
|
|
|
|
Chặn ở lúc CHỌN chứ không ở lúc Lưu: "Lưu project" chỉ ghi tên, mô tả và
|
|
chỉ dẫn — chặn ở đó sẽ khoá luôn việc đổi tên một project lỡ đang trùng
|
|
thư mục, tức phạt người dùng vì một trạng thái họ chưa kịp sửa.
|
|
"""
|
|
from ...core.projects import folder_conflict
|
|
|
|
khac = folder_conflict(path, ignore_id=ignore_id)
|
|
if khac is None:
|
|
return False
|
|
QMessageBox.warning(parent, tr("workspace.folder_taken_title"),
|
|
tr("workspace.folder_taken_body", name=khac.name,
|
|
folder=str(khac.workspace_dir())))
|
|
return True
|
|
|
|
|
|
|
|
class ProjectFolderRuleMixin:
|
|
"""Nửa giao diện của luật. Trộn vào ``WorkspaceTab``."""
|
|
|
|
def install_project_folder_rule(self) -> None:
|
|
"""Dựng nhãn cảnh báo và nối nó vào việc đổi project.
|
|
|
|
Gọi từ ``install_project_editing``, tức sau khi form đã dựng xong.
|
|
"""
|
|
self._folder_warn_lbl = QLabel()
|
|
self._folder_warn_lbl.setObjectName("warning") # màu lấy từ theme/
|
|
self._folder_warn_lbl.setWordWrap(True)
|
|
self._folder_warn_lbl.hide()
|
|
self._gan_nhan_canh_bao_thu_muc()
|
|
self.project_selected.connect(self._sync_folder_warning)
|
|
|
|
def _gan_nhan_canh_bao_thu_muc(self) -> None:
|
|
"""Chèn nhãn cảnh báo ngay DƯỚI hàng chứa ô Thư mục làm việc.
|
|
|
|
Chèn từ đây thay vì thêm dòng vào ``_build_project_tab``: file
|
|
``ui/workspace_tab.py`` đang vượt trần của ``scripts/check_loc.py``,
|
|
nên mọi dòng mới đều phải tránh nó (cùng lý do nút "Sửa project" được
|
|
chèn bằng ``_row_layout_of``).
|
|
"""
|
|
hang = _row_layout_of(self.folder_lbl)
|
|
cha = self.folder_lbl.parentWidget()
|
|
if hang is None or cha is None or cha.layout() is None:
|
|
return
|
|
tim = _layout_chua(cha.layout(), hang)
|
|
if tim is None:
|
|
return
|
|
layout, vi_tri = tim
|
|
layout.insertWidget(vi_tri + 1, self._folder_warn_lbl)
|
|
|
|
def _sync_folder_warning(self, *_a) -> None:
|
|
"""Hiện/ẩn cảnh báo "thư mục đang dùng chung" theo project đang mở."""
|
|
from ...core.projects import folder_conflict, load_project
|
|
|
|
pid = getattr(self, "_current_id", "")
|
|
project = load_project(pid) if pid else None
|
|
khac = (folder_conflict(project.workspace_dir(), ignore_id=pid)
|
|
if project is not None else None)
|
|
if khac is None:
|
|
self._folder_warn_lbl.hide()
|
|
return
|
|
self._folder_warn_lbl.setText(
|
|
tr("workspace.folder_shared_warning", name=khac.name))
|
|
self._folder_warn_lbl.show()
|