Skip to content

Commit

Permalink
Merge pull request #2 from UbiCastTeam/t37780-long-polling-actions-in…
Browse files Browse the repository at this point in the history
…-progress

Allow to mark commands as in progress in long polling | refs #37780
  • Loading branch information
fthiery authored May 17, 2023
2 parents 5d1a351 + e9d718c commit 43f3d7c
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 181 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ If you are using the first version of this client (commit `33b554991303b573254d5
* Replace all occurences of `CHECK_SSL` by `VERIFY_SSL` in all configuration.


## Example
## Examples

### Ping the server

Expand All @@ -58,7 +58,24 @@ response = mmc.api_request('PING')
print(response)
```

There are more examples in the `examples` directory.

### Recorder system

This example is the use case of a recorder system that can be controlled through the long polling.

[Link to the file](/examples/recorder_controller.py)


### Wake on LAN requests

This example is the use case of a client that forwards wake on LAN requests received through the long polling to its network.

[Link to the file](/examples/wol_relay.py)


### Other examples

There are more examples in the [examples](/examples) directory.


## Actions
Expand Down
81 changes: 0 additions & 81 deletions examples/fake_mediacoder.py

This file was deleted.

93 changes: 70 additions & 23 deletions examples/recorder_controller.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,100 @@
#!/usr/bin/env python3
'''
An example of Miris Manager client usage.
This script is intended to control a recorder.
Fake MediaCoder client for tests.
'''
import json
import logging
import os
import sys
import time
from mm_client.client import MirisManagerClient

logger = logging.getLogger('recorder_controller')


class RecorderController(MirisManagerClient):
DEFAULT_CONF = {
'CAPABILITIES': ['record', 'shutdown'],
'CAPABILITIES': ['record', 'network_record', 'web_control', 'screenshot'],
}
PROFILES = {
'main': {
'has_password': False,
'can_live': False,
'name': 'main',
'label': 'Main',
'type': 'recorder'
}
}
PROFILES = {'main': {'has_password': False, 'can_live': False, 'name': 'main', 'label': 'Main', 'type': 'recorder'}}

def handle_action(self, action, params):
if action == 'SHUTDOWN':
logger.info('Shutdown requested.')
# TODO
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

elif action == 'REBOOT':
logger.info('Reboot requested.')
# TODO
self.update_capabilities()
self.set_status(
status='ready',
status_message='Ready to record',
remaining_space='auto'
)
try:
self.long_polling_loop()
except KeyboardInterrupt:
logger.info('KeyboardInterrupt received, stopping application.')

elif action == 'START_RECORDING':
def handle_action(self, uid, action, params):
# See help on the handle action function:
# https://github.com/UbiCastTeam/miris-manager-client/blob/main/mm_client/client.py#L184
# Possible actions:
# https://mirismanager.ubicast.eu/static/skyreach/docs/api/values.html#system-command-actions
if action == 'START_RECORDING':
logger.info('Starting recording with params %s', params)
self.set_status(status='recording', remaining_space='auto')
# TODO
self.set_status(
status='initializing',
status_message='',
remaining_space='auto'
)
time.sleep(3)
self.set_status(
status='running',
status_message='',
status_info='{"playlist": "/videos/BigBuckBunny_320x180.m3u8"}',
remaining_space='auto'
)
return 'DONE', ''

elif action == 'STOP_RECORDING':
logger.info('Stopping recording.')
self.set_status(status='recorder_idle', remaining_space='auto')
# TODO
self.set_status(
status='ready',
status_message='',
remaining_space='auto'
)
return 'DONE', ''

elif action == 'LIST_PROFILES':
logger.info('Returning list of profiles.')
return json.dumps(self.PROFILES)
return 'DONE', json.dumps(self.PROFILES)

elif action == 'GET_SCREENSHOT':
self.set_status(remaining_space='auto') # Send remaining space to Miris Manager
self.set_screenshot(
path='/var/lib/AccountsService/icons/%s' % (os.environ.get('USER') or 'root'),
file_name='screen.png'
)
logger.info('Screenshot sent.')
return 'DONE', ''

elif action == 'UPGRADE':
logger.info('Starting upgrade.')

# Start your asynchronous upgrade process here then call:
# self.set_command_status(uid, 'DONE', '')

return 'IN_PROGRESS', ''

else:
raise Exception('Unsupported action: %s.' % action)
raise NotImplementedError('Unsupported action: %s.' % action)


if __name__ == '__main__':
local_conf = sys.argv[1] if len(sys.argv) > 1 else None
client = RecorderController(local_conf)
try:
client.long_polling_loop()
except KeyboardInterrupt:
logger.info('KeyboardInterrupt received, stopping application.')
RecorderController(local_conf)
19 changes: 6 additions & 13 deletions examples/screen_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,26 @@ class ScreenController(MirisManagerClient):
'CAPABILITIES': ['screen_control', 'screenshot'],
}

