<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import argparse
import os
import sys
import traceback

from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
import irc.bot
import irc.logging
import irc.strings
import lxml.html
import requests

from db import DatabaseConnection
from model import Hostmask, Nickname, Consumption, Consumable, URL
from spotify_lookup import SpotifyLookup
from twitter_lookup import TwitterLookup

class BogBot(irc.bot.SingleServerIRCBot):
    def __init__(self, channel, nickname, realname, server, port=6667):
        irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, realname)
        self.channel = channel
        self.dbcon = DatabaseConnection()

    def on_disconnect(self, c, e):
        raise SystemExit()

    def on_welcome(self, c, e):
        c.join(self.channel)

    def on_nicknameinuse(self, c, e):
        c.nick(c.get_nickname() + "_")

    def on_privmsg(self, c, e):
        self.do_command(e, e.arguments[0])

    def on_pubmsg(self, srvcon, event):
        try:
            if event.arguments[0].strip().startswith("!"):
                self.do_command(event, event.arguments[0][1:])
            else:
                self.process_text(event)
        except Exception as e:
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            tb = traceback.format_list(traceback.extract_tb(exc_traceback)[-1:])[-1]
            tb = ''.join(tb.splitlines())
            msg = "%s: %s  %s" % (exc_type, e.message, tb)
            if len(msg) > 400:
                msg = "%s %s %s" % (msg[:340], "...", msg[-50:])
            self.connection.privmsg("jabr", msg)

    def process_text(self, event):
        message = event.arguments[0]
        if "http" in  message:
            # Add nick/user/host
            hostmask_id = self.add_or_update_hostmask(event.source)

            start = message.find("http")
            end = message.find(" ", start)
            if end == -1:
                url = message[start:]
            else:
                url = message[start:end]

            if "spotify.com" in url:
                spl = SpotifyLookup()
                spotify_meta = spl.lookup(url)
                if spotify_meta is not None:
                    self.connection.notice(event.target, spotify_meta)
                return
            elif "twitter.com" and "status" in url:
                twit_lookup = TwitterLookup()
                twit_meta = twit_lookup.compose_meta(url)
                if twit_meta is not None:
                    self.connection.notice(event.target, twit_meta)
                return

            redirect, idn, title = self._get_url_meta(url)
            if title is not None:
                if redirect is not None:
                    self.add_url(redirect, title, hostmask_id, event.target)
                else:
                    self.add_url(url, title, hostmask_id, event.target)

                url_meta = self._compose_url_meta_string(url, redirect,
                                                         idn, title)
                self.connection.notice(event.target, url_meta)

    def _get_url_meta(self, url):
        abort, redirect, idn = self._check_headers(url)
        if abort:
            return None

        doc = self._get_url_content(url)
        title = self._get_html_title(doc)
        if title is not None and title != "":
            return redirect, idn, title
        return

    def _compose_url_meta_string(self, url, redirect, idn, title):
        meta = ""
        if redirect is not None and idn is False:
            meta = "%s )> " % redirect

        if title is not None and title != "":
            meta = "%s%s" % (meta, title)
            return meta

    def _get_html_title(self, doc):
        """
        Parse the string representation ('document') of the web page.
        """
        parsed_doc = lxml.html.fromstring(doc)
        title = parsed_doc.find(".//title")
        if title is not None:
            title_stripped = ''.join(title.text.splitlines())
            return title_stripped.strip()

    def _check_headers(self, url):
        """
        Check size of URL content is within limit. Also check if URL and
        response URL are different, and if the response URL indicates
        that the original URL is a Internationalized Domain Name (IDN).
        """

        response = requests.head(url)
        if response.headers is not None:
            if "content-type" in response.headers:
                if "text/html" not in response.headers['content-type']:
                    self.connection.privmsg("jabr", "No 'text/html' in headers for %s" % url)
                    return True, None, None
            if "content-length" in response.headers:
                # 5.000.000 bytes ~= 5MB
                if int(response.headers['content-length']) > 5000000:
                    self.connection.privmsg("jabr", "Content length too long for %s" % url)
                    return True, None, None
        else:
            self.connection.privmsg("jabr", "No response headers for %s" % url)
            return True, None, None

        if url != response.url:
            if response.url.split('://')[1].startswith('xn--'):
                return False, response.url, True
            return False, response.url, False
        return False, None, False

    def _get_url_content(self, url):
        response = requests.get(url)
        if response.text and response.encoding is not None:
            return response.text.encode(response.encoding)

    def add_or_update_hostmask(self, hostmask_str):
        nick, user, host = self.parse_hostmask(hostmask_str)
        hostmask_id, nick_present = self.is_nick_in_hostmask(nick, user, host)

        if hostmask_id is not None:
            if nick_present:
                self.connection.privmsg("jabr", "Nickname, username and hostmask already registered.")
                return hostmask_id
            else:
                self.connection.privmsg("jabr", "Username and hostmask already registered; adding nick.")
                return self.dbcon.add_nick(nick, user, host)
        else:
            self.connection.privmsg("jabr", "Username and hostmask not registered; adding hostmask.")
            return self.dbcon.add_hostmask(nick, user, host)

    def add_consumption(self, hostmask_id, consumable_str, source=None):
        with self.dbcon.scoped_db_session() as session:
            consumable_qr = session.query(Consumable).\
                            filter(Consumable.name==consumable_str).all() # One?
            if len(consumable_qr) == 0:
                self.connection.privmsg("jabr", "Consumable not registered. Registering.")
                consumable = Consumable(consumable_str)
                session.add(consumable)
            elif len(consumable_qr) == 1:
                self.connection.privmsg("jabr", "The consumable was found in database.")
                consumable = consumable_qr[0]
            else:
                self.connection.privmsg("jabr","ERROR: Several consumables with same name!")

            consumption = Consumption(source, consumable)
            hostmask = session.query(Hostmask).get(hostmask_id)
            hostmask.consumption.append(consumption)

    def add_url(self, url, title, hostmask_id, channel=None):
        with self.dbcon.scoped_db_session() as session:
            url_qr = session.query(URL).filter(URL.url==url).all() # One?
            if len(url_qr) == 0:
                url_obj = URL()
                #self.connection.privmsg("jabr", type(url_obj.hostmask_id))

                url_obj.url = url
                url_obj.title = title
                url_obj.channel = channel
                url_obj.hostmask_id = hostmask_id
                session.add(url_obj)
                msg = "URL %s added by %d" % (url, hostmask_id)
                self.connection.privmsg("jabr", msg)
            elif len(url_qr) == 1:
                msg = "URL %s already exist" % url
                self.connection.privmsg("jabr", msg)
            else:
                msg = "ERROR: %d instances of URL (%s) in DB" % (len(url_qr), url)
                self.connection.privmsg("jabr", msg)

    def do_command(self, event, cmd):
        self.connection.privmsg("jabr", "%s requested command %s" % (event.source.nick, cmd))

        hostmask_id = self.add_or_update_hostmask(event.source)
        self.connection.privmsg("jabr", "Hostmask ID: %s" % hostmask_id)

        if cmd == "kaffe":
            self.add_consumption(hostmask_id, cmd, event.target)
            self.connection.privmsg(event.target, "Coffee added!")
        elif cmd == "brus":
            self.add_consumption(hostmask_id, cmd, event.target)
            self.connection.privmsg(event.target, "Brus added!")
        elif cmd == "halt" and event.source == "jabr!jorabra@cringer.pludre.net":
            self.die()
        elif cmd == "stats":
            for chname, chobj in self.channels.items():
                c.notice(nick, "--- Channel statistics ---")
                c.notice(nick, "Channel: " + chname)
                users = chobj.users()
                users.sort()
                c.notice(nick, "Users: " + ", ".join(users))
                opers = chobj.opers()
                opers.sort()
                c.notice(nick, "Opers: " + ", ".join(opers))
                voiced = chobj.voiced()
                voiced.sort()
                c.notice(nick, "Voiced: " + ", ".join(voiced))

    def parse_hostmask(self, hostmask):
        nick = hostmask.split('!', 1)[0]
        user_host = hostmask.split('!', 1)[1].split('@', 1)
        user = user_host[0]
        host = user_host[1]
        return nick, user, host

    def is_nick_in_hostmask(self, nick, user, host):
        with self.dbcon.scoped_ro_db_session() as session:
            try:
                hostmask = session.query(Hostmask).\
                            filter(Hostmask.username==user).\
                            filter(Hostmask.hostname==host).one()
            except MultipleResultsFound, e:
                self.connection.privmsg("jabr",
                    "Multiple hostmasks found for username and hostname. Should not be possible: %s" % e)
            except NoResultFound, e:
                return None, False

            if nick in (nickname.nickname for nickname in hostmask.nickname):
                return hostmask.id, True
            else:
                return hostmask.id, False

def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('server')
    parser.add_argument('channel')
    parser.add_argument('nickname')
    parser.add_argument('realname')
    parser.add_argument('-p', '--port', default=6667, type=int)
    irc.logging.add_arguments(parser)
    return parser.parse_args()

def main():

    args = get_args()
    irc.logging.setup(args)

    bot = BogBot(args.channel, args.nickname, args.realname, args.server, args.port)
    bot.start()

if __name__ == "__main__":
    main()

<commit_msg>Fix return values.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import argparse
import os
import sys
import traceback

from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
import irc.bot
import irc.logging
import irc.strings
import lxml.html
import requests

from db import DatabaseConnection
from model import Hostmask, Nickname, Consumption, Consumable, URL
from spotify_lookup import SpotifyLookup
from twitter_lookup import TwitterLookup

