dimples 0.3.2__tar.gz → 0.3.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.
Files changed (148) hide show
  1. {dimples-0.3.2 → dimples-0.3.4}/PKG-INFO +1 -1
  2. {dimples-0.3.2 → dimples-0.3.4}/dimples/__init__.py +2 -0
  3. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/__init__.py +2 -0
  4. dimples-0.3.4/dimples/client/archivist.py +243 -0
  5. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/checkpoint.py +10 -6
  6. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_query.py +19 -0
  7. dimples-0.3.4/dimples/client/facebook.py +172 -0
  8. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/messenger.py +26 -155
  9. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/packer.py +51 -5
  10. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/processor.py +35 -11
  11. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/__init__.py +2 -0
  12. dimples-0.3.4/dimples/common/archivist.py +214 -0
  13. dimples-0.3.4/dimples/common/facebook.py +239 -0
  14. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/messenger.py +8 -64
  15. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/packer.py +71 -80
  16. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/login.py +18 -11
  17. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/gatekeeper.py +8 -1
  18. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/queue.py +19 -1
  19. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_login.py +1 -1
  20. {dimples-0.3.2 → dimples-0.3.4}/dimples/edge/octopus.py +6 -5
  21. {dimples-0.3.2 → dimples-0.3.4}/dimples/edge/shared.py +6 -2
  22. {dimples-0.3.2 → dimples-0.3.4}/dimples/group/delegate.py +27 -58
  23. {dimples-0.3.2 → dimples-0.3.4}/dimples/group/emitter.py +31 -1
  24. {dimples-0.3.2 → dimples-0.3.4}/dimples/group/helper.py +14 -2
  25. {dimples-0.3.2 → dimples-0.3.4}/dimples/group/manager.py +1 -1
  26. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/__init__.py +2 -0
  27. dimples-0.3.4/dimples/server/archivist.py +118 -0
  28. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/broadcast.py +25 -4
  29. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/handshake.py +6 -2
  30. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/login.py +4 -2
  31. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/report.py +1 -0
  32. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/dispatcher.py +3 -0
  33. dimples-0.3.4/dimples/server/messenger.py +106 -0
  34. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/packer.py +2 -13
  35. dimples-0.3.4/dimples/server/processor.py +278 -0
  36. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/session.py +4 -5
  37. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/trace.py +12 -7
  38. {dimples-0.3.2 → dimples-0.3.4}/dimples/station/handler.py +1 -17
  39. {dimples-0.3.2 → dimples-0.3.4}/dimples/station/shared.py +30 -2
  40. {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/__init__.py +34 -4
  41. {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/PKG-INFO +1 -1
  42. {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/SOURCES.txt +3 -1
  43. {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/requires.txt +2 -2
  44. {dimples-0.3.2 → dimples-0.3.4}/setup.py +3 -3
  45. dimples-0.3.2/dimples/client/facebook.py +0 -60
  46. dimples-0.3.2/dimples/common/facebook.py +0 -243
  47. dimples-0.3.2/dimples/server/messenger.py +0 -215
  48. dimples-0.3.2/dimples/server/processor.py +0 -132
  49. dimples-0.3.2/dimples/utils/checker.py +0 -104
  50. {dimples-0.3.2 → dimples-0.3.4}/README.md +0 -0
  51. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/__init__.py +0 -0
  52. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/commands.py +0 -0
  53. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/creator.py +0 -0
  54. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/group.py +0 -0
  55. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_expel.py +0 -0
  56. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_invite.py +0 -0
  57. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_join.py +0 -0
  58. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_quit.py +0 -0
  59. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_reset.py +0 -0
  60. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/grp_resign.py +0 -0
  61. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/cpu/handshake.py +0 -0
  62. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/network/__init__.py +0 -0
  63. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/network/session.py +0 -0
  64. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/network/state.py +0 -0
  65. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/network/transition.py +0 -0
  66. {dimples-0.3.2 → dimples-0.3.4}/dimples/client/terminal.py +0 -0
  67. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/anonymous.py +0 -0
  68. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/ans.py +0 -0
  69. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/__init__.py +0 -0
  70. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/btc.py +0 -0
  71. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/compatible.py +0 -0
  72. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/entity.py +0 -0
  73. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/meta.py +0 -0
  74. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/compat/network.py +0 -0
  75. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/dbi/__init__.py +0 -0
  76. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/dbi/account.py +0 -0
  77. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/dbi/message.py +0 -0
  78. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/dbi/session.py +0 -0
  79. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/__init__.py +0 -0
  80. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/ans.py +0 -0
  81. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/block.py +0 -0
  82. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/handshake.py +0 -0
  83. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/mute.py +0 -0
  84. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/protocol/report.py +0 -0
  85. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/register.py +0 -0
  86. {dimples-0.3.2 → dimples-0.3.4}/dimples/common/session.py +0 -0
  87. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/__init__.py +0 -0
  88. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/gate.py +0 -0
  89. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/mars.py +0 -0
  90. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/mtp.py +0 -0
  91. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/protocol/__init__.py +0 -0
  92. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/protocol/mars.py +0 -0
  93. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/protocol/ws.py +0 -0
  94. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/seeker.py +0 -0
  95. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/session.py +0 -0
  96. {dimples-0.3.2 → dimples-0.3.4}/dimples/conn/ws.py +0 -0
  97. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/__init__.py +0 -0
  98. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/account.py +0 -0
  99. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/__init__.py +0 -0
  100. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/base.py +0 -0
  101. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/document.py +0 -0
  102. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/group.py +0 -0
  103. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/group_history.py +0 -0
  104. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/group_keys.py +0 -0
  105. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/login.py +0 -0
  106. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/meta.py +0 -0
  107. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/private.py +0 -0
  108. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/station.py +0 -0
  109. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/dos/user.py +0 -0
  110. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/message.py +0 -0
  111. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/session.py +0 -0
  112. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_cipherkey.py +0 -0
  113. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_document.py +0 -0
  114. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_group.py +0 -0
  115. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_group_history.py +0 -0
  116. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_group_keys.py +0 -0
  117. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_message.py +0 -0
  118. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_meta.py +0 -0
  119. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_private.py +0 -0
  120. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_station.py +0 -0
  121. {dimples-0.3.2 → dimples-0.3.4}/dimples/database/t_user.py +0 -0
  122. {dimples-0.3.2 → dimples-0.3.4}/dimples/edge/__init__.py +0 -0
  123. {dimples-0.3.2 → dimples-0.3.4}/dimples/edge/start.py +0 -0
  124. {dimples-0.3.2 → dimples-0.3.4}/dimples/group/__init__.py +0 -0
  125. {dimples-0.3.2 → dimples-0.3.4}/dimples/group/admin.py +0 -0
  126. {dimples-0.3.2 → dimples-0.3.4}/dimples/group/builder.py +0 -0
  127. {dimples-0.3.2 → dimples-0.3.4}/dimples/group/packer.py +0 -0
  128. {dimples-0.3.2 → dimples-0.3.4}/dimples/register/__init__.py +0 -0
  129. {dimples-0.3.2 → dimples-0.3.4}/dimples/register/base.py +0 -0
  130. {dimples-0.3.2 → dimples-0.3.4}/dimples/register/ext.py +0 -0
  131. {dimples-0.3.2 → dimples-0.3.4}/dimples/register/run.py +0 -0
  132. {dimples-0.3.2 → dimples-0.3.4}/dimples/register/shared.py +0 -0
  133. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/__init__.py +0 -0
  134. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/ans.py +0 -0
  135. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/cpu/document.py +0 -0
  136. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/push.py +0 -0
  137. {dimples-0.3.2 → dimples-0.3.4}/dimples/server/session_center.py +0 -0
  138. {dimples-0.3.2 → dimples-0.3.4}/dimples/station/__init__.py +0 -0
  139. {dimples-0.3.2 → dimples-0.3.4}/dimples/station/start.py +0 -0
  140. {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/cache.py +0 -0
  141. {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/config.py +0 -0
  142. {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/dos.py +0 -0
  143. {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/log.py +0 -0
  144. {dimples-0.3.2 → dimples-0.3.4}/dimples/utils/singleton.py +0 -0
  145. {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/dependency_links.txt +0 -0
  146. {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/entry_points.txt +0 -0
  147. {dimples-0.3.2 → dimples-0.3.4}/dimples.egg-info/top_level.txt +0 -0
  148. {dimples-0.3.2 → dimples-0.3.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dimples
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: DIMP Library for Edges and Stations
5
5
  Home-page: https://github.com/dimchat/demo-py
6
6
  Author: Albert Moky
@@ -221,6 +221,7 @@ __all__ = [
221
221
  #
222
222
 
223
223
  'AddressNameService', 'CipherKeyDelegate',
224
+ 'Archivist',
224
225
  'Facebook', 'Messenger',
225
226
  'MessageProcessor', 'MessagePacker',
226
227
 
@@ -330,6 +331,7 @@ __all__ = [
330
331
  #
331
332
  'Anonymous', 'Register',
332
333
  'AddressNameServer', 'ANSFactory',
334
+ 'CommonArchivist',
333
335
  'CommonFacebook', 'CommonMessenger',
334
336
  'CommonMessagePacker', 'Transmitter',
335
337
  'Session',
@@ -32,6 +32,7 @@
32
32
  from .network import ClientSession
33
33
  from .network import SessionState
34
34
 
35
+ from .archivist import ClientArchivist
35
36
  from .facebook import ClientFacebook
36
37
  from .messenger import ClientMessenger
37
38
  from .packer import ClientMessagePacker
@@ -47,6 +48,7 @@ __all__ = [
47
48
  #
48
49
  'ClientSession', 'SessionState',
49
50
 
51
+ 'ClientArchivist',
50
52
  'ClientFacebook', 'ClientMessenger',
51
53
  'ClientMessagePacker',
52
54
  'ClientMessageProcessor', 'ClientContentProcessorCreator',
@@ -0,0 +1,243 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # DIM-SDK : Decentralized Instant Messaging Software Development Kit
4
+ #
5
+ # Written in 2023 by Moky <albert.moky@gmail.com>
6
+ #
7
+ # ==============================================================================
8
+ # MIT License
9
+ #
10
+ # Copyright (c) 2023 Albert Moky
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ # of this software and associated documentation files (the "Software"), to deal
14
+ # in the Software without restriction, including without limitation the rights
15
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ # copies of the Software, and to permit persons to whom the Software is
17
+ # furnished to do so, subject to the following conditions:
18
+ #
19
+ # The above copyright notice and this permission notice shall be included in all
20
+ # copies or substantial portions of the Software.
21
+ #
22
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ # SOFTWARE.
29
+ # ==============================================================================
30
+
31
+ from abc import ABC
32
+ from typing import Dict, List
33
+
34
+ from dimsdk import ID
35
+ from dimsdk import FrequencyChecker
36
+ from dimsdk import Document
37
+ from dimsdk import MetaCommand, DocumentCommand, GroupCommand, QueryCommand
38
+ from dimsdk import Station
39
+
40
+ from ..common import AccountDBI
41
+ from ..common import CommonArchivist
42
+ from ..common import CommonMessenger
43
+
44
+
45
+ def get_facebook(archivist: CommonArchivist):
46
+ facebook = archivist.facebook
47
+ if facebook is not None:
48
+ from .facebook import ClientFacebook
49
+ assert isinstance(facebook, ClientFacebook), 'facebook error: %s' % facebook
50
+ return facebook
51
+
52
+
53
+ def get_messenger(archivist: CommonArchivist):
54
+ messenger = archivist.messenger
55
+ if messenger is not None:
56
+ assert isinstance(messenger, CommonMessenger), 'messenger error: %s' % messenger
57
+ return messenger
58
+
59
+
60
+ class ClientArchivist(CommonArchivist, ABC):
61
+
62
+ # each respond will be expired after 10 minutes
63
+ RESPOND_EXPIRES = 600.0 # seconds
64
+
65
+ def __init__(self, database: AccountDBI):
66
+ super().__init__(database=database)
67
+ self.__document_responses = FrequencyChecker(expires=self.RESPOND_EXPIRES)
68
+ self.__last_active_members: Dict[ID, ID] = {} # group => member
69
+
70
+ # protected
71
+ def is_documents_respond_expired(self, identifier: ID, force: bool) -> bool:
72
+ return self.__document_responses.is_expired(key=identifier, force=force)
73
+
74
+ def set_last_active_member(self, member: ID, group: ID):
75
+ self.__last_active_members[group] = member
76
+
77
+ # Override
78
+ def query_meta(self, identifier: ID) -> bool:
79
+ if not self.is_meta_query_expired(identifier=identifier):
80
+ # query not expired yet
81
+ self.info(msg='meta query not expired yet: %s' % identifier)
82
+ return False
83
+ messenger = get_messenger(archivist=self)
84
+ if messenger is None:
85
+ self.warning(msg='messenger not ready yet')
86
+ return False
87
+ self.info(msg='querying meta for: %s' % identifier)
88
+ command = MetaCommand.query(identifier=identifier)
89
+ _, r_msg = messenger.send_content(sender=None, receiver=Station.ANY, content=command, priority=1)
90
+ return r_msg is not None
91
+
92
+ # Override
93
+ def query_documents(self, identifier: ID, documents: List[Document]) -> bool:
94
+ if not self.is_documents_query_expired(identifier=identifier):
95
+ # query not expired yet
96
+ self.info(msg='document query not expired yet: %s' % identifier)
97
+ return False
98
+ messenger = get_messenger(archivist=self)
99
+ if messenger is None:
100
+ self.warning(msg='messenger not ready yet')
101
+ return False
102
+ last_time = self.get_last_document_time(identifier=identifier, documents=documents)
103
+ self.info(msg='querying document for: %s, last time: %s' % (identifier, last_time))
104
+ command = DocumentCommand.query(identifier=identifier, last_time=last_time)
105
+ _, r_msg = messenger.send_content(sender=None, receiver=Station.ANY, content=command, priority=1)
106
+ return r_msg is not None
107
+
108
+ # Override
109
+ def query_members(self, group: ID, members: List[ID]) -> bool:
110
+ if not self.is_members_query_expired(group=group):
111
+ # query not expired yet
112
+ self.info('members query not expired yet: %s' % group)
113
+ return False
114
+ facebook = get_facebook(archivist=self)
115
+ messenger = get_messenger(archivist=self)
116
+ if facebook is None or messenger is None:
117
+ self.warning(msg='facebook messenger not ready yet')
118
+ return False
119
+ user = facebook.current_user
120
+ if user is None:
121
+ self.error(msg='failed to get current user')
122
+ return False
123
+ me = user.identifier
124
+ last_time = self.get_last_group_history_time(group=group)
125
+ self.info(msg='querying members for group: %s, last time: %s' % (group, last_time))
126
+ # build query command for group members
127
+ command = GroupCommand.query(group=group, last_time=last_time)
128
+ # 1. check group bots
129
+ ok = self.query_members_from_assistants(command=command, sender=me, group=group)
130
+ if ok:
131
+ return True
132
+ # 2. check administrators
133
+ ok = self.query_members_from_administrators(command=command, sender=me, group=group)
134
+ if ok:
135
+ return True
136
+ # 3. check group owner
137
+ ok = self.query_members_from_owner(command=command, sender=me, group=group)
138
+ if ok:
139
+ return True
140
+ # all failed, try last active member
141
+ last_member = self.__last_active_members.get(group)
142
+ if last_member is None:
143
+ r_msg = None
144
+ else:
145
+ self.info(msg='querying members from: %s, group: %s' % (last_member, group))
146
+ _, r_msg = messenger.send_content(sender=me, receiver=last_member, content=command, priority=1)
147
+ self.error(msg='group not ready: %s' % group)
148
+ return r_msg is not None
149
+
150
+ # protected
151
+ def query_members_from_assistants(self, command: QueryCommand, sender: ID, group: ID) -> bool:
152
+ facebook = get_facebook(archivist=self)
153
+ messenger = get_messenger(archivist=self)
154
+ if facebook is None or messenger is None:
155
+ self.warning(msg='facebook messenger not ready yet')
156
+ return False
157
+ bots = facebook.assistants(group)
158
+ if len(bots) == 0:
159
+ self.warning(msg='assistants not designated for group: %s' % group)
160
+ return False
161
+ success = 0
162
+ # querying members from bots
163
+ self.info(msg='querying members from bots: %s, group: %s' % (bots, group))
164
+ for receiver in bots:
165
+ if receiver == sender:
166
+ self.warning(msg='ignore cycled querying: %s, group: %s' % (receiver, group))
167
+ continue
168
+ _, r_msg = messenger.send_content(sender=sender, receiver=receiver, content=command, priority=1)
169
+ if r_msg is not None:
170
+ success += 1
171
+ if success == 0:
172
+ # failed
173
+ return False
174
+ last_member = self.__last_active_members.get(group)
175
+ if last_member is None or last_member in bots:
176
+ # last active member is a bot??
177
+ pass
178
+ else:
179
+ self.info(msg='querying members from: %s, group: %s' % (last_member, group))
180
+ messenger.send_content(sender=sender, receiver=last_member, content=command, priority=1)
181
+ return True
182
+
183
+ # protected
184
+ def query_members_from_administrators(self, command: QueryCommand, sender: ID, group: ID) -> bool:
185
+ facebook = get_facebook(archivist=self)
186
+ messenger = get_messenger(archivist=self)
187
+ if facebook is None or messenger is None:
188
+ self.warning(msg='facebook messenger not ready yet')
189
+ return False
190
+ admins = facebook.administrators(group)
191
+ if len(admins) == 0:
192
+ self.warning(msg='administrators not found for group: %s' % group)
193
+ return False
194
+ success = 0
195
+ # querying members from admins
196
+ self.info(msg='querying members from admins: %s, group: %s' % (admins, group))
197
+ for receiver in admins:
198
+ if receiver == sender:
199
+ self.warning(msg='ignore cycled querying: %s, group: %s' % (receiver, group))
200
+ continue
201
+ _, r_msg = messenger.send_content(sender=sender, receiver=receiver, content=command, priority=1)
202
+ if r_msg is not None:
203
+ success += 1
204
+ if success == 0:
205
+ # failed
206
+ return False
207
+ last_member = self.__last_active_members.get(group)
208
+ if last_member is None or last_member in admins:
209
+ # last active member is an admin, already queried
210
+ pass
211
+ else:
212
+ self.info(msg='querying members from: %s, group: %s' % (last_member, group))
213
+ messenger.send_content(sender=sender, receiver=last_member, content=command, priority=1)
214
+ return True
215
+
216
+ # protected
217
+ def query_members_from_owner(self, command: QueryCommand, sender: ID, group: ID) -> bool:
218
+ facebook = get_facebook(archivist=self)
219
+ messenger = get_messenger(archivist=self)
220
+ if facebook is None or messenger is None:
221
+ self.warning(msg='facebook messenger not ready yet')
222
+ return False
223
+ owner = facebook.owner(group)
224
+ if owner is None:
225
+ self.warning(msg='owner not found for group: %s' % group)
226
+ return False
227
+ elif owner == sender:
228
+ self.error(msg='you are the owner of group: %s' % group)
229
+ return False
230
+ # querying members from owner
231
+ self.info(msg='querying members from owner: %s, group: %s' % (owner, group))
232
+ _, r_msg = messenger.send_content(sender=sender, receiver=owner, content=command, priority=1)
233
+ if r_msg is None:
234
+ # failed
235
+ return False
236
+ last_member = self.__last_active_members.get(group)
237
+ if last_member is None or last_member == owner:
238
+ # last active member is the owner, already queried
239
+ pass
240
+ else:
241
+ self.info(msg='querying members from: %s, group: %s' % (last_member, group))
242
+ messenger.send_content(sender=sender, receiver=last_member, content=command, priority=1)
243
+ return True
@@ -40,7 +40,7 @@ class SigPool:
40
40
  def __init__(self):
41
41
  super().__init__()
42
42
  self._next_time = 0
43
- self.__caches: Dict[str, float] = {} # str(msg.signature) => timestamp
43
+ self.__caches: Dict[str, float] = {} # signature:receiver => timestamp
44
44
 
45
45
  def purge(self, now: DateTime):
46
46
  """ remove expired traces """
@@ -52,22 +52,26 @@ class SigPool:
52
52
  self._next_time = timestamp + 3600
53
53
  expired = timestamp - self.EXPIRES
54
54
  keys = set(self.__caches.keys())
55
- for sig in keys:
56
- msg_time = self.__caches.get(sig)
55
+ for tag in keys:
56
+ msg_time = self.__caches.get(tag)
57
57
  if msg_time is None or msg_time < expired:
58
- self.__caches.pop(sig, None)
58
+ self.__caches.pop(tag, None)
59
59
  return True
60
60
 
61
61
  def duplicated(self, msg: ReliableMessage) -> bool:
62
62
  """ check whether duplicated """
63
63
  sig = msg.get('signature')
64
64
  assert sig is not None, 'message error: %s' % msg
65
- cached = self.__caches.get(sig)
65
+ if len(sig) > 16:
66
+ sig = sig[-16:]
67
+ add = msg.receiver.address
68
+ tag = '%s:%s' % (sig, add)
69
+ cached = self.__caches.get(tag)
66
70
  if cached is None:
67
71
  # cache not found, create a new one with message time
68
72
  when = msg.time
69
73
  timestamp = 0 if when is None else when.timestamp
70
- self.__caches[sig] = timestamp
74
+ self.__caches[tag] = timestamp
71
75
  return False
72
76
  else:
73
77
  return True
@@ -78,6 +78,25 @@ class QueryCommandProcessor(GroupCommandProcessor):
78
78
  }
79
79
  })
80
80
 
81
+ # check last group time
82
+ query_time = content.last_time
83
+ if query_time is not None:
84
+ # check last group history time
85
+ archivist = self.facebook.archivist
86
+ last_time = archivist.get_last_group_history_time(group=group)
87
+ if last_time is None:
88
+ self.error(msg='group history error: %s' % group)
89
+ elif not last_time.after(query_time):
90
+ # group history not updated
91
+ text = 'Group history not updated.'
92
+ return self._respond_receipt(text=text, content=content, envelope=r_msg.envelope, extra={
93
+ 'template': 'Group history not updated: ${ID}, last time: ${time}',
94
+ 'replacements': {
95
+ 'ID': str(group),
96
+ 'time': last_time.timestamp,
97
+ }
98
+ })
99
+
81
100
  # 3. send newest group history commands
82
101
  ok = self.send_group_histories(group=group, receiver=sender)
83
102
  assert ok, 'failed to send history for group: %s => %s' % (group, sender)
@@ -0,0 +1,172 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ==============================================================================
3
+ # MIT License
4
+ #
5
+ # Copyright (c) 2023 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
+ """
27
+ Facebook for client
28
+ ~~~~~~~~~~~~~~~~~~~
29
+
30
+ Barrack for cache entities
31
+ """
32
+
33
+ from typing import Optional, List
34
+
35
+ from dimsdk import EntityType
36
+ from dimsdk import ID, Document, Bulletin
37
+ from dimsdk import BroadcastHelper
38
+
39
+ from ..common import CommonFacebook
40
+
41
+
42
+ class ClientFacebook(CommonFacebook):
43
+
44
+ # Override
45
+ def save_document(self, document: Document) -> bool:
46
+ ok = super().save_document(document=document)
47
+ if ok and isinstance(document, Bulletin):
48
+ # check administrators
49
+ array = document.get_property(key='administrators')
50
+ if array is not None:
51
+ group = document.identifier
52
+ assert group.is_group, 'group ID error: %s' % group
53
+ admins = ID.convert(array=array)
54
+ ok = self.save_administrators(administrators=admins, group=group)
55
+ return ok
56
+
57
+ #
58
+ # GroupDataSource
59
+ #
60
+
61
+ # Override
62
+ def founder(self, identifier: ID) -> Optional[ID]:
63
+ # check broadcast group
64
+ if identifier.is_broadcast:
65
+ # founder of broadcast group
66
+ return BroadcastHelper.broadcast_founder(group=identifier)
67
+ # check bulletin document
68
+ doc = self.bulletin(identifier=identifier)
69
+ if doc is None:
70
+ # the owner(founder) should be set in the bulletin document of group
71
+ return None
72
+ db = self.archivist
73
+ # check local storage
74
+ user = db.founder(identifier=identifier)
75
+ if user is not None:
76
+ # got from local storage
77
+ return user
78
+ # get from bulletin document
79
+ user = doc.founder
80
+ if user is None:
81
+ self.error(msg='founder not designated for group: %s' % identifier)
82
+ return user
83
+
84
+ # Override
85
+ def owner(self, identifier: ID) -> Optional[ID]:
86
+ # check broadcast group
87
+ if identifier.is_broadcast:
88
+ # owner of broadcast group
89
+ return BroadcastHelper.broadcast_owner(group=identifier)
90
+ # check bulletin document
91
+ doc = self.bulletin(identifier=identifier)
92
+ if doc is None:
93
+ # the owner(founder) should be set in the bulletin document of group
94
+ return None
95
+ db = self.archivist
96
+ # check local storage
97
+ user = db.owner(identifier=identifier)
98
+ if user is not None:
99
+ # got from local storage
100
+ return user
101
+ # check group type
102
+ if identifier.type == EntityType.GROUP:
103
+ # Polylogue's owner is its founder
104
+ user = db.founder(identifier=identifier)
105
+ if user is None:
106
+ user = doc.founder
107
+ if user is None:
108
+ self.error(msg='owner not found for group: %s' % identifier)
109
+ return user
110
+
111
+ # Override
112
+ def members(self, identifier: ID) -> List[ID]:
113
+ owner = self.owner(identifier=identifier)
114
+ if owner is None:
115
+ self.error(msg='group empty: %s' % identifier)
116
+ return []
117
+ db = self.archivist
118
+ # check local storage
119
+ users = db.members(identifier=identifier)
120
+ db.check_members(group=identifier, members=users)
121
+ if len(users) == 0:
122
+ users = [owner]
123
+ else:
124
+ assert users[0] == owner, 'group owner must be the first member: %s' % identifier
125
+ return users
126
+
127
+ # Override
128
+ def assistants(self, identifier: ID) -> List[ID]:
129
+ # check bulletin document
130
+ doc = self.bulletin(identifier=identifier)
131
+ if doc is None:
132
+ # the assistants should be set in the bulletin document of group
133
+ return []
134
+ db = self.archivist
135
+ # check local storage
136
+ bots = db.assistants(identifier=identifier)
137
+ if len(bots) > 0:
138
+ # got from local storage
139
+ return bots
140
+ # get from bulletin document
141
+ bots = doc.assistants
142
+ return [] if bots is None else bots
143
+
144
+ #
145
+ # Organizational Structure
146
+ #
147
+
148
+ def administrators(self, group: ID) -> List[ID]:
149
+ # check bulletin document
150
+ doc = self.bulletin(identifier=group)
151
+ if doc is None:
152
+ # the administrators should be set in the bulletin document
153
+ return []
154
+ db = self.archivist
155
+ # the 'administrators' should be saved into local storage
156
+ # when the newest bulletin document received,
157
+ # so we must get them from the local storage only,
158
+ # not from the bulletin document.
159
+ return db.administrators(group=group)
160
+
161
+ # protected
162
+ def save_administrators(self, administrators: List[ID], group: ID) -> bool:
163
+ db = self.archivist
164
+ return db.save_administrators(administrators, group=group)
165
+
166
+ # protected
167
+ def save_members(self, members: List[ID], group: ID) -> bool:
168
+ db = self.archivist
169
+ return db.save_members(members, group=group)
170
+
171
+
172
+ # TODO: ANS?