comparison sat_frontends/jp/cmd_roster.py @ 2562:26edcf3a30eb

core, setup: huge cleaning: - moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention - move twisted directory to root - removed all hacks from setup.py, and added missing dependencies, it is now clean - use https URL for website in setup.py - removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed - renamed sat.sh to sat and fixed its installation - added python_requires to specify Python version needed - replaced glib2reactor which use deprecated code by gtk3reactor sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author Goffi <goffi@goffi.org>
date Mon, 02 Apr 2018 19:44:50 +0200
parents frontends/src/jp/cmd_roster.py@0046283a285d
children 378188abe941
comparison
equal deleted inserted replaced
2561:bd30dc3ffe5a 2562:26edcf3a30eb
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # jp: a SAT command line tool
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
6 # Copyright (C) 2003-2016 Adrien Cossa (souliane@mailoo.org)
7
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
17
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 import base
22 from sat_frontends.jp.constants import Const as C
23 from sat.core.i18n import _
24
25 from twisted.words.protocols.jabber import jid
26 from collections import OrderedDict
27
28 __commands__ = ["Roster"]
29
30
31
32 class Purge(base.CommandBase):
33
34 def __init__(self, host):
35 super(Purge, self).__init__(host, 'purge', help=_('Purge the roster from its contacts with no subscription'))
36 self.need_loop = True
37
38 def add_parser_options(self):
39 self.parser.add_argument("--no_from", action="store_true", help=_("Also purge contacts with no 'from' subscription"))
40 self.parser.add_argument("--no_to", action="store_true", help=_("Also purge contacts with no 'to' subscription"))
41
42 def start(self):
43 self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error)
44
45 def error(self, failure):
46 print (_("Error while retrieving the contacts [%s]") % failure)
47 self.host.quit(1)
48
49 def ask_confirmation(self, no_sub, no_from, no_to):
50 """Ask the confirmation before removing contacts.
51
52 @param no_sub (list[unicode]): list of contacts with no subscription
53 @param no_from (list[unicode]): list of contacts with no 'from' subscription
54 @param no_to (list[unicode]): list of contacts with no 'to' subscription
55 @return bool
56 """
57 if no_sub:
58 print "There's no subscription between profile [%s] and the following contacts:" % self.host.profile
59 print " " + "\n ".join(no_sub)
60 if no_from:
61 print "There's no 'from' subscription between profile [%s] and the following contacts:" % self.host.profile
62 print " " + "\n ".join(no_from)
63 if no_to:
64 print "There's no 'to' subscription between profile [%s] and the following contacts:" % self.host.profile
65 print " " + "\n ".join(no_to)
66 message = "REMOVE them from profile [%s]'s roster" % self.host.profile
67 while True:
68 res = raw_input("%s (y/N)? " % message)
69 if not res or res.lower() == 'n':
70 return False
71 if res.lower() == 'y':
72 return True
73
74 def gotContacts(self, contacts):
75 """Process the list of contacts.
76
77 @param contacts(list[tuple]): list of contacts with their attributes and groups
78 """
79 no_sub, no_from, no_to = [], [], []
80 for contact, attrs, groups in contacts:
81 from_, to = C.bool(attrs["from"]), C.bool(attrs["to"])
82 if not from_:
83 if not to:
84 no_sub.append(contact)
85 elif self.args.no_from:
86 no_from.append(contact)
87 elif not to and self.args.no_to:
88 no_to.append(contact)
89 if not no_sub and not no_from and not no_to:
90 print "Nothing to do - there's a from and/or to subscription(s) between profile [%s] and each of its contacts" % self.host.profile
91 elif self.ask_confirmation(no_sub, no_from, no_to):
92 for contact in no_sub + no_from + no_to:
93 self.host.bridge.delContact(contact, profile_key=self.host.profile, callback=lambda dummy: None, errback=lambda failure: None)
94 self.host.quit()
95
96
97 class Stats(base.CommandBase):
98
99 def __init__(self, host):
100 super(Stats, self).__init__(host, 'stats', help=_('Show statistics about a roster'))
101 self.need_loop = True
102
103 def add_parser_options(self):
104 pass
105
106 def start(self):
107 self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error)
108
109 def error(self, failure):
110 print (_("Error while retrieving the contacts [%s]") % failure)
111 self.host.quit(1)
112
113 def gotContacts(self, contacts):
114 """Process the list of contacts.
115
116 @param contacts(list[tuple]): list of contacts with their attributes and groups
117 """
118 hosts = {}
119 unique_groups = set()
120 no_sub, no_from, no_to, no_group, total_group_subscription = 0, 0, 0, 0, 0
121 for contact, attrs, groups in contacts:
122 from_, to = C.bool(attrs["from"]), C.bool(attrs["to"])
123 if not from_:
124 if not to:
125 no_sub += 1
126 else:
127 no_from += 1
128 elif not to:
129 no_to += 1
130 host = jid.JID(contact).host
131 hosts.setdefault(host, 0)
132 hosts[host] += 1
133 if groups:
134 unique_groups.update(groups)
135 total_group_subscription += len(groups)
136 if not groups:
137 no_group += 1
138 hosts = OrderedDict(sorted(hosts.items(), key=lambda item:-item[1]))
139
140 print
141 print "Total number of contacts: %d" % len(contacts)
142 print "Number of different hosts: %d" % len(hosts)
143 print
144 for host, count in hosts.iteritems():
145 print "Contacts on {host}: {count} ({rate:.1f}%)".format(host=host, count=count, rate=100 * float(count) / len(contacts))
146 print
147 print "Contacts with no 'from' subscription: %d" % no_from
148 print "Contacts with no 'to' subscription: %d" % no_to
149 print "Contacts with no subscription at all: %d" % no_sub
150 print
151 print "Total number of groups: %d" % len(unique_groups)
152 try:
153 contacts_per_group = float(total_group_subscription) / len(unique_groups)
154 except ZeroDivisionError:
155 contacts_per_group = 0
156 print "Average contacts per group: {:.1f}".format(contacts_per_group)
157 try:
158 groups_per_contact = float(total_group_subscription) / len(contacts)
159 except ZeroDivisionError:
160 groups_per_contact = 0
161 print "Average groups' subscriptions per contact: {:.1f}".format(groups_per_contact)
162 print "Contacts not assigned to any group: %d" % no_group
163 self.host.quit()
164
165
166 class Get(base.CommandBase):
167
168 def __init__(self, host):
169 super(Get, self).__init__(host, 'get', help=_('Retrieve the roster contacts'))
170 self.need_loop = True
171
172 def add_parser_options(self):
173 self.parser.add_argument("--subscriptions", action="store_true", help=_("Show the contacts' subscriptions"))
174 self.parser.add_argument("--groups", action="store_true", help=_("Show the contacts' groups"))
175 self.parser.add_argument("--name", action="store_true", help=_("Show the contacts' names"))
176
177 def start(self):
178 self.host.bridge.getContacts(profile_key=self.host.profile, callback=self.gotContacts, errback=self.error)
179
180 def error(self, failure):
181 print (_("Error while retrieving the contacts [%s]") % failure)
182 self.host.quit(1)
183
184 def gotContacts(self, contacts):
185 """Process the list of contacts.
186
187 @param contacts(list[tuple]): list of contacts with their attributes and groups
188 """
189 field_count = 1 # only display the contact by default
190 if self.args.subscriptions:
191 field_count += 3 # ask, from, to
192 if self.args.name:
193 field_count += 1
194 if self.args.groups:
195 field_count += 1
196 for contact, attrs, groups in contacts:
197 args = [contact]
198 if self.args.subscriptions:
199 args.append("ask" if C.bool(attrs["ask"]) else "")
200 args.append("from" if C.bool(attrs["from"]) else "")
201 args.append("to" if C.bool(attrs["to"]) else "")
202 if self.args.name:
203 args.append(unicode(attrs.get("name", "")))
204 if self.args.groups:
205 args.append(u"\t".join(groups) if groups else "")
206 print u";".join(["{}"] * field_count).format(*args).encode("utf-8")
207 self.host.quit()
208
209
210 class Roster(base.CommandBase):
211 subcommands = (Get, Stats, Purge)
212
213 def __init__(self, host):
214 super(Roster, self).__init__(host, 'roster', use_profile=True, help=_("Manage an entity's roster"))