You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
3.4 KiB

  1. import posixpath
  2. class File:
  3. """File abstraction. Only real method is to get fullpath"""
  4. def __init__(self, name, parent_dir):
  5. self.name = name
  6. self.parent_dir = parent_dir
  7. self.fullpath = None
  8. def get_fullpath(self, real_time=False):
  9. if self.fullpath is None or real_time:
  10. if self.parent is None:
  11. self.fullpath = self.name
  12. else:
  13. self.fullpath = posixpath.join(
  14. self.parent_dir.get_fullpath(real_time), self.name
  15. )
  16. return self.fullpath
  17. class Directory(File):
  18. """Directory abstraction with optional listener"""
  19. def __init__(self, name, is_listening=False):
  20. super().__init__(name, None)
  21. self.children = {}
  22. self.is_listening = is_listening
  23. if is_listening:
  24. self.modified = False
  25. def __index__(self, key):
  26. try:
  27. return self.children[key]
  28. except KeyError:
  29. raise FileNotFoundError
  30. def mark_modified(self):
  31. if self.is_listening:
  32. self.modified = True
  33. if self.parent_dir:
  34. self.parent_dir.mark_modified()
  35. def traverse(self, path, create_intermediate=False, notify=True):
  36. """Traverse filesystem returning either file or directory"""
  37. if type(path) == str:
  38. path = path.split("/")
  39. if len(path) == 1:
  40. return self[path[0]]
  41. _next, *rest = path
  42. if _next == "..":
  43. if self.parent_dir:
  44. selected = self.parent_dir
  45. else:
  46. selected = self
  47. elif _next == ".":
  48. selected = self
  49. else:
  50. try:
  51. selected = self[_next]
  52. except FileNotFoundError as e:
  53. if create_intermediate:
  54. new = Directory(_next, self.is_listening)
  55. self.add_child(new, notify)
  56. selected = new
  57. else:
  58. raise e
  59. return selected.traverse(
  60. rest, create_intermediate=create_intermediate, notify=notify
  61. )
  62. def add_child(self, child, notify=True):
  63. if notify and self.is_listening:
  64. self.mark_modified()
  65. self.children[child.name] = child
  66. child.parent_dir = self
  67. def remove_child(self, child_name, notify=True):
  68. if notify and self.is_listening:
  69. self.mark_modified()
  70. del self[child_name]
  71. def iter_files(self):
  72. for child in self.children.values():
  73. if isinstance(child, Directory):
  74. yield from child.iter_files()
  75. else:
  76. yield child
  77. @classmethod
  78. def from_dir_listing(cls, listing, rootname="", sep="/", is_listening=False):
  79. """Create a Directory object with files from file listing"""
  80. root = Directory(rootname, is_listening=is_listening)
  81. for file in listing:
  82. parts, file = file.split(sep)
  83. parent = root.traverse(parts, create_intermediate=True, notify=False)
  84. file = File(file)
  85. parent.add_child(file, notify=False)
  86. return root
  87. def get_unmodified(dirtree: Directory):
  88. if dirtree.modified:
  89. for child in dirtree.children.values():
  90. if isinstance(child, Directory):
  91. yield from get_unmodified(child)
  92. else:
  93. yield child.get_fullpath()
  94. else:
  95. yield dirtree.get_fullpath() + dirtree.sep