Skip to content

Commit 8c94c28

Browse files
committed
Performance optimization and auto-release notes\n\n- Implement concurrent bulk email verification (5 threads) for 5x speed increase.\n- Move single email verification to background thread to fix Not Responding UI freeze.\n- Add graceful shutdown handling for bulk operations.\n- Update GitHub Actions to auto-generate release body from commit messages.
1 parent 26dd752 commit 8c94c28

File tree

2 files changed

+112
-35
lines changed

2 files changed

+112
-35
lines changed

.github/workflows/build_and_release.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,28 @@ jobs:
6262
git tag ${{ steps.tag_version.outputs.new_tag }}
6363
git push origin ${{ steps.tag_version.outputs.new_tag }}
6464
65+
- name: Generate Release Notes
66+
id: release_notes
67+
run: |
68+
# Get the last tag before the new one we just created
69+
previous_tag=$(git describe --tags --abbrev=0 ${{ steps.tag_version.outputs.new_tag }}^ 2>/dev/null || echo "")
70+
71+
if [ -z "$previous_tag" ]; then
72+
# If no previous tag, get log from the beginning
73+
git log --pretty=format:"- %s" > release_notes.txt
74+
else
75+
# Get log from previous tag to new tag
76+
git log ${previous_tag}..HEAD --pretty=format:"- %s" > release_notes.txt
77+
fi
78+
79+
cat release_notes.txt
80+
6581
- name: Create Release
6682
uses: softprops/action-gh-release@v1
6783
with:
6884
tag_name: ${{ steps.tag_version.outputs.new_tag }}
6985
name: Release ${{ steps.tag_version.outputs.new_tag }}
86+
body_path: release_notes.txt
7087
draft: false
7188
prerelease: false
7289

src/gui.py

Lines changed: 95 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -88,33 +88,75 @@ def show_status_help(self):
8888
help_dialog.setStandardButtons(QtWidgets.QMessageBox.Ok)
8989
help_dialog.exec_()
9090

91+
import concurrent.futures
92+
93+
class SingleVerificationWorker(QtCore.QObject):
94+
finished = QtCore.pyqtSignal(str, bool)
95+
96+
def __init__(self, email):
97+
super().__init__()
98+
self.email = email
99+
100+
def run(self):
101+
try:
102+
domain = self.email.split('@')[1]
103+
if not has_mx_record(domain):
104+
self.finished.emit("Domain does not have MX records", False)
105+
return
106+
107+
if not verify_email_smtp(self.email):
108+
self.finished.emit("SMTP verification failed! Email is not valid.", False)
109+
else:
110+
self.finished.emit("Email is valid and appears to be reachable", True)
111+
except Exception as e:
112+
self.finished.emit(f"Error: {str(e)}", False)
113+
91114
class BulkVerificationThread(QtCore.QThread):
92115
result_signal = QtCore.pyqtSignal(str, str)
93116
all_done = QtCore.pyqtSignal()
94117

95118
def __init__(self, emails):
96119
super().__init__()
97120
self.emails = emails
121+
self.is_running = True
98122

99123
def run(self):
100-
for email in self.emails:
101-
if not email:
102-
continue
124+
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
125+
future_to_email = {executor.submit(self.verify_single_email, email): email for email in self.emails if email}
126+
127+
for future in concurrent.futures.as_completed(future_to_email):
128+
if not self.is_running:
129+
executor.shutdown(wait=False, cancel_futures=True)
130+
break
131+
132+
email = future_to_email[future]
133+
try:
134+
status = future.result()
135+
except Exception as e:
136+
status = f"Error: {str(e)}"
103137

104-
try:
105-
if not is_valid_email_syntax(email):
106-
status = "Invalid (Syntax)"
107-
elif not has_mx_record(email.split('@')[1]):
108-
status = "Invalid (No MX)"
109-
elif not verify_email_smtp(email):
110-
status = "Invalid (SMTP)"
111-
else:
112-
status = "Valid"
113-
except Exception as e:
114-
status = f"Error: {str(e)}"
138+
self.result_signal.emit(email, status)
115139