def handle_action(self, action, params):
if action == 'SHUTDOWN':
logger.info('Shutdown requested.')
# TODO

elif action == 'REBOOT':
logger.info('Reboot requested.')
# TODO

elif action == 'GET_SCREENSHOT':
def handle_action(self, uid, action, params):
if action == 'GET_SCREENSHOT':
self.set_status(remaining_space='auto') # Send remaining space to Miris Manager
self.set_screenshot(
path='/var/lib/AccountsService/icons/%s' % (os.environ.get('USER') or 'root'),
file_name='screen.png'
)
logger.info('Screenshot sent.')
return 'DONE', ''

elif action == 'SIMULATE_CLICK':
logger.info('Click requested: %s.', params)
# TODO
return 'DONE', ''

elif action == 'SEND_TEXT':
logger.info('Text received: %s.', params)
# TODO
return 'DONE', ''

else:
raise Exception('Unsupported action: %s.' % action)
raise NotImplementedError('Unsupported action: %s.' % action)


if __name__ == '__main__':
Expand Down
28 changes: 18 additions & 10 deletions examples/wol_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,29 @@ class WOLRelay(MirisManagerClient):
'WOL_PATH': 'wakeonlan', # Path to the wake on lan binary, installed with `apt install wakeonlan`
}

def handle_action(self, action, params):
# This method must be implemented in your client
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.update_capabilities()
try:
self.long_polling_loop()
except KeyboardInterrupt:
logger.info('KeyboardInterrupt received, stopping application.')

def handle_action(self, uid, action, params):
# See help on the handle action function:
# https://github.com/UbiCastTeam/miris-manager-client/blob/main/mm_client/client.py#L184
# Possible actions:
# https://mirismanager.ubicast.eu/static/skyreach/docs/api/values.html#system-command-actions
if action == 'WAKE_ON_LAN': # wol_relay capability
# Send wake on lan
success, message = self.send_wake_on_lan(params)
logger.info('Running wake on lan: success: %s, message: %s', success, message)
if not success:
raise Exception('Failed to send wake on lan: %s' % message)
raise RuntimeError('Failed to send wake on lan: %s' % message)
return 'DONE', ''
else:
raise Exception('Unsupported action: %s.' % action)
raise NotImplementedError('Unsupported action: %s.' % action)

def send_wake_on_lan(self, params):
# Check that arguments are valid
Expand Down Expand Up @@ -56,9 +69,4 @@ def send_wake_on_lan(self, params):

if __name__ == '__main__':
local_conf = sys.argv[1] if len(sys.argv) > 1 else None
client = WOLRelay(local_conf)
client.update_capabilities()
try:
client.long_polling_loop()
except KeyboardInterrupt:
logger.info('KeyboardInterrupt received, stopping application.')
WOLRelay(local_conf)
30 changes: 20 additions & 10 deletions mm_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def api_request(self, url_or_action, method='get', headers=None, params=None,
# headers with "_" are ignored by Django
_headers = {'api-key': self.conf['API_KEY']}
if not anonymous:
signature = signing_lib.get_signature(self)
signature = signing_lib.get_signature(self.conf)
if signature:
_headers.update(signature)
if headers:
Expand All @@ -176,17 +176,27 @@ def api_request(self, url_or_action, method='get', headers=None, params=None,
)
return response

def long_polling_loop(self):
def long_polling_loop(self, single_loop=False):
if not self._long_polling_manager:
self._long_polling_manager = long_polling_lib.LongPollingManager(self)
self._long_polling_manager.loop()

def handle_action(self, action, params):
# Function that should be implemented in your client to process the
# long polling responses.
# IMPORTANT: Any code written here should not be blocking more than 5s
# because of the delay after which the system is considered as offline
# in Miris Manager.
self._long_polling_manager.loop(single_loop)

def handle_action(self, uid, action, params):
'''
Function that should be implemented in your client to process the long polling responses.
IMPORTANT: Any code written here should not be blocking more than 5s because of the
delay after which the system is considered as offline in Miris Manager.
Arguments:
- uid: The system command unique identifier.
- action: The action to run.
- params: The action parameters.
Must return a tuple: (status, data)
- status: The system command status (string). Possible values:
- "DONE": The command has been executed successfully.
- "IN_PROGRESS": The command has been started but is not yet completed.
- "FAILED": The command execution has failed.
- data: The command result data (string). It can be a json dump or a message. Empty strings are allowed.
'''
raise NotImplementedError('Your class should override the "handle_action" method.')

def set_command_status(self, command_uid, status='DONE', data=None):
Expand Down
Loading

0 comments on commit 43f3d7c

Please sign in to comment.