11from pathlib import Path
2- import yaml
3- from PySide6 .QtCore import QThread , Signal , Qt , QElapsedTimer
4- from PySide6 .QtWidgets import (QFileDialog , QDialog , QVBoxLayout , QTextEdit , QPushButton , QHBoxLayout , QMessageBox , QProgressDialog , QApplication , QFileSystemModel )
52from multiprocessing import Pool , cpu_count
3+
4+ import yaml
5+ from PySide6 .QtCore import QElapsedTimer , QThread , Signal , Qt
6+ from PySide6 .QtWidgets import (
7+ QApplication ,
8+ QFileDialog ,
9+ QFileSystemModel ,
10+ QHBoxLayout ,
11+ QProgressDialog ,
12+ QVBoxLayout ,
13+ QDialog ,
14+ QTextEdit ,
15+ QPushButton ,
16+ QMessageBox ,
17+ )
18+
619from create_symlinks import _create_single_symlink
7- ALLOWED_EXTENSIONS = {'.pdf' , '.docx' , '.epub' , '.txt' , '.enex' , '.eml' , '.msg' , '.csv' , '.xls' , '.xlsx' , '.rtf' , '.odt' , '.png' , '.jpg' , '.jpeg' , '.bmp' , '.gif' , '.tif' , '.tiff' , '.html' , '.htm' , '.md' , '.doc' }
20+
21+ ALLOWED_EXTENSIONS = {
22+ ".pdf" ,
23+ ".docx" ,
24+ ".epub" ,
25+ ".txt" ,
26+ ".enex" ,
27+ ".eml" ,
28+ ".msg" ,
29+ ".csv" ,
30+ ".xls" ,
31+ ".xlsx" ,
32+ ".rtf" ,
33+ ".odt" ,
34+ ".png" ,
35+ ".jpg" ,
36+ ".jpeg" ,
37+ ".bmp" ,
38+ ".gif" ,
39+ ".tif" ,
40+ ".tiff" ,
41+ ".html" ,
42+ ".htm" ,
43+ ".md" ,
44+ ".doc" ,
45+ }
46+
847DOCS_FOLDER = "Docs_for_DB"
948CONFIG_FILE = "config.yaml"
49+
50+
1051class SymlinkWorker (QThread ):
1152 progress = Signal (int )
1253 finished = Signal (int , list )
54+
1355 def __init__ (self , source , target_dir , parent = None ):
1456 super ().__init__ (parent )
1557 self .source = source
1658 self .target_dir = Path (target_dir )
59+
1760 def run (self ):
1861 if isinstance (self .source , (str , Path )):
1962 dir_path = Path (self .source )
20- files = [str (p ) for p in dir_path .iterdir () if p .is_file () and p .suffix .lower () in ALLOWED_EXTENSIONS ]
63+ files = [
64+ str (p )
65+ for p in dir_path .iterdir ()
66+ if p .is_file () and p .suffix .lower () in ALLOWED_EXTENSIONS
67+ ]
2168 else :
2269 files = list (self .source )
70+
2371 total = len (files )
2472 made = 0
2573 errors = []
2674 last_pct = - 1
2775 timer = QElapsedTimer ()
2876 timer .start ()
2977 step = max (1 , total // 100 ) if total else 1
78+
3079 if total > 1000 :
3180 processes = min ((total // 10000 ) + 1 , cpu_count ())
3281 file_args = [(f , str (self .target_dir )) for f in files ]
3382 with Pool (processes = processes ) as pool :
34- for i , (ok , err ) in enumerate (pool .imap_unordered (_create_single_symlink , file_args ), 1 ):
83+ for i , (ok , err ) in enumerate (
84+ pool .imap_unordered (_create_single_symlink , file_args ), 1
85+ ):
3586 if ok :
3687 made += 1
3788 if err :
@@ -46,6 +97,7 @@ def run(self):
4697 for f in files :
4798 if self .isInterruptionRequested ():
4899 break
100+
49101 ok , err = _create_single_symlink ((f , str (self .target_dir )))
50102 if ok :
51103 made += 1
@@ -57,72 +109,100 @@ def run(self):
57109 self .progress .emit (pct )
58110 last_pct = pct
59111 timer .restart ()
112+
60113 self .finished .emit (made , errors )
114+
115+
61116def choose_documents_directory ():
62117 current_dir = Path (__file__ ).parent .resolve ()
63118 target_dir = current_dir / DOCS_FOLDER
64119 target_dir .mkdir (parents = True , exist_ok = True )
120+
65121 msg_box = QMessageBox ()
66122 msg_box .setWindowTitle ("Selection Type" )
67123 msg_box .setText ("Would you like to select a directory or individual files?" )
124+
68125 dir_button = msg_box .addButton ("Select Directory" , QMessageBox .ActionRole )
69126 files_button = msg_box .addButton ("Select Files" , QMessageBox .ActionRole )
70127 cancel_button = msg_box .addButton ("Cancel" , QMessageBox .RejectRole )
128+
71129 msg_box .exec ()
72130 clicked_button = msg_box .clickedButton ()
131+
73132 if clicked_button == cancel_button :
74133 return
134+
75135 file_dialog = QFileDialog ()
136+
76137 def start_worker (source ):
77- progress = QProgressDialog ("Creating symlinks..." , "Cancel" , 0 , 0 )
138+ progress = QProgressDialog (
139+ "Creating symlinks..." , "Cancel" , 0 , 0
140+ )
78141 progress .setWindowModality (Qt .WindowModal )
79142 progress .setMinimumDuration (0 )
143+
80144 worker = SymlinkWorker (source , target_dir )
81145 main_window = _get_main_window ()
82- if main_window and hasattr (main_window , ' databases_tab' ):
146+ if main_window and hasattr (main_window , " databases_tab" ):
83147 db_tab = main_window .databases_tab
84- if hasattr (db_tab , 'docs_model' ) and db_tab .docs_model :
85- if hasattr (QFileSystemModel , 'DontWatchForChanges' ):
86- db_tab .docs_model .setOption (QFileSystemModel .DontWatchForChanges , True )
87- if hasattr (db_tab , 'docs_refresh' ):
148+ if hasattr (db_tab , "docs_model" ) and db_tab .docs_model :
149+ if hasattr (QFileSystemModel , "DontWatchForChanges" ):
150+ db_tab .docs_model .setOption (
151+ QFileSystemModel .DontWatchForChanges , True
152+ )
153+ if hasattr (db_tab , "docs_refresh" ):
88154 db_tab .docs_refresh .start ()
155+
89156 progress .canceled .connect (worker .requestInterruption )
157+
90158 def update_progress (pct ):
91159 if progress .maximum () == 0 :
92160 progress .setRange (0 , 100 )
93161 progress .setValue (pct )
162+
94163 worker .progress .connect (update_progress )
164+
95165 def _done (count , errs ):
96- if main_window and hasattr (main_window , ' databases_tab' ):
166+ if main_window and hasattr (main_window , " databases_tab" ):
97167 db_tab = main_window .databases_tab
98- if hasattr (db_tab , ' docs_refresh' ):
168+ if hasattr (db_tab , " docs_refresh" ):
99169 db_tab .docs_refresh .stop ()
100- if hasattr (db_tab , ' docs_model' ) and db_tab .docs_model :
101- if hasattr (db_tab .docs_model , ' refresh' ):
170+ if hasattr (db_tab , " docs_model" ) and db_tab .docs_model :
171+ if hasattr (db_tab .docs_model , " refresh" ):
102172 db_tab .docs_model .refresh ()
103- elif hasattr (db_tab .docs_model , ' reindex' ):
173+ elif hasattr (db_tab .docs_model , " reindex" ):
104174 db_tab .docs_model .reindex ()
105- if hasattr (QFileSystemModel , 'DontWatchForChanges' ):
106- db_tab .docs_model .setOption (QFileSystemModel .DontWatchForChanges , False )
175+ if hasattr (QFileSystemModel , "DontWatchForChanges" ):
176+ db_tab .docs_model .setOption (
177+ QFileSystemModel .DontWatchForChanges , False
178+ )
179+
107180 progress .reset ()
108181 msg = f"Created { count } symlinks"
109182 if errs :
110183 msg += f" – { len (errs )} errors (see console)"
111184 print (* errs , sep = "\n " )
112185 QMessageBox .information (None , "Symlinks" , msg )
186+
113187 worker .finished .connect (_done )
114188 worker .progress .connect (update_progress )
115189 worker .start ()
190+
116191 choose_documents_directory ._symlink_thread = worker
192+
117193 if clicked_button == dir_button :
118194 file_dialog .setFileMode (QFileDialog .Directory )
119195 file_dialog .setOption (QFileDialog .ShowDirsOnly , True )
120- selected_dir = file_dialog .getExistingDirectory (None , "Choose Directory for Database" , str (current_dir ))
196+ selected_dir = file_dialog .getExistingDirectory (
197+ None , "Choose Directory for Database" , str (current_dir )
198+ )
121199 if selected_dir :
122200 start_worker (Path (selected_dir ))
123201 else :
124202 file_dialog .setFileMode (QFileDialog .ExistingFiles )
125- file_paths = file_dialog .getOpenFileNames (None , "Choose Documents and Images for Database" , str (current_dir ))[0 ]
203+ file_paths = file_dialog .getOpenFileNames (
204+ None , "Choose Documents and Images for Database" , str (current_dir )
205+ )[0 ]
126206 if file_paths :
127207 compatible_files = []
128208 incompatible_files = []
@@ -132,44 +212,73 @@ def _done(count, errs):
132212 compatible_files .append (str (path ))
133213 else :
134214 incompatible_files .append (path .name )
135- if incompatible_files and not show_incompatible_files_dialog (incompatible_files ):
215+
216+ if incompatible_files and not show_incompatible_files_dialog (
217+ incompatible_files
218+ ):
136219 return
220+
137221 if compatible_files :
138222 start_worker (compatible_files )
223+
224+
139225def show_incompatible_files_dialog (incompatible_files ):
140- dialog_text = ("The following files cannot be added here due to their file extension:\n \n " + "\n " .join (incompatible_files ) + "\n \n However, if any of them are audio files you can still add them directly in the Tools Tab."
141- "\n \n Click 'Ok' to add the compatible documents only (remembering to add audio files separately) or 'Cancel' to back out completely." )
226+ dialog_text = (
227+ "The following files cannot be added here due to their file extension:\n \n "
228+ + "\n " .join (incompatible_files )
229+ + "\n \n However, if any of them are audio files you can still add them directly in the Tools Tab."
230+ "\n \n Click 'Ok' to add the compatible documents only (remembering to add audio files separately)"
231+ " or 'Cancel' to back out completely."
232+ )
233+
142234 incompatible_dialog = QDialog ()
143235 incompatible_dialog .resize (800 , 600 )
144236 incompatible_dialog .setWindowTitle ("Incompatible Files Detected" )
237+
145238 layout = QVBoxLayout ()
146239 text_edit = QTextEdit ()
147240 text_edit .setReadOnly (True )
148241 text_edit .setText (dialog_text )
149242 layout .addWidget (text_edit )
243+
150244 button_box = QHBoxLayout ()
151245 ok_button = QPushButton ("OK" )
152246 cancel_button = QPushButton ("Cancel" )
153247 button_box .addWidget (ok_button )
154248 button_box .addWidget (cancel_button )
249+
155250 layout .addLayout (button_box )
156251 incompatible_dialog .setLayout (layout )
252+
157253 ok_button .clicked .connect (incompatible_dialog .accept )
158254 cancel_button .clicked .connect (incompatible_dialog .reject )
255+
159256 return incompatible_dialog .exec () == QDialog .Accepted
257+
258+
160259def load_config ():
161- with open (CONFIG_FILE , 'r' , encoding = ' utf-8' ) as stream :
260+ with open (CONFIG_FILE , "r" , encoding = " utf-8" ) as stream :
162261 return yaml .safe_load (stream )
262+
263+
163264def select_embedding_model_directory ():
164- initial_dir = Path ('Models' ) if Path ('Models' ).exists () else Path .home ()
165- chosen_directory = QFileDialog .getExistingDirectory (None , "Select Embedding Model Directory" , str (initial_dir ))
265+ initial_dir = Path ("Models" ) if Path ("Models" ).exists () else Path .home ()
266+ chosen_directory = QFileDialog .getExistingDirectory (
267+ None , "Select Embedding Model Directory" , str (initial_dir )
268+ )
166269 if chosen_directory :
167270 config_file_path = Path (CONFIG_FILE )
168- config_data = yaml .safe_load (config_file_path .read_text (encoding = 'utf-8' )) if config_file_path .exists () else {}
271+ config_data = (
272+ yaml .safe_load (config_file_path .read_text (encoding = "utf-8" ))
273+ if config_file_path .exists ()
274+ else {}
275+ )
169276 config_data ["EMBEDDING_MODEL_NAME" ] = chosen_directory
170- config_file_path .write_text (yaml .dump (config_data ), encoding = 'utf-8' )
277+ config_file_path .write_text (yaml .dump (config_data ), encoding = "utf-8" )
278+
279+
171280def _get_main_window ():
172281 for widget in QApplication .topLevelWidgets ():
173- if hasattr (widget , ' databases_tab' ):
282+ if hasattr (widget , " databases_tab" ):
174283 return widget
175284 return None
0 commit comments