snapshot_folder.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. #
  2. # Copyright (c) Contributors to the Open 3D Engine Project.
  3. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. #
  5. # SPDX-License-Identifier: Apache-2.0 OR MIT
  6. #
  7. #
  8. import os
  9. import fnmatch
  10. import pathlib
  11. """This module contains FolderSnapshot, a class which can create and compare 'snapshots'
  12. of folders (The snapshots just store the modtimes / existence of files and folders), and
  13. also can compare two snapshots to return a SnapshotComparison which represents the diffs
  14. """
  15. class SnapshotComparison:
  16. """ This class just holds the diffs calculated between two folder trees."""
  17. def __init__(self):
  18. self.deleted_files = []
  19. self.added_files = []
  20. self.changed_files = []
  21. self.dirs_added = []
  22. self.dirs_removed = []
  23. def any_changed(self):
  24. """Returns True if any changes were detected"""
  25. return self.deleted_files or self.added_files or self.changed_files or self.dirs_added or self.dirs_removed
  26. def enumerate_changes(self):
  27. """Enumerates changes, yielding each as a pair of (string, string), that is, (type of change, filename)"""
  28. for file_entry in self.deleted_files:
  29. yield ("DELETED", file_entry)
  30. for file_entry in self.added_files:
  31. yield ("ADDED", file_entry)
  32. for file_entry in self.changed_files:
  33. yield ("CHANGED", file_entry)
  34. for dir_entry in self.dirs_added:
  35. yield ("FOLDER_ADDED", dir_entry)
  36. for dir_entry in self.dirs_removed:
  37. yield ("FOLDER_DELETED", dir_entry)
  38. class FolderSnapshot:
  39. """ This class stores a snapshot of a folder state and has utility functions to compare snapshots"""
  40. def __init__(self):
  41. self.file_modtimes = {}
  42. self.folder_paths = []
  43. pass
  44. @staticmethod
  45. def _matches_ignore_pattern(in_string, ignore_patterns):
  46. for pattern in ignore_patterns:
  47. if fnmatch.fnmatch(in_string, pattern):
  48. return True
  49. # we also care if the last part is in the patterns
  50. # this is to cover situatiosn where the pattern has 'build' in it as opposed to *build*
  51. # and the name of the file is literally 'build'
  52. _, filepart = os.path.split(in_string)
  53. if fnmatch.fnmatch(filepart, pattern):
  54. return True
  55. return False
  56. @staticmethod
  57. def CreateSnapshot(root_folder, ignore_patterns):
  58. """Create a new FolderSnapshot based on a root folder and ignore patterns."""
  59. folder_snap = FolderSnapshot()
  60. ignored_folders = []
  61. for root, dir_names, file_names in os.walk(root_folder, followlinks=False):
  62. for dir_name in dir_names:
  63. fullpath = os.path.normpath(os.path.join(root, dir_name)).replace('\\', '/')
  64. if FolderSnapshot._matches_ignore_pattern(fullpath, ignore_patterns):
  65. ignored_folders.append(fullpath)
  66. continue
  67. if os.path.dirname(fullpath) in ignored_folders:
  68. # we want to emulate not 'walking' down any folders themselves that have been omitted:
  69. ignored_folders.append(fullpath)
  70. continue
  71. folder_snap.folder_paths.append(fullpath)
  72. for file_name in file_names:
  73. fullpath = os.path.normpath(os.path.join(root, file_name)).replace('\\', '/')
  74. if FolderSnapshot._matches_ignore_pattern(fullpath, ignore_patterns):
  75. continue
  76. if os.path.dirname(fullpath) in ignored_folders:
  77. # we want to emulate not 'walking' down any folders themselves that have been omitted:
  78. continue
  79. folder_snap.file_modtimes[fullpath] = os.stat(fullpath).st_mtime
  80. return folder_snap
  81. @staticmethod
  82. def CompareSnapshots(before, after):
  83. """Return a SnapshotComparison representing the difference between two FolderShapshot objects"""
  84. comparison = SnapshotComparison()
  85. for file_name in before.file_modtimes.keys():
  86. if file_name not in after.file_modtimes:
  87. comparison.deleted_files.append(file_name)
  88. else:
  89. if before.file_modtimes[file_name] != after.file_modtimes[file_name]:
  90. comparison.changed_files.append(file_name)
  91. for file_name in after.file_modtimes.keys():
  92. if file_name not in before.file_modtimes:
  93. comparison.added_files.append(file_name)
  94. for folder_path in before.folder_paths:
  95. if folder_path not in after.folder_paths:
  96. comparison.dirs_removed.append(folder_path)
  97. for folder_path in after.folder_paths:
  98. if folder_path not in before.folder_paths:
  99. comparison.dirs_added.append(folder_path)
  100. return comparison