From 2a0802b22f5451e6198b14aa8bfd6fc09545383b Mon Sep 17 00:00:00 2001 From: the_og Date: Tue, 7 Jul 2026 20:26:00 -0400 Subject: [PATCH] bcc.py: unify paste/drop collision handling via shared _import_server() prompt --- bcc.py | 87 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/bcc.py b/bcc.py index 599eaac..35f4f29 100644 --- a/bcc.py +++ b/bcc.py @@ -374,6 +374,9 @@ class ServerEditor(QFrame): self.spawn_btn.setToolTip("Spawn the server for 3 s and report whether it starts cleanly") self.spawn_btn.clicked.connect(self._test_spawn) self.spawn_btn.setVisible(False) + self.logs_btn = QPushButton("View logs") + self.logs_btn.setToolTip("Open this server's MCP log in a read-only, auto-tailing viewer") + self.logs_btn.clicked.connect(self._view_logs) self.details_btn = QPushButton("Details ▸") self.details_btn.setCheckable(True) self.details_btn.toggled.connect(self._toggle_diag) @@ -384,6 +387,7 @@ class ServerEditor(QFrame): dep.addWidget(self.fix_btn) dep.addWidget(self.test_btn) dep.addWidget(self.spawn_btn) + dep.addWidget(self.logs_btn) dep.addWidget(self.details_btn) dep.addWidget(recheck) outer.addLayout(dep) @@ -728,6 +732,13 @@ class ServerEditor(QFrame): elif self.diag_card.isVisible(): self.diag_text.setPlainText(self._full_diag_text()) + def _view_logs(self): + name = self.current_name() + if not name: + return + dlg = LogViewerDialog(self.window(), name) + dlg.exec() + # --------------------------------------------------------------------------- # # Arguments editor: one line = one argument, with a numbered gutter so that @@ -1104,7 +1115,7 @@ class MainWindow(QMainWindow): self.full_config: dict = {} self.servers: list[core.ServerEntry] = [] self.current_profile: core.Profile | None = None - self._loaded_mtime: float | None = None + self._loaded_stat: core.ConfigStat | None = None self.dirty = False self._suppress_table = False self._suppress_sel = False @@ -1380,7 +1391,7 @@ class MainWindow(QMainWindow): return self.full_config = cfg repaired = True - self._loaded_mtime = core.config_mtime(profile.path) + self._loaded_stat = core.config_fingerprint(profile.path) self.current_profile = profile self.servers = core.extract_servers(self.full_config) self.dirty = False @@ -1620,34 +1631,38 @@ class MainWindow(QMainWindow): self._refresh_tables(select_index=min(idx, len(self.servers) - 1)) self._mark_dirty() + def _import_server(self, name: str, data: dict) -> tuple[bool, bool]: + """ + Add a pasted/dropped `name`/`data` server to self.servers, resolving + a name collision with an explicit prompt (never a silent overwrite). + + Returns (added, replaced). + """ + existing = {s.name: i for i, s in enumerate(self.servers)} + if name in existing: + ans = QMessageBox.question( + self, + "Server exists", + f"“{name}” already exists. Replace it?\n\nYes = replace · No = keep both (renamed)", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + ) + if ans == QMessageBox.StandardButton.Yes: + self.servers[existing[name]].data = data + return False, True + name = core.resolve_name_collision(name, {s.name for s in self.servers}) + self.servers.append(core.ServerEntry(name, data, True)) + return True, False + def paste_json(self): dlg = PasteDialog(self) if dlg.exec() != QDialog.DialogCode.Accepted or not dlg.result_servers: return self._push_undo() added, replaced = 0, 0 - existing = {s.name: i for i, s in enumerate(self.servers)} for name, data in dlg.result_servers.items(): - if name in existing: - ans = QMessageBox.question( - self, - "Server exists", - f"“{name}” already exists. Replace it?\n\nYes = replace · No = keep both (renamed)", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, - ) - if ans == QMessageBox.StandardButton.Yes: - self.servers[existing[name]].data = data - replaced += 1 - continue - new = f"{name}-2" - k = 3 - names = {s.name for s in self.servers} - while new in names: - new = f"{name}-{k}" - k += 1 - name = new - self.servers.append(core.ServerEntry(name, data, True)) - added += 1 + a, r = self._import_server(name, data) + added += int(a) + replaced += int(r) self._refresh_tables(select_index=len(self.servers) - 1) self._mark_dirty() self.status.setText(f"Imported {added} added, {replaced} replaced. Review and Save.") @@ -1719,11 +1734,14 @@ class MainWindow(QMainWindow): return # Stale-file check: if the file changed on disk since we loaded it, prompt. - disk_mtime = core.config_mtime(self.current_profile.path) + # Compare mtime AND size (not mtime alone) so a concurrent external write + # that lands within the mtime resolution window, or that restores the + # original mtime, still gets caught. + disk_stat = core.config_fingerprint(self.current_profile.path) if ( - disk_mtime is not None - and self._loaded_mtime is not None - and disk_mtime != self._loaded_mtime + disk_stat is not None + and self._loaded_stat is not None + and disk_stat != self._loaded_stat ): changed_keys, server_diff = core.external_change_summary( self.full_config, self.current_profile.path @@ -1744,7 +1762,7 @@ class MainWindow(QMainWindow): QMessageBox.critical(self, "Save failed", str(e)) return self.full_config = fresh - self._loaded_mtime = core.config_mtime(self.current_profile.path) + self._loaded_stat = core.config_fingerprint(self.current_profile.path) self.current_profile.config_exists = True self.dirty = False self.save_btn.setEnabled(False) @@ -1762,7 +1780,7 @@ class MainWindow(QMainWindow): except Exception as e: QMessageBox.critical(self, "Save failed", str(e)) return - self._loaded_mtime = core.config_mtime(self.current_profile.path) + self._loaded_stat = core.config_fingerprint(self.current_profile.path) self.current_profile.config_exists = True self.dirty = False self.save_btn.setEnabled(False) @@ -1835,15 +1853,16 @@ class MainWindow(QMainWindow): QMessageBox.warning(self, "Couldn't import", f"{Path(path).name}:\n{ex}") continue self._push_undo() + added, replaced = 0, 0 for name, data in servers.items(): - names = {s.name for s in self.servers} - if name in names: - name = f"{name}-imported" - self.servers.append(core.ServerEntry(name, data, True)) + a, r = self._import_server(name, data) + added += int(a) + replaced += int(r) self._refresh_tables(select_index=len(self.servers) - 1) self._mark_dirty() self.status.setText( - f"Imported {len(servers)} server(s) from {Path(path).name}. Review and Save." + f"Imported {added} added, {replaced} replaced from {Path(path).name}. " + "Review and Save." ) break