1# distutils: language = c++
2# distutils: extra_compile_args = -std=c++11
3# distutils: include_dirs = ../../include
4# distutils: library_dirs = ../../src
5# distutils: libraries = opendht gnutls
6# cython: language_level=3
7#
8# Copyright (c) 2015-2019 Savoir-faire Linux Inc.
9# Author(s): Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
10#            Adrien Béraud <adrien.beraud@savoirfairelinux.com>
11#            Simon Désaulniers <sim.desaulniers@gmail.com>
12#
13# This wrapper is written for Cython 0.22
14#
15# This file is part of OpenDHT Python Wrapper.
16#
17# OpenDHT Python Wrapper is free software:  you can redistribute it and/or modify
18# it under the terms of the GNU General Public License as published by
19# the Free Software Foundation, either version 3 of the License, or
20# (at your option) any later version.
21#
22# OpenDHT Python Wrapper is distributed in the hope that it will be useful,
23# but WITHOUT ANY WARRANTY; without even the implied warranty of
24# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25# GNU General Public License for more details.
26#
27# You should have received a copy of the GNU General Public License
28# along with OpenDHT Python Wrapper. If not, see <https://www.gnu.org/licenses/>.
29
30from libcpp.map cimport map as map
31from libcpp cimport bool
32from libcpp.utility cimport pair
33from libcpp.string cimport string
34from libcpp.memory cimport shared_ptr
35
36from cython.parallel import parallel, prange
37from cython.operator cimport dereference as deref, preincrement as inc, predecrement as dec
38from cpython cimport ref
39
40cimport opendht_cpp as cpp
41
42import threading
43
44cdef inline void lookup_callback(cpp.vector[cpp.shared_ptr[cpp.IndexValue]]* values, cpp.Prefix* p, void *user_data) with gil:
45    cbs = <object>user_data
46    if 'lookup' in cbs and cbs['lookup']:
47        vals = []
48        for val in deref(values):
49            v = IndexValue()
50            v._value = val
51            vals.append(v)
52        cbs['lookup'](vals, p.toString())
53
54cdef inline void shutdown_callback(void* user_data) with gil:
55    cbs = <object>user_data
56    if 'shutdown' in cbs and cbs['shutdown']:
57        cbs['shutdown']()
58    ref.Py_DECREF(cbs)
59
60cdef inline bool get_callback(shared_ptr[cpp.Value] value, void *user_data) with gil:
61    cbs = <object>user_data
62    cb = cbs['get']
63    f = cbs['filter'] if 'filter' in cbs else None
64    pv = Value()
65    pv._value = value
66    return cb(pv) if not f or f(pv) else True
67
68cdef inline void done_callback(bool done, cpp.vector[shared_ptr[cpp.Node]]* nodes, void *user_data) with gil:
69    node_ids = []
70    for n in deref(nodes):
71        h = NodeEntry()
72        h._v.first = n.get().getId()
73        h._v.second = n
74        node_ids.append(h)
75    cbs = <object>user_data
76    if 'done' in cbs and cbs['done']:
77        cbs['done'](done, node_ids)
78    ref.Py_DECREF(cbs)
79
80cdef inline void done_callback_simple(bool done, void *user_data) with gil:
81    cbs = <object>user_data
82    if 'done' in cbs and cbs['done']:
83        cbs['done'](done)
84    ref.Py_DECREF(cbs)
85
86cdef class _WithID(object):
87    def __repr__(self):
88        return "<%s '%s'>" % (self.__class__.__name__, str(self))
89    def __str__(self):
90        return self.getId().toString().decode()
91
92cdef class InfoHash(_WithID):
93    cdef cpp.InfoHash _infohash
94    def __cinit__(self, bytes str=b''):
95        self._infohash = cpp.InfoHash(str) if str else cpp.InfoHash()
96    def __bool__(InfoHash self):
97        return <bool>self._infohash
98    def __richcmp__(InfoHash self, InfoHash other, int op):
99        if op == 0:
100            return self._infohash < other._infohash
101        if op == 1:
102            return self._infohash < other._infohash or self._infohash == other._infohash
103        if op == 2:
104            return self._infohash == other._infohash
105        return NotImplemented
106    def getBit(InfoHash self, bit):
107        return self._infohash.getBit(bit)
108    def setBit(InfoHash self, bit, b):
109        self._infohash.setBit(bit, b)
110    def getId(InfoHash self):
111        return self
112    def toString(InfoHash self):
113        return self._infohash.toString()
114    def toFloat(InfoHash self):
115        return self._infohash.toFloat()
116    @staticmethod
117    def commonBits(InfoHash a, InfoHash b):
118        return cpp.InfoHash.commonBits(a._infohash, b._infohash)
119    @staticmethod
120    def get(str key):
121        h = InfoHash()
122        h._infohash = cpp.InfoHash.get(key.encode())
123        return h
124    @staticmethod
125    def getRandom():
126        h = InfoHash()
127        h._infohash = cpp.InfoHash.getRandom()
128        return h
129
130cdef class SockAddr(object):
131    cdef cpp.SockAddr _addr
132    def toString(SockAddr self):
133        return self._addr.toString()
134    def getPort(SockAddr self):
135        return self._addr.getPort()
136    def getFamily(SockAddr self):
137        return self._addr.getFamily()
138    def setPort(SockAddr self, cpp.in_port_t port):
139        return self._addr.setPort(port)
140    def setFamily(SockAddr self, cpp.sa_family_t af):
141        return self._addr.setFamily(af)
142    def isLoopback(SockAddr self):
143        return self._addr.isLoopback()
144    def isPrivate(SockAddr self):
145        return self._addr.isPrivate()
146    def isUnspecified(SockAddr self):
147        return self._addr.isUnspecified()
148    def __str__(self):
149        return self.toString().decode()
150    def __repr__(self):
151        return "<%s '%s'>" % (self.__class__.__name__, str(self))
152
153cdef class Node(_WithID):
154    cdef shared_ptr[cpp.Node] _node
155    def getId(self):
156        h = InfoHash()
157        h._infohash = self._node.get().getId()
158        return h
159    def getAddr(self):
160        return self._node.get().getAddrStr()
161    def isExpired(self):
162        return self._node.get().isExpired()
163
164cdef class NodeEntry(_WithID):
165    cdef cpp.pair[cpp.InfoHash, shared_ptr[cpp.Node]] _v
166    def getId(self):
167        h = InfoHash()
168        h._infohash = self._v.first
169        return h
170    def getNode(self):
171        n = Node()
172        n._node = self._v.second
173        return n
174
175cdef class Query(object):
176    cdef cpp.Query _query
177    def __cinit__(self, str q_str=''):
178        self._query = cpp.Query(q_str.encode())
179    def __str__(self):
180        return self._query.toString().decode()
181    def buildFrom(self, Select s, Where w):
182        self._query = cpp.Query(s._select, w._where)
183    def isSatisfiedBy(self, Query q):
184        return self._query.isSatisfiedBy(q._query)
185
186cdef class Select(object):
187    cdef cpp.Select _select
188    def __cinit__(self, str q_str=None):
189        if q_str:
190            self._select = cpp.Select(q_str.encode())
191        else:
192            self._select = cpp.Select()
193    def __str__(self):
194        return self._select.toString().decode()
195    def isSatisfiedBy(self, Select os):
196        return self._select.isSatisfiedBy(os._select)
197    def field(self, int field):
198        self._select.field(<cpp.Field> field)
199        return self
200
201cdef class Where(object):
202    cdef cpp.Where _where
203    def __cinit__(self, str q_str=None):
204        if q_str:
205            self._where = cpp.Where(q_str.encode())
206        else:
207            self._where = cpp.Where()
208    def __str__(self):
209        return self._where.toString().decode()
210    def isSatisfiedBy(self, Where where):
211        return self._where.isSatisfiedBy(where._where)
212    def id(self, cpp.uint64_t id):
213        self._where.id(id)
214        return self
215    def valueType(self, cpp.uint16_t type):
216        self._where.valueType(type)
217        return self
218    def owner(self, InfoHash owner_pk_hash):
219        self._where.owner(owner_pk_hash._infohash)
220        return self
221    def seq(self, cpp.uint16_t seq_no):
222        self._where.seq(seq_no)
223        return self
224    def userType(self, str user_type):
225        self._where.userType(user_type.encode())
226        return self
227
228cdef class Value(object):
229    cdef shared_ptr[cpp.Value] _value
230    def __init__(self, bytes val=b''):
231        self._value.reset(new cpp.Value(val, len(val)))
232    def __str__(self):
233        return self._value.get().toString().decode()
234    property owner:
235        def __get__(self):
236            h = InfoHash()
237            h._infohash = self._value.get().owner.get().getId()
238            return h
239    property recipient:
240        def __get__(self):
241            h = InfoHash()
242            h._infohash = self._value.get().recipient
243            return h
244        def __set__(self, InfoHash h):
245            self._value.get().recipient = h._infohash
246    property data:
247        def __get__(self):
248            return string(<char*>self._value.get().data.data(), self._value.get().data.size())
249        def __set__(self, bytes value):
250            self._value.get().data = value
251    property user_type:
252        def __get__(self):
253            return self._value.get().user_type.decode()
254        def __set__(self, str t):
255            self._value.get().user_type = t.encode()
256    property id:
257        def __get__(self):
258            return self._value.get().id
259        def __set__(self, cpp.uint64_t value):
260            self._value.get().id = value
261    property size:
262        def __get__(self):
263            return self._value.get().size()
264
265cdef class NodeSetIter(object):
266    cdef map[cpp.InfoHash, shared_ptr[cpp.Node]]* _nodes
267    cdef map[cpp.InfoHash, shared_ptr[cpp.Node]].iterator _curIter
268    def __init__(self, NodeSet s):
269        self._nodes = &s._nodes
270        self._curIter = self._nodes.begin()
271    def __next__(self):
272        if self._curIter == self._nodes.end():
273            raise StopIteration
274        h = NodeEntry()
275        h._v = deref(self._curIter)
276        inc(self._curIter)
277        return h
278
279cdef class NodeSet(object):
280    cdef map[cpp.InfoHash, shared_ptr[cpp.Node]] _nodes
281    def size(self):
282        return self._nodes.size()
283    def insert(self, NodeEntry l):
284        return self._nodes.insert(l._v).second
285    def extend(self, li):
286        for n in li:
287            self.insert(n)
288    def first(self):
289        if self._nodes.empty():
290            raise IndexError()
291        h = InfoHash()
292        h._infohash = deref(self._nodes.begin()).first
293        return h
294    def last(self):
295        if self._nodes.empty():
296            raise IndexError()
297        h = InfoHash()
298        h._infohash = deref(dec(self._nodes.end())).first
299        return h
300    def __str__(self):
301        s = ''
302        cdef map[cpp.InfoHash, shared_ptr[cpp.Node]].iterator it = self._nodes.begin()
303        while it != self._nodes.end():
304            s += deref(it).first.toString().decode() + ' ' + deref(it).second.get().getAddrStr().decode() + '\n'
305            inc(it)
306        return s
307    def __iter__(self):
308        return NodeSetIter(self)
309
310cdef class PrivateKey(_WithID):
311    cdef shared_ptr[cpp.PrivateKey] _key
312    def getId(self):
313        h = InfoHash()
314        h._infohash = self._key.get().getPublicKey().getId()
315        return h
316    def getPublicKey(self):
317        pk = PublicKey()
318        pk._key = self._key.get().getPublicKey()
319        return pk
320    def decrypt(self, bytes dat):
321        cdef size_t d_len = len(dat)
322        cdef cpp.uint8_t* d_ptr = <cpp.uint8_t*>dat
323        cdef cpp.Blob indat
324        indat.assign(d_ptr, <cpp.uint8_t*>(d_ptr + d_len))
325        cdef cpp.Blob decrypted = self._key.get().decrypt(indat)
326        cdef char* decrypted_c_str = <char *>decrypted.data()
327        cdef Py_ssize_t length = decrypted.size()
328        return decrypted_c_str[:length]
329    def __str__(self):
330        return self.getId().toString().decode()
331    @staticmethod
332    def generate():
333        k = PrivateKey()
334        k._key = cpp.make_shared[cpp.PrivateKey](cpp.PrivateKey.generate())
335        return k
336    @staticmethod
337    def generateEC():
338        k = PrivateKey()
339        k._key = cpp.make_shared[cpp.PrivateKey](cpp.PrivateKey.generateEC())
340        return k
341
342cdef class PublicKey(_WithID):
343    cdef cpp.PublicKey _key
344    def getId(self):
345        h = InfoHash()
346        h._infohash = self._key.getId()
347        return h
348    def encrypt(self, bytes dat):
349        cdef size_t d_len = len(dat)
350        cdef cpp.uint8_t* d_ptr = <cpp.uint8_t*>dat
351        cdef cpp.Blob indat
352        indat.assign(d_ptr, <cpp.uint8_t*>(d_ptr + d_len))
353        cdef cpp.Blob encrypted = self._key.encrypt(indat)
354        cdef char* encrypted_c_str = <char *>encrypted.data()
355        cdef Py_ssize_t length = encrypted.size()
356        return encrypted_c_str[:length]
357
358cdef class Certificate(_WithID):
359    cdef shared_ptr[cpp.Certificate] _cert
360    def __init__(self, bytes dat = None):
361        if dat:
362            self._cert = cpp.make_shared[cpp.Certificate](<cpp.string>dat)
363    def getId(self):
364        h = InfoHash()
365        if self._cert:
366            h._infohash = self._cert.get().getId()
367        return h
368    def toString(self):
369        return self._cert.get().toString().decode()
370    def getName(self):
371        return self._cert.get().getName()
372    def revoke(self, PrivateKey k, Certificate c):
373        self._cert.get().revoke(deref(k._key.get()), deref(c._cert.get()));
374    def __bytes__(self):
375        return self._cert.get().toString() if self._cert else b''
376    property issuer:
377        def __get__(self):
378            c = Certificate()
379            c._cert = self._cert.get().issuer
380            return c;
381    @staticmethod
382    def generate(PrivateKey k, str name, Identity i = Identity(), bool is_ca = False):
383        c = Certificate()
384        c._cert = cpp.make_shared[cpp.Certificate](cpp.Certificate.generate(deref(k._key.get()), name.encode(), i._id, is_ca))
385        return c
386
387cdef class VerifyResult(object):
388    cdef cpp.TrustListVerifyResult _result
389    def __bool__(self):
390        return self._result.isValid()
391    def __str(self):
392        return self._result.toString()
393
394cdef class TrustList(object):
395    cdef cpp.TrustList _trust
396    def add(self, Certificate cert):
397        self._trust.add(deref(cert._cert.get()))
398    def remove(self, Certificate cert):
399        self._trust.remove(deref(cert._cert.get()))
400    def verify(self, Certificate cert):
401        r = VerifyResult()
402        r._result = self._trust.verify(deref(cert._cert.get()))
403        return r
404
405cdef class ListenToken(object):
406    cdef cpp.InfoHash _h
407    cdef cpp.shared_future[size_t] _t
408    _cb = dict()
409
410cdef class Identity(object):
411    cdef cpp.Identity _id
412    def __init__(self, PrivateKey k = None, Certificate c = None):
413        if k:
414            self._id.first = k._key
415        if c:
416            self._id.second = c._cert
417    @staticmethod
418    def generate(str name = "pydht", Identity ca = Identity(), unsigned bits = 4096):
419        i = Identity()
420        i._id = cpp.generateIdentity(name.encode(), ca._id, bits)
421        return i
422    property publickey:
423        def __get__(self):
424            k = PublicKey()
425            k._key = self._id.first.get().getPublicKey()
426            return k
427    property certificate:
428        def __get__(self):
429            c = Certificate()
430            c._cert = self._id.second
431            return c
432    property key:
433        def __get__(self):
434            k = PrivateKey()
435            k._key = self._id.first
436            return k
437
438cdef class DhtConfig(object):
439    cdef cpp.DhtRunnerConfig _config
440    def __init__(self):
441        self._config = cpp.DhtRunnerConfig()
442        self._config.threaded = True;
443    def setIdentity(self, Identity id):
444        self._config.dht_config.id = id._id
445    def setBootstrapMode(self, bool bootstrap):
446        self._config.dht_config.node_config.is_bootstrap = bootstrap
447    def setNodeId(self, InfoHash id):
448        self._config.dht_config.node_config.node_id = id._infohash
449    def setNetwork(self, unsigned netid):
450        self._config.dht_config.node_config.network = netid
451    def setMaintainStorage(self, bool maintain_storage):
452        self._config.dht_config.node_config.maintain_storage = maintain_storage
453
454cdef class DhtRunner(_WithID):
455    cdef cpp.shared_ptr[cpp.DhtRunner] thisptr
456    def __cinit__(self):
457        self.thisptr.reset(new cpp.DhtRunner())
458    def getId(self):
459        h = InfoHash()
460        if self.thisptr:
461            h._infohash = self.thisptr.get().getId()
462        return h
463    def getNodeId(self):
464        return self.thisptr.get().getNodeId().toString()
465    def ping(self, SockAddr addr, done_cb=None):
466        if done_cb:
467            cb_obj = {'done':done_cb}
468            ref.Py_INCREF(cb_obj)
469            self.thisptr.get().bootstrap(addr._addr, cpp.bindDoneCbSimple(done_callback_simple, <void*>cb_obj))
470        else:
471            lock = threading.Condition()
472            pending = 0
473            ok = False
474            def tmp_done(ok_ret):
475                nonlocal pending, ok, lock
476                with lock:
477                    ok = ok_ret
478                    pending -= 1
479                    lock.notify()
480            with lock:
481                pending += 1
482                self.ping(addr, done_cb=tmp_done)
483                while pending > 0:
484                    lock.wait()
485            return ok
486    def bootstrap(self, str host, str port=None):
487        host_bytes = host.encode()
488        port_bytes = port.encode() if port else b'4222'
489        self.thisptr.get().bootstrap(<cpp.const_char*>host_bytes, <cpp.const_char*>port_bytes)
490    def run(self, Identity id=None, is_bootstrap=False, cpp.in_port_t port=0, str ipv4="", str ipv6="", DhtConfig config=DhtConfig()):
491        if id:
492            config.setIdentity(id)
493        if ipv4 or ipv6:
494            bind4 = ipv4.encode() if ipv4 else b''
495            bind6 = ipv6.encode() if ipv6 else b''
496            self.thisptr.get().run(bind4, bind6, str(port).encode(), config._config)
497        else:
498            self.thisptr.get().run(port, config._config)
499    def join(self):
500        self.thisptr.get().join()
501    def shutdown(self, shutdown_cb=None):
502        cb_obj = {'shutdown':shutdown_cb}
503        ref.Py_INCREF(cb_obj)
504        self.thisptr.get().shutdown(cpp.bindShutdownCb(shutdown_callback, <void*>cb_obj))
505    def enableLogging(self):
506        cpp.enableLogging(self.thisptr.get()[0])
507    def disableLogging(self):
508        cpp.disableLogging(self.thisptr.get()[0])
509    def enableFileLogging(self, str path):
510        cpp.enableFileLogging(self.thisptr.get()[0], path.encode())
511    def isRunning(self):
512        return self.thisptr.get().isRunning()
513    def getBound(self, cpp.sa_family_t af = 0):
514        s = SockAddr()
515        s._addr = self.thisptr.get().getBound(af)
516        return s
517    def getStorageLog(self):
518        return self.thisptr.get().getStorageLog().decode()
519    def getRoutingTablesLog(self, cpp.sa_family_t af):
520        return self.thisptr.get().getRoutingTablesLog(af).decode()
521    def getSearchesLog(self, cpp.sa_family_t af):
522        return self.thisptr.get().getSearchesLog(af).decode()
523    def getNodeMessageStats(self):
524        stats = []
525        cdef cpp.vector[unsigned] res = self.thisptr.get().getNodeMessageStats(False)
526        for n in res:
527            stats.append(n)
528        return stats
529
530    def get(self, InfoHash key, get_cb=None, done_cb=None, filter=None, Where where=None):
531        """Retreive values associated with a key on the DHT.
532
533        key     -- the key for which to search
534        get_cb  -- is set, makes the operation non-blocking. Called when a value
535                   is found on the DHT.
536        done_cb -- optional callback used when get_cb is set. Called when the
537                   operation is completed.
538        """
539        if get_cb:
540            cb_obj = {'get':get_cb, 'done':done_cb, 'filter':filter}
541            ref.Py_INCREF(cb_obj)
542            if where is None:
543                where = Where()
544            self.thisptr.get().get(key._infohash, cpp.bindGetCb(get_callback, <void*>cb_obj),
545                    cpp.bindDoneCb(done_callback, <void*>cb_obj),
546                    cpp.nullptr, #filter implemented in the get_callback
547                    where._where)
548        else:
549            lock = threading.Condition()
550            pending = 0
551            res = []
552            def tmp_get(v):
553                nonlocal res
554                res.append(v)
555                return True
556            def tmp_done(ok, nodes):
557                nonlocal pending, lock
558                with lock:
559                    pending -= 1
560                    lock.notify()
561            with lock:
562                pending += 1
563                self.get(key, get_cb=tmp_get, done_cb=tmp_done, filter=filter, where=where)
564                while pending > 0:
565                    lock.wait()
566            return res
567    def put(self, InfoHash key, Value val, done_cb=None):
568        """Publish a new value on the DHT at key.
569
570        key     -- the DHT key where to put the value
571        val     -- the value to put on the DHT
572        done_cb -- optional callback called when the operation is completed.
573        """
574        if done_cb:
575            cb_obj = {'done':done_cb}
576            ref.Py_INCREF(cb_obj)
577            self.thisptr.get().put(key._infohash, val._value, cpp.bindDoneCb(done_callback, <void*>cb_obj))
578        else:
579            lock = threading.Condition()
580            pending = 0
581            ok = False
582            def tmp_done(ok_ret, nodes):
583                nonlocal pending, ok, lock
584                with lock:
585                    ok = ok_ret
586                    pending -= 1
587                    lock.notify()
588            with lock:
589                pending += 1
590                self.put(key, val, done_cb=tmp_done)
591                while pending > 0:
592                    lock.wait()
593            return ok
594    def listen(self, InfoHash key, get_cb):
595        t = ListenToken()
596        t._h = key._infohash
597        cb_obj = {'get':get_cb}
598        t._cb['cb'] = cb_obj
599        # avoid the callback being destructed if the token is destroyed
600        ref.Py_INCREF(cb_obj)
601        t._t = self.thisptr.get().listen(t._h, cpp.bindGetCb(get_callback, <void*>cb_obj)).share()
602        return t
603    def cancelListen(self, ListenToken token):
604        self.thisptr.get().cancelListen(token._h, token._t)
605        ref.Py_DECREF(<object>token._cb['cb'])
606        # fixme: not thread safe
607
608cdef class IndexValue(object):
609    cdef cpp.shared_ptr[cpp.IndexValue] _value
610    def __init__(self, InfoHash h=None, cpp.uint64_t vid=0):
611       cdef cpp.InfoHash hh = h._infohash
612       self._value.reset(new cpp.IndexValue(hh, vid))
613    def __str__(self):
614        return "(" + self.getKey().toString().decode() +", "+ str(self.getValueId()) +")"
615    def getKey(self):
616        h = InfoHash()
617        h._infohash = self._value.get().first
618        return h
619    def getValueId(self):
620        return self._value.get().second
621
622cdef class Pht(object):
623    cdef cpp.Pht* thisptr
624    def __cinit__(self, bytes name, key_spec, DhtRunner dht):
625        cdef cpp.IndexKeySpec cpp_key_spec
626        for kk, size in key_spec.items():
627            cpp_key_spec[bytes(kk, 'utf-8')] = size
628        self.thisptr = new cpp.Pht(name, cpp_key_spec, dht.thisptr)
629    property MAX_NODE_ENTRY_COUNT:
630        def __get__(self):
631            return cpp.PHT_MAX_NODE_ENTRY_COUNT
632    def lookup(self, key, lookup_cb=None, done_cb=None):
633        """Query the Index with a specified key.
634
635        key       -- the key for to the entry in the index.
636        lookup_cb -- function called when the operation is completed. This
637                     function takes a list of IndexValue objects and a string
638                     representation of the prefix where the value was indexed in
639                     the PHT.
640        """
641        cb_obj = {'lookup':lookup_cb, 'done':done_cb} # TODO: donecallback is to be removed
642        ref.Py_INCREF(cb_obj)
643        cdef cpp.IndexKey cppk
644        for kk, v in key.items():
645            cppk[bytes(kk, 'utf-8')] = bytes(v)
646        self.thisptr.lookup(
647                cppk,
648                cpp.Pht.bindLookupCb(lookup_callback, <void*>cb_obj),
649                cpp.bindDoneCbSimple(done_callback_simple, <void*>cb_obj)
650        )
651    def insert(self, key, IndexValue value, done_cb=None):
652        """Add an index entry to the Index.
653
654        key     -- the key for to the entry in the index.
655        value   -- an IndexValue object describing the indexed value.
656        done_cb -- Called when the operation is completed.
657        """
658        cb_obj = {'done':done_cb}
659        ref.Py_INCREF(cb_obj)
660        cdef cpp.IndexKey cppk
661        for kk, v in key.items():
662            cppk[bytes(kk, 'utf-8')] = bytes(v)
663        cdef cpp.IndexValue val
664        val.first = (<InfoHash>value.getKey())._infohash
665        val.second = value.getValueId()
666        self.thisptr.insert(
667                cppk,
668                val,
669                cpp.bindDoneCbSimple(done_callback_simple, <void*>cb_obj)
670        )
671