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:
@@ -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
|
||||
|
Reference in New Issue
Block a user