# -*- coding: utf-8 -*-
############################################################################
#
# Copyright © 2013, 2014, 2015 OnlineGroups.net and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
############################################################################
# Standard libraries
from __future__ import (absolute_import, unicode_literals, print_function,
division)
from argparse import ArgumentParser # Standard in Python 2.7
from httplib import OK as HTTP_OK
from json import loads as load_json
from operator import itemgetter
import sys
from urlparse import urlparse
# Extra libraries
from blessings import Terminal
# GroupServer libraries
from gs.config.config import Config, ConfigError
from gs.form import post_multipart
# Local libraries
from .errorvals import exit_vals
[docs]class NotOk(Exception):
'''Raised when a web-hook does not return a 200 status code'''
def get_args(configFileName):
p = ArgumentParser(
description='Send the topic digests from GroupServer.',
epilog='Usually %(prog)s is called by cron(8) once a day.')
p.add_argument('url', metavar='url',
help='The URL for the GroupServer site.')
p.add_argument(
'-c', '--config', dest='config', default=configFileName, type=str,
help='The name of the GroupServer configuration file (default '
'"%(default)s") that contains the token that will be used to '
'authenticate the script when it tries to send the digests.')
p.add_argument(
'-i', '--instance', dest='instance', default='default', type=str,
help='The identifier of the GroupServer instance configuration to '
'use (default "%(default)s").')
p.add_argument(
'-v', '--verbose', dest='verbose', default=False,
action='store_true',
help='Turn on verbose output (feedback). Default %(default)s.')
retval = p.parse_args()
return retval
def get_token_from_config(configSet, configFileName):
config = Config(configSet, configFileName)
config.set_schema('webservice', {'token': str})
ws = config.get('webservice')
retval = ws['token']
if not retval:
m = 'The token was not set.'
raise ValueError(m)
return retval
#: The URL of the web-hook that returns (as a JSON blob) the list of groups
#: that need a digest.
DIGEST_GROUPS_URI = '/gs-group-messages-topic-digest-groups.html'
[docs]def get_digest_groups(hostname, token):
'''Get the list of groups to send the digest to.
:param str hostname: The name of the host to use.
:param str token: The token to use for authentication.
:raises NotOk: When the page does not return an HTTP 200 status code.
:returns: A list of 2-tuples ``(siteId, groupId)``, sorted alphabetically
:rtype: tuple'''
fields = {'token': token, 'get': '', }
status, reason, data = post_multipart(hostname, DIGEST_GROUPS_URI,
fields)
if status != HTTP_OK:
m = '{reason} ({status} <{host}>)'
msg = m.format(reason=reason, status=status, host=hostname)
raise NotOk(msg)
retval = load_json(data)
retval.sort(key=itemgetter(0, 1)) # Nicer when sorted by site & group
return retval
#: The URL of the web-hook used to send a digest to a group
SEND_DIGEST_URI = '/gs-group-messages-topic-digest-send.html'
[docs]def send_digest(hostname, siteId, groupId, token):
'''Send a digest for a particular group
:param str hostname: The name of the host to use.
:param str siteId: The identifier for the site that contains the group.
:param str groupId: The identifier for the group.
:param str token: The token to use for authentication.
:raises NotOk: When the page does not return an HTTP 200 status code.'''
fields = {
'form.siteId': siteId,
'form.groupId': groupId,
'form.token': token,
'form.actions.send': 'Send'}
status, reason, data = post_multipart(hostname, SEND_DIGEST_URI,
fields)
if status != HTTP_OK:
m = '{reason} ({status} <{host}>)'
msg = m.format(reason=reason, status=status, host=hostname)
raise NotOk(msg)
def show_progress(siteId, groupId, curr, total):
'''Show the progress for sending the digest
:param str siteId: The identifier for the site that contains the group.
:param str groupId: The identifier for the group.
:param int curr: The current index of the group.
:param int total: The total number of groups.
:func:`show_progress` displays the *verbose* feedback, including the name of
the site and group. A progress bar is also displayed if the terminal
supports it.'''
t = Terminal()
# Write the site and group
if curr > 0 and t.does_styling:
# Clear the line above (the progress bar)
sys.stdout.write(t.move_up + t.move_x(0) + t.clear_eol)
sys.stdout.flush()
m = 'Sending digest to {0} on {1}\n'
sys.stdout.write(t.white(m.format(groupId, siteId)))
# Display progress
if t.does_styling:
# Length of the bar = (width of term - the two brackets) * progress
p = int(((t.width - 2) * (curr / total)))
bar = '=' * (p + 1) # +1 because Python is 0-indexed
# Space at end = terminal width - bar width - brackets - 1
# (0-indexed)
space = ' ' * (t.width - p - 3)
sys.stdout.write(t.bold('[' + bar + space + ']\n'))
sys.stdout.flush()
def main(configFileName='etc/gsconfig.ini'):
args = get_args(configFileName)
try:
token = get_token_from_config(args.instance, args.config)
except ConfigError as ce:
m = 'Error with the configuration file "{config}":\n{error}\n'
msg = m.format(config=args.config, error=ce.message)
sys.stderr.write(msg)
sys.exit(exit_vals['config_error'])
parsedUrl = urlparse(args.url)
if not parsedUrl.hostname:
m = 'No host in the URL <{0}>\n'.format(args.url)
sys.stderr.write(m)
sys.exit(exit_vals['url_bung'])
hostname = parsedUrl.hostname
if args.verbose:
sys.stdout.write('Retrieving the list of groups\n')
sys.stdout.flush()
try:
groups = get_digest_groups(hostname, token)
except NotOk as no:
m = 'Error communicating with the server while recieving the '\
'list of groups to send the digest to:\n{message}\n'
msg = m.format(message=no)
sys.stderr.write(msg)
sys.exit(exit_vals['communication_failure'])
if args.verbose:
sys.stdout.write('Sending the digest to each group\n')
for i, info in enumerate(groups):
siteId, groupId = info
if args.verbose:
show_progress(siteId, groupId, i, len(groups))
try:
send_digest(hostname, siteId, groupId, token)
except NotOk, no:
m = 'Error communicating with the server while sending the '\
'digest to the group {0} on the site {1}:\n{2}\n'
msg = m.format(siteId, groupId, no)
sys.stderr.write(msg)
sys.exit(exit_vals['success'])
if __name__ == '__main__':
main()