vsjetengine 1.3.0__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/PKG-INFO +1 -1
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_futures.py +116 -3
- vsjetengine-1.4.0/vsengine/_version.py +2 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/futures.py +78 -24
- vsjetengine-1.3.0/vsengine/_version.py +0 -2
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/.gitignore +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/COPYING +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/README.md +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/pyproject.toml +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/__init__.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/_testutils.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/conftest.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/fixtures/heuristic_examples.json +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/fixtures/test.vpy +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_helpers.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_hospice.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_loop_adapters.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_loops.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_policy.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_policy_store.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_video.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/tests/test_vpy.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/__init__.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/_helpers.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/_hospice.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/_nodes.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/adapters/__init__.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/adapters/asyncio.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/adapters/trio.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/loops.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/policy.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/py.typed +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/video.py +0 -0
- {vsjetengine-1.3.0 → vsjetengine-1.4.0}/vsengine/vpy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vsjetengine
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: An engine for vapoursynth previewers, renderers and script analyis tools.
|
|
5
5
|
Project-URL: Source Code, https://github.com/Jaded-Encoding-Thaumaturgy/vs-jet-engine
|
|
6
6
|
Project-URL: Contact, https://discord.gg/XTpc6Fa9eB
|
|
@@ -141,9 +141,6 @@ def test_unified_future_catch() -> None:
|
|
|
141
141
|
|
|
142
142
|
@pytest.mark.asyncio
|
|
143
143
|
async def test_unified_future_add_loop_callback() -> None:
|
|
144
|
-
from vsengine.adapters.asyncio import AsyncIOLoop
|
|
145
|
-
from vsengine.loops import set_loop
|
|
146
|
-
|
|
147
144
|
set_loop(AsyncIOLoop())
|
|
148
145
|
|
|
149
146
|
def _init_thread(fut: Future[threading.Thread]) -> None:
|
|
@@ -170,6 +167,122 @@ async def test_unified_future_add_loop_callback() -> None:
|
|
|
170
167
|
assert cb_thread != loop_thread
|
|
171
168
|
|
|
172
169
|
|
|
170
|
+
@pytest.mark.asyncio
|
|
171
|
+
async def test_unified_future_add_loop_callback_chaining() -> None:
|
|
172
|
+
set_loop(AsyncIOLoop())
|
|
173
|
+
|
|
174
|
+
fut = Future[int]()
|
|
175
|
+
unified_fut = UnifiedFuture.from_future(fut)
|
|
176
|
+
|
|
177
|
+
recorded_value: int | None = None
|
|
178
|
+
|
|
179
|
+
def _side_effect(f: Future[int]) -> None:
|
|
180
|
+
nonlocal recorded_value
|
|
181
|
+
recorded_value = f.result() * 2
|
|
182
|
+
|
|
183
|
+
chained_fut = unified_fut.add_loop_callback(_side_effect)
|
|
184
|
+
|
|
185
|
+
results = list[int]()
|
|
186
|
+
chained_fut.then(lambda val: results.append(val))
|
|
187
|
+
|
|
188
|
+
fut.set_result(21)
|
|
189
|
+
|
|
190
|
+
res = await chained_fut
|
|
191
|
+
assert res == 21
|
|
192
|
+
assert results == [21]
|
|
193
|
+
assert recorded_value == 42
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@pytest.mark.asyncio
|
|
197
|
+
async def test_unified_future_then_on_loop() -> None:
|
|
198
|
+
set_loop(AsyncIOLoop())
|
|
199
|
+
|
|
200
|
+
fut = Future[int]()
|
|
201
|
+
unified_fut = UnifiedFuture.from_future(fut)
|
|
202
|
+
|
|
203
|
+
loop_thread: threading.Thread | None = None
|
|
204
|
+
callback_thread: threading.Thread | None = None
|
|
205
|
+
|
|
206
|
+
def _init_thread() -> None:
|
|
207
|
+
fut.set_result(42)
|
|
208
|
+
|
|
209
|
+
def _on_success(v: int) -> int:
|
|
210
|
+
nonlocal callback_thread
|
|
211
|
+
callback_thread = threading.current_thread()
|
|
212
|
+
return v * 2
|
|
213
|
+
|
|
214
|
+
chained = unified_fut.then(_on_success, on_loop=True)
|
|
215
|
+
|
|
216
|
+
thr = threading.Thread(target=_init_thread)
|
|
217
|
+
thr.start()
|
|
218
|
+
|
|
219
|
+
res = await chained
|
|
220
|
+
loop_thread = threading.current_thread()
|
|
221
|
+
|
|
222
|
+
assert callback_thread == loop_thread
|
|
223
|
+
assert res == 84
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@pytest.mark.asyncio
|
|
227
|
+
async def test_unified_future_map_on_loop() -> None:
|
|
228
|
+
set_loop(AsyncIOLoop())
|
|
229
|
+
|
|
230
|
+
fut = Future[int]()
|
|
231
|
+
unified_fut = UnifiedFuture.from_future(fut)
|
|
232
|
+
|
|
233
|
+
loop_thread: threading.Thread | None = None
|
|
234
|
+
callback_thread: threading.Thread | None = None
|
|
235
|
+
|
|
236
|
+
def _init_thread() -> None:
|
|
237
|
+
fut.set_result(10)
|
|
238
|
+
|
|
239
|
+
def _map_fn(v: int) -> str:
|
|
240
|
+
nonlocal callback_thread
|
|
241
|
+
callback_thread = threading.current_thread()
|
|
242
|
+
return f"value: {v}"
|
|
243
|
+
|
|
244
|
+
chained = unified_fut.map(_map_fn, on_loop=True)
|
|
245
|
+
|
|
246
|
+
thr = threading.Thread(target=_init_thread)
|
|
247
|
+
thr.start()
|
|
248
|
+
|
|
249
|
+
res = await chained
|
|
250
|
+
loop_thread = threading.current_thread()
|
|
251
|
+
|
|
252
|
+
assert callback_thread == loop_thread
|
|
253
|
+
assert res == "value: 10"
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@pytest.mark.asyncio
|
|
257
|
+
async def test_unified_future_catch_on_loop() -> None:
|
|
258
|
+
set_loop(AsyncIOLoop())
|
|
259
|
+
|
|
260
|
+
fut = Future[int]()
|
|
261
|
+
unified_fut = UnifiedFuture.from_future(fut)
|
|
262
|
+
|
|
263
|
+
loop_thread: threading.Thread | None = None
|
|
264
|
+
callback_thread: threading.Thread | None = None
|
|
265
|
+
|
|
266
|
+
def _init_thread() -> None:
|
|
267
|
+
fut.set_exception(ValueError("error!"))
|
|
268
|
+
|
|
269
|
+
def _catch_fn(e: BaseException) -> str:
|
|
270
|
+
nonlocal callback_thread
|
|
271
|
+
callback_thread = threading.current_thread()
|
|
272
|
+
return str(e)
|
|
273
|
+
|
|
274
|
+
chained = unified_fut.catch(_catch_fn, on_loop=True)
|
|
275
|
+
|
|
276
|
+
thr = threading.Thread(target=_init_thread)
|
|
277
|
+
thr.start()
|
|
278
|
+
|
|
279
|
+
res = await chained
|
|
280
|
+
loop_thread = threading.current_thread()
|
|
281
|
+
|
|
282
|
+
assert callback_thread == loop_thread
|
|
283
|
+
assert res == "error!"
|
|
284
|
+
|
|
285
|
+
|
|
173
286
|
# UnifiedIterator tests
|
|
174
287
|
|
|
175
288
|
|
|
@@ -27,6 +27,10 @@ class AsyncFutureLike[V](Protocol):
|
|
|
27
27
|
async def awaitable(self) -> V: ...
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
type UnifiedFutureLike[T] = UnifiedFuture[T]
|
|
31
|
+
type UnifiedIteratorLike[T] = UnifiedIterator[T]
|
|
32
|
+
|
|
33
|
+
|
|
30
34
|
class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncContextManager[Any], Awaitable[T]):
|
|
31
35
|
"""
|
|
32
36
|
A Promise-inspired Future that unifies concurrent.futures.Future
|
|
@@ -34,7 +38,12 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
34
38
|
"""
|
|
35
39
|
|
|
36
40
|
@classmethod
|
|
37
|
-
def from_call[**P](
|
|
41
|
+
def from_call[S, **P](
|
|
42
|
+
cls: type[UnifiedFutureLike[S]],
|
|
43
|
+
func: Callable[P, Future[S]],
|
|
44
|
+
*args: P.args,
|
|
45
|
+
**kwargs: P.kwargs,
|
|
46
|
+
) -> UnifiedFutureLike[S]:
|
|
38
47
|
"""
|
|
39
48
|
Call `func` and wrap the returned `Future` as a `UnifiedFuture`.
|
|
40
49
|
|
|
@@ -54,7 +63,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
54
63
|
return cls.from_future(future)
|
|
55
64
|
|
|
56
65
|
@classmethod
|
|
57
|
-
def from_future(cls, future: Future[
|
|
66
|
+
def from_future[S](cls: type[UnifiedFutureLike[S]], future: Future[S]) -> UnifiedFutureLike[S]:
|
|
58
67
|
"""
|
|
59
68
|
Wrap an existing `Future` as a `UnifiedFuture`.
|
|
60
69
|
|
|
@@ -68,7 +77,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
68
77
|
|
|
69
78
|
result = cls()
|
|
70
79
|
|
|
71
|
-
def _receive(fn: Future[
|
|
80
|
+
def _receive(fn: Future[S]) -> None:
|
|
72
81
|
if (exc := future.exception()) is not None:
|
|
73
82
|
result.set_exception(exc)
|
|
74
83
|
else:
|
|
@@ -78,7 +87,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
78
87
|
return result
|
|
79
88
|
|
|
80
89
|
@classmethod
|
|
81
|
-
def resolve(cls, value:
|
|
90
|
+
def resolve[S](cls: type[UnifiedFutureLike[S]], value: S) -> UnifiedFutureLike[S]:
|
|
82
91
|
"""
|
|
83
92
|
Return an already-resolved `UnifiedFuture` carrying `value`.
|
|
84
93
|
|
|
@@ -90,7 +99,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
90
99
|
return future
|
|
91
100
|
|
|
92
101
|
@classmethod
|
|
93
|
-
def reject(cls, error: BaseException) ->
|
|
102
|
+
def reject(cls: type[UnifiedFutureLike[Any]], error: BaseException) -> UnifiedFutureLike[Any]:
|
|
94
103
|
"""
|
|
95
104
|
Return an already-rejected `UnifiedFuture` carrying `error`.
|
|
96
105
|
|
|
@@ -114,7 +123,7 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
114
123
|
# The done_callback should inherit the environment of the current call.
|
|
115
124
|
super().add_done_callback(keep_environment(fn))
|
|
116
125
|
|
|
117
|
-
def add_loop_callback(self, func: Callable[[Future[T]], Any]) ->
|
|
126
|
+
def add_loop_callback(self, func: Callable[[Future[T]], Any]) -> UnifiedFuture[T]:
|
|
118
127
|
"""
|
|
119
128
|
Register a callback that is guaranteed to run on the event-loop thread.
|
|
120
129
|
|
|
@@ -122,25 +131,51 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
122
131
|
this method marshals `func` back to the main event loop via `EventLoop.from_thread`.
|
|
123
132
|
|
|
124
133
|
:param func: A callable that receives the completed future.
|
|
134
|
+
:return: A UnifiedFuture that resolves to the original future's result once the callback completes
|
|
135
|
+
on the event loop.
|
|
125
136
|
"""
|
|
137
|
+
result = UnifiedFuture[T]()
|
|
126
138
|
|
|
127
139
|
def _wrapper(future: Future[T]) -> None:
|
|
128
|
-
get_loop().from_thread(func, future)
|
|
140
|
+
loop_fut = get_loop().from_thread(func, future)
|
|
141
|
+
|
|
142
|
+
def _forward(lf: Future[Any]) -> None:
|
|
143
|
+
if (exc := lf.exception()) is not None:
|
|
144
|
+
result.set_exception(exc)
|
|
145
|
+
elif (orig_exc := future.exception()) is not None:
|
|
146
|
+
result.set_exception(orig_exc)
|
|
147
|
+
else:
|
|
148
|
+
result.set_result(future.result())
|
|
149
|
+
|
|
150
|
+
loop_fut.add_done_callback(_forward)
|
|
129
151
|
|
|
130
152
|
self.add_done_callback(_wrapper)
|
|
153
|
+
return result
|
|
131
154
|
|
|
132
155
|
# Manipulating futures
|
|
133
156
|
@overload
|
|
134
|
-
def then[S](self, success_cb: Callable[[T], S]) -> UnifiedFuture[S]: ...
|
|
157
|
+
def then[S](self, success_cb: Callable[[T], S], *, on_loop: bool = ...) -> UnifiedFuture[S]: ...
|
|
135
158
|
@overload
|
|
136
|
-
def then[S](self, success_cb: Callable[[T], S], err_cb: None = ...) -> UnifiedFuture[S]: ...
|
|
159
|
+
def then[S](self, success_cb: Callable[[T], S], err_cb: None = ..., *, on_loop: bool = ...) -> UnifiedFuture[S]: ...
|
|
137
160
|
@overload
|
|
138
|
-
def then[V](self, success_cb: None, err_cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]: ...
|
|
161
|
+
def then[V](self, success_cb: None, err_cb: Callable[[BaseException], V], *, on_loop: bool = ...) -> UnifiedFuture[T | V]: ... # fmt: skip # noqa: E501
|
|
139
162
|
@overload
|
|
140
|
-
def then[V](self, *, err_cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]: ...
|
|
163
|
+
def then[V](self, *, err_cb: Callable[[BaseException], V], on_loop: bool = ...) -> UnifiedFuture[T | V]: ...
|
|
141
164
|
@overload
|
|
142
|
-
def then[S, V](
|
|
143
|
-
|
|
165
|
+
def then[S, V](
|
|
166
|
+
self,
|
|
167
|
+
success_cb: Callable[[T], S],
|
|
168
|
+
err_cb: Callable[[BaseException], V],
|
|
169
|
+
*,
|
|
170
|
+
on_loop: bool = ...,
|
|
171
|
+
) -> UnifiedFuture[S | V]: ...
|
|
172
|
+
def then[S, V](
|
|
173
|
+
self,
|
|
174
|
+
success_cb: Callable[[T], S] | None = None,
|
|
175
|
+
err_cb: Callable[[BaseException], V] | None = None,
|
|
176
|
+
*,
|
|
177
|
+
on_loop: bool = False,
|
|
178
|
+
) -> Any:
|
|
144
179
|
"""
|
|
145
180
|
Attach fulfilment and/or rejection handlers, returning a new future.
|
|
146
181
|
|
|
@@ -153,17 +188,29 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
153
188
|
|
|
154
189
|
:param success_cb: Called with the resolved value, or `None` to passthrough.
|
|
155
190
|
:param err_cb: Called with the exception, or `None` to passthrough.
|
|
191
|
+
:param on_loop: If True, execute the callback on the main loop.
|
|
156
192
|
:return: A new `UnifiedFuture` carrying the callback's return value.
|
|
157
193
|
"""
|
|
158
194
|
result = UnifiedFuture[Any]()
|
|
159
195
|
|
|
160
196
|
def _run_cb(cb: Callable[[Any], Any], v: T | BaseException) -> None:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
197
|
+
if on_loop:
|
|
198
|
+
loop_fut = get_loop().from_thread(cb, v)
|
|
199
|
+
|
|
200
|
+
def _forward(lf: Future[Any]) -> None:
|
|
201
|
+
if (exc := lf.exception()) is not None:
|
|
202
|
+
result.set_exception(exc)
|
|
203
|
+
else:
|
|
204
|
+
result.set_result(lf.result())
|
|
205
|
+
|
|
206
|
+
loop_fut.add_done_callback(_forward)
|
|
165
207
|
else:
|
|
166
|
-
|
|
208
|
+
try:
|
|
209
|
+
r = cb(v)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
result.set_exception(e)
|
|
212
|
+
else:
|
|
213
|
+
result.set_result(r)
|
|
167
214
|
|
|
168
215
|
def _done(fn: Future[T]) -> None:
|
|
169
216
|
if (exc := self.exception()) is not None:
|
|
@@ -180,23 +227,25 @@ class UnifiedFuture[T](Future[T], AbstractContextManager[Any], AbstractAsyncCont
|
|
|
180
227
|
self.add_done_callback(_done)
|
|
181
228
|
return result
|
|
182
229
|
|
|
183
|
-
def map[V](self, cb: Callable[[T], V]) -> UnifiedFuture[V]:
|
|
230
|
+
def map[V](self, cb: Callable[[T], V], *, on_loop: bool = False) -> UnifiedFuture[V]:
|
|
184
231
|
"""
|
|
185
232
|
Transform the resolved value with `cb`, returning a new future.
|
|
186
233
|
|
|
187
234
|
:param cb: A callable that transforms the resolved value.
|
|
235
|
+
:param on_loop: If True, execute the callback on the main loop.
|
|
188
236
|
:return: A new `UnifiedFuture` carrying the transformed value.
|
|
189
237
|
"""
|
|
190
|
-
return self.then(cb, None)
|
|
238
|
+
return self.then(cb, None, on_loop=on_loop)
|
|
191
239
|
|
|
192
|
-
def catch[V](self, cb: Callable[[BaseException], V]) -> UnifiedFuture[T | V]:
|
|
240
|
+
def catch[V](self, cb: Callable[[BaseException], V], *, on_loop: bool = False) -> UnifiedFuture[T | V]:
|
|
193
241
|
"""
|
|
194
242
|
Recover from a rejection by handling the exception with `cb`.
|
|
195
243
|
|
|
196
244
|
:param cb: A callable that handles the exception and returns a recovery value.
|
|
245
|
+
:param on_loop: If True, execute the callback on the main loop.
|
|
197
246
|
:return: A new `UnifiedFuture` carrying the recovery value on error, or the original resolved value on success.
|
|
198
247
|
"""
|
|
199
|
-
return self.then(None, cb)
|
|
248
|
+
return self.then(None, cb, on_loop=on_loop)
|
|
200
249
|
|
|
201
250
|
# Nicer Syntax
|
|
202
251
|
def __enter__[EnterT](self: FutureLike[AbstractContextManager[EnterT, Any]]) -> EnterT:
|
|
@@ -276,11 +325,16 @@ class UnifiedIterator[T](Iterator[T], AsyncIterator[T]):
|
|
|
276
325
|
self.future_iterable = future_iterable
|
|
277
326
|
|
|
278
327
|
@classmethod
|
|
279
|
-
def from_call[**P](
|
|
328
|
+
def from_call[S, **P](
|
|
329
|
+
cls: type[UnifiedIteratorLike[S]],
|
|
330
|
+
func: Callable[P, Iterator[Future[S]]],
|
|
331
|
+
*args: P.args,
|
|
332
|
+
**kwargs: P.kwargs,
|
|
333
|
+
) -> UnifiedIteratorLike[S]:
|
|
280
334
|
"""
|
|
281
335
|
Call `func` and wrap the returned iterator as a `UnifiedIterator`.
|
|
282
336
|
|
|
283
|
-
:param func: A callable that returns an `Iterator[Future[
|
|
337
|
+
:param func: A callable that returns an `Iterator[Future[S]]`.
|
|
284
338
|
:param args: Positional arguments forwarded to `func`.
|
|
285
339
|
:param kwargs: Keyword arguments forwarded to `func`.
|
|
286
340
|
:return: A `UnifiedIterator` wrapping the returned iterator.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|