mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-31 04:50:34 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			244 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright 2003 Dave Abrahams
 | |
| # Copyright 2001, 2002 Vladimir Prus
 | |
| # Copyright 2012 Jurko Gospodnetic
 | |
| # Distributed under the Boost Software License, Version 1.0.
 | |
| # (See accompanying file LICENSE_1_0.txt or copy at
 | |
| # http://www.boost.org/LICENSE_1_0.txt)
 | |
| 
 | |
| ###############################################################################
 | |
| #
 | |
| # Based in part on an old Subversion tree.py source file (tools for comparing
 | |
| # directory trees). See http://subversion.tigris.org for more information.
 | |
| #
 | |
| # Copyright (c) 2001 Sam Tobin-Hochstadt.  All rights reserved.
 | |
| #
 | |
| # This software is licensed as described in the file COPYING, which you should
 | |
| # have received as part of this distribution. The terms are also available at
 | |
| # http://subversion.tigris.org/license-1.html. If newer versions of this
 | |
| # license are posted there, you may use a newer version instead, at your
 | |
| # option.
 | |
| #
 | |
| ###############################################################################
 | |
| 
 | |
| import os
 | |
| import os.path
 | |
| import stat
 | |
| import sys
 | |
| 
 | |
| 
 | |
| class TreeNode:
 | |
|     """
 | |
|       Fundamental data type used to build file system tree structures.
 | |
| 
 | |
|       If CHILDREN is None, then the node represents a file. Otherwise, CHILDREN
 | |
|     is a list of the nodes representing that directory's children.
 | |
| 
 | |
|       NAME is simply the name of the file or directory. CONTENTS is a string
 | |
|     holding the file's contents (if a file).
 | |
| 
 | |
|     """
 | |
| 
 | |
|     def __init__(self, name, children=None, contents=None):
 | |
|         assert children is None or contents is None
 | |
|         self.name = name
 | |
|         self.mtime = 0
 | |
|         self.children = children
 | |
|         self.contents = contents
 | |
|         self.path = name
 | |
| 
 | |
|     def add_child(self, newchild):
 | |
|         assert not self.is_file()
 | |
|         for a in self.children:
 | |
|             if a.name == newchild.name:
 | |
|                 if newchild.is_file():
 | |
|                     a.contents = newchild.contents
 | |
|                     a.path = os.path.join(self.path, newchild.name)
 | |
|                 else:
 | |
|                     for i in newchild.children:
 | |
|                         a.add_child(i)
 | |
|                 break
 | |
|         else:
 | |
|             self.children.append(newchild)
 | |
|             newchild.path = os.path.join(self.path, newchild.name)
 | |
| 
 | |
|     def get_child(self, name):
 | |
|         """
 | |
|           If the given TreeNode directory NODE contains a child named NAME,
 | |
|         return the child; else, return None.
 | |
| 
 | |
|         """
 | |
|         for n in self.children:
 | |
|             if n.name == name:
 | |
|                 return n
 | |
| 
 | |
|     def is_file(self):
 | |
|         return self.children is None
 | |
| 
 | |
|     def pprint(self):
 | |
|         print(" * Node name: %s" % self.name)
 | |
|         print("    Path:     %s" % self.path)
 | |
|         print("    Contents: %s" % self.contents)
 | |
|         if self.is_file():
 | |
|             print("    Children: is a file.")
 | |
|         else:
 | |
|             print("    Children: %d" % len(self.children))
 | |
| 
 | |
| 
 | |
| class TreeDifference:
 | |
|     def __init__(self):
 | |
|         self.added_files = []
 | |
|         self.removed_files = []
 | |
|         self.modified_files = []
 | |
|         self.touched_files = []
 | |
| 
 | |
|     def append(self, other):
 | |
|         self.added_files.extend(other.added_files)
 | |
|         self.removed_files.extend(other.removed_files)
 | |
|         self.modified_files.extend(other.modified_files)
 | |
|         self.touched_files.extend(other.touched_files)
 | |
| 
 | |
|     def ignore_directories(self):
 | |
|         """Removes directories from our lists of found differences."""
 | |
|         not_dir = lambda x : x[-1] != "/"
 | |
|         self.added_files = filter(not_dir, self.added_files)
 | |
|         self.removed_files = filter(not_dir, self.removed_files)
 | |
|         self.modified_files = filter(not_dir, self.modified_files)
 | |
|         self.touched_files = filter(not_dir, self.touched_files)
 | |
| 
 | |
|     def pprint(self, file=sys.stdout):
 | |
|         file.write("Added files   : %s\n" % self.added_files)
 | |
|         file.write("Removed files : %s\n" % self.removed_files)
 | |
|         file.write("Modified files: %s\n" % self.modified_files)
 | |
|         file.write("Touched files : %s\n" % self.touched_files)
 | |
| 
 | |
|     def empty(self):
 | |
|         return not (self.added_files or self.removed_files or
 | |
|             self.modified_files or self.touched_files)
 | |
| 
 | |
| 
 | |
| def build_tree(path):
 | |
