manifest.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import logging
  2. import os
  3. import posixpath
  4. from pathlib import Path
  5. from flipper.utils import timestamp, file_md5
  6. from flipper.utils.fstree import FsNode, compare_fs_trees
  7. MANIFEST_VERSION = 0
  8. class ManifestRecord:
  9. tag = None
  10. @staticmethod
  11. def fromLine(line):
  12. raise NotImplementedError
  13. def toLine(self):
  14. raise NotImplementedError
  15. def _unpack(self, manifest, key, nodetype):
  16. key, value = manifest.readline().split(":", 1)
  17. assert key == key
  18. return nodetype(value)
  19. MANIFEST_TAGS_RECORDS = {}
  20. def addManifestRecord(record: ManifestRecord):
  21. assert record.tag
  22. MANIFEST_TAGS_RECORDS[record.tag] = record
  23. class ManifestRecordVersion(ManifestRecord):
  24. tag = "V"
  25. def __init__(self, version):
  26. self.version = version
  27. @staticmethod
  28. def fromLine(line):
  29. return ManifestRecordVersion(int(line))
  30. def toLine(self):
  31. return f"{self.tag}:{self.version}\n"
  32. addManifestRecord(ManifestRecordVersion)
  33. class ManifestRecordTimestamp(ManifestRecord):
  34. tag = "T"
  35. def __init__(self, timestamp: int):
  36. self.timestamp = int(timestamp)
  37. @staticmethod
  38. def fromLine(line):
  39. return ManifestRecordTimestamp(int(line))
  40. def toLine(self):
  41. return f"{self.tag}:{self.timestamp}\n"
  42. addManifestRecord(ManifestRecordTimestamp)
  43. class ManifestRecordDirectory(ManifestRecord):
  44. tag = "D"
  45. def __init__(self, path: str):
  46. self.path = path
  47. @staticmethod
  48. def fromLine(line):
  49. return ManifestRecordDirectory(line)
  50. def toLine(self):
  51. return f"{self.tag}:{self.path}\n"
  52. addManifestRecord(ManifestRecordDirectory)
  53. class ManifestRecordFile(ManifestRecord):
  54. tag = "F"
  55. def __init__(self, path: str, md5: str, size: int):
  56. self.path = path
  57. self.md5 = md5
  58. self.size = size
  59. @staticmethod
  60. def fromLine(line):
  61. data = line.split(":", 3)
  62. return ManifestRecordFile(data[2], data[0], int(data[1]))
  63. def toLine(self):
  64. return f"{self.tag}:{self.md5}:{self.size}:{self.path}\n"
  65. addManifestRecord(ManifestRecordFile)
  66. class Manifest:
  67. def __init__(self, timestamp_value=None):
  68. self.version = None
  69. self.records = []
  70. self.records.append(ManifestRecordVersion(MANIFEST_VERSION))
  71. self.records.append(ManifestRecordTimestamp(timestamp_value or timestamp()))
  72. self.logger = logging.getLogger(self.__class__.__name__)
  73. def load(self, filename):
  74. with open(filename, "r") as manifest:
  75. for line in manifest.readlines():
  76. line = line.strip()
  77. if len(line) == 0:
  78. continue
  79. tag, line = line.split(":", 1)
  80. record = MANIFEST_TAGS_RECORDS[tag].fromLine(line)
  81. self.records.append(record)
  82. def save(self, filename):
  83. with open(filename, "w+", newline="\n") as manifest:
  84. for record in self.records:
  85. manifest.write(record.toLine())
  86. def addDirectory(self, path):
  87. self.records.append(ManifestRecordDirectory(path))
  88. def addFile(self, path, md5, size):
  89. self.records.append(ManifestRecordFile(path, md5, size))
  90. def create(self, directory_path, ignore_files=["Manifest"]):
  91. for root, dirs, files in os.walk(directory_path):
  92. dirs.sort()
  93. files.sort()
  94. relative_root = root.replace(directory_path, "", 1)
  95. if relative_root:
  96. relative_root = Path(relative_root).as_posix()
  97. if relative_root.startswith("/"):
  98. relative_root = relative_root[1:]
  99. # process directories
  100. for dirname in dirs:
  101. relative_dir_path = posixpath.join(relative_root, dirname)
  102. self.logger.debug(f'Adding directory: "{relative_dir_path}"')
  103. self.addDirectory(relative_dir_path)
  104. # Process files
  105. for file in files:
  106. relative_file_path = posixpath.join(relative_root, file)
  107. if file in ignore_files:
  108. self.logger.info(f'Skipping file "{relative_file_path}"')
  109. continue
  110. full_file_path = posixpath.join(root, file)
  111. self.logger.debug(f'Adding file: "{relative_file_path}"')
  112. self.addFile(
  113. relative_file_path,
  114. file_md5(full_file_path),
  115. os.path.getsize(full_file_path),
  116. )
  117. def toFsTree(self):
  118. root = FsNode("", FsNode.NodeType.Directory)
  119. for record in self.records:
  120. if isinstance(record, ManifestRecordDirectory):
  121. root.addDirectory(record.path)
  122. elif isinstance(record, ManifestRecordFile):
  123. root.addFile(record.path, record.md5, record.size)
  124. return root
  125. @staticmethod
  126. def compare(left: "Manifest", right: "Manifest"):
  127. return compare_fs_trees(left.toFsTree(), right.toFsTree())