diff --git a/lua/cp/cache.lua b/lua/cp/cache.lua index 7ff1824..6d56228 100644 --- a/lua/cp/cache.lua +++ b/lua/cp/cache.lua @@ -56,8 +56,11 @@ function M.load() end if vim.fn.filereadable(cache_file) == 0 then - vim.fn.writefile({}, cache_file) - vim.fn.setfperm(cache_file, 'rw-------') + vim.fn.mkdir(vim.fn.fnamemodify(cache_file, ':h'), 'p') + local tmpfile = vim.fn.tempname() + vim.fn.writefile({}, tmpfile) + vim.fn.setfperm(tmpfile, 'rw-------') + vim.uv.fs_rename(tmpfile, cache_file) loaded = true return end @@ -107,8 +110,10 @@ function M.save() cache_data._version = CACHE_VERSION local encoded = vim.json.encode(cache_data) local lines = vim.split(encoded, '\n') - vim.fn.writefile(lines, cache_file) - vim.fn.setfperm(cache_file, 'rw-------') + local tmpfile = vim.fn.tempname() + vim.fn.writefile(lines, tmpfile) + vim.fn.setfperm(tmpfile, 'rw-------') + vim.uv.fs_rename(tmpfile, cache_file) end) end diff --git a/lua/cp/credentials.lua b/lua/cp/credentials.lua index 4f22038..1d7e399 100644 --- a/lua/cp/credentials.lua +++ b/lua/cp/credentials.lua @@ -120,8 +120,10 @@ function M.logout(platform) local ok, data = pcall(vim.fn.json_decode, vim.fn.readfile(cookie_file, 'b')) if ok and type(data) == 'table' then data[platform] = nil - vim.fn.writefile({ vim.fn.json_encode(data) }, cookie_file) - vim.fn.setfperm(cookie_file, 'rw-------') + local tmpfile = vim.fn.tempname() + vim.fn.writefile({ vim.fn.json_encode(data) }, tmpfile) + vim.fn.setfperm(tmpfile, 'rw-------') + vim.uv.fs_rename(tmpfile, cookie_file) end end logger.log(display .. ' credentials cleared', { level = vim.log.levels.INFO, override = true }) diff --git a/scrapers/base.py b/scrapers/base.py index e98990c..4d36206 100644 --- a/scrapers/base.py +++ b/scrapers/base.py @@ -3,6 +3,7 @@ import json import os import re import sys +import tempfile from abc import ABC, abstractmethod from pathlib import Path from typing import Any @@ -20,6 +21,18 @@ from .models import ( _COOKIE_FILE = Path.home() / ".cache" / "cp-nvim" / "cookies.json" +def _atomic_write(path: Path, content: str) -> None: + fd, tmp = tempfile.mkstemp(dir=path.parent, prefix=".tmp-") + try: + os.fchmod(fd, 0o600) + with os.fdopen(fd, "w") as f: + f.write(content) + os.replace(tmp, path) + except BaseException: + os.unlink(tmp) + raise + + def load_platform_cookies(platform: str) -> Any | None: try: data = json.loads(_COOKIE_FILE.read_text()) @@ -29,22 +42,20 @@ def load_platform_cookies(platform: str) -> Any | None: def save_platform_cookies(platform: str, data: Any) -> None: - _COOKIE_FILE.parent.mkdir(parents=True, exist_ok=True) + _COOKIE_FILE.parent.mkdir(parents=True, exist_ok=True, mode=0o700) try: existing = json.loads(_COOKIE_FILE.read_text()) except Exception: existing = {} existing[platform] = data - _COOKIE_FILE.write_text(json.dumps(existing)) - _COOKIE_FILE.chmod(0o600) + _atomic_write(_COOKIE_FILE, json.dumps(existing)) def clear_platform_cookies(platform: str) -> None: try: existing = json.loads(_COOKIE_FILE.read_text()) existing.pop(platform, None) - _COOKIE_FILE.write_text(json.dumps(existing)) - _COOKIE_FILE.chmod(0o600) + _atomic_write(_COOKIE_FILE, json.dumps(existing)) except Exception: pass