clerk-sdk 0.2.5__tar.gz → 0.2.7__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.
- {clerk_sdk-0.2.5/clerk_sdk.egg-info → clerk_sdk-0.2.7}/PKG-INFO +1 -1
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/client.py +11 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/client_actor/client_actor.py +2 -2
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/decorators/gui_automation.py +8 -2
- clerk_sdk-0.2.7/clerk/gui_automation/ui_actions/support.py +124 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_machine/state_machine.py +3 -0
- clerk_sdk-0.2.7/clerk/models/ui_operator.py +15 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7/clerk_sdk.egg-info}/PKG-INFO +1 -1
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk_sdk.egg-info/SOURCES.txt +1 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/setup.py +1 -1
- clerk_sdk-0.2.5/clerk/gui_automation/ui_actions/support.py +0 -69
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/LICENSE +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/MANIFEST.in +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/README.md +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/base.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/client.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/decorator/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/decorator/models.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/decorator/task_decorator.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/action_model/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/action_model/model.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/action_model/utils.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/client_actor/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/client_actor/exception.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/client_actor/model.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/decorators/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/exceptions/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/exceptions/agent_manager.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/exceptions/modality/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/exceptions/modality/exc.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/exceptions/websocket.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/requirements.txt +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_actions/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_actions/actions.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_actions/base.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_inspector/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_inspector/gui_vision.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_inspector/models.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_machine/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_machine/ai_recovery.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_machine/decorators.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_machine/exceptions.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/gui_automation/ui_state_machine/models.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/models/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/models/document.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/models/document_statuses.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/models/file.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/models/remote_device.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/models/response_model.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/utils/__init__.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/utils/logger.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk/utils/save_artifact.py +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk_sdk.egg-info/dependency_links.txt +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk_sdk.egg-info/requires.txt +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/clerk_sdk.egg-info/top_level.txt +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/pyproject.toml +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/requirements.txt +0 -0
- {clerk_sdk-0.2.5 → clerk_sdk-0.2.7}/setup.cfg +0 -0
|
@@ -11,6 +11,7 @@ from clerk.gui_automation.ui_state_inspector.models import (
|
|
|
11
11
|
TargetWithAnchor,
|
|
12
12
|
)
|
|
13
13
|
from clerk.models.remote_device import RemoteDevice
|
|
14
|
+
from clerk.models.ui_operator import UiOperatorTask
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class RPAClerk(BaseClerk):
|
|
@@ -46,6 +47,16 @@ class RPAClerk(BaseClerk):
|
|
|
46
47
|
raise RuntimeError("No coordinates found in the response.")
|
|
47
48
|
return Coords(**res.data[0])
|
|
48
49
|
|
|
50
|
+
def create_ui_operator_task(self, payload: Dict) -> UiOperatorTask:
|
|
51
|
+
endpoint = "/ui_operator"
|
|
52
|
+
res = self.post_request(endpoint=endpoint, json=payload)
|
|
53
|
+
return UiOperatorTask(**res.data[0])
|
|
54
|
+
|
|
55
|
+
def get_ui_operator_task(self, id: str) -> UiOperatorTask:
|
|
56
|
+
endpoint = "/ui_operator"
|
|
57
|
+
res = self.get_request(endpoint=endpoint, params={"task_id": id})
|
|
58
|
+
return UiOperatorTask(**res.data[0])
|
|
59
|
+
|
|
49
60
|
|
|
50
61
|
class GUIVisionClerk(BaseClerk):
|
|
51
62
|
root_endpoint: str = "/gui_automation/vision"
|
|
@@ -66,7 +66,7 @@ async def _get_screen_async() -> str:
|
|
|
66
66
|
and returns the base64 encoded image of the screen captured.
|
|
67
67
|
"""
|
|
68
68
|
payload = {
|
|
69
|
-
"proc_inst_id": os.getenv("
|
|
69
|
+
"proc_inst_id": os.getenv("_run_id"),
|
|
70
70
|
"client_name": os.getenv("REMOTE_DEVICE_NAME"),
|
|
71
71
|
"headless": True,
|
|
72
72
|
"action": {"action_type": "screenshot"},
|
|
@@ -132,7 +132,7 @@ async def _perform_action_async(
|
|
|
132
132
|
PerformActionException: If the action fails with an error message.
|
|
133
133
|
"""
|
|
134
134
|
req_payload: Dict = {
|
|
135
|
-
"proc_inst_id": os.getenv("
|
|
135
|
+
"proc_inst_id": os.getenv("_run_id"),
|
|
136
136
|
"client_name": os.getenv("REMOTE_DEVICE_NAME"),
|
|
137
137
|
"headless": (
|
|
138
138
|
True if os.getenv("HEADLESS", "True").lower() == "true" else False
|
|
@@ -95,7 +95,8 @@ def gui_automation(
|
|
|
95
95
|
def wrapper(payload: ClerkCodePayload, *args, **kwargs):
|
|
96
96
|
global global_ws
|
|
97
97
|
force_deallocate = False
|
|
98
|
-
os.environ["
|
|
98
|
+
os.environ["_document_id"] = payload.document.id
|
|
99
|
+
os.environ["_run_id"] = payload.run_id
|
|
99
100
|
|
|
100
101
|
remote_device = _allocate_remote_device(
|
|
101
102
|
clerk_client, group_name, payload.run_id
|
|
@@ -122,12 +123,17 @@ def gui_automation(
|
|
|
122
123
|
raise WebSocketConnectionFailed()
|
|
123
124
|
|
|
124
125
|
except Exception as e:
|
|
125
|
-
os.environ.pop("PROC_ID", None)
|
|
126
126
|
force_deallocate = True
|
|
127
127
|
raise
|
|
128
128
|
finally:
|
|
129
|
+
os.environ.pop("_run_id", None)
|
|
130
|
+
os.environ.pop("_document_id", None)
|
|
129
131
|
if not reserve_client or force_deallocate:
|
|
130
132
|
_deallocate_target(clerk_client, remote_device, payload.run_id)
|
|
133
|
+
else:
|
|
134
|
+
logger.warning(
|
|
135
|
+
f"The client stayed reserved for the this run id: {payload.run_id}"
|
|
136
|
+
)
|
|
131
137
|
|
|
132
138
|
if global_ws and global_ws.state is State.OPEN:
|
|
133
139
|
close_task = event_loop.create_task(close_ws_connection(global_ws))
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from datetime import timedelta, datetime
|
|
2
|
+
import os
|
|
3
|
+
import base64
|
|
4
|
+
import time
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from backoff._typing import Details
|
|
7
|
+
|
|
8
|
+
from clerk.models.ui_operator import TaskStatuses, UiOperatorTask
|
|
9
|
+
from clerk.utils.save_artifact import save_artifact
|
|
10
|
+
from clerk.utils import logger
|
|
11
|
+
from ..client_actor import get_screen
|
|
12
|
+
from ..ui_actions.base import BaseAction
|
|
13
|
+
from ..decorators.gui_automation import clerk_client
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_MAP = {
|
|
17
|
+
"y": True,
|
|
18
|
+
"yes": True,
|
|
19
|
+
"t": True,
|
|
20
|
+
"true": True,
|
|
21
|
+
"on": True,
|
|
22
|
+
"1": True,
|
|
23
|
+
"n": False,
|
|
24
|
+
"no": False,
|
|
25
|
+
"f": False,
|
|
26
|
+
"false": False,
|
|
27
|
+
"off": False,
|
|
28
|
+
"0": False,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def strtobool(value):
|
|
33
|
+
try:
|
|
34
|
+
return _MAP[str(value).lower()]
|
|
35
|
+
except KeyError:
|
|
36
|
+
raise ValueError('"{}" is not a valid bool value'.format(value))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def save_screenshot(filename: str, sub_folder: Optional[str] = None) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Save a screenshot into the process instance folder.
|
|
42
|
+
|
|
43
|
+
This function retrieves the base64 representation of the screen from the target environment using the 'get_screen' function.
|
|
44
|
+
Then, it saves the screenshot into the process instance folder using the 'save_file_into_instance_folder' function.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
filename (str): The name of the file to save the screenshot as.
|
|
48
|
+
sub_folder (str, optional): The name of the subfolder within the instance folder where the screenshot will be saved. Defaults to None.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
str: The file path of the saved screenshot.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
# get the base64 screen from target environment
|
|
55
|
+
screen_b64: str = get_screen()
|
|
56
|
+
return save_artifact(
|
|
57
|
+
filename=filename,
|
|
58
|
+
file_bytes=base64.b64decode(screen_b64),
|
|
59
|
+
subfolder=sub_folder,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _format_action_string(action: BaseAction) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Formats action in the same format as the one used in task modules.
|
|
66
|
+
"""
|
|
67
|
+
action_string = (
|
|
68
|
+
f"{action.__class__.__name__}(target='{action.target_name or action.target}')"
|
|
69
|
+
)
|
|
70
|
+
for anchor in action.anchors:
|
|
71
|
+
action_string += f".{anchor.relation}('{anchor.value}')"
|
|
72
|
+
if action.click_offset != [0, 0]:
|
|
73
|
+
action_string += (
|
|
74
|
+
f".offset(x={action.click_offset[0]}, y={action.click_offset[1]})"
|
|
75
|
+
)
|
|
76
|
+
action_string += ".do()"
|
|
77
|
+
return action_string
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def maybe_engage_operator_ui_action(details: Details) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Makes a call to the operator queue server to create an issue and waits for the allotted time for it to be resolved.
|
|
83
|
+
:param details: A dictionary containing the details of the exception raised (https://pypi.org/project/backoff/)
|
|
84
|
+
:returns: None
|
|
85
|
+
:raises: The exception raised by the action if the issue is not resolved within the allotted time
|
|
86
|
+
"""
|
|
87
|
+
# Determine if the operator should be engaged
|
|
88
|
+
ui_operator_enabled = strtobool(os.getenv("_ui_operator_enabled", default="False"))
|
|
89
|
+
|
|
90
|
+
if not ui_operator_enabled:
|
|
91
|
+
raise details["exception"] # type: ignore
|
|
92
|
+
|
|
93
|
+
ui_operator_pooling_interval = int(os.getenv("_ui_operator_pooling_interval", "1"))
|
|
94
|
+
ui_operator_timeout = int(os.getenv("_ui_operator_timeout", "3600"))
|
|
95
|
+
resolution_deadline = datetime.now() + timedelta(seconds=ui_operator_timeout)
|
|
96
|
+
|
|
97
|
+
# Extract the action object from the details dictionary
|
|
98
|
+
action: BaseAction = details["args"][0]
|
|
99
|
+
issue_description = _format_action_string(action)
|
|
100
|
+
|
|
101
|
+
# create ui operator task
|
|
102
|
+
payload = {
|
|
103
|
+
"document_id": os.getenv("_document_id"),
|
|
104
|
+
"remote_device_id": os.getenv("REMOTE_DEVICE_ID"),
|
|
105
|
+
"issue_description": issue_description,
|
|
106
|
+
}
|
|
107
|
+
task: UiOperatorTask = clerk_client.create_ui_operator_task(payload)
|
|
108
|
+
while datetime.now() < resolution_deadline:
|
|
109
|
+
task: UiOperatorTask = clerk_client.get_ui_operator_task(task.id)
|
|
110
|
+
if task.status == TaskStatuses.COMPLETED:
|
|
111
|
+
logger.debug(
|
|
112
|
+
f"The ui operator task {task.id} has been resolved by {task.assignee_name}"
|
|
113
|
+
)
|
|
114
|
+
return
|
|
115
|
+
elif task.status == TaskStatuses.CANCELLED:
|
|
116
|
+
logger.warning(f"The ui operator task {task.id} has been cancelled")
|
|
117
|
+
raise details["exception"]
|
|
118
|
+
|
|
119
|
+
time.sleep(ui_operator_pooling_interval)
|
|
120
|
+
|
|
121
|
+
logger.warning(
|
|
122
|
+
f"The ui operator task {task.id} was not resolved after {ui_operator_timeout} seconds"
|
|
123
|
+
)
|
|
124
|
+
raise details["exception"]
|
|
@@ -44,6 +44,9 @@ class ScreenPilot:
|
|
|
44
44
|
tolerate_repeat_transitions (int): Number of repeated transitions to tolerate before breaking the execution. Default 5.
|
|
45
45
|
tolerate_repeat_states (int): Number of repeated states to tolerate before breaking the execution. Default 5.
|
|
46
46
|
enable_force_close_app_process (bool): If true, terminates the application process via `taskkill` command. Default False.
|
|
47
|
+
ui_operator_enabled (bool): if true, enables the creation of ui operator task which needs to be resolved in Clerk.
|
|
48
|
+
ui_operator_pooling_interval (int): in seconds, defines the time pooling interval for ui operator task. Default 1.
|
|
49
|
+
ui_operator_timeout (int): in seconds, defines the max waiting time for the ui operator task to be resolved before raising and exception.
|
|
47
50
|
process_name (Optional[str]): Name of the process that needs to be closed (ie. process.exe). Required attribute if `enable_force_close_app_process` is True
|
|
48
51
|
|
|
49
52
|
Methods:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TaskStatuses(str, Enum):
|
|
7
|
+
OPEN = "open"
|
|
8
|
+
COMPLETED = "completed"
|
|
9
|
+
CANCELLED = "cancelled"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UiOperatorTask(BaseModel):
|
|
13
|
+
id: str
|
|
14
|
+
status: TaskStatuses
|
|
15
|
+
assignee_name: Optional[str]
|
|
@@ -13,7 +13,7 @@ gui_requirements = get_requirements("./clerk/gui_automation")
|
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name="clerk-sdk",
|
|
16
|
-
version="0.2.
|
|
16
|
+
version="0.2.7",
|
|
17
17
|
description="Library for interacting with Clerk",
|
|
18
18
|
long_description=open("README.md").read(),
|
|
19
19
|
long_description_content_type="text/markdown",
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import base64
|
|
3
|
-
from typing import Optional
|
|
4
|
-
from backoff._typing import Details
|
|
5
|
-
|
|
6
|
-
from clerk.utils.save_artifact import save_artifact
|
|
7
|
-
from ..client_actor import get_screen
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
_MAP = {
|
|
11
|
-
"y": True,
|
|
12
|
-
"yes": True,
|
|
13
|
-
"t": True,
|
|
14
|
-
"true": True,
|
|
15
|
-
"on": True,
|
|
16
|
-
"1": True,
|
|
17
|
-
"n": False,
|
|
18
|
-
"no": False,
|
|
19
|
-
"f": False,
|
|
20
|
-
"false": False,
|
|
21
|
-
"off": False,
|
|
22
|
-
"0": False,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def strtobool(value):
|
|
27
|
-
try:
|
|
28
|
-
return _MAP[str(value).lower()]
|
|
29
|
-
except KeyError:
|
|
30
|
-
raise ValueError('"{}" is not a valid bool value'.format(value))
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def save_screenshot(filename: str, sub_folder: Optional[str] = None) -> str:
|
|
34
|
-
"""
|
|
35
|
-
Save a screenshot into the process instance folder.
|
|
36
|
-
|
|
37
|
-
This function retrieves the base64 representation of the screen from the target environment using the 'get_screen' function.
|
|
38
|
-
Then, it saves the screenshot into the process instance folder using the 'save_file_into_instance_folder' function.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
filename (str): The name of the file to save the screenshot as.
|
|
42
|
-
sub_folder (str, optional): The name of the subfolder within the instance folder where the screenshot will be saved. Defaults to None.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
str: The file path of the saved screenshot.
|
|
46
|
-
|
|
47
|
-
"""
|
|
48
|
-
# get the base64 screen from target environment
|
|
49
|
-
screen_b64: str = get_screen()
|
|
50
|
-
return save_artifact(
|
|
51
|
-
filename=filename,
|
|
52
|
-
file_bytes=base64.b64decode(screen_b64),
|
|
53
|
-
subfolder=sub_folder,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def maybe_engage_operator_ui_action(details: Details) -> None:
|
|
58
|
-
"""
|
|
59
|
-
Makes a call to the operator queue server to create an issue and waits for the allotted time for it to be resolved.
|
|
60
|
-
:param details: A dictionary containing the details of the exception raised (https://pypi.org/project/backoff/)
|
|
61
|
-
:returns: None
|
|
62
|
-
:raises: The exception raised by the action if the issue is not resolved within the allotted time
|
|
63
|
-
"""
|
|
64
|
-
# Determine if the operator should be engaged
|
|
65
|
-
use_operator = strtobool(os.getenv("USE_OPERATOR", default="False"))
|
|
66
|
-
if not use_operator:
|
|
67
|
-
raise details["exception"] # type: ignore
|
|
68
|
-
|
|
69
|
-
raise NotImplementedError("Feature not yet implemented")
|
|
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
|