Coverage for openhcs/core/xdg_paths.py: 22.6%
103 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
1"""
2XDG Base Directory Specification utilities for OpenHCS.
4Provides standardized paths following XDG Base Directory Specification:
5- Data: ~/.local/share/openhcs/
6- Cache: ~/.local/share/openhcs/cache/
7- Config: ~/.local/share/openhcs/config/
9Includes migration utilities to move existing cache files from legacy ~/.openhcs/ location.
10"""
12import logging
13import shutil
14from pathlib import Path
15from typing import Optional
17logger = logging.getLogger(__name__)
20def get_openhcs_data_dir() -> Path:
21 """
22 Get the OpenHCS data directory following XDG Base Directory Specification.
24 Returns:
25 Path to ~/.local/share/openhcs/
26 """
27 data_dir = Path.home() / ".local" / "share" / "openhcs"
28 data_dir.mkdir(parents=True, exist_ok=True)
29 return data_dir
32def get_openhcs_cache_dir() -> Path:
33 """
34 Get the OpenHCS cache directory following XDG Base Directory Specification.
36 Returns:
37 Path to ~/.local/share/openhcs/cache/
38 """
39 cache_dir = get_openhcs_data_dir() / "cache"
40 cache_dir.mkdir(parents=True, exist_ok=True)
41 return cache_dir
44def get_openhcs_config_dir() -> Path:
45 """
46 Get the OpenHCS config directory following XDG Base Directory Specification.
48 Returns:
49 Path to ~/.local/share/openhcs/config/
50 """
51 config_dir = get_openhcs_data_dir() / "config"
52 config_dir.mkdir(parents=True, exist_ok=True)
53 return config_dir
56def get_legacy_openhcs_dir() -> Path:
57 """
58 Get the legacy OpenHCS directory (~/.openhcs/).
60 Returns:
61 Path to ~/.openhcs/
62 """
63 return Path.home() / ".openhcs"
66def migrate_cache_file(legacy_path: Path, new_path: Path, description: str) -> bool:
67 """
68 Migrate a cache file from legacy location to XDG-compliant location.
70 Args:
71 legacy_path: Path to the legacy cache file
72 new_path: Path to the new XDG-compliant location
73 description: Human-readable description of the cache file
75 Returns:
76 True if migration was performed, False if no migration needed
77 """
78 if not legacy_path.exists():
79 logger.debug(f"No legacy {description} found at {legacy_path}")
80 return False
82 if new_path.exists():
83 logger.debug(f"XDG {description} already exists at {new_path}, skipping migration")
84 return False
86 try:
87 # Ensure target directory exists
88 new_path.parent.mkdir(parents=True, exist_ok=True)
90 # Copy the file to new location
91 shutil.copy2(legacy_path, new_path)
92 logger.info(f"Migrated {description} from {legacy_path} to {new_path}")
94 # Remove the legacy file
95 legacy_path.unlink()
96 logger.debug(f"Removed legacy {description} at {legacy_path}")
98 return True
100 except Exception as e:
101 logger.warning(f"Failed to migrate {description} from {legacy_path} to {new_path}: {e}")
102 return False
105def migrate_cache_directory(legacy_dir: Path, new_dir: Path, description: str) -> bool:
106 """
107 Migrate an entire cache directory from legacy location to XDG-compliant location.
109 Args:
110 legacy_dir: Path to the legacy cache directory
111 new_dir: Path to the new XDG-compliant directory
112 description: Human-readable description of the cache directory
114 Returns:
115 True if migration was performed, False if no migration needed
116 """
117 if not legacy_dir.exists():
118 logger.debug(f"No legacy {description} found at {legacy_dir}")
119 return False
121 if new_dir.exists() and any(new_dir.iterdir()):
122 logger.debug(f"XDG {description} already exists and is not empty at {new_dir}, skipping migration")
123 return False
125 try:
126 # Ensure target parent directory exists
127 new_dir.parent.mkdir(parents=True, exist_ok=True)
129 # Copy the entire directory to new location
130 if new_dir.exists():
131 shutil.rmtree(new_dir)
132 shutil.copytree(legacy_dir, new_dir)
133 logger.info(f"Migrated {description} from {legacy_dir} to {new_dir}")
135 # Remove the legacy directory
136 shutil.rmtree(legacy_dir)
137 logger.debug(f"Removed legacy {description} at {legacy_dir}")
139 return True
141 except Exception as e:
142 logger.warning(f"Failed to migrate {description} from {legacy_dir} to {new_dir}: {e}")
143 return False
146def migrate_all_legacy_cache_files() -> None:
147 """
148 Migrate all known legacy cache files to XDG-compliant locations.
150 This function should be called during application startup to ensure
151 smooth transition from legacy cache locations.
152 """
153 legacy_dir = get_legacy_openhcs_dir()
155 if not legacy_dir.exists():
156 logger.debug("No legacy ~/.openhcs directory found, no migration needed")
157 return
159 logger.info("Checking for legacy cache files to migrate to XDG locations...")
161 # Migrate individual cache files
162 migrations = [
163 (legacy_dir / "path_cache.json", get_openhcs_data_dir() / "path_cache.json", "path cache"),
164 (legacy_dir / "global_config.config", get_openhcs_config_dir() / "global_config.config", "global config cache"),
165 ]
167 migrated_files = 0
168 for legacy_path, new_path, description in migrations:
169 if migrate_cache_file(legacy_path, new_path, description):
170 migrated_files += 1
172 # Migrate cache directory
173 legacy_cache_dir = legacy_dir / "cache"
174 new_cache_dir = get_openhcs_cache_dir()
175 if migrate_cache_directory(legacy_cache_dir, new_cache_dir, "cache directory"):
176 migrated_files += 1
178 # Clean up empty legacy directory
179 try:
180 if legacy_dir.exists() and not any(legacy_dir.iterdir()):
181 legacy_dir.rmdir()
182 logger.info(f"Removed empty legacy directory {legacy_dir}")
183 except Exception as e:
184 logger.debug(f"Could not remove legacy directory {legacy_dir}: {e}")
186 if migrated_files > 0:
187 logger.info(f"Successfully migrated {migrated_files} cache files/directories to XDG locations")
188 else:
189 logger.debug("No legacy cache files found to migrate")
192def get_cache_file_path(filename: str, legacy_filename: Optional[str] = None) -> Path:
193 """
194 Get path for a cache file, with automatic migration from legacy location.
196 Args:
197 filename: Name of the cache file in XDG location
198 legacy_filename: Optional different filename in legacy location
200 Returns:
201 Path to the cache file in XDG location
202 """
203 new_path = get_openhcs_cache_dir() / filename
205 # Check if we need to migrate from legacy location
206 if not new_path.exists():
207 legacy_filename = legacy_filename or filename
208 legacy_path = get_legacy_openhcs_dir() / "cache" / legacy_filename
210 if legacy_path.exists(): 210 ↛ 211line 210 didn't jump to line 211 because the condition on line 210 was never true
211 migrate_cache_file(legacy_path, new_path, f"cache file {filename}")
213 return new_path
216def get_config_file_path(filename: str, legacy_filename: Optional[str] = None) -> Path:
217 """
218 Get path for a config file, with automatic migration from legacy location.
220 Args:
221 filename: Name of the config file in XDG location
222 legacy_filename: Optional different filename in legacy location
224 Returns:
225 Path to the config file in XDG location
226 """
227 new_path = get_openhcs_config_dir() / filename
229 # Check if we need to migrate from legacy location
230 if not new_path.exists():
231 legacy_filename = legacy_filename or filename
232 legacy_path = get_legacy_openhcs_dir() / legacy_filename
234 if legacy_path.exists():
235 migrate_cache_file(legacy_path, new_path, f"config file {filename}")
237 return new_path
240def get_data_file_path(filename: str, legacy_filename: Optional[str] = None) -> Path:
241 """
242 Get path for a data file, with automatic migration from legacy location.
244 Args:
245 filename: Name of the data file in XDG location
246 legacy_filename: Optional different filename in legacy location
248 Returns:
249 Path to the data file in XDG location
250 """
251 new_path = get_openhcs_data_dir() / filename
253 # Check if we need to migrate from legacy location
254 if not new_path.exists():
255 legacy_filename = legacy_filename or filename
256 legacy_path = get_legacy_openhcs_dir() / legacy_filename
258 if legacy_path.exists():
259 migrate_cache_file(legacy_path, new_path, f"data file {filename}")
261 return new_path