Skip to content

Commit 87b4ab2

Browse files
committed
Improve write_cache() function, suggested by CRAI
1 parent 26d5994 commit 87b4ab2

File tree

1 file changed

+70
-40
lines changed

1 file changed

+70
-40
lines changed

plugwise_usb/helpers/cache.py

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)