# zot/object.py
#
#
""" basic package for the program. """
__copyright__ = "Copyright 2015, B.H.J Thate"
## IMPORTS
from zot.utils import get_strace, smooth, pretty, get_plugname, get_name, elapsed_days, get_type
from zot.utils import short_date, j, mj, rtime, a_time, str_day, cdir, nr_days, check_permissions
from zot.utils import headertxt, txt_parse, list_files, error, get_func
from zot.defines import hostname, port
from zot.errors import WrongSignature
from zot import __version__
import threading
import hashlib
import logging
import errno
import fcntl
import types
import json
import time
import os
## BASE
[docs]class Object(dict):
""" basic Object 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 == "timed": return zelf.get_timed()
if name == "parsed": return zelf.get_parsed()
if name == "type": return get_type(zelf)
if name == "url": return zelf.get_url("get")
if name == "url_show": return zelf.get_url("show")
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):
from zot.runtime 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):
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)])
## STATE
[docs] def define(zelf, *args, **kwargs):
""" set a attribute on this object. """
name = args[0]
value = args[1]
zelf[name] = value
[docs] def register(zelf, *args, **kwargs):
""" callback type with corresponding callback function. """
o = Object()
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" % get_name(obj))
zelf[o.cmnd].append(o)
logging.debug("# %s/register %s %s" % (zelf.type, o.cmnd, get_name(o.func)))
[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 check(zelf, *args, **kwargs):
""" determine the command in the zelf.txt attribute, if present. """
from zot.runtime import kernel
if "txt" in zelf and zelf.txt:
splitted = zelf.txt.split()
if not splitted: return
val = splitted[0]
if "cc" in zelf:
if val[0] != zelf.cc: return False
val = val[1:]
try: kernel[val] ; return val
except KeyError: pass
return False
[docs] def rest(zelf, *args, **kwargs):
""" get the rest of the txt attribute. """
try: cmnd, rest = zelf.txt.split(" ", 1) ; return rest
except ValueError: return ""
[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 object's creation time. """
t1 = time.time()
t2 = a_time(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 object. """
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
return val
[docs] def get_timed(zelf, *args, **kwargs):
""" retrieve the creation time of an object. """
return short_date(zelf.dated()) or ""
[docs] def get_parsed(zelf, *args, **kwargs):
""" parse the txt attribute. """
if "cc" in zelf: parsed = txt_parse(zelf.txt, zelf.cc)
else: parsed = txt_parse(zelf.txt)
logging.debug("! %s/%s %s" % (zelf.type, "parsed", str(parsed)))
return parsed
[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 obj(zelf, *args, **kwargs):
""" cloned object, with only the proper keys used. """
res = Object()
for key in zelf.names(): res[key] = zelf[key]
return res
[docs] def slice(zelf, *args, **kwargs):
""" take a slice of the Object. """
o = Object()
arguments = args[0]
for arg in arguments:
try: o[arg] = zelf[arg]
except KeyError: continue
return o
[docs] def filetime(zelf, *args, **kwargs):
""" timestamp of related filename. """
return zelf._path.split(os.sep)[-1]
[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 object's file so that it can be retrieved when API server is running. """
from zot.runtime import cfg
if "workdir" in zelf: root = zelf.workdir
else: root = cfg.workdir
fn = os.path.normpath(zelf._path.split(root)[-1])
if fn: return "http://%s:%s/%s%s" % (hostname, cfg.port, args[0], fn)
[docs] def get_root(zelf, *args, **kwargs):
from zot.runtime 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 object 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 object 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 object. """
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
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 = Object()
todisk.data = zelf.obj()
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.info("# 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
txt = args[0]
if "channel" in zelf: zelf._target.say(zelf.channel, txt)
else: zelf._target.say(zelf.origin, txt)
[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):
if "keys" in kwargs: keys = kwargs["keys"]
else: keys = zelf.names()
txt = " ".join([str(getattr(zelf, str(key), "")) for key in keys])
txt = txt.rstrip() + " - %s" % zelf.days()
return txt
[docs] def display_list(zelf, *args, **kwargs):
try: index = zelf.index
except AttributeError: index = None
nr = -1
for obj in args[0]:
nr += 1
if index and nr != index: continue
zelf.reply("%s %s" % (nr, obj.display(*args, **kwargs)))
[docs] def reply_list(zelf, *args, **kwargs):
try: index = zelf.parsed.index
except AttributeError: index = None
nr = -1
for obj in args[0]:
nr += 1
if index and nr != index: continue
zelf.reply(obj.format(nr, *args, **kwargs) + "\n")
## ITERATORS
[docs] def all(zelf, *args, **kwargs):
result = []
if args: key = args[0]
else: key = ""
for o in zelf.objects(full=True):
if "data" in o and key in o.data: result.append(o)
elif key in o: result.append(o)
return result
[docs] def objects(zelf, *args, **kwargs):
""" list of all object's. """
if "time" in kwargs: desired_time = kwargs["time"]
else: desired_time = ""
objs = []
if args: path = args[0]
else: path = zelf.get_path()
nr = 1
skipped = 0
for fnn in list_files(path):
if os.path.isdir(fnn): objs.extend(zelf.objects(fnn)) ; continue
try: obj = Object().load(fnn)
except UnicodeDecodeError as ex: logging.info("%s %s" % (fnn, str(ex))); continue
if not obj: logging.error("noload %s" % fnn) ; continue
if desired_time and desired_time not in obj.timed(): continue
if "full" not in kwargs and "deleted" in obj and obj.deleted: skipped += 1 ; continue
objs.append(obj)
nr += 1
logging.info("# objects %s skipped %s" % (nr, skipped))
return objs
[docs] def full(zelf, *args, **kwargs):
parsed = args[0]
objs = []
result = []
skipped = 0
for obj in zelf.objects(**kwargs):
if obj.check_notwanted(parsed.not_wanted): continue
if "uniq" in parsed.switch:
if obj[parsed.switch["uniq"]] in result: continue
else: result.append(obj[parsed.switch["uniq"]])
objs.append(obj)
return objs
[docs] def selected(zelf, *args, **kwargs):
""" list of desired objects. """
if args: parsed = args[0]
else: parsed = zelf.parsed
if not parsed.args: return []
if "exclude" in kwargs: exclude = kwargs["exclude"]
else: exclude = None
if "exclude" in parsed.args: parsed.args.remove(exclude)
if "time" in parsed.wanted: kwargs["time"] = parsed.wanted.time
objs = []
result = []
nr = -1
for obj in zelf.objects(**kwargs):
nr += 1
if not obj.check_wanted(parsed.wanted): continue
if not obj.selector(parsed.args): continue
if obj.check_notwanted(parsed.not_wanted): continue
if "uniq" in parsed.switch:
if obj[parsed.switch["uniq"]] in result: continue
else: result.append(obj[parsed.switch["uniq"]])
if "index" in parsed and nr == parsed.index: return [obj, ]
objs.append(obj)
return objs
## SELECTOR
[docs] def selector(zelf, *args, **kwargs):
""" see if this objects has the desired attributes. """
wanted = args[0]
if not wanted: return True
go = False
for w in wanted:
if zelf.get(w, ""): go = True ; break
return go
## TIME RELATED
[docs] def first(zelf, *args, **kwargs):
""" return the first object where the key and/or value matches. """
key = args[0]
try: value = args[1]
except IndexError: value = None
result = None
for o in zelf.objects():
if key not in o: continue
if value and o[key] != value: continue
if not result: result = o
if o.get("timed", "") < result.get("timed", ""): result = o
return result
[docs] def last(zelf, *args, **kwargs):
""" return the last object where the key and/or value matches. """
key = args[0]
try: value = args[1]
except IndexError: value = None
result = None
for o in zelf.objects():
if key not in o: continue
if value and value != o[key]: continue
if not result: result = o
if o.timed > result.timed: result = o
logging.info("last %s %s" % (key, value))
return result
## WAITERS
[docs] def ready(zelf):
""" signal to ready state. """
logging.debug("! %s/ready %s %s" % (zelf.type, str(zelf._ready), get_func()))
zelf._ready.set()
[docs] def clear(zelf):
""" clear the ready state. """
logging.debug("! %s/clear %s" % (zelf.type, str(zelf._ready)))
zelf._ready.clear()
[docs] def wait(zelf, sec=180.0):
""" wait for ready state. """
logging.debug("! %s/wait %s %s" % (zelf.type, str(zelf._ready), get_func()))
zelf._ready.wait(sec)
## HELPERS
[docs] def json(zelf, *args, **kwargs):
""" JSON string representation of this object. """
return json.dumps(zelf.obj(*args, **kwargs), default=smooth, *args, **kwargs)
[docs] def to_full(zelf, *args, **kwargs):
""" full JSON dump of this object. """
return json.dumps(zelf, default=smooth, indent=4)
[docs] def make_signature(zelf):
""" signature of the data contained in this object. """
return str(hashlib.sha1(bytes(str(zelf), "utf-8")).hexdigest())
[docs] def pretty(zelf, *args, **kwargs):
""" nice formatted JSON string of this object. """
return json.dumps(zelf.obj(), indent=2, default=pretty, sort_keys=True, ensure_ascii=True)