comparison src/plugins/plugin_exp_command_export.py @ 2144:1d3f73e065e1

core, jp: component handling + client handling refactoring: - SàT can now handle components - plugin have now a "modes" key in PLUGIN_INFO where they declare if they can be used with clients and or components. They default to be client only. - components are really similar to clients, but with some changes in behaviour: * component has "entry point", which is a special plugin with a componentStart method, which is called just after component is connected * trigger end with a different suffixes (e.g. profileConnected vs profileConnectedComponent), so a plugin which manage both clients and components can have different workflow * for clients, only triggers of plugins handling client mode are launched * for components, only triggers of plugins needed in dependencies are launched. They all must handle component mode. * component have a sendHistory attribute (False by default) which can be set to True to allow saving sent messages into history * for convenience, "client" is still used in method even if it can now be a component * a new "component" boolean attribute tells if we have a component or a client * components have to add themselve Message protocol * roster and presence protocols are not added for components * component default port is 5347 (which is Prosody's default port) - asyncCreateProfile has been renamed for profileCreate, both to follow new naming convention and to prepare the transition to fully asynchronous bridge - createProfile has a new "component" attribute. When used to create a component, it must be set to a component entry point - jp: added --component argument to profile/create - disconnect bridge method is now asynchronous, this way frontends can know when disconnection is finished - new PI_* constants for PLUGIN_INFO values (not used everywhere yet) - client/component connection workflow has been moved to their classes instead of being a host methods - host.messageSend is now client.sendMessage, and former client.sendMessage is now client.sendMessageData. - identities are now handled in client.identities list, so it can be updated dynamically by plugins (in the future, frontends should be able to update them too through bridge) - profileConnecting* profileConnected* profileDisconnected* and getHandler now all use client instead of profile
author Goffi <goffi@goffi.org>
date Sun, 12 Feb 2017 17:55:43 +0100
parents a2bc5089c2eb
children 33c8c4973743
comparison
equal deleted inserted replaced
2143:c3cac21157d4 2144:1d3f73e065e1
39 } 39 }
40 40
41 class ExportCommandProtocol(protocol.ProcessProtocol): 41 class ExportCommandProtocol(protocol.ProcessProtocol):
42 """ Try to register an account with prosody """ 42 """ Try to register an account with prosody """
43 43
44 def __init__(self, parent, target, options, profile): 44 def __init__(self, parent, client, target, options):
45 self.parent = parent 45 self.parent = parent
46 self.target = target 46 self.target = target
47 self.options = options 47 self.options = options
48 self.profile = profile 48 self.client = client
49 49
50 def _clean(self, data): 50 def _clean(self, data):
51 if not data: 51 if not data:
52 log.error ("data should not be empty !") 52 log.error ("data should not be empty !")
53 return u"" 53 return u""
56 56
57 def connectionMade(self): 57 def connectionMade(self):
58 log.info("connectionMade :)") 58 log.info("connectionMade :)")
59 59
60 def outReceived(self, data): 60 def outReceived(self, data):
61 self.parent.host.messageSend(self.target, {'': self._clean(data)}, no_trigger=True, profile_key=self.profile) 61 self.client.sendMessage(self.target, {'': self._clean(data)}, no_trigger=True)
62 62
63 def errReceived(self, data): 63 def errReceived(self, data):
64 self.parent.host.messageSend(self.target, {'': self._clean(data)}, no_trigger=True, profile_key=self.profile) 64 self.client.sendMessage(self.target, {'': self._clean(data)}, no_trigger=True)
65 65
66 def processEnded(self, reason): 66 def processEnded(self, reason):
67 log.info (u"process finished: %d" % (reason.value.exitCode,)) 67 log.info (u"process finished: %d" % (reason.value.exitCode,))
68 self.parent.removeProcess(self.target, self) 68 self.parent.removeProcess(self.target, self)
69 69
79 return value.lower() == "true" 79 return value.lower() == "true"
80 80
81 81
82 class CommandExport(object): 82 class CommandExport(object):
83 """Command export plugin: export a command to an entity""" 83 """Command export plugin: export a command to an entity"""
84 #XXX: This plugin can be potentially dangerous if we don't trust entities linked 84 # XXX: This plugin can be potentially dangerous if we don't trust entities linked
85 # this is specially true if we have other triggers. 85 # this is specially true if we have other triggers.
86 # FIXME: spawned should be a client attribute, not a class one
86 87
87 def __init__(self, host): 88 def __init__(self, host):
88 log.info(_("Plugin command export initialization")) 89 log.info(_("Plugin command export initialization"))
89 self.host = host 90 self.host = host
90 self.spawned = {} # key = entity 91 self.spawned = {} # key = entity
94 def removeProcess(self, entity, process): 95 def removeProcess(self, entity, process):
95 """ Called when the process is finished 96 """ Called when the process is finished
96 @param entity: jid.JID attached to the process 97 @param entity: jid.JID attached to the process
97 @param process: process to remove""" 98 @param process: process to remove"""
98 try: 99 try:
99 processes_set = self.spawned[(entity, process.profile)] 100 processes_set = self.spawned[(entity, process.client.profile)]
100 processes_set.discard(process) 101 processes_set.discard(process)
101 if not processes_set: 102 if not processes_set:
102 del(self.spawned[(entity, process.profile)]) 103 del(self.spawned[(entity, process.client.profile)])
103 except ValueError: 104 except ValueError:
104 pass 105 pass
105 106
106 def MessageReceivedTrigger(self, client, message_elt, post_treat): 107 def MessageReceivedTrigger(self, client, message_elt, post_treat):
107 """ Check if source is linked and repeat message, else do nothing """ 108 """ Check if source is linked and repeat message, else do nothing """
138 - exclusive: if set, skip all other triggers 139 - exclusive: if set, skip all other triggers
139 - loop: if set, restart the command once terminated #TODO 140 - loop: if set, restart the command once terminated #TODO
140 - pty: if set, launch in a pseudo terminal 141 - pty: if set, launch in a pseudo terminal
141 - continue: continue normal MessageReceived handling 142 - continue: continue normal MessageReceived handling
142 """ 143 """
143 profile = self.host.memory.getProfileName(profile_key) 144 client = self.host.getClient(profile_key)
144 if not profile:
145 log.warning(u"Unknown profile [%s]" % (profile,))
146 return
147
148 for target in targets: 145 for target in targets:
149 try: 146 try:
150 _jid = jid.JID(target) 147 _jid = jid.JID(target)
151 if not _jid.user or not _jid.host: 148 if not _jid.user or not _jid.host:
152 raise jid.InvalidFormat 149 raise jid.InvalidFormat
153 _jid = _jid.userhostJID() 150 _jid = _jid.userhostJID()
154 except (RuntimeError, jid.InvalidFormat, AttributeError): 151 except (RuntimeError, jid.InvalidFormat, AttributeError):
155 log.info(u"invalid target ignored: %s" % (target,)) 152 log.info(u"invalid target ignored: %s" % (target,))
156 continue 153 continue
157 process_prot = ExportCommandProtocol(self, _jid, options, profile) 154 process_prot = ExportCommandProtocol(self, client, _jid, options)
158 self.spawned.setdefault((_jid, profile),set()).add(process_prot) 155 self.spawned.setdefault((_jid, client.profile),set()).add(process_prot)
159 reactor.spawnProcess(process_prot, command, args, usePTY = process_prot.boolOption('pty')) 156 reactor.spawnProcess(process_prot, command, args, usePTY = process_prot.boolOption('pty'))
160 157