class BogBot(irc.bot.SingleServerIRCBot):
    def __init__(self, channel, nickname, realname, server, port=6667):
        irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, realname)
        self.channel = channel
        self.dbcon = DatabaseConnection()

    def on_disconnect(self, c, e):
        raise SystemExit()

    def on_welcome(self, c, e):
        c.join(self.channel)

    def on_nicknameinuse(self, c, e):
        c.nick(c.get_nickname() + "_")

    def on_privmsg(self, c, e):
        self.do_command(e, e.arguments[0])

    def on_pubmsg(self, srvcon, event):
        try:
            if event.arguments[0].strip().startswith("!"):
                self.do_command(event, event.arguments[0][1:])
            else:
                self.process_text(event)
        except Exception as e:
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            tb = traceback.format_list(traceback.extract_tb(exc_traceback)[-1:])[-1]
            tb = ''.join(tb.splitlines())
            msg = "%s: %s  %s" % (exc_type, e.message, tb)
            if len(msg) > 400:
                msg = "%s %s %s" % (msg[:340], "...", msg[-50:])
            self.connection.privmsg("jabr", msg)

    def process_text(self, event):
        message = event.arguments[0]
        if "http" in  message:
            # Add nick/user/host
            hostmask_id = self.add_or_update_hostmask(event.source)

            start = message.find("http")
            end = message.find(" ", start)
            if end == -1:
                url = message[start:]
            else:
                url = message[start:end]

            if "spotify.com" in url:
                spl = SpotifyLookup()
                spotify_meta = spl.lookup(url)
                if spotify_meta is not None:
                    self.connection.notice(event.target, spotify_meta)
                return
            elif "twitter.com" and "status" in url:
                twit_lookup = TwitterLookup()
                twit_meta = twit_lookup.compose_meta(url)
                if twit_meta is not None:
                    self.connection.notice(event.target, twit_meta)
                return

            redirect, idn, title = self._get_url_meta(url)
            if title is not None:
                if redirect is not None:
                    self.add_url(redirect, title, hostmask_id, event.target)
                else:
                    self.add_url(url, title, hostmask_id, event.target)

                url_meta = self._compose_url_meta_string(url, redirect,
                                                         idn, title)
                self.connection.notice(event.target, url_meta)

    def _get_url_meta(self, url):
        abort, redirect, idn = self._check_headers(url)
        if abort:
            return None, None, None

        doc = self._get_url_content(url)
        title = self._get_html_title(doc)
        if title is not None and title != "":
            return redirect, idn, title
        return None, None, None

    def _compose_url_meta_string(self, url, redirect, idn, title):
        meta = ""
        if redirect is not None and idn is False:
            meta = "%s )> " % redirect

        if title is not None and title != "":
            meta = "%s%s" % (meta, title)
            return meta

    def _get_html_title(self, doc):
        """
        Parse the string representation ('document') of the web page.
        """
        parsed_doc = lxml.html.fromstring(doc)
        title = parsed_doc.find(".//title")
        if title is not None:
            title_stripped = ''.join(title.text.splitlines())
            return title_stripped.strip()

    def _check_headers(self, url):
        """
        Check size of URL content is within limit. Also check if URL and
        response URL are different, and if the response URL indicates
        that the original URL is a Internationalized Domain Name (IDN).
        """

        response = requests.head(url)
        if response.headers is not None:
            if "content-type" in response.headers:
                if "text/html" not in response.headers['content-type']:
                    self.connection.privmsg("jabr", "No 'text/html' in headers for %s" % url)
                    return True, None, None
            if "content-length" in response.headers:
                # 5.000.000 bytes ~= 5MB
                if int(response.headers['content-length']) > 5000000:
                    self.connection.privmsg("jabr", "Content length too long for %s" % url)
                    return True, None, None
        else:
            self.connection.privmsg("jabr", "No response headers for %s" % url)
            return True, None, None

        if url != response.url:
            if response.url.split('://')[1].startswith('xn--'):
                return False, response.url, True
            return False, response.url, False
        return False, None, False

    def _get_url_content(self, url):
        response = requests.get(url)
        if response.text and response.encoding is not None:
            return response.text.encode(response.encoding)

    def add_or_update_hostmask(self, hostmask_str):
        nick, user, host = self.parse_hostmask(hostmask_str)
        hostmask_id, nick_present = self.is_nick_in_hostmask(nick, user, host)

        if hostmask_id is not None:
            if nick_present:
                self.connection.privmsg("jabr", "Nickname, username and hostmask already registered.")
                return hostmask_id
            else:
                self.connection.privmsg("jabr", "Username and hostmask already registered; adding nick.")
                return self.dbcon.add_nick(nick, user, host)
        else:
            self.connection.privmsg("jabr", "Username and hostmask not registered; adding hostmask.")
            return self.dbcon.add_hostmask(nick, user, host)

    def add_consumption(self, hostmask_id, consumable_str, source=None):
        with self.dbcon.scoped_db_session() as session:
            consumable_qr = session.query(Consumable).\
                            filter(Consumable.name==consumable_str).all() # One?
            if len(consumable_qr) == 0:
                self.connection.privmsg("jabr", "Consumable not registered. Registering.")
                consumable = Consumable(consumable_str)
                session.add(consumable)
            elif len(consumable_qr) == 1:
                self.connection.privmsg("jabr", "The consumable was found in database.")
                consumable = consumable_qr[0]
            else:
                self.connection.privmsg("jabr","ERROR: Several consumables with same name!")

            consumption = Consumption(source, consumable)
            hostmask = session.query(Hostmask).get(hostmask_id)
            hostmask.consumption.append(consumption)

    def add_url(self, url, title, hostmask_id, channel=None):
        with self.dbcon.scoped_db_session() as session:
            url_qr = session.query(URL).filter(URL.url==url).all() # One?
            if len(url_qr) == 0:
                url_obj = URL()
                #self.connection.privmsg("jabr", type(url_obj.hostmask_id))

                url_obj.url = url
                url_obj.title = title
                url_obj.channel = channel
                url_obj.hostmask_id = hostmask_id
                session.add(url_obj)
                msg = "URL %s added by %d" % (url, hostmask_id)
                self.connection.privmsg("jabr", msg)
            elif len(url_qr) == 1:
                msg = "URL %s already exist" % url
                self.connection.privmsg("jabr", msg)
            else:
                msg = "ERROR: %d instances of URL (%s) in DB" % (len(url_qr), url)
                self.connection.privmsg("jabr", msg)

    def do_command(self, event, cmd):
        self.connection.privmsg("jabr", "%s requested command %s" % (event.source.nick, cmd))

        hostmask_id = self.add_or_update_hostmask(event.source)
        self.connection.privmsg("jabr", "Hostmask ID: %s" % hostmask_id)

        if cmd == "kaffe":
            self.add_consumption(hostmask_id, cmd, event.target)
            self.connection.privmsg(event.target, "Coffee added!")
        elif cmd == "brus":
            self.add_consumption(hostmask_id, cmd, event.target)
            self.connection.privmsg(event.target, "Brus added!")
        elif cmd == "halt" and event.source == "jabr!jorabra@cringer.pludre.net":
            self.die()
        elif cmd == "stats":
            for chname, chobj in self.channels.items():
                c.notice(nick, "--- Channel statistics ---")
                c.notice(nick, "Channel: " + chname)
                users = chobj.users()
                users.sort()
                c.notice(nick, "Users: " + ", ".join(users))
                opers = chobj.opers()
                opers.sort()
                c.notice(nick, "Opers: " + ", ".join(opers))
                voiced = chobj.voiced()
                voiced.sort()
                c.notice(nick, "Voiced: " + ", ".join(voiced))

    def parse_hostmask(self, hostmask):
        nick = hostmask.split('!', 1)[0]
        user_host = hostmask.split('!', 1)[1].split('@', 1)
        user = user_host[0]
        host = user_host[1]
        return nick, user, host

    def is_nick_in_hostmask(self, nick, user, host):
        with self.dbcon.scoped_ro_db_session() as session:
            try:
                hostmask = session.query(Hostmask).\
                            filter(Hostmask.username==user).\
                            filter(Hostmask.hostname==host).one()
            except MultipleResultsFound, e:
                self.connection.privmsg("jabr",
                    "Multiple hostmasks found for username and hostname. Should not be possible: %s" % e)
            except NoResultFound, e:
                return None, False

            if nick in (nickname.nickname for nickname in hostmask.nickname):
                return hostmask.id, True
            else:
                return hostmask.id, False

def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('server')
    parser.add_argument('channel')
    parser.add_argument('nickname')
    parser.add_argument('realname')
    parser.add_argument('-p', '--port', default=6667, type=int)
    irc.logging.add_arguments(parser)
    return parser.parse_args()

def main():

    args = get_args()
    irc.logging.setup(args)

    bot = BogBot(args.channel, args.nickname, args.realname, args.server, args.port)
    bot.start()

if __name__ == "__main__":
    main()

<|endoftext|>


<commit_before># Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import contextlib
import errno
import functools
import os
import shutil
import subprocess
import sys
import tempfile
import threading
import time
import weakref

from oslo.config import cfg

from openstack.common import fileutils
from openstack.common.gettextutils import _  # noqa
from openstack.common import local
from openstack.common import log as logging

LOG = logging.getLogger(__name__)

util_opts = [
    cfg.BoolOpt('disable_process_locking', default=False,
                help='Whether to disable inter-process locks'),
    cfg.StrOpt('lock_path',
               default=os.environ.get("OSLO_LOCK_PATH"),
               help=('Directory to use for lock files.'))
]

