gui_main.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. '''
  4. @Contact : liuyuqi.gov@msn.cn
  5. @Time : 2023/04/27 02:55:59
  6. @License : Copyright © 2017-2022 liuyuqi. All Rights Reserved.
  7. @Desc : repo_sync GUI入口
  8. '''
  9. import sys
  10. import os
  11. import threading
  12. import subprocess
  13. try:
  14. from PyQt5.QtWidgets import (
  15. QApplication, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
  16. QRadioButton, QPushButton, QButtonGroup, QGroupBox, QMessageBox,
  17. QLineEdit, QScrollArea, QFileDialog, QFormLayout, QCheckBox, QTextEdit,
  18. QComboBox, QDialog, QDialogButtonBox, QGridLayout, QToolButton, QListWidget, QListWidgetItem
  19. )
  20. from PyQt5.QtCore import Qt, pyqtSignal, QObject
  21. HAS_QT = True
  22. except ImportError:
  23. HAS_QT = False
  24. print("PyQt5 not installed, GUI mode not available")
  25. # 直接导入repo_sync模块
  26. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
  27. try:
  28. from repo_sync.repo_sync import RepoSync
  29. from repo_sync.version import __version__
  30. except ImportError:
  31. print("无法导入repo_sync模块,尝试直接导入...")
  32. try:
  33. from repo_sync import RepoSync
  34. from repo_sync.version import __version__
  35. except ImportError:
  36. print("导入repo_sync模块失败,请确保repo_sync已正确安装")
  37. __version__ = "未知"
  38. # 创建一个空的RepoSync类作为替代
  39. class RepoSync:
  40. def __init__(self, params):
  41. self.params = params
  42. def run(self):
  43. print("RepoSync模块未找到,无法执行操作")
  44. try:
  45. from dotenv import load_dotenv, set_key, find_dotenv, dotenv_values
  46. except ImportError:
  47. print("python-dotenv未安装,请使用pip install python-dotenv安装")
  48. sys.exit(1)
  49. import json
  50. import uuid
  51. # 确保.env文件存在
  52. def ensure_env_file():
  53. env_file = find_dotenv()
  54. if not env_file:
  55. # 创建空的.env文件
  56. env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.env')
  57. with open(env_file, 'w') as f:
  58. f.write("# repo_sync配置文件\n")
  59. return env_file
  60. # Explorer路径获取
  61. try:
  62. import win32com.client
  63. def get_active_explorer_path():
  64. shell = win32com.client.Dispatch("Shell.Application")
  65. for window in shell.Windows():
  66. if window.Name in ["文件资源管理器", "Windows Explorer"]:
  67. return window.Document.Folder.Self.Path
  68. return os.getcwd()
  69. except ImportError:
  70. def get_active_explorer_path():
  71. return os.getcwd()
  72. # 命令执行信号类
  73. class CommandSignals(QObject):
  74. output = pyqtSignal(str)
  75. finished = pyqtSignal(int)
  76. class MainTab(QWidget):
  77. def __init__(self, parent=None):
  78. super().__init__(parent)
  79. layout = QVBoxLayout()
  80. # 路径选择
  81. path_layout = QHBoxLayout()
  82. self.path_label = QLabel("Local Path: ")
  83. self.path_edit = QLineEdit()
  84. self.path_edit.setText(get_active_explorer_path())
  85. self.path_btn = QPushButton("Browse...")
  86. self.path_btn.clicked.connect(self.choose_path)
  87. path_layout.addWidget(self.path_label)
  88. path_layout.addWidget(self.path_edit)
  89. path_layout.addWidget(self.path_btn)
  90. layout.addLayout(path_layout)
  91. # 操作
  92. op_group = QGroupBox("Operation:")
  93. op_layout = QHBoxLayout()
  94. self.op_buttons = QButtonGroup(self)
  95. for i, op in enumerate(["create", "push", "pull", "clone", "delete"]):
  96. btn = QRadioButton(op.capitalize())
  97. if i == 0:
  98. btn.setChecked(True)
  99. self.op_buttons.addButton(btn, i)
  100. op_layout.addWidget(btn)
  101. op_group.setLayout(op_layout)
  102. layout.addWidget(op_group)
  103. # 平台
  104. pf_group = QGroupBox("Platform:")
  105. pf_layout = QHBoxLayout()
  106. self.pf_buttons = QButtonGroup(self)
  107. self.platforms = ["github", "gitlab", "gitee", "gitcode", "git.yoq.me", "coding", "aliyun", "cnb"]
  108. for i, pf in enumerate(self.platforms):
  109. btn = QRadioButton(pf.capitalize())
  110. if i == 0:
  111. btn.setChecked(True)
  112. self.pf_buttons.addButton(btn, i)
  113. pf_layout.addWidget(btn)
  114. pf_group.setLayout(pf_layout)
  115. layout.addWidget(pf_group)
  116. # 账户选择
  117. account_layout = QHBoxLayout()
  118. account_layout.addWidget(QLabel("Account:"))
  119. self.account_combo = QComboBox()
  120. self.account_combo.setMinimumWidth(200)
  121. account_layout.addWidget(self.account_combo)
  122. account_layout.addStretch()
  123. layout.addLayout(account_layout)
  124. # 按钮区域 (run和cancel)
  125. btn_layout = QHBoxLayout()
  126. self.run_btn = QPushButton("run it")
  127. self.run_btn.clicked.connect(self.run_repo_sync)
  128. self.cancel_btn = QPushButton("Cancel")
  129. self.cancel_btn.clicked.connect(self.cancel_operation)
  130. self.cancel_btn.setEnabled(False)
  131. btn_layout.addWidget(self.run_btn)
  132. btn_layout.addWidget(self.cancel_btn)
  133. layout.addLayout(btn_layout)
  134. # 命令执行结果
  135. result_group = QGroupBox("Execution Result:")
  136. result_layout = QVBoxLayout()
  137. self.result_text = QTextEdit()
  138. self.result_text.setReadOnly(True)
  139. result_layout.addWidget(self.result_text)
  140. result_group.setLayout(result_layout)
  141. layout.addWidget(result_group)
  142. self.setLayout(layout)
  143. # 命令执行相关
  144. self.process = None
  145. self.command_signals = CommandSignals()
  146. self.command_signals.output.connect(self.update_output)
  147. self.command_signals.finished.connect(self.process_finished)
  148. # 平台变更时更新账户列表
  149. self.pf_buttons.buttonClicked.connect(self.update_account_list)
  150. self.update_account_list()
  151. def choose_path(self):
  152. path = QFileDialog.getExistingDirectory(self, "Select Directory")
  153. if path:
  154. self.path_edit.setText(path)
  155. def update_account_list(self):
  156. self.account_combo.clear()
  157. pf_id = self.pf_buttons.checkedId()
  158. platform = self.platforms[pf_id]
  159. # 读取所有账户
  160. accounts = self.get_platform_accounts(platform)
  161. # 找出启用的账户
  162. enabled_account = "default"
  163. env_values = dotenv_values(find_dotenv())
  164. for account in accounts:
  165. if account != "default" and env_values.get(f"{platform}_{account}_enabled", "").lower() == "true":
  166. enabled_account = account
  167. break
  168. # 添加账户到下拉框,启用的账户放在最前面
  169. if enabled_account in accounts:
  170. accounts.remove(enabled_account)
  171. self.account_combo.addItem(f"{enabled_account} (启用中)")
  172. for account in accounts:
  173. self.account_combo.addItem(account)
  174. # 默认选择启用的账户
  175. self.account_combo.setCurrentIndex(0)
  176. def get_platform_accounts(self, platform):
  177. # 读取.env文件中的所有配置
  178. env_values = dotenv_values(find_dotenv())
  179. accounts = set()
  180. # 默认账户
  181. accounts.add("default")
  182. # 查找带有账户名的配置
  183. prefix = f"{platform}_"
  184. for key in env_values.keys():
  185. if key.startswith(prefix) and "_" in key[len(prefix):]:
  186. account_name = key[len(prefix):].split("_")[0]
  187. if account_name != "default" and account_name != "":
  188. accounts.add(account_name)
  189. return sorted(list(accounts))
  190. def run_repo_sync(self):
  191. repo_path = self.path_edit.text().strip()
  192. if not repo_path:
  193. QMessageBox.warning(self, "Warning", "Please select a local path.")
  194. return
  195. op_id = self.op_buttons.checkedId()
  196. pf_id = self.pf_buttons.checkedId()
  197. op = ["create", "push", "pull", "clone", "delete"][op_id]
  198. pf = self.platforms[pf_id]
  199. # 获取选择的账户名(去掉可能的"(启用中)"后缀)
  200. account_text = self.account_combo.currentText()
  201. account = account_text.split(" (")[0] if " (" in account_text else account_text
  202. # 清空结果区域
  203. self.result_text.clear()
  204. # 检查平台配置
  205. load_dotenv()
  206. token_key = f"{pf}_{account}_token" if account != "default" else f"{pf}_token"
  207. if not os.getenv(token_key):
  208. QMessageBox.warning(self, "Warning", f"Please configure {pf} token for account '{account}' in Settings tab first.")
  209. return
  210. # 构建命令
  211. cmd = [sys.executable, "-m", "repo_sync"]
  212. cmd.append(op)
  213. cmd.extend(["-p", pf])
  214. cmd.extend(["-repo_path", repo_path])
  215. # 如果不是默认账户,需要设置环境变量
  216. env = os.environ.copy()
  217. if account != "default":
  218. # 读取账户配置
  219. env_values = dotenv_values(find_dotenv())
  220. for key, value in env_values.items():
  221. if key.startswith(f"{pf}_{account}_"):
  222. field = key[len(f"{pf}_{account}_"):]
  223. env[f"{pf}_{field}"] = value
  224. # 执行命令
  225. self.run_btn.setEnabled(False)
  226. self.cancel_btn.setEnabled(True)
  227. self.result_text.append(f"Running: {' '.join(cmd)}\n")
  228. # 在新线程中执行命令
  229. self.process_thread = threading.Thread(
  230. target=self.run_process,
  231. args=(cmd, env)
  232. )
  233. self.process_thread.daemon = True
  234. self.process_thread.start()
  235. def run_process(self, cmd, env=None):
  236. try:
  237. self.process = subprocess.Popen(
  238. cmd,
  239. stdout=subprocess.PIPE,
  240. stderr=subprocess.STDOUT,
  241. text=True,
  242. bufsize=1,
  243. universal_newlines=True,
  244. env=env
  245. )
  246. # 读取输出
  247. for line in self.process.stdout:
  248. self.command_signals.output.emit(line)
  249. self.process.wait()
  250. self.command_signals.finished.emit(self.process.returncode)
  251. except Exception as e:
  252. self.command_signals.output.emit(f"Error: {str(e)}")
  253. self.command_signals.finished.emit(1)
  254. def update_output(self, text):
  255. self.result_text.append(text)
  256. # 自动滚动到底部
  257. self.result_text.verticalScrollBar().setValue(
  258. self.result_text.verticalScrollBar().maximum()
  259. )
  260. def process_finished(self, return_code):
  261. self.process = None
  262. self.run_btn.setEnabled(True)
  263. self.cancel_btn.setEnabled(False)
  264. if return_code == 0:
  265. self.result_text.append("\nOperation completed successfully.")
  266. else:
  267. self.result_text.append(f"\nOperation failed with return code {return_code}.")
  268. def cancel_operation(self):
  269. if self.process:
  270. self.process.terminate()
  271. self.result_text.append("\nOperation cancelled by user.")
  272. # 添加账户对话框
  273. class AddAccountDialog(QDialog):
  274. def __init__(self, platform, parent=None):
  275. super().__init__(parent)
  276. self.platform = platform
  277. self.setWindowTitle(f"Add {platform.capitalize()} Account")
  278. self.resize(400, 200)
  279. layout = QVBoxLayout()
  280. form_layout = QFormLayout()
  281. self.account_name = QLineEdit()
  282. form_layout.addRow("Account Name:", self.account_name)
  283. # 根据平台添加相应字段
  284. self.fields = {}
  285. platform_fields = {
  286. "github": ["username", "token", "private"],
  287. "gitlab": ["host", "username", "token", "private"],
  288. "gitee": ["username", "token", "private"],
  289. "gitcode": ["username", "token", "private"],
  290. "git.yoq.me": ["username", "token", "private"],
  291. "coding": ["username", "token", "project", "private"],
  292. "aliyun": ["compoanyid", "group_id", "username", "token", "private"],
  293. "cnb": ["username", "token", "private"]
  294. }
  295. for field in platform_fields.get(platform, ["username", "token"]):
  296. if field == "private":
  297. widget = QCheckBox()
  298. widget.setChecked(True)
  299. else:
  300. widget = QLineEdit()
  301. if field in ["token", "password"]:
  302. widget.setEchoMode(QLineEdit.Password)
  303. self.fields[field] = widget
  304. form_layout.addRow(f"{field.capitalize()}:", widget)
  305. layout.addLayout(form_layout)
  306. # 按钮
  307. buttons = QDialogButtonBox(
  308. QDialogButtonBox.Ok | QDialogButtonBox.Cancel
  309. )
  310. buttons.accepted.connect(self.accept)
  311. buttons.rejected.connect(self.reject)
  312. layout.addWidget(buttons)
  313. self.setLayout(layout)
  314. def get_account_data(self):
  315. data = {"name": self.account_name.text()}
  316. for field, widget in self.fields.items():
  317. if isinstance(widget, QCheckBox):
  318. data[field] = widget.isChecked()
  319. else:
  320. data[field] = widget.text()
  321. return data
  322. class SettingsTab(QWidget):
  323. def __init__(self, parent=None):
  324. super().__init__(parent)
  325. # 确保.env文件存在
  326. ensure_env_file()
  327. # 初始化account_lists字典
  328. self.account_lists = {}
  329. self.init_ui()
  330. def init_ui(self):
  331. layout = QVBoxLayout()
  332. # 创建平台选择标签页
  333. self.platform_tabs = QTabWidget()
  334. # 平台配置
  335. self.platform_configs = {
  336. "github": ["username", "token", "private"],
  337. "gitlab": ["host", "username", "token", "private"],
  338. "gitee": ["username", "token", "private"],
  339. "gitcode": ["username", "token", "private"],
  340. "git.yoq.me": ["username", "token", "private"],
  341. "coding": ["username", "token", "project", "private"],
  342. "aliyun": ["compoanyid", "group_id", "username", "token", "private"],
  343. "cnb": ["username", "token", "private"]
  344. }
  345. # 为每个平台创建标签页
  346. self.platform_pages = {}
  347. for platform in self.platform_configs.keys():
  348. page = QWidget()
  349. page_layout = QVBoxLayout()
  350. # 账户管理区域
  351. account_group = QGroupBox("账户管理")
  352. account_layout = QVBoxLayout()
  353. # 账户列表
  354. accounts_list_layout = QHBoxLayout()
  355. # 左侧:账户列表
  356. account_list = QListWidget()
  357. account_list.setMinimumWidth(200)
  358. account_list.itemClicked.connect(lambda item, p=platform: self.select_account(p, item))
  359. account_list.setStyleSheet("""
  360. QListWidget::item:selected { background-color: #a6d8ff; }
  361. QListWidget::item[enabled="true"] { font-weight: bold; color: #0066cc; }
  362. """)
  363. accounts_list_layout.addWidget(account_list)
  364. self.account_lists[platform] = account_list
  365. # 右侧:账户详情
  366. account_details = QWidget()
  367. account_form = QFormLayout()
  368. account_details.setLayout(account_form)
  369. accounts_list_layout.addWidget(account_details, 1)
  370. account_layout.addLayout(accounts_list_layout)
  371. # 账户操作按钮
  372. buttons_layout = QHBoxLayout()
  373. add_btn = QPushButton("添加账户")
  374. add_btn.clicked.connect(lambda checked=False, p=platform: self.add_account(p))
  375. delete_btn = QPushButton("删除账户")
  376. delete_btn.clicked.connect(lambda checked=False, p=platform: self.delete_account(p))
  377. enable_btn = QPushButton("设为启用")
  378. enable_btn.clicked.connect(lambda checked=False, p=platform: self.enable_account(p))
  379. buttons_layout.addWidget(add_btn)
  380. buttons_layout.addWidget(delete_btn)
  381. buttons_layout.addWidget(enable_btn)
  382. buttons_layout.addStretch()
  383. account_layout.addLayout(buttons_layout)
  384. account_group.setLayout(account_layout)
  385. page_layout.addWidget(account_group)
  386. # 保存页面引用
  387. self.platform_pages[platform] = {
  388. "page": page,
  389. "form": account_form,
  390. "details": account_details
  391. }
  392. page.setLayout(page_layout)
  393. self.platform_tabs.addTab(page, platform.capitalize())
  394. layout.addWidget(self.platform_tabs)
  395. # 保存按钮
  396. self.save_btn = QPushButton("保存设置")
  397. self.save_btn.clicked.connect(self.save_settings)
  398. layout.addWidget(self.save_btn, alignment=Qt.AlignCenter)
  399. self.setLayout(layout)
  400. # 加载设置
  401. try:
  402. self.load_settings()
  403. except Exception as e:
  404. print(f"加载设置时出错: {e}")
  405. import traceback
  406. traceback.print_exc()
  407. # 连接标签页变更事件
  408. self.platform_tabs.currentChanged.connect(self.tab_changed)
  409. def tab_changed(self, index):
  410. try:
  411. # 使用有序字典确保顺序一致性
  412. platforms = list(self.platform_configs.keys())
  413. if index < 0 or index >= len(platforms):
  414. return
  415. platform = platforms[index]
  416. self.update_account_details(platform)
  417. except Exception as e:
  418. print(f"Error in tab_changed: {e}")
  419. def load_settings(self):
  420. # 读取.env文件
  421. try:
  422. env_file = find_dotenv()
  423. if not env_file:
  424. print("找不到.env文件,创建一个空的")
  425. env_file = ensure_env_file()
  426. env_values = dotenv_values(env_file)
  427. print(f"成功读取.env文件: {env_file}")
  428. # 为每个平台加载账户
  429. for platform in self.platform_configs.keys():
  430. if platform not in self.account_lists:
  431. print(f"警告: 找不到平台 {platform} 的账户列表")
  432. continue
  433. account_list = self.account_lists[platform]
  434. account_list.clear()
  435. # 查找该平台的所有账户
  436. accounts = self.get_platform_accounts(platform, env_values)
  437. # 添加到列表
  438. for account in accounts:
  439. item = QListWidgetItem(account)
  440. # 标记默认账户
  441. if account == "default":
  442. item.setData(Qt.UserRole, "default")
  443. # 检查是否有其他账户被设为启用
  444. is_enabled = True
  445. for other_account in accounts:
  446. if other_account != "default" and self.is_account_enabled(platform, other_account, env_values):
  447. is_enabled = False
  448. break
  449. item.setData(Qt.UserRole + 1, is_enabled)
  450. else:
  451. item.setData(Qt.UserRole, account)
  452. item.setData(Qt.UserRole + 1, self.is_account_enabled(platform, account, env_values))
  453. # 设置启用状态的显示
  454. if item.data(Qt.UserRole + 1):
  455. item.setData(Qt.UserRole + 2, "true")
  456. item.setText(f"{account} (启用中)")
  457. account_list.addItem(item)
  458. # 选择第一个账户
  459. if account_list.count() > 0:
  460. account_list.setCurrentRow(0)
  461. self.select_account(platform, account_list.item(0))
  462. except Exception as e:
  463. print(f"加载设置时出错: {e}")
  464. import traceback
  465. traceback.print_exc()
  466. def is_account_enabled(self, platform, account, env_values=None):
  467. """检查账户是否被设为启用"""
  468. if env_values is None:
  469. env_values = dotenv_values(find_dotenv())
  470. if account == "default":
  471. # 默认账户,检查是否有其他账户被设为启用
  472. for key in env_values.keys():
  473. if key.startswith(f"{platform}_") and "_enabled" in key and env_values[key].lower() == "true":
  474. return False
  475. return True
  476. else:
  477. # 其他账户,检查是否有enabled标记
  478. return env_values.get(f"{platform}_{account}_enabled", "").lower() == "true"
  479. def get_platform_accounts(self, platform, env_values=None):
  480. if env_values is None:
  481. env_values = dotenv_values(find_dotenv())
  482. accounts = set()
  483. # 默认账户
  484. accounts.add("default")
  485. # 查找带有账户名的配置
  486. prefix = f"{platform}_"
  487. for key in env_values.keys():
  488. if key.startswith(prefix) and "_" in key[len(prefix):]:
  489. account_name = key[len(prefix):].split("_")[0]
  490. if account_name != "default" and account_name != "":
  491. accounts.add(account_name)
  492. return sorted(list(accounts))
  493. def select_account(self, platform, item):
  494. if not item:
  495. return
  496. account = item.data(Qt.UserRole)
  497. form = self.platform_pages[platform]["form"]
  498. # 清空表单
  499. while form.rowCount() > 0:
  500. form.removeRow(0)
  501. # 读取账户配置
  502. env_values = dotenv_values(find_dotenv())
  503. # 创建表单项
  504. self.field_widgets = {}
  505. for field in self.platform_configs[platform]:
  506. key = f"{platform}_{account}_{field}" if account != "default" else f"{platform}_{field}"
  507. value = env_values.get(key, "")
  508. if field == "private":
  509. widget = QCheckBox()
  510. widget.setChecked(value.lower() != "false")
  511. else:
  512. widget = QLineEdit()
  513. if field in ["token", "password"]:
  514. widget.setEchoMode(QLineEdit.Password)
  515. widget.setText(value)
  516. self.field_widgets[key] = widget
  517. form.addRow(f"{field.capitalize()}:", widget)
  518. def add_account(self, platform):
  519. dialog = AddAccountDialog(platform, self)
  520. if dialog.exec_() == QDialog.Accepted:
  521. account_data = dialog.get_account_data()
  522. account_name = account_data["name"]
  523. if not account_name:
  524. QMessageBox.warning(self, "Warning", "Account name cannot be empty.")
  525. return
  526. # 更新.env文件
  527. env_file = find_dotenv()
  528. if not env_file:
  529. env_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
  530. for field, value in account_data.items():
  531. if field == "name":
  532. continue
  533. key = f"{platform}_{account_name}_{field}"
  534. if isinstance(value, bool):
  535. value = str(value).lower()
  536. set_key(env_file, key, value)
  537. # 重新加载设置
  538. self.load_settings()
  539. # 选择新账户
  540. account_list = self.account_lists[platform]
  541. for i in range(account_list.count()):
  542. if account_list.item(i).data(Qt.UserRole) == account_name:
  543. account_list.setCurrentRow(i)
  544. self.select_account(platform, account_list.item(i))
  545. break
  546. def delete_account(self, platform):
  547. account_list = self.account_lists[platform]
  548. current_item = account_list.currentItem()
  549. if not current_item:
  550. return
  551. account = current_item.data(Qt.UserRole)
  552. if account == "default":
  553. QMessageBox.warning(self, "Warning", "Cannot delete the default account.")
  554. return
  555. reply = QMessageBox.question(
  556. self,
  557. "Confirm Deletion",
  558. f"Are you sure you want to delete the account '{account}' for {platform}?",
  559. QMessageBox.Yes | QMessageBox.No,
  560. QMessageBox.No
  561. )
  562. if reply == QMessageBox.Yes:
  563. # 删除.env中的相关配置
  564. env_file = find_dotenv()
  565. env_values = dotenv_values(env_file)
  566. prefix = f"{platform}_{account}_"
  567. keys_to_remove = [k for k in env_values.keys() if k.startswith(prefix)]
  568. # 重写.env文件,排除要删除的键
  569. with open(env_file, 'w') as f:
  570. for k, v in env_values.items():
  571. if k not in keys_to_remove:
  572. f.write(f"{k}={v}\n")
  573. # 重新加载设置
  574. self.load_settings()
  575. def enable_account(self, platform):
  576. account_list = self.account_lists[platform]
  577. current_item = account_list.currentItem()
  578. if not current_item:
  579. return
  580. account = current_item.data(Qt.UserRole)
  581. # 读取账户配置
  582. env_file = find_dotenv()
  583. env_values = dotenv_values(env_file)
  584. # 先清除所有账户的启用状态
  585. for key in list(env_values.keys()):
  586. if key.startswith(f"{platform}_") and key.endswith("_enabled"):
  587. del env_values[key]
  588. # 设置当前账户为启用
  589. if account != "default":
  590. set_key(env_file, f"{platform}_{account}_enabled", "true")
  591. # 将账户配置复制到默认配置
  592. account_config = {}
  593. prefix = f"{platform}_{account}_"
  594. for key, value in env_values.items():
  595. if key.startswith(prefix):
  596. field = key[len(prefix):]
  597. if field != "enabled": # 不复制enabled标记
  598. account_config[field] = value
  599. # 更新默认配置
  600. for field, value in account_config.items():
  601. default_key = f"{platform}_{field}"
  602. set_key(env_file, default_key, value)
  603. QMessageBox.information(
  604. self,
  605. "Success",
  606. f"Account '{account}' has been enabled for {platform}."
  607. )
  608. # 重新加载设置
  609. self.load_settings()
  610. def update_account_details(self, platform):
  611. account_list = self.account_lists[platform]
  612. current_item = account_list.currentItem()
  613. if current_item:
  614. self.select_account(platform, current_item)
  615. def save_settings(self):
  616. env_file = find_dotenv()
  617. if not env_file:
  618. env_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
  619. # 保存当前显示的账户配置
  620. platform = list(self.platform_configs.keys())[self.platform_tabs.currentIndex()]
  621. account_list = self.account_lists[platform]
  622. current_item = account_list.currentItem()
  623. if current_item and hasattr(self, 'field_widgets'):
  624. account = current_item.data(Qt.UserRole)
  625. for key, widget in self.field_widgets.items():
  626. if key.startswith(f"{platform}_{account}"):
  627. if isinstance(widget, QCheckBox):
  628. value = str(widget.isChecked()).lower()
  629. else:
  630. value = widget.text().strip()
  631. if value:
  632. set_key(env_file, key, value)
  633. QMessageBox.information(self, "Success", "Settings saved successfully!")
  634. # 如果修改的是启用的账户,更新默认配置
  635. if current_item and current_item.data(Qt.UserRole + 1):
  636. self.enable_account(platform)
  637. class AboutTab(QWidget):
  638. def __init__(self, parent=None):
  639. super().__init__(parent)
  640. layout = QVBoxLayout()
  641. layout.addWidget(QLabel(f"repo_sync tools v{__version__}"))
  642. layout.addWidget(QLabel("作者: liuyuqi.gov@msn.cn"))
  643. layout.addWidget(QLabel("GitHub: https://github.com/jianboy/repo_sync"))
  644. layout.addWidget(QLabel("\n功能说明:"))
  645. layout.addWidget(QLabel("- 支持多个代码托管平台"))
  646. layout.addWidget(QLabel("- 支持创建/推送/拉取/克隆/删除操作"))
  647. layout.addWidget(QLabel("- 自动获取资源管理器当前路径"))
  648. layout.addWidget(QLabel("- 配置信息保存在.env文件中"))
  649. layout.addWidget(QLabel("- 支持每个平台配置多个账户"))
  650. layout.addWidget(QLabel("- 命令行执行结果实时显示"))
  651. self.setLayout(layout)
  652. class RepoSyncMainWindow(QTabWidget):
  653. def __init__(self):
  654. super().__init__()
  655. self.setWindowTitle('repo_sync tools v1.12')
  656. self.resize(800, 700)
  657. self.main_tab = MainTab()
  658. self.addTab(self.main_tab, '主界面')
  659. self.settings_tab = SettingsTab()
  660. self.addTab(self.settings_tab, '设置')
  661. self.about_tab = AboutTab()
  662. self.addTab(self.about_tab, '关于')
  663. def main():
  664. """GUI主入口函数"""
  665. if not HAS_QT:
  666. print("PyQt5 not installed. Please install it with: pip install PyQt5")
  667. print("Running in fallback mode...")
  668. # 这里可以添加一个简单的命令行界面作为后备
  669. return
  670. try:
  671. # 确保.env文件存在
  672. ensure_env_file()
  673. app = QApplication(sys.argv)
  674. window = RepoSyncMainWindow()
  675. window.show()
  676. sys.exit(app.exec_())
  677. except Exception as e:
  678. print(f"Error starting GUI: {str(e)}")
  679. import traceback
  680. traceback.print_exc()
  681. if __name__ == '__main__':
  682. main()