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