# core/thing.py
#
#
""" basic package for the program. """
__copyright__ = "Copyright 2015, B.H.J Thate"
## IMPORTS
from core.utils.time import elapsed_days, short_date, rtime, a_time, fn_time, to_day, get_day
from core.utils.file import cdir, check_permissions, list_files
from core.utils.trace import get_plugname, get_location
from core.utils.serialize import smooth, pretty
from core.utils.name import name, typed
from core.utils.parse import txt_parse
from core.utils.other import headertxt
from core.defines import hostname
from core.utils.join import j
import threading
import hashlib
import logging
import errno
import fcntl
import json
import time
import os
## BASE
[docs]class Thing(dict):
""" basic Thing on which the rest of the program is based. """
def __getattr__(zelf, name):
if name in zelf: return zelf[name]
if name == "_ready": zelf._ready = threading.Event()
if name == "skip": return ["parsed", ]
if name == "url": return zelf.get_url()
if name == "timed": return zelf.get_timed()
if name == "cc": return "!"
if name == "type": return typed(zelf)
if name not in zelf: raise AttributeError(name)
return zelf[name]
def __contains__(zelf, name):
try: zelf[name] ; return True
except KeyError: return False
def __setattr__(zelf, name, value): return dict.__setitem__(zelf, name, value)
[docs] def announce(zelf, *args, **kwargs):
"""
Announce a text on all channels of all bots.
args[0] = "text to announce"
>>> import core.bots
>>> bot = core.bots.Bot()
>>> bot.announce("hello")
hello
"""
from core.kernel import fleet
thrs = []
for bot in fleet: thrs.append(zelf.put(bot.announce, *args, **kwargs))
if thrs: zelf.collect(thrs)
return thrs
## FLEET
[docs] def search(zelf, *args, **kwargs):
"""
search all attribute names.
args[0] = "attribute name to search for"
>>> from core.thing import Thing
>>> thing = Thing()
>>> thing.data = "test"
>>> thing.search("da")
['data']
"""
l = []
for key in zelf.names():
if args[0] in key: l.append(key)
return l
[docs] def represent(zelf, *args, **kwargs):
return ", ".join([str(getattr(zelf, x, None)) for x in zelf.names() if getattr(zelf, x, None)])
[docs] def highest(zelf, *args, **kwargs):
name = ""
upper = 0
for key, value in zelf.items():
if int(value) > upper: upper = int(value) ; name = key
return name
[docs] def lowest(zelf, *args, **kwargs):
name = ""
lower = 100000000
for key, value in zelf.items():
if int(value) < lower: lower = int(value) ; name = key
return name
## STATE
[docs] def define(zelf, *args, **kwargs):
""" set a attribute on this ding. """
name = args[0]
value = args[1]
zelf[name] = value
[docs] def register(zelf, *args, **kwargs):
""" callback type with corresponding callback function. """
o = Thing()
o.cmnd = args[0]
o.func = args[1]
o.plugname = get_plugname()
if o.cmnd not in zelf: zelf[o.cmnd] = []
todo = []
for obj in zelf[o.cmnd]:
if obj.cmnd == o.cmnd and obj.plugname == o.plugname: todo.append(obj)
for obj in todo: zelf[o.cmnd].remove(obj) ; logging.info("! remove %s" % name(obj))
zelf[o.cmnd].append(o)
logging.debug("- %s/register %s" % (zelf.type, o.cmnd))
[docs] def remove(zelf, *args, **kwargs):
name = args[0]
if name in zelf: del zelf[name]
zelf[name] = []
## GETTERS
[docs] def find(zelf, *args, **kwargs):
rest = args[0]
result = []
for x in set(zelf.search(rest)):
if x.split(".")[-1] != rest: continue
obj = zelf.get(x)
if type(obj) in [list, tuple]: result.extend(obj) ; continue
result.append(obj)
return result
[docs] def by_key(zelf, *args, **kwargs):
want = args[0]
for key in zelf:
if want in key: yield key
[docs] def words(zelf, *args, **kwargs):
""" get the arguments of the txt attribute. """
if "txt" in zelf: return zelf.txt.split()
[docs] def days(zelf, *args, **kwargs):
""" get the number of days relative to the ding's creation time. """
t1 = time.time()
t2 = zelf.get_timed()
if t2:
time_diff = float(t1 - t2)
return elapsed_days(time_diff)
[docs] def dated(zelf, *args, **kwargs):
""" retrieve the creation time of an ding. """
val = ""
if "Date" in zelf: val = zelf.Date
elif "date" in zelf: val = zelf.date
elif "published" in zelf: val = zelf.published
elif "added" in zelf: val = zelf.added
elif "saved" in zelf: val = zelf.saved
elif "timed" in zelf: val = zelf.timed
return val
[docs] def get_fn(zelf, *args, **kwargs): return os.sep.join(zelf._path.split(os.sep)[-2:])
[docs] def get_timed(zelf, *args, **kwargs):
""" retrieve the creation time of an ding. """
t = short_date(zelf.dated())
if t: t = a_time(t)
if not t and "_path" in zelf: t = fn_time(zelf._path)
if not t: t = fn_time(rtime())
return t
[docs] def get_parsed(zelf, *args, **kwargs):
""" parse the txt attribute. """
return txt_parse(zelf.txt, zelf.cc)
[docs] def names(zelf, *args, **kwargs):
""" skip the unwanted keys e.g those that start with a "_". """
for key in zelf.keys():
k = str(key)
if k.startswith("_"): continue
yield key
[docs] def clone(zelf, *args, **kwargs):
""" cloned ding, with only the proper keys used. """
res = Thing()
for key in zelf.names(): res[key] = zelf[key]
return res
[docs] def slice(zelf, *args, **kwargs):
""" take a slice of the Thing. """
o = Thing()
try: arguments = args[0]
except IndexError: arguments = zelf.names()
for arg in arguments:
try: o[arg] = zelf[arg]
except KeyError: continue
return o
[docs] def filedatetime(zelf, *args, **kwargs):
""" timestamp of related filename. """
return os.sep.join(zelf._path.split(os.sep)[-2:])
[docs] def filedate(zelf, *args, **kwargs):
""" timestamp of related filename. """
return zelf._path.split(os.sep)[-2]
[docs] def get_url(zelf, *args, **kwargs):
""" url of the Thing's file so that it can be retrieved when API server is running. """
from core.kernel import cfg
return "http://%s:%s/%s" % (hostname, cfg.port, zelf.filedatetime())
[docs] def get_root(zelf, *args, **kwargs):
from core.kernel import cfg
if "workdir" in zelf: root = zelf.workdir
else: root = cfg.workdir
path = os.path.abspath(root)
check_permissions(path)
return path
[docs] def get_path(zelf, *args, **kwargs):
root = zelf.get_root()
if "prefix" in zelf: root = j(root, zelf.prefix)
return os.path.abspath(root)
## CHECKERS
[docs] def check_wanted(zelf, *args, **kwargs):
""" whether an thing is desired. """
want = args[0]
for key, value in want.items():
if key == "format": continue
if key not in zelf: continue
if value.startswith("-"): continue
if value not in str(zelf[key]): return False
return True
[docs] def check_notwanted(zelf, *args, **kwargs):
""" whether an thing is not desired. """
not_want = args[0]
for key, value in not_want.items():
if key == "format": continue
if key not in zelf: continue
if value in zelf[key]: return True
return False
## INPUT
[docs] def load(zelf, *args, **kwargs):
""" load a JSON file into this ding. """
if args: path = args[0]
else: path = zelf._path
ondisk = zelf.read(path)
fromdisk = json.loads(ondisk)
if "data" in fromdisk: zelf.update(fromdisk["data"])
else: zelf.update(fromdisk)
if "saved" in fromdisk: zelf.saved = fromdisk["saved"]
zelf._path = path
logging.debug("load %s" % path)
return zelf
[docs] def read(zelf, *args, **kwargs):
""" read the JSON file from disk. """
path = args[0]
try: f = open(path, "r")
except IOError as ex:
if ex.errno == errno.ENOENT: return "{}"
raise
res = ""
for line in f:
if not line.strip().startswith("#"): res += line
if not res.strip(): return "{}"
f.close()
return res
## PERSISTENCE
[docs] def prepare(zelf, *args, **kwargs):
""" create JSON ready to be saved to disk. """
path = args[0]
todisk = Thing()
todisk.data = zelf.slice()
todisk.saved_from = get_plugname(2)
todisk.type = zelf.type
todisk.saved = zelf.saved = time.ctime(time.time())
todisk.signature = todisk.data.make_signature()
try: result = todisk.json(indent=2, ensure_ascii=False, sort_keys=True)
except TypeError: raise NoJSON()
return result
[docs] def save(zelf, *args, **kwargs):
""" save JSON to disk. """
if not args: t = rtime()
else: t = args[0]
zelf.sync(j(zelf.get_path(), t))
return t
[docs] def sync(zelf, *args, **kwargs):
""" sync JSON to disk. """
try: path = args[0]
except IndexError:
try: path = zelf._path
except AttributeError: path = zelf._path = j(zelf.get_path(), rtime())
logging.warn("! sync %s" % path)
d, fn = os.path.split(path)
cdir(d)
todisk = zelf.prepare(path, **kwargs)
datafile = open(os.path.abspath(path) + ".tmp", 'w')
fcntl.flock(datafile, fcntl.LOCK_EX | fcntl.LOCK_NB)
datafile.write(headertxt % "%s characters" % len(todisk))
datafile.write(todisk)
datafile.write("\n")
fcntl.flock(datafile, fcntl.LOCK_UN)
datafile.close()
os.rename(path + ".tmp", path)
return zelf
## OUTPUT
[docs] def reply(zelf, *args, **kwargs):
""" send reply to origin. """
if "outer" in zelf: zelf.outer.write(str(args[0]) + "\n") ; zelf.outer.flush() ; return
if "channel" in zelf: zelf.say(zelf.channel, args[0])
else: zelf.say(zelf.origin, args[0])
[docs] def say(zelf, *args, **kwargs): zelf._target.say(*args, **kwargs)
[docs] def ok(zelf, *args, **kwargs):
""" signal ok. """
if "_target" in zelf: zelf.reply("ok %s" % " ".join([str(a) for a in args]))
[docs] def show(zelf, *args, **kwargs):
""" list of key,value pairs. """
return ["%s=%s" % (a, zelf[a]) for a in sorted(zelf.names())]
[docs] def display(zelf, *args, **kwargs):
parsed = args[0]
keys = parsed.args
txt = " ".join([str(getattr(zelf, str(key), "")) for key in keys])
txt = txt.rstrip()
txt += " - %s" % zelf.days()
return txt
## ITERATORS
[docs] def filename(zelf, *args, **kwargs):
name = args[0]
for fn in zelf.all():
if fn.endswith(name): return fn
[docs] def all(zelf, *args, **kwargs):
fns = sorted(list_files(zelf.get_path(), *args, **kwargs), key=lambda fn: fn_time(" ".join(fn.split(os.sep)[-2:])))
res = []
for fn in fns:
if kwargs.get("time", "") not in fn: continue
res.append(fn)
return res
[docs] def first(zelf, *args, **kwargs):
fns = zelf.all(*args)
for fn in fns:
obj = Thing().load(fn)
if args and len(args) > 1 and args[1] != obj.get(args[0], ""): continue
return obj
[docs] def last(zelf, *args, **kwargs):
fns = zelf.all(*args)
res = None
for fn in fns:
obj = Thing().load(fn)
if args and len(args) > 1 and args[1] != obj.get(args[0], ""): continue
res = obj
return res
[docs] def selected(zelf, *args, **kwargs):
""" list of desired things. """
parsed = args[0]
objs = []
uniqlist = []
for fn in zelf.all(**parsed.wanted):
try: obj = Thing().load(fn)
except: logging.warn("fail %s" % fn) ; continue
if "deleted" in obj and obj.deleted: continue
if not obj.check_wanted(parsed.wanted): continue
if not obj.selector(parsed.args): continue
if obj.check_notwanted(parsed.not_wanted): continue
uniq = parsed.switch.get("uniq", "")
if uniq:
val = obj.get(uniq, "")
if val in uniqlist: continue
else: uniqlist.append(val)
objs.append(obj)
return objs
[docs] def period(zelf, *args, **kwargs):
parsed = args[0]
try: nrofdays = int(parsed.args[0])
except ValueError: return []
seconds = nrofdays * 60 *60 * 24
pasttime = time.time() - seconds
result = []
for fnn in list_files(zelf.get_path()):
date = fnn.split(os.sep)[-2]
stamp = to_day(date)
if stamp > pasttime: result.append(fnn)
return result
## SELECTOR
[docs] def selector(zelf, *args, **kwargs):
""" see if this things has the desired attributes. """
wanted = args[0]
if not wanted: return False
go = False
for arg in wanted:
if arg in zelf and zelf[arg]: go = True ; break
return go
## WAITERS
[docs] def ready(zelf):
""" signal to ready state. """
logging.debug("! %s/ready %s" % (zelf.type, get_location()))
zelf._ready.set()
[docs] def clear(zelf):
""" clear the ready state. """
logging.debug("! %s/clean %s" % (zelf.type, get_location()))
zelf._ready.clear()
[docs] def wait(zelf, sec=180.0):
""" wait for ready state. """
logging.debug("! %s/wait %s" % (zelf.type, get_location()))
zelf._ready.wait(sec)
## HELPERS
[docs] def json(zelf, *args, **kwargs):
""" JSON string representation of this ding. """
return json.dumps(zelf.slice(*args, **kwargs), default=smooth, *args, **kwargs)
[docs] def make_signature(zelf):
""" signature of the data contained in this ding. """
return str(hashlib.sha1(bytes(str(zelf), "utf-8")).hexdigest())
[docs] def pretty(zelf, *args, **kwargs):
""" nice formatted JSON string of this ding. """
return json.dumps(zelf.slice(), indent=2, default=pretty, sort_keys=True, ensure_ascii=True)