@@ -79,46 +79,76 @@ def _get_writable_os_dir(self) -> str:
7979 )
8080 return os_path_join (os_path_expand_user ("~" ), CACHE_DIR )
8181
82- async def write_cache (self , data : dict [str , str ], rewrite : bool = False ) -> None :
83- """Save information to cache file."""
84- if not self ._initialized :
85- raise CacheError (
86- f"Unable to save cache. Initialize cache file '{ self ._file_name } ' first."
87- )
88-
89- current_data : dict [str , str ] = {}
90- if not rewrite :
91- current_data = await self .read_cache ()
92- processed_keys : list [str ] = []
93- data_to_write : list [str ] = []
94- for _cur_key , _cur_val in current_data .items ():
95- _write_val = _cur_val
96- if _cur_key in data :
97- _write_val = data [_cur_key ]
98- processed_keys .append (_cur_key )
99- data_to_write .append (f"{ _cur_key } { CACHE_KEY_SEPARATOR } { _write_val } \n " )
100- # Write remaining new data
101- for _key , _value in data .items ():
102- if _key not in processed_keys :
103- data_to_write .append (f"{ _key } { CACHE_KEY_SEPARATOR } { _value } \n " )
104-
105- try :
106- async with aiofiles_open (
107- file = self ._cache_file ,
108- mode = "w" ,
109- encoding = UTF8 ,
110- ) as file_data :
111- await file_data .writelines (data_to_write )
112- except OSError as exc :
113- _LOGGER .warning (
114- "%s while writing data to cache file %s" , exc , str (self ._cache_file )
115- )
116- else :
117- if not self ._cache_file_exists :
118- self ._cache_file_exists = True
119- _LOGGER .debug (
120- "Saved %s lines to cache file %s" , str (len (data )), self ._cache_file
121- )
82+ async def write_cache (self , data : dict [str , str ], rewrite : bool = False ) -> None :
83+ """Save information to cache file atomically using aiofiles + temp file."""
84+ if not self ._initialized :
85+ raise CacheError (
86+ f"Unable to save cache. Initialize cache file '{ self ._file_name } ' first."
87+ )
88+
89+ current_data : dict [str , str ] = {}
90+ if not rewrite :
91+ current_data = await self .read_cache ()
92+
93+ processed_keys : list [str ] = []
94+ data_to_write : list [str ] = []
95+
96+ # Prepare data exactly as in original implementation
97+ for _cur_key , _cur_val in current_data .items ():
98+ _write_val = _cur_val
99+ if _cur_key in data :
100+ _write_val = data [_cur_key ]
101+ processed_keys .append (_cur_key )
102+ data_to_write .append (f"{ _cur_key } { CACHE_KEY_SEPARATOR } { _write_val } \n " )
103+
104+ # Write remaining new data
105+ for _key , _value in data .items ():
106+ if _key not in processed_keys :
107+ data_to_write .append (f"{ _key } { CACHE_KEY_SEPARATOR } { _value } \n " )
108+
109+ # Atomic write using aiofiles with temporary file
110+ cache_file_path = Path (self ._cache_file )
111+ temp_path = cache_file_path .with_name (f".{ cache_file_path .name } .tmp.{ os .getpid ()} " )
112+
113+ try :
114+ # Write to temporary file using aiofiles
115+ async with aiofiles_open (
116+ file = str (temp_path ),
117+ mode = "w" ,
118+ encoding = UTF8 ,
119+ ) as temp_file :
120+ await temp_file .writelines (data_to_write )
121+ await temp_file .flush ()
122+
123+ # Atomic rename
124+ if os .name == 'nt' and cache_file_path .exists ():
125+ cache_file_path .unlink ()
126+
127+ temp_path .replace (cache_file_path )
128+ temp_path = None # Successfully renamed
129+
130+ if not self ._cache_file_exists :
131+ self ._cache_file_exists = True
132+
133+ _LOGGER .debug (
134+ "Saved %s lines to cache file %s (aiofiles atomic write)" ,
135+ str (len (data )),
136+ self ._cache_file
137+ )
138+
139+ except OSError as exc :
140+ _LOGGER .warning (
141+ "%s while writing data to cache file %s (aiofiles atomic write)" ,
142+ exc ,
143+ str (self ._cache_file )
144+ )
145+ finally :
146+ # Cleanup on error
147+ if temp_path and temp_path .exists ():
148+ try :
149+ temp_path .unlink ()
150+ except OSError :
151+ pass
122152
123153 async def read_cache (self ) -> dict [str , str ]:
124154 """Return current data from cache file."""
0 commit comments