aprsd 3.4.4__py3-none-any.whl → 4.0.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.
Files changed (114) hide show
  1. aprsd/cli_helper.py +12 -5
  2. aprsd/client/aprsis.py +31 -9
  3. aprsd/client/base.py +13 -2
  4. aprsd/client/drivers/aprsis.py +6 -3
  5. aprsd/client/drivers/fake.py +15 -20
  6. aprsd/client/factory.py +0 -2
  7. aprsd/client/fake.py +0 -2
  8. aprsd/client/kiss.py +17 -2
  9. aprsd/client/stats.py +1 -3
  10. aprsd/cmds/completion.py +7 -4
  11. aprsd/cmds/dev.py +38 -42
  12. aprsd/cmds/fetch_stats.py +140 -143
  13. aprsd/cmds/healthcheck.py +5 -3
  14. aprsd/cmds/list_plugins.py +140 -134
  15. aprsd/cmds/listen.py +13 -9
  16. aprsd/cmds/server.py +53 -27
  17. aprsd/conf/__init__.py +1 -2
  18. aprsd/conf/client.py +3 -4
  19. aprsd/conf/common.py +19 -93
  20. aprsd/conf/log.py +2 -4
  21. aprsd/conf/opts.py +5 -4
  22. aprsd/conf/plugin_common.py +11 -121
  23. aprsd/exception.py +2 -0
  24. aprsd/log/log.py +7 -46
  25. aprsd/main.py +10 -4
  26. aprsd/packets/__init__.py +14 -4
  27. aprsd/packets/core.py +57 -67
  28. aprsd/packets/log.py +8 -8
  29. aprsd/packets/packet_list.py +9 -6
  30. aprsd/plugin.py +22 -11
  31. aprsd/plugins/notify.py +1 -4
  32. aprsd/plugins/weather.py +10 -8
  33. aprsd/stats/collector.py +5 -2
  34. aprsd/threads/__init__.py +3 -2
  35. aprsd/threads/aprsd.py +12 -7
  36. aprsd/threads/{keep_alive.py → keepalive.py} +14 -45
  37. aprsd/threads/registry.py +3 -3
  38. aprsd/threads/rx.py +9 -6
  39. aprsd/threads/stats.py +2 -2
  40. aprsd/threads/tx.py +3 -4
  41. aprsd/utils/__init__.py +42 -10
  42. aprsd/utils/json.py +9 -4
  43. aprsd/utils/keepalive_collector.py +55 -0
  44. aprsd/utils/trace.py +0 -2
  45. aprsd-4.0.1.dist-info/AUTHORS +1 -0
  46. {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/METADATA +307 -408
  47. aprsd-4.0.1.dist-info/RECORD +74 -0
  48. {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/WHEEL +1 -1
  49. aprsd/cmds/admin.py +0 -57
  50. aprsd/cmds/webchat.py +0 -662
  51. aprsd/conf/plugin_email.py +0 -105
  52. aprsd/plugins/email.py +0 -715
  53. aprsd/plugins/location.py +0 -181
  54. aprsd/threads/log_monitor.py +0 -121
  55. aprsd/web/__init__.py +0 -0
  56. aprsd/web/admin/__init__.py +0 -0
  57. aprsd/web/admin/static/css/index.css +0 -84
  58. aprsd/web/admin/static/css/prism.css +0 -4
  59. aprsd/web/admin/static/css/tabs.css +0 -35
  60. aprsd/web/admin/static/images/Untitled.png +0 -0
  61. aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
  62. aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
  63. aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
  64. aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
  65. aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
  66. aprsd/web/admin/static/js/charts.js +0 -235
  67. aprsd/web/admin/static/js/echarts.js +0 -465
  68. aprsd/web/admin/static/js/logs.js +0 -26
  69. aprsd/web/admin/static/js/main.js +0 -231
  70. aprsd/web/admin/static/js/prism.js +0 -12
  71. aprsd/web/admin/static/js/send-message.js +0 -114
  72. aprsd/web/admin/static/js/tabs.js +0 -28
  73. aprsd/web/admin/templates/index.html +0 -196
  74. aprsd/web/chat/static/css/chat.css +0 -115
  75. aprsd/web/chat/static/css/index.css +0 -66
  76. aprsd/web/chat/static/css/style.css.map +0 -1
  77. aprsd/web/chat/static/css/tabs.css +0 -41
  78. aprsd/web/chat/static/css/upstream/bootstrap.min.css +0 -6
  79. aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
  80. aprsd/web/chat/static/css/upstream/google-fonts.css +0 -23
  81. aprsd/web/chat/static/css/upstream/jquery-ui.css +0 -1311
  82. aprsd/web/chat/static/css/upstream/jquery.toast.css +0 -28
  83. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
  84. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
  85. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
  86. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
  87. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
  88. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
  89. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
  90. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
  91. aprsd/web/chat/static/images/Untitled.png +0 -0
  92. aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
  93. aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
  94. aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
  95. aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
  96. aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
  97. aprsd/web/chat/static/images/globe.svg +0 -3
  98. aprsd/web/chat/static/js/gps.js +0 -84
  99. aprsd/web/chat/static/js/main.js +0 -45
  100. aprsd/web/chat/static/js/send-message.js +0 -612
  101. aprsd/web/chat/static/js/tabs.js +0 -28
  102. aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +0 -7
  103. aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +0 -2
  104. aprsd/web/chat/static/js/upstream/jquery-ui.min.js +0 -13
  105. aprsd/web/chat/static/js/upstream/jquery.toast.js +0 -374
  106. aprsd/web/chat/static/js/upstream/semantic.min.js +0 -11
  107. aprsd/web/chat/static/js/upstream/socket.io.min.js +0 -7
  108. aprsd/web/chat/templates/index.html +0 -139
  109. aprsd/wsgi.py +0 -322
  110. aprsd-3.4.4.dist-info/AUTHORS +0 -13
  111. aprsd-3.4.4.dist-info/RECORD +0 -134
  112. {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/LICENSE +0 -0
  113. {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/entry_points.txt +0 -0
  114. {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/top_level.txt +0 -0
@@ -4,12 +4,9 @@ import inspect
4
4
  import logging
5
5
  import os
6
6
  import pkgutil
7
- import re
8
7
  import sys
9
8
  from traceback import print_tb
10
- from urllib.parse import urljoin
11
9
 
12
- from bs4 import BeautifulSoup
13
10
  import click
14
11
  import requests
15
12
  from rich.console import Console
@@ -20,17 +17,14 @@ from thesmuggler import smuggle
20
17
  from aprsd import cli_helper
21
18
  from aprsd import plugin as aprsd_plugin
22
19
  from aprsd.main import cli
23
- from aprsd.plugins import (
24
- email, fortune, location, notify, ping, time, version, weather,
25
- )
20
+ from aprsd.plugins import fortune, notify, ping, time, version, weather
26
21
 
27
-
28
- LOG = logging.getLogger("APRSD")
29
- PYPI_URL = "https://pypi.org/search/"
22
+ LOG = logging.getLogger('APRSD')
23
+ PYPI_URL = 'https://pypi.org/search/'
30
24
 
31
25
 
32
26
  def onerror(name):
33
- print(f"Error importing module {name}")
27
+ print(f'Error importing module {name}')
34
28
  type, value, traceback = sys.exc_info()
35
29
  print_tb(traceback)
36
30
 
@@ -46,19 +40,19 @@ def is_plugin(obj):
46
40
  def plugin_type(obj):
47
41
  for c in inspect.getmro(obj):
48
42
  if issubclass(c, aprsd_plugin.APRSDRegexCommandPluginBase):
49
- return "RegexCommand"
43
+ return 'RegexCommand'
50
44
  if issubclass(c, aprsd_plugin.APRSDWatchListPluginBase):
51
- return "WatchList"
45
+ return 'WatchList'
52
46
  if issubclass(c, aprsd_plugin.APRSDPluginBase):
53
- return "APRSDPluginBase"
47
+ return 'APRSDPluginBase'
54
48
 
55
- return "Unknown"
49
+ return 'Unknown'
56
50
 
57
51
 
58
52
  def walk_package(package):
59
53
  return pkgutil.walk_packages(
60
54
  package.__path__,
61
- package.__name__ + ".",
55
+ package.__name__ + '.',
62
56
  onerror=onerror,
63
57
  )
64
58
 
@@ -68,22 +62,23 @@ def get_module_info(package_name, module_name, module_path):
68
62
  return None
69
63
 
70
64
  dir_path = os.path.realpath(module_path)
71
- pattern = "*.py"
65
+ pattern = '*.py'
72
66
 
73
67
  obj_list = []
74
68
 
75
69
  for path, _subdirs, files in os.walk(dir_path):
76
70
  for name in files:
77
71
  if fnmatch.fnmatch(name, pattern):
78
- module = smuggle(f"{path}/{name}")
72
+ module = smuggle(f'{path}/{name}')
79
73
  for mem_name, obj in inspect.getmembers(module):
80
74
  if inspect.isclass(obj) and is_plugin(obj):
81
75
  obj_list.append(
82
76
  {
83
- "package": package_name,
84
- "name": mem_name, "obj": obj,
85
- "version": obj.version,
86
- "path": f"{'.'.join([module_name, obj.__name__])}",
77
+ 'package': package_name,
78
+ 'name': mem_name,
79
+ 'obj': obj,
80
+ 'version': obj.version,
81
+ 'path': f'{".".join([module_name, obj.__name__])}',
87
82
  },
88
83
  )
89
84
 
@@ -94,18 +89,18 @@ def _get_installed_aprsd_items():
94
89
  # installed plugins
95
90
  plugins = {}
96
91
  extensions = {}
97
- for finder, name, ispkg in pkgutil.iter_modules():
98
- if name.startswith("aprsd_"):
99
- print(f"Found aprsd_ module: {name}")
100
- if ispkg:
101
- module = importlib.import_module(name)
102
- pkgs = walk_package(module)
103
- for pkg in pkgs:
104
- pkg_info = get_module_info(module.__name__, pkg.name, module.__path__[0])
105
- if "plugin" in name:
106
- plugins[name] = pkg_info
107
- elif "extension" in name:
108
- extensions[name] = pkg_info
92
+ for _finder, name, ispkg in pkgutil.iter_modules():
93
+ if ispkg and name.startswith('aprsd_'):
94
+ module = importlib.import_module(name)
95
+ pkgs = walk_package(module)
96
+ for pkg in pkgs:
97
+ pkg_info = get_module_info(
98
+ module.__name__, pkg.name, module.__path__[0]
99
+ )
100
+ if 'plugin' in name:
101
+ plugins[name] = pkg_info
102
+ elif 'extension' in name:
103
+ extensions[name] = pkg_info
109
104
  return plugins, extensions
110
105
 
111
106
 
@@ -122,7 +117,7 @@ def get_installed_extensions():
122
117
 
123
118
 
124
119
  def show_built_in_plugins(console):
125
- modules = [email, fortune, location, notify, ping, time, version, weather]
120
+ modules = [fortune, notify, ping, time, version, weather]
126
121
  plugins = []
127
122
 
128
123
  for module in modules:
@@ -131,132 +126,141 @@ def show_built_in_plugins(console):
131
126
  cls = entry[1]
132
127
  if issubclass(cls, aprsd_plugin.APRSDPluginBase):
133
128
  info = {
134
- "name": cls.__qualname__,
135
- "path": f"{cls.__module__}.{cls.__qualname__}",
136
- "version": cls.version,
137
- "docstring": cls.__doc__,
138
- "short_desc": cls.short_description,
129
+ 'name': cls.__qualname__,
130
+ 'path': f'{cls.__module__}.{cls.__qualname__}',
131
+ 'version': cls.version,
132
+ 'docstring': cls.__doc__,
133
+ 'short_desc': cls.short_description,
139
134
  }
140
135
 
141
136
  if issubclass(cls, aprsd_plugin.APRSDRegexCommandPluginBase):
142
- info["command_regex"] = cls.command_regex
143
- info["type"] = "RegexCommand"
137
+ info['command_regex'] = cls.command_regex
138
+ info['type'] = 'RegexCommand'
144
139
 
145
140
  if issubclass(cls, aprsd_plugin.APRSDWatchListPluginBase):
146
- info["type"] = "WatchList"
141
+ info['type'] = 'WatchList'
147
142
 
148
143
  plugins.append(info)
149
144
 
150
- plugins = sorted(plugins, key=lambda i: i["name"])
145
+ plugins = sorted(plugins, key=lambda i: i['name'])
151
146
 
152
147
  table = Table(
153
- title="[not italic]:snake:[/] [bold][magenta]APRSD Built-in Plugins [not italic]:snake:[/]",
148
+ title='[not italic]:snake:[/] [bold][magenta]APRSD Built-in Plugins [not italic]:snake:[/]',
154
149
  )
155
- table.add_column("Plugin Name", style="cyan", no_wrap=True)
156
- table.add_column("Info", style="bold yellow")
157
- table.add_column("Type", style="bold green")
158
- table.add_column("Plugin Path", style="bold blue")
150
+ table.add_column('Plugin Name', style='cyan', no_wrap=True)
151
+ table.add_column('Info', style='bold yellow')
152
+ table.add_column('Type', style='bold green')
153
+ table.add_column('Plugin Path', style='bold blue')
159
154
  for entry in plugins:
160
- table.add_row(entry["name"], entry["short_desc"], entry["type"], entry["path"])
155
+ table.add_row(entry['name'], entry['short_desc'], entry['type'], entry['path'])
161
156
 
162
157
  console.print(table)
163
158
 
164
159
 
165
160
  def _get_pypi_packages():
166
- query = "aprsd"
167
- snippets = []
168
- s = requests.Session()
169
- for page in range(1, 3):
170
- params = {"q": query, "page": page}
171
- r = s.get(PYPI_URL, params=params)
172
- soup = BeautifulSoup(r.text, "html.parser")
173
- snippets += soup.select('a[class*="snippet"]')
174
- if not hasattr(s, "start_url"):
175
- s.start_url = r.url.rsplit("&page", maxsplit=1).pop(0)
176
-
177
- return snippets
161
+ if simple_r := requests.get(
162
+ 'https://pypi.org/simple',
163
+ headers={'Accept': 'application/vnd.pypi.simple.v1+json'},
164
+ ):
165
+ simple_response = simple_r.json()
166
+ else:
167
+ simple_response = {}
168
+
169
+ key = 'aprsd'
170
+ matches = [
171
+ p['name'] for p in simple_response['projects'] if p['name'].startswith(key)
172
+ ]
173
+
174
+ packages = []
175
+ for pkg in matches:
176
+ # Get info for first match
177
+ if r := requests.get(
178
+ f'https://pypi.org/pypi/{pkg}/json',
179
+ headers={'Accept': 'application/json'},
180
+ ):
181
+ packages.append(r.json())
182
+
183
+ return packages
178
184
 
179
185
 
180
186
  def show_pypi_plugins(installed_plugins, console):
181
- snippets = _get_pypi_packages()
187
+ packages = _get_pypi_packages()
182
188
 
183
189
  title = Text.assemble(
184
- ("Pypi.org APRSD Installable Plugin Packages\n\n", "bold magenta"),
185
- ("Install any of the following plugins with\n", "bold yellow"),
186
- ("'pip install ", "bold white"),
187
- ("<Plugin Package Name>'", "cyan"),
190
+ ('Pypi.org APRSD Installable Plugin Packages\n\n', 'bold magenta'),
191
+ ('Install any of the following plugins with\n', 'bold yellow'),
192
+ ("'pip install ", 'bold white'),
193
+ ("<Plugin Package Name>'", 'cyan'),
188
194
  )
189
195
 
190
196
  table = Table(title=title)
191
- table.add_column("Plugin Package Name", style="cyan", no_wrap=True)
192
- table.add_column("Description", style="yellow")
193
- table.add_column("Version", style="yellow", justify="center")
194
- table.add_column("Released", style="bold green", justify="center")
195
- table.add_column("Installed?", style="red", justify="center")
196
- for snippet in snippets:
197
- link = urljoin(PYPI_URL, snippet.get("href"))
198
- package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
199
- version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
200
- created = re.sub(r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip())
201
- description = re.sub(r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip())
202
- emoji = ":open_file_folder:"
203
-
204
- if "aprsd-" not in package or "-plugin" not in package:
197
+ table.add_column('Plugin Package Name', style='cyan', no_wrap=True)
198
+ table.add_column('Description', style='yellow')
199
+ table.add_column('Version', style='yellow', justify='center')
200
+ table.add_column('Released', style='bold green', justify='center')
201
+ table.add_column('Installed?', style='red', justify='center')
202
+ emoji = ':open_file_folder:'
203
+ for package in packages:
204
+ link = package['info']['package_url']
205
+ version = package['info']['version']
206
+ package_name = package['info']['name']
207
+ description = package['info']['summary']
208
+ created = package['releases'][version][0]['upload_time']
209
+
210
+ if 'aprsd-' not in package_name or '-plugin' not in package_name:
205
211
  continue
206
212
 
207
- under = package.replace("-", "_")
208
- if under in installed_plugins:
209
- installed = "Yes"
210
- else:
211
- installed = "No"
212
-
213
+ under = package_name.replace('-', '_')
214
+ installed = 'Yes' if under in installed_plugins else 'No'
213
215
  table.add_row(
214
- f"[link={link}]{emoji}[/link] {package}",
215
- description, version, created, installed,
216
+ f'[link={link}]{emoji}[/link] {package_name}',
217
+ description,
218
+ version,
219
+ created,
220
+ installed,
216
221
  )
217
222
 
218
- console.print("\n")
223
+ console.print('\n')
219
224
  console.print(table)
220
225
 
221
226
 
222
227
  def show_pypi_extensions(installed_extensions, console):
223
- snippets = _get_pypi_packages()
228
+ packages = _get_pypi_packages()
224
229
 
225
230
  title = Text.assemble(
226
- ("Pypi.org APRSD Installable Extension Packages\n\n", "bold magenta"),
227
- ("Install any of the following extensions by running\n", "bold yellow"),
228
- ("'pip install ", "bold white"),
229
- ("<Plugin Package Name>'", "cyan"),
231
+ ('Pypi.org APRSD Installable Extension Packages\n\n', 'bold magenta'),
232
+ ('Install any of the following extensions by running\n', 'bold yellow'),
233
+ ("'pip install ", 'bold white'),
234
+ ("<Plugin Package Name>'", 'cyan'),
230
235
  )
231
236
  table = Table(title=title)
232
- table.add_column("Extension Package Name", style="cyan", no_wrap=True)
233
- table.add_column("Description", style="yellow")
234
- table.add_column("Version", style="yellow", justify="center")
235
- table.add_column("Released", style="bold green", justify="center")
236
- table.add_column("Installed?", style="red", justify="center")
237
- for snippet in snippets:
238
- link = urljoin(PYPI_URL, snippet.get("href"))
239
- package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
240
- version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
241
- created = re.sub(r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip())
242
- description = re.sub(r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip())
243
- emoji = ":open_file_folder:"
244
-
245
- if "aprsd-" not in package or "-extension" not in package:
237
+ table.add_column('Extension Package Name', style='cyan', no_wrap=True)
238
+ table.add_column('Description', style='yellow')
239
+ table.add_column('Version', style='yellow', justify='center')
240
+ table.add_column('Released', style='bold green', justify='center')
241
+ table.add_column('Installed?', style='red', justify='center')
242
+ emoji = ':open_file_folder:'
243
+
244
+ for package in packages:
245
+ link = package['info']['package_url']
246
+ version = package['info']['version']
247
+ package_name = package['info']['name']
248
+ description = package['info']['summary']
249
+ created = package['releases'][version][0]['upload_time']
250
+ if 'aprsd-' not in package_name or '-extension' not in package_name:
246
251
  continue
247
252
 
248
- under = package.replace("-", "_")
249
- if under in installed_extensions:
250
- installed = "Yes"
251
- else:
252
- installed = "No"
253
-
253
+ under = package_name.replace('-', '_')
254
+ installed = 'Yes' if under in installed_extensions else 'No'
254
255
  table.add_row(
255
- f"[link={link}]{emoji}[/link] {package}",
256
- description, version, created, installed,
256
+ f'[link={link}]{emoji}[/link] {package_name}',
257
+ description,
258
+ version,
259
+ created,
260
+ installed,
257
261
  )
258
262
 
259
- console.print("\n")
263
+ console.print('\n')
260
264
  console.print(table)
261
265
 
262
266
 
@@ -265,24 +269,24 @@ def show_installed_plugins(installed_plugins, console):
265
269
  return
266
270
 
267
271
  table = Table(
268
- title="[not italic]:snake:[/] [bold][magenta]APRSD Installed 3rd party Plugins [not italic]:snake:[/]",
272
+ title='[not italic]:snake:[/] [bold][magenta]APRSD Installed 3rd party Plugins [not italic]:snake:[/]',
269
273
  )
270
- table.add_column("Package Name", style=" bold white", no_wrap=True)
271
- table.add_column("Plugin Name", style="cyan", no_wrap=True)
272
- table.add_column("Version", style="yellow", justify="center")
273
- table.add_column("Type", style="bold green")
274
- table.add_column("Plugin Path", style="bold blue")
274
+ table.add_column('Package Name', style=' bold white', no_wrap=True)
275
+ table.add_column('Plugin Name', style='cyan', no_wrap=True)
276
+ table.add_column('Version', style='yellow', justify='center')
277
+ table.add_column('Type', style='bold green')
278
+ table.add_column('Plugin Path', style='bold blue')
275
279
  for name in installed_plugins:
276
280
  for plugin in installed_plugins[name]:
277
281
  table.add_row(
278
- name.replace("_", "-"),
279
- plugin["name"],
280
- plugin["version"],
281
- plugin_type(plugin["obj"]),
282
- plugin["path"],
282
+ name.replace('_', '-'),
283
+ plugin['name'],
284
+ plugin['version'],
285
+ plugin_type(plugin['obj']),
286
+ plugin['path'],
283
287
  )
284
288
 
285
- console.print("\n")
289
+ console.print('\n')
286
290
  console.print(table)
287
291
 
288
292
 
@@ -294,14 +298,14 @@ def list_plugins(ctx):
294
298
  """List the built in plugins available to APRSD."""
295
299
  console = Console()
296
300
 
297
- with console.status("Show Built-in Plugins") as status:
301
+ with console.status('Show Built-in Plugins') as status:
298
302
  show_built_in_plugins(console)
299
303
 
300
- status.update("Fetching pypi.org plugins")
304
+ status.update('Fetching pypi.org plugins')
301
305
  installed_plugins = get_installed_plugins()
302
306
  show_pypi_plugins(installed_plugins, console)
303
307
 
304
- status.update("Looking for installed APRSD plugins")
308
+ status.update('Looking for installed APRSD plugins')
305
309
  show_installed_plugins(installed_plugins, console)
306
310
 
307
311
 
@@ -313,7 +317,9 @@ def list_extensions(ctx):
313
317
  """List the built in plugins available to APRSD."""
314
318
  console = Console()
315
319
 
316
- with console.status("Show APRSD Extensions") as status:
317
- status.update("Fetching pypi.org APRSD Extensions")
320
+ with console.status('Show APRSD Extensions') as status:
321
+ status.update('Fetching pypi.org APRSD Extensions')
322
+
323
+ status.update('Looking for installed APRSD Extensions')
318
324
  installed_extensions = get_installed_extensions()
319
325
  show_pypi_extensions(installed_extensions, console)
aprsd/cmds/listen.py CHANGED
@@ -23,11 +23,10 @@ from aprsd.packets import collector as packet_collector
23
23
  from aprsd.packets import log as packet_log
24
24
  from aprsd.packets import seen_list
25
25
  from aprsd.stats import collector
26
- from aprsd.threads import keep_alive, rx
26
+ from aprsd.threads import keepalive, rx
27
27
  from aprsd.threads import stats as stats_thread
28
28
  from aprsd.threads.aprsd import APRSDThread
29
29
 
30
-
31
30
  # setup the global logger
32
31
  # log.basicConfig(level=log.DEBUG) # level=10
33
32
  LOG = logging.getLogger("APRSD")
@@ -51,8 +50,12 @@ def signal_handler(sig, frame):
51
50
 
52
51
  class APRSDListenThread(rx.APRSDRXThread):
53
52
  def __init__(
54
- self, packet_queue, packet_filter=None, plugin_manager=None,
55
- enabled_plugins=[], log_packets=False,
53
+ self,
54
+ packet_queue,
55
+ packet_filter=None,
56
+ plugin_manager=None,
57
+ enabled_plugins=[],
58
+ log_packets=False,
56
59
  ):
57
60
  super().__init__(packet_queue)
58
61
  self.packet_filter = packet_filter
@@ -88,7 +91,6 @@ class APRSDListenThread(rx.APRSDRXThread):
88
91
  self.plugin_manager.run(packet)
89
92
  else:
90
93
  if self.log_packets:
91
- LOG.error("PISS")
92
94
  packet_log.log(packet)
93
95
  if self.plugin_manager:
94
96
  # Don't do anything with the reply.
@@ -111,6 +113,7 @@ class ListenStatsThread(APRSDThread):
111
113
  stats_json = collector.Collector().collect()
112
114
  stats = stats_json["PacketList"]
113
115
  total_rx = stats["rx"]
116
+ packet_count = len(stats["packets"])
114
117
  rx_delta = total_rx - self._last_total_rx
115
118
  rate = rx_delta / 10
116
119
 
@@ -118,7 +121,8 @@ class ListenStatsThread(APRSDThread):
118
121
  LOGU.opt(colors=True).info(
119
122
  f"<green>RX Rate: {rate} pps</green> "
120
123
  f"<yellow>Total RX: {total_rx}</yellow> "
121
- f"<red>RX Last 10 secs: {rx_delta}</red>",
124
+ f"<red>RX Last 10 secs: {rx_delta}</red> "
125
+ f"<white>Packets in PacketList: {packet_count}</white>",
122
126
  )
123
127
  self._last_total_rx = total_rx
124
128
 
@@ -266,7 +270,7 @@ def listen(
266
270
  LOG.debug(f"Filter by '{filter}'")
267
271
  aprs_client.set_filter(filter)
268
272
 
269
- keepalive = keep_alive.KeepAliveThread()
273
+ keepalive_thread = keepalive.KeepAliveThread()
270
274
 
271
275
  if not CONF.enable_seen_list:
272
276
  # just deregister the class from the packet collector
@@ -310,9 +314,9 @@ def listen(
310
314
  listen_stats = ListenStatsThread()
311
315
  listen_stats.start()
312
316
 
313
- keepalive.start()
317
+ keepalive_thread.start()
314
318
  LOG.debug("keepalive Join")
315
- keepalive.join()
319
+ keepalive_thread.join()
316
320
  LOG.debug("listen_thread Join")
317
321
  listen_thread.join()
318
322
  stats.join()
aprsd/cmds/server.py CHANGED
@@ -6,22 +6,54 @@ import click
6
6
  from oslo_config import cfg
7
7
 
8
8
  import aprsd
9
- from aprsd import cli_helper
9
+ from aprsd import cli_helper, plugin, threads, utils
10
10
  from aprsd import main as aprsd_main
11
- from aprsd import plugin, threads, utils
12
11
  from aprsd.client import client_factory
13
12
  from aprsd.main import cli
14
13
  from aprsd.packets import collector as packet_collector
15
14
  from aprsd.packets import seen_list
16
- from aprsd.threads import keep_alive, log_monitor, registry, rx
15
+ from aprsd.threads import aprsd as aprsd_threads
16
+ from aprsd.threads import keepalive, registry, rx, tx
17
17
  from aprsd.threads import stats as stats_thread
18
- from aprsd.threads import tx
19
-
18
+ from aprsd.utils import singleton
20
19
 
21
20
  CONF = cfg.CONF
22
21
  LOG = logging.getLogger("APRSD")
23
22
 
24
23
 
24
+ @singleton
25
+ class ServerThreads:
26
+ """Registry for threads that the server command runs.
27
+
28
+ This enables extensions to register a thread to run during
29
+ the server command.
30
+
31
+ """
32
+
33
+ def __init__(self):
34
+ self.threads: list[aprsd_threads.APRSDThread] = []
35
+
36
+ def register(self, thread: aprsd_threads.APRSDThread):
37
+ if not isinstance(thread, aprsd_threads.APRSDThread):
38
+ raise TypeError(f"Thread {thread} is not an APRSDThread")
39
+ self.threads.append(thread)
40
+
41
+ def unregister(self, thread: aprsd_threads.APRSDThread):
42
+ if not isinstance(thread, aprsd_threads.APRSDThread):
43
+ raise TypeError(f"Thread {thread} is not an APRSDThread")
44
+ self.threads.remove(thread)
45
+
46
+ def start(self):
47
+ """Start all threads in the list."""
48
+ for thread in self.threads:
49
+ thread.start()
50
+
51
+ def join(self):
52
+ """Join all the threads in the list"""
53
+ for thread in self.threads:
54
+ thread.join()
55
+
56
+
25
57
  # main() ###
26
58
  @cli.command()
27
59
  @cli_helper.add_options(cli_helper.common_options)
@@ -41,6 +73,8 @@ def server(ctx, flush):
41
73
  signal.signal(signal.SIGINT, aprsd_main.signal_handler)
42
74
  signal.signal(signal.SIGTERM, aprsd_main.signal_handler)
43
75
 
76
+ server_threads = ServerThreads()
77
+
44
78
  level, msg = utils._check_version()
45
79
  if level:
46
80
  LOG.warning(msg)
@@ -110,36 +144,28 @@ def server(ctx, flush):
110
144
 
111
145
  # Now start all the main processing threads.
112
146
 
113
- keepalive = keep_alive.KeepAliveThread()
114
- keepalive.start()
115
-
116
- stats_store_thread = stats_thread.APRSDStatsStoreThread()
117
- stats_store_thread.start()
118
-
119
- rx_thread = rx.APRSDPluginRXThread(
120
- packet_queue=threads.packet_queue,
147
+ server_threads.register(keepalive.KeepAliveThread())
148
+ server_threads.register(stats_thread.APRSDStatsStoreThread())
149
+ server_threads.register(
150
+ rx.APRSDPluginRXThread(
151
+ packet_queue=threads.packet_queue,
152
+ ),
121
153
  )
122
- process_thread = rx.APRSDPluginProcessPacketThread(
123
- packet_queue=threads.packet_queue,
154
+ server_threads.register(
155
+ rx.APRSDPluginProcessPacketThread(
156
+ packet_queue=threads.packet_queue,
157
+ ),
124
158
  )
125
- rx_thread.start()
126
- process_thread.start()
127
159
 
128
160
  if CONF.enable_beacon:
129
161
  LOG.info("Beacon Enabled. Starting Beacon thread.")
130
- bcn_thread = tx.BeaconSendThread()
131
- bcn_thread.start()
162
+ server_threads.register(tx.BeaconSendThread())
132
163
 
133
164
  if CONF.aprs_registry.enabled:
134
165
  LOG.info("Registry Enabled. Starting Registry thread.")
135
- registry_thread = registry.APRSRegistryThread()
136
- registry_thread.start()
137
-
138
- if CONF.admin.web_enabled:
139
- log_monitor_thread = log_monitor.LogMonitorThread()
140
- log_monitor_thread.start()
166
+ server_threads.register(registry.APRSRegistryThread())
141
167
 
142
- rx_thread.join()
143
- process_thread.join()
168
+ server_threads.start()
169
+ server_threads.join()
144
170
 
145
171
  return 0
aprsd/conf/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from oslo_config import cfg
2
2
 
3
- from aprsd.conf import client, common, log, plugin_common, plugin_email
3
+ from aprsd.conf import client, common, log, plugin_common
4
4
 
5
5
 
6
6
  CONF = cfg.CONF
@@ -11,7 +11,6 @@ client.register_opts(CONF)
11
11
 
12
12
  # plugins
13
13
  plugin_common.register_opts(CONF)
14
- plugin_email.register_opts(CONF)
15
14
 
16
15
 
17
16
  def set_lib_defaults():
aprsd/conf/client.py CHANGED
@@ -4,7 +4,6 @@ The options for log setup
4
4
 
5
5
  from oslo_config import cfg
6
6
 
7
-
8
7
  DEFAULT_LOGIN = "NOCALL"
9
8
 
10
9
  aprs_group = cfg.OptGroup(
@@ -31,7 +30,7 @@ aprs_opts = [
31
30
  "enabled",
32
31
  default=True,
33
32
  help="Set enabled to False if there is no internet connectivity."
34
- "This is useful for a direwolf KISS aprs connection only.",
33
+ "This is useful for a direwolf KISS aprs connection only.",
35
34
  ),
36
35
  cfg.StrOpt(
37
36
  "login",
@@ -42,8 +41,8 @@ aprs_opts = [
42
41
  "password",
43
42
  secret=True,
44
43
  help="APRS Password "
45
- "Get the passcode for your callsign here: "
46
- "https://apps.magicbug.co.uk/passcode",
44
+ "Get the passcode for your callsign here: "
45
+ "https://apps.magicbug.co.uk/passcode",
47
46
  ),
48
47
  cfg.HostAddressOpt(
49
48
  "host",