116-
self.result_signal.emit(email, status)
117-
self.all_done.emit() # Signal completion after all emails
140+
self.all_done.emit()
141+
142+
def verify_single_email(self, email):
143+
try:
144+
if not is_valid_email_syntax(email):
145+
return "Invalid (Syntax)"
146+
147+
domain = email.split('@')[1]
148+
if not has_mx_record(domain):
149+
return "Invalid (No MX)"
150+
151+
if not verify_email_smtp(email):
152+
return "Invalid (SMTP)"
153+
154+
return "Valid"
155+
except Exception as e:
156+
return f"Error: {str(e)}"
157+
158+
def stop(self):
159+
self.is_running = False
118160

119161
class EmailValidatorApp(QtWidgets.QWidget):
120162
def __init__(self):
@@ -124,7 +166,8 @@ def __init__(self):
124166
self.verifying_timer = QtCore.QTimer()
125167
self.verifying_counter = 1
126168
self.verifying_timer.timeout.connect(self.update_verifying_text)
127-
self.verification_thread = None
169+
self.bulk_thread = None
170+
self.single_worker_thread = None
128171

129172
def init_ui(self):
130173
self.setWindowTitle("KnowEmail")
@@ -232,10 +275,14 @@ def bulk_verify(self):
232275
self.results_dialog.show()
233276

234277
# Start verification in background
235-
self.verification_thread = BulkVerificationThread(emails)
236-
self.verification_thread.result_signal.connect(self.update_results)
237-
self.verification_thread.all_done.connect(self.show_completion_popup) # Add this
238-
self.verification_thread.start()
278+
if self.bulk_thread and self.bulk_thread.isRunning():
279+
self.bulk_thread.stop()
280+
self.bulk_thread.wait()
281+
282+
self.bulk_thread = BulkVerificationThread(emails)
283+
self.bulk_thread.result_signal.connect(self.update_results)
284+
self.bulk_thread.all_done.connect(self.show_completion_popup)
285+
self.bulk_thread.start()
239286

240287
def show_completion_popup(self):
241288
QtWidgets.QMessageBox.information(
@@ -266,25 +313,38 @@ def validate_email(self):
266313
if not is_valid_email_syntax(email):
267314
QtWidgets.QMessageBox.warning(self, "Error", "Invalid email syntax")
268315
return
316+
269317
self.verifying_counter = 1
270318
self.verifying_timer.start(500)
271319
self.result_label.setText("Verifying...")
272-
QtCore.QTimer.singleShot(100, lambda: self.verify_in_background(email))
320+
self.validate_button.setEnabled(False)
321+
self.email_input.setEnabled(False)
322+
323+
# Create worker and thread for async execution
324+
self.single_worker_thread = QtCore.QThread()
325+
self.worker = SingleVerificationWorker(email)
326+
self.worker.moveToThread(self.single_worker_thread)
327+
328+
# Connect signals
329+
self.single_worker_thread.started.connect(self.worker.run)
330+
self.worker.finished.connect(self.handle_single_verification_result)
331+
self.worker.finished.connect(self.single_worker_thread.quit)
332+
self.worker.finished.connect(self.worker.deleteLater)
333+
self.single_worker_thread.finished.connect(self.single_worker_thread.deleteLater)
334+
335+
self.single_worker_thread.start()
336+
337+
def handle_single_verification_result(self, message, is_valid):
338+
self.verifying_timer.stop()
339+
self.result_label.setText("")
340+
self.validate_button.setEnabled(True)
341+
self.email_input.setEnabled(True)
342+
343+
self.show_popup(message)
273344

274345
def verify_in_background(self, email):
275-
domain = email.split('@')[1]
276-
if not has_mx_record(domain):
277-
self.show_popup("Domain does not have MX records")
278-
return
279-
try:
280-
if not verify_email_smtp(email):
281-
self.show_popup("SMTP verification failed! Email is not valid.")
282-
else:
283-
self.show_popup("Email is valid and appears to be reachable")
284-
except Exception as e:
285-
self.show_popup(f"Error: {str(e)}")
286-
finally:
287-
self.verifying_timer.stop()
346+
# Deprecated: Logic moved to SingleVerificationWorker
347+
pass
288348

289349
def show_popup(self, message):
290350
self.verifying_timer.stop()

0 commit comments

Comments
 (0)