1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-10 07:12:21 +01:00

Make file generation saving atomic (#792)

* Make file generation saving atomic

* Lint

* Python 2 Compat

* Fix

* Handle file not found error
This commit is contained in:
Otto Winter
2019-10-24 21:53:42 +02:00
committed by GitHub
parent d62ef35860
commit bb2582717f
6 changed files with 123 additions and 77 deletions

View File

@@ -1,10 +1,11 @@
from __future__ import print_function
import codecs
import logging
import os
from esphome.py_compat import char_to_byte, text_type
from esphome.py_compat import char_to_byte, text_type, IS_PY2, encode_text
_LOGGER = logging.getLogger(__name__)
@@ -79,15 +80,15 @@ def run_system_command(*args):
def mkdir_p(path):
import errno
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
except OSError as err:
import errno
if err.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
raise
from esphome.core import EsphomeError
raise EsphomeError(u"Error creating directories {}: {}".format(path, err))
def is_ip_address(host):
@@ -151,17 +152,6 @@ def is_hassio():
return get_bool_env('ESPHOME_IS_HASSIO')
def copy_file_if_changed(src, dst):
src_text = read_file(src)
if os.path.isfile(dst):
dst_text = read_file(dst)
else:
dst_text = None
if src_text == dst_text:
return
write_file(dst, src_text)
def walk_files(path):
for root, _, files in os.walk(path):
for name in files:
@@ -172,28 +162,99 @@ def read_file(path):
try:
with codecs.open(path, 'r', encoding='utf-8') as f_handle:
return f_handle.read()
except OSError:
except OSError as err:
from esphome.core import EsphomeError
raise EsphomeError(u"Could not read file at {}".format(path))
raise EsphomeError(u"Error reading file {}: {}".format(path, err))
except UnicodeDecodeError as err:
from esphome.core import EsphomeError
raise EsphomeError(u"Error reading file {}: {}".format(path, err))
def _write_file(path, text):
import tempfile
directory = os.path.dirname(path)
mkdir_p(directory)
tmp_path = None
data = encode_text(text)
try:
with tempfile.NamedTemporaryFile(mode="wb", dir=directory, delete=False) as f_handle:
tmp_path = f_handle.name
f_handle.write(data)
# Newer tempfile implementations create the file with mode 0o600
os.chmod(tmp_path, 0o644)
if IS_PY2:
if os.path.exists(path):
os.remove(path)
os.rename(tmp_path, path)
else:
# If destination exists, will be overwritten
os.replace(tmp_path, path)
finally:
if tmp_path is not None and os.path.exists(tmp_path):
try:
os.remove(tmp_path)
except OSError as err:
_LOGGER.error("Write file cleanup failed: %s", err)
def write_file(path, text):
try:
mkdir_p(os.path.dirname(path))
with codecs.open(path, 'w+', encoding='utf-8') as f_handle:
f_handle.write(text)
_write_file(path, text)
except OSError:
from esphome.core import EsphomeError
raise EsphomeError(u"Could not write file at {}".format(path))
def write_file_if_changed(text, dst):
def write_file_if_changed(path, text):
src_content = None
if os.path.isfile(dst):
src_content = read_file(dst)
if os.path.isfile(path):
src_content = read_file(path)
if src_content != text:
write_file(dst, text)
write_file(path, text)
def copy_file_if_changed(src, dst):
import shutil
if file_compare(src, dst):
return
mkdir_p(os.path.dirname(dst))
try:
shutil.copy(src, dst)
except OSError as err:
from esphome.core import EsphomeError
raise EsphomeError(u"Error copying file {} to {}: {}".format(src, dst, err))
def list_starts_with(list_, sub):
return len(sub) <= len(list_) and all(list_[i] == x for i, x in enumerate(sub))
def file_compare(path1, path2):
"""Return True if the files path1 and path2 have the same contents."""
import stat
try:
stat1, stat2 = os.stat(path1), os.stat(path2)
except OSError:
# File doesn't exist or another error -> not equal
return False
if stat.S_IFMT(stat1.st_mode) != stat.S_IFREG or stat.S_IFMT(stat2.st_mode) != stat.S_IFREG:
# At least one of them is not a regular file (or does not exist)
return False
if stat1.st_size != stat2.st_size:
# Different sizes
return False
bufsize = 8*1024
# Read files in blocks until a mismatch is found
with open(path1, 'rb') as fh1, open(path2, 'rb') as fh2:
while True:
blob1, blob2 = fh1.read(bufsize), fh2.read(bufsize)
if blob1 != blob2:
# Different content
return False
if not blob1:
# Reached end
return True