CONF = cfg.CONF
CONF.register_opts(util_opts)

def set_defaults(lock_path):
    cfg.set_defaults(util_opts, lock_path=lock_path)

class _InterProcessLock(object):
    """Lock implementation which allows multiple locks, working around
    issues like bugs.debian.org/cgi-bin/bugreport.cgi?bug=632857 and does
    not require any cleanup. Since the lock is always held on a file
    descriptor rather than outside of the process, the lock gets dropped
    automatically if the process crashes, even if __exit__ is not executed.

    There are no guarantees regarding usage by multiple green threads in a
    single process here. This lock works only between processes. Exclusive
    access between local threads should be achieved using the semaphores
    in the @synchronized decorator.

    Note these locks are released when the descriptor is closed, so it's not
    safe to close the file descriptor while another green thread holds the
    lock. Just opening and closing the lock file can break synchronisation,
    so lock files must be accessed only using this abstraction.
    """

    def __init__(self, name):
        self.lockfile = None
        self.fname = name

    def __enter__(self):
        self.lockfile = open(self.fname, 'w')

        while True:
            try:
                # Using non-blocking locks since green threads are not
                # patched to deal with blocking locking calls.
                # Also upon reading the MSDN docs for locking(), it seems
                # to have a laughable 10 attempts "blocking" mechanism.
                self.trylock()
                return self
            except IOError as e:
                if e.errno in (errno.EACCES, errno.EAGAIN):
                    # external locks synchronise things like iptables
                    # updates - give it some time to prevent busy spinning
                    time.sleep(0.01)
                else:
                    raise

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self.unlock()
            self.lockfile.close()
        except IOError:
            LOG.exception(_("Could not release the acquired lock `%s`"),
                          self.fname)

    def trylock(self):
        raise NotImplementedError()

    def unlock(self):
        raise NotImplementedError()

class _WindowsLock(_InterProcessLock):
    def trylock(self):
        msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1)

    def unlock(self):
        msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1)

class _PosixLock(_InterProcessLock):
    def trylock(self):
        fcntl.lockf(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)

    def unlock(self):
        fcntl.lockf(self.lockfile, fcntl.LOCK_UN)

if os.name == 'nt':
    import msvcrt
    InterProcessLock = _WindowsLock
else:
    import fcntl
    InterProcessLock = _PosixLock

_semaphores = weakref.WeakValueDictionary()
_semaphores_lock = threading.Lock()

@contextlib.contextmanager
def lock(name, lock_file_prefix=None, external=False, lock_path=None):
    """Context based lock

    This function yields a `threading.Semaphore` instance (if we don't use
    eventlet.monkey_patch(), else `semaphore.Semaphore`) unless external is
    True, in which case, it'll yield an InterProcessLock instance.

    :param lock_file_prefix: The lock_file_prefix argument is used to provide
    lock files on disk with a meaningful prefix.

    :param external: The external keyword argument denotes whether this lock
    should work across multiple processes. This means that if two different
    workers both run a a method decorated with @synchronized('mylock',
    external=True), only one of them will execute at a time.

    :param lock_path: The lock_path keyword argument is used to specify a
    special location for external lock files to live. If nothing is set, then
    CONF.lock_path is used as a default.
    """
    with _semaphores_lock:
        try:
            sem = _semaphores[name]
        except KeyError:
            sem = threading.Semaphore()
            _semaphores[name] = sem

    with sem:
        LOG.debug(_('Got semaphore "%(lock)s"'), {'lock': name})

        # NOTE(mikal): I know this looks odd
        if not hasattr(local.strong_store, 'locks_held'):
            local.strong_store.locks_held = []
        local.strong_store.locks_held.append(name)

        try:
            if external and not CONF.disable_process_locking:
                LOG.debug(_('Attempting to grab file lock "%(lock)s"'),
                          {'lock': name})

                # We need a copy of lock_path because it is non-local
                local_lock_path = lock_path or CONF.lock_path
                if not local_lock_path:
                    raise cfg.RequiredOptError('lock_path')

                if not os.path.exists(local_lock_path):
                    fileutils.ensure_tree(local_lock_path)
                    LOG.info(_('Created lock path: %s'), local_lock_path)

                def add_prefix(name, prefix):
                    if not prefix:
                        return name
                    sep = '' if prefix.endswith('-') else '-'
                    return '%s%s%s' % (prefix, sep, name)

                # NOTE(mikal): the lock name cannot contain directory
                # separators
                lock_file_name = add_prefix(name.replace(os.sep, '_'),
                                            lock_file_prefix)

                lock_file_path = os.path.join(local_lock_path, lock_file_name)

                try:
                    lock = InterProcessLock(lock_file_path)
                    with lock as lock:
                        LOG.debug(_('Got file lock "%(lock)s" at %(path)s'),
                                  {'lock': name, 'path': lock_file_path})
                        yield lock
                finally:
                    LOG.debug(_('Released file lock "%(lock)s" at %(path)s'),
                              {'lock': name, 'path': lock_file_path})
            else:
                yield sem

        finally:
            local.strong_store.locks_held.remove(name)

def synchronized(name, lock_file_prefix=None, external=False, lock_path=None):
    """Synchronization decorator.

    Decorating a method like so::

        @synchronized('mylock')
        def foo(self, *args):
           ...

    ensures that only one thread will execute the foo method at a time.

    Different methods can share the same lock::

        @synchronized('mylock')
        def foo(self, *args):
           ...

        @synchronized('mylock')
        def bar(self, *args):
           ...

    This way only one of either foo or bar can be executing at a time.
    """

    def wrap(f):
        @functools.wraps(f)
        def inner(*args, **kwargs):
            try:
                with lock(name, lock_file_prefix, external, lock_path):
                    LOG.debug(_('Got semaphore / lock "%(function)s"'),
                              {'function': f.__name__})
                    return f(*args, **kwargs)
            finally:
                LOG.debug(_('Semaphore / lock released "%(function)s"'),
                          {'function': f.__name__})
        return inner
    return wrap

def synchronized_with_prefix(lock_file_prefix):
    """Partial object generator for the synchronization decorator.

    Redefine @synchronized in each project like so::

        (in nova/utils.py)
        from nova.openstack.common import lockutils

        synchronized = lockutils.synchronized_with_prefix('nova-')

        (in nova/foo.py)
        from nova import utils

        @utils.synchronized('mylock')
        def bar(self, *args):
           ...

    The lock_file_prefix argument is used to provide lock files on disk with a
    meaningful prefix.
    """

    return functools.partial(synchronized, lock_file_prefix=lock_file_prefix)

def main(argv):
    """Create a dir for locks and pass it to command from arguments

    If you run this:
    python -m openstack.common.lockutils python setup.py testr <etc>

    a temporary directory will be created for all your locks and passed to all
    your tests in an environment variable. The temporary dir will be deleted
    afterwards and the return value will be preserved.
    """

    lock_dir = tempfile.mkdtemp()
    os.environ["OSLO_LOCK_PATH"] = lock_dir
    try:
        ret_val = subprocess.call(argv[1:])
    finally:
        shutil.rmtree(lock_dir, ignore_errors=True)
    return ret_val

if __name__ == '__main__':
    sys.exit(main(sys.argv))
<commit_msg>Trivial: Make vertical white space after license header consistent<commit_after># Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import contextlib
import errno
import functools
import os
import shutil
import subprocess
import sys
import tempfile
import threading
import time
import weakref

from oslo.config import cfg

from openstack.common import fileutils
from openstack.common.gettextutils import _  # noqa
from openstack.common import local
from openstack.common import log as logging

LOG = logging.getLogger(__name__)

util_opts = [
    cfg.BoolOpt('disable_process_locking', default=False,
                help='Whether to disable inter-process locks'),
    cfg.StrOpt('lock_path',
               default=os.environ.get("OSLO_LOCK_PATH"),
               help=('Directory to use for lock files.'))
]

CONF = cfg.CONF
CONF.register_opts(util_opts)

def set_defaults(lock_path):
    cfg.set_defaults(util_opts, lock_path=lock_path)

class _InterProcessLock(object):
    """Lock implementation which allows multiple locks, working around
    issues like bugs.debian.org/cgi-bin/bugreport.cgi?bug=632857 and does
    not require any cleanup. Since the lock is always held on a file
    descriptor rather than outside of the process, the lock gets dropped
    automatically if the process crashes, even if __exit__ is not executed.

    There are no guarantees regarding usage by multiple green threads in a
    single process here. This lock works only between processes. Exclusive
    access between local threads should be achieved using the semaphores
    in the @synchronized decorator.

    Note these locks are released when the descriptor is closed, so it's not
    safe to close the file descriptor while another green thread holds the
    lock. Just opening and closing the lock file can break synchronisation,
    so lock files must be accessed only using this abstraction.
    """

    def __init__(self, name):
        self.lockfile = None
        self.fname = name

    def __enter__(self):
        self.lockfile = open(self.fname, 'w')

        while True:
            try:
                # Using non-blocking locks since green threads are not
                # patched to deal with blocking locking calls.
                # Also upon reading the MSDN docs for locking(), it seems
                # to have a laughable 10 attempts "blocking" mechanism.
                self.trylock()
                return self
            except IOError as e:
                if e.errno in (errno.EACCES, errno.EAGAIN):
                    # external locks synchronise things like iptables
                    # updates - give it some time to prevent busy spinning
                    time.sleep(0.01)
                else:
                    raise

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self.unlock()
            self.lockfile.close()
        except IOError:
            LOG.exception(_("Could not release the acquired lock `%s`"),
                          self.fname)

    def trylock(self):
        raise NotImplementedError()

    def unlock(self):
        raise NotImplementedError()

