1
1
from 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 )
5
2
from 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
+
6
19
from 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
+
8
47
DOCS_FOLDER = "Docs_for_DB"
9
48
CONFIG_FILE = "config.yaml"
49
+
50
+
10
51
class SymlinkWorker (QThread ):
11
52
progress = Signal (int )
12
53
finished = Signal (int , list )
54
+
13
55
def __init__ (self , source , target_dir , parent = None ):
14
56
super ().__init__ (parent )
15
57
self .source = source
16
58
self .target_dir = Path (target_dir )
59
+
17
60
def run (self ):
18
61
if isinstance (self .source , (str , Path )):
19
62
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
+ ]
21
68
else :
22
69
files = list (self .source )
70
+
23
71
total = len (files )
24
72
made = 0
25
73
errors = []
26
74
last_pct = - 1
27
75
timer = QElapsedTimer ()
28
76
timer .start ()
29
77
step = max (1 , total // 100 ) if total else 1
78
+
30
79
if total > 1000 :
31
80
processes = min ((total // 10000 ) + 1 , cpu_count ())
32
81
file_args = [(f , str (self .target_dir )) for f in files ]
33
82
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
+ ):
35
86
if ok :
36
87
made += 1
37
88
if err :
@@ -46,6 +97,7 @@ def run(self):
46
97
for f in files :
47
98
if self .isInterruptionRequested ():
48
99
break
100
+
49
101
ok , err = _create_single_symlink ((f , str (self .target_dir )))
50
102
if ok :
51
103
made += 1
@@ -57,72 +109,100 @@ def run(self):
57
109
self .progress .emit (pct )
58
110
last_pct = pct
59
111
timer .restart ()
112
+
60
113
self .finished .emit (made , errors )
114
+
115
+
61
116
def choose_documents_directory ():
62
117
current_dir = Path (__file__ ).parent .resolve ()
63
118
target_dir = current_dir / DOCS_FOLDER
64
119
target_dir .mkdir (parents = True , exist_ok = True )
120
+
65
121
msg_box = QMessageBox ()
66
122
msg_box .setWindowTitle ("Selection Type" )
67
123
msg_box .setText ("Would you like to select a directory or individual files?" )
124
+
68
125
dir_button = msg_box .addButton ("Select Directory" , QMessageBox .ActionRole )
69
126
files_button = msg_box .addButton ("Select Files" , QMessageBox .ActionRole )
70
127
cancel_button = msg_box .addButton ("Cancel" , QMessageBox .RejectRole )
128
+
71
129
msg_box .exec ()
72
130
clicked_button = msg_box .clickedButton ()
131
+
73
132
if clicked_button == cancel_button :
74
133
return
134
+
75
135
file_dialog = QFileDialog ()
136
+
76
137
def start_worker (source ):
77
- progress = QProgressDialog ("Creating symlinks..." , "Cancel" , 0 , 0 )
138
+ progress = QProgressDialog (
139
+ "Creating symlinks..." , "Cancel" , 0 , 0
140
+ )
78
141
progress .setWindowModality (Qt .WindowModal )
79
142
progress .setMinimumDuration (0 )
143
+
80
144
worker = SymlinkWorker (source , target_dir )
81
145
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" ):
83
147
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" ):
88
154
db_tab .docs_refresh .start ()
155
+
89
156
progress .canceled .connect (worker .requestInterruption )
157
+
90
158
def update_progress (pct ):
91
159
if progress .maximum () == 0 :
92
160
progress .setRange (0 , 100 )
93
161
progress .setValue (pct )
162
+
94
163
worker .progress .connect (update_progress )
164
+
95
165
def _done (count , errs ):
96
- if main_window and hasattr (main_window , ' databases_tab' ):
166
+ if main_window and hasattr (main_window , " databases_tab" ):
97
167
db_tab = main_window .databases_tab
98
- if hasattr (db_tab , ' docs_refresh' ):
168
+ if hasattr (db_tab , " docs_refresh" ):
99
169
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" ):
102
172
db_tab .docs_model .refresh ()
103
- elif hasattr (db_tab .docs_model , ' reindex' ):
173
+ elif hasattr (db_tab .docs_model , " reindex" ):
104
174
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
+
107
180
progress .reset ()
108
181
msg = f"Created { count } symlinks"
109
182
if errs :
110
183
msg += f" – { len (errs )} errors (see console)"
111
184
print (* errs , sep = "\n " )
112
185
QMessageBox .information (None , "Symlinks" , msg )
186
+
113
187
worker .finished .connect (_done )
114
188
worker .progress .connect (update_progress )
115
189
worker .start ()
190
+
116
191
choose_documents_directory ._symlink_thread = worker
192
+
117
193
if clicked_button == dir_button :
118
194
file_dialog .setFileMode (QFileDialog .Directory )
119
195
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
+ )
121
199
if selected_dir :
122
200
start_worker (Path (selected_dir ))
123
201
else :
124
202
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 ]
126
206
if file_paths :
127
207
compatible_files = []
128
208
incompatible_files = []
@@ -132,44 +212,73 @@ def _done(count, errs):
132
212
compatible_files .append (str (path ))
133
213
else :
134
214
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
+ ):
136
219
return
220
+
137
221
if compatible_files :
138
222
start_worker (compatible_files )
223
+
224
+
139
225
def 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
+
142
234
incompatible_dialog = QDialog ()
143
235
incompatible_dialog .resize (800 , 600 )
144
236
incompatible_dialog .setWindowTitle ("Incompatible Files Detected" )
237
+
145
238
layout = QVBoxLayout ()
146
239
text_edit = QTextEdit ()
147
240
text_edit .setReadOnly (True )
148
241
text_edit .setText (dialog_text )
149
242
layout .addWidget (text_edit )
243
+
150
244
button_box = QHBoxLayout ()
151
245
ok_button = QPushButton ("OK" )
152
246
cancel_button = QPushButton ("Cancel" )
153
247
button_box .addWidget (ok_button )
154
248
button_box .addWidget (cancel_button )
249
+
155
250
layout .addLayout (button_box )
156
251
incompatible_dialog .setLayout (layout )
252
+
157
253
ok_button .clicked .connect (incompatible_dialog .accept )
158
254
cancel_button .clicked .connect (incompatible_dialog .reject )
255
+
159
256
return incompatible_dialog .exec () == QDialog .Accepted
257
+
258
+
160
259
def load_config ():
161
- with open (CONFIG_FILE , 'r' , encoding = ' utf-8' ) as stream :
260
+ with open (CONFIG_FILE , "r" , encoding = " utf-8" ) as stream :
162
261
return yaml .safe_load (stream )
262
+
263
+
163
264
def 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
+ )
166
269
if chosen_directory :
167
270
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
+ )
169
276
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
+
171
280
def _get_main_window ():
172
281
for widget in QApplication .topLevelWidgets ():
173
- if hasattr (widget , ' databases_tab' ):
282
+ if hasattr (widget , " databases_tab" ):
174
283
return widget
175
284
return None
0 commit comments