supervise-v 4.56 KB
Newer Older
dylan grafmyre's avatar
dylan grafmyre committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python3
"""
    supervise-v - host a vavle bsp modifying process, and return exitcode 2
    if the bsp file did not change

    arguments given in form
    <prog> <args> <mapname>
"""
import sys
import os
import hashlib
import subprocess
import logging
import collections
15
16
17
18
19
20
21
22
23
24
25
26
import copy

BELOW_NORMAL_PRIORITY_CLASS = 0x00004000

def execve_low(execve, *args, **kwargs):
    logging.info('execve: %r', execve)
    return subprocess.run(
            execve,
            *args,
            creationflags=BELOW_NORMAL_PRIORITY_CLASS,
            **kwargs,
    )
dylan grafmyre's avatar
dylan grafmyre committed
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

def hashfile(file_, bufsize=None, hashfunc=hashlib.sha1):
    if bufsize is None:
        bufsize = 1<<16

    obj = hashfunc()

    while True:
        data = file_.read(bufsize)
        if not data:
            break
        obj.update(data)

    return obj

class FileFingerprint:
    def __init__(self, name):
        self.name = name
        self.stat = os.stat(name)
        self.hash = self.hashfile()

    def hashfile(self):
        logging.info('%r: hashing ...', self.name)
        with open(self.name, 'rb') as fh:
            hash_ = hashfile(fh)
        logging.info('%r: hashing ... done: %r', self.name, hash_.hexdigest())
        return hash_

    def __eq__(self, other):
dylan grafmyre's avatar
dylan grafmyre committed
56
57
        if other is None:
            return False
dylan grafmyre's avatar
dylan grafmyre committed
58
59
        if other.stat.st_size != self.stat.st_size:
            return False
dylan grafmyre's avatar
dylan grafmyre committed
60
61
        if not isinstance(other, self.__class__):
            raise NotImplementedError('cannot compare {s.__class__.__name__!r} to {o.__class__.__name__:!r}'.format(s=self, o=other))
dylan grafmyre's avatar
dylan grafmyre committed
62
63
64
        return other.hash.digest() == self.hash.digest()

class Argset:
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    bin_ = None
    @classmethod
    def new_from_argv(cls, argv):
        prog = os.path.basename(argv[0])
        bin_ = argv[1]
        bin_basename = os.path.basename(bin_)
        bin_name = os.path.splitext(bin_basename)[0]
        BINMAP = {
            'bspzip': BspzipArgset,
            'vbsp': BspCompileArgset,
            'vvis': BspCompileArgset,
            'vrad': BspCompileArgset,
        }
        if bin_name not in BINMAP:
dylan grafmyre's avatar
dylan grafmyre committed
79
            raise NotImplementedError('no argset supervisor for bin {!r}'.format(bin_name))
80
81
82
83
84
        logging.info('Argset for %r', bin_name)
        return  BINMAP[bin_name](argv)

    def supervise(self):
        raise NotImplementedError
dylan grafmyre's avatar
dylan grafmyre committed
85

86
87
88
    @property
    def name(self):
        return os.path.basename(self.bin_)
dylan grafmyre's avatar
dylan grafmyre committed
89
90
91
92
93
94
    def __repr__(self):
        return '<{s.__class__.__name__} {t}>'.format(
            s=self,
            t=self.__dict__
        )

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
139
140
141
class BspzipArgset(Argset):
    def __init__(self, argv):
        self.bin_ = argv[1]
        self.mode = argv[2]
        self.ibsp = argv[3]
        self.addlist = argv[4]
        self.obsp = argv[5]
        self.vargs = argv[6:]
        assert self.mode == '-addlist'

    def supervise(self):
        logging.info('%s', self)
        if not os.path.isfile(self.ibsp):
            logging.info('ENOENT no ibsp %r', self.ibsp)
            return 2
        if not os.path.isfile(self.addlist):
            logging.info('ENOENT no addlist %r', self.addlist)
            return 2
        ifp = FileFingerprint(self.ibsp)

        execve_low([
            self.bin_,
            self.mode,
            self.ibsp,
            self.addlist,
            self.obsp,
            ] + self.vargs + [
        ])

        if not os.path.isfile(self.obsp):
            logging.info('ENOENT no obsp %r', self.obsp)
            return 2

        ofp = FileFingerprint(self.obsp)

        bsp_changed = (ifp == ofp)
        return bsp_changed

class BspCompileArgset(Argset):
    def __init__(self, argv):
        self.prog = os.path.basename(argv[0])
        self.bin_ = argv[1]
        self.file_ = argv[-1]
        self.vargs = argv[2:-1]
        self.has_vmf = os.path.isfile(self.vmf)
        self.has_bsp = os.path.isfile(self.bsp)

dylan grafmyre's avatar
dylan grafmyre committed
142
143
    @property
    def vmf(self):
144
        return '{}.vmf'.format(self.file_)
dylan grafmyre's avatar
dylan grafmyre committed
145
146
147

    @property
    def bsp(self):
148
        return '{}.bsp'.format(self.file_)
dylan grafmyre's avatar
dylan grafmyre committed
149

150
151
152
153
154
155
    def supervise(self):
        ifp = None
        if self.has_bsp:
            ifp = FileFingerprint(self.bsp)
        else:
            logging.info('no bsp file %r', self.bsp)
dylan grafmyre's avatar
dylan grafmyre committed
156

157
158
159
        execve_low(
            [self.bin_] + self.vargs + [self.file_]
        )
dylan grafmyre's avatar
dylan grafmyre committed
160

161
162
163
        if not os.path.isfile(self.bsp):
            logging.info('no bsp file %r', self.bsp)
            return 2
dylan grafmyre's avatar
dylan grafmyre committed
164

165
        ofp = FileFingerprint(self.bsp)
dylan grafmyre's avatar
dylan grafmyre committed
166

167
168
169
170
171
172
173
        bsp_changed = (ifp == ofp)
        return bsp_changed

def main():
    logging.basicConfig(level=logging.INFO)
    argset = Argset.new_from_argv(sys.argv)
    exit(argset.supervise())
dylan grafmyre's avatar
dylan grafmyre committed
174
175
176

if __name__ == '__main__':
    main()