Skip to content

Commit 5a594ae

Browse files
committed
Modified download to go to user home directory, added version number to UI
1 parent 7552b2c commit 5a594ae

38 files changed

+5428
-6913
lines changed
-10.4 KB
Binary file not shown.

pyinstaller_lin/app.py

Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
import os
2+
import sys
3+
import typing
4+
5+
from PyQt5 import QtCore
6+
from PyQt5.QtCore import QObject
7+
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog
8+
9+
from wordleech import Ui_MainWindow
10+
from worker import LeechWorker
11+
12+
13+
# noinspection PyUnresolvedReferences
14+
class AppWindow(QMainWindow, QObject):
15+
def __init__(self):
16+
super().__init__()
17+
self.ui: Ui_MainWindow = Ui_MainWindow()
18+
self.dict_file: typing.Optional[str] = None # The dictionary file
19+
self.source_files: typing.List[str] = [] # The source files to process
20+
21+
# Prepare threads and workers
22+
self.dict_worker: typing.Optional[LeechWorker] = None # Used to load dictionary
23+
self.dict_thread: typing.Optional[QtCore.QThread] = None
24+
self.source_worker: typing.Optional[LeechWorker] = None # Used to load sources
25+
self.source_thread: typing.Optional[QtCore.QThread] = None
26+
self.compare_worker: typing.Optional[LeechWorker] = None # Used to add words to dictionary
27+
self.compare_thread: typing.Optional[QtCore.QThread] = None
28+
self.download_worker: typing.Optional[LeechWorker] = None # Used to download latest dictionary
29+
self.download_thread: typing.Optional[QtCore.QThread] = None
30+
31+
self.lst_found: typing.List[str] = [] # List of new words found
32+
self.lst_add: typing.List[str] = [] # List of new words to add to dictionary
33+
34+
self.init_threads()
35+
self.ui.setupUi(self)
36+
self.bind_widgets()
37+
self.show()
38+
39+
def init_threads(self):
40+
"""
41+
Initialise the various threads used by the app
42+
:return: None
43+
"""
44+
45+
threaded_tasks = ['dict', 'source', 'compare', 'download']
46+
47+
for task in threaded_tasks:
48+
worker = LeechWorker()
49+
worker.status_signal.connect(self.update_status)
50+
worker.progress_signal.connect(self.update_progress)
51+
setattr(self, task + '_worker', worker)
52+
setattr(self, task + '_thread', QtCore.QThread(self))
53+
getattr(self, task + '_worker').moveToThread(getattr(self, task + '_thread'))
54+
55+
# noinspection DuplicatedCode
56+
def bind_widgets(self):
57+
"""
58+
Bind buttons to functions
59+
:return: None
60+
"""
61+
62+
self.ui.btn_load_dict.clicked.connect(self.load_dict)
63+
self.ui.btn_load_source.clicked.connect(self.load_sources)
64+
self.ui.btn_process.clicked.connect(self.process_files)
65+
self.ui.btn_reset.clicked.connect(self.reset)
66+
self.ui.btn_download_dict.clicked.connect(self.download_dict)
67+
self.ui.btn_remove_all.clicked.connect(self.remove_all)
68+
self.ui.btn_remove.clicked.connect(self.remove)
69+
self.ui.btn_add.clicked.connect(self.add)
70+
self.ui.btn_add_all.clicked.connect(self.add_all)
71+
self.ui.btn_generate.clicked.connect(self.write_dict)
72+
73+
def load_dict(self):
74+
"""
75+
Set the filename to load the dictionary from
76+
:return: None
77+
"""
78+
79+
options = QFileDialog.Options()
80+
filename = QFileDialog.getOpenFileName(self, "Choose Dictionary File", "",
81+
"Dictionary File (*.dic)", options=options)
82+
if filename and filename[0] != '':
83+
self.dict_file = str(filename[0])
84+
self.ui.lbl_loaded_dict.setText(os.path.basename(self.dict_file))
85+
self.toggle_process()
86+
else:
87+
self.ui.lbl_loaded_dict.setText('No File Loaded.')
88+
self.dict_file = None
89+
90+
# noinspection DuplicatedCode
91+
def download_dict(self):
92+
"""
93+
Start the thread to download the latest dictionary
94+
:return: None
95+
"""
96+
97+
self.ui.btn_process.setEnabled(False)
98+
self.ui.btn_load_dict.setEnabled(False)
99+
self.ui.btn_load_source.setEnabled(False)
100+
self.download_worker.do_run = True
101+
self.ui.pb_process.setValue(0)
102+
self.download_worker.callback = self.download_complete
103+
self.download_thread.started.connect(self.download_worker.download_latest)
104+
self.download_thread.start()
105+
106+
def download_complete(self, filename: str):
107+
"""
108+
Callback when file download is complete
109+
:param filename: str name of the downloaded file
110+
:return: None
111+
"""
112+
113+
self.dict_file = filename
114+
self.ui.lbl_loaded_dict.setText(self.dict_file)
115+
self.ui.lbl_status.setText('Download complete.')
116+
self.ui.btn_load_dict.setEnabled(True)
117+
self.ui.btn_load_source.setEnabled(True)
118+
self.toggle_process()
119+
120+
def load_sources(self):
121+
"""
122+
Specify the source filenames to process
123+
:return: None
124+
"""
125+
126+
self.source_files.clear()
127+
options = QFileDialog.Options()
128+
filenames = QFileDialog.getOpenFileNames(self, "Choose Source Files", "",
129+
"Portable Document Format (*.pdf)", options=options)
130+
131+
index = 0
132+
if filenames:
133+
if len(filenames[0]) > 0:
134+
file_paths = filenames[0]
135+
self.ui.lbl_loaded_sources.setText('')
136+
for path in file_paths:
137+
self.source_files.append(path)
138+
char = '' if index == 0 else ', '
139+
self.ui.lbl_loaded_sources.setText(
140+
self.ui.lbl_loaded_sources.text() + char + os.path.basename(path))
141+
index += 1
142+
self.toggle_process()
143+
else:
144+
self.source_files = []
145+
self.ui.lbl_loaded_sources.setText('No Sources Loaded.')
146+
147+
def toggle_process(self):
148+
"""
149+
Check whether or not to enable the Process button
150+
:return: None
151+
"""
152+
153+
if self.dict_file is not None and len(self.source_files) > 0:
154+
self.ui.btn_process.setEnabled(True)
155+
156+
# noinspection PyUnresolvedReferences
157+
def process_files(self):
158+
"""
159+
Start processing. Starts by calling the worker which ingests the dictionary
160+
:return: None
161+
"""
162+
163+
self.ui.btn_process.setEnabled(False)
164+
self.ui.btn_download_dict.setEnabled(False)
165+
self.ui.btn_load_dict.setEnabled(False)
166+
self.ui.btn_load_source.setEnabled(False)
167+
self.dict_worker.do_run = True
168+
self.ui.pb_process.setValue(0)
169+
self.dict_worker.target_file = self.dict_file
170+
self.dict_worker.callback = self.process_files_stage_2
171+
self.dict_thread.started.connect(self.dict_worker.load_dict)
172+
self.dict_thread.start()
173+
174+
def process_files_stage_2(self):
175+
"""
176+
Callback after dictionary is ingested.
177+
Starts thread to process source files.
178+
:return: None
179+
"""
180+
181+
self.source_worker.do_run = True
182+
self.source_worker.target_files = self.source_files
183+
self.source_worker.callback = self.do_compare
184+
self.source_thread.started.connect(self.source_worker.load_source)
185+
self.source_thread.start()
186+
187+
def do_compare(self):
188+
"""
189+
Callback after source files are processed.
190+
Starts thread to check for new words.
191+
:return: None
192+
"""
193+
194+
self.compare_worker.do_run = True
195+
self.compare_worker.dict = self.dict_worker.dict
196+
self.compare_worker.source_words = self.source_worker.source_words
197+
self.compare_worker.callback = self.show_results
198+
self.compare_thread.started.connect(self.compare_worker.compare_words)
199+
self.compare_thread.start()
200+
201+
def show_results(self):
202+
"""
203+
Callback after comparison is complete.
204+
Shows new words found
205+
:return: None
206+
"""
207+
208+
self.lst_found = self.compare_worker.new_words
209+
self.sync_lists()
210+
self.ui.grp_results.setEnabled(True)
211+
self.ui.lbl_new.setText('New Words Found ({})'.format(len(self.compare_worker.new_words)))
212+
self.update_status('Files processed.')
213+
self.ui.btn_load_dict.setEnabled(True)
214+
self.ui.btn_load_source.setEnabled(True)
215+
self.terminate_threads()
216+
217+
def terminate_threads(self):
218+
"""
219+
Safely terminates threads and workers
220+
:return: None
221+
"""
222+
223+
# Stop workers
224+
self.dict_worker.do_run = False
225+
self.source_worker.do_run = False
226+
self.compare_worker.do_run = False
227+
self.download_worker.do_run = False
228+
229+
# Stop threads
230+
if self.dict_thread.started:
231+
self.dict_thread.quit()
232+
self.dict_thread.wait()
233+
234+
if self.source_thread.started:
235+
self.source_thread.quit()
236+
self.source_thread.wait()
237+
238+
if self.compare_thread.started:
239+
self.compare_thread.quit()
240+
self.compare_thread.wait()
241+
242+
if self.download_thread.started:
243+
self.download_thread.quit()
244+
self.download_thread.wait()
245+
246+
def reset(self):
247+
"""
248+
Resets app to initial state
249+
:return: None
250+
"""
251+
252+
self.terminate_threads()
253+
254+
# Re-init threads
255+
self.init_threads()
256+
257+
self.lst_add.clear()
258+
self.lst_found.clear()
259+
260+
# Reset UI
261+
self.ui.lst_found.clear()
262+
self.ui.lst_add.clear()
263+
self.ui.grp_results.setEnabled(False)
264+
self.ui.btn_process.setEnabled(False)
265+
self.ui.btn_generate.setEnabled(False)
266+
self.ui.btn_download_dict.setEnabled(True)
267+
self.ui.lbl_loaded_sources.setText('No Sources Loaded.')
268+
self.ui.lbl_loaded_dict.setText('No File Loaded.')
269+
self.ui.lbl_new.setText('New Words Found')
270+
self.ui.lbl_add.setText('Words To Add')
271+
self.dict_file = None
272+
self.source_files = []
273+
self.ui.pb_process.setValue(0)
274+
self.ui.lbl_status.setText('Idle')
275+
276+
app.processEvents()
277+
278+
def sync_lists(self):
279+
"""
280+
Synchronise displayed lists with actual lists
281+
:return: None
282+
"""
283+
284+
self.ui.lst_found.clear()
285+
self.ui.lst_add.clear()
286+
self.lst_found.sort()
287+
self.lst_add.sort()
288+
self.ui.lst_found.addItems(self.lst_found)
289+
self.ui.lst_add.addItems(self.lst_add)
290+
291+
total_words = len(self.lst_add) + len(self.lst_found)
292+
self.ui.lbl_new.setText('New Words Found ({}/{})'.format(len(self.lst_found), total_words))
293+
self.ui.lbl_add.setText('Words To Add ({})'.format(len(self.lst_add)))
294+
295+
if len(self.lst_add) > 0:
296+
self.ui.btn_generate.setEnabled(True)
297+
else:
298+
self.ui.btn_generate.setEnabled(False)
299+
300+
def remove_all(self):
301+
"""
302+
Remove all words from the list to be added
303+
:return: None
304+
"""
305+
306+
self.lst_found.extend(self.lst_add)
307+
self.lst_add.clear()
308+
self.sync_lists()
309+
310+
def remove(self):
311+
"""
312+
Remove selected word from the list to be added
313+
:return: None
314+
"""
315+
316+
if self.ui.lst_add.currentItem():
317+
self.lst_found.append(self.ui.lst_add.currentItem().text())
318+
self.lst_add.remove(self.ui.lst_add.currentItem().text())
319+
self.sync_lists()
320+
321+
def add(self):
322+
"""
323+
Add the selected word to the list of words to be added
324+
:return: None
325+
"""
326+
327+
if self.ui.lst_found.currentItem():
328+
self.lst_add.append(self.ui.lst_found.currentItem().text())
329+
self.lst_found.remove(self.ui.lst_found.currentItem().text())
330+
self.sync_lists()
331+
332+
def add_all(self):
333+
"""
334+
Add all found words to the list of words to be added
335+
:return: None
336+
"""
337+
338+
self.lst_add.extend(self.lst_found)
339+
self.lst_found.clear()
340+
self.sync_lists()
341+
342+
def write_dict(self):
343+
"""
344+
Write a new dictionary to file
345+
:return: None
346+
"""
347+
348+
merged_dict = list(set().union(self.dict_worker.dict, self.lst_add))
349+
merged_dict.sort()
350+
351+
options = QFileDialog.Options()
352+
filename = QFileDialog.getSaveFileName(self, "Choose Dictionary File", "",
353+
"Dictionary File (*.dic)", options=options)
354+
if filename and filename[0] != '':
355+
save_file = str(filename[0])
356+
357+
self.update_status("Writing file, please wait...")
358+
359+
with open(save_file, 'w') as new_dict:
360+
new_dict.write("%s\n" % len(merged_dict))
361+
for word in merged_dict:
362+
new_dict.write("%s\n" % word)
363+
364+
self.update_status("New dictionary written to {}".format(save_file))
365+
366+
@QtCore.pyqtSlot(str)
367+
def update_status(self, status: str):
368+
"""
369+
Called by threads to update status label
370+
:param status: str status to be displayed
371+
:return: None
372+
"""
373+
374+
self.ui.lbl_status.setText(status)
375+
376+
@QtCore.pyqtSlot(int)
377+
def update_progress(self, progress: int):
378+
"""
379+
Called by threads to update progress bar
380+
:param progress: int percentage progress to be displayed
381+
:return: None
382+
"""
383+
384+
self.ui.pb_process.setValue(progress)
385+
386+
387+
app = QApplication(sys.argv)
388+
w = AppWindow()
389+
w.show()
390+
sys.exit(app.exec_())

0 commit comments

Comments
 (0)