-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCheckManifest.py
138 lines (113 loc) · 4.3 KB
/
CheckManifest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/env python
"""Determine whether the manifest for client files is up to date.
"""
import hashlib
import os
import cdr
from cdrcgi import Controller, Reporter
import lxml.etree as etree
class Control(Controller):
SUBTITLE = "Client Manifest Check"
COLNAMES = "Path", "Manifest", "File", "Status"
MANIFEST_NAME = cdr.MANIFEST_NAME.upper()
def populate_form(self, form):
"""Bypass the form, going straight to the report."""
self.show_report()
def build_tables(self):
"""Show the problems found in the manifest, if any."""
manifest_files = self.parse_manifest()
client_files = self.gather_files()
rows = sorted(self.find_errors(manifest_files, client_files))
caption = f"{len(rows):d} error(s) found"
return Reporter.Table(rows, columns=self.COLNAMES, caption=caption)
def find_errors(self, manifest, client):
"""Compare the manifest with the files looking for problems.
Pass:
manifest
dictionary of `File` objects pulled from the manifest
client
dictionary of `File` objects representing the file system
Return:
sequence of tuples representing problems found
"""
errors = []
for key in manifest:
if self.MANIFEST_NAME in key:
continue
f = manifest[key]
if key not in client:
errors.append((f.path, f.checksum, None, "File missing"))
else:
c = client[key]
if c.error:
errors.append((f.path, f.checksum, None, c.error))
elif f.checksum != c.checksum:
errors.append((f.path, f.checksum, c.checksum,
"Checksums don't match"))
for key in client:
if key not in manifest:
f = client[key]
errors.append((f.path, None, f.checksum, "Not in manifest"))
return errors
def parse_manifest(self):
"""Create dictionary of `File` objects for nodes in the manifest."""
tree = etree.parse(cdr.MANIFEST_PATH)
files = {}
for node in tree.getroot().findall("FileList/File"):
f = File(node)
files[f.path.upper()] = f
return files
def gather_files(self):
"""Create a dictionary of `File` objects for the client files."""
os.chdir(cdr.CLIENT_FILES_DIR)
files = {}
for f in self.recurse("."):
files[f.path.upper()] = f
return files
def recurse(self, dir_path):
"""Do the real work for the `gather_files()` method.
Pass:
dir_path
string for the directory in which to look for files
Return:
sequence of `File` objects
"""
files = []
for name in os.listdir(dir_path):
this_path = os.path.join(dir_path, name)
if os.path.isdir(this_path):
files += self.recurse(this_path)
else:
files.append(File(path=this_path))
return files
class File:
"""Capture the checksum and file path for a CDR client file.
Can represent information from a node in the manifest or
information from a file on the disk.
"""
def __init__(self, node=None, path=None):
"""Remember the path and get the file's checksum.
Pass either a node object if parsing the manifest, or
a path string if traversing the file system.
"""
# Initialize the attributes.
self.path = path
self.checksum = self.error = None
# Parse the node if this is from the manifest.
if node is not None:
for child in node:
if child.tag == "Name":
self.path = child.text
elif child.tag == "Checksum":
self.checksum = child.text
# Otherwise, get the bytes from the file and calculate the checksum.
else:
try:
with open(path, "rb") as fp:
file_bytes = fp.read()
m = hashlib.md5()
m.update(file_bytes)
self.checksum = m.hexdigest().lower()
except Exception as e:
self.error = str(e)
Control().run()