entry.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. # SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. from typing import Any, Optional
  4. from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct
  5. from .exceptions import LowerCaseException, TooLongNameException
  6. from .fatfs_state import FATFSState
  7. from .utils import is_valid_fatfs_name, pad_string
  8. class Entry:
  9. """
  10. The class Entry represents entry of the directory.
  11. """
  12. ATTR_READ_ONLY = 0x01
  13. ATTR_HIDDEN = 0x02
  14. ATTR_SYSTEM = 0x04
  15. ATTR_VOLUME_ID = 0x08
  16. ATTR_DIRECTORY = 0x10
  17. ATTR_ARCHIVE = 0x20
  18. MAX_NAME_SIZE_S = 8
  19. MAX_EXT_SIZE_S = 3
  20. ENTRY_FORMAT_SHORT_NAME = Struct(
  21. 'DIR_Name' / PaddedString(MAX_NAME_SIZE_S, 'utf-8'),
  22. 'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE_S, 'utf-8'),
  23. 'DIR_Attr' / Int8ul,
  24. 'DIR_NTRes' / Const(b'\x00'),
  25. 'DIR_CrtTimeTenth' / Const(b'\x00'),
  26. 'DIR_CrtTime' / Const(b'\x01\x00'),
  27. 'DIR_CrtDate' / Const(b'\x21\x00'),
  28. 'DIR_LstAccDate' / Const(b'\x00\x00'),
  29. 'DIR_FstClusHI' / Const(b'\x00\x00'),
  30. 'DIR_WrtTime' / Const(b'\x01\x00'),
  31. 'DIR_WrtDate' / Const(b'\x01\x00'),
  32. 'DIR_FstClusLO' / Int16ul,
  33. 'DIR_FileSize' / Int32ul,
  34. )
  35. # IDF-4044
  36. ENTRY_FORMAT_LONG_NAME = Struct()
  37. def __init__(self,
  38. entry_id: int,
  39. parent_dir_entries_address: int,
  40. fatfs_state: FATFSState) -> None:
  41. self.fatfs_state = fatfs_state
  42. self.id = entry_id
  43. self.entry_address = parent_dir_entries_address + self.id * self.fatfs_state.entry_size
  44. self._is_alias = False
  45. self._is_empty = True
  46. @property
  47. def is_empty(self) -> bool:
  48. return self._is_empty
  49. def _parse_entry(self, entry_bytearray: Optional[bytearray]) -> dict:
  50. if self.fatfs_state.long_names_enabled:
  51. return Entry.ENTRY_FORMAT_LONG_NAME.parse(entry_bytearray) # type: ignore
  52. return Entry.ENTRY_FORMAT_SHORT_NAME.parse(entry_bytearray) # type: ignore
  53. def _build_entry(self, **kwargs) -> Any: # type: ignore
  54. if self.fatfs_state.long_names_enabled:
  55. return Entry.ENTRY_FORMAT_LONG_NAME.build(dict(**kwargs))
  56. return Entry.ENTRY_FORMAT_SHORT_NAME.build(dict(**kwargs))
  57. @property
  58. def entry_bytes(self) -> Any:
  59. return self.fatfs_state.binary_image[self.entry_address: self.entry_address + self.fatfs_state.entry_size]
  60. @entry_bytes.setter
  61. def entry_bytes(self, value: int) -> None:
  62. self.fatfs_state.binary_image[self.entry_address: self.entry_address + self.fatfs_state.entry_size] = value
  63. def _clean_entry(self) -> None:
  64. self.entry_bytes = self.fatfs_state.entry_size * b'\x00'
  65. def allocate_entry(self,
  66. first_cluster_id: int,
  67. entity_name: str,
  68. entity_type: int,
  69. entity_extension: str = '',
  70. size: int = 0) -> None:
  71. """
  72. :param first_cluster_id: id of the first data cluster for given entry
  73. :param entity_name: name recorded in the entry
  74. :param entity_extension: extension recorded in the entry
  75. :param size: size of the content of the file
  76. :param entity_type: type of the entity (file [0x20] or directory [0x10])
  77. :returns: None
  78. :raises LowerCaseException: In case when long_names_enabled is set to False and filename exceeds 8 chars
  79. for name or 3 chars for extension the exception is raised
  80. """
  81. if not ((is_valid_fatfs_name(entity_name) and
  82. is_valid_fatfs_name(entity_extension)) or
  83. self.fatfs_state.long_names_enabled):
  84. raise LowerCaseException('Lower case is not supported because long name support is not enabled!')
  85. # clean entry before allocation
  86. self._clean_entry()
  87. self._is_empty = False
  88. object_name = entity_name.upper()
  89. object_extension = entity_extension.upper()
  90. # implementation of long names support will be part of IDF-4044
  91. exceeds_short_name = len(object_name) > Entry.MAX_NAME_SIZE_S or len(object_extension) > Entry.MAX_EXT_SIZE_S
  92. if not self.fatfs_state.long_names_enabled and exceeds_short_name:
  93. raise TooLongNameException(
  94. 'Maximal length of the object name is 8 characters and 3 characters for extension!')
  95. start_address = self.entry_address
  96. end_address = start_address + self.fatfs_state.entry_size
  97. self.fatfs_state.binary_image[start_address: end_address] = self._build_entry(
  98. DIR_Name=pad_string(object_name, size=Entry.MAX_NAME_SIZE_S),
  99. DIR_Name_ext=pad_string(object_extension, size=Entry.MAX_EXT_SIZE_S),
  100. DIR_Attr=entity_type,
  101. DIR_FstClusLO=first_cluster_id,
  102. DIR_FileSize=size
  103. )
  104. def update_content_size(self, content_size: int) -> None:
  105. parsed_entry = self._parse_entry(self.entry_bytes)
  106. parsed_entry.DIR_FileSize = content_size # type: ignore
  107. self.entry_bytes = Entry.ENTRY_FORMAT_SHORT_NAME.build(parsed_entry)