class _WindowsLock(_InterProcessLock):
    def trylock(self):
        msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1)

    def unlock(self):
        msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1)

class _PosixLock(_InterProcessLock):
    def trylock(self):
        fcntl.lockf(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)

    def unlock(self):
        fcntl.lockf(self.lockfile, fcntl.LOCK_UN)

if os.name == 'nt':
    import msvcrt
    InterProcessLock = _WindowsLock
else:
    import fcntl
    InterProcessLock = _PosixLock

_semaphores = weakref.WeakValueDictionary()
_semaphores_lock = threading.Lock()

@contextlib.contextmanager
def lock(name, lock_file_prefix=None, external=False, lock_path=None):
    """Context based lock

    This function yields a `threading.Semaphore` instance (if we don't use
    eventlet.monkey_patch(), else `semaphore.Semaphore`) unless external is
    True, in which case, it'll yield an InterProcessLock instance.

    :param lock_file_prefix: The lock_file_prefix argument is used to provide
    lock files on disk with a meaningful prefix.

    :param external: The external keyword argument denotes whether this lock
    should work across multiple processes. This means that if two different
    workers both run a a method decorated with @synchronized('mylock',
    external=True), only one of them will execute at a time.

    :param lock_path: The lock_path keyword argument is used to specify a
    special location for external lock files to live. If nothing is set, then
    CONF.lock_path is used as a default.
    """
    with _semaphores_lock:
        try:
            sem = _semaphores[name]
        except KeyError:
            sem = threading.Semaphore()
            _semaphores[name] = sem

    with sem:
        LOG.debug(_('Got semaphore "%(lock)s"'), {'lock': name})

        # NOTE(mikal): I know this looks odd
        if not hasattr(local.strong_store, 'locks_held'):
            local.strong_store.locks_held = []
        local.strong_store.locks_held.append(name)

        try:
            if external and not CONF.disable_process_locking:
                LOG.debug(_('Attempting to grab file lock "%(lock)s"'),
                          {'lock': name})

                # We need a copy of lock_path because it is non-local
                local_lock_path = lock_path or CONF.lock_path
                if not local_lock_path:
                    raise cfg.RequiredOptError('lock_path')

                if not os.path.exists(local_lock_path):
                    fileutils.ensure_tree(local_lock_path)
                    LOG.info(_('Created lock path: %s'), local_lock_path)

                def add_prefix(name, prefix):
                    if not prefix:
                        return name
                    sep = '' if prefix.endswith('-') else '-'
                    return '%s%s%s' % (prefix, sep, name)

                # NOTE(mikal): the lock name cannot contain directory
                # separators
                lock_file_name = add_prefix(name.replace(os.sep, '_'),
                                            lock_file_prefix)

                lock_file_path = os.path.join(local_lock_path, lock_file_name)

                try:
                    lock = InterProcessLock(lock_file_path)
                    with lock as lock:
                        LOG.debug(_('Got file lock "%(lock)s" at %(path)s'),
                                  {'lock': name, 'path': lock_file_path})
                        yield lock
                finally:
                    LOG.debug(_('Released file lock "%(lock)s" at %(path)s'),
                              {'lock': name, 'path': lock_file_path})
            else:
                yield sem

        finally:
            local.strong_store.locks_held.remove(name)

def synchronized(name, lock_file_prefix=None, external=False, lock_path=None):
    """Synchronization decorator.

    Decorating a method like so::

        @synchronized('mylock')
        def foo(self, *args):
           ...

    ensures that only one thread will execute the foo method at a time.

    Different methods can share the same lock::

        @synchronized('mylock')
        def foo(self, *args):
           ...

        @synchronized('mylock')
        def bar(self, *args):
           ...

    This way only one of either foo or bar can be executing at a time.
    """

    def wrap(f):
        @functools.wraps(f)
        def inner(*args, **kwargs):
            try:
                with lock(name, lock_file_prefix, external, lock_path):
                    LOG.debug(_('Got semaphore / lock "%(function)s"'),
                              {'function': f.__name__})
                    return f(*args, **kwargs)
            finally:
                LOG.debug(_('Semaphore / lock released "%(function)s"'),
                          {'function': f.__name__})
        return inner
    return wrap

def synchronized_with_prefix(lock_file_prefix):
    """Partial object generator for the synchronization decorator.

    Redefine @synchronized in each project like so::

        (in nova/utils.py)
        from nova.openstack.common import lockutils

        synchronized = lockutils.synchronized_with_prefix('nova-')

        (in nova/foo.py)
        from nova import utils

        @utils.synchronized('mylock')
        def bar(self, *args):
           ...

    The lock_file_prefix argument is used to provide lock files on disk with a
    meaningful prefix.
    """

    return functools.partial(synchronized, lock_file_prefix=lock_file_prefix)

def main(argv):
    """Create a dir for locks and pass it to command from arguments

    If you run this:
    python -m openstack.common.lockutils python setup.py testr <etc>

    a temporary directory will be created for all your locks and passed to all
    your tests in an environment variable. The temporary dir will be deleted
    afterwards and the return value will be preserved.
    """

    lock_dir = tempfile.mkdtemp()
    os.environ["OSLO_LOCK_PATH"] = lock_dir
    try:
        ret_val = subprocess.call(argv[1:])
    finally:
        shutil.rmtree(lock_dir, ignore_errors=True)
    return ret_val

if __name__ == '__main__':
    sys.exit(main(sys.argv))
<|endoftext|>


<commit_before>#!/usr/bin/python
# -*- coding: utf-8 -*-

'''This script counts page visits on the previous day. pagecounts files available from dumps.wikimedia.org are split in hours so this
is basically to sum data from 24 files, sort them in decreasing order and put on pl.wikt'''
import codecs
import os
import glob #need this to remove files
import subprocess
import pywikibot
import urllib.request, urllib.parse, urllib.error
import datetime
import gzip
import config
from operator import itemgetter

def checkSum(folder, filename, dateString):
    urlmd5 = 'http://dumps.wikimedia.org/other/pagecounts-raw/%s/%s-%s/md5sums.txt' % (dateString[:4], dateString[:4], dateString[4:6])
    md5 = urllib.request.urlopen(urlmd5)
    sum = None
    for line in md5:
        if filename in line:
            sum = line.split()[0]
    if sum:
        sumDownloaded = subprocess.check_output(["md5sum", folder+filename])[:32]
        if sum == sumDownloaded:
            return 1
        else:
            return 0
    else:
        return 0

def main():
    #get the default site - the bot is operating on pl.wikt only
    site = pywikibot.Site()

    #a list and a dict to handle individual entries
    lista = []
    rankingDict = {}

    #control how many entries should appear on the stats site
    limitEntriesTo = 1000

    date_yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
    date_string = date_yesterday.strftime("%Y%m%d")
    data_slownie = date_string[6] + date_string[7] + '.' + date_string[4] + date_string[5] + '.' + date_string[0] + date_string[1] + date_string[2] + date_string[3]

    statSite = pywikibot.Page(site, 'Wikipedysta:AlkamidBot/statystyka/wizyty')

    for hour in range(24):

        for seconds in range(25):

            folder = config.path['pagecounts']
            filename = '{0}/{1}-{2}/pagecounts-{3}-{4:02d}00{5:02d}.gz'.format(date_string[:4], date_string[:4], date_string[4:6], date_string, hour, seconds)
            try: inp = gzip.open(folder + filename, mode='rt')
            except IOError:
                continue

            #here was the code used for downloading pagecounts, moved to the end of this file

        try:
            for line in inp:

                #only process lines starting with "pl.d" which means pl.wiktionary
                if line[:4] == 'pl.d':

                    #the lines should look like this: pl.d pagename visits size, where size is the size of the content returned
                    a = line.split()

                    #convert %-escaped characters to utf-8
                    a[1] = urllib.parse.unquote(a[1])

                    #if the page is not in the dictionary, add it with the initial count; if it is in dictionary, sum the visits
                    rankingDict[a[1]] = rankingDict.get(a[1], 0) + int(a[2])
        except IOError:
            pass
        #print(hour) #- just for debugging, shows which hour we are in
        inp.close

    ranking = sorted(list(rankingDict.items()), key=itemgetter(1), reverse=True)

    text = 'Statystyka wizyt na stronach z %s. Nie obejmuje przestrzeni nazw: Plik, Szablon, Specjalna, Kategoria, Dyskusja Wikipedysty.' % data_slownie
    text += '\n\n{| class="wikitable sortable"\n|-\n!strona\n!odwiedzin'
    textFile = ''

    i = 0
    for a in ranking:
        if (i<limitEntriesTo) and 'Plik:' not in a[0] and 'Szablon:' not in a[0] and 'Specjalna:' not in a[0] and 'Kategoria:' not in a[0] and 'Special:' not in a[0] and 'Dyskusja Wikipedysty:' not in a[0] and 'admin/' not in a[0]:
            textFile = textFile + '\n%s|%d' % (a[0], a[1])
            text = text + '\n|-\n|[[%s]]\n|%d' % (a[0], a[1])
            i += 1

    text += '\n|}'
    text = text.replace('_', ' ')

    with open('{0}output/visits.txt'.format(config.path['scripts']), encoding='utf-8', mode='w') as f:
        f.write(textFile)

    statSite.text = text
    statSite.save(comment = 'aktualizacja', botflag=False)

    #cleanup  - only if you download files along the way
    '''os.chdir('/mnt/user-store/alkamid/stats/')
    toRemove= glob.glob('pagecounts*.gz')
    for file in toRemove:
            os.remove(file)'''

