Skip to content

networking

RVConnector

Source code in client/ayon_openrv/networking.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
class RVConnector:
    addon_settings = get_addon_settings(OpenRVAddon.name, __version__)

    def __init__(self, host: str = None, name: str = None, port: int = None):
        self.host = host or "localhost"
        self.name = name or self.addon_settings["network"]["conn_name"]
        self.port = port or self.addon_settings["network"]["conn_port"]

        self.is_connected = False
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        self.connect()

    def __enter__(self):
        """Enters the context manager."""
        start = time()
        while not self.is_connected:
            if time() - start > self.addon_settings["network"]["timeout"]:
                raise Exception(f"Timeout reached. Tried with {self.host = } "
                                f"{self.port =  } {self.name = }")
            self.connect()
        return self

    def __exit__(self, *args):
        """Exits the context manager."""
        self.close()

    @property
    def message_available(self) -> bool:
        """Checks if a message is available."""
        try:
            msg = self.sock.recv(1, socket.MSG_PEEK)
            if len(msg) > 0:
                return True
        except Exception as err:
            log.error(err, exc_info=True)

        return False

    def connect(self) -> None:
        """Connects to the RV server."""
        log.debug("Connecting with: "
                  f"{self.host = } {self.port = } {self.name = }")
        if self.is_connected:
            return
        self.__connect_socket()

    def send_message(self, message):
        log.debug(f"send_message: {message}")
        if not self.is_connected:
            return

        msg = f"MESSAGE {len(message)} {message}"
        try:
            self.sock.sendall(msg.encode("utf-8"))
        except Exception:
            self.close()

    def send_event(self, eventName, eventContents, shall_return=True):
        """
        Send a remote event, then wait for a return value (string).
        eventName must be one of the events
        listed in the RV Reference Manual.
        """
        message = f"RETURNEVENT {eventName} * {eventContents}"
        self.send_message(message)
        if shall_return:
            return self.__process_events(process_return_only=True)

    def close(self):
        if self.is_connected:
            self.send_message("DISCONNECT")
            timeout = os.environ.get("AYON_RV_SOCKET_CLOSE_TIMEOUT", 100)

            if not isinstance(timeout, int):
                timeout = int(timeout)

            sleep(timeout / 1000) # wait for the message to be sent

        self.sock.shutdown(socket.SHUT_RDWR)
        self.sock.close()
        self.is_connected = False

    def receive_message(self):
        msg_type, msg_data = "", None

        try:
            while True:
                char = self.sock.recv(1).decode("utf-8")
                if char == " ":
                    break
                msg_type += char
            msg_data = self.sock.recv(
                len(msg_type)).decode("utf-8")
        except Exception as err:
            log.error(err, exc_info=True)

        return (msg_type, msg_data)

    def __send_initial_greeting(self):
        greeting = f"{self.name} rvController"
        cmd = f"NEWGREETING {len(greeting)} {greeting}"
        try:
            self.sock.sendall(cmd.encode("utf-8"))
        except Exception:
            self.is_connected = False

    def process_message(self, data):
        log.debug(f"process message: {data = }")

    def __process_events(self, process_return_only=False):
        while True:
            sleep(0.01)
            while not self.message_available:
                if not self.is_connected:
                    return ""

                if not self.message_available and process_return_only:
                    sleep(0.01)
                else:
                    break

            if not self.message_available:
                break

            # get single message
            resp_type, resp_data = self.receive_message()
            log.debug(f"received message: {resp_type}: {resp_data}")

            if resp_type == "MESSAGE":
                if resp_data == "DISCONNECT":
                    self.is_connected = False
                    self.close()
                    return
                # (event, event_data) = self.process_message()
                self.process_message()

            if resp_type == "PING":
                self.sock.sendall("PONG 1 p".encode("utf-8"))

    def __connect_socket(self):
        try:
            self.sock.connect((self.host, self.port))
            self.__send_initial_greeting()
            self.sock.sendall("PINGPONGCONTROL 1 0".encode("utf-8"))
            self.is_connected = True
        except Exception:
            self.is_connected = False

message_available property

Checks if a message is available.

__enter__()

Enters the context manager.

Source code in client/ayon_openrv/networking.py
42
43
44
45
46
47
48
49
50
def __enter__(self):
    """Enters the context manager."""
    start = time()
    while not self.is_connected:
        if time() - start > self.addon_settings["network"]["timeout"]:
            raise Exception(f"Timeout reached. Tried with {self.host = } "
                            f"{self.port =  } {self.name = }")
        self.connect()
    return self

__exit__(*args)

Exits the context manager.

Source code in client/ayon_openrv/networking.py
52
53
54
def __exit__(self, *args):
    """Exits the context manager."""
    self.close()

connect()

Connects to the RV server.

Source code in client/ayon_openrv/networking.py
68
69
70
71
72
73
74
def connect(self) -> None:
    """Connects to the RV server."""
    log.debug("Connecting with: "
              f"{self.host = } {self.port = } {self.name = }")
    if self.is_connected:
        return
    self.__connect_socket()

send_event(eventName, eventContents, shall_return=True)

Send a remote event, then wait for a return value (string). eventName must be one of the events listed in the RV Reference Manual.

Source code in client/ayon_openrv/networking.py
87
88
89
90
91
92
93
94
95
96
def send_event(self, eventName, eventContents, shall_return=True):
    """
    Send a remote event, then wait for a return value (string).
    eventName must be one of the events
    listed in the RV Reference Manual.
    """
    message = f"RETURNEVENT {eventName} * {eventContents}"
    self.send_message(message)
    if shall_return:
        return self.__process_events(process_return_only=True)

load_representations(representation_ids, loader_type)

Load representations into current app session.

Source code in client/ayon_openrv/networking.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def load_representations(representation_ids: list[str], loader_type: str):
    """Load representations into current app session."""
    project_name = get_current_project_name()

    available_loaders = discover_loader_plugins(project_name)
    if not loader_type:
        raise ValueError("Loader type not provided. "
                         "Expected 'FramesLoader' or 'MovLoader'.")
    Loader = next(loader for loader in available_loaders
                  if loader.__name__ == loader_type)

    representations = get_representations(project_name,
        representation_ids=representation_ids)

    for representation in representations:
        load_container(Loader, representation, project_name=project_name)