Changeset - fa77922d3ae3
[Not reviewed]
Merge default
0 2 0
Dennis Fink - 9 years ago 2016-05-16 17:30:34
dennis.fink@c3l.lu
Merge hotfix-update-flags
2 files changed with 4 insertions and 1 deletions:
0 comments (0 inline, 0 general)
ennstatus/api/model.py
Show inline comments
 
# Ënnstatus
 
# Copyright (C) 2015  Dennis Fink
 
#
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import ipaddress
 
import json
 
import functools
 
import statistics
 

	
 
from pathlib import Path
 
from datetime import datetime
 

	
 
import jsonschema
 
import strict_rfc3339
 

	
 
from flask import current_app
 
from pkg_resources import resource_filename
 
from onion_py.manager import Manager
 
from onion_py.caching import OnionSimpleCache
 

	
 
from ..utils import check_ip
 

	
 

	
 
schema = json.load(
 
    open(
 
        resource_filename('ennstatus.api', 'schema/server.json'),
 
        encoding='utf-8'
 
    )
 
)
 

	
 
validate = functools.partial(
 
    jsonschema.validate,
 
    schema=schema,
 
    format_checker=jsonschema.FormatChecker()
 
)
 

	
 
manager = Manager(OnionSimpleCache())
 

	
 

	
 
def calculate_weight(data):
 

	
 
    obj = {}
 

	
 
    for subkey in ('1_week', '1_month', '3_months', '1_year', '5_years'):
 

	
 
        try:
 
            subdata = data[subkey]
 
        except KeyError:
 
            continue
 

	
 
        factor = subdata.factor
 

	
 
        values = [x * factor for x in subdata.values if x is not None]
 

	
 
        if values:
 
            obj[subkey] = statistics.mean(values) * 100
 
        else:
 
            obj[subkey] = None
 

	
 
    return obj
 

	
 

	
 
