backup.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. """Backup."""
  2. from __future__ import annotations
  3. import os
  4. import shutil
  5. import tempfile
  6. from time import sleep
  7. from typing import TYPE_CHECKING
  8. from .path import is_safe
  9. if TYPE_CHECKING:
  10. from ..base import HacsBase
  11. from ..repositories.base import HacsRepository
  12. DEFAULT_BACKUP_PATH = f"{tempfile.gettempdir()}/hacs_backup/"
  13. class Backup:
  14. """Backup."""
  15. def __init__(
  16. self,
  17. hacs: HacsBase,
  18. local_path: str | None = None,
  19. backup_path: str = DEFAULT_BACKUP_PATH,
  20. repository: HacsRepository | None = None,
  21. ) -> None:
  22. """initialize."""
  23. self.hacs = hacs
  24. self.repository = repository
  25. self.local_path = local_path or repository.content.path.local
  26. self.backup_path = backup_path
  27. if repository:
  28. self.backup_path = (
  29. tempfile.gettempdir()
  30. + f"/hacs_persistent_{repository.data.category}/"
  31. + repository.data.name
  32. )
  33. self.backup_path_full = f"{self.backup_path}{self.local_path.split('/')[-1]}"
  34. def _init_backup_dir(self) -> bool:
  35. """Init backup dir."""
  36. if not os.path.exists(self.local_path):
  37. return False
  38. if not is_safe(self.hacs, self.local_path):
  39. return False
  40. if os.path.exists(self.backup_path):
  41. shutil.rmtree(self.backup_path)
  42. # Wait for the folder to be removed
  43. while os.path.exists(self.backup_path):
  44. sleep(0.1)
  45. os.makedirs(self.backup_path, exist_ok=True)
  46. return True
  47. def create(self) -> None:
  48. """Create a backup in /tmp"""
  49. if not self._init_backup_dir():
  50. return
  51. try:
  52. if os.path.isfile(self.local_path):
  53. shutil.copyfile(self.local_path, self.backup_path_full)
  54. os.remove(self.local_path)
  55. else:
  56. shutil.copytree(self.local_path, self.backup_path_full)
  57. shutil.rmtree(self.local_path)
  58. while os.path.exists(self.local_path):
  59. sleep(0.1)
  60. self.hacs.log.debug(
  61. "Backup for %s, created in %s",
  62. self.local_path,
  63. self.backup_path_full,
  64. )
  65. except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
  66. self.hacs.log.warning("Could not create backup: %s", exception)
  67. def restore(self) -> None:
  68. """Restore from backup."""
  69. if not os.path.exists(self.backup_path_full):
  70. return
  71. if os.path.isfile(self.backup_path_full):
  72. if os.path.exists(self.local_path):
  73. os.remove(self.local_path)
  74. shutil.copyfile(self.backup_path_full, self.local_path)
  75. else:
  76. if os.path.exists(self.local_path):
  77. shutil.rmtree(self.local_path)
  78. while os.path.exists(self.local_path):
  79. sleep(0.1)
  80. shutil.copytree(self.backup_path_full, self.local_path)
  81. self.hacs.log.debug("Restored %s, from backup %s", self.local_path, self.backup_path_full)
  82. def cleanup(self) -> None:
  83. """Cleanup backup files."""
  84. if not os.path.exists(self.backup_path):
  85. return
  86. shutil.rmtree(self.backup_path)
  87. # Wait for the folder to be removed
  88. while os.path.exists(self.backup_path):
  89. sleep(0.1)
  90. self.hacs.log.debug("Backup dir %s cleared", self.backup_path)
  91. class BackupNetDaemon(Backup):
  92. """BackupNetDaemon."""
  93. def create(self) -> None:
  94. """Create a backup in /tmp"""
  95. if not self._init_backup_dir():
  96. return
  97. for filename in os.listdir(self.repository.content.path.local):
  98. if not filename.endswith(".yaml"):
  99. continue
  100. source_file_name = f"{self.repository.content.path.local}/{filename}"
  101. target_file_name = f"{self.backup_path}/{filename}"
  102. shutil.copyfile(source_file_name, target_file_name)
  103. def restore(self) -> None:
  104. """Create a backup in /tmp"""
  105. if not os.path.exists(self.backup_path):
  106. return
  107. for filename in os.listdir(self.backup_path):
  108. if not filename.endswith(".yaml"):
  109. continue
  110. source_file_name = f"{self.backup_path}/{filename}"
  111. target_file_name = f"{self.repository.content.path.local}/{filename}"
  112. shutil.copyfile(source_file_name, target_file_name)