1from datetime import datetime, timezone
2
3from _sequoia import ffi, lib
4from . import error
5
6def invoke(fun, *args):
7    """Invokes the given FFI function.
8
9    This function invokes the given FFI function.  It must only be
10    used for functions that expect an error pointer as first argument.
11
12    If an error is encountered, an exception is raised.
13    """
14    err = ffi.new("pgp_error_t[1]")
15    result = fun(err, *args)
16    if err[0] != ffi.NULL:
17        raise Error._from(err[0])
18    return result
19
20class SQObject(object):
21    # These class attributes determine what features the wrapper class
22    # implements.  They must be set to the relevant Sequoia functions.
23    #
24    # XXX: Once we can assume Python3.6 we can use '__init_subclass__'
25    # and reflection on the 'lib' object to set them automatically
26    # using the type name.
27    _del = None
28    _clone = None
29    _eq = None
30    _str = None
31    _debug = None
32    _hash = None
33
34    def __init__(self, o, context=None, owner=None, references=None):
35        if o == ffi.NULL:
36            raise error.Error._last(context)
37        self.__o = None
38        self.ref_replace(o, owner=owner, references=references)
39        self.__ctx = context
40        if self.__class__._hash is None and not hasattr(self.__class__, '__hash__'):
41            # Unhashable types must have '__hash__' set to None.
42            # Until we can use '__init_subclass__', we need to patch
43            # the class here.  Yuck.
44            self.__class__.__hash__ = None
45
46    def ref(self):
47        return self.__o
48
49    def ref_consume(self):
50        ref = self.ref()
51        self._delete(skip_free=True)
52        return ref
53
54    def ref_replace(self, new, owner=None, references=None):
55        old = self.ref_consume()
56        if self._del and owner == None:
57            # There is a destructor and We own the referenced object
58            # new.
59            self.__o = ffi.gc(new, self._del)
60        else:
61            self.__o = new
62        self.__owner = owner
63        self.__references = references
64        return old
65
66    def _delete(self, skip_free=False):
67        if not self.__o:
68            return
69        if self._del and skip_free:
70            ffi.gc(self.__o, None)
71        self.__o = None
72        self.__owner = None
73        self.__references = None
74
75    def context(self):
76        return self.__ctx
77
78    def __str__(self):
79        if self._str:
80            return _str(self._str(self.ref()))
81        else:
82            return repr(self)
83
84    def __eq__(self, other):
85        if self._eq:
86            return (isinstance(other, self.__class__)
87                    and bool(self._eq(self.ref(), other.ref())))
88        else:
89            return NotImplemented
90
91    def copy(self):
92        if self._clone:
93            return self.__class__(self._clone(self.ref()))
94        else:
95            raise NotImplementedError()
96
97    def __hash__(self):
98        return self._hash(self.ref())
99
100    def debug(self):
101        if self._debug:
102            return _str(self._debug(self.ref()))
103        else:
104            raise NotImplementedError()
105
106def sq_str(s):
107    t = ffi.string(s).decode()
108    lib.free(s)
109    return t
110_str = sq_str
111
112def sq_static_str(s):
113    return ffi.string(s).decode()
114_static_str = sq_static_str
115
116def sq_iterator(iterator, next_fn, map=lambda x: x):
117    while True:
118        entry = next_fn(iterator)
119        if entry == ffi.NULL:
120            break
121        yield map(entry)
122
123def sq_time(t):
124    if t == 0:
125        return None
126    else:
127        return datetime.fromtimestamp(t, timezone.utc)
128