class ServerEncoder(json.JSONEncoder):
 

	
 
    def default(self, obj):
 

	
 
        if isinstance(obj, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
 
            return str(obj)
 

	
 
        if isinstance(obj, datetime):
 
            return strict_rfc3339.timestamp_to_rfc3339_utcoffset(
 
                obj.timestamp()
 
            )
 

	
 
        return json.JSONEncoder.default(self, obj)
 

	
 

	
 
class ServerDecoder(json.JSONDecoder):
 

	
 
    def decode(self, json_string):
 

	
 
        default_obj = super().decode(json_string)
 

	
 
        for key in ('ip', 'ip6'):
 
            if key in default_obj:
 
                current_app.logger.debug('{}: {}'.format(
 
                    key, default_obj[key]
 
                )
 
                )
 
                default_obj[key] = ipaddress.ip_address(default_obj[key])
 

	
 
        current_app.logger.debug('Loading last_updated')
 
        default_obj['last_updated'] = datetime.fromtimestamp(
 
            strict_rfc3339.rfc3339_to_timestamp(default_obj['last_updated'])
 
        )
 

	
 
        return default_obj
 

	
 

	
 
class Server:
 

	
 
    def __init__(self, *args, **kwargs):
 

	
 
        self.name = kwargs['name']
 
        self.type = kwargs['type']
 
        self.status = kwargs.get('status')
 
        self.fingerprint = kwargs['fingerprint']
 
        self.last_updated = kwargs['last_updated']
 
        self.country = kwargs['country']
 
        self.bandwidth = kwargs.get('bandwidth')
 
        self.flags = kwargs.get('flags')
 

	
 
        if self.type == 'bridge':
 
            self.obfs = kwargs.get('obfs')
 
            self.fteproxy = kwargs.get('fteproxy')
 
            self.flashproxy = kwargs.get('flashproxy')
 
            self.meek = kwargs.get('meek')
 
        else:
 
            self.ip = kwargs['ip']
 

	
 
            if 'ip6' in kwargs:
 
                self.ip6 = kwargs['ip6']
 

	
 
            default_weights = {
 
                '1_week': None,
 
                '1_month': None,
 
                '3_months': None,
 
                '1_year': None,
 
                '5_years': None
 
            }
 

	
 
            self.mean_consensus_weight = kwargs.get(
 
                'mean_consensus_weight',
 
                default_weights
 
            )
 
            self.mean_guard_probability = kwargs.get(
 
                'mean_guard_probability',
 
                default_weights
 
            )
 
            self.mean_exit_probability = kwargs.get(
 
                'mean_exit_probability',
 
                default_weights
 
            )
 
            self.mean_consensus_weight_fraction = kwargs.get(
 
                'mean_consensus_weight_fraction',
 
                default_weights
 
            )
 
            self.mean_middle_probability = kwargs.get(
 
                'mean_middle_probability',
 
                default_weights
 
            )
 

	
 
    @classmethod
 
    def from_file_by_name(cls, name):
 

	
 
        filepath = Path('data') / (name.lower() + '.json')
 

	
 
        current_app.logger.info('Loading {}'.format(str(filepath)))
 
        if filepath.exists() and filepath.is_file():
 
            try:
 
                with filepath.open(encoding='utf-8') as f:
 
                    data = json.load(f, cls=ServerDecoder)
 
            except (IOError, ValueError):
 
                current_app.logger.error('IOError or ValueError')
 
                return False
 
            else:
 
                return cls(**data)
 
        else:
 
            current_app.logger.error('File error!')
 
            return False
 

	
 
    @classmethod
 
    def from_json(cls, server):
 

	
 
        try:
 
            if cls.check_json_format(json.loads(server)):
 
                decoded = json.loads(server, cls=ServerDecoder)
 
                return cls(**decoded)
 
        except (jsonschema.ValidationError, ValueError) as e:
 
            raise e
 

	
 
    @classmethod
 
    def from_dict(cls, server):
 
        return cls.from_json(json.dumps(server))
 

	
 
    def json(self):
 
        return json.dumps(self.__dict__, cls=ServerEncoder)
 

	
 
    @staticmethod
 
    def check_json_format(server):
 

	
 
        try:
 
            validate(server)
 
        except jsonschema.ValidationError as e:
 
            raise e
 

	
 
        for key in ('ip', 'ip6'):
 
            if key in server:
 
                address = ipaddress.ip_address(server[key])
 
                if not check_ip(address):
 
                    raise ValueError('{} is not accepted!\n'.format(key))
 
        return True
 

	
 
    def save(self):
 

	
 
        filepath = Path('data') / (self.name.lower() + '.json')
 

	
 
        try:
 
            with filepath.open(mode='w', encoding='utf-8') as f:
 
                json.dump(self.__dict__, f, cls=ServerEncoder)
 
        except Exception as e:
 
            raise e
 

	
 
    def update_weights(self):
 

	
 
        if self.type not in ('exit', 'relay'):
 
            raise NotImplementedError
 

	
 
        try:
 
            data = manager.query('weights', lookup=self.fingerprint)
 
        except:
 
            raise NotImplementedError
 

	
 
        if data is not None:
 
            data = data.relays[0]
 

	
 
        self.mean_consensus_weight = calculate_weight(data.consensus_weight)
 
        self.mean_exit_probability = calculate_weight(data.exit_probability)
 
        self.mean_guard_probability = calculate_weight(
 
            data.guard_probability
 
        )
 
        self.mean_middle_probability = calculate_weight(
 
            data.middle_probability
 
        )
 
        self.mean_consensus_weight_fraction = calculate_weight(
 
            data.consensus_weight_fraction
 
        )
 

	
 
    def update_flags(self):
 

	
 
        try:
 
            data = manager.query('details', lookup=self.fingerprint)
 
        except:
 
            raise NotImplementedError
 

	
 
        try:
 
        if data is not None:
 
            self.flags = data.relays[0].flags
 
        else:
 
            raise NotImplementedError
 
        except IndexError as e:
 
            raise NotImplementedError from e
 

	
 
    def check_status(self):
 

	
 
        now = datetime.utcnow()
 
        delta = now - self.last_updated
 

	
 
        if delta.seconds >= 3600:
 
            self.status = False
 
        elif delta.seconds >= 600:
 
            self.status = None
setup.py
Show inline comments
 
from setuptools import setup, find_packages
 

	
 

	
 
def _get_requirements():
 

	
 
    with open('requirements.in', encoding='utf-8') as f:
 
        lines = f.readlines()
 

	
 
    lines = [line[:-1] for line in lines if not line.startswith('#')]
 
    return lines
 

	
 

	
 
setup(name='Ennstatus',
 
      version='5.4.6',
 
      version='5.4.7',
 
      description=('Ennstatus provides the user with vital information about '
 
                   'the status of the organizations Tor servers.'),
 
      author='Frënn vun der Ënn',
 
      author_email='info@enn.lu',
 
      url='https://bitbucket.org/fvde/ennstatus',
 
      license='GPLv3+',
 
      packages=find_packages(),
 
      install_requires=_get_requirements(),
 
      include_package_data=True,
 
      entry_points={
 
          'console_scripts': [
 
              'ennstatuscli = ennstatus.cli:cli',
 
          ]
 
      },
 
      classifiers=['Development Status :: 5 - Production/Stable',
 
                   'Environment :: Web Environment',
 
                   'Operating System :: POSIX',
 
                   'Programming Language :: Python',
 
                   'Programming Language :: Python :: 3',
 
                   'Programming Language :: Python :: 3.3',
 
                   'Topic :: Internet',
 
                   'Topic :: Internet :: WWW/HTTP',
 
                   'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
 
                   'Topic :: Internet :: WWW/HTTP :: WSGI',
 
                   'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
 
                   ('License :: OSI Approved :: '
 
                    'GNU General Public License v3 or later (GPLv3+)'),
 
                   ]
 
      )
0 comments (0 inline, 0 general)