aprsd 4.0.2__py3-none-any.whl → 4.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aprsd/cli_helper.py +36 -35
- aprsd/client/base.py +14 -11
- aprsd/client/drivers/aprsis.py +87 -35
- aprsd/client/drivers/kiss.py +28 -5
- aprsd/client/kiss.py +1 -0
- aprsd/cmds/listen.py +84 -91
- aprsd/cmds/send_message.py +30 -28
- aprsd/cmds/server.py +29 -64
- aprsd/conf/common.py +100 -101
- aprsd/conf/log.py +32 -22
- aprsd/log/log.py +31 -18
- aprsd/main.py +22 -22
- aprsd/packets/__init__.py +6 -0
- aprsd/packets/core.py +5 -2
- aprsd/packets/filter.py +58 -0
- aprsd/packets/filters/__init__.py +0 -0
- aprsd/packets/filters/dupe_filter.py +68 -0
- aprsd/packets/filters/packet_type.py +53 -0
- aprsd/packets/packet_list.py +33 -27
- aprsd/plugin.py +52 -52
- aprsd/plugin_utils.py +20 -21
- aprsd/plugins/weather.py +110 -109
- aprsd/threads/__init__.py +1 -2
- aprsd/threads/rx.py +83 -75
- aprsd/threads/service.py +42 -0
- aprsd/threads/stats.py +4 -9
- aprsd/utils/objectstore.py +12 -13
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/METADATA +22 -20
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/RECORD +34 -29
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/WHEEL +1 -1
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/AUTHORS +0 -0
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/LICENSE +0 -0
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/entry_points.txt +0 -0
- {aprsd-4.0.2.dist-info → aprsd-4.1.1.dist-info}/top_level.txt +0 -0
aprsd/packets/filter.py
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Callable, Protocol, runtime_checkable, Union, Dict
|
3
|
+
|
4
|
+
from aprsd.packets import core
|
5
|
+
from aprsd.utils import singleton
|
6
|
+
|
7
|
+
LOG = logging.getLogger("APRSD")
|
8
|
+
|
9
|
+
|
10
|
+
@runtime_checkable
|
11
|
+
class PacketFilterProtocol(Protocol):
|
12
|
+
"""Protocol API for a packet filter class.
|
13
|
+
"""
|
14
|
+
def filter(self, packet: type[core.Packet]) -> Union[type[core.Packet], None]:
|
15
|
+
"""When we get a packet from the network.
|
16
|
+
|
17
|
+
Return a Packet object if the filter passes. Return None if the
|
18
|
+
Packet is filtered out.
|
19
|
+
"""
|
20
|
+
...
|
21
|
+
|
22
|
+
|
23
|
+
@singleton
|
24
|
+
class PacketFilter:
|
25
|
+
|
26
|
+
def __init__(self):
|
27
|
+
self.filters: Dict[str, Callable] = {}
|
28
|
+
|
29
|
+
def register(self, packet_filter: Callable) -> None:
|
30
|
+
if not isinstance(packet_filter, PacketFilterProtocol):
|
31
|
+
raise TypeError(f"class {packet_filter} is not a PacketFilterProtocol object")
|
32
|
+
|
33
|
+
if packet_filter not in self.filters:
|
34
|
+
self.filters[packet_filter] = packet_filter()
|
35
|
+
|
36
|
+
def unregister(self, packet_filter: Callable) -> None:
|
37
|
+
if not isinstance(packet_filter, PacketFilterProtocol):
|
38
|
+
raise TypeError(f"class {packet_filter} is not a PacketFilterProtocol object")
|
39
|
+
if packet_filter in self.filters:
|
40
|
+
del self.filters[packet_filter]
|
41
|
+
|
42
|
+
def filter(self, packet: type[core.Packet]) -> Union[type[core.Packet], None]:
|
43
|
+
"""Run through each of the filters.
|
44
|
+
|
45
|
+
This will step through each registered filter class
|
46
|
+
and call filter on it.
|
47
|
+
|
48
|
+
If the filter object returns None, we are done filtering.
|
49
|
+
If the filter object returns the packet, we continue filtering.
|
50
|
+
"""
|
51
|
+
for packet_filter in self.filters:
|
52
|
+
try:
|
53
|
+
if not self.filters[packet_filter].filter(packet):
|
54
|
+
LOG.debug(f"{self.filters[packet_filter].__class__.__name__} dropped {packet.__class__.__name__}:{packet.human_info}")
|
55
|
+
return None
|
56
|
+
except Exception as ex:
|
57
|
+
LOG.error(f"{packet_filter.__clas__.__name__} failed filtering packet {packet.__class__.__name__} : {ex}")
|
58
|
+
return packet
|
File without changes
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Union
|
3
|
+
|
4
|
+
from oslo_config import cfg
|
5
|
+
|
6
|
+
from aprsd import packets
|
7
|
+
from aprsd.packets import core
|
8
|
+
|
9
|
+
CONF = cfg.CONF
|
10
|
+
LOG = logging.getLogger('APRSD')
|
11
|
+
|
12
|
+
|
13
|
+
class DupePacketFilter:
|
14
|
+
"""This is a packet filter to detect duplicate packets.
|
15
|
+
|
16
|
+
This Uses the PacketList object to see if a packet exists
|
17
|
+
already. If it does exist in the PacketList, then we need to
|
18
|
+
check the flag on the packet to see if it's been processed before.
|
19
|
+
If the packet has been processed already within the allowed
|
20
|
+
timeframe, then it's a dupe.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def filter(self, packet: type[core.Packet]) -> Union[type[core.Packet], None]:
|
24
|
+
# LOG.debug(f"{self.__class__.__name__}.filter called for packet {packet}")
|
25
|
+
"""Filter a packet out if it's already been seen and processed."""
|
26
|
+
if isinstance(packet, core.AckPacket):
|
27
|
+
# We don't need to drop AckPackets, those should be
|
28
|
+
# processed.
|
29
|
+
# Send the AckPacket to the queue for processing elsewhere.
|
30
|
+
return packet
|
31
|
+
else:
|
32
|
+
# Make sure we aren't re-processing the same packet
|
33
|
+
# For RF based APRS Clients we can get duplicate packets
|
34
|
+
# So we need to track them and not process the dupes.
|
35
|
+
pkt_list = packets.PacketList()
|
36
|
+
found = False
|
37
|
+
try:
|
38
|
+
# Find the packet in the list of already seen packets
|
39
|
+
# Based on the packet.key
|
40
|
+
found = pkt_list.find(packet)
|
41
|
+
if not packet.msgNo:
|
42
|
+
# If the packet doesn't have a message id
|
43
|
+
# then there is no reliable way to detect
|
44
|
+
# if it's a dupe, so we just pass it on.
|
45
|
+
# it shouldn't get acked either.
|
46
|
+
found = False
|
47
|
+
except KeyError:
|
48
|
+
found = False
|
49
|
+
|
50
|
+
if not found:
|
51
|
+
# We haven't seen this packet before, so we process it.
|
52
|
+
return packet
|
53
|
+
|
54
|
+
if not packet.processed:
|
55
|
+
# We haven't processed this packet through the plugins.
|
56
|
+
return packet
|
57
|
+
elif packet.timestamp - found.timestamp < CONF.packet_dupe_timeout:
|
58
|
+
# If the packet came in within N seconds of the
|
59
|
+
# Last time seeing the packet, then we drop it as a dupe.
|
60
|
+
LOG.warning(
|
61
|
+
f'Packet {packet.from_call}:{packet.msgNo} already tracked, dropping.'
|
62
|
+
)
|
63
|
+
else:
|
64
|
+
LOG.warning(
|
65
|
+
f'Packet {packet.from_call}:{packet.msgNo} already tracked '
|
66
|
+
f'but older than {CONF.packet_dupe_timeout} seconds. processing.',
|
67
|
+
)
|
68
|
+
return packet
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Union
|
3
|
+
|
4
|
+
from oslo_config import cfg
|
5
|
+
|
6
|
+
from aprsd import packets
|
7
|
+
from aprsd.packets import core
|
8
|
+
from aprsd.utils import singleton
|
9
|
+
|
10
|
+
CONF = cfg.CONF
|
11
|
+
LOG = logging.getLogger('APRSD')
|
12
|
+
|
13
|
+
|
14
|
+
@singleton
|
15
|
+
class PacketTypeFilter:
|
16
|
+
"""This filter is used to filter out packets that don't match a specific type.
|
17
|
+
|
18
|
+
To use this, register it with the PacketFilter class,
|
19
|
+
then instante it and call set_allow_list() with a list of packet types
|
20
|
+
you want to allow to pass the filtering. All other packets will be
|
21
|
+
filtered out.
|
22
|
+
"""
|
23
|
+
|
24
|
+
filters = {
|
25
|
+
packets.Packet.__name__: packets.Packet,
|
26
|
+
packets.AckPacket.__name__: packets.AckPacket,
|
27
|
+
packets.BeaconPacket.__name__: packets.BeaconPacket,
|
28
|
+
packets.GPSPacket.__name__: packets.GPSPacket,
|
29
|
+
packets.MessagePacket.__name__: packets.MessagePacket,
|
30
|
+
packets.MicEPacket.__name__: packets.MicEPacket,
|
31
|
+
packets.ObjectPacket.__name__: packets.ObjectPacket,
|
32
|
+
packets.StatusPacket.__name__: packets.StatusPacket,
|
33
|
+
packets.ThirdPartyPacket.__name__: packets.ThirdPartyPacket,
|
34
|
+
packets.WeatherPacket.__name__: packets.WeatherPacket,
|
35
|
+
packets.UnknownPacket.__name__: packets.UnknownPacket,
|
36
|
+
}
|
37
|
+
|
38
|
+
allow_list = ()
|
39
|
+
|
40
|
+
def set_allow_list(self, filter_list):
|
41
|
+
tmp_list = []
|
42
|
+
for filter in filter_list:
|
43
|
+
LOG.warning(
|
44
|
+
f'Setting filter {filter} : {self.filters[filter]} to tmp {tmp_list}'
|
45
|
+
)
|
46
|
+
tmp_list.append(self.filters[filter])
|
47
|
+
self.allow_list = tuple(tmp_list)
|
48
|
+
|
49
|
+
def filter(self, packet: type[core.Packet]) -> Union[type[core.Packet], None]:
|
50
|
+
"""Only allow packets of certain types to filter through."""
|
51
|
+
if self.allow_list:
|
52
|
+
if isinstance(packet, self.allow_list):
|
53
|
+
return packet
|
aprsd/packets/packet_list.py
CHANGED
@@ -7,7 +7,7 @@ from aprsd.packets import core
|
|
7
7
|
from aprsd.utils import objectstore
|
8
8
|
|
9
9
|
CONF = cfg.CONF
|
10
|
-
LOG = logging.getLogger(
|
10
|
+
LOG = logging.getLogger('APRSD')
|
11
11
|
|
12
12
|
|
13
13
|
class PacketList(objectstore.ObjectStoreMixin):
|
@@ -27,8 +27,8 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
27
27
|
|
28
28
|
def _init_data(self):
|
29
29
|
self.data = {
|
30
|
-
|
31
|
-
|
30
|
+
'types': {},
|
31
|
+
'packets': OrderedDict(),
|
32
32
|
}
|
33
33
|
|
34
34
|
def rx(self, packet: type[core.Packet]):
|
@@ -37,11 +37,11 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
37
37
|
self._total_rx += 1
|
38
38
|
self._add(packet)
|
39
39
|
ptype = packet.__class__.__name__
|
40
|
-
type_stats = self.data[
|
40
|
+
type_stats = self.data['types'].setdefault(
|
41
41
|
ptype,
|
42
|
-
{
|
42
|
+
{'tx': 0, 'rx': 0},
|
43
43
|
)
|
44
|
-
type_stats[
|
44
|
+
type_stats['rx'] += 1
|
45
45
|
|
46
46
|
def tx(self, packet: type[core.Packet]):
|
47
47
|
"""Add a packet that was received."""
|
@@ -49,32 +49,32 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
49
49
|
self._total_tx += 1
|
50
50
|
self._add(packet)
|
51
51
|
ptype = packet.__class__.__name__
|
52
|
-
type_stats = self.data[
|
52
|
+
type_stats = self.data['types'].setdefault(
|
53
53
|
ptype,
|
54
|
-
{
|
54
|
+
{'tx': 0, 'rx': 0},
|
55
55
|
)
|
56
|
-
type_stats[
|
56
|
+
type_stats['tx'] += 1
|
57
57
|
|
58
58
|
def add(self, packet):
|
59
59
|
with self.lock:
|
60
60
|
self._add(packet)
|
61
61
|
|
62
62
|
def _add(self, packet):
|
63
|
-
if not self.data.get(
|
63
|
+
if not self.data.get('packets'):
|
64
64
|
self._init_data()
|
65
|
-
if packet.key in self.data[
|
66
|
-
self.data[
|
67
|
-
elif len(self.data[
|
68
|
-
self.data[
|
69
|
-
self.data[
|
65
|
+
if packet.key in self.data['packets']:
|
66
|
+
self.data['packets'].move_to_end(packet.key)
|
67
|
+
elif len(self.data['packets']) == self.maxlen:
|
68
|
+
self.data['packets'].popitem(last=False)
|
69
|
+
self.data['packets'][packet.key] = packet
|
70
70
|
|
71
71
|
def find(self, packet):
|
72
72
|
with self.lock:
|
73
|
-
return self.data[
|
73
|
+
return self.data['packets'][packet.key]
|
74
74
|
|
75
75
|
def __len__(self):
|
76
76
|
with self.lock:
|
77
|
-
return len(self.data[
|
77
|
+
return len(self.data['packets'])
|
78
78
|
|
79
79
|
def total_rx(self):
|
80
80
|
with self.lock:
|
@@ -87,17 +87,23 @@ class PacketList(objectstore.ObjectStoreMixin):
|
|
87
87
|
def stats(self, serializable=False) -> dict:
|
88
88
|
with self.lock:
|
89
89
|
# Get last N packets directly using list slicing
|
90
|
-
|
91
|
-
|
92
|
-
|
90
|
+
if CONF.packet_list_stats_maxlen >= 0:
|
91
|
+
packets_list = list(self.data.get('packets', {}).values())
|
92
|
+
pkts = packets_list[-CONF.packet_list_stats_maxlen :][::-1]
|
93
|
+
else:
|
94
|
+
# We have to copy here, because this get() results in a pointer
|
95
|
+
# to the packets internally here, which can change after this
|
96
|
+
# function returns, which would cause a problem trying to save
|
97
|
+
# the stats to disk.
|
98
|
+
pkts = self.data.get('packets', {}).copy()
|
93
99
|
stats = {
|
94
|
-
|
100
|
+
'total_tracked': self._total_rx
|
95
101
|
+ self._total_tx, # Fixed typo: was rx + rx
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
+
'rx': self._total_rx,
|
103
|
+
'tx': self._total_tx,
|
104
|
+
'types': self.data.get('types', {}), # Changed default from [] to {}
|
105
|
+
'packet_count': len(self.data.get('packets', [])),
|
106
|
+
'maxlen': self.maxlen,
|
107
|
+
'packets': pkts,
|
102
108
|
}
|
103
109
|
return stats
|
aprsd/plugin.py
CHANGED
@@ -17,24 +17,24 @@ from aprsd.packets import watch_list
|
|
17
17
|
|
18
18
|
# setup the global logger
|
19
19
|
CONF = cfg.CONF
|
20
|
-
LOG = logging.getLogger(
|
20
|
+
LOG = logging.getLogger('APRSD')
|
21
21
|
|
22
22
|
CORE_MESSAGE_PLUGINS = [
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
'aprsd.plugins.email.EmailPlugin',
|
24
|
+
'aprsd.plugins.fortune.FortunePlugin',
|
25
|
+
'aprsd.plugins.location.LocationPlugin',
|
26
|
+
'aprsd.plugins.ping.PingPlugin',
|
27
|
+
'aprsd.plugins.time.TimePlugin',
|
28
|
+
'aprsd.plugins.weather.USWeatherPlugin',
|
29
|
+
'aprsd.plugins.version.VersionPlugin',
|
30
30
|
]
|
31
31
|
|
32
32
|
CORE_NOTIFY_PLUGINS = [
|
33
|
-
|
33
|
+
'aprsd.plugins.notify.NotifySeenPlugin',
|
34
34
|
]
|
35
35
|
|
36
|
-
hookspec = pluggy.HookspecMarker(
|
37
|
-
hookimpl = pluggy.HookimplMarker(
|
36
|
+
hookspec = pluggy.HookspecMarker('aprsd')
|
37
|
+
hookimpl = pluggy.HookimplMarker('aprsd')
|
38
38
|
|
39
39
|
|
40
40
|
class APRSDPluginSpec:
|
@@ -76,14 +76,14 @@ class APRSDPluginBase(metaclass=abc.ABCMeta):
|
|
76
76
|
else:
|
77
77
|
LOG.error(
|
78
78
|
"Can't start thread {}:{}, Must be a child "
|
79
|
-
|
79
|
+
'of aprsd.threads.APRSDThread'.format(
|
80
80
|
self,
|
81
81
|
thread,
|
82
82
|
),
|
83
83
|
)
|
84
84
|
except Exception:
|
85
85
|
LOG.error(
|
86
|
-
|
86
|
+
'Failed to start threads for plugin {}'.format(
|
87
87
|
self,
|
88
88
|
),
|
89
89
|
)
|
@@ -93,7 +93,7 @@ class APRSDPluginBase(metaclass=abc.ABCMeta):
|
|
93
93
|
return self.message_counter
|
94
94
|
|
95
95
|
def help(self) -> str:
|
96
|
-
return
|
96
|
+
return 'Help!'
|
97
97
|
|
98
98
|
@abc.abstractmethod
|
99
99
|
def setup(self):
|
@@ -147,10 +147,10 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
147
147
|
# make sure the timeout is set or this doesn't work
|
148
148
|
if watch_list:
|
149
149
|
aprs_client = client.client_factory.create().client
|
150
|
-
filter_str =
|
150
|
+
filter_str = 'b/{}'.format('/'.join(watch_list))
|
151
151
|
aprs_client.set_filter(filter_str)
|
152
152
|
else:
|
153
|
-
LOG.warning(
|
153
|
+
LOG.warning('Watch list enabled, but no callsigns set.')
|
154
154
|
|
155
155
|
@hookimpl
|
156
156
|
def filter(self, packet: type[packets.Packet]) -> str | packets.MessagePacket:
|
@@ -164,7 +164,7 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
164
164
|
result = self.process(packet)
|
165
165
|
except Exception as ex:
|
166
166
|
LOG.error(
|
167
|
-
|
167
|
+
'Plugin {} failed to process packet {}'.format(
|
168
168
|
self.__class__,
|
169
169
|
ex,
|
170
170
|
),
|
@@ -172,7 +172,7 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
172
172
|
if result:
|
173
173
|
self.tx_inc()
|
174
174
|
else:
|
175
|
-
LOG.warning(f
|
175
|
+
LOG.warning(f'{self.__class__} plugin is not enabled')
|
176
176
|
|
177
177
|
return result
|
178
178
|
|
@@ -196,7 +196,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
196
196
|
raise NotImplementedError
|
197
197
|
|
198
198
|
def help(self):
|
199
|
-
return
|
199
|
+
return '{}: {}'.format(
|
200
200
|
self.command_name.lower(),
|
201
201
|
self.command_regex,
|
202
202
|
)
|
@@ -207,7 +207,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
207
207
|
|
208
208
|
@hookimpl
|
209
209
|
def filter(self, packet: packets.MessagePacket) -> str | packets.MessagePacket:
|
210
|
-
LOG.debug(f
|
210
|
+
LOG.debug(f'{self.__class__.__name__} called')
|
211
211
|
if not self.enabled:
|
212
212
|
result = f"{self.__class__.__name__} isn't enabled"
|
213
213
|
LOG.warning(result)
|
@@ -215,7 +215,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
215
215
|
|
216
216
|
if not isinstance(packet, packets.MessagePacket):
|
217
217
|
LOG.warning(
|
218
|
-
f
|
218
|
+
f'{self.__class__.__name__} Got a {packet.__class__.__name__} ignoring'
|
219
219
|
)
|
220
220
|
return packets.NULL_MESSAGE
|
221
221
|
|
@@ -237,7 +237,7 @@ class APRSDRegexCommandPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
|
|
237
237
|
result = self.process(packet)
|
238
238
|
except Exception as ex:
|
239
239
|
LOG.error(
|
240
|
-
|
240
|
+
'Plugin {} failed to process packet {}'.format(
|
241
241
|
self.__class__,
|
242
242
|
ex,
|
243
243
|
),
|
@@ -254,7 +254,7 @@ class APRSFIKEYMixin:
|
|
254
254
|
|
255
255
|
def ensure_aprs_fi_key(self):
|
256
256
|
if not CONF.aprs_fi.apiKey:
|
257
|
-
LOG.error(
|
257
|
+
LOG.error('Config aprs_fi.apiKey is not set')
|
258
258
|
self.enabled = False
|
259
259
|
else:
|
260
260
|
self.enabled = True
|
@@ -266,25 +266,25 @@ class HelpPlugin(APRSDRegexCommandPluginBase):
|
|
266
266
|
This plugin is in this file to prevent a circular import.
|
267
267
|
"""
|
268
268
|
|
269
|
-
command_regex =
|
270
|
-
command_name =
|
269
|
+
command_regex = '^[hH]'
|
270
|
+
command_name = 'help'
|
271
271
|
|
272
272
|
def help(self):
|
273
|
-
return
|
273
|
+
return 'Help: send APRS help or help <plugin>'
|
274
274
|
|
275
275
|
def process(self, packet: packets.MessagePacket):
|
276
|
-
LOG.info(
|
276
|
+
LOG.info('HelpPlugin')
|
277
277
|
# fromcall = packet.get("from")
|
278
278
|
message = packet.message_text
|
279
279
|
# ack = packet.get("msgNo", "0")
|
280
|
-
a = re.search(r
|
280
|
+
a = re.search(r'^.*\s+(.*)', message)
|
281
281
|
command_name = None
|
282
282
|
if a is not None:
|
283
283
|
command_name = a.group(1).lower()
|
284
284
|
|
285
285
|
pm = PluginManager()
|
286
286
|
|
287
|
-
if command_name and
|
287
|
+
if command_name and '?' not in command_name:
|
288
288
|
# user wants help for a specific plugin
|
289
289
|
reply = None
|
290
290
|
for p in pm.get_plugins():
|
@@ -303,20 +303,20 @@ class HelpPlugin(APRSDRegexCommandPluginBase):
|
|
303
303
|
LOG.debug(p)
|
304
304
|
if p.enabled and isinstance(p, APRSDRegexCommandPluginBase):
|
305
305
|
name = p.command_name.lower()
|
306
|
-
if name not in list and
|
306
|
+
if name not in list and 'help' not in name:
|
307
307
|
list.append(name)
|
308
308
|
|
309
309
|
list.sort()
|
310
|
-
reply =
|
310
|
+
reply = ' '.join(list)
|
311
311
|
lines = textwrap.wrap(reply, 60)
|
312
312
|
replies = ["Send APRS MSG of 'help' or 'help <plugin>'"]
|
313
313
|
for line in lines:
|
314
|
-
replies.append(f
|
314
|
+
replies.append(f'plugins: {line}')
|
315
315
|
|
316
316
|
for entry in replies:
|
317
|
-
LOG.debug(f
|
317
|
+
LOG.debug(f'{len(entry)} {entry}')
|
318
318
|
|
319
|
-
LOG.debug(f
|
319
|
+
LOG.debug(f'{replies}')
|
320
320
|
return replies
|
321
321
|
|
322
322
|
|
@@ -341,17 +341,17 @@ class PluginManager:
|
|
341
341
|
return cls._instance
|
342
342
|
|
343
343
|
def _init(self):
|
344
|
-
self._pluggy_pm = pluggy.PluginManager(
|
344
|
+
self._pluggy_pm = pluggy.PluginManager('aprsd')
|
345
345
|
self._pluggy_pm.add_hookspecs(APRSDPluginSpec)
|
346
346
|
# For the watchlist plugins
|
347
|
-
self._watchlist_pm = pluggy.PluginManager(
|
347
|
+
self._watchlist_pm = pluggy.PluginManager('aprsd')
|
348
348
|
self._watchlist_pm.add_hookspecs(APRSDPluginSpec)
|
349
349
|
|
350
350
|
def stats(self, serializable=False) -> dict:
|
351
351
|
"""Collect and return stats for all plugins."""
|
352
352
|
|
353
353
|
def full_name_with_qualname(obj):
|
354
|
-
return
|
354
|
+
return '{}.{}'.format(
|
355
355
|
obj.__class__.__module__,
|
356
356
|
obj.__class__.__qualname__,
|
357
357
|
)
|
@@ -361,10 +361,10 @@ class PluginManager:
|
|
361
361
|
if plugins:
|
362
362
|
for p in plugins:
|
363
363
|
plugin_stats[full_name_with_qualname(p)] = {
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
364
|
+
'enabled': p.enabled,
|
365
|
+
'rx': p.rx_count,
|
366
|
+
'tx': p.tx_count,
|
367
|
+
'version': p.version,
|
368
368
|
}
|
369
369
|
|
370
370
|
return plugin_stats
|
@@ -392,19 +392,19 @@ class PluginManager:
|
|
392
392
|
module_name = None
|
393
393
|
class_name = None
|
394
394
|
try:
|
395
|
-
module_name, class_name = module_class_string.rsplit(
|
395
|
+
module_name, class_name = module_class_string.rsplit('.', 1)
|
396
396
|
module = importlib.import_module(module_name)
|
397
397
|
# Commented out because the email thread starts in a different context
|
398
398
|
# and hence gives a different singleton for the EmailStats
|
399
399
|
# module = importlib.reload(module)
|
400
400
|
except Exception as ex:
|
401
401
|
if not module_name:
|
402
|
-
LOG.error(f
|
402
|
+
LOG.error(f'Failed to load Plugin {module_class_string}')
|
403
403
|
else:
|
404
404
|
LOG.error(f"Failed to load Plugin '{module_name}' : '{ex}'")
|
405
405
|
return
|
406
406
|
|
407
|
-
assert hasattr(module, class_name),
|
407
|
+
assert hasattr(module, class_name), 'class {} is not in {}'.format(
|
408
408
|
class_name,
|
409
409
|
module_name,
|
410
410
|
)
|
@@ -412,7 +412,7 @@ class PluginManager:
|
|
412
412
|
# class_name, module_name))
|
413
413
|
cls = getattr(module, class_name)
|
414
414
|
if super_cls is not None:
|
415
|
-
assert issubclass(cls, super_cls),
|
415
|
+
assert issubclass(cls, super_cls), 'class {} should inherit from {}'.format(
|
416
416
|
class_name,
|
417
417
|
super_cls.__name__,
|
418
418
|
)
|
@@ -444,7 +444,7 @@ class PluginManager:
|
|
444
444
|
self._watchlist_pm.register(plugin_obj)
|
445
445
|
else:
|
446
446
|
LOG.warning(
|
447
|
-
f
|
447
|
+
f'Plugin {plugin_obj.__class__.__name__} is disabled'
|
448
448
|
)
|
449
449
|
elif isinstance(plugin_obj, APRSDRegexCommandPluginBase):
|
450
450
|
if plugin_obj.enabled:
|
@@ -458,7 +458,7 @@ class PluginManager:
|
|
458
458
|
self._pluggy_pm.register(plugin_obj)
|
459
459
|
else:
|
460
460
|
LOG.warning(
|
461
|
-
f
|
461
|
+
f'Plugin {plugin_obj.__class__.__name__} is disabled'
|
462
462
|
)
|
463
463
|
elif isinstance(plugin_obj, APRSDPluginBase):
|
464
464
|
if plugin_obj.enabled:
|
@@ -471,7 +471,7 @@ class PluginManager:
|
|
471
471
|
self._pluggy_pm.register(plugin_obj)
|
472
472
|
else:
|
473
473
|
LOG.warning(
|
474
|
-
f
|
474
|
+
f'Plugin {plugin_obj.__class__.__name__} is disabled'
|
475
475
|
)
|
476
476
|
except Exception as ex:
|
477
477
|
LOG.error(f"Couldn't load plugin '{plugin_name}'")
|
@@ -485,11 +485,11 @@ class PluginManager:
|
|
485
485
|
def setup_plugins(
|
486
486
|
self,
|
487
487
|
load_help_plugin=True,
|
488
|
-
plugin_list=
|
488
|
+
plugin_list=None,
|
489
489
|
):
|
490
490
|
"""Create the plugin manager and register plugins."""
|
491
491
|
|
492
|
-
LOG.info(
|
492
|
+
LOG.info('Loading APRSD Plugins')
|
493
493
|
# Help plugin is always enabled.
|
494
494
|
if load_help_plugin:
|
495
495
|
_help = HelpPlugin()
|
@@ -509,7 +509,7 @@ class PluginManager:
|
|
509
509
|
for p_name in CORE_MESSAGE_PLUGINS:
|
510
510
|
self._load_plugin(p_name)
|
511
511
|
|
512
|
-
LOG.info(
|
512
|
+
LOG.info('Completed Plugin Loading.')
|
513
513
|
|
514
514
|
def run(self, packet: packets.MessagePacket):
|
515
515
|
"""Execute all the plugins run method."""
|
@@ -524,7 +524,7 @@ class PluginManager:
|
|
524
524
|
"""Stop all threads created by all plugins."""
|
525
525
|
with self.lock:
|
526
526
|
for p in self.get_plugins():
|
527
|
-
if hasattr(p,
|
527
|
+
if hasattr(p, 'stop_threads'):
|
528
528
|
p.stop_threads()
|
529
529
|
|
530
530
|
def register_msg(self, obj):
|