Projekt

Obecné

Profil

Stáhnout (4.65 KB) Statistiky
| Větev: | Tag: | Revize:
1
import json
2
import requests
3
import logging
4
from time import sleep
5
from diskcache import Deque
6
from requests import HTTPError, ConnectionError
7
from requests.exceptions import InvalidSchema
8

    
9

    
10
_uri = None     # server uri (url, port, and endpoint)
11
_cache = None   # cache (failed payloads)
12
_config = None  # instance of Config
13

    
14

    
15
def api_client_set_config(config):
16
    """Initializes the client API module.
17

    
18
    This function is meant to be called prior to calling any other function
19
    of the API module. It stores the instance of Config (config manager)
20
    into a private variable. It also initializes the cache for unsuccessful
21
    payloads and constructs a URI (endpoint on the server side).
22

    
23
    :param config: instance of Config which holds all values defined
24
                   in the configuration file.
25
    """
26
    # Store the variables globally within the module (file).
27
    global _config, _cache, _uri
28

    
29
    # Store the instance of Config and initialize the cache.
30
    _config = config
31
    _cache = _init_cache()
32

    
33
    # Creates the URI which is made of the server url, port, and path (endpoint).
34
    _uri = config.server_url + ":" + config.server_port + config.server_endpoint
35

    
36

    
37
def _init_cache():
38
    """ Initializes and returns a disk-based cache.
39

    
40
    The cache holds payloads that the application failed
41
    to send to the server. It periodically attempts to resend
42
    them to the server. All parameters can be seen in the
43
    configuration file.
44

    
45
    :return: instance of a new cache (Deque - FIFO)
46
    """
47
    return Deque(directory=_config.cache_dir)
48

    
49

    
50
def send_data(payload: dict):
51
    """Sends a payload off to the server.
52

    
53
    This function is called whenever a USB is connected
54
    or disconnected. If there is no internet connection or the
55
    server is not up and running, the payload will be stored
56
    into the disk cache.
57

    
58
    :param payload: payload to be sent to the server
59
    """
60
    # Make sure that the URI has been constructed properly.
61
    # It is supposed to be done by calling the api_client_set_config function
62
    # with appropriate parameters.
63
    if _uri is None:
64
        logging.warning(f"sending payload = {payload} failed because uri is set to None")
65
        _cache_failed_payload(payload)
66
        return
67
    try:
68
        logging.info(f"sending payload = {payload} to {_uri}")
69
        response = requests.post(url=_uri, data=json.dumps(payload))
70
        logging.info(f"response text: {response.text}")
71
    except (ConnectionError, InvalidSchema):
72
        logging.warning(f"sending payload = {payload} to {_uri} failed")
73
        _cache_failed_payload(payload)
74
    except HTTPError as error:
75
        logging.error(f"HTTP Error ({_uri}) payload = {payload}, {error}")
76
        _cache_failed_payload(payload)
77

    
78

    
79
def _cache_failed_payload(payload: dict):
80
    """ Caches a payload.
81

    
82
    This function is called when the application fails to send a payload
83
    to the server. The payload gets stored into a file-based cache from which
84
    it will be periodically retrieved as the client will attempt to send
85
    it to the server again. All parameters regarding the cache can be found
86
    in the configuration file.
87

    
88
    :param payload: payload to be cached
89
    """
90
    # If the cache is "full", discard the oldest record.
91
    if len(_cache) >= _config.cache_max_entries:
92
        oldest_payload = _cache.pop()
93
        logging.warning(f"cache is full - discarding payload = {oldest_payload}")
94

    
95
    # Store the payload into the cache.
96
    logging.info(f"adding payload = {payload} into cache")
97
    _cache.append(payload)
98

    
99

    
100
def _resend_cached_payloads():
101
    """Reattempts to send cached payloads to the server (API).
102

    
103
    In the configuration file, there is a predefined number of
104
    payloads that can be sent to the server with each call of this function.
105
    This function is called periodically from api_client_run in order
106
    to resend failed payloads to the server.
107

    
108
    """
109
    # Calculate how many payload will be sent to the server
110
    retries = min(_config.cache_max_retries, len(_cache))
111
    logging.info(f"emptying the cache ({retries} records)")
112

    
113
    # Send the payloads to the server one by one.
114
    for _ in range(0, retries):
115
        payload = _cache.pop()
116
        send_data(payload)
117

    
118

    
119
def api_client_run():
120
    """ Keeps resending failed payloads to the server.
121

    
122
    This function is instantiated as a thread that periodically
123
    calls the _resend_cached_payloads function in order to empty
124
    the cache (failed payloads). The period can be set in the
125
    configuration file.
126
    """
127
    while True:
128
        # Resend a predefined amount of failed payloads to the server.
129
        _resend_cached_payloads()
130

    
131
        # Sleep for a predefined amount of seconds.
132
        sleep(_config.cache_retry_period_seconds)
(2-2/5)