if __name__ == '__main__':
    try:
        main()
    finally:
        pywikibot.stopme()
<commit_msg>added encoding to gzip.open()<commit_after>#!/usr/bin/python
# -*- coding: utf-8 -*-

'''This script counts page visits on the previous day. pagecounts files available from dumps.wikimedia.org are split in hours so this
is basically to sum data from 24 files, sort them in decreasing order and put on pl.wikt'''
import codecs
import os
import glob #need this to remove files
import subprocess
import pywikibot
import urllib.request, urllib.parse, urllib.error
import datetime
import gzip
import config
from operator import itemgetter

def checkSum(folder, filename, dateString):
    urlmd5 = 'http://dumps.wikimedia.org/other/pagecounts-raw/%s/%s-%s/md5sums.txt' % (dateString[:4], dateString[:4], dateString[4:6])
    md5 = urllib.request.urlopen(urlmd5)
    sum = None
    for line in md5:
        if filename in line:
            sum = line.split()[0]
    if sum:
        sumDownloaded = subprocess.check_output(["md5sum", folder+filename])[:32]
        if sum == sumDownloaded:
            return 1
        else:
            return 0
    else:
        return 0

def main():
    #get the default site - the bot is operating on pl.wikt only
    site = pywikibot.Site()

    #a list and a dict to handle individual entries
    lista = []
    rankingDict = {}

    #control how many entries should appear on the stats site
    limitEntriesTo = 1000

    date_yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
    date_string = date_yesterday.strftime("%Y%m%d")
    data_slownie = date_string[6] + date_string[7] + '.' + date_string[4] + date_string[5] + '.' + date_string[0] + date_string[1] + date_string[2] + date_string[3]

    statSite = pywikibot.Page(site, 'Wikipedysta:AlkamidBot/statystyka/wizyty')

    for hour in range(24):

        for seconds in range(25):

            folder = config.path['pagecounts']
            filename = '{0}/{1}-{2}/pagecounts-{3}-{4:02d}00{5:02d}.gz'.format(date_string[:4], date_string[:4], date_string[4:6], date_string, hour, seconds)
            try: inp = gzip.open(folder + filename, encoding='utf-8', mode='rt')
            except IOError:
                continue

            #here was the code used for downloading pagecounts, moved to the end of this file

        try:
            for line in inp:

                #only process lines starting with "pl.d" which means pl.wiktionary
                if line[:4] == 'pl.d':

                    #the lines should look like this: pl.d pagename visits size, where size is the size of the content returned
                    a = line.split()

                    #convert %-escaped characters to utf-8
                    a[1] = urllib.parse.unquote(a[1])

                    #if the page is not in the dictionary, add it with the initial count; if it is in dictionary, sum the visits
                    rankingDict[a[1]] = rankingDict.get(a[1], 0) + int(a[2])
        except IOError:
            pass
        #print(hour) #- just for debugging, shows which hour we are in
        inp.close

    ranking = sorted(list(rankingDict.items()), key=itemgetter(1), reverse=True)

    text = 'Statystyka wizyt na stronach z %s. Nie obejmuje przestrzeni nazw: Plik, Szablon, Specjalna, Kategoria, Dyskusja Wikipedysty.' % data_slownie
    text += '\n\n{| class="wikitable sortable"\n|-\n!strona\n!odwiedzin'
    textFile = ''

    i = 0
    for a in ranking:
        if (i<limitEntriesTo) and 'Plik:' not in a[0] and 'Szablon:' not in a[0] and 'Specjalna:' not in a[0] and 'Kategoria:' not in a[0] and 'Special:' not in a[0] and 'Dyskusja Wikipedysty:' not in a[0] and 'admin/' not in a[0]:
            textFile = textFile + '\n%s|%d' % (a[0], a[1])
            text = text + '\n|-\n|[[%s]]\n|%d' % (a[0], a[1])
            i += 1

    text += '\n|}'
    text = text.replace('_', ' ')

    with open('{0}output/visits.txt'.format(config.path['scripts']), encoding='utf-8', mode='w') as f:
        f.write(textFile)

    statSite.text = text
    statSite.save(comment = 'aktualizacja', botflag=False)

    #cleanup  - only if you download files along the way
    '''os.chdir('/mnt/user-store/alkamid/stats/')
    toRemove= glob.glob('pagecounts*.gz')
    for file in toRemove:
            os.remove(file)'''

if __name__ == '__main__':
    try:
        main()
    finally:
        pywikibot.stopme()
<|endoftext|>


<commit_before>""" python frontend for vnstat cmd/tool """

from subprocess import getoutput
from datetime import datetime
import json
if __name__ == 'vnstat.vnstat':
    from . import jalali
else:
    import jalali

def read():
    """ it return a vnstat as json object """
    cmd = 'vnstat --json'
    vnstat_out = getoutput(cmd)
    data_json = json.loads(vnstat_out)
    return data_json

def format_data(data):
    """ reformat data
    add persian (jalali) date
    add date as object
    sort data by date
    add total of rx + tx

    args:
        data: dictionary create from json output
    """

    for interface in data['interfaces']:
        for traffic_type in ['days', 'months', 'hours']:
            for record in interface['traffic'][traffic_type]:
                # if day not specified in date it replaced with 1
                # usually it happens in month data
                if 'day' not in record['date']:
                    record['date']['day'] = 1
                date_string = '%d/%d/%d' % (record['date']['year'],
                                            record['date']['month'], record['date']['day'])
                record['date'] = datetime(record['date']['year'],
                                          record['date']['month'], record['date']['day'])
                record['jdate'] = jalali.Gregorian(date_string)

                record['total'] = record['rx'] + record['tx']
                # calucate totla for tops
            for record in interface['traffic']['tops']:
                record['total'] = record['rx'] + record['tx']
            interface['traffic'][traffic_type].sort(key=lambda x: x.get('date'))
    return data

def change_unit(data, to_unit='M'):
    """ change traffic unit from KiB to MiB or GiB """
    units = {'M':10**3, 'G':10**6}
    divisor = units[to_unit]
    for interface in data['interfaces']:
        for traffic_type in ['days', 'months', 'hours']:
            for record in interface['traffic'][traffic_type]:
                record['rx'] = record['rx'] / divisor
                record['tx'] = record['tx'] / divisor
                record['total'] = record['total'] / divisor
            for record in interface['traffic']['tops']:
                record['rx'] = record['rx'] / divisor
                record['tx'] = record['tx'] / divisor
                record['total'] = record['total'] / divisor
    return data

def rx_sum(data):
    """ return sum of rx traffic's """
    rx_traffic = 0
    for i in data['interfaces'][0]['traffic']['days']:
        rx_traffic = rx_traffic + i['rx']
    return rx_traffic

def get(data, traffic_set='days', interface=None):
    """get set of specific data like days , months
       if no interface specified it returns all interfaces data in format:
       (nick, [ tops ])
       """

    if interface is None:
        return {item['nick']:item['traffic'][traffic_set] for item in data['interfaces']}
    else:
        iface_data = [item for item in data['interfaces'] if item['nick'] == interface][0]
        return iface_data['traffic'][traffic_set]
<commit_msg>get_days added for ease of use<commit_after>""" python frontend for vnstat cmd/tool """

from subprocess import getoutput
from datetime import datetime
import json
if __name__ == 'vnstat.vnstat':
    from . import jalali
else:
    import jalali

def read():
    """ it return a vnstat as json object """
    cmd = 'vnstat --json'
    vnstat_out = getoutput(cmd)
    data_json = json.loads(vnstat_out)
    return data_json

def format_data(data):
    """ reformat data
    add persian (jalali) date
    add date as object
    sort data by date
    add total of rx + tx

    args:
        data: dictionary create from json output
    """

    for interface in data['interfaces']:
        for traffic_type in ['days', 'months', 'hours']:
            for record in interface['traffic'][traffic_type]:
                # if day not specified in date it replaced with 1
                # usually it happens in month data
                if 'day' not in record['date']:
                    record['date']['day'] = 1
                date_string = '%d/%d/%d' % (record['date']['year'],
                                            record['date']['month'], record['date']['day'])
                record['date'] = datetime(record['date']['year'],
                                          record['date']['month'], record['date']['day'])
                record['jdate'] = jalali.Gregorian(date_string)

                record['total'] = record['rx'] + record['tx']
                # calucate totla for tops
            for record in interface['traffic']['tops']:
                record['total'] = record['rx'] + record['tx']
            interface['traffic'][traffic_type].sort(key=lambda x: x.get('date'))
    return data

def change_unit(data, to_unit='M'):
    """ change traffic unit from KiB to MiB or GiB """
    units = {'M':10**3, 'G':10**6}
    divisor = units[to_unit]
    for interface in data['interfaces']:
        for traffic_type in ['days', 'months', 'hours']:
            for record in interface['traffic'][traffic_type]:
                record['rx'] = record['rx'] / divisor
                record['tx'] = record['tx'] / divisor
                record['total'] = record['total'] / divisor
            for record in interface['traffic']['tops']:
                record['rx'] = record['rx'] / divisor
                record['tx'] = record['tx'] / divisor
                record['total'] = record['total'] / divisor
    return data

def rx_sum(data):
    """ return sum of rx traffic's """
    rx_traffic = 0
    for i in data['interfaces'][0]['traffic']['days']:
        rx_traffic = rx_traffic + i['rx']
    return rx_traffic

