You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/scripts/detect_file_changes/snapshot_folder/snapshot_folder.py

119 lines
5.2 KiB
Python

#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import os
import fnmatch
import pathlib
"""This module contains FolderSnapshot, a class which can create and compare 'snapshots'
of folders (The snapshots just store the modtimes / existence of files and folders), and
also can compare two snapshots to return a SnapshotComparison which represents the diffs
"""
class SnapshotComparison:
""" This class just holds the diffs calculated between two folder trees."""
def __init__(self):
self.deleted_files = []
self.added_files = []
self.changed_files = []
self.dirs_added = []
self.dirs_removed = []
def any_changed(self):
"""Returns True if any changes were detected"""
return self.deleted_files or self.added_files or self.changed_files or self.dirs_added or self.dirs_removed
def enumerate_changes(self):
"""Enumerates changes, yielding each as a pair of (string, string), that is, (type of change, filename)"""
for file_entry in self.deleted_files:
yield ("DELETED", file_entry)
for file_entry in self.added_files:
yield ("ADDED", file_entry)
for file_entry in self.changed_files:
yield ("CHANGED", file_entry)
for dir_entry in self.dirs_added:
yield ("FOLDER_ADDED", dir_entry)
for dir_entry in self.dirs_removed:
yield ("FOLDER_DELETED", dir_entry)
class FolderSnapshot:
""" This class stores a snapshot of a folder state and has utility functions to compare snapshots"""
def __init__(self):
self.file_modtimes = {}
self.folder_paths = []
pass
@staticmethod
def _matches_ignore_pattern(in_string, ignore_patterns):
for pattern in ignore_patterns:
if fnmatch.fnmatch(in_string, pattern):
return True
# we also care if the last part is in the patterns
# this is to cover situatiosn where the pattern has 'build' in it as opposed to *build*
# and the name of the file is literally 'build'
_, filepart = os.path.split(in_string)
if fnmatch.fnmatch(filepart, pattern):
return True
return False
@staticmethod
def CreateSnapshot(root_folder, ignore_patterns):
"""Create a new FolderSnapshot based on a root folder and ignore patterns."""
folder_snap = FolderSnapshot()
ignored_folders = []
for root, dir_names, file_names in os.walk(root_folder, followlinks=False):
for dir_name in dir_names:
fullpath = os.path.normpath(os.path.join(root, dir_name)).replace('\\', '/')
if FolderSnapshot._matches_ignore_pattern(fullpath, ignore_patterns):
ignored_folders.append(fullpath)
continue
if os.path.dirname(fullpath) in ignored_folders:
# we want to emulate not 'walking' down any folders themselves that have been omitted:
ignored_folders.append(fullpath)
continue
folder_snap.folder_paths.append(fullpath)
for file_name in file_names:
fullpath = os.path.normpath(os.path.join(root, file_name)).replace('\\', '/')
if FolderSnapshot._matches_ignore_pattern(fullpath, ignore_patterns):
continue
if os.path.dirname(fullpath) in ignored_folders:
# we want to emulate not 'walking' down any folders themselves that have been omitted:
continue
folder_snap.file_modtimes[fullpath] = os.stat(fullpath).st_mtime
return folder_snap
@staticmethod
def CompareSnapshots(before, after):
"""Return a SnapshotComparison representing the difference between two FolderShapshot objects"""
comparison = SnapshotComparison()
for file_name in before.file_modtimes.keys():
if file_name not in after.file_modtimes:
comparison.deleted_files.append(file_name)
else:
if before.file_modtimes[file_name] != after.file_modtimes[file_name]:
comparison.changed_files.append(file_name)
for file_name in after.file_modtimes.keys():
if file_name not in before.file_modtimes:
comparison.added_files.append(file_name)
for folder_path in before.folder_paths:
if folder_path not in after.folder_paths:
comparison.dirs_removed.append(folder_path)
for folder_path in after.folder_paths:
if folder_path not in before.folder_paths:
comparison.dirs_added.append(folder_path)
return comparison