dimples 1.6.2__tar.gz → 1.6.4__tar.gz
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.
- {dimples-1.6.2 → dimples-1.6.4}/PKG-INFO +1 -1
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/gatekeeper.py +50 -1
- {dimples-1.6.2 → dimples-1.6.4}/dimples/utils/__init__.py +2 -0
- dimples-1.6.4/dimples/utils/conf_item.py +240 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/utils/config.py +15 -168
- {dimples-1.6.2 → dimples-1.6.4}/dimples.egg-info/PKG-INFO +1 -1
- {dimples-1.6.2 → dimples-1.6.4}/dimples.egg-info/SOURCES.txt +1 -0
- {dimples-1.6.2 → dimples-1.6.4}/setup.py +1 -1
- {dimples-1.6.2 → dimples-1.6.4}/LICENSE +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/README.md +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/checker.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/checkpoint.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/compat/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/compat/loader.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/app/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/app/filter.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/app/group.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/app/handler.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/commands.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/creator.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/customized.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/group.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/grp_expel.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/grp_invite.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/grp_join.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/grp_query.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/grp_quit.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/grp_reset.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/grp_resign.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/cpu/handshake.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/facebook.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/messenger.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/network/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/network/session.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/network/state.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/network/transition.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/packer.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/processor.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/client/terminal.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/anonymous.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/ans.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/archivist.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/checker.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/compat/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/compat/address.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/compat/compatible.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/compat/compressor.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/compat/entity.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/compat/loader.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/compat/meta.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/compat/network.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/dbi/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/dbi/account.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/dbi/message.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/dbi/session.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/facebook.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/messenger.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/mkm/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/mkm/bot.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/mkm/station.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/mkm/utils.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/packer.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/processer.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/ans.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/app.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/block.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/groups.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/grp_admin.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/handshake.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/login.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/mute.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/password.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/report.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/roles.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/utils.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/protocol/version.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/queue.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/register.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/common/session.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/flexible.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/gate.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/mars.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/mtp.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/protocol/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/protocol/mars.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/protocol/ws.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/queue.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/seeker.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/session.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/conn/ws.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/account.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/base.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/document.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/group.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/group_history.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/group_keys.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/login.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/meta.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/private.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/station.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/dos/user.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/message.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/base.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/document.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/group.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/grp_history.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/grp_keys.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/login.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/message.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/meta.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/station.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/redis/user.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/session.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_base.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_cipherkey.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_document.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_group.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_group_history.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_group_keys.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_login.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_message.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_meta.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_private.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_station.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/database/t_user.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/edge/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/edge/messenger.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/edge/octopus.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/edge/shared.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/edge/start.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/emitter.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/admin.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/builder.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/delegate.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/emitter.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/helper.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/manager.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/packer.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/group/shared.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/register/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/register/base.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/register/ext.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/register/run.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/register/shared.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/checker.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/cpu/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/cpu/ans.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/cpu/creator.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/cpu/document.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/cpu/handshake.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/cpu/login.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/cpu/report.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/deliver.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/dis_roamer.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/dispatcher.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/facebook.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/messenger.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/packer.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/processor.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/push.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/session.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/session_center.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/server/trace.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/station/__init__.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/station/handler.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/station/shared.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/station/start.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/utils/cache.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/utils/checker.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/utils/digest.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/utils/http.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples/utils/log.py +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples.egg-info/dependency_links.txt +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples.egg-info/entry_points.txt +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples.egg-info/requires.txt +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/dimples.egg-info/top_level.txt +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/setup.cfg +0 -0
- {dimples-1.6.2 → dimples-1.6.4}/tests/test.py +0 -0
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
import socket
|
|
32
32
|
import time
|
|
33
33
|
import traceback
|
|
34
|
+
import weakref
|
|
34
35
|
from typing import Optional
|
|
35
36
|
|
|
36
37
|
from dimsdk import ReliableMessage
|
|
@@ -38,7 +39,8 @@ from dimsdk import ReliableMessage
|
|
|
38
39
|
from startrek.types import SocketAddress
|
|
39
40
|
from startrek import Channel, Hub
|
|
40
41
|
from startrek import BaseChannel
|
|
41
|
-
from startrek import Connection, ConnectionDelegate
|
|
42
|
+
from startrek import Connection, ConnectionDelegate
|
|
43
|
+
from startrek import BaseConnection, ActiveConnection
|
|
42
44
|
from startrek import Arrival, Departure
|
|
43
45
|
from startrek import Porter, PorterStatus, PorterDelegate
|
|
44
46
|
from startrek import SocketHelper
|
|
@@ -88,6 +90,22 @@ class StreamServerHub(ServerHub):
|
|
|
88
90
|
remote: SocketAddress, local: Optional[SocketAddress]) -> Optional[Connection]:
|
|
89
91
|
return super()._remove_connection(connection=connection, remote=remote, local=None)
|
|
90
92
|
|
|
93
|
+
# Override
|
|
94
|
+
def _create_connection(self, remote: SocketAddress, local: Optional[SocketAddress]) -> Optional[Connection]:
|
|
95
|
+
conn = ServerConnection(remote=remote, local=local)
|
|
96
|
+
conn.delegate = self.delegate # gate
|
|
97
|
+
return conn
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ServerConnection(BaseConnection):
|
|
101
|
+
|
|
102
|
+
# Override
|
|
103
|
+
async def start(self, hub: Hub):
|
|
104
|
+
# 1. start state machine
|
|
105
|
+
await self._start_machine()
|
|
106
|
+
# 2. open channel via the hub
|
|
107
|
+
await self._open_channel(hub=hub)
|
|
108
|
+
|
|
91
109
|
|
|
92
110
|
class StreamClientHub(ClientHub):
|
|
93
111
|
|
|
@@ -122,6 +140,37 @@ class StreamClientHub(ClientHub):
|
|
|
122
140
|
remote: SocketAddress, local: Optional[SocketAddress]) -> Optional[Connection]:
|
|
123
141
|
return super()._remove_connection(connection=connection, remote=remote, local=None)
|
|
124
142
|
|
|
143
|
+
# Override
|
|
144
|
+
def _create_connection(self, remote: SocketAddress, local: Optional[SocketAddress]) -> Optional[Connection]:
|
|
145
|
+
conn = ClientConnection(remote=remote, local=local)
|
|
146
|
+
conn.delegate = self.delegate # gate
|
|
147
|
+
return conn
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ClientConnection(ActiveConnection):
|
|
151
|
+
|
|
152
|
+
def __init__(self, remote: SocketAddress, local: Optional[SocketAddress]):
|
|
153
|
+
super().__init__(remote=remote, local=local)
|
|
154
|
+
self.__hub_ref = None
|
|
155
|
+
|
|
156
|
+
@property # Override
|
|
157
|
+
def hub(self) -> Optional[Hub]:
|
|
158
|
+
ref = self.__hub_ref
|
|
159
|
+
if ref is not None:
|
|
160
|
+
return ref()
|
|
161
|
+
|
|
162
|
+
# Override
|
|
163
|
+
async def start(self, hub: Hub):
|
|
164
|
+
# 1. start state machine
|
|
165
|
+
await self._start_machine()
|
|
166
|
+
# 2. open channel via the hub
|
|
167
|
+
await self._open_channel(hub=hub)
|
|
168
|
+
# 3. weak reference for the hub
|
|
169
|
+
self.__hub_ref = weakref.ref(hub)
|
|
170
|
+
# 4. start an async task to check channel
|
|
171
|
+
Runner.async_task(coro=self.run())
|
|
172
|
+
# await self.run()
|
|
173
|
+
|
|
125
174
|
|
|
126
175
|
def reset_send_buffer_size(conn: Connection = None, sock: socket.socket = None) -> bool:
|
|
127
176
|
if sock is None:
|
|
@@ -64,6 +64,7 @@ from .cache import CachePool, SharedCacheManager
|
|
|
64
64
|
|
|
65
65
|
from .http import HttpSession, HttpClient
|
|
66
66
|
|
|
67
|
+
from .conf_item import IConfig, MessageTransferAgent, Supervisor, NeighborLoader
|
|
67
68
|
from .config import Config
|
|
68
69
|
|
|
69
70
|
|
|
@@ -147,6 +148,7 @@ __all__ = [
|
|
|
147
148
|
|
|
148
149
|
'FrequencyChecker', 'RecentTimeChecker',
|
|
149
150
|
|
|
151
|
+
'IConfig', 'MessageTransferAgent', 'Supervisor', 'NeighborLoader',
|
|
150
152
|
'Config',
|
|
151
153
|
|
|
152
154
|
'is_before',
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ==============================================================================
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2026 Albert Moky
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
# ==============================================================================
|
|
25
|
+
|
|
26
|
+
import weakref
|
|
27
|
+
from abc import ABC, abstractmethod
|
|
28
|
+
from typing import Optional, Any, Set, List, Dict
|
|
29
|
+
from typing import Iterable
|
|
30
|
+
|
|
31
|
+
from aiou import JSONFile
|
|
32
|
+
|
|
33
|
+
from dimsdk import JSON
|
|
34
|
+
from dimsdk import Dictionary
|
|
35
|
+
from dimsdk import EntityType, ID
|
|
36
|
+
from dimsdk import Facebook
|
|
37
|
+
|
|
38
|
+
from .log import Logging
|
|
39
|
+
from .http import HttpClient
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class IConfig(ABC):
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def get_section(self, section: str) -> Optional[Dict]:
|
|
46
|
+
raise NotImplementedError('NotImplemented')
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def get_integer(self, section: str, option: str) -> int:
|
|
50
|
+
raise NotImplementedError('NotImplemented')
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def get_boolean(self, section: str, option: str) -> bool:
|
|
54
|
+
raise NotImplementedError('NotImplemented')
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def get_string(self, section: str, option: str) -> Optional[str]:
|
|
58
|
+
raise NotImplementedError('NotImplemented')
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def get_list(self, section: str, option: str, separator: str = ',') -> Optional[List[str]]:
|
|
62
|
+
raise NotImplementedError('NotImplemented')
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class MessageTransferAgent(Dictionary):
|
|
66
|
+
""" DIM Network Node """
|
|
67
|
+
|
|
68
|
+
# Override
|
|
69
|
+
def __str__(self) -> str:
|
|
70
|
+
clazz = self.__class__.__name__
|
|
71
|
+
return '<%s host="%s" port=%d id="%s" />' % (clazz, self.host, self.port, self.identifier)
|
|
72
|
+
|
|
73
|
+
# Override
|
|
74
|
+
def __repr__(self) -> str:
|
|
75
|
+
clazz = self.__class__.__name__
|
|
76
|
+
return '<%s host="%s" port=%d id="%s" />' % (clazz, self.host, self.port, self.identifier)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def identifier(self) -> Optional[ID]:
|
|
80
|
+
string = self.get(key='did')
|
|
81
|
+
if string is None:
|
|
82
|
+
string = self.get(key='ID')
|
|
83
|
+
return ID.parse(identifier=string)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def host(self) -> str:
|
|
87
|
+
return self.get(key='host', default='')
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def port(self) -> int:
|
|
91
|
+
return self.get(key='port', default=0)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def parse(cls, node: Any):
|
|
95
|
+
if node is None:
|
|
96
|
+
return None
|
|
97
|
+
elif isinstance(node, MessageTransferAgent):
|
|
98
|
+
return node
|
|
99
|
+
elif isinstance(node, Dictionary):
|
|
100
|
+
node = node.to_dict()
|
|
101
|
+
host = node.get('host')
|
|
102
|
+
port = node.get('port')
|
|
103
|
+
if host is not None and port is not None and port > 0:
|
|
104
|
+
return cls(dictionary=node)
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def convert(cls, array: Iterable[Any]):
|
|
108
|
+
stations = []
|
|
109
|
+
for node in array:
|
|
110
|
+
item = cls.parse(node=node)
|
|
111
|
+
if item is not None:
|
|
112
|
+
stations.append(item)
|
|
113
|
+
return stations
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def revert(cls, stations: Iterable) -> List[Dict]:
|
|
117
|
+
array = []
|
|
118
|
+
for node in stations:
|
|
119
|
+
assert isinstance(node, MessageTransferAgent), 'station node error: %s' % node
|
|
120
|
+
info = node.to_dict()
|
|
121
|
+
array.append(info)
|
|
122
|
+
return array
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Supervisor(Logging):
|
|
126
|
+
""" System Administrators """
|
|
127
|
+
|
|
128
|
+
def __init__(self, facebook: Facebook):
|
|
129
|
+
super().__init__()
|
|
130
|
+
self.__facebook = weakref.ref(facebook)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def facebook(self) -> Optional[Facebook]:
|
|
134
|
+
ref = self.__facebook
|
|
135
|
+
if ref is not None:
|
|
136
|
+
return ref()
|
|
137
|
+
|
|
138
|
+
# noinspection PyMethodMayBeStatic
|
|
139
|
+
def check_user(self, identifier: ID) -> bool:
|
|
140
|
+
""" Filter user """
|
|
141
|
+
return identifier.type == EntityType.USER
|
|
142
|
+
|
|
143
|
+
# noinspection PyMethodMayBeStatic
|
|
144
|
+
def get_identifiers(self, config: IConfig, section: str, option: str) -> List[ID]:
|
|
145
|
+
""" Get ID list from config """
|
|
146
|
+
array = config.get_list(section=section, option=option)
|
|
147
|
+
return [] if array is None else ID.convert(array=array)
|
|
148
|
+
|
|
149
|
+
async def get_users(self, config: IConfig, section: str = 'system', option: str = 'supervisors') -> Set[ID]:
|
|
150
|
+
""" Get system administrators from config """
|
|
151
|
+
all_users = set()
|
|
152
|
+
array = self.get_identifiers(config=config, section=section, option=option)
|
|
153
|
+
if array is None or len(array) == 0:
|
|
154
|
+
return all_users
|
|
155
|
+
facebook = self.facebook
|
|
156
|
+
if facebook is None:
|
|
157
|
+
# only filter user
|
|
158
|
+
for item in array:
|
|
159
|
+
if self.check_user(identifier=item):
|
|
160
|
+
all_users.add(item)
|
|
161
|
+
return all_users
|
|
162
|
+
# extract group members
|
|
163
|
+
for item in array:
|
|
164
|
+
if item.is_user:
|
|
165
|
+
if self.check_user(identifier=item):
|
|
166
|
+
all_users.add(item)
|
|
167
|
+
continue
|
|
168
|
+
assert item.is_group, 'group ID error: %s' % item
|
|
169
|
+
group_members = await facebook.get_members(identifier=item)
|
|
170
|
+
if group_members is None or len(group_members) == 0:
|
|
171
|
+
self.warning(msg='failed to get members for group: %s' % item)
|
|
172
|
+
continue
|
|
173
|
+
for member in group_members:
|
|
174
|
+
if self.check_user(identifier=member):
|
|
175
|
+
all_users.add(member)
|
|
176
|
+
return all_users
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class NeighborLoader(Logging):
|
|
180
|
+
|
|
181
|
+
def __init__(self):
|
|
182
|
+
super().__init__()
|
|
183
|
+
self.__http = HttpClient()
|
|
184
|
+
|
|
185
|
+
async def load_stations(self, config: IConfig) -> Optional[List[MessageTransferAgent]]:
|
|
186
|
+
# check remote URL
|
|
187
|
+
source = config.get_string(section='neighbors', option='source')
|
|
188
|
+
if source is None:
|
|
189
|
+
stations = None
|
|
190
|
+
else:
|
|
191
|
+
stations = await self._download_stations(url=source)
|
|
192
|
+
# check local path
|
|
193
|
+
output = config.get_string(section='neighbors', option='output')
|
|
194
|
+
if output is None:
|
|
195
|
+
self.warning(msg='neighbors path not set')
|
|
196
|
+
elif stations is None:
|
|
197
|
+
stations = await self._load_stations(path=output)
|
|
198
|
+
else:
|
|
199
|
+
await self._save_stations(stations=stations, path=output)
|
|
200
|
+
# OK
|
|
201
|
+
return stations
|
|
202
|
+
|
|
203
|
+
async def _download_stations(self, url: str) -> Optional[List[MessageTransferAgent]]:
|
|
204
|
+
self.info(msg='downloading stations: %s' % url)
|
|
205
|
+
http = self.__http
|
|
206
|
+
try:
|
|
207
|
+
response = http.cache_get(url=url)
|
|
208
|
+
if response is None or response.status_code != 200:
|
|
209
|
+
self.error(msg='failed to get URL: %s response: %s' % (url, response))
|
|
210
|
+
return None
|
|
211
|
+
else:
|
|
212
|
+
text = response.text
|
|
213
|
+
stations = JSON.decode(string=text)
|
|
214
|
+
except Exception as error:
|
|
215
|
+
self.error(msg='failed to download stations: %s, %s' % (error, url))
|
|
216
|
+
return None
|
|
217
|
+
if isinstance(stations, Dict):
|
|
218
|
+
stations = stations.get('stations')
|
|
219
|
+
if isinstance(stations, List):
|
|
220
|
+
return MessageTransferAgent.convert(array=stations)
|
|
221
|
+
|
|
222
|
+
async def _load_stations(self, path: str) -> Optional[List[MessageTransferAgent]]:
|
|
223
|
+
self.info(msg='loading stations: %s' % path)
|
|
224
|
+
try:
|
|
225
|
+
stations = await JSONFile(path=path).read()
|
|
226
|
+
except Exception as error:
|
|
227
|
+
self.error(msg='failed to load stations: %s, %s' % (error, path))
|
|
228
|
+
return None
|
|
229
|
+
if isinstance(stations, Dict):
|
|
230
|
+
stations = stations.get('stations')
|
|
231
|
+
if isinstance(stations, List):
|
|
232
|
+
return MessageTransferAgent.convert(array=stations)
|
|
233
|
+
|
|
234
|
+
async def _save_stations(self, stations: List[MessageTransferAgent], path: str) -> bool:
|
|
235
|
+
info = MessageTransferAgent.revert(stations=stations)
|
|
236
|
+
self.info(msg='saving %d station(s): %s' % (len(stations), path))
|
|
237
|
+
try:
|
|
238
|
+
return await JSONFile(path=path).write(info)
|
|
239
|
+
except Exception as error:
|
|
240
|
+
self.error(msg='failed to save stations: %s, %s' % (error, path))
|
|
@@ -24,83 +24,20 @@
|
|
|
24
24
|
# ==============================================================================
|
|
25
25
|
|
|
26
26
|
from configparser import ConfigParser
|
|
27
|
-
from typing import Optional,
|
|
28
|
-
from typing import Iterable
|
|
27
|
+
from typing import Optional, List, Dict
|
|
29
28
|
|
|
30
|
-
from aiou import JSONFile
|
|
31
29
|
from aiou import RedisConnector
|
|
32
30
|
|
|
33
|
-
from dimsdk import JSON
|
|
34
|
-
from dimsdk import Dictionary
|
|
35
31
|
from dimsdk import ID
|
|
36
|
-
from dimsdk import Facebook
|
|
37
32
|
|
|
38
33
|
from .log import Log, Logging
|
|
39
|
-
from .
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class MessageTransferAgent(Dictionary):
|
|
43
|
-
""" DIM Network Node """
|
|
44
|
-
|
|
45
|
-
# Override
|
|
46
|
-
def __str__(self) -> str:
|
|
47
|
-
clazz = self.__class__.__name__
|
|
48
|
-
return '<%s host="%s" port=%d id="%s" />' % (clazz, self.host, self.port, self.identifier)
|
|
49
|
-
|
|
50
|
-
# Override
|
|
51
|
-
def __repr__(self) -> str:
|
|
52
|
-
clazz = self.__class__.__name__
|
|
53
|
-
return '<%s host="%s" port=%d id="%s" />' % (clazz, self.host, self.port, self.identifier)
|
|
54
|
-
|
|
55
|
-
@property
|
|
56
|
-
def identifier(self) -> Optional[ID]:
|
|
57
|
-
string = self.get(key='did')
|
|
58
|
-
if string is None:
|
|
59
|
-
string = self.get(key='ID')
|
|
60
|
-
return ID.parse(identifier=string)
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def host(self) -> str:
|
|
64
|
-
return self.get(key='host', default='')
|
|
65
|
-
|
|
66
|
-
@property
|
|
67
|
-
def port(self) -> int:
|
|
68
|
-
return self.get(key='port', default=0)
|
|
69
|
-
|
|
70
|
-
@classmethod
|
|
71
|
-
def parse(cls, node: Any):
|
|
72
|
-
if node is None:
|
|
73
|
-
return None
|
|
74
|
-
elif isinstance(node, MessageTransferAgent):
|
|
75
|
-
return node
|
|
76
|
-
elif isinstance(node, Dictionary):
|
|
77
|
-
node = node.to_dict()
|
|
78
|
-
host = node.get('host')
|
|
79
|
-
port = node.get('port')
|
|
80
|
-
if host is not None and port is not None and port > 0:
|
|
81
|
-
return cls(dictionary=node)
|
|
82
|
-
|
|
83
|
-
@classmethod
|
|
84
|
-
def convert(cls, array: Iterable[Any]):
|
|
85
|
-
stations = []
|
|
86
|
-
for node in array:
|
|
87
|
-
item = cls.parse(node=node)
|
|
88
|
-
if item is not None:
|
|
89
|
-
stations.append(item)
|
|
90
|
-
return stations
|
|
91
|
-
|
|
92
|
-
@classmethod
|
|
93
|
-
def revert(cls, stations: Iterable) -> List[Dict]:
|
|
94
|
-
array = []
|
|
95
|
-
for node in stations:
|
|
96
|
-
assert isinstance(node, MessageTransferAgent), 'station node error: %s' % node
|
|
97
|
-
info = node.to_dict()
|
|
98
|
-
array.append(info)
|
|
99
|
-
return array
|
|
34
|
+
from .conf_item import IConfig
|
|
35
|
+
from .conf_item import MessageTransferAgent
|
|
36
|
+
from .conf_item import NeighborLoader
|
|
100
37
|
|
|
101
38
|
|
|
102
39
|
# @Singleton
|
|
103
|
-
class Config(Logging):
|
|
40
|
+
class Config(IConfig, Logging):
|
|
104
41
|
""" Config info from ini file """
|
|
105
42
|
|
|
106
43
|
def __init__(self):
|
|
@@ -149,11 +86,13 @@ class Config(Logging):
|
|
|
149
86
|
def __repr__(self) -> str:
|
|
150
87
|
return 'Config: %s' % self.to_dict()
|
|
151
88
|
|
|
89
|
+
# Override
|
|
152
90
|
def get_section(self, section: str) -> Optional[Dict]:
|
|
153
91
|
parser = self.__parser
|
|
154
92
|
if parser is not None:
|
|
155
93
|
return _section_options(parser=parser, section=section)
|
|
156
94
|
|
|
95
|
+
# Override
|
|
157
96
|
def get_integer(self, section: str, option: str) -> int:
|
|
158
97
|
parser = self.__parser
|
|
159
98
|
if parser is None:
|
|
@@ -164,6 +103,7 @@ class Config(Logging):
|
|
|
164
103
|
self.error(msg='failed to get integer: %s, %s, %s' % (section, option, error))
|
|
165
104
|
return 0
|
|
166
105
|
|
|
106
|
+
# Override
|
|
167
107
|
def get_boolean(self, section: str, option: str) -> bool:
|
|
168
108
|
parser = self.__parser
|
|
169
109
|
if parser is None:
|
|
@@ -173,6 +113,7 @@ class Config(Logging):
|
|
|
173
113
|
except Exception as error:
|
|
174
114
|
self.error(msg='failed to get boolean: %s, %s, %s' % (section, option, error))
|
|
175
115
|
|
|
116
|
+
# Override
|
|
176
117
|
def get_string(self, section: str, option: str) -> Optional[str]:
|
|
177
118
|
parser = self.__parser
|
|
178
119
|
if parser is None:
|
|
@@ -182,15 +123,12 @@ class Config(Logging):
|
|
|
182
123
|
except Exception as error:
|
|
183
124
|
self.error(msg='failed to get string : %s, %s, %s' % (section, option, error))
|
|
184
125
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return ID.parse(identifier=value)
|
|
188
|
-
|
|
189
|
-
def get_list(self, section: str, option: str, separator: str = ',') -> List[str]:
|
|
126
|
+
# Override
|
|
127
|
+
def get_list(self, section: str, option: str, separator: str = ',') -> Optional[List[str]]:
|
|
190
128
|
""" get str and separate to a list """
|
|
191
129
|
text = self.get_string(section=section, option=option)
|
|
192
130
|
if text is None:
|
|
193
|
-
return
|
|
131
|
+
return None
|
|
194
132
|
result = []
|
|
195
133
|
array = text.split(separator)
|
|
196
134
|
for item in array:
|
|
@@ -199,36 +137,9 @@ class Config(Logging):
|
|
|
199
137
|
result.append(string)
|
|
200
138
|
return result
|
|
201
139
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def get_identifiers(self, section: str, option: str) -> List[ID]:
|
|
207
|
-
array = self.get_list(section=section, option=option)
|
|
208
|
-
return ID.convert(array=array)
|
|
209
|
-
|
|
210
|
-
async def get_users(self, section: str, option: str, facebook: Facebook) -> List[ID]:
|
|
211
|
-
users = []
|
|
212
|
-
array = self.get_identifiers(section=section, option=option)
|
|
213
|
-
for item in array:
|
|
214
|
-
if item.is_user:
|
|
215
|
-
if item not in users:
|
|
216
|
-
users.append(item)
|
|
217
|
-
continue
|
|
218
|
-
# extract group members
|
|
219
|
-
members = await facebook.get_members(identifier=item)
|
|
220
|
-
for usr in members:
|
|
221
|
-
if usr not in users:
|
|
222
|
-
users.append(usr)
|
|
223
|
-
return users
|
|
224
|
-
|
|
225
|
-
async def get_supervisors(self, section: str = 'admin', option: str = 'supervisors',
|
|
226
|
-
facebook: Facebook = None) -> List[ID]:
|
|
227
|
-
""" extract group members when facebook available """
|
|
228
|
-
if facebook is None:
|
|
229
|
-
return self.get_identifiers(section=section, option=option)
|
|
230
|
-
else:
|
|
231
|
-
return await self.get_users(section=section, option=option, facebook=facebook)
|
|
140
|
+
def get_identifier(self, section: str, option: str) -> Optional[ID]:
|
|
141
|
+
value = self.get_string(section=section, option=option)
|
|
142
|
+
return ID.parse(identifier=value)
|
|
232
143
|
|
|
233
144
|
#
|
|
234
145
|
# database
|
|
@@ -339,70 +250,6 @@ class Config(Logging):
|
|
|
339
250
|
return neighbor_stations
|
|
340
251
|
|
|
341
252
|
|
|
342
|
-
class NeighborLoader(Logging):
|
|
343
|
-
|
|
344
|
-
def __init__(self):
|
|
345
|
-
super().__init__()
|
|
346
|
-
self.__http = HttpClient()
|
|
347
|
-
|
|
348
|
-
async def load_stations(self, config: Config) -> Optional[List[MessageTransferAgent]]:
|
|
349
|
-
# check remote URL
|
|
350
|
-
source = config.get_string(section='neighbors', option='source')
|
|
351
|
-
if source is None:
|
|
352
|
-
stations = None
|
|
353
|
-
else:
|
|
354
|
-
stations = await self._download_stations(url=source)
|
|
355
|
-
# check local path
|
|
356
|
-
output = config.get_string(section='neighbors', option='output')
|
|
357
|
-
if output is None:
|
|
358
|
-
self.warning(msg='neighbors path not set')
|
|
359
|
-
elif stations is None:
|
|
360
|
-
stations = await self._load_stations(path=output)
|
|
361
|
-
else:
|
|
362
|
-
await self._save_stations(stations=stations, path=output)
|
|
363
|
-
# OK
|
|
364
|
-
return stations
|
|
365
|
-
|
|
366
|
-
async def _download_stations(self, url: str) -> Optional[List[MessageTransferAgent]]:
|
|
367
|
-
self.info(msg='downloading stations: %s' % url)
|
|
368
|
-
http = self.__http
|
|
369
|
-
try:
|
|
370
|
-
response = http.cache_get(url=url)
|
|
371
|
-
if response is None or response.status_code != 200:
|
|
372
|
-
self.error(msg='failed to get URL: %s response: %s' % (url, response))
|
|
373
|
-
return None
|
|
374
|
-
else:
|
|
375
|
-
text = response.text
|
|
376
|
-
stations = JSON.decode(string=text)
|
|
377
|
-
except Exception as error:
|
|
378
|
-
self.error(msg='failed to download stations: %s, %s' % (error, url))
|
|
379
|
-
return None
|
|
380
|
-
if isinstance(stations, Dict):
|
|
381
|
-
stations = stations.get('stations')
|
|
382
|
-
if isinstance(stations, List):
|
|
383
|
-
return MessageTransferAgent.convert(array=stations)
|
|
384
|
-
|
|
385
|
-
async def _load_stations(self, path: str) -> Optional[List[MessageTransferAgent]]:
|
|
386
|
-
self.info(msg='loading stations: %s' % path)
|
|
387
|
-
try:
|
|
388
|
-
stations = await JSONFile(path=path).read()
|
|
389
|
-
except Exception as error:
|
|
390
|
-
self.error(msg='failed to load stations: %s, %s' % (error, path))
|
|
391
|
-
return None
|
|
392
|
-
if isinstance(stations, Dict):
|
|
393
|
-
stations = stations.get('stations')
|
|
394
|
-
if isinstance(stations, List):
|
|
395
|
-
return MessageTransferAgent.convert(array=stations)
|
|
396
|
-
|
|
397
|
-
async def _save_stations(self, stations: List[MessageTransferAgent], path: str) -> bool:
|
|
398
|
-
info = MessageTransferAgent.revert(stations=stations)
|
|
399
|
-
self.info(msg='saving %d station(s): %s' % (len(stations), path))
|
|
400
|
-
try:
|
|
401
|
-
return await JSONFile(path=path).write(info)
|
|
402
|
-
except Exception as error:
|
|
403
|
-
self.error(msg='failed to save stations: %s, %s' % (error, path))
|
|
404
|
-
|
|
405
|
-
|
|
406
253
|
def _update_sections(info: Dict, parser: ConfigParser) -> Dict:
|
|
407
254
|
sections = parser.sections()
|
|
408
255
|
for name in sections:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|