|
2 | 2 | from __future__ import absolute_import, division, print_function
|
3 | 3 |
|
4 | 4 | import re
|
5 |
| -import os |
6 |
| -import errno |
7 |
| -import atexit |
8 |
| -import operator |
9 |
| -import six |
10 |
| -from functools import reduce |
11 |
| -import uuid |
12 |
| -from six.moves import map |
13 | 5 | import pytest
|
14 | 6 | import py
|
15 | 7 | from _pytest.monkeypatch import MonkeyPatch
|
16 |
| -from .compat import Path |
17 | 8 | import attr
|
18 | 9 | import shutil
|
19 | 10 | import tempfile
|
20 |
| -import itertools |
21 |
| - |
22 |
| - |
23 |
| -LOCK_TIMEOUT = 60 * 60 * 3 |
24 |
| - |
25 |
| -get_lock_path = operator.methodcaller("joinpath", ".lock") |
26 |
| - |
27 |
| - |
28 |
| -def find_prefixed(root, prefix): |
29 |
| - l_prefix = prefix.lower() |
30 |
| - for x in root.iterdir(): |
31 |
| - if x.name.lower().startswith(l_prefix): |
32 |
| - yield x |
33 |
| - |
34 |
| - |
35 |
| -def extract_suffixes(iter, prefix): |
36 |
| - p_len = len(prefix) |
37 |
| - for p in iter: |
38 |
| - yield p.name[p_len:] |
39 |
| - |
40 |
| - |
41 |
| -def find_suffixes(root, prefix): |
42 |
| - return extract_suffixes(find_prefixed(root, prefix), prefix) |
43 |
| - |
44 |
| - |
45 |
| -def parse_num(maybe_num): |
46 |
| - try: |
47 |
| - return int(maybe_num) |
48 |
| - except ValueError: |
49 |
| - return -1 |
50 |
| - |
51 |
| - |
52 |
| -def _max(iterable, default): |
53 |
| - # needed due to python2.7 lacking the default argument for max |
54 |
| - return reduce(max, iterable, default) |
55 |
| - |
56 |
| - |
57 |
| -def make_numbered_dir(root, prefix): |
58 |
| - for i in range(10): |
59 |
| - # try up to 10 times to create the folder |
60 |
| - max_existing = _max(map(parse_num, find_suffixes(root, prefix)), -1) |
61 |
| - new_number = max_existing + 1 |
62 |
| - new_path = root.joinpath("{}{}".format(prefix, new_number)) |
63 |
| - try: |
64 |
| - new_path.mkdir() |
65 |
| - except Exception: |
66 |
| - pass |
67 |
| - else: |
68 |
| - return new_path |
69 |
| - else: |
70 |
| - raise EnvironmentError( |
71 |
| - "could not create numbered dir with prefix {prefix} in {root})".format( |
72 |
| - prefix=prefix, root=root |
73 |
| - ) |
74 |
| - ) |
75 |
| - |
76 |
| - |
77 |
| -def create_cleanup_lock(p): |
78 |
| - lock_path = get_lock_path(p) |
79 |
| - try: |
80 |
| - fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644) |
81 |
| - except OSError as e: |
82 |
| - if e.errno == errno.EEXIST: |
83 |
| - six.raise_from( |
84 |
| - EnvironmentError("cannot create lockfile in {path}".format(path=p)), e |
85 |
| - ) |
86 |
| - else: |
87 |
| - raise |
88 |
| - else: |
89 |
| - pid = os.getpid() |
90 |
| - spid = str(pid) |
91 |
| - if not isinstance(spid, six.binary_type): |
92 |
| - spid = spid.encode("ascii") |
93 |
| - os.write(fd, spid) |
94 |
| - os.close(fd) |
95 |
| - if not lock_path.is_file(): |
96 |
| - raise EnvironmentError("lock path got renamed after sucessfull creation") |
97 |
| - return lock_path |
98 |
| - |
99 |
| - |
100 |
| -def register_cleanup_lock_removal(lock_path, register=atexit.register): |
101 |
| - pid = os.getpid() |
102 |
| - |
103 |
| - def cleanup_on_exit(lock_path=lock_path, original_pid=pid): |
104 |
| - current_pid = os.getpid() |
105 |
| - if current_pid != original_pid: |
106 |
| - # fork |
107 |
| - return |
108 |
| - try: |
109 |
| - lock_path.unlink() |
110 |
| - except (OSError, IOError): |
111 |
| - pass |
112 |
| - |
113 |
| - return register(cleanup_on_exit) |
114 |
| - |
115 |
| - |
116 |
| -def delete_a_numbered_dir(path): |
117 |
| - create_cleanup_lock(path) |
118 |
| - parent = path.parent |
119 |
| - |
120 |
| - garbage = parent.joinpath("garbage-{}".format(uuid.uuid4())) |
121 |
| - path.rename(garbage) |
122 |
| - shutil.rmtree(str(garbage), ignore_errors=True) |
123 |
| - |
124 |
| - |
125 |
| -def ensure_deletable(path, consider_lock_dead_if_created_before): |
126 |
| - lock = get_lock_path(path) |
127 |
| - if not lock.exists(): |
128 |
| - return True |
129 |
| - try: |
130 |
| - lock_time = lock.stat().st_mtime |
131 |
| - except Exception: |
132 |
| - return False |
133 |
| - else: |
134 |
| - if lock_time < consider_lock_dead_if_created_before: |
135 |
| - lock.unlink() |
136 |
| - return True |
137 |
| - else: |
138 |
| - return False |
139 |
| - |
140 |
| - |
141 |
| -def try_cleanup(path, consider_lock_dead_if_created_before): |
142 |
| - if ensure_deletable(path, consider_lock_dead_if_created_before): |
143 |
| - delete_a_numbered_dir(path) |
144 |
| - |
145 |
| - |
146 |
| -def cleanup_candidates(root, prefix, keep): |
147 |
| - max_existing = _max(map(parse_num, find_suffixes(root, prefix)), -1) |
148 |
| - max_delete = max_existing - keep |
149 |
| - paths = find_prefixed(root, prefix) |
150 |
| - paths, paths2 = itertools.tee(paths) |
151 |
| - numbers = map(parse_num, extract_suffixes(paths2, prefix)) |
152 |
| - for path, number in zip(paths, numbers): |
153 |
| - if number <= max_delete: |
154 |
| - yield path |
155 |
| - |
156 |
| - |
157 |
| -def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before): |
158 |
| - for path in cleanup_candidates(root, prefix, keep): |
159 |
| - try_cleanup(path, consider_lock_dead_if_created_before) |
160 |
| - for path in root.glob("garbage-*"): |
161 |
| - try_cleanup(path, consider_lock_dead_if_created_before) |
162 |
| - |
163 |
| - |
164 |
| -def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout): |
165 |
| - e = None |
166 |
| - for i in range(10): |
167 |
| - try: |
168 |
| - p = make_numbered_dir(root, prefix) |
169 |
| - lock_path = create_cleanup_lock(p) |
170 |
| - register_cleanup_lock_removal(lock_path) |
171 |
| - except Exception as e: |
172 |
| - pass |
173 |
| - else: |
174 |
| - consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout |
175 |
| - cleanup_numbered_dir( |
176 |
| - root=root, |
177 |
| - prefix=prefix, |
178 |
| - keep=keep, |
179 |
| - consider_lock_dead_if_created_before=consider_lock_dead_if_created_before, |
180 |
| - ) |
181 |
| - return p |
182 |
| - assert e is not None |
183 |
| - raise e |
| 11 | +from .pathlib import Path, make_numbered_dir, make_numbered_dir_with_cleanup |
184 | 12 |
|
185 | 13 |
|
186 | 14 | @attr.s
|
|
0 commit comments