|     """
 | |
|       Takes PATH as the folder path, walks the file system below that path, and
 | |
|     creates a tree structure based on any files and folders found there.
 | |
|     Returns the prepared tree structure plus the maximum file modification
 | |
|     timestamp under the given folder.
 | |
| 
 | |
|     """
 | |
|     return _handle_dir(os.path.normpath(path))
 | |
| 
 | |
| 
 | |
| def tree_difference(a, b):
 | |
|     """Compare TreeNodes A and B, and create a TreeDifference instance."""
 | |
|     return _do_tree_difference(a, b, "", True)
 | |
| 
 | |
| 
 | |
| def _do_tree_difference(a, b, parent_path, root=False):
 | |
|     """Internal recursive worker function for tree_difference()."""
 | |
| 
 | |
|     # We do not want to list root node names.
 | |
|     if root:
 | |
|         assert not parent_path
 | |
|         assert not a.is_file()
 | |
|         assert not b.is_file()
 | |
|         full_path = ""
 | |
|     else:
 | |
|         assert a.name == b.name
 | |
|         full_path = parent_path + a.name
 | |
|     result = TreeDifference()
 | |
| 
 | |
|     # A and B are both files.
 | |
|     if a.is_file() and b.is_file():
 | |
|         if a.contents != b.contents:
 | |
|             result.modified_files.append(full_path)
 | |
|         elif a.mtime != b.mtime:
 | |
|             result.touched_files.append(full_path)
 | |
|         return result
 | |
| 
 | |
|     # Directory converted to file.
 | |
|     if not a.is_file() and b.is_file():
 | |
|         result.removed_files.extend(_traverse_tree(a, parent_path))
 | |
|         result.added_files.append(full_path)
 | |
| 
 | |
|     # File converted to directory.
 | |
|     elif a.is_file() and not b.is_file():
 | |
|         result.removed_files.append(full_path)
 | |
|         result.added_files.extend(_traverse_tree(b, parent_path))
 | |
| 
 | |
|     # A and B are both directories.
 | |
|     else:
 | |
|         if full_path:
 | |
|             full_path += "/"
 | |
|         accounted_for = []  # Children present in both trees.
 | |
|         for a_child in a.children:
 | |
|             b_child = b.get_child(a_child.name)
 | |
|             if b_child:
 | |
|                 accounted_for.append(b_child)
 | |
|                 result.append(_do_tree_difference(a_child, b_child, full_path))
 | |
|             else:
 | |
|                 result.removed_files.append(full_path + a_child.name)
 | |
|         for b_child in b.children:
 | |
|             if b_child not in accounted_for:
 | |
|                 result.added_files.extend(_traverse_tree(b_child, full_path))
 | |
| 
 | |
|     return result
 | |
| 
 | |
| 
 | |
| def _traverse_tree(t, parent_path):
 | |
|     """Returns a list of all names in a tree."""
 | |
|     assert not parent_path or parent_path[-1] == "/"
 | |
|     full_node_name = parent_path + t.name
 | |
|     if t.is_file():
 | |
|         result = [full_node_name]
 | |
|     else:
 | |
|         name_prefix = full_node_name + "/"
 | |
|         result = [name_prefix]
 | |
|         for i in t.children:
 | |
|             result.extend(_traverse_tree(i, name_prefix))
 | |
|     return result
 | |
| 
 | |
| 
 | |
| def _get_text(path):
 | |
|     """Return a string with the textual contents of a file at PATH."""
 | |
|     fp = open(path, 'r')
 | |
|     try:
 | |
|         return fp.read()
 | |
|     finally:
 | |
|         fp.close()
 | |
| 
 | |
| 
 | |
| def _handle_dir(path):
 | |
|     """
 | |
|       Main recursive worker function for build_tree(). Returns a newly created
 | |
|     tree node representing the given normalized folder path as well as the
 | |
|     maximum file/folder modification time detected under the same path.
 | |
| 
 | |
|     """
 | |
|     files = []
 | |
|     dirs = []
 | |
|     node = TreeNode(os.path.basename(path), children=[])
 | |
|     max_mtime = node.mtime = os.stat(path).st_mtime
 | |
| 
 | |
|     # List files & folders.
 | |
|     for f in os.listdir(path):
 | |
|         f = os.path.join(path, f)
 | |
|         if os.path.isdir(f):
 | |
|             dirs.append(f)
 | |
|         elif os.path.isfile(f):
 | |
|             files.append(f)
 | |
| 
 | |
|     # Add a child node for each file.
 | |
|     for f in files:
 | |
|         fcontents = _get_text(f)
 | |
|         new_file_node = TreeNode(os.path.basename(f), contents=fcontents)
 | |
|         new_file_node.mtime = os.stat(f).st_mtime
 | |
|         max_mtime = max(max_mtime, new_file_node.mtime)
 | |
|         node.add_child(new_file_node)
 | |
| 
 | |
|     # For each subdir, create a node, walk its tree, add it as a child.
 | |
|     for d in dirs:
 | |
|         new_dir_node, new_max_mtime = _handle_dir(d)
 | |
|         max_mtime = max(max_mtime, new_max_mtime)
 | |
|         node.add_child(new_dir_node)
 | |
| 
 | |
|     return node, max_mtime
 |