main_gui.py 25 KB

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