Skip to content

Commit 50d3cb0

Browse files
author
Shubham
committed
Add login, connection and error listeners
1 parent dabc331 commit 50d3cb0

File tree

4 files changed

+164
-22
lines changed

4 files changed

+164
-22
lines changed

objectbox/c.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -299,18 +299,6 @@ class SyncState(IntEnum):
299299
STOPPED = 6
300300
DEAD = 7
301301

302-
303-
class SyncCode(IntEnum):
304-
OK = 20
305-
REQ_REJECTED = 40
306-
CREDENTIALS_REJECTED = 43
307-
UNKNOWN = 50
308-
AUTH_UNREACHABLE = 53
309-
BAD_VERSION = 55
310-
CLIENT_ID_TAKEN = 61
311-
TX_VIOLATED_UNIQUE = 71
312-
313-
314302
class OBXSyncError(IntEnum):
315303
REJECT_TX_NO_PERMISSION = 1 # Sync client received rejection of transaction writes due to missing permissions
316304

@@ -1222,4 +1210,12 @@ def c_array_pointer(py_list: Union[List[Any], np.ndarray], c_type):
12221210
obx_sync_protocol_version = c_fn('obx_sync_protocol_version', ctypes.c_uint32, [])
12231211
obx_sync_protocol_version_server = c_fn('obx_sync_protocol_version_server', ctypes.c_uint32, [OBX_sync_p])
12241212

1225-
obx_sync_close = c_fn_rc('obx_sync_close', [OBX_sync_p])
1213+
obx_sync_close = c_fn_rc('obx_sync_close', [OBX_sync_p])
1214+
1215+
obx_sync_listener_connect = c_fn('obx_sync_listener_connect', None, [OBX_sync_p, OBX_sync_listener_connect, ctypes.c_void_p])
1216+
obx_sync_listener_disconnect = c_fn('obx_sync_listener_disconnect', None, [OBX_sync_p, OBX_sync_listener_disconnect, ctypes.c_void_p])
1217+
obx_sync_listener_login = c_fn('obx_sync_listener_login', None, [OBX_sync_p, OBX_sync_listener_login, ctypes.c_void_p])
1218+
obx_sync_listener_login_failure = c_fn('obx_sync_listener_login_failure', None, [OBX_sync_p, OBX_sync_listener_login_failure, ctypes.c_void_p])
1219+
obx_sync_listener_error = c_fn('obx_sync_listener_error', None, [OBX_sync_p, OBX_sync_listener_error, ctypes.c_void_p])
1220+
1221+
obx_sync_wait_for_logged_in_state = c_fn_rc('obx_sync_wait_for_logged_in_state', [OBX_sync_p, ctypes.c_uint64])

objectbox/sync.py

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import ctypes
2+
from collections.abc import Callable
3+
from ctypes import c_void_p
4+
25
import objectbox.c as c
36
from objectbox import Store
4-
from enum import Enum, auto
7+
from enum import Enum, auto, IntEnum
8+
9+
from objectbox.c import OBX_sync_listener_login
10+
511

612
class SyncCredentials:
713

@@ -86,6 +92,15 @@ class SyncLoginEvent:
8692
CREDENTIALS_REJECTED = 'credentials_rejected'
8793
UNKNOWN_ERROR = 'unknown_error'
8894

95+
class SyncCode(IntEnum):
96+
OK = 20
97+
REQ_REJECTED = 40
98+
CREDENTIALS_REJECTED = 43
99+
UNKNOWN = 50
100+
AUTH_UNREACHABLE = 53
101+
BAD_VERSION = 55
102+
CLIENT_ID_TAKEN = 61
103+
TX_VIOLATED_UNIQUE = 71
89104