def get(data, traffic_set='days', interface=None):
    """get set of specific data like days , months
       if no interface specified it returns all interfaces data in format:
       (nick, [ tops ])
       """

    if interface is None:
        return {item['nick']:item['traffic'][traffic_set] for item in data['interfaces']}
    else:
        iface_data = [item for item in data['interfaces'] if item['nick'] == interface][0]
        return iface_data['traffic'][traffic_set]

def get_days(data, interface=None):
    """ get daily traffic's
    if no interface specified it returns all interfaces data in format:
       {nick: [ days ]}
    """
    return get(data, 'days', interface)
<|endoftext|>


<commit_before>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# volley.py - Create match thread style info for reddit from volleyball games
# Further Information: https://crap.solutions/pages/volley.html - https://github.com/sigttou/volley.py

import urllib3
import sys
from bs4 import BeautifulSoup
from string import Template

if len(sys.argv) < 2:
    print("./volley <GAME NR.>")
    exit(-1)

# Variables we want to receive from the website
HOME_TEAM = ""
AWAY_TEAM = ""
HOME_POINTS = ""
AWAY_POINTS = ""
SET_TIME = [None] * 5
HOME_SET_POINTS = [None] * 5
AWAY_SET_POINTS = [None] * 5
REFS = [None] * 2
LOCATION = ""
KICK_OFF = ""
HOME_MEMBERS = {}
HOME_NUMS = {}
AWAY_MEMBERS = {}
AWAY_NUMS = {}
HOME_COACH = ""
AWAY_COACH = ""

# Get some info from Stats website
url = "http://volleynet.at/datavolley/2016/women/&D1-" + sys.argv[1] + "_REPORT.htm"
http_pool = urllib3.connection_from_url(url)
r = http_pool.urlopen('GET',url)
content = str(r.data.decode())
soup = BeautifulSoup(content, 'html.parser')

lookfor = []
for td in soup.find_all('td'):
    if td.attrs.get('class') == ["TabResults_Value"]:
        if td.contents[0].attrs.get('color') == "#333333":
            lookfor.append(td.contents[0])

for i in range(0, len(lookfor)):
    analyse = lookfor[i].contents
    if len(analyse) > 1:
        analyse = analyse[1]
    else:
        continue
    if analyse.attrs.get('id') and analyse.attrs.get('id').startswith("corpo_pagina_GV_elenco_casa_L_Nome_"):
        HOME_MEMBERS[len(HOME_MEMBERS)] = analyse.contents[0]
        HOME_NUMS[len(HOME_NUMS)] = lookfor[i-1].contents[0]
    elif analyse.attrs.get('id') and analyse.attrs.get('id').startswith("corpo_pagina_GV_elenco_fuori_L_Nome_"):
        AWAY_MEMBERS[len(AWAY_MEMBERS)] = analyse.contents[0]
        AWAY_NUMS[len(AWAY_NUMS)] = lookfor[i-1].contents[0]

for span in soup.find_all('span'):
    if span.attrs.get('id') == 'corpo_pagina_L_Impianto':
        LOCATION = span.contents[0]
    elif span.attrs.get('id') and span.attrs.get('id').startswith("corpo_pagina_L_Arbitro"):
        try:
            REFS[int(span.attrs.get('id')[-1])-1] = span.contents[0]
        except:
            pass
    elif span.attrs.get('id') == 'corpo_pagina_L_MatchHour':
        KICK_OFF = span.contents[0]
    elif span.attrs.get('id') == 'corpo_pagina_Coach_Casa':
        HOME_COACH = span.contents[0][1:-1]
    elif span.attrs.get('id') == 'corpo_pagina_Coach_Fuori':
        AWAY_COACH = span.contents[0][1:-1]

# Now we fetch the website with the live stats
url = "http://volleynet.at/datavolley/2016/women/D1-" + sys.argv[1] + "_LIVE.htm"
http_pool = urllib3.connection_from_url(url)
r = http_pool.urlopen('GET',url)
content = str(r.data.decode())
soup = BeautifulSoup(content, 'html.parser')

for span in soup.find_all('span'):
    if span.attrs.get('id') == 'L_HomeTeam':
        HOME_TEAM = span.contents[0]
    elif span.attrs.get('id') == 'L_WonSetHome':
        HOME_POINTS = span.contents[0]
    elif span.attrs.get('id') == 'L_GuestTeam':
        AWAY_TEAM = span.contents[0]
    elif span.attrs.get('id') == 'L_WonSetGuest':
        AWAY_POINTS = span.contents[0]
    elif span.attrs.get('id').startswith("L_TimeSet"):
        try:
            SET_TIME[int(span.attrs.get('id')[-1])-1] = int(span.contents[0][:-1])
        except:
            pass
    elif span.attrs.get('id').startswith("L_Set"):
        try:
            index = int(span.attrs.get('id')[5]) - 1
            points = int(span.contents[0])
        except:
            pass
        if span.attrs.get('id').endswith("Guest"):
            HOME_SET_POINTS[index] = points
        else:
            AWAY_SET_POINTS[index] = points

HOME_OVERALL = sum(HOME_SET_POINTS)
AWAY_OVERALL = sum(AWAY_SET_POINTS)
TIME_OVERALL = sum(SET_TIME)
COMPETITION = "Austrian Volley League Women, regular season"
STREAM = "TBA"
LINKS = ""

data = {'home_team': HOME_TEAM,
        'away_team': AWAY_TEAM,
        'home_points': HOME_POINTS,
        'away_points': AWAY_POINTS,
        'home_over': HOME_OVERALL,
        'away_over': AWAY_OVERALL,
        'time_over': TIME_OVERALL,
        'location': LOCATION,
        'kick_off': KICK_OFF,
        'competition': COMPETITION,
        'refs': ", ".join(REFS),
        'stream': STREAM,
        'links': LINKS,
        'teams': "",
        'scoreline': "",
        'updates': ""
        }

# Team table sucks in template, so here is the code for it:

data['teams'] += "\# | {home_team} | \# | {away_team}\n".format(**data)
data['teams'] += "---|---|---|----\n"
for i in range(0, max(len(HOME_MEMBERS), len(AWAY_MEMBERS))):
    data['teams'] += "{} | {} | {} | {}\n".format( HOME_NUMS.get(i) if HOME_NUMS.get(i) else "", HOME_MEMBERS.get(i) if HOME_MEMBERS.get(i) else "", AWAY_NUMS.get(i) if AWAY_NUMS.get(i) else "", AWAY_MEMBERS.get(i) if AWAY_MEMBERS.get(i) else "")
data['teams'] += "|||\n"
data['teams'] += u" | {} | | {}\n".format(HOME_COACH, AWAY_COACH)

for i in range(0,len(SET_TIME)):
    data['scoreline'] += "{} | {}' | {}:{}\n".format(i+1, SET_TIME[i], AWAY_SET_POINTS[i], HOME_SET_POINTS[i])
data['scoreline'] += "**OA** | **{time_over}'** | **{home_over}:{away_over}**\n".format(**data)

filein = open("templates/thread.tpl")
src = Template(filein.read())
result = src.substitute(data)
print(result)
<commit_msg>IMP: general source improvements<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# volley.py - Create match thread style info for reddit from volleyball games
# Further Information: https://crap.solutions/pages/volley.html - https://github.com/sigttou/volley.py

import urllib3
import sys
from bs4 import BeautifulSoup
from string import Template

def main():
    if len(sys.argv) < 2:
        print("./volley <GAME NR.>")
        exit(-1)

    data = {'home_team': '',
            'away_team': '',
            'home_points': 0,
            'away_points': 0,
            'home_over': 0,
            'away_over': 0,
            'time_over': 0,
            'location': '',
            'kick_off': '',
            'competition': '',
            'refs': '',
            'stream': '',
            'links': '',
            'teams': '',
            'scoreline': '',
            'updates': '',
            'stat_url': '',
            'score_url': '',
            'home_members': {},
            'away_members': {},
            'home_nums': {},
            'away_nums': {},
            'home_coach': '',
            'away_coach': '',
            'set_time': [None] * 5,
            'home_set_points': [None] * 5,
            'away_set_points': [None] * 5
            }
    parse_config(data)
    get_scoreline(data)
    get_general_info(data)
    print_thread_content(data)
    exit(0)

def parse_config(data):
    #Config
    from config import CONFIG
    for k in CONFIG:
        data[k] = CONFIG[k]

