1
+ import collections
1
2
import functools
2
3
import importlib
3
4
import importlib .util
7
8
import platform
8
9
import shlex
9
10
import sys
11
+ import time
10
12
import types
11
13
import uuid
12
14
import errno
@@ -228,7 +230,7 @@ def _screen_file_extensions(preferred_extension):
228
230
return extensions
229
231
230
232
231
- def find_file (fname , base_path = None , mode = None , extra_path = None , raise_if_not_found = False ):
233
+ def find_file (fname , base_path = None , mode = None , raise_if_not_found = False , subdir_scan_enabled = False , subdir_scan_base_path_only = True ):
232
234
"""
233
235
Look for files at the search paths common to PyDM.
234
236
@@ -238,7 +240,6 @@ def find_file(fname, base_path=None, mode=None, extra_path=None, raise_if_not_fo
238
240
* Qt Designer Path - the path for the current form as reported by the
239
241
designer
240
242
* The current working directory
241
- * Directories listed in ``extra_path``
242
243
* Directories listed in the environment variable ``PYDM_DISPLAYS_PATH``
243
244
244
245
Parameters
@@ -251,11 +252,15 @@ def find_file(fname, base_path=None, mode=None, extra_path=None, raise_if_not_fo
251
252
mode : int
252
253
The mode required for the file, defaults to os.F_OK | os.R_OK.
253
254
Which ensure that the file exists and we can read it.
254
- extra_path : list
255
- Additional paths to look for file.
256
255
raise_if_not_found : bool
257
256
Flag which if False will add a check that raises a FileNotFoundError
258
257
instead of returning None when the file is not found.
258
+ subdir_scan_enabled : bool
259
+ If the file cannot be found in the given directories, check
260
+ subdirectories. Defaults to False.
261
+ subdir_scan_base_path_only : bool
262
+ If it is necessary to scan subdirectories for the requested file,
263
+ only scan subdirectories of the base_path. Defaults to True.
259
264
260
265
Returns
261
266
-------
@@ -267,23 +272,19 @@ def find_file(fname, base_path=None, mode=None, extra_path=None, raise_if_not_fo
267
272
if mode is None :
268
273
mode = os .F_OK | os .R_OK
269
274
270
- x_path = []
275
+ x_path = collections . deque ()
271
276
272
277
if base_path :
273
- x_path .extend ([os .path .abspath (base_path )])
278
+ base_path = os .path .abspath (base_path )
279
+ x_path .append (base_path )
274
280
275
281
if is_qt_designer ():
276
282
designer_path = get_designer_current_path ()
277
283
if designer_path :
278
- x_path .extend ([ designer_path ] )
284
+ x_path .append ( designer_path )
279
285
280
286
# Current working directory
281
- x_path .extend ([os .getcwd ()])
282
- if extra_path :
283
- if not isinstance (extra_path , (list , tuple )):
284
- extra_path = [extra_path ]
285
- extra_path = [os .path .expanduser (os .path .expandvars (x )) for x in extra_path ]
286
- x_path .extend (extra_path )
287
+ x_path .append (os .getcwd ())
287
288
288
289
pydm_search_path = os .getenv ("PYDM_DISPLAYS_PATH" , None )
289
290
if pydm_search_path :
@@ -294,15 +295,43 @@ def find_file(fname, base_path=None, mode=None, extra_path=None, raise_if_not_fo
294
295
295
296
root , ext = os .path .splitext (fname )
296
297
297
- # loop through the possible screen file extensions
298
- for e in _screen_file_extensions (ext ):
299
- file_path = which (str (root ) + str (e ), mode = mode , pathext = e , extra_path = x_path )
300
- if file_path is not None :
301
- break # pick the first screen file found
298
+ # 3 seconds should be more than generous enough
299
+ SUBDIR_SCAN_TIME_LIMIT = 3
300
+ start_time = time .perf_counter ()
301
+
302
+ file_path = None
303
+ while file_path is None and len (x_path ) > 0 :
304
+ # Loop through the possible screen file extensions
305
+ for e in _screen_file_extensions (ext ):
306
+ file_path = which (str (root ) + str (e ), mode = mode , pathext = e , extra_path = x_path )
307
+ if file_path is not None :
308
+ break # pick the first screen file found
309
+
310
+ if not subdir_scan_enabled or time .perf_counter () - start_time >= SUBDIR_SCAN_TIME_LIMIT :
311
+ if not file_path and raise_if_not_found :
312
+ raise FileNotFoundError (errno .ENOENT , os .strerror (errno .ENOENT ), fname )
313
+ break
302
314
303
- if raise_if_not_found :
304
- if not file_path :
305
- raise FileNotFoundError (errno .ENOENT , os .strerror (errno .ENOENT ), fname )
315
+ # Only search recursively under base path
316
+ if subdir_scan_base_path_only :
317
+ if base_path is None or len (base_path ) == 0 :
318
+ break
319
+ x_path .clear ()
320
+ x_path .append (os .path .expanduser (os .path .expandvars (base_path )))
321
+ # Prevent entering this block again
322
+ subdir_scan_base_path_only = False
323
+
324
+ # This might get large in some situations, but it's the easiest way to do BFS without
325
+ # changing too much of the existing logic, and ideally recursion isn't needed
326
+ path_count = len (x_path )
327
+ for _ in range (path_count ):
328
+ for subdir in os .listdir (x_path [0 ]):
329
+ if subdir .startswith ("." ) or subdir .startswith ("__pycache__" ):
330
+ continue
331
+ new_path = os .path .join (x_path [0 ], subdir )
332
+ if os .path .isdir (new_path ):
333
+ x_path .append (new_path )
334
+ x_path .popleft ()
306
335
307
336
return file_path
308
337
@@ -371,9 +400,6 @@ def _access_check(fn, mode):
371
400
return None
372
401
path = path .split (os .pathsep )
373
402
374
- if extra_path is not None :
375
- path = extra_path + path
376
-
377
403
if sys .platform == "win32" :
378
404
# The current directory takes precedence on Windows.
379
405
if os .curdir not in path :
@@ -397,14 +423,17 @@ def _access_check(fn, mode):
397
423
files = [cmd ]
398
424
399
425
seen = set ()
400
- for dir_ in path :
401
- normdir = os .path .normcase (dir_ )
402
- if normdir not in seen :
403
- seen .add (normdir )
404
- for thefile in files :
405
- name = os .path .join (dir_ , thefile )
406
- if _access_check (name , mode ):
407
- return name
426
+ for paths in extra_path , path :
427
+ if paths is None :
428
+ continue
429
+ for dir_ in paths :
430
+ normdir = os .path .normcase (dir_ )
431
+ if normdir not in seen :
432
+ seen .add (normdir )
433
+ for thefile in files :
434
+ name = os .path .join (dir_ , thefile )
435
+ if _access_check (name , mode ):
436
+ return name
408
437
return None
409
438
410
439
0 commit comments