#!/usr/bin/env python3 import subprocess import platform from pathlib import Path import threading from concurrent.futures import ThreadPoolExecutor, as_completed import tkinter as tk from tkinter import filedialog, messagebox, ttk # ---------- Core ping + conversion logic ---------- def ping(host: str, timeout_ms: int = 1000) -> bool: """ Return True if host responds to a single ping, False otherwise. Cross-platform flags: - Windows: ping -n 1 -w - Unix: ping -c 1 -W """ system = platform.system().lower() if system.startswith("win"): cmd = ["ping", "-n", "1", "-w", str(timeout_ms), host] else: sec = max(1, (timeout_ms + 999) // 1000) cmd = ["ping", "-c", "1", "-W", str(sec), host] try: result = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) return result.returncode == 0 except Exception: return False def convert_txt_to_csv(txt_path: Path, csv_path: Path, header: str): """ Converts lines from a .txt file to a single-column CSV where spaces in a line are replaced with commas (matching your batch behavior). """ if not txt_path.exists(): return lines = [line.rstrip("\r\n") for line in txt_path.read_text(encoding="utf-8", errors="ignore").splitlines()] with csv_path.open("w", encoding="utf-8", newline="") as f: f.write(header + "\n") for line in lines: transformed = line.replace(" ", ",") f.write(transformed + "\n") # ---------- GUI App with parallel pings ---------- class App(tk.Tk): def __init__(self): super().__init__() self.title("Dinger Pinger — GUI (Parallel)") self.geometry("780x520") self.minsize(680, 460) self.selected_file: Path | None = None self.stop_requested = False # Top controls top = ttk.Frame(self, padding=10) top.pack(side=tk.TOP, fill=tk.X) self.select_btn = ttk.Button(top, text="Select IP List & Run", command=self.select_and_run) self.select_btn.pack(side=tk.LEFT) self.timeout_label = ttk.Label(top, text="Timeout (ms):") self.timeout_label.pack(side=tk.LEFT, padx=(16, 4)) self.timeout_var = tk.StringVar(value="1000") self.timeout_entry = ttk.Entry(top, width=8, textvariable=self.timeout_var) self.timeout_entry.pack(side=tk.LEFT) self.conc_label = ttk.Label(top, text="Concurrency:") self.conc_label.pack(side=tk.LEFT, padx=(16, 4)) self.concurrency_var = tk.IntVar(value=64) # IO-bound; high default is fine self.conc_spin = ttk.Spinbox( top, from_=1, to=1024, textvariable=self.concurrency_var, width=6, wrap=True ) self.conc_spin.pack(side=tk.LEFT) self.stop_btn = ttk.Button(top, text="Stop", command=self.request_stop, state=tk.DISABLED) self.stop_btn.pack(side=tk.LEFT, padx=(16, 0)) # Progress + status prog_frame = ttk.Frame(self, padding=(10, 0, 10, 10)) prog_frame.pack(side=tk.TOP, fill=tk.X) self.progress = ttk.Progressbar(prog_frame, mode="determinate") self.progress.pack(fill=tk.X) self.status_var = tk.StringVar(value="Select a file to begin.") self.status = ttk.Label(prog_frame, textvariable=self.status_var) self.status.pack(anchor="w", pady=(6, 0)) # Output text area txt_frame = ttk.Frame(self, padding=(10, 0, 10, 10)) txt_frame.pack(fill=tk.BOTH, expand=True) self.text = tk.Text(txt_frame, wrap="word") self.text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) yscroll = ttk.Scrollbar(txt_frame, orient="vertical", command=self.text.yview) yscroll.pack(side=tk.RIGHT, fill=tk.Y) self.text.configure(yscrollcommand=yscroll.set) # Style try: self.style = ttk.Style(self) if "vista" in self.style.theme_names(): self.style.theme_use("vista") elif "clam" in self.style.theme_names(): self.style.theme_use("clam") except Exception: pass # Locks for thread-safe logs/counters self._log_lock = threading.Lock() def log(self, msg: str): with self._log_lock: self.text.insert(tk.END, msg + "\n") self.text.see(tk.END) self.update_idletasks() def set_running_state(self, running: bool): self.select_btn.config(state=tk.DISABLED if running else tk.NORMAL) self.stop_btn.config(state=tk.NORMAL if running else tk.DISABLED) self.timeout_entry.config(state=tk.DISABLED if running else tk.NORMAL) self.conc_spin.config(state=tk.DISABLED if running else tk.NORMAL) def request_stop(self): self.stop_requested = True self.status_var.set("Stopping after current tasks...") def select_and_run(self): file_path = filedialog.askopenfilename( title="Select IP list (text file)", filetypes=[("Text files", "*.txt"), ("All files", "*.*")] ) if not file_path: return self.selected_file = Path(file_path) try: timeout = int(self.timeout_var.get().strip()) if timeout <= 0: raise ValueError except ValueError: messagebox.showerror("Invalid Timeout", "Please enter a positive integer for timeout (ms).") return try: conc = int(self.concurrency_var.get()) if conc < 1: raise ValueError except ValueError: messagebox.showerror("Invalid Concurrency", "Concurrency must be an integer ≥ 1.") return self.stop_requested = False self.text.delete("1.0", tk.END) self.status_var.set(f"Running pings from {self.selected_file} with concurrency={conc} ...") self.set_running_state(True) thread = threading.Thread( target=self.run_pings_parallel_worker, args=(self.selected_file, timeout, conc), daemon=True ) thread.start() def run_pings_parallel_worker(self, ip_list_path: Path, timeout_ms: int, concurrency: int): # Read / prepare try: lines = ip_list_path.read_text(encoding="utf-8", errors="ignore").splitlines() except Exception as e: self.after(0, lambda: messagebox.showerror("Read Error", f"Failed to read file:\n{e}")) self.after(0, lambda: self.set_running_state(False)) return out_dir = ip_list_path.parent success_file = out_dir / "successful.txt" error_file = out_dir / "error.txt" processed_success_csv = out_dir / "processed.csv" processed_error_csv = out_dir / "processederror.csv" targets = [] for raw in lines: ip = raw.strip() if not ip or ip.startswith("#") or ip.startswith(";"): continue targets.append(ip) total = len(targets) self.after(0, lambda: self.progress.configure(maximum=total, value=0)) self.after(0, lambda: self.status_var.set(f"{total} target(s) queued...")) # Collect results in-memory (thread-safe) ok_list = [] err_list = [] ok_count = 0 err_count = 0 progress_count = 0 counters_lock = threading.Lock() def _task(ip: str): """Single task executed in thread pool.""" if self.stop_requested: # Early exit; treat as not processed (won't be counted) return None, None result = ping(ip, timeout_ms=timeout_ms) return ip, result try: with ThreadPoolExecutor(max_workers=concurrency, thread_name_prefix="ping") as ex: futures = [] # Submit gradually so Stop can halt further submissions for ip in targets: if self.stop_requested: break futures.append(ex.submit(_task, ip)) # If stop is requested now, cancel any futures not yet started if self.stop_requested: for f in futures: f.cancel() # Best effort; there may be a few already running. for f in as_completed(futures): if self.stop_requested and f.cancelled(): continue try: ip, result = f.result() except Exception as e: ip, result = None, None self.after(0, lambda e=e: self.log(f"[WORKER ERROR] {e}")) if ip is None: continue with counters_lock: progress_count += 1 if result: ok_list.append(ip) ok_count += 1 else: err_list.append(ip) err_count += 1 # UI updates self.after(0, lambda pc=progress_count: self.progress.configure(value=pc)) self.after( 0, lambda ip=ip, result=result, pc=progress_count, t=total, ok=ok_count, err=err_count: (self.log(f"[OK] {ip}") if result else self.log(f"[ERR] {ip}"), self.status_var.set(f"Processed {pc}/{t} • OK: {ok} • ERR: {err}")) ) except Exception as e: self.after(0, lambda e=e: self.log(f"[POOL ERROR] {e}")) # Write outputs once try: success_file.write_text("\n".join(ok_list) + ("\n" if ok_list else ""), encoding="utf-8") error_file.write_text("\n".join(err_list) + ("\n" if err_list else ""), encoding="utf-8") except Exception as e: self.after(0, lambda e=e: self.log(f"[WRITE ERROR] {e}")) # Convert to CSVs (space->comma), matching original behavior try: convert_txt_to_csv(success_file, processed_success_csv, header="Successful") convert_txt_to_csv(error_file, processed_error_csv, header="Error") except Exception as e: self.after(0, lambda e=e: self.log(f"[CSV ERROR] {e}")) def finish_ui(): self.set_running_state(False) done_msg = "Stopped (no new tasks submitted)." if self.stop_requested else "Done." self.log("") self.log(done_msg) self.log("Outputs:") self.log(f" {success_file.resolve()}") self.log(f" {error_file.resolve()}") self.log(f" {processed_success_csv.resolve()}") self.log(f" {processed_error_csv.resolve()}") self.status_var.set(f"{done_msg} • OK: {ok_count} • ERR: {err_count}") self.after(0, finish_ui) if __name__ == "__main__": App().mainloop()