def get_general_info(data):
    url = data['stat_url']
    http_pool = urllib3.connection_from_url(url)
    r = http_pool.urlopen('GET',url)
    content = str(r.data.decode())
    soup = BeautifulSoup(content, 'html.parser')

    lookfor = []
    for td in soup.find_all('td'):
        if td.attrs.get('class') == ["TabResults_Value"]:
            if td.contents[0].attrs.get('color') == "#333333":
                lookfor.append(td.contents[0])

    for i in range(0, len(lookfor)):
        analyse = lookfor[i].contents
        if len(analyse) > 1:
            analyse = analyse[1]
        else:
            continue
        if analyse.attrs.get('id') and analyse.attrs.get('id').startswith("corpo_pagina_GV_elenco_casa_L_Nome_"):
            data['home_members'][len(data['home_members'])] = analyse.contents[0]
            data['home_nums'][len(data['home_nums'])] = lookfor[i-1].contents[0]
        elif analyse.attrs.get('id') and analyse.attrs.get('id').startswith("corpo_pagina_GV_elenco_fuori_L_Nome_"):
            data['away_members'][len(data['away_members'])] = analyse.contents[0]
            data['away_nums'][len(data['away_nums'])] = lookfor[i-1].contents[0]

    for span in soup.find_all('span'):
        if span.attrs.get('id') == 'corpo_pagina_L_Impianto':
            data['location'] = span.contents[0]
        elif span.attrs.get('id') and span.attrs.get('id').startswith("corpo_pagina_L_Arbitro"):
            try:
                if(not data['refs']):
                    data['refs'] += span.contents[0] + ","
                else:
                    data['refs'] += " " + span.contents[0]
            except:
                pass
        elif span.attrs.get('id') == 'corpo_pagina_L_MatchHour':
            data['kick_off'] = span.contents[0]
        elif span.attrs.get('id') == 'corpo_pagina_Coach_Casa':
            data['home_coach'] = span.contents[0][1:-1]
        elif span.attrs.get('id') == 'corpo_pagina_Coach_Fuori':
            data['away_coach'] = span.contents[0][1:-1]

    data['teams'] += "\# | {home_team} | \# | {away_team}\n".format(**data)
    data['teams'] += "---|---|---|----\n"
    for i in range(0, max(len(data['home_members']), len(data['away_members']))):
        data['teams'] += "{} | {} | {} | {}\n".format(
                data['home_nums'].get(i) if data['home_nums'].get(i) else "",
                data['home_members'].get(i) if data['home_members'].get(i) else "",
                data['away_nums'].get(i) if data['away_nums'].get(i) else "",
                data['away_members'].get(i) if data['away_members'].get(i) else "")
    data['teams'] += "|||\n"
    data['teams'] += u" | {} | | {}\n".format(  data['home_coach'],
                                                data['away_coach'])

def get_scoreline(data):
    url = data['score_url']
    http_pool = urllib3.connection_from_url(url)
    r = http_pool.urlopen('GET',url)
    content = str(r.data.decode())
    soup = BeautifulSoup(content, 'html.parser')

    for span in soup.find_all('span'):
        if span.attrs.get('id') == 'L_HomeTeam':
            data['home_team'] = span.contents[0]
        elif span.attrs.get('id') == 'L_WonSetHome':
            data['home_points'] = span.contents[0]
        elif span.attrs.get('id') == 'L_GuestTeam':
            data['away_team'] = span.contents[0]
        elif span.attrs.get('id') == 'L_WonSetGuest':
            data['away_points'] = span.contents[0]
        elif span.attrs.get('id').startswith("L_TimeSet"):
            try:
                data['set_time'][int(span.attrs.get('id')[-1])-1] = int(span.contents[0][:-1])
            except:
                pass
        elif span.attrs.get('id').startswith("L_Set"):
            try:
                index = int(span.attrs.get('id')[5]) - 1
                points = int(span.contents[0])
            except:
                pass
            if span.attrs.get('id').endswith("Guest"):
                data['away_set_points'][index] = points
            else:
                data['home_set_points'][index] = points

    data['home_over'] = sum(data['home_set_points'])
    data['away_over'] = sum(data['away_set_points'])
    data['time_over'] = sum(data['set_time'])

    for i in range(0,len(data['set_time'])):
        data['scoreline'] += "{} | {}' | {}:{}\n".format(   i+1,
                                                            data['set_time'][i],
                                                            data['home_set_points'][i],
                                                            data['away_set_points'][i])
    data['scoreline'] += "**OA** | **{time_over}'** | **{home_over}:{away_over}**\n".format(**data)

def print_thread_content(data):
    filein = open("templates/thread.tpl")
    src = Template(filein.read())
    result = src.substitute(data)
    print(result)

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before>########################################################
#        Program to Download Wallpapers from           #
#                  alpha.wallhaven.cc                  #
#                                                      #
#                 Author - Saurabh Bhan                #
#                                                      #
#                  dated- 26 June 2016                 #
########################################################

import os
import bs4
import re
import requests

os.makedirs('Wallhaven', exist_ok=True)
url = 'https://alpha.wallhaven.cc/latest'
urlreq = requests.get(url)
soup = bs4.BeautifulSoup(urlreq.text, 'lxml')
soupid = soup.findAll('a', {'class': 'preview'})
res = re.compile(r'\d+')
imgid = res.findall(str(soupid))
imgext = ['jpg', 'png', 'bmp']
for i in range(len(imgid)):
    url = 'http://wallpapers.wallhaven.cc/wallpapers/full/wallhaven-%s.' % imgid[
        i]
    iurl = url + imgext[0]
    imgreq = requests.get(iurl)
    er = imgreq.status_code
    if er == 404:
        iurl == url + imgext[1]
        er2 = imgreq.status_code
        if er2 == 404:
            iurl == url + imgext[2]
    print('Downloading: ' + iurl)
    imageFile = open(os.path.join('Wallhaven', os.path.basename(iurl)), 'ab')
    for chunk in imgreq.iter_content(100000):
        imageFile.write(chunk)
    imageFile.close()
<commit_msg>Added Page support and Progress bar<commit_after>########################################################
#        Program to Download Wallpapers from           #
#                  alpha.wallhaven.cc                  #
#                                                      #
#                 Author - Saurabh Bhan                #
#                                                      #
#                  dated- 26 June 2016                 #
########################################################

import os
import bs4
import re
import requests
import time
import tqdm

os.makedirs('Wallhaven', exist_ok=True)
url = 'https://alpha.wallhaven.cc/latest'
urlreq = requests.get(url)
soup = bs4.BeautifulSoup(urlreq.text, 'lxml')
soupid = soup.findAll('a', {'class': 'preview'})
res = re.compile(r'\d+')
imgid = res.findall(str(soupid))
imgext = ['jpg', 'png', 'bmp']
for i in range(len(imgid)):
    url = 'http://wallpapers.wallhaven.cc/wallpapers/full/wallhaven-%s.' % imgid[
        i]
        for ext in imgext:
            iurl = url + ext
            imgreq = requests.get(iurl)
            if imgreq.status_code == 200:
                print('Downloading: ' + iurl)
                with open(os.path.join('Wallhaven', os.path.basename(iurl)), 'ab') as imageFile:
                    for chunk in tqdm.tqdm(imgreq.iter_content(1024), total=(int(imgreq.headers['content-length']) / 1024), unit='KB'):
                        time.sleep(0.01)
                        imageFile.write(chunk)
            break
<|endoftext|>


<commit_before>"""
Main entry point of the logup-factory
"""
from base64 import b64encode, b64decode

from flask import Flask
from flask.ext.mongoengine import MongoEngine
from flask import request
from flask import session
from flask import jsonify
from flask import render_template
from flask import url_for
from flask import g
from models.models import User, Token, PasswordRenewToken, BetaRequest
from authentication.authentication import generate_token
from authentication.authentication import requires_token
from authentication.authentication import redirect_app
from utils.mailgun_helper import send_password_reset
from python_mailgun.client import Client
from config import Config
from werkzeug.exceptions import abort

app = Flask(__name__, static_url_path='', static_folder='frontend/dist')

def configure_app():
    """ Function to be called for flask's configuration

    @return: the configured app
    """
    app.config.from_envvar(Config.APP_ENVVAR)
    app.db = MongoEngine(app)
    app.mailgun_client = None
    if 'MAILGUN_API_KEY' and 'MAILGUN_DOMAIN' in app.config:
        app.mailgun_client = Client(app.config['MAILGUN_API_KEY'],
                                    app.config['MAILGUN_DOMAIN'])
    return app

@app.before_request
def get_user_info():
    """ function to executed before each received request

    Will test if the user is authenticated and consequently insert its
    information into the g variable
    """
    if 'token' in session:
        t = Token.objects(id=session['token'])
        u = t.first()
        if u:
            g.current_user = {'email': u.user.email}
        else:
            g.current_user = None

@app.route('/')
def index():
    """ Application's index

    @return: rendering for the index page
    """
    return render_template('index.html')

@app.route('/app')
@requires_token
def product_home():
    """ Product's home

    @return: rendering for the product's home page or a 401 (Unauthorized)
    response caused by the requires_token wrapper
    """
    return render_template('user-home.html')

@app.route('/signup', methods=['GET'])
@redirect_app
def signup_page():
    """ Signup page

    @return: rendering for the signup page
    """
    return render_template('signup.html')

@app.route('/signup', methods=['POST'])
def signup():
    """ Signup endpoint

    Attempt to register a user to the website
    @return: A json response containing an email field upon success or an
    error description
    """
    if not Config.EMAIL_REGEX.match(request.form['email']):
        return jsonify(error='Invalid email address')

    elif User.objects(email=request.form['email']).count() > 0:
        return jsonify(error='Already registered email')

    else:
        user = User.create_and_store(
            request.form['email'],
            request.form['password']
        )
        return jsonify(email=user.email)

@app.route('/login', methods=['GET'])
@redirect_app
def login_page():
    """ Login page

    @return: rendering for the login page
    """
    return render_template('login.html')

@app.route('/login', methods=['POST'])
def login():
    """ login endpoint

    Attempt to login a user
    @return: A json response containing an email field upon success or an
    error description
    """
    token = generate_token(request.form['email'], request.form['password'])
    if token:
        session['token'] = str(token.id)
        return jsonify(email=token.user.email)
    else:
        return jsonify(error='Invalid credentials')

@app.route('/forgot-password', methods=['GET'])
@redirect_app
def forgot_password_page():
    """ The forgot password page

    Render the forgot password page if a mailgun configuration is provided
    @return: rendering for the forgot password page if a mailgun client is
    configured or abort with 404 (not found) status code
    """
    if app.mailgun_client:
        return render_template('forgot-password.html')
    else:
        return abort(404)

