1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
|
# Copyright 2004,2005 Pierre Martineau <pmartino@users.sourceforge.net>
# This file is part of Bibus, a bibliographic database that can
# work together with OpenOffice.org to generate bibliographic indexes.
#
# Bibus is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Bibus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bibus; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""Base class for db connection.
__init__
selectDatabase
__encode
__listEncode
get_insert_id
getDbInfo
duplicateIdentifier
getGrants
must be overrided
You must also define Error and IntegrityError
"""
#
import wx
import time
import BIB
SUBSTR = 'SUBSTR' # SUBSTRING keyword. 'SUBSTR' in SQLite and MySQL >= 4.1; 'SUBSTRING' in MySQL all versions
TABLE_REF, TABLE_LINK, TABLE_KEY, TABLE_QUERY, TABLE_MODIF, TABLE_FILE = "","","","","","" # set in derived classes
class dbBib(object):
def __init__(self,parent=None,paramstyle='pyformat'):
self.tableRef = ''
self.tableKey = ''
self.tableLink = ''
self.tableQuery = ''
self.tableModif = ''
self.tableFile = ''
self.parent=parent
self.dbConnection = None # database connection
# parameter for DB API2 = "%s" for 'format' and 'pyformat'; "?" for "qmark"
if paramstyle in ('pyformat','format'):
self.param = "%s"
elif paramstyle == 'qmark':
self.param = "?"
else:
print "Error, the dbBibBase can only handle paramstyle == 'format,'pyformat' or 'qmark' but not %s"%paramstyle
#
# This function format the Identifier
# You can overload it if you want to change the default behaviour
def newIdentifier(self,record):
"""Return a Identifier. Identifier = AuthorYear where Author is the first author name.
Spaces are also replaced by _"""
author = record[BIB.BIBLIOGRAPHIC_FIELDS['Author']]
if not author: author = 'Anonymous' # put anonymous for the name => more readable
return author.split(',')[0].replace(' ','_')+record[BIB.BIBLIOGRAPHIC_FIELDS['Year']] # FirstAuthorYear
#
# Database specific funtions
#
#
def get_insert_id(self):
"""Return the last inserted auto_incremented id"""
return None
def getDbInfo(self,key=''):
"""Return a tuple that represent the database connection. For mysql we use (host,db,table).
For another database it could be a file name or ... something that identify the database + connection"""
return (None,)
def createDatabase(self,db_name,tableref,tablekey,tablelink,tablequery,tablemodif,tablefile):
return
def duplicateIdentifier(self,e):
return True
def getGrants(self):
"""Return a string
True if temp table may be created with INSERT,DELETE,SELECT
'rw' if bibref is INSERT,DELETE,SELECT,UPDATE ; 'r' if INSERT ; '' else
'rw' if bibrefKey is INSERT,DELETE,SELECT,UPDATE or 'r' or ''
'rw' if bibrefLink is INSERT,DELETE,SELECT,UPDATE or 'r' or ''
'rw' if bibquery is INSERT,DELETE,SELECT,UPDATE or 'r' or ''
return:
'rw' if True,'rw','rw','rw','rw'
'ro' if Any,'r','rw','rw','rw'
'rk' if Any,'r','r','r','r'
'rr' else (normally we need at least 'r' on bibref) # rr = ReadRestricted
"""
return 'rw'
# the following classes should be ok for any database compatible with API 2.0
def __writeRef(self,ref):
"""Error if Identifier already exists => we have to solve the duplicate"""
# we first test for duplicate references according to settings
if BIB.DUPLICATES_TEST:
if BIB.DUPLICATES_CASE:
tmpstr = """select Id from %s where """% self.tableRef + ' and '.join( '%s = %s'%(field,self.param) for field in BIB.DUPLICATES )
else:
tmpstr = """select Id from %s where """% self.tableRef + ' and '.join( 'lower(%s) = lower(%s)'%(field,self.param) for field in BIB.DUPLICATES )
self.dbCursor.execute( tmpstr, tuple([ref[BIB.BIBLIOGRAPHIC_FIELDS[field]] for field in BIB.DUPLICATES]) )
self.dbConnection.commit()
ref_id = self.dbCursor.fetchone()
if ref_id: # we found a duplicate. We use the first one.
if not BIB.DUPLICATES_KEEP_OLD: # we update the old ref with the new values, except Id,Identifier
self.modifyRef(BIB.BIB_FIELDS[2:],ref[2:],ref_id[0])
return ref_id[0] # we return the id of the duplicate
# The reference is not in the db already => we save it
try:
tmpstr = """INSERT INTO %s VALUES (""" % self.tableRef
self.dbCursor.execute(tmpstr + (len(ref)*('%s,'%self.param))[:-1] + ")", ref)
self.dbConnection.commit()
ref_id = self.get_insert_id()
self.updateCreator( ref_id )
return ref_id
#except IntegrityError,errorType: # does not work with pysqlite2 because duplicates don't raise IntegrityError but an Error
except Error,errorType:
if self.duplicateIdentifier(errorType):
storedIdentifier = ref[1]
ref[1] = None
ref_id = self.__writeRef(ref)
#ref_id = self.get_insert_id()
self.modifyRef(('Identifier',),(storedIdentifier,),ref_id)
return ref_id
else:
self.showError("dbBibBase.__writeRef " + `errorType.args`)
return None
#except Error,errorType:
# print Error
# print errorType
# self.showError(`errorType.args`)
# return None
def writeRef(self,ref):
ref = list(ref)
if not ref[BIB.BIBLIOGRAPHIC_FIELDS['Identifier']]:
ref[BIB.BIBLIOGRAPHIC_FIELDS['Identifier']] = self.newIdentifier(ref) # Format a new Identifier if needed
return self.__writeRef(ref)
def __write(self,table,ref):
"""Automatically take care of duplicates"""
try:
tmpstr = """REPLACE INTO %s VALUES (""" % (table)
self.dbCursor.execute(tmpstr + (len(ref)*('%s,'%self.param))[:-1] + ")", ref)
self.dbConnection.commit()
except Error,errorType:
self.showError("dbBibBase.__write " + `errorType.args`)
def writeRefOnline(self,ref):
ref = list(ref)
if not ref[BIB.BIBLIOGRAPHIC_FIELDS['Identifier']]:
ref[BIB.BIBLIOGRAPHIC_FIELDS['Identifier']] = self.newIdentifier(ref) # Format a new Identifier if needed
self.__write(BIB.TMP_ONLINE,ref)
def writeRefImport(self,ref):
ref = list(ref)
if not ref[BIB.BIBLIOGRAPHIC_FIELDS['Identifier']]:
ref[BIB.BIBLIOGRAPHIC_FIELDS['Identifier']] = self.newIdentifier(ref) # Format a new Identifier if needed
self.__write(BIB.TMP_IMPORT,ref)
def writeKey(self,user,ref):
"""Write a new Key. key_id is autoincremented
ref = (parentID,key_name)"""
#self.__write(self.tableKey,(self.user,NULL)+ref) # does not work anymore with pysqlite2 because we cannot get the Id with lastrowid
# as it was the case with pysqlite1.x
# we must thus use an INSERT instead of a REPLACE
# ('user','key_Id','parent','key_name')
try:
self.dbCursor.execute( """INSERT INTO %s VALUES (%s,%s,%s,%s)""" % \
(self.tableKey,self.param,self.param,self.param,self.param) , \
(user,None)+ref )
self.dbConnection.commit()
except Error,errorType:
self.showError('dbBibBase. writeKey: ' + `errorType.args`)
def writeLink(self,link):
"""link = tuple = (key_id,ref_id)"""
self.__write(self.tableLink,link)
def writeNewQuery(self,user,query_name,query):
"""query = search_string"""
self.__write(self.tableQuery,(None,user,query_name,query))
def delLink(self,key_id,ref_id):
tmpstr = """DELETE FROM %s WHERE %s=%s AND %s=%s""" % (self.tableLink,'key_id',self.param,'ref_id',self.param)
try:
self.dbCursor.execute(tmpstr,(key_id,ref_id))
self.dbConnection.commit()
except Error,errorType:
self.showError('dbBib.delLink: '+`errorType.args`)
def __deleteKey(self,user,key_id):
"""Delete the key key_id and all the links containing key_id"""
try:
self.dbCursor.execute("""DELETE FROM %s WHERE key_id = %s"""%(self.tableLink,self.param),(key_id,))
self.dbCursor.execute("""DELETE FROM %s WHERE key_id = %s AND user = %s"""%(self.tableKey,self.param,self.param),(key_id,user))
self.dbConnection.commit()
except Error,errorType:
self.showError(u'dbBib.deleteKey:' + `errorType.args`)
def __deleteKeyChildren(self,user,key_id):
"""Recursively delete the children of key_id and all the children's links"""
for name,child_id in self.getKeyChildren(user,key_id):
self.__deleteKeyChildren(user,child_id)
self.__deleteKey(user,child_id)
def deleteKey(self,user,key_id):
"""Recursively delete the key with key_id, its children and all the links"""
self.__deleteKeyChildren(user,key_id) # delete children, etc..
self.__deleteKey(user,key_id) # delete the key and its links
def __getAllRef(self,table,collist,order,how=BIB.LIST_HOW,short=False):
try:
if not short:
self.dbCursor.execute("""SELECT %s FROM %s ORDER BY %s %s"""% (','.join(('Id',)+collist),table,order,how)) # column Id added for SetData of refList
else:
# SUBSTR requires MySQL 4.1.1; For previous versions we should use SUBSTRING
self.dbCursor.execute("""SELECT %s FROM %s ORDER BY %s %s"""% (','.join(['Id']+["%s(%s,1,%s)"%(SUBSTR,i,BIB.LIST_SHORT) for i in collist]),table,order,how)) # column Id added for SetData of refList
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.__getAllRef " + `errorType.args`)
return None
def getAllDatabase(self,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return all the refs including the Trash"""
return self.__getAllRef(self.tableRef,collist,order,how,short)
def getAllRefOnline(self,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
return self.__getAllRef(BIB.TMP_ONLINE,collist,order,how,short)
def getAllRefImport(self,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
return self.__getAllRef(BIB.TMP_IMPORT,collist,order,how,short)
def getAllRef(self,user,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return all the references tagged by user, ie all the refs except Trash"""
try:
if short:
self.dbCursor.execute("""SELECT %s FROM %s WHERE Id IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE user=%s) ORDER BY %s %s"""\
%(','.join(['Id']+["%s(%s,1,%s)"%(SUBSTR,i,BIB.LIST_SHORT) for i in collist]),\
self.tableRef, self.tableLink, self.tableKey,self.param,order,how),\
(user,))
else:
self.dbCursor.execute("""SELECT %s FROM %s WHERE Id IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE user=%s) ORDER BY %s %s"""\
%(','.join(('Id',)+collist),\
self.tableRef, self.tableLink, self.tableKey,self.param,order,how),\
(user,))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getAllRef " + `errorType.args`)
return None
def getTrashRef(self,user,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return all the refs non tagged by the current user => Trash"""
try:
if short:
self.dbCursor.execute("""SELECT %s FROM %s WHERE Id NOT IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE user=%s) ORDER BY %s %s"""\
%(','.join(['Id']+["%s(%s,1,%s)"%(SUBSTR,i,BIB.LIST_SHORT) for i in collist]),\
self.tableRef, self.tableLink, self.tableKey,self.param,order,how),\
(user,))
else:
self.dbCursor.execute("""SELECT %s FROM %s WHERE Id NOT IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE user=%s) ORDER BY %s %s"""\
%(','.join(('Id',)+collist),\
self.tableRef, self.tableLink, self.tableKey,self.param,order,how),\
(user,))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getTrashRef " + `errorType.args`)
return None
def __deleteTmpRef(self,table):
try:
self.dbCursor.execute("""DELETE FROM %s"""%table)
self.dbConnection.commit()
except Error,errorType:
self.showError('dbBib.__deleteTmpRef' + `errorType.args`)
def deleteImport(self):
"""Delete all the ref in BIB.TMP_IMPORT table"""
self.__deleteTmpRef(BIB.TMP_IMPORT)
def deleteOnline(self):
"""Delete all the ref in BIB.TMP_ONLINE table"""
self.__deleteTmpRef(BIB.TMP_ONLINE)
def __getRef(self,table,ref_id,collist,idcol='Id'):
"""Return the reference corresponding to ref_id for column idcol"""
try:
tmpstr = """SELECT %s FROM %s WHERE %s=%s""" % (','.join(collist),table,idcol,self.param)
self.dbCursor.execute(tmpstr, (ref_id,))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.__getRef " + `errorType.args`)
return None
def getRefFromIdentifier(self,identifier,collist=BIB.BIB_PRINT_FIELD):
"""Return the reference corresponding to identifier in database
This is used for the connection with OOo"""
return self.__getRef(self.tableRef,identifier,collist,'Identifier')
def getRef(self,ref_id,collist=BIB.BIB_PRINT_FIELD):
"""Return the reference corresponding to ref_id in database"""
return self.__getRef(self.tableRef,ref_id,collist)
def getRefImport(self,ref_id,collist=BIB.BIB_PRINT_FIELD):
"""Return the reference corresponding to ref_id in TMP_IMPORT"""
return self.__getRef(BIB.TMP_IMPORT,ref_id,collist)
def getRefOnline(self,ref_id,collist=BIB.BIB_PRINT_FIELD):
"""Return the reference corresponding to ref_id in TMP_ONLINE"""
return self.__getRef(BIB.TMP_ONLINE,ref_id,collist)
def getRefKey(self,key_id,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return the references corresponding to the key with key_id"""
try:
if not short:
tmpstr = """SELECT %s FROM %s as t1,%s as t2 WHERE t1.%s=t2.%s AND t2.%s=%s ORDER BY %s %s""" %('Id,' + ','.join(collist),self.tableRef,self.tableLink,'Id','ref_id','key_id',self.param,order,how)
else:
tmpstr = """SELECT %s FROM %s as t1,%s as t2 WHERE t1.%s=t2.%s AND t2.%s=%s ORDER BY %s %s""" %(','.join(['Id'] + ['%s(%s,1,%s)'%(SUBSTR,i,BIB.LIST_SHORT) for i in collist]),self.tableRef,self.tableLink,'Id','ref_id','key_id',self.param,order,how)
self.dbCursor.execute(tmpstr, (key_id,))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getRefKey " + `errorType.args`)
return None
def getRefKeySearch(self,key_id,searchStr,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return the references corresponding to the key with key_id and the search 'searchStr'"""
searchStr = searchStr.replace('%','%%') # protect against substitution
try:
if not short:
tmpstr = """SELECT %s FROM %s as t1,%s as t2 WHERE t1.%s=t2.%s AND t2.%s=%s AND %s ORDER BY %s %s""" %(','.join(('Id',)+ collist),self.tableRef,self.tableLink,'Id','ref_id','key_id',self.param,searchStr,order,how)
else:
tmpstr = """SELECT %s FROM %s as t1,%s as t2 WHERE t1.%s=t2.%s AND t2.%s=%s AND %s ORDER BY %s %s""" %(','.join(['Id']+ ['%s(%s,1,%s)'%(SUBSTR,i,BIB.LIST_SHORT) for i in collist]),self.tableRef,self.tableLink,'Id','ref_id','key_id',self.param,searchStr,order,how)
#print tmpstr
self.dbCursor.execute(tmpstr.encode(BIB.ENCODING,BIB.ENC_ERRORS), (key_id,))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getRefKeySearch " + `errorType.args`)
return None
def __getAllRefSearch(self,searchStr,table,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return the references corresponding to the search 'searchStr' from table"""
try:
#print searchStr.encode('latin1')
if not short:
tmpstr = u"""SELECT %s FROM %s WHERE %s ORDER BY %s %s""" %(','.join(('Id',)+ collist),table,searchStr,order,how)
else:
tmpstr = u"""SELECT %s FROM %s WHERE %s ORDER BY %s %s""" %(','.join(['Id']+ ['%s(%s,1,%s)' %(SUBSTR,i,BIB.LIST_SHORT) for i in collist]),table,searchStr,order,how)
self.dbCursor.execute(tmpstr.encode(BIB.ENCODING,BIB.ENC_ERRORS))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getAllRefSearch: " + `errorType.args`)
return None
def getAllDatabaseRefSearch(self,searchStr,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return the references corresponding to the search 'searchStr'"""
return self.__getAllRefSearch(searchStr,self.tableRef,collist,order,how,short)
def getOnlineRefSearch(self,searchStr,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
return self.__getAllRefSearch(searchStr,BIB.TMP_ONLINE,collist,order,how,short)
def getImportRefSearch(self,searchStr,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
return self.__getAllRefSearch(searchStr,BIB.TMP_IMPORT,collist,order,how,short)
def getAllRefSearch(self,user,searchStr,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return the references corresponding to the search 'searchStr' from all the refs belonging to current user"""
try:
if not short:
tmpstr = """SELECT %s FROM %s WHERE Id IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE user="%s") AND %s ORDER BY %s %s"""\
%(','.join(('Id',)+ collist),\
self.tableRef, self.tableLink, self.tableKey,user,searchStr,order,how)
else:
tmpstr = """SELECT %s FROM %s WHERE Id IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE user="%s") AND %s ORDER BY %s %s"""\
%(','.join(['Id']+["%s(%s,1,%s)"%(SUBSTR,i,BIB.LIST_SHORT) for i in collist]),\
self.tableRef, self.tableLink, self.tableKey,user,searchStr,order,how)
self.dbCursor.execute(tmpstr.encode(BIB.ENCODING,BIB.ENC_ERRORS))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getAllRefSearch: " + `errorType.args`)
return None
def getTrashRefSearch(self,user,searchStr,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return the references corresponding to the search 'searchStr' from all the refs in the Trash of current user"""
try:
if short:
tmpstr = """SELECT %s FROM %s WHERE Id NOT IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE user="%s") AND %s ORDER BY %s %s"""\
%(','.join(['Id']+["%s(%s,1,%s)"%(SUBSTR,i,BIB.LIST_SHORT)\
for i in collist]),\
self.tableRef, self.tableLink, self.tableKey,user,searchStr,order,how)
else:
tmpstr = """SELECT %s FROM %s WHERE Id NOT IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE user="%s") AND %s ORDER BY %s %s"""\
%(','.join(('Id',)+collist),\
self.tableRef, self.tableLink, self.tableKey,user,searchStr,order,how)
self.dbCursor.execute(tmpstr.encode(BIB.ENCODING,BIB.ENC_ERRORS))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getTrashRefSearch: " + `errorType.args`)
return None
def getRefFromIdentifiers(self,Identifiers,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW):
"""Return the references corresponding to the list of Identifiers"""
if not Identifiers: return ()
try:
query = \
u"""SELECT %s FROM %s WHERE """ %('Id,' + ','.join(collist),self.tableRef) \
+ "OR".join((u"""Identifier=%%s""",)*len(Identifiers)) \
+ u"""WHERE %s ORDER BY %s %s""" %(order,how)
self.dbCursor.execute(query,Identifiers)
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getRefFromIdentifiers: " + `errorType.args`)
return None
def getQueryRef(self,user,query_id,collist=BIB.LIST_DISPLAY,order=BIB.LIST_ORDER,how=BIB.LIST_HOW,short=False):
"""Return the references corresponding to the query with id query_id in table bibquery"""
try:
self.dbCursor.execute("""SELECT query FROM %s WHERE query_id = %s""" %(self.tableQuery,query_id))
searchstr = self.dbCursor.fetchall()[0][0]
return self.getAllRefSearch(user,searchstr,collist,order,how,short)
except Error,errorType:
self.showError("dbBibBase.getQueryRef " + `errorType.args`)
return None
def __getQuery(self,col,query_id):
try:
tmpstr = """SELECT %s FROM %s WHERE query_id = %s""" % (col,self.tableQuery,self.param)
self.dbCursor.execute(tmpstr, (query_id,))
return self.dbCursor.fetchall()[0][0]
except Error,errorType:
self.showError('dbBib.__getQuery: '+`errorType.args`)
def getQueryName(self,query_id):
"""Return the name of query query_id"""
return self.__getQuery('name',query_id)
def getQuery(self,query_id):
"""Return the query of query query_id"""
return self.__getQuery('query',query_id)
def getQueries(self,user,restrict=''):
"""Return list of (name,key_id) of children of Key ID_QUERY_ROOT from table self.tableQuery
restricted to name LIKE %restrict% """
try:
tmpstr = """SELECT %s,%s FROM %s WHERE user=%s AND name LIKE %s""" % ('name','query_id',self.tableQuery,self.param,self.param)
self.dbCursor.execute(tmpstr, (user,'%'+restrict+'%'))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getQueries " + `errorType.args`)
return None
def __modifyQuery(self,col,query_id,value):
"""Change the column col value of the query with query_id"""
try:
tmpstr = """UPDATE %s SET %s = %s where query_id = %s""" % (self.tableQuery,col,self.param,self.param)
self.dbCursor.execute(tmpstr, (value,query_id))
self.dbConnection.commit()
except Error,errorType:
self.showError('dbBib.__modifyQuery: '+`errorType.args`)
def renameQuery(self,query_id,newName):
"""Change the name of the current query"""
self.__modifyQuery('name',query_id,newName)
def setQuery(self,query_id,query):
"""Set a new 'query' in the current query"""
self.__modifyQuery('query',query_id,query)
def deleteQuery(self,query_id):
"""Delete the query with id query_id"""
try:
self.dbCursor.execute("""DELETE FROM %s WHERE query_id=%s"""%(self.tableQuery,self.param),(query_id,))
self.dbConnection.commit()
except Error,errorType:
self.showError('dbBib.deleteQuery: '+`errorType.args`)
def modifyRef(self,collist,valuelist,ref_id):
valuelist = list(valuelist)
collist = list(collist)
try:
if not valuelist[collist.index('Identifier')]: # Identifier is ''
ref = list( self.getRef(ref_id,BIB.BIB_FIELDS)[0] ) # get the ref from the database
for pos in xrange(len(collist)):
ref[BIB.BIBLIOGRAPHIC_FIELDS[collist[pos]]] = valuelist[pos] # update the fields
valuelist[collist.index('Identifier')] = self.newIdentifier(ref) # Format a new Identifier if needed
except ValueError:
pass # Identifier is not in the changed Fields => we don't have to check for empty Identifier
#
try:
tmpstr = """UPDATE %s SET """ % self.tableRef + ','.join( ["%s = %s"%(c,self.param) for c in collist] )
valuelist.append(ref_id)
#print tmpstr + """ WHERE Id=%s"""%self.param
self.dbCursor.execute(tmpstr + """ WHERE Id=%s"""%self.param , tuple(valuelist) )
self.dbConnection.commit()
self.updateLastModif( ref_id )
#except IntegrityError,errorType: # This does not work with pysqlite2 # duplicate Identifier
except Error,errorType: # duplicate Identifier
if self.duplicateIdentifier(errorType): # possible only if we redefined the Identifier => is in collist
valuelist.pop() # remove the ref_id we added before
valuelist[collist.index('Identifier')] = BIB.SEP_DUP.join( (valuelist[collist.index('Identifier')],repr(ref_id)) )
self.modifyRef(collist,valuelist,ref_id)
else:
self.showError("dbBibBase.modifyRef " + `errorType.args`)
def getKeyChildren(self,user,key_id,restrict=''):
"""Return list of (key_name,key_id) of children of Key with id key_id
where name LIKE restrict"""
try:
tmpstr = """SELECT %s,%s FROM %s WHERE user=%s AND parent=%s AND key_name LIKE %s ORDER BY key_name""" % ('key_name','key_id',self.tableKey,self.param,self.param,self.param)
self.dbCursor.execute(tmpstr, (user,key_id,'%'+restrict+'%'))
#print "getKeyLike",self.dbCursor.fetchall()
#print self.dbCursor.fetchall()
return self.dbCursor.fetchall()
except Error,errorType:
self.showError("dbBibBase.getKeyChildren " + `errorType.args`)
return None
def getKeyParent(self,user,key_id):
"""Return key_id of the parent of Key with id key_id"""
try:
tmpstr = """SELECT %s FROM %s WHERE user=%s AND %s=%s""" % ('parent',self.tableKey,self.param,'key_Id',self.param)
self.dbCursor.execute(tmpstr, (user,key_id))
#print "getKeyLike",self.dbCursor.fetchall()
return self.dbCursor.fetchall()[0][0]
except Error,errorType:
self.showError('dbBib.getKeyParent: '+`errorType.args`)
return None
def getTupleKeyParent(self,key_id):
"""Return key_id,name of the parent of Key with id key_id"""
try:
tmpstr = """SELECT t1.parent,t2.key_name from %s as t1 LEFT JOIN %s as t2 ON t1.parent = t2.key_Id WHERE t1.key_Id = %s""" %(self.tableKey,self.tableKey,self.param)
self.dbCursor.execute(tmpstr, (key_id,))
return self.dbCursor.fetchone()
except Error,errorType:
self.showError('dbBib.getTupleKeyParent: '+`errorType.args`)
return None
def getKeyPath(self,user,key_id):
"""Return a tuple of lists [references,child1,child2,...,name(key_id)],[id_ref,id_child1,...,key_id]
in the keytree of the user"""
tmp = [self.getKeyName(user,key_id)]
tmpId = [key_id]
parent,name = self.getTupleKeyParent(key_id)
while parent:
tmp.append(name)
tmpId.append(parent)
parent,name = self.getTupleKeyParent(parent)
tmp.reverse()
tmpId.reverse()
return tmp[1:],tmpId[1:]
def getKeyId(self,parent,key_name):
"""return the Id of the key with parent and key_name"""
try:
tmpstr = """SELECT %s FROM %s WHERE %s=%s AND %s=%s AND %s=%s""" % ('key_id',self.tableKey,'user',self.param,'parent',self.param,'key_name',self.param)
self.dbCursor.execute(tmpstr, (self.user,parent,key_name))
return self.dbCursor.fetchall()[0][0]
except Error,errorType:
self.showError('dbBib.getKeyId: '+`errorType.args`)
def getKeyName(self,user,key_id):
"""return the name of the key"""
try:
self.dbCursor.execute("select key_name from %s where key_id=%s AND user=%s"%(self.tableKey,self.param,self.param), (key_id,user))
return self.dbCursor.fetchall()[0][0]
except Error,errorType:
self.showError('dbBib.getKeyName: '+`errorType.args`)
def __modifyKey(self,user,col,value,key_id):
try:
tmpstr = """UPDATE %s SET %s=%s WHERE %s=%s and %s=%s""" % (self.tableKey,col,self.param,'user',self.param,'key_id',self.param)
self.dbCursor.execute(tmpstr , (value,user,key_id))
self.dbConnection.commit()
except Error,errorType:
self.showError('dbBib.__modifyKey: '+`errorType.args`)
def modifyKeyName(self,user,name,key_id):
self.__modifyKey(user,'key_name',name,key_id)
def modifyKeyParent(self,user,parent,key_id):
self.__modifyKey(user,'parent',parent,key_id)
def keyExist(self,user,parent_id,key_name):
"""Return true if a key with the same name exist"""
try:
tmpstr = """SELECT %s FROM %s WHERE %s=%s AND %s=%s AND %s=%s""" % ('key_id',self.tableKey,'user',self.param,'key_name',self.param,'parent',self.param)
self.dbCursor.execute(tmpstr , (user,key_name,parent_id))
return bool(self.dbCursor.fetchall())
except Error,errorType:
self.showError('dbBib.keyExist: '+`errorType.args`)
def getKeys(self,ref_id):
"""Return list of key_id associated with ref_id for the current user"""
try:
self.dbCursor.execute("""SELECT t2.key_id FROM %s AS t1,%s AS t2 WHERE t1.key_id = t2.key_id AND t1.ref_id=%s AND t2.user=%s"""%(self.tableLink,self.tableKey,self.param,self.param) , (ref_id,self.user))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError('dbBib.getKeys: '+`errorType.args`)
def getRoot(self,user=''):
"""Return the key_id of root"""
try:
tmpstr = """SELECT %s FROM %s WHERE %s=%s AND parent IS NULL""" % ('key_id',self.tableKey,'user',self.param)
self.dbCursor.execute(tmpstr , (user,))
return self.dbCursor.fetchall()
except Error,errorType:
self.showError('dbBib.getRoot: '+`errorType.args`)
def check_db(self):
""" Check that all the fields are indeed present in the selected database and print an error on failure"""
fields = self.getFields(self.tableRef)
if self.getGrants() in ('rw','ro'):
fields2 = self.getFields(self.tableKey)
fields3 = self.getFields(self.tableLink)
fields4 = self.getFields(self.tableModif)
fields5 = self.getFields(self.tableQuery)
fields6 = self.getFields(self.tableFile)
#
for i in BIB.BIB_FIELDS:
if i not in fields: break
else:
for j in BIB.BIB_KEYS:
if j not in fields2: break
else:
for k in BIB.BIB_LINKS:
if k not in fields3: break
else:
for l in BIB.BIB_MODIF:
if l not in fields4: break
else:
for m in BIB.BIB_QUERY:
if m not in fields5: break
else:
for n in BIB.BIB_FILE:
if n not in fields6: break
else:
return True
self.showError(_("Some needed fields are absent from the selected database/tables.\nContinue at your own risk"))
# Can SQlite 1.3/1.4 to 1.5 database conversion be invoked from elsewhere than the FirstStart Wizard?
# e.g. here, or as a separate utility
return False
else: # grants == 'rr'
for i in BIB.BIB_FIELDS:
if i not in fields:
self.showError(_("Some needed fields are absent from the selected database/tables.\nContinue at your own risk"))
# Can SQlite 1.3/1.4 to 1.5 database conversion be invoked from elsewhere than the FirstStart Wizard?
# e.g. here, or as a separate utility
return False
return True
# the following routines are for sqlite export
def getAllKeys(self):
"""return all the keys from the table bibrefKey"""
try:
self.dbCursor.execute("""SELECT * FROM %s"""%self.tableKey)
return self.dbCursor.fetchall()
except Error,errorType:
self.showError('dbBib.bibrefKey: '+`errorType.args`)
def dumpKey(self,key):
self.__write(self.tableKey,key)
def getAllLinks(self):
"""return all the keys from the table bibrefLink"""
try:
self.dbCursor.execute("""SELECT * FROM %s"""%self.tableLink)
return self.dbCursor.fetchall()
except Error,errorType:
self.showError('dbBib.getAllLinks: '+`errorType.args`)
def getAllQueries(self):
"""return all the keys from the table bibrefLink"""
try:
self.dbCursor.execute("""SELECT * FROM %s"""%self.tableQuery)
return self.dbCursor.fetchall()
except Error,errorType:
self.showError('dbBib.getAllQueries: '+`errorType.args`)
def getAllModifs(self):
"""return all the modifs from the table table_modif"""
try:
self.dbCursor.execute("""SELECT * FROM %s"""%self.tableModif)
return self.dbCursor.fetchall()
except Error,errorType:
self.showError('dbBib.getAllModifs: '+`errorType.args`)
def getAllFiles(self):
"""return all the files from the table table_file"""
try:
self.dbCursor.execute("""SELECT * FROM %s"""%self.tableFile)
return self.dbCursor.fetchall()
except Error,errorType:
self.showError('dbBib.getAllFiles: '+`errorType.args`)
def dumpQuery(self,query):
self.__write(self.tableQuery,query)
# Modifications
def setCreator(self,ref_id,creator,dateCreator,modif,dateModif):
try:
tmpstr = """INSERT INTO %s VALUES (%s,%s,%s,%s,%s)"""%(self.tableModif,self.param,self.param,self.param,self.param,self.param)
self.dbCursor.execute(tmpstr, (ref_id,creator,dateCreator,modif,dateModif))
self.dbConnection.commit()
except Error,errorType:
self.showError("dbBibBase.setCreator " + `errorType.args`)
def resetCreator(self,ref_id,creator,dateCreator,modif,dateModif):
"""replace creator etc.. with new values. Used for exporting with no modif of modification table"""
try:
tmpstr = """UPDATE %s SET creator=%s,date=%s,user_modif = %s,date_modif = %s WHERE ref_Id = %s"""%(self.tableModif,self.param,self.param,self.param,self.param,self.param)
self.dbCursor.execute(tmpstr, (creator,dateCreator,modif,dateModif,ref_id))
self.dbConnection.commit()
except Error,errorType:
self.showError("dbBibBase.resetCreator " + `errorType.args`)
def updateCreator(self,ref_id):
t = time.time()
self.setCreator(ref_id,self.user,t,self.user,t)
def dataCreatorExist(self,ref_id):
self.dbCursor.execute( """SELECT ref_Id from %s where ref_Id=%s"""%(self.tableModif,self.param), (ref_id,) )
return self.dbCursor.fetchall() != []
def updateLastModif(self,ref_id):
if self.dataCreatorExist(ref_id):
try:
tmpstr = """UPDATE %s SET user_modif = %s,date_modif = %s WHERE ref_Id = %s"""%(self.tableModif,self.param,self.param,self.param)
self.dbCursor.execute(tmpstr, (self.user,time.time(),ref_id))
self.dbConnection.commit()
except Error,errorType:
self.showError("dbBibBase.updateLastModif " + `errorType.args`)
else:
self.setCreator(ref_id,_("Unknown"),0,self.user,time.time())
def getModifs(self,ref_id):
try:
tmpstr = """SELECT creator,date,user_modif,date_modif from %s WHERE ref_Id=%s"""%(self.tableModif,self.param)
self.dbCursor.execute(tmpstr, (ref_id,))
return self.dbCursor.fetchall()[0]
except:
return (_("Unknown"),0,_("Unknown"),0)
# Fulltext files. Table self.tableFile
def getFiles(self,ref_Id):
try:
self.dbCursor.execute("""SELECT path FROM %s WHERE ref_Id = %s""" %(self.tableFile,self.param), (ref_Id,))
tmp = self.dbCursor.fetchall()
if tmp:
return tuple([i[0] for i in tmp])
else:
return ()
except Error,errorType:
self.showError("dbBibBase.getFiles " + `errorType.args`)
def setFiles(self,ref_Id,refs):
try:
self.dbCursor.execute("""DELETE FROM %s WHERE ref_Id = %s""" %(self.tableFile,self.param), (ref_Id,))
values = tuple([(ref_Id,ref) for ref in refs])
self.dbCursor.executemany("""INSERT INTO %s VALUES (%s,%s)""" %(self.tableFile,self.param,self.param),values)
self.dbConnection.commit()
except Error,errorType:
self.showError("dbBibBase.setFiles " + `errorType.args`)
# Database cleanup
def emptyTrash(self):
"""We delete all the ref which are not tagged by any user"""
try:
# We delete first from table_modif
#self.dbCursor.execute("""DELETE FROM %s WHERE ref_Id IN (SELECT Id FROM %s LEFT JOIN %s ON Id=ref_Id WHERE ref_Id IS NULL)"""\
# %(self.tableModif,self.tableRef,self.tableLink))
self.dbCursor.execute("""DELETE FROM %s WHERE ref_Id NOT IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id)"""\
%(self.tableModif, self.tableLink, self.tableKey))
# Then the references
#self.dbCursor.execute("""DELETE FROM %s WHERE Id IN (SELECT Id FROM %s LEFT JOIN %s ON Id=ref_Id WHERE ref_Id IS NULL)"""\
# %(self.tableRef,self.tableRef,self.tableLink))
self.dbCursor.execute("""DELETE FROM %s WHERE Id NOT IN \
(SELECT t1.ref_Id FROM %s AS t1 \
JOIN %s AS t2 ON t1.key_Id=t2.key_Id)"""\
%(self.tableRef, self.tableLink, self.tableKey))
self.dbConnection.commit()
except Error,errorType:
self.showError('dbBib.emptyTrash' + `errorType.args`)
def checkDatabase(self):
error = False
# Checking that all the tables are present:
tables = self.getTables()
tablenames = { self.tableRef:TABLE_REF,\
self.tableKey:TABLE_KEY,\
self.tableLink:TABLE_LINK,\
self.tableQuery:TABLE_QUERY,\
self.tableModif:TABLE_MODIF,\
self.tableFile:TABLE_FILE}
for table in tablenames.keys():
if table not in tables:
error = True
ret = wx.MessageBox(_("""Table "%(name)s" is absent from the database.\nShould I fix it?""")%{'name':table},\
_("Database error"),wx.YES_NO|wx.ICON_ERROR,self.parent)
if ret == wx.YES:
self.dbCursor.execute("""create table %s %s""" % (table,tablenames[table]))
# Checking that all the fields
if not self.check_db():
self.showError( _("""Sorry, I can't fix this error."""))
error = True
return
# Checking for NULL Identifiers in bibref
self.dbCursor.execute("""SELECT Id FROM %s WHERE Identifier IS NULL"""%self.tableRef)
ids = self.dbCursor.fetchone()
if ids:
error = True
ret = wx.MessageBox(_("The database contains references with NULL Identifiers\nShould I delete them ?"),_("Database error"),wx.YES_NO|wx.ICON_ERROR,self.parent)
if ret == wx.YES:
self.dbCursor.execute("""DELETE FROM %s WHERE Identifier IS NULL"""%self.tableRef)
self.dbConnection.commit()
# Checking for Links with non-existent references
self.dbCursor.execute("""SELECT key_Id,ref_Id FROM %s LEFT JOIN %s ON Id=ref_Id WHERE Id IS NULL"""\
%(self.tableLink,self.tableRef))
ids = self.dbCursor.fetchall()
if ids:
error = True
ret = wx.MessageBox(_("The database contains wrong links\nShould I delete them ?"),_("Database error"),wx.YES_NO|wx.ICON_ERROR,self.parent)
if ret == wx.YES:
# We first delete where key or ref is NULL
self.dbCursor.execute("""DELETE FROM %s WHERE key_Id IS NULL OR ref_Id IS NULL"""%self.tableLink)
for key_Id,ref_Id in ids:
# We could use a single DELETE with MySQL but not with SQLite
# Since there are only few links to remove, it should not be a problem
self.dbCursor.execute("""DELETE FROM %s WHERE key_Id=%s AND ref_Id=%s"""\
%(self.tableLink,self.param,self.param),\
(key_Id,ref_Id))
self.dbConnection.commit()
# Checking for Links with non-existent keys
self.dbCursor.execute("""SELECT t1.key_Id,ref_Id FROM %s AS t1 \
LEFT JOIN %s AS t2 ON t1.key_Id=t2.key_Id \
WHERE t2.key_Id IS NULL"""
%(self.tableLink,self.tableKey))
ids = self.dbCursor.fetchall()
if ids:
error = True
ret = wx.MessageBox(_("The database contains wrong links\nShould I delete them ?"),_("Database error"),wx.YES_NO|wx.ICON_ERROR,self.parent)
if ret == wx.YES:
# We first delete where key or ref is NULL
self.dbCursor.execute("""DELETE FROM %s WHERE key_Id IS NULL OR ref_Id IS NULL"""%self.tableLink)
for key_Id,ref_Id in ids:
self.dbCursor.execute("""DELETE FROM %s WHERE key_Id=%s AND ref_Id=%s"""\
%(self.tableLink,self.param,self.param),\
(key_Id,ref_Id))
self.dbConnection.commit()
# Checking for Modif of non-existent references
self.dbCursor.execute("""SELECT ref_Id FROM %s LEFT JOIN %s \
ON ref_Id=Id WHERE Id IS NULL"""\
%(self.tableModif,self.tableRef))
ids = self.dbCursor.fetchone()
if ids:
error = True
ret = wx.MessageBox(_("The database contains wrong links in table_modif.\nShould I delete them ?"),_("Database error"),wx.YES_NO|wx.ICON_ERROR,self.parent)
if ret == wx.YES:
self.clean_tableModif()
# Checking for File links to non-existent references
self.dbCursor.execute("""SELECT ref_Id FROM %s LEFT JOIN %s \
ON ref_Id=Id WHERE Id IS NULL"""\
%(self.tableFile,self.tableRef))
ids = self.dbCursor.fetchone()
if ids:
error = True
ret = wx.MessageBox(_("The database contains Fulltext references pointing to non-existent references.\nShould I delete them ?"),_("Database error"),wx.YES_NO|wx.ICON_ERROR,self.parent)
if ret == wx.YES:
self.clean_tableFile()
# No error
if not error:
wx.MessageBox(_("Your database is a valid bibus database."),_("Database check"), wx.OK | wx.ICON_INFORMATION, self.parent)
#
def showError(self,message=''):
dlg=wx.MessageDialog(self.parent, message, caption = _("Database Error"), style = wx.OK | wx.CENTRE, pos = wx.DefaultPosition)
try:
dlg.ShowModal()
finally:
dlg.Destroy()
|