voxkit.storage.utils
This module provides utility functions for common VoxKit storage operations, including path management and unique identifier generation.
API
- get_storage_root: Get the root directory for storing VoxKit data
- generate_unique_id: Generate a unique identifier with timestamp
- readable_from_unique_id: Convert a unique ID to human-readable format
- is_first_launch: Check if this is the first launch of the application
- mark_first_launch_complete: Mark the first launch as complete
- save_json: Save JSON data to a file within storage
Notes
- The storage root uses tilde (~) notation to ensure it references the user's home directory
- Unique IDs are based on timestamps with microsecond precision (YYYYMMDD_HHMMSS_ffffff)
- The storage root path is cached for performance using lru_cache
- First launch tracking uses a flag file (.first_launch_complete) in the storage root
1"""This module provides utility functions for common VoxKit storage operations, 2including path management and unique identifier generation. 3 4API 5--- 6- **get_storage_root**: Get the root directory for storing VoxKit data 7- **generate_unique_id**: Generate a unique identifier with timestamp 8- **readable_from_unique_id**: Convert a unique ID to human-readable format 9- **is_first_launch**: Check if this is the first launch of the application 10- **mark_first_launch_complete**: Mark the first launch as complete 11- **save_json**: Save JSON data to a file within storage 12 13Notes 14----- 15- The storage root uses tilde (~) notation to ensure it references the user's home directory 16- Unique IDs are based on timestamps with microsecond precision (YYYYMMDD_HHMMSS_ffffff) 17- The storage root path is cached for performance using lru_cache 18- First launch tracking uses a flag file (.first_launch_complete) in the storage root 19""" 20 21import json 22import threading 23from datetime import datetime, timedelta 24from functools import lru_cache 25from pathlib import Path 26from typing import Any 27 28from .constants import STORAGE_ROOT 29 30_id_lock = threading.Lock() 31_last_id_dt: datetime | None = None 32 33 34@lru_cache(maxsize=1) 35def get_storage_root() -> Path: 36 """Get the root directory for storing VoxKit data. 37 38 Returns: 39 Path to the storage root directory (expanded from tilde notation) 40 41 Raises: 42 ValueError: If STORAGE_ROOT does not start with tilde (~) 43 """ 44 if STORAGE_ROOT.startswith("~"): 45 return Path(STORAGE_ROOT).expanduser() 46 else: 47 raise ValueError("STORAGE_ROOT must be a valid path starting with '~'") 48 49 50def generate_unique_id(prefix: str | None = None) -> str: 51 """Generate a unique identifier with the given prefix and current timestamp. 52 53 Args: 54 prefix: Optional prefix to prepend to the timestamp 55 56 Returns: 57 Unique identifier string in format: [prefix_]YYYYMMDD_HHMMSS_ffffff 58 """ 59 global _last_id_dt 60 # Windows datetime.now() has ~15ms resolution; enforce monotonic microsecond precision. 61 with _id_lock: 62 now = datetime.now() 63 if _last_id_dt is not None and now <= _last_id_dt: 64 now = _last_id_dt + timedelta(microseconds=1) 65 _last_id_dt = now 66 now_str = now.strftime("%Y%m%d_%H%M%S_%f") 67 if prefix: 68 return f"{prefix}_{now_str}" 69 return now_str 70 71 72def readable_from_unique_id(date_str: str) -> str: 73 """Convert a unique ID timestamp to a human-readable format. 74 75 Accepts both plain timestamps (YYYYMMDD_HHMMSS_ffffff) and prefixed IDs 76 produced by generate_unique_id (e.g. prefix_YYYYMMDD_HHMMSS_ffffff). 77 78 Args: 79 date_str: Unique ID string, optionally prefixed as [prefix_]YYYYMMDD_HHMMSS_ffffff 80 81 Returns: 82 Human-readable date string (e.g., "January 01, 2024 at 12:00:00 PM") 83 """ 84 parts = date_str.split("_") 85 for i, part in enumerate(parts): 86 if len(part) == 8 and part.isdigit(): 87 date_str = "_".join(parts[i:]) 88 break 89 else: 90 raise ValueError(f"No valid timestamp found in unique ID: {date_str!r}") 91 dt = datetime.strptime(date_str, "%Y%m%d_%H%M%S_%f") 92 return dt.strftime("%B %d, %Y at %I:%M:%S %p") 93 94 95def is_first_launch() -> bool: 96 """Check if this is the first launch of the application. 97 98 Returns: 99 True if this is the first launch, False otherwise 100 """ 101 storage_root = get_storage_root() 102 flag_file = storage_root / ".first_launch_complete" 103 return not flag_file.exists() 104 105 106def mark_first_launch_complete() -> None: 107 """Mark the first launch as complete by creating a flag file.""" 108 storage_root = get_storage_root() 109 storage_root.mkdir(parents=True, exist_ok=True) 110 flag_file = storage_root / ".first_launch_complete" 111 flag_file.touch() 112 113 114def save_json(file_path: Path, data: dict[str, Any]) -> None: 115 """Save JSON data to a file within storage. 116 117 Creates parent directories if they don't exist. 118 119 Args: 120 file_path: Path to the JSON file (should be within storage root) 121 data: Dictionary to serialize as JSON 122 """ 123 file_path.parent.mkdir(parents=True, exist_ok=True) 124 with open(file_path, "w", encoding="utf-8") as f: 125 json.dump(data, f, indent=4)
35@lru_cache(maxsize=1) 36def get_storage_root() -> Path: 37 """Get the root directory for storing VoxKit data. 38 39 Returns: 40 Path to the storage root directory (expanded from tilde notation) 41 42 Raises: 43 ValueError: If STORAGE_ROOT does not start with tilde (~) 44 """ 45 if STORAGE_ROOT.startswith("~"): 46 return Path(STORAGE_ROOT).expanduser() 47 else: 48 raise ValueError("STORAGE_ROOT must be a valid path starting with '~'")
Get the root directory for storing VoxKit data.
Returns: Path to the storage root directory (expanded from tilde notation)
Raises: ValueError: If STORAGE_ROOT does not start with tilde (~)
51def generate_unique_id(prefix: str | None = None) -> str: 52 """Generate a unique identifier with the given prefix and current timestamp. 53 54 Args: 55 prefix: Optional prefix to prepend to the timestamp 56 57 Returns: 58 Unique identifier string in format: [prefix_]YYYYMMDD_HHMMSS_ffffff 59 """ 60 global _last_id_dt 61 # Windows datetime.now() has ~15ms resolution; enforce monotonic microsecond precision. 62 with _id_lock: 63 now = datetime.now() 64 if _last_id_dt is not None and now <= _last_id_dt: 65 now = _last_id_dt + timedelta(microseconds=1) 66 _last_id_dt = now 67 now_str = now.strftime("%Y%m%d_%H%M%S_%f") 68 if prefix: 69 return f"{prefix}_{now_str}" 70 return now_str
Generate a unique identifier with the given prefix and current timestamp.
Args: prefix: Optional prefix to prepend to the timestamp
Returns: Unique identifier string in format: [prefix_]YYYYMMDD_HHMMSS_ffffff
73def readable_from_unique_id(date_str: str) -> str: 74 """Convert a unique ID timestamp to a human-readable format. 75 76 Accepts both plain timestamps (YYYYMMDD_HHMMSS_ffffff) and prefixed IDs 77 produced by generate_unique_id (e.g. prefix_YYYYMMDD_HHMMSS_ffffff). 78 79 Args: 80 date_str: Unique ID string, optionally prefixed as [prefix_]YYYYMMDD_HHMMSS_ffffff 81 82 Returns: 83 Human-readable date string (e.g., "January 01, 2024 at 12:00:00 PM") 84 """ 85 parts = date_str.split("_") 86 for i, part in enumerate(parts): 87 if len(part) == 8 and part.isdigit(): 88 date_str = "_".join(parts[i:]) 89 break 90 else: 91 raise ValueError(f"No valid timestamp found in unique ID: {date_str!r}") 92 dt = datetime.strptime(date_str, "%Y%m%d_%H%M%S_%f") 93 return dt.strftime("%B %d, %Y at %I:%M:%S %p")
Convert a unique ID timestamp to a human-readable format.
Accepts both plain timestamps (YYYYMMDD_HHMMSS_ffffff) and prefixed IDs produced by generate_unique_id (e.g. prefix_YYYYMMDD_HHMMSS_ffffff).
Args: date_str: Unique ID string, optionally prefixed as [prefix_]YYYYMMDD_HHMMSS_ffffff
Returns: Human-readable date string (e.g., "January 01, 2024 at 12:00:00 PM")
96def is_first_launch() -> bool: 97 """Check if this is the first launch of the application. 98 99 Returns: 100 True if this is the first launch, False otherwise 101 """ 102 storage_root = get_storage_root() 103 flag_file = storage_root / ".first_launch_complete" 104 return not flag_file.exists()
Check if this is the first launch of the application.
Returns: True if this is the first launch, False otherwise
107def mark_first_launch_complete() -> None: 108 """Mark the first launch as complete by creating a flag file.""" 109 storage_root = get_storage_root() 110 storage_root.mkdir(parents=True, exist_ok=True) 111 flag_file = storage_root / ".first_launch_complete" 112 flag_file.touch()
Mark the first launch as complete by creating a flag file.
115def save_json(file_path: Path, data: dict[str, Any]) -> None: 116 """Save JSON data to a file within storage. 117 118 Creates parent directories if they don't exist. 119 120 Args: 121 file_path: Path to the JSON file (should be within storage root) 122 data: Dictionary to serialize as JSON 123 """ 124 file_path.parent.mkdir(parents=True, exist_ok=True) 125 with open(file_path, "w", encoding="utf-8") as f: 126 json.dump(data, f, indent=4)
Save JSON data to a file within storage.
Creates parent directories if they don't exist.
Args: file_path: Path to the JSON file (should be within storage root) data: Dictionary to serialize as JSON