"""Redis database engine."""
import time
import logging
import datetime
import functools
try:
import redis
_has_redis = True
except ImportError:
redis = None
_has_redis = False
from pyzor.engines.common import *
VERSION = "1"
NAMESPACE = "pyzord.digest_v%s" % VERSION
def encode_date(date):
"""Convert the date to Unix Timestamp"""
if date is None:
return 0
return int(time.mktime(date.timetuple()))
def decode_date(stamp):
"""Return a datetime object from a Unix Timestamp."""
stamp = int(stamp)
if stamp == 0:
return None
return datetime.datetime.fromtimestamp(stamp)
def safe_call(f):
"""Decorator that wraps a method for handling database operations."""
def wrapped_f(self, *args, **kwargs):
# This only logs the error and raise the usual Error for consistency,
# the redis library takes care of reconnecting and everything else.
try:
return f(self, *args, **kwargs)
except redis.exceptions.RedisError as e:
self.log.error("Redis error while calling %s: %s",
f.__name__, e)
raise DatabaseError("Database temporarily unavailable.")
return wrapped_f
class RedisDBHandle(BaseEngine):
absolute_source = False
handles_one_step = True
log = logging.getLogger("pyzord")
def __init__(self, fn, mode, max_age=None):
self.max_age = max_age
# The 'fn' is host,port,password,db. We ignore mode.
# We store the authentication details so that we can reconnect if
# necessary.
self._dsn = fn
fn = fn.split(",")
self.host = fn[0] or "localhost"
self.port = fn[1] or "6379"
self.passwd = fn[2] or None
self.db_name = fn[3] or "0"
self.db = self._get_new_connection()
self._check_version()
@staticmethod
def _encode_record(r):
return {"r_count": r.r_count,
"r_entered": encode_date(r.r_entered),
"r_updated": encode_date(r.r_updated),
"wl_count": r.wl_count,
"wl_entered": encode_date(r.wl_entered),
"wl_updated": encode_date(r.wl_updated)
}
@staticmethod
def _decode_record(r):
if not r:
return Record()
return Record(r_count=int(r.get(b"r_count", 0)),
r_entered=decode_date(r.get(b"r_entered", 0)),
r_updated=decode_date(r.get(b"r_updated", 0)),
wl_count=int(r.get(b"wl_count", 0)),
wl_entered=decode_date(r.get(b"wl_entered", 0)),
wl_updated=decode_date(r.get(b"wl_updated", 0)))
def __iter__(self):
for key in self.db.keys(self._real_key("*")):
yield key.rsplit(".", 1)[-1]
def _iteritems(self):
for key in self:
try:
yield key, self[key]
except Exception as ex:
self.log.warning("Invalid record %s: %s", key, ex)
def iteritems(self):
return self._iteritems()
def items(self):
return list(self._iteritems())
@staticmethod
def _real_key(key):
return "%s.%s" % (NAMESPACE, key)
@safe_call
def _get_new_connection(self):
if "/" in self.host:
return redis.StrictRedis(unix_socket_path=self.host,
db=int(self.db_name), password=self.passwd)
return redis.StrictRedis(host=self.host, port=int(self.port),
db=int(self.db_name), password=self.passwd)
@safe_call
def __getitem__(self, key):
return self._decode_record(self.db.hgetall(self._real_key(key)))
@safe_call
def __setitem__(self, key, value):
real_key = self._real_key(key)
self.db.hmset(real_key, self._encode_record(value))
if self.max_age is not None:
self.db.expire(real_key, self.max_age)
@safe_call
def __delitem__(self, key):
self.db.delete(self._real_key(key))
@safe_call
def report(self, keys):
now = int(time.time())
for key in keys:
real_key = self._real_key(key)
self.db.hincrby(real_key, "r_count")
self.db.hsetnx(real_key, "r_entered", now)
self.db.hset(real_key, "r_updated", now)
if self.max_age:
self.db.expire(real_key, self.max_age)
@safe_call
def whitelist(self, keys):
now = int(time.time())
for key in keys:
real_key = self._real_key(key)
self.db.hincrby(real_key, "wl_count")
self.db.hsetnx(real_key, "wl_entered", now)
self.db.hset(real_key, "wl_updated", now)
if self.max_age:
self.db.expire(real_key, self.max_age)
@classmethod
def get_prefork_connections(cls, fn, mode, max_age=None):
"""Yields a number of database connections suitable for a Pyzor
pre-fork server.
"""
while True:
yield functools.partial(cls, fn, mode, max_age=max_age)
def _check_version(self):
"""Check if there are deprecated records and warn the user."""
old_keys = len(self.db.keys("pyzord.digest.*"))
if old_keys:
cmd = ("pyzor-migrate --delete --se=redis_v0 --sd=%s "
"--de=redis --dd=%s" % (self._dsn, self._dsn))
self.log.critical("You have %s records in the deprecated version "
"of the redis engine.", old_keys)
self.log.critical("Please migrate the records with: %r", cmd)
class ThreadedRedisDBHandle(RedisDBHandle):
def __init__(self, fn, mode, max_age=None, bound=None):
RedisDBHandle.__init__(self, fn, mode, max_age=max_age)
if not _has_redis:
handle = DBHandle(single_threaded=None,
multi_threaded=None,
multi_processing=None,
prefork=None)
else:
handle = DBHandle(single_threaded=RedisDBHandle,
multi_threaded=ThreadedRedisDBHandle,
multi_processing=None,
prefork=RedisDBHandle)
Copyright 2K16 - 2K18 Indonesian Hacker Rulez