@@ -25,37 +25,62 @@ def _patch_symlinks_for_windows():
2525 if sys .platform != "win32" :
2626 return
2727
28+ import time
2829 original_symlink = os .symlink
2930
3031 def symlink_or_copy (src , dst , target_is_directory = False , * , dir_fd = None ):
3132 """Replace symlink with copy on Windows to avoid privilege errors."""
3233 try :
33- # First try the original symlink (works if user has privileges)
3434 original_symlink (src , dst , target_is_directory , dir_fd = dir_fd )
3535 except OSError as e :
36- if e .winerror == 1314 : # ERROR_PRIVILEGE_NOT_HELD
37- # Fall back to copying the file/directory
38- src_path = Path (src ) if not os .path .isabs (src ) else Path (src )
39- dst_path = Path (dst )
40-
41- # Handle relative symlinks (HuggingFace uses these)
42- if not src_path .is_absolute ():
43- src_path = dst_path .parent / src_path
44-
45- try :
46- if src_path .is_dir ():
47- if dst_path .exists ():
48- shutil .rmtree (dst_path )
49- shutil .copytree (src_path , dst_path )
50- else :
51- dst_path .parent .mkdir (parents = True , exist_ok = True )
52- shutil .copy2 (src_path , dst_path )
53- except Exception :
54- # If copy also fails, raise the original error
55- raise e
56- else :
36+ if getattr (e , 'winerror' , None ) != 1314 : # Not ERROR_PRIVILEGE_NOT_HELD
5737 raise
5838
39+ # Convert to Path objects for easier handling
40+ dst_path = Path (dst )
41+ src_path = Path (src )
42+
43+ # Resolve relative symlinks (HuggingFace uses paths like "../../blobs/xxx")
44+ if not src_path .is_absolute ():
45+ src_path = (dst_path .parent / src_path ).resolve ()
46+ else :
47+ src_path = src_path .resolve ()
48+
49+ # Ensure destination parent directory exists
50+ dst_path .parent .mkdir (parents = True , exist_ok = True )
51+
52+ # Remove existing destination if present
53+ if dst_path .exists () or dst_path .is_symlink ():
54+ if dst_path .is_dir () and not dst_path .is_symlink ():
55+ shutil .rmtree (dst_path )
56+ else :
57+ dst_path .unlink ()
58+
59+ # Wait briefly for source file if it doesn't exist yet (race condition)
60+ if not src_path .exists ():
61+ for _ in range (10 ):
62+ time .sleep (0.1 )
63+ if src_path .exists ():
64+ break
65+
66+ if not src_path .exists ():
67+ raise FileNotFoundError (
68+ f"Source file not found for symlink fallback: { src_path } "
69+ ) from e
70+
71+ # Try hardlink first (works without admin on same volume, no space usage)
72+ try :
73+ os .link (src_path , dst_path )
74+ return
75+ except OSError :
76+ pass # Hardlink failed, fall back to copy
77+
78+ # Fall back to file copy
79+ if src_path .is_dir ():
80+ shutil .copytree (src_path , dst_path )
81+ else :
82+ shutil .copy2 (src_path , dst_path )
83+
5984 os .symlink = symlink_or_copy
6085
6186
@@ -153,6 +178,6 @@ def setup_docling_cache():
153178
154179# Application metadata
155180APP_NAME = "PDF Extractor"
156- APP_VERSION = "1.0.8 "
181+ APP_VERSION = "1.0.11 "
157182APP_AUTHOR = "Dan Ribes"
158183APP_IDENTIFIER = "com.pdfextractor.app"
0 commit comments