mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-06-11 11:56:18 +00:00
timeout for each contact in get_contacts
This commit is contained in:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "meshcore"
|
name = "meshcore"
|
||||||
version = "2.1.12"
|
version = "2.1.13"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
|
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
|
||||||
{ name="Alex Wolden", email="awolden@gmail.com" },
|
{ name="Alex Wolden", email="awolden@gmail.com" },
|
||||||
|
|||||||
@@ -80,6 +80,53 @@ class CommandHandlerBase:
|
|||||||
)-> None:
|
)-> None:
|
||||||
self._get_contact_by_prefix = func
|
self._get_contact_by_prefix = func
|
||||||
|
|
||||||
|
async def wait_for_events(
|
||||||
|
self,
|
||||||
|
expected_events: Optional[Union[EventType, List[EventType]]] = None,
|
||||||
|
timeout: Optional[float] = None,
|
||||||
|
) -> Event:
|
||||||
|
try:
|
||||||
|
# Convert single event to list if needed
|
||||||
|
if not isinstance(expected_events, list):
|
||||||
|
expected_events = [expected_events]
|
||||||
|
|
||||||
|
logger.debug(f"Waiting for events {expected_events}, timeout={timeout}")
|
||||||
|
|
||||||
|
# Create futures for all expected events
|
||||||
|
futures = []
|
||||||
|
for event_type in expected_events:
|
||||||
|
future = asyncio.create_task(
|
||||||
|
self.dispatcher.wait_for_event(event_type, {}, timeout)
|
||||||
|
)
|
||||||
|
futures.append(future)
|
||||||
|
|
||||||
|
# Wait for the first event to complete or all to timeout
|
||||||
|
done, pending = await asyncio.wait(
|
||||||
|
futures, timeout=timeout, return_when=asyncio.FIRST_COMPLETED
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cancel all pending futures
|
||||||
|
for future in pending:
|
||||||
|
future.cancel()
|
||||||
|
|
||||||
|
# Check if any future completed successfully
|
||||||
|
for future in done:
|
||||||
|
event = await future
|
||||||
|
if event:
|
||||||
|
return event
|
||||||
|
|
||||||
|
# Create an error event when no event is received
|
||||||
|
return Event(EventType.ERROR, {"reason": "no_event_received"})
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.debug(f"Command timed out {data}")
|
||||||
|
return Event(EventType.ERROR, {"reason": "timeout"})
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Command error: {e}")
|
||||||
|
return Event(EventType.ERROR, {"error": str(e)})
|
||||||
|
|
||||||
|
return Event(EventType.ERROR, {})
|
||||||
|
|
||||||
|
|
||||||
async def send(
|
async def send(
|
||||||
self,
|
self,
|
||||||
data: bytes,
|
data: bytes,
|
||||||
@@ -110,45 +157,9 @@ class CommandHandlerBase:
|
|||||||
await self._sender_func(data)
|
await self._sender_func(data)
|
||||||
|
|
||||||
if expected_events:
|
if expected_events:
|
||||||
try:
|
|
||||||
# Convert single event to list if needed
|
|
||||||
if not isinstance(expected_events, list):
|
|
||||||
expected_events = [expected_events]
|
|
||||||
|
|
||||||
logger.debug(f"Waiting for events {expected_events}, timeout={timeout}")
|
|
||||||
|
|
||||||
# Create futures for all expected events
|
|
||||||
futures = []
|
|
||||||
for event_type in expected_events:
|
|
||||||
future = asyncio.create_task(
|
|
||||||
self.dispatcher.wait_for_event(event_type, {}, timeout)
|
|
||||||
)
|
|
||||||
futures.append(future)
|
|
||||||
|
|
||||||
# Wait for the first event to complete or all to timeout
|
|
||||||
done, pending = await asyncio.wait(
|
|
||||||
futures, timeout=timeout, return_when=asyncio.FIRST_COMPLETED
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cancel all pending futures
|
|
||||||
for future in pending:
|
|
||||||
future.cancel()
|
|
||||||
|
|
||||||
# Check if any future completed successfully
|
|
||||||
for future in done:
|
|
||||||
event = await future
|
|
||||||
if event:
|
|
||||||
return event
|
|
||||||
|
|
||||||
# Create an error event when no event is received
|
|
||||||
return Event(EventType.ERROR, {"reason": "no_event_received"})
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
logger.debug(f"Command timed out {data}")
|
|
||||||
return Event(EventType.ERROR, {"reason": "timeout"})
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Command error: {e}")
|
|
||||||
return Event(EventType.ERROR, {"error": str(e)})
|
|
||||||
# For commands that don't expect events, return a success event
|
# For commands that don't expect events, return a success event
|
||||||
|
return await self.wait_for_events(expected_events, timeout)
|
||||||
|
|
||||||
return Event(EventType.OK, {})
|
return Event(EventType.OK, {})
|
||||||
|
|
||||||
# attached at base because its a common method
|
# attached at base because its a common method
|
||||||
|
|||||||
@@ -8,12 +8,35 @@ logger = logging.getLogger("meshcore")
|
|||||||
|
|
||||||
|
|
||||||
class ContactCommands(CommandHandlerBase):
|
class ContactCommands(CommandHandlerBase):
|
||||||
async def get_contacts(self, lastmod=0) -> Event:
|
async def get_contacts(self, lastmod=0, anim=False) -> Event:
|
||||||
logger.debug("Getting contacts")
|
logger.debug("Getting contacts")
|
||||||
data = b"\x04"
|
data = b"\x04"
|
||||||
if lastmod > 0:
|
if lastmod > 0:
|
||||||
data = data + lastmod.to_bytes(4, "little")
|
data = data + lastmod.to_bytes(4, "little")
|
||||||
return await self.send(data, [EventType.CONTACTS, EventType.ERROR], timeout=30)
|
if anim:
|
||||||
|
print("Fetching contacts ", end="", flush=True)
|
||||||
|
# wait first event
|
||||||
|
res = await self.send(data)
|
||||||
|
while True:
|
||||||
|
# wait next event
|
||||||
|
res = await self.wait_for_events(
|
||||||
|
[EventType.NEXT_CONTACT, EventType.CONTACTS, EventType.ERROR],
|
||||||
|
timeout=5)
|
||||||
|
if res is None: # Timeout
|
||||||
|
if anim:
|
||||||
|
print(" Timeout")
|
||||||
|
return res
|
||||||
|
if res.type == EventType.ERROR:
|
||||||
|
if anim:
|
||||||
|
print(" Error")
|
||||||
|
return res
|
||||||
|
elif res.type == EventType.CONTACTS:
|
||||||
|
if anim:
|
||||||
|
print(" Done")
|
||||||
|
return res
|
||||||
|
elif res.type == EventType.NEXT_CONTACT:
|
||||||
|
if anim:
|
||||||
|
print(".", end="", flush=True)
|
||||||
|
|
||||||
async def reset_path(self, key: DestinationType) -> Event:
|
async def reset_path(self, key: DestinationType) -> Event:
|
||||||
key_bytes = _validate_destination(key, prefix_length=32)
|
key_bytes = _validate_destination(key, prefix_length=32)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class EventType(Enum):
|
|||||||
DEVICE_INFO = "device_info"
|
DEVICE_INFO = "device_info"
|
||||||
MSG_SENT = "message_sent"
|
MSG_SENT = "message_sent"
|
||||||
NEW_CONTACT = "new_contact"
|
NEW_CONTACT = "new_contact"
|
||||||
|
NEXT_CONTACT = "next_contact"
|
||||||
|
|
||||||
# Push notifications
|
# Push notifications
|
||||||
ADVERTISEMENT = "advertisement"
|
ADVERTISEMENT = "advertisement"
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ class MessageReader:
|
|||||||
if packet_type_value == PacketType.PUSH_CODE_NEW_ADVERT.value:
|
if packet_type_value == PacketType.PUSH_CODE_NEW_ADVERT.value:
|
||||||
await self.dispatcher.dispatch(Event(EventType.NEW_CONTACT, c))
|
await self.dispatcher.dispatch(Event(EventType.NEW_CONTACT, c))
|
||||||
else:
|
else:
|
||||||
|
await self.dispatcher.dispatch(Event(EventType.NEXT_CONTACT, c))
|
||||||
self.contacts[c["public_key"]] = c
|
self.contacts[c["public_key"]] = c
|
||||||
|
|
||||||
elif packet_type_value == PacketType.CONTACT_END.value:
|
elif packet_type_value == PacketType.CONTACT_END.value:
|
||||||
|
|||||||
Reference in New Issue
Block a user