Alaea 2.6.0__tar.gz → 2.6.2__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.
- alaea-2.6.2/.VERSION +1 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Blogging.py +86 -54
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/storage.py +1 -1
- {alaea-2.6.0 → alaea-2.6.2}/Alaea.egg-info/PKG-INFO +1 -1
- {alaea-2.6.0 → alaea-2.6.2}/ChangeLog.md +8 -0
- {alaea-2.6.0 → alaea-2.6.2}/PKG-INFO +1 -1
- {alaea-2.6.0 → alaea-2.6.2}/setup.py +1 -1
- {alaea-2.6.0 → alaea-2.6.2}/tests/test_core.py +4 -1
- {alaea-2.6.0 → alaea-2.6.2}/tests/test_logging_multiprocess.py +32 -13
- alaea-2.6.0/.VERSION +0 -1
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Pyshell/RS_File.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Pyshell/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/S_DateTime.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/SameAsMATLAB/CoorTrans.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/SameAsMATLAB/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/SameAsMATLAB/calculatefuns.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/vars_name.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/xr_atmos.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/xr_attrs.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/xr_ice.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/xr_netcdf.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/xr_vars.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/xr_wave.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/Standard/xr_wind.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/baysalt/Forage/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/baysalt/Forage/erosion_data/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/baysalt/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/baysalt/erosion_coastline.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/commonCode.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/cprintfs.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/exutils/Interp/Interp_g2s.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/exutils/Interp/Interp_s2g.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/exutils/Interp/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/exutils/Interp/interpolate.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/exutils/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/pFVCOM/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/pFVCOM/fvcom_class.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/pFVCOM/fvcom_image.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/pFVCOM/fvcom_load.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/pFVCOM/fvcom_tools.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/pFVCOM/writeFile.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/processBar.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/read_conf.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/server_info.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/tools/P_params.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/tools/Read.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/tools/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/tools/download_check.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/tools/geo_distance.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/tools/kml2shp.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea/warningTools/__init__.py +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea.egg-info/SOURCES.txt +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea.egg-info/dependency_links.txt +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea.egg-info/requires.txt +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/Alaea.egg-info/top_level.txt +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/LICENSE +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/MANIFEST.in +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/README.md +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/pyproject.toml +0 -0
- {alaea-2.6.0 → alaea-2.6.2}/setup.cfg +0 -0
alaea-2.6.2/.VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v2.6.2
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import datetime
|
|
15
|
-
import errno
|
|
16
15
|
import gzip
|
|
16
|
+
import hashlib
|
|
17
17
|
import logging
|
|
18
18
|
import os
|
|
19
19
|
import shutil
|
|
@@ -29,10 +29,21 @@ try:
|
|
|
29
29
|
except ImportError:
|
|
30
30
|
fcntl = None
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
import
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
if os.name == 'nt':
|
|
33
|
+
import ctypes
|
|
34
|
+
from ctypes import wintypes
|
|
35
|
+
|
|
36
|
+
_windows_kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
|
|
37
|
+
_windows_kernel32.CreateMutexW.argtypes = [wintypes.LPVOID, wintypes.BOOL, wintypes.LPCWSTR]
|
|
38
|
+
_windows_kernel32.CreateMutexW.restype = wintypes.HANDLE
|
|
39
|
+
_windows_kernel32.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD]
|
|
40
|
+
_windows_kernel32.WaitForSingleObject.restype = wintypes.DWORD
|
|
41
|
+
_windows_kernel32.ReleaseMutex.argtypes = [wintypes.HANDLE]
|
|
42
|
+
_windows_kernel32.ReleaseMutex.restype = wintypes.BOOL
|
|
43
|
+
_windows_kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
|
|
44
|
+
_windows_kernel32.CloseHandle.restype = wintypes.BOOL
|
|
45
|
+
else:
|
|
46
|
+
_windows_kernel32 = None
|
|
36
47
|
|
|
37
48
|
try:
|
|
38
49
|
import zstandard
|
|
@@ -70,7 +81,11 @@ def _local_converter(timestamp):
|
|
|
70
81
|
|
|
71
82
|
|
|
72
83
|
class _ProcessSafeRotatingMixin:
|
|
73
|
-
"""
|
|
84
|
+
"""通过日志目录锁在多进程之间串行执行写入和轮转。"""
|
|
85
|
+
|
|
86
|
+
_WAIT_OBJECT_0 = 0
|
|
87
|
+
_WAIT_ABANDONED = 0x80
|
|
88
|
+
_INFINITE = 0xFFFFFFFF
|
|
74
89
|
|
|
75
90
|
def __init__(self, *args, **kwargs):
|
|
76
91
|
"""初始化 handler,并避免 Windows 长期占用当前日志文件。"""
|
|
@@ -79,40 +94,65 @@ class _ProcessSafeRotatingMixin:
|
|
|
79
94
|
|
|
80
95
|
def _close_windows_stream(self):
|
|
81
96
|
"""在 Windows 上关闭日志流,使其他进程能够重命名当前日志文件。"""
|
|
82
|
-
if
|
|
97
|
+
if _windows_kernel32 is not None and self.stream is not None:
|
|
83
98
|
self.stream.close()
|
|
84
99
|
self.stream = None
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
def _acquire_process_lock(self):
|
|
102
|
+
"""锁定日志目录,返回当前平台用于释放锁的标记和句柄。
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
``('fcntl', fd)``、``('windows', handle)``,不支持时返回 ``None``。
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
OSError: 无法打开日志目录或取得系统锁时抛出。
|
|
109
|
+
"""
|
|
89
110
|
if fcntl is not None:
|
|
90
|
-
|
|
91
|
-
return True
|
|
92
|
-
if msvcrt is None:
|
|
93
|
-
return False
|
|
94
|
-
lock_stream.seek(0, os.SEEK_END)
|
|
95
|
-
if lock_stream.tell() == 0:
|
|
96
|
-
lock_stream.write(b'\0')
|
|
97
|
-
lock_stream.flush()
|
|
98
|
-
while True:
|
|
99
|
-
lock_stream.seek(0)
|
|
111
|
+
directory_fd = os.open(os.path.dirname(self.baseFilename), os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0))
|
|
100
112
|
try:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
fcntl.flock(directory_fd, fcntl.LOCK_EX)
|
|
114
|
+
except OSError:
|
|
115
|
+
os.close(directory_fd)
|
|
116
|
+
raise
|
|
117
|
+
return 'fcntl', directory_fd
|
|
118
|
+
if _windows_kernel32 is None:
|
|
119
|
+
return None
|
|
120
|
+
directory = os.path.normcase(os.path.realpath(os.path.dirname(self.baseFilename)))
|
|
121
|
+
digest = hashlib.sha256(directory.encode('utf-8', errors='surrogatepass')).hexdigest()
|
|
122
|
+
handle = _windows_kernel32.CreateMutexW(None, False, f'Global\\AlaeaLogRotation-{digest}')
|
|
123
|
+
if not handle:
|
|
124
|
+
raise OSError('无法创建 Windows 日志目录互斥量')
|
|
125
|
+
result = _windows_kernel32.WaitForSingleObject(handle, self._INFINITE)
|
|
126
|
+
if result not in {self._WAIT_OBJECT_0, self._WAIT_ABANDONED}:
|
|
127
|
+
_windows_kernel32.CloseHandle(handle)
|
|
128
|
+
raise OSError(f'无法取得 Windows 日志目录互斥量: {result}')
|
|
129
|
+
return 'windows', handle
|
|
107
130
|
|
|
108
131
|
@staticmethod
|
|
109
|
-
def _release_process_lock(
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
def _release_process_lock(lock_token):
|
|
133
|
+
"""释放并关闭当前平台的日志目录锁。
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
lock_token: ``_acquire_process_lock`` 返回的平台标记和句柄。
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
None。
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
OSError: 系统拒绝释放锁时抛出。
|
|
143
|
+
"""
|
|
144
|
+
lock_kind, handle = lock_token
|
|
145
|
+
if lock_kind == 'fcntl':
|
|
146
|
+
try:
|
|
147
|
+
fcntl.flock(handle, fcntl.LOCK_UN)
|
|
148
|
+
finally:
|
|
149
|
+
os.close(handle)
|
|
150
|
+
return
|
|
151
|
+
try:
|
|
152
|
+
if not _windows_kernel32.ReleaseMutex(handle):
|
|
153
|
+
raise OSError('无法释放 Windows 日志目录互斥量')
|
|
154
|
+
finally:
|
|
155
|
+
_windows_kernel32.CloseHandle(handle)
|
|
116
156
|
|
|
117
157
|
def _reopen_replaced_stream(self):
|
|
118
158
|
"""其他进程替换当前日志文件后,重新打开日志流。
|
|
@@ -171,37 +211,29 @@ class _ProcessSafeRotatingMixin:
|
|
|
171
211
|
Raises:
|
|
172
212
|
不额外抛出异常;当前平台没有文件锁或锁不可用时回退到标准 handler 行为。
|
|
173
213
|
"""
|
|
174
|
-
if fcntl is None and
|
|
214
|
+
if fcntl is None and _windows_kernel32 is None:
|
|
175
215
|
super().emit(record)
|
|
176
216
|
return
|
|
177
217
|
self._close_windows_stream()
|
|
178
218
|
try:
|
|
179
|
-
|
|
219
|
+
lock_token = self._acquire_process_lock()
|
|
180
220
|
except OSError:
|
|
181
221
|
super().emit(record)
|
|
182
222
|
self._close_windows_stream()
|
|
183
223
|
return
|
|
184
|
-
|
|
224
|
+
if lock_token is None:
|
|
225
|
+
super().emit(record)
|
|
226
|
+
return
|
|
227
|
+
try:
|
|
228
|
+
self._reopen_replaced_stream()
|
|
229
|
+
self._synchronize_timed_rollover()
|
|
230
|
+
super().emit(record)
|
|
231
|
+
finally:
|
|
232
|
+
self._close_windows_stream()
|
|
185
233
|
try:
|
|
186
|
-
|
|
234
|
+
self._release_process_lock(lock_token)
|
|
187
235
|
except OSError:
|
|
188
|
-
|
|
189
|
-
self._close_windows_stream()
|
|
190
|
-
return
|
|
191
|
-
if not acquired:
|
|
192
|
-
super().emit(record)
|
|
193
|
-
self._close_windows_stream()
|
|
194
|
-
return
|
|
195
|
-
try:
|
|
196
|
-
self._reopen_replaced_stream()
|
|
197
|
-
self._synchronize_timed_rollover()
|
|
198
|
-
super().emit(record)
|
|
199
|
-
finally:
|
|
200
|
-
self._close_windows_stream()
|
|
201
|
-
try:
|
|
202
|
-
self._release_process_lock(lock_stream)
|
|
203
|
-
except OSError:
|
|
204
|
-
pass
|
|
236
|
+
pass
|
|
205
237
|
|
|
206
238
|
|
|
207
239
|
class _ProcessSafeRotatingFileHandler(_ProcessSafeRotatingMixin, RotatingFileHandler):
|
|
@@ -43,7 +43,7 @@ def run_storage_fallback(storage_mode, auto_methods, operations, fallback_errors
|
|
|
43
43
|
try:
|
|
44
44
|
result = normalized_operations[method]()
|
|
45
45
|
if logger is not None:
|
|
46
|
-
logger.
|
|
46
|
+
logger.debug('Storage method succeeded: context=%s, method=%s, wallSeconds=%.2f', context, method, perf_counter() - started_at)
|
|
47
47
|
return result
|
|
48
48
|
except fallback_errors as error:
|
|
49
49
|
if active_mode != 'AUTO':
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## v2.6.2
|
|
4
|
+
|
|
5
|
+
- `run_storage_fallback` 的成功线路明细由 INFO 调整为 DEBUG,减少 `Storage method succeeded` 高频常规日志;实际失败并降级仍保持 WARNING。
|
|
6
|
+
|
|
7
|
+
## v2.6.1
|
|
8
|
+
|
|
9
|
+
- macOS/Linux 使用 `fcntl` 锁定日志目录,Windows 使用由日志目录确定的系统命名互斥量,让不同程序的按时间、按大小及组合日志轮转无额外锁文件地跨进程串行;多个进程在同一时刻压缩共享日志时只轮转一次,Windows 写入后会关闭日志句柄以允许其他进程重命名文件,其他 handler 会同步当前日志和下次轮转时间,避免 `.gz.tmp`/`.zst.tmp` 竞争、重复归档和轮转后日志丢失。
|
|
10
|
+
|
|
3
11
|
## v2.6.0
|
|
4
12
|
|
|
5
13
|
- macOS/Linux 使用 `fcntl`、Windows 使用 `msvcrt`,让按时间、按大小及组合日志轮转通过同路径协作式文件锁跨进程串行;多个进程在同一时刻压缩共享日志时只轮转一次,Windows 写入后会关闭日志句柄以允许其他进程重命名文件,其他 handler 会同步当前日志和下次轮转时间,避免 `.gz.tmp`/`.zst.tmp` 竞争、重复归档和轮转后日志丢失。
|
|
@@ -345,8 +345,11 @@ class UtilityRegressionTest(unittest.TestCase):
|
|
|
345
345
|
return 'nfs-result'
|
|
346
346
|
|
|
347
347
|
operations = {'CACHE': unavailable, 'NFS': direct}
|
|
348
|
-
|
|
348
|
+
logger = mock.Mock()
|
|
349
|
+
self.assertEqual(run_storage_fallback('AUTO', ('CACHE', 'NFS'), operations, logger=logger, context='down-gfs-cache'), 'nfs-result')
|
|
349
350
|
self.assertEqual(calls, ['CACHE', 'NFS'])
|
|
351
|
+
logger.debug.assert_called_once_with('Storage method succeeded: context=%s, method=%s, wallSeconds=%.2f', 'down-gfs-cache', 'NFS', mock.ANY)
|
|
352
|
+
logger.info.assert_not_called()
|
|
350
353
|
with self.assertRaisesRegex(OSError, 'cache unavailable'):
|
|
351
354
|
run_storage_fallback('CACHE', ('CACHE', 'NFS'), operations)
|
|
352
355
|
|
|
@@ -13,17 +13,34 @@ from Alaea.Blogging import Blog
|
|
|
13
13
|
blogging = importlib.import_module('Alaea.Blogging')
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class
|
|
17
|
-
"""在非 Windows
|
|
16
|
+
class FakeKernel32:
|
|
17
|
+
"""在非 Windows 测试环境中记录命名互斥量调用。"""
|
|
18
18
|
|
|
19
|
-
LK_NBLCK = 1
|
|
20
|
-
LK_UNLCK = 2
|
|
21
19
|
calls = []
|
|
22
20
|
|
|
23
21
|
@classmethod
|
|
24
|
-
def
|
|
25
|
-
"""
|
|
26
|
-
cls.calls.append((
|
|
22
|
+
def CreateMutexW(cls, _attributes, _initial_owner, name):
|
|
23
|
+
"""记录互斥量名称并返回测试句柄。"""
|
|
24
|
+
cls.calls.append(('create', name))
|
|
25
|
+
return 42
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def WaitForSingleObject(cls, handle, timeout):
|
|
29
|
+
"""记录等待调用并模拟成功取得互斥量。"""
|
|
30
|
+
cls.calls.append(('wait', handle, timeout))
|
|
31
|
+
return 0
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def ReleaseMutex(cls, handle):
|
|
35
|
+
"""记录释放调用并模拟成功。"""
|
|
36
|
+
cls.calls.append(('release', handle))
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def CloseHandle(cls, handle):
|
|
41
|
+
"""记录句柄关闭调用并模拟成功。"""
|
|
42
|
+
cls.calls.append(('close', handle))
|
|
43
|
+
return True
|
|
27
44
|
|
|
28
45
|
|
|
29
46
|
def emit_after_barrier(log_path: str, rollover_at: int, barrier, message: str) -> None:
|
|
@@ -89,11 +106,12 @@ def main() -> None:
|
|
|
89
106
|
with gzip.open(archives[0], 'rt', encoding='utf-8') as archive:
|
|
90
107
|
archived_log = archive.read()
|
|
91
108
|
assert 'before' in archived_log and 'warmup-first' in archived_log and 'warmup-second' in archived_log
|
|
109
|
+
assert not list(log_path.parent.glob('*.rotate.lock'))
|
|
92
110
|
|
|
93
|
-
original_fcntl,
|
|
111
|
+
original_fcntl, original_kernel32 = blogging.fcntl, blogging._windows_kernel32
|
|
94
112
|
try:
|
|
95
|
-
blogging.fcntl, blogging.
|
|
96
|
-
|
|
113
|
+
blogging.fcntl, blogging._windows_kernel32 = None, FakeKernel32
|
|
114
|
+
FakeKernel32.calls = []
|
|
97
115
|
with tempfile.TemporaryDirectory() as directory:
|
|
98
116
|
log_path = Path(directory) / 'WINDOWS.log'
|
|
99
117
|
blog = Blog(_loger_name='windows-lock-test', _switch_write_bug_log=True, _switch_print_log=False)
|
|
@@ -101,11 +119,12 @@ def main() -> None:
|
|
|
101
119
|
handler = next(item for item in blog.logger.handlers if isinstance(item, RotatingFileHandler))
|
|
102
120
|
blog.logger.error('windows-lock')
|
|
103
121
|
assert handler.stream is None
|
|
104
|
-
assert
|
|
105
|
-
assert
|
|
122
|
+
assert [call[0] for call in FakeKernel32.calls] == ['create', 'wait', 'release', 'close']
|
|
123
|
+
assert FakeKernel32.calls[0][1].startswith('Global\\AlaeaLogRotation-')
|
|
124
|
+
assert not list(log_path.parent.glob('*.rotate.lock'))
|
|
106
125
|
blog.setup_Blog(switch_write_error_plus_log=False)
|
|
107
126
|
finally:
|
|
108
|
-
blogging.fcntl, blogging.
|
|
127
|
+
blogging.fcntl, blogging._windows_kernel32 = original_fcntl, original_kernel32
|
|
109
128
|
|
|
110
129
|
|
|
111
130
|
if __name__ == '__main__':
|
alaea-2.6.0/.VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
v2.6.0
|
|
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
|
|
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
|