summaryrefslogtreecommitdiff
path: root/indra/viewer_components/updater/scripts/darwin/janitor.py
blob: cdf33df731b93baeebdc50aba63274c9a9c1f7c3 (plain)
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
#!/usr/bin/python
"""\
@file   janitor.py
@author Nat Goodspeed
@date   2011-09-14
@brief  Janitor class to clean up arbitrary resources

2013-01-04 cloned from vita because it's exactly what update_install.py needs.

$LicenseInfo:firstyear=2011&license=viewerlgpl$
Copyright (c) 2011, Linden Research, Inc.
$/LicenseInfo$
"""

import sys
import functools
import itertools

class Janitor(object):
    """
    Usage:

    Basic:
    self.janitor = Janitor(sys.stdout) # report cleanup actions on stdout
    ...
    self.janitor.later(os.remove, some_temp_file)
    self.janitor.later(os.remove, some_other_file)
    ...
    self.janitor.cleanup()          # perform cleanup actions

    Context Manager:
    with Janitor() as janitor:      # clean up quietly
        ...
        janitor.later(shutil.rmtree, some_temp_directory)
        ...
    # exiting 'with' block performs cleanup

    Test Class:
    class TestMySoftware(unittest.TestCase, Janitor):
        def __init__(self):
            Janitor.__init__(self)  # quiet cleanup
            ...

        def setUp(self):
            ...
            self.later(os.rename, saved_file, original_location)
            ...

        def tearDown(self):
            Janitor.tearDown(self)  # calls cleanup()
            ...
            # Or, if you have no other tearDown() logic for
            # TestMySoftware, you can omit the TestMySoftware.tearDown()
            # def entirely and let it inherit Janitor.tearDown().
    """
    def __init__(self, stream=None):
        """
        If you pass stream= (e.g.) sys.stdout or sys.stderr, Janitor will
        report its cleanup operations as it performs them. If you don't, it
        will perform them quietly -- unless one or more of the actions throws
        an exception, in which case you'll get output on stderr.
        """
        self.stream   = stream
        self.cleanups = []

    def later(self, func, *args, **kwds):
        """
        Pass the callable you want to call at cleanup() time, plus any
        positional or keyword args you want to pass it.
        """
        # Get a name string for 'func'
        try:
            # A free function has a __name__
            name = func.__name__
        except AttributeError:
            try:
                # A class object (even builtin objects like ints!) support
                # __class__.__name__
                name = func.__class__.__name__
            except AttributeError:
                # Shrug! Just use repr() to get a string describing this func.
                name = repr(func)
        # Construct a description of this operation in Python syntax from
        # args, kwds.
        desc = "%s(%s)" % \
               (name, ", ".join(itertools.chain((repr(a) for a in args),
                                                ("%s=%r" % (k, v) for (k, v) in kwds.iteritems()))))
        # Use functools.partial() to bind passed args and keywords to the
        # passed func so we get a nullary callable that does what caller
        # wants.
        bound = functools.partial(func, *args, **kwds)
        self.cleanups.append((desc, bound))

    def cleanup(self):
        """
        Perform all the actions saved with later() calls.
        """
        # Typically one allocates resource A, then allocates resource B that
        # depends on it. In such a scenario it's appropriate to delete B
        # before A -- so perform cleanup actions in reverse order. (This is
        # the same strategy used by atexit().)
        while self.cleanups:
            # Until our list is empty, pop the last pair.
            desc, bound = self.cleanups.pop(-1)

            # If requested, report the action.
            if self.stream is not None:
                print >>self.stream, desc

            try:
                # Call the bound callable
                bound()
            except Exception, err:
                # This is cleanup. Report the problem but continue.
                print >>(self.stream or sys.stderr), "Calling %s\nraised  %s: %s" % \
                      (desc, err.__class__.__name__, err)

    def tearDown(self):
        """
        If a unittest.TestCase subclass (or a nose test class) adds Janitor as
        one of its base classes, and has no other tearDown() logic, let it
        inherit Janitor.tearDown().
        """
        self.cleanup()

    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        # Perform cleanup no matter how we exit this 'with' statement
        self.cleanup()
        # Propagate any exception from the 'with' statement, don't swallow it
        return False