90105
class SyncChange:
91106
def __init__(self, entity_id: int, entity: type, puts: list[int], removals: list[int]):
@@ -94,11 +109,36 @@ def __init__(self, entity_id: int, entity: type, puts: list[int], removals: list
94109
self.puts = puts
95110
self.removals = removals
96111

112+
class SyncLoginListener:
113+
114+
def on_logged_in(self):
115+
pass
116+
117+
def on_login_failed(self, sync_login_code: SyncCode):
118+
pass
119+
120+
class SyncConnectionListener:
121+
122+
def on_connected(self):
123+
pass
124+
125+
def on_disconnected(self):
126+
pass
127+
128+
class SyncErrorListener:
129+
130+
def on_error(self, sync_error_code: int):
131+
pass
97132

98133
class SyncClient:
99134

100-
def __init__(self, store: Store, server_urls: list[str], credentials: list[SyncCredentials],
135+
def __init__(self, store: Store, server_urls: list[str],
101136
filter_variables: dict[str, str] | None = None):
137+
self.__c_login_listener = None
138+
self.__c_login_failure_listener = None
139+
self.__c_connect_listener = None
140+
self.__c_disconnect_listener = None
141+
self.__c_error_listener = None
102142
if not server_urls:
103143
raise ValueError("Provide at least one server URL")
104144

@@ -109,14 +149,13 @@ def __init__(self, store: Store, server_urls: list[str], credentials: list[SyncC
109149
# 'Please visit https://objectbox.io/sync/ for options.')
110150

111151
self.__store = store
112-
self.__server_urls = server_urls
113-
self.__credentials = credentials
152+
self.__server_urls = [url.encode('utf-8') for url in server_urls]
114153

115-
server_urls = [url.encode('utf-8') for url in server_urls]
116-
self.__c_sync_client_ptr = c.obx_sync_urls(store.c_store(), c.c_array_pointer(server_urls, ctypes.c_char_p),
117-
len(server_urls))
154+
self.__c_sync_client_ptr = c.obx_sync_urls(store.c_store(), c.c_array_pointer(self.__server_urls, ctypes.c_char_p),
155+
len(self.__server_urls))
118156

119157
def set_credentials(self, credentials: SyncCredentials):
158+
self.__credentials = credentials
120159
if isinstance(credentials, SyncCredentialsNone):
121160
c.obx_sync_credentials(self.__c_sync_client_ptr, credentials.type, None, 0)
122161
elif isinstance(credentials, SyncCredentialsUserPassword):
@@ -181,3 +220,42 @@ def close(self):
181220

182221
def is_closed(self) -> bool:
183222
return self.__c_sync_client_ptr is None
223+
224+
def set_login_listener(self, login_listener: SyncLoginListener):
225+
self.__c_login_listener = c.OBX_sync_listener_login(lambda arg: login_listener.on_logged_in())
226+
self.__c_login_failure_listener = c.OBX_sync_listener_login_failure(lambda arg, sync_login_code: login_listener.on_login_failed(sync_login_code))
227+
c.obx_sync_listener_login(
228+
self.__c_sync_client_ptr,
229+
self.__c_login_listener,
230+
None
231+
)
232+
c.obx_sync_listener_login_failure(
233+
self.__c_sync_client_ptr,
234+
self.__c_login_failure_listener,
235+
None
236+
)
237+
238+
def set_connection_listener(self, connection_listener: SyncConnectionListener):
239+
self.__c_connect_listener = c.OBX_sync_listener_connect(lambda arg: connection_listener.on_connected())
240+
self.__c_disconnect_listener = c.OBX_sync_listener_disconnect(lambda arg: connection_listener.on_disconnected())
241+
c.obx_sync_listener_connect(
242+
self.__c_sync_client_ptr,
243+
self.__c_connect_listener,
244+
None
245+
)
246+
c.obx_sync_listener_disconnect(
247+
self.__c_sync_client_ptr,
248+
self.__c_disconnect_listener,
249+
None
250+
)
251+
252+
def set_error_listener(self, error_listener: SyncErrorListener):
253+
self.__c_error_listener = c.OBX_sync_listener_error(lambda arg, sync_error_code: error_listener.on_error(sync_error_code))
254+
c.obx_sync_listener_error(
255+
self.__c_sync_client_ptr,
256+
self.__c_error_listener,
257+
None
258+
)
259+
260+
def wait_for_logged_in_state(self, timeout_millis: int):
261+
c.obx_sync_wait_for_logged_in_state(self.__c_sync_client_ptr, timeout_millis)

tests/conftest.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
from objectbox.logger import logger
3+
from objectbox.sync import SyncLoginListener, SyncConnectionListener, SyncErrorListener
34
from common import *
45

56

@@ -19,3 +20,54 @@ def test_store():
1920
store = create_test_store()
2021
yield store
2122
store.close()
23+
24+
class TestLoginListener(SyncLoginListener):
25+
def __init__(self):
26+
self.logged_in_called = False
27+
self.login_failure_code = None
28+
29+
def on_logged_in(self):
30+
self.logged_in_called = True
31+
32+
def on_login_failed(self, sync_login_code: int):
33+
self.login_failure_code = sync_login_code
34+
35+
36+
class TestConnectionListener(SyncConnectionListener):
37+
def __init__(self):
38+
self.connected_called = False
39+
self.disconnected_called = False
40+
41+
def on_connected(self):
42+
self.connected_called = True
43+
44+
def on_disconnected(self):
45+
self.disconnected_called = True
46+
47+
48+
class TestErrorListener(SyncErrorListener):
49+
def __init__(self):
50+
self.sync_error_code = None
51+
52+
def on_error(self, sync_error_code: int):
53+
self.sync_error_code = sync_error_code
54+
55+
@pytest.fixture
56+
def connection_listener():
57+
listener = TestConnectionListener()
58+
yield listener
59+
listener.connected_called = False
60+
listener.disconnected_called = False
61+
62+
@pytest.fixture
63+
def login_listener():
64+
listener = TestLoginListener()
65+
yield listener
66+
listener.logged_in_called = False
67+
listener.login_failure_code = None
68+
69+
@pytest.fixture
70+
def error_listener():
71+
listener = TestErrorListener()
72+
yield listener
73+
listener.sync_error_code = None

tests/test_sync.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1+
from time import sleep
12
from objectbox.sync import *
23

3-
44
def test_sync_protocol_version():
55
version = SyncClient.protocol_version()
66
assert version >= 1
77

8-
98
def test_sync_client_states(test_store):
109
server_urls = ["ws://localhost:9999"]
1110
credentials = [SyncCredentials.none()]
@@ -16,3 +15,20 @@ def test_sync_client_states(test_store):
1615
client.stop()
1716
assert client.get_sync_state() == SyncState.STOPPED
1817
client.close()
18+
19+
def test_sync_listener(test_store, login_listener, connection_listener):
20+
server_urls = ["ws://127.0.0.1:9999"]
21+
client = SyncClient(test_store, server_urls)
22+
client.set_credentials(SyncCredentials.shared_secret_string("shared_secret"))
23+
client.set_login_listener(login_listener)
24+
client.set_connection_listener(connection_listener)
25+
26+
client.start()
27+
sleep(1)
28+
client.stop()
29+
client.close()
30+
31+
assert login_listener.login_failure_code is not None
32+
assert login_listener.login_failure_code == SyncCode.CREDENTIALS_REJECTED
33+
assert connection_listener.connected_called
34+
assert connection_listener.disconnected_called

0 commit comments

Comments
 (0)