pythonfmu3 0.1.12__py3-none-any.whl → 0.1.13__py3-none-any.whl
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.
- pythonfmu3/_version.py +1 -1
- pythonfmu3/fmi3slave.py +1 -1
- pythonfmu3/pythonfmu-export/src/pythonfmu/IPyState.hpp +18 -0
- pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp +60 -6
- pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp +3 -1
- pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp +67 -23
- pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so +0 -0
- pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll +0 -0
- pythonfmu3/variables.py +1 -1
- {pythonfmu3-0.1.12.dist-info → pythonfmu3-0.1.13.dist-info}/METADATA +1 -1
- {pythonfmu3-0.1.12.dist-info → pythonfmu3-0.1.13.dist-info}/RECORD +15 -14
- {pythonfmu3-0.1.12.dist-info → pythonfmu3-0.1.13.dist-info}/LICENSE +0 -0
- {pythonfmu3-0.1.12.dist-info → pythonfmu3-0.1.13.dist-info}/WHEEL +0 -0
- {pythonfmu3-0.1.12.dist-info → pythonfmu3-0.1.13.dist-info}/entry_points.txt +0 -0
- {pythonfmu3-0.1.12.dist-info → pythonfmu3-0.1.13.dist-info}/top_level.txt +0 -0
pythonfmu3/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.13"
|
pythonfmu3/fmi3slave.py
CHANGED
|
@@ -188,7 +188,7 @@ class Fmi3Slave(ABC):
|
|
|
188
188
|
raise Exception(f"Unsupported type {type(var)}!")
|
|
189
189
|
var.start = refs if len(getattr(var, "dimensions", [])) > 0 else refs[0]
|
|
190
190
|
|
|
191
|
-
def register_variable(self, var: ModelVariable, nested: bool = True,
|
|
191
|
+
def register_variable(self, var: ModelVariable, nested: bool = True, var_type: Any = None):
|
|
192
192
|
"""Register a variable as FMU interface.
|
|
193
193
|
|
|
194
194
|
Args:
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#ifndef PYTHONFMU_IPYTHONSTATE_HPP
|
|
2
|
+
#define PYTHONFMU_IPYTHONSTATE_HPP
|
|
3
|
+
|
|
4
|
+
namespace pythonfmu
|
|
5
|
+
{
|
|
6
|
+
class IPyState
|
|
7
|
+
{
|
|
8
|
+
public:
|
|
9
|
+
IPyState() = default;
|
|
10
|
+
IPyState(IPyState const& other) = delete;
|
|
11
|
+
IPyState(IPyState&& other) = delete;
|
|
12
|
+
IPyState& operator=(IPyState const& other) = delete;
|
|
13
|
+
IPyState& operator=(IPyState&& other) = delete;
|
|
14
|
+
virtual ~IPyState() = default;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#endif //PYTHONFMU_IPYTHONSTATE_HPP
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
|
|
2
|
+
#include "pythonfmu/IPyState.hpp"
|
|
2
3
|
#include "pythonfmu/PySlaveInstance.hpp"
|
|
3
4
|
|
|
4
5
|
#include "pythonfmu/PyState.hpp"
|
|
@@ -7,6 +8,7 @@
|
|
|
7
8
|
|
|
8
9
|
#include <fstream>
|
|
9
10
|
#include <functional>
|
|
11
|
+
#include <mutex>
|
|
10
12
|
#include <regex>
|
|
11
13
|
#include <sstream>
|
|
12
14
|
#include <utility>
|
|
@@ -44,8 +46,9 @@ inline void py_safe_run(const std::function<void(PyGILState_STATE gilState)>& f)
|
|
|
44
46
|
PyGILState_Release(gil_state);
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible)
|
|
48
|
-
:
|
|
49
|
+
PySlaveInstance::PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, const bool visible, std::shared_ptr<IPyState> pyState)
|
|
50
|
+
: pyState_{ std::move(pyState) }
|
|
51
|
+
, instanceName_(std::move(instanceName))
|
|
49
52
|
, resources_(std::move(resources))
|
|
50
53
|
, logger_(logger)
|
|
51
54
|
, visible_(visible)
|
|
@@ -625,7 +628,10 @@ PySlaveInstance::~PySlaveInstance()
|
|
|
625
628
|
|
|
626
629
|
} // namespace pythonfmu
|
|
627
630
|
|
|
628
|
-
|
|
631
|
+
namespace {
|
|
632
|
+
std::mutex pyStateMutex{};
|
|
633
|
+
std::shared_ptr<pythonfmu::PyState> pyState{};
|
|
634
|
+
}
|
|
629
635
|
|
|
630
636
|
std::unique_ptr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
|
|
631
637
|
cppfmu::FMIString instanceName,
|
|
@@ -650,9 +656,57 @@ std::unique_ptr<cppfmu::SlaveInstance> CppfmuInstantiateSlave(
|
|
|
650
656
|
#endif
|
|
651
657
|
}
|
|
652
658
|
|
|
653
|
-
|
|
654
|
-
|
|
659
|
+
{
|
|
660
|
+
auto const ensurePyStateAlive = [&]() {
|
|
661
|
+
auto const lock = std::lock_guard{pyStateMutex};
|
|
662
|
+
if (nullptr == pyState) pyState = std::make_shared<pythonfmu::PyState>();
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
ensurePyStateAlive();
|
|
666
|
+
return std::make_unique<pythonfmu::PySlaveInstance>(
|
|
667
|
+
instanceName, resources, logger, visible, pyState);
|
|
655
668
|
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
extern "C" {
|
|
672
|
+
|
|
673
|
+
// The PyState instance owns it's own thread for constructing and destroying the Py* from the same thread.
|
|
674
|
+
// Creation of an std::thread increments ref counter of a shared library. So, when the client unloads the library
|
|
675
|
+
// the library won't be freed, as std::thread is alive, and the std::thread itself waits for de-initialization request.
|
|
676
|
+
// Thus, use DllMain on Windows and __attribute__((destructor)) on Linux for signaling to the PyState about de-initialization.
|
|
677
|
+
void finalizePythonInterpreter()
|
|
678
|
+
{
|
|
679
|
+
pyState = nullptr;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
656
682
|
|
|
657
|
-
|
|
683
|
+
namespace
|
|
684
|
+
{
|
|
685
|
+
#ifdef _WIN32
|
|
686
|
+
#include <windows.h>
|
|
687
|
+
|
|
688
|
+
BOOL APIENTRY DllMain(HMODULE hModule,
|
|
689
|
+
DWORD ul_reason_for_call,
|
|
690
|
+
LPVOID lpReserved)
|
|
691
|
+
{
|
|
692
|
+
switch (ul_reason_for_call) {
|
|
693
|
+
case DLL_PROCESS_ATTACH:
|
|
694
|
+
break;
|
|
695
|
+
case DLL_THREAD_ATTACH:
|
|
696
|
+
case DLL_THREAD_DETACH:
|
|
697
|
+
break;
|
|
698
|
+
case DLL_PROCESS_DETACH:
|
|
699
|
+
finalizePythonInterpreter();
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
return TRUE;
|
|
703
|
+
}
|
|
704
|
+
#elif defined(__linux__)
|
|
705
|
+
__attribute__((destructor)) void onLibraryUnload()
|
|
706
|
+
{
|
|
707
|
+
finalizePythonInterpreter();
|
|
708
|
+
}
|
|
709
|
+
#else
|
|
710
|
+
#error port the code
|
|
711
|
+
#endif
|
|
658
712
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#define PYTHONFMU_SLAVEINSTANCE_HPP
|
|
4
4
|
|
|
5
5
|
#include "cppfmu/cppfmu_cs.hpp"
|
|
6
|
+
#include "pythonfmu/IPyState.hpp"
|
|
6
7
|
|
|
7
8
|
#include <Python.h>
|
|
8
9
|
#include <string>
|
|
@@ -15,7 +16,7 @@ class PySlaveInstance : public cppfmu::SlaveInstance
|
|
|
15
16
|
{
|
|
16
17
|
|
|
17
18
|
public:
|
|
18
|
-
PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, bool visible);
|
|
19
|
+
PySlaveInstance(std::string instanceName, std::string resources, const cppfmu::Logger& logger, bool visible, std::shared_ptr<IPyState> pyState);
|
|
19
20
|
|
|
20
21
|
void initialize(PyGILState_STATE gilState);
|
|
21
22
|
|
|
@@ -53,6 +54,7 @@ public:
|
|
|
53
54
|
~PySlaveInstance() override;
|
|
54
55
|
|
|
55
56
|
private:
|
|
57
|
+
std::shared_ptr<IPyState> pyState_;
|
|
56
58
|
PyObject* pClass_;
|
|
57
59
|
PyObject* pInstance_{};
|
|
58
60
|
PyObject* pMessages_{};
|
|
@@ -2,40 +2,84 @@
|
|
|
2
2
|
#ifndef PYTHONFMU_PYTHONSTATE_HPP
|
|
3
3
|
#define PYTHONFMU_PYTHONSTATE_HPP
|
|
4
4
|
|
|
5
|
+
#include "IPyState.hpp"
|
|
5
6
|
#include <Python.h>
|
|
7
|
+
#include <condition_variable>
|
|
6
8
|
#include <iostream>
|
|
9
|
+
#include <mutex>
|
|
10
|
+
#include <thread>
|
|
7
11
|
|
|
8
12
|
namespace pythonfmu
|
|
9
13
|
{
|
|
10
|
-
|
|
11
|
-
class PyState
|
|
12
|
-
{
|
|
13
|
-
public:
|
|
14
|
-
PyState()
|
|
14
|
+
class PyState : public IPyState
|
|
15
15
|
{
|
|
16
|
-
|
|
16
|
+
public:
|
|
17
|
+
PyState()
|
|
18
|
+
: _initDeinitPyThread_{ &PyState::Worker, this }
|
|
19
|
+
{
|
|
20
|
+
auto lock = std::unique_lock{ mutex_ };
|
|
21
|
+
conditionalVariable_.wait(lock, [&] { return consructed_; });
|
|
22
|
+
}
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
~PyState() override
|
|
25
|
+
{
|
|
26
|
+
{
|
|
27
|
+
auto const lock = std::lock_guard{ mutex_ };
|
|
28
|
+
destroyRequested_ = true;
|
|
29
|
+
}
|
|
30
|
+
conditionalVariable_.notify_one();
|
|
31
|
+
if (_initDeinitPyThread_.joinable()) _initDeinitPyThread_.join();
|
|
23
32
|
}
|
|
24
|
-
}
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
private:
|
|
35
|
+
|
|
36
|
+
// In accordance to the documentation https://docs.python.org/3/c-api/init.html#c.Py_FinalizeEx
|
|
37
|
+
// the Py_Initialize/Py_Finalize should be called from the same
|
|
38
|
+
// thread. The FMI standard allows to call fmi functions from different threads, also different threads
|
|
39
|
+
// could be used for loading unloading the FMU libraries. There is a deadlock, when the simulation tool
|
|
40
|
+
// unloads the FMU library from the thread which is differs from the thread where the Py_Initialize was called.
|
|
41
|
+
// Create a new thread which is used for calling Py_Initialize and Py_Deinitialize. The thread waits for
|
|
42
|
+
// notification from destructor and then calls Py_Deinitialize.
|
|
43
|
+
|
|
44
|
+
void Worker()
|
|
45
|
+
{
|
|
46
|
+
// It will be nullptr in case when some other tool already called Py_IsInitialized.
|
|
47
|
+
// There is no need to call Py_Finalize thus, this thread can exit ASAP.
|
|
48
|
+
auto const mainPyThread = []() -> PyThreadState* {
|
|
49
|
+
auto const justInitialized = !Py_IsInitialized();
|
|
50
|
+
if (justInitialized) {
|
|
51
|
+
Py_SetProgramName(L"./PythonFMU");
|
|
52
|
+
Py_Initialize();
|
|
53
|
+
#if PY_VERSION_HEX < 0x03070000
|
|
54
|
+
PyEval_InitThreads();
|
|
55
|
+
#endif
|
|
56
|
+
return PyEval_SaveThread();
|
|
57
|
+
}
|
|
58
|
+
return nullptr;
|
|
59
|
+
}();
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
auto const lock = std::lock_guard{ mutex_ };
|
|
63
|
+
consructed_ = true;
|
|
64
|
+
}
|
|
65
|
+
conditionalVariable_.notify_one();
|
|
66
|
+
|
|
67
|
+
if (nullptr != mainPyThread) {
|
|
68
|
+
auto lock = std::unique_lock{ mutex_ };
|
|
69
|
+
conditionalVariable_.wait(lock, [&] { return destroyRequested_; });
|
|
70
|
+
|
|
71
|
+
PyEval_RestoreThread(mainPyThread);
|
|
72
|
+
Py_Finalize();
|
|
73
|
+
}
|
|
31
74
|
}
|
|
32
|
-
}
|
|
33
75
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
76
|
+
bool consructed_ = false;
|
|
77
|
+
bool destroyRequested_ = false;
|
|
78
|
+
std::condition_variable conditionalVariable_;
|
|
79
|
+
std::mutex mutex_;
|
|
80
|
+
std::thread _initDeinitPyThread_;
|
|
81
|
+
};
|
|
38
82
|
|
|
39
83
|
} // namespace pythonfmu
|
|
40
84
|
|
|
41
|
-
#endif //PYTHONFMU_PYTHONSTATE_HPP
|
|
85
|
+
#endif //PYTHONFMU_PYTHONSTATE_HPP
|
|
Binary file
|
|
Binary file
|
pythonfmu3/variables.py
CHANGED
|
@@ -144,7 +144,7 @@ class Dimension(object):
|
|
|
144
144
|
if self.start:
|
|
145
145
|
return self.start
|
|
146
146
|
else:
|
|
147
|
-
result = next((
|
|
147
|
+
result = next((value for vr, value in vars.items() if vr == int(self.value_reference)), None)
|
|
148
148
|
return result.getter()
|
|
149
149
|
|
|
150
150
|
def to_xml(self) -> Element:
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
pythonfmu3/__init__.py,sha256=PT1R0LpSInyVdXGJbkWMTQym6_SEU7CqUkG7OH5j1f0,396
|
|
2
2
|
pythonfmu3/__main__.py,sha256=gC1T8x2d02hhqshyNQGSS70SyCk-3I3t8zPqoRo2Xd8,1543
|
|
3
|
-
pythonfmu3/_version.py,sha256=
|
|
3
|
+
pythonfmu3/_version.py,sha256=khDKUuWafURKVs5EAZkpOMiUHI2-V7axlqrWLPUpuZo,23
|
|
4
4
|
pythonfmu3/builder.py,sha256=YLTeLLpiFOkoDYwnJ55GEhb3ham0xxROMjMN6bvaw5Y,8784
|
|
5
5
|
pythonfmu3/default_experiment.py,sha256=FJ0l0FpU7pT4Pox2RiA2y-Cb_e7DE2XlbWKySRYQaVk,293
|
|
6
6
|
pythonfmu3/deploy.py,sha256=eqFWVxZUkJCLvmq1-GuMNjn2j0VCGoaXPhRWrCk2Vxg,3741
|
|
7
7
|
pythonfmu3/enums.py,sha256=ZmC6vGeBTGt7HarPQ4uYV10ezPsvIxAHH1ypjNq7G3E,729
|
|
8
|
-
pythonfmu3/fmi3slave.py,sha256=
|
|
8
|
+
pythonfmu3/fmi3slave.py,sha256=7rDp_5TCQnkFLV11UF55mKEvUDXbpZPfn8sOjYCNCSw,16569
|
|
9
9
|
pythonfmu3/logmsg.py,sha256=0wn11GLwq0LLWqyC2ktLRjQFWlpj07wWliZ8rUHx1gg,359
|
|
10
10
|
pythonfmu3/osutil.py,sha256=KMAoMf_BpqFQQUy3WzHnt9jiklkS1xy0syIbj7kNH6A,656
|
|
11
11
|
pythonfmu3/unit.py,sha256=SjOaywb5lVQbAO_iLjc_7Ly3nYrmAvIW2_hAGt12UFU,1475
|
|
12
12
|
pythonfmu3/variable_types.py,sha256=Q0D4wlGi3i3zE5UsmGJEpNh7SV_W8mn_8p_WIkwrLQA,2881
|
|
13
|
-
pythonfmu3/variables.py,sha256=
|
|
13
|
+
pythonfmu3/variables.py,sha256=jLVaT9Qirw08QBHo4cpMg6lng7tEwD3Tk3Czi6TpL2I,11566
|
|
14
14
|
pythonfmu3/pythonfmu-export/.clang-format,sha256=ApbsHWYwilCXJ1iBux4nqOjLkaqgbGLsVrc_U3hX7WI,944
|
|
15
15
|
pythonfmu3/pythonfmu-export/CMakeLists.txt,sha256=p0-4Xh_sAaOI9AVb8lsUsMxJPc6bg9b_6CF6NJt5FHE,1847
|
|
16
16
|
pythonfmu3/pythonfmu-export/build_unix.sh,sha256=I5RrF2O6y0hRG_SN4ExKN_CCFYaa2l-ngKzqmBXnHmA,73
|
|
@@ -23,11 +23,12 @@ pythonfmu3/pythonfmu-export/src/cppfmu/fmi_functions.cpp,sha256=sTHQVUCH_dwIxRl6
|
|
|
23
23
|
pythonfmu3/pythonfmu-export/src/fmi/fmi3FunctionTypes.h,sha256=Tk1WY0nAYBGiOAQ8zVbmqRAfdQttITWsLba7udhBwG4,30050
|
|
24
24
|
pythonfmu3/pythonfmu-export/src/fmi/fmi3Functions.h,sha256=NsHuclp20wcuUInYf-91UXZbljTeu2cPXENqNEORdgU,16262
|
|
25
25
|
pythonfmu3/pythonfmu-export/src/fmi/fmi3PlatformTypes.h,sha256=ZCQLJgSbRGrMsBiQ4auaYCY4J3Hck6g3t1iZp5NEguw,4251
|
|
26
|
-
pythonfmu3/pythonfmu-export/src/pythonfmu/
|
|
27
|
-
pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.
|
|
28
|
-
pythonfmu3/pythonfmu-export/src/pythonfmu/
|
|
29
|
-
pythonfmu3/
|
|
30
|
-
pythonfmu3/resources/binaries/x86_64-
|
|
26
|
+
pythonfmu3/pythonfmu-export/src/pythonfmu/IPyState.hpp,sha256=GLiOjhKLDVEICersnqVwezwEshbxfdh_cyMxBJGFbu8,456
|
|
27
|
+
pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.cpp,sha256=FnzyEPNPFM8X49NwPi_BuC61Y10iv-lgZ_DlzWIkIts,24186
|
|
28
|
+
pythonfmu3/pythonfmu-export/src/pythonfmu/PySlaveInstance.hpp,sha256=Q8eulMAkv7YkfzCi2KSwxvYngv0I_nRbmfTJR9vT92M,4063
|
|
29
|
+
pythonfmu3/pythonfmu-export/src/pythonfmu/PyState.hpp,sha256=CjFEIrZu8b1jxQAIgfI_o36hyItW_SdQ5VSkImf7JNs,2977
|
|
30
|
+
pythonfmu3/resources/binaries/x86_64-linux/libpythonfmu-export.so,sha256=zB2OzZ-BQAPaGVm2R1up5Qp7S4JT2RTXdDm61iax0v0,365120
|
|
31
|
+
pythonfmu3/resources/binaries/x86_64-windows/pythonfmu-export.dll,sha256=Pg1pi8wTFVwcS0jLJYUfBRC3ZHjbHw_yIKU-ykhbG6w,431104
|
|
31
32
|
pythonfmu3/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
33
|
pythonfmu3/tests/conftest.py,sha256=B6XxTnBdeF_tEJ3w2DlfPKUfvlk-oULgIhQtXkgOJIc,171
|
|
33
34
|
pythonfmu3/tests/test_builder.py,sha256=StR9oNmF6dtuxxKtPFlncLQN-O7n97nvHm6EFH4Qn14,6252
|
|
@@ -45,9 +46,9 @@ pythonfmu3/tests/slaves/localmodule.py,sha256=-q5Lmh2I88MG7nLTscms0n5xhHLkUDed1I
|
|
|
45
46
|
pythonfmu3/tests/slaves/pythonslave.py,sha256=H75vLhqshSWWvMj2NQt6a_aQhA6BGSkBVJstjjVE0AU,2024
|
|
46
47
|
pythonfmu3/tests/slaves/pythonslave_read_file.py,sha256=NIAmyZZGNAcnqf8R5jb1yFcQxj6iSMOAc13pY0GFtco,634
|
|
47
48
|
pythonfmu3/tests/slaves/slavewithdep.py,sha256=4yqxa0vsOTLLvtaUQFkjfU9OcwjDUtuUe-qIjnD-jrw,647
|
|
48
|
-
pythonfmu3-0.1.
|
|
49
|
-
pythonfmu3-0.1.
|
|
50
|
-
pythonfmu3-0.1.
|
|
51
|
-
pythonfmu3-0.1.
|
|
52
|
-
pythonfmu3-0.1.
|
|
53
|
-
pythonfmu3-0.1.
|
|
49
|
+
pythonfmu3-0.1.13.dist-info/LICENSE,sha256=24Cw_MPouLK-G_t8Hgjm59IS9BnPoL6SKkyuZ0xesXY,1155
|
|
50
|
+
pythonfmu3-0.1.13.dist-info/METADATA,sha256=0hn7POMD7cxS8WKqb_FicfAsYXxqPXNswAxL0MKBATk,7315
|
|
51
|
+
pythonfmu3-0.1.13.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
|
52
|
+
pythonfmu3-0.1.13.dist-info/entry_points.txt,sha256=GgkWYs0DKGHLcijMEYZGkhkHOK2RVUpjGwvl1fwyDwQ,60
|
|
53
|
+
pythonfmu3-0.1.13.dist-info/top_level.txt,sha256=6ne7bh_cME1hepxCx1BG8N5QVhVAufrp7LLok6vq7nE,11
|
|
54
|
+
pythonfmu3-0.1.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|