@app.route('/forgot-password', methods=['POST'])
def forgot_password():
    """ The forgot password endpoint

    Attempt to create an url to be sent for password reset
    @return: The json answer {'success': true} upon successful email generation
    and sending  or an error description. If no mailgun configuration is
    provided abort with 404 (not found) status code
    """
    if not app.mailgun_client:
        abort(404)

    users = User.objects(email=request.form['email'])
    if users.count() == 0:
        return jsonify(error='Unknown email')
    user = users.first()
    if PasswordRenewToken.objects(user=user).count() > 0:
        renew_token = PasswordRenewToken.objects(user=user).first()
        hashed = b64encode(str(renew_token.id))
    else:
        renew_token = PasswordRenewToken(user=user)
        renew_token.save()
        hashed = b64encode(str(renew_token.id))
    reset_link = url_for('password_reset', token=hashed, _external=True)

    answer = send_password_reset(
        app.mailgun_client,
        app.config['MAILGUN_MAILING_ADDRESS'],
        user.email,
        render_template('email/forgot-password.txt', link=reset_link),
        render_template('email/forgot-password.html', link=reset_link)
    )

    if answer.status_code == 200:
        return jsonify(success=True)
    else:
        return jsonify(error='Unable to sent mail')

@app.route('/password-reset/<token>', methods=['GET'])
@redirect_app
def password_reset_page(token):
    """ Password reset page

    @param token: the token url parameter
    @return: rendering for the password reset page
    """
    return render_template('password-reset.html', token=token)

@app.route('/password-reset/<token>', methods=['POST'])
@redirect_app
def password_reset(token):
    """ Password reset endpoint

    @param token: the token url parameter
    @return: The json answer {'success': true} upon successful reset or an error
    description
    """
    try:
        clear = b64decode(token)
    except TypeError:
        return jsonify(error='Invalid reset request')

    p_token = PasswordRenewToken.objects(id=clear)
    if p_token.count() == 0:
        return jsonify(error='Invalid reset request')
    p_token.first().user.update_password(request.form['password'])
    p_token.delete()
    return jsonify(success=True)

@app.route('/logout', methods=['GET'])
@requires_token
def logout():
    """ The logout endpoint

    Attempt to logout the user and delete its token from mongoDB
    @return: {'success': true} upon successful logout or a 401 (Unauthorized)
    response caused by the requires_token wrapper
    """
    Token.objects(id=session['token']).delete()
    session.pop('token', None)
    return jsonify(success=True)

@app.route('/request-beta-access', methods=['POST'])
def request_beta_access():
    """ request beta access endpoint

    Attempt to add the user to the beta request list
    @return: {'success': true} upon successful subscription or an error
    description
    """
    email = request.form['email']
    if not Config.EMAIL_REGEX.match(email):
        return jsonify(error='Invalid email address')

    if User.objects(email=email).count() == 0 and BetaRequest.objects(
            email=email).count() == 0:
        BetaRequest(email=email, code=request.form['promo_code']).save()
        return jsonify(success=True)
    else:
        return jsonify(error='Already registered')

configure_app()
if __name__ == '__main__':
    # configure_app('confs/prod.cfg')
    app.run(threaded=True)
<commit_msg>Remove commented code<commit_after>"""
Main entry point of the logup-factory
"""
from base64 import b64encode, b64decode

from flask import Flask
from flask.ext.mongoengine import MongoEngine
from flask import request
from flask import session
from flask import jsonify
from flask import render_template
from flask import url_for
from flask import g
from models.models import User, Token, PasswordRenewToken, BetaRequest
from authentication.authentication import generate_token
from authentication.authentication import requires_token
from authentication.authentication import redirect_app
from utils.mailgun_helper import send_password_reset
from python_mailgun.client import Client
from config import Config
from werkzeug.exceptions import abort

app = Flask(__name__, static_url_path='', static_folder='frontend/dist')

def configure_app():
    """ Function to be called for flask's configuration

    @return: the configured app
    """
    app.config.from_envvar(Config.APP_ENVVAR)
    app.db = MongoEngine(app)
    app.mailgun_client = None
    if 'MAILGUN_API_KEY' and 'MAILGUN_DOMAIN' in app.config:
        app.mailgun_client = Client(app.config['MAILGUN_API_KEY'],
                                    app.config['MAILGUN_DOMAIN'])
    return app

@app.before_request
def get_user_info():
    """ function to executed before each received request

    Will test if the user is authenticated and consequently insert its
    information into the g variable
    """
    if 'token' in session:
        t = Token.objects(id=session['token'])
        u = t.first()
        if u:
            g.current_user = {'email': u.user.email}
        else:
            g.current_user = None

@app.route('/')
def index():
    """ Application's index

    @return: rendering for the index page
    """
    return render_template('index.html')

@app.route('/app')
@requires_token
def product_home():
    """ Product's home

    @return: rendering for the product's home page or a 401 (Unauthorized)
    response caused by the requires_token wrapper
    """
    return render_template('user-home.html')

@app.route('/signup', methods=['GET'])
@redirect_app
def signup_page():
    """ Signup page

    @return: rendering for the signup page
    """
    return render_template('signup.html')

@app.route('/signup', methods=['POST'])
def signup():
    """ Signup endpoint

    Attempt to register a user to the website
    @return: A json response containing an email field upon success or an
    error description
    """
    if not Config.EMAIL_REGEX.match(request.form['email']):
        return jsonify(error='Invalid email address')

    elif User.objects(email=request.form['email']).count() > 0:
        return jsonify(error='Already registered email')

    else:
        user = User.create_and_store(
            request.form['email'],
            request.form['password']
        )
        return jsonify(email=user.email)

@app.route('/login', methods=['GET'])
@redirect_app
def login_page():
    """ Login page

    @return: rendering for the login page
    """
    return render_template('login.html')

@app.route('/login', methods=['POST'])
def login():
    """ login endpoint

    Attempt to login a user
    @return: A json response containing an email field upon success or an
    error description
    """
    token = generate_token(request.form['email'], request.form['password'])
    if token:
        session['token'] = str(token.id)
        return jsonify(email=token.user.email)
    else:
        return jsonify(error='Invalid credentials')

@app.route('/forgot-password', methods=['GET'])
@redirect_app
def forgot_password_page():
    """ The forgot password page

    Render the forgot password page if a mailgun configuration is provided
    @return: rendering for the forgot password page if a mailgun client is
    configured or abort with 404 (not found) status code
    """
    if app.mailgun_client:
        return render_template('forgot-password.html')
    else:
        return abort(404)

@app.route('/forgot-password', methods=['POST'])
def forgot_password():
    """ The forgot password endpoint

    Attempt to create an url to be sent for password reset
    @return: The json answer {'success': true} upon successful email generation
    and sending  or an error description. If no mailgun configuration is
    provided abort with 404 (not found) status code
    """
    if not app.mailgun_client:
        abort(404)

    users = User.objects(email=request.form['email'])
    if users.count() == 0:
        return jsonify(error='Unknown email')
    user = users.first()
    if PasswordRenewToken.objects(user=user).count() > 0:
        renew_token = PasswordRenewToken.objects(user=user).first()
        hashed = b64encode(str(renew_token.id))
    else:
        renew_token = PasswordRenewToken(user=user)
        renew_token.save()
        hashed = b64encode(str(renew_token.id))
    reset_link = url_for('password_reset', token=hashed, _external=True)

    answer = send_password_reset(
        app.mailgun_client,
        app.config['MAILGUN_MAILING_ADDRESS'],
        user.email,
        render_template('email/forgot-password.txt', link=reset_link),
        render_template('email/forgot-password.html', link=reset_link)
    )

    if answer.status_code == 200:
        return jsonify(success=True)
    else:
        return jsonify(error='Unable to sent mail')

@app.route('/password-reset/<token>', methods=['GET'])
@redirect_app
def password_reset_page(token):
    """ Password reset page

    @param token: the token url parameter
    @return: rendering for the password reset page
    """
    return render_template('password-reset.html', token=token)

@app.route('/password-reset/<token>', methods=['POST'])
@redirect_app
def password_reset(token):
    """ Password reset endpoint

    @param token: the token url parameter
    @return: The json answer {'success': true} upon successful reset or an error
    description
    """
    try:
        clear = b64decode(token)
    except TypeError:
        return jsonify(error='Invalid reset request')

    p_token = PasswordRenewToken.objects(id=clear)
    if p_token.count() == 0:
        return jsonify(error='Invalid reset request')
    p_token.first().user.update_password(request.form['password'])
    p_token.delete()
    return jsonify(success=True)

@app.route('/logout', methods=['GET'])
@requires_token
def logout():
    """ The logout endpoint

    Attempt to logout the user and delete its token from mongoDB
    @return: {'success': true} upon successful logout or a 401 (Unauthorized)
    response caused by the requires_token wrapper
    """
    Token.objects(id=session['token']).delete()
    session.pop('token', None)
    return jsonify(success=True)

@app.route('/request-beta-access', methods=['POST'])
def request_beta_access():
    """ request beta access endpoint

    Attempt to add the user to the beta request list
    @return: {'success': true} upon successful subscription or an error
    description
    """
    email = request.form['email']
    if not Config.EMAIL_REGEX.match(email):
        return jsonify(error='Invalid email address')

    if User.objects(email=email).count() == 0 and BetaRequest.objects(
            email=email).count() == 0:
        BetaRequest(email=email, code=request.form['promo_code']).save()
        return jsonify(success=True)
    else:
        return jsonify(error='Already registered')

configure_app()
if __name__ == '__main__':
    app.run(threaded=True)
<|endoftext|>