mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-11-04 05:50:31 -05: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
							 |