1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2007 Donald N. Allingham 5# Copyright (C) 2008 Brian G. Matherly 6# Copyright (C) 2010 Jakim Friant 7# Copyright (C) 2011 Tim G L Lyons 8# Copyright (C) 2012 Michiel D. Nauta 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program; if not, write to the Free Software 22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 23# 24 25"""Tools/Database Repair/Check and Repair Database""" 26# pylint: disable=too-many-statements,too-many-locals,too-many-branches 27# pylint: disable=wrong-import-position,too-many-public-methods,no-self-use 28# pylint: disable=too-many-arguments 29# ------------------------------------------------------------------------- 30# 31# python modules 32# 33# ------------------------------------------------------------------------- 34import os 35from io import StringIO 36from collections import defaultdict 37import time 38 39# ------------------------------------------------------------------------ 40# 41# Set up logging 42# 43# ------------------------------------------------------------------------ 44import logging 45 46# ------------------------------------------------------------------------- 47# 48# gtk modules 49# 50# ------------------------------------------------------------------------- 51from gi.repository import Gtk 52 53# ------------------------------------------------------------------------- 54# 55# Gramps modules 56# 57# ------------------------------------------------------------------------- 58from gramps.gen.const import GRAMPS_LOCALE as glocale 59_ = glocale.translation.gettext 60ngettext = glocale.translation.ngettext # else "nearby" comments are ignored 61from gramps.gen.lib import (Citation, Event, EventType, Family, Media, 62 Name, Note, Person, Place, Repository, Source, 63 StyledText, Tag) 64from gramps.gen.db import DbTxn, CLASS_TO_KEY_MAP 65from gramps.gen.config import config 66from gramps.gen.utils.id import create_id 67from gramps.gen.utils.db import family_name 68from gramps.gen.utils.unknown import make_unknown 69from gramps.gen.utils.file import (media_path_full, find_file) 70from gramps.gui.managedwindow import ManagedWindow 71from gramps.gen.utils.file import create_checksum 72from gramps.gui.plug import tool 73from gramps.gui.dialog import OkDialog, MissingMediaDialog 74from gramps.gen.display.name import displayer as _nd 75from gramps.gui.glade import Glade 76from gramps.gen.errors import HandleError 77 78# table for handling control chars in notes. 79# All except 09, 0A, 0D are replaced with space. 80strip_dict = dict.fromkeys(list(range(9)) + list(range(11, 13)) + 81 list(range(14, 32)), " ") 82 83 84class ProgressMeter: 85 def __init__(self, *args, **kwargs): 86 pass 87 88 def set_pass(self, *args): 89 pass 90 91 def step(self): 92 pass 93 94 def close(self): 95 pass 96 97 98# ------------------------------------------------------------------------- 99# 100# Low Level repair 101# 102# ------------------------------------------------------------------------- 103def cross_table_duplicates(db, uistate): 104 """ 105 Function to find the presence of identical handles that occur in different 106 database tables. 107 108 Assumes there are no intable duplicates, see low_level function. 109 110 :param db: the database to check 111 :type db: :class:`gen.db.read.DbBsddbRead` 112 :returns: the presence of cross table duplicate handles 113 :rtype: bool 114 """ 115 if uistate: 116 parent = uistate.window 117 else: 118 parent = None 119 progress = ProgressMeter(_('Checking Database'), '', parent=parent) 120 progress.set_pass(_('Looking for cross table duplicates'), 9) 121 logging.info('Looking for cross table duplicates') 122 total_nr_handles = 0 123 all_handles = set([]) 124 for get_handles_func in [db.get_person_handles, 125 db.get_family_handles, 126 db.get_event_handles, 127 db.get_place_handles, 128 db.get_source_handles, 129 db.get_citation_handles, 130 db.get_media_handles, 131 db.get_repository_handles, 132 db.get_note_handles]: 133 handle_list = get_handles_func() 134 total_nr_handles += len(handle_list) 135 all_handles.update(handle_list) 136 progress.step() 137 progress.close() 138 num_errors = total_nr_handles - len(all_handles) 139 if num_errors == 0: 140 logging.info(' OK: No cross table duplicates') 141 else: 142 logging.warning(' FAIL: Found %d cross table duplicates', 143 num_errors) 144 return total_nr_handles > len(all_handles) 145 146 147# ------------------------------------------------------------------------- 148# 149# runTool 150# 151# ------------------------------------------------------------------------- 152class Check(tool.BatchTool): 153 def __init__(self, dbstate, user, options_class, name, callback=None): 154 uistate = user.uistate 155 156 tool.BatchTool.__init__(self, dbstate, user, options_class, name) 157 if self.fail: 158 return 159 160 cli = uistate is None 161 if uistate: 162 from gramps.gui.utils import ProgressMeter as PM 163 global ProgressMeter 164 ProgressMeter = PM 165 166 if self.db.readonly: 167 # TODO: split plugin in a check and repair part to support 168 # checking of a read only database 169 return 170 171 # The low-level repair is bypassing the transaction mechanism. 172 # As such, we run it before starting the transaction. 173 # We only do this for the dbdir backend. 174 if self.db.__class__.__name__ == 'DbBsddb': 175 if cross_table_duplicates(self.db, uistate): 176 CheckReport(uistate, _( 177 "Your Family Tree contains cross table duplicate handles." 178 "\n " 179 "This is bad and can be fixed by making a backup of your\n" 180 "Family Tree and importing that backup in an empty family" 181 "\n" 182 "tree. The rest of the checking is skipped, the Check and" 183 "\n" 184 "Repair tool should be run anew on this new Family Tree."), 185 cli) 186 return 187 with DbTxn(_("Check Integrity"), self.db, batch=True) as trans: 188 self.db.disable_signals() 189 checker = CheckIntegrity(dbstate, uistate, trans) 190 # start with empty objects, broken links can be corrected below 191 # then. This is done before fixing encoding and missing photos, 192 # since otherwise we will be trying to fix empty records which are 193 # then going to be deleted. 194 checker.cleanup_empty_objects() 195 checker.fix_encoding() 196 checker.fix_alt_place_names() 197 checker.fix_ctrlchars_in_notes() 198 checker.cleanup_missing_photos(cli) 199 checker.cleanup_deleted_name_formats() 200 201 prev_total = -1 202 total = 0 203 204 while prev_total != total: 205 prev_total = total 206 207 checker.check_for_broken_family_links() 208 checker.check_parent_relationships() 209 checker.cleanup_empty_families(cli) 210 checker.cleanup_duplicate_spouses() 211 212 total = checker.family_errors() 213 214 checker.fix_duplicated_grampsid() 215 checker.check_events() 216 checker.check_person_references() 217 checker.check_family_references() 218 checker.check_place_references() 219 checker.check_source_references() 220 checker.check_citation_references() 221 checker.check_media_references() 222 checker.check_repo_references() 223 checker.check_note_references() 224 checker.check_tag_references() 225 checker.check_checksum() 226 checker.check_media_sourceref() 227 228 # for bsddb the check_backlinks doesn't work in 'batch' mode because 229 # the table used for backlinks is closed. 230 with DbTxn(_("Check Backlink Integrity"), self.db, 231 batch=False) as checker.trans: 232 checker.check_backlinks() 233 234 # rebuilding reference maps needs to be done outside of a transaction 235 # to avoid nesting transactions. 236 if checker.bad_backlinks: 237 checker.progress.set_pass(_('Rebuilding reference maps...'), 6) 238 logging.info('Rebuilding reference maps...') 239 self.db.reindex_reference_map(checker.callback) 240 else: 241 logging.info(' OK: no backlink problems found') 242 243 self.db.enable_signals() 244 self.db.request_rebuild() 245 246 errs = checker.build_report(uistate) 247 if errs: 248 CheckReport(uistate, checker.text.getvalue(), cli) 249 250 251# ------------------------------------------------------------------------- 252# 253# 254# 255# ------------------------------------------------------------------------- 256class CheckIntegrity: 257 258 def __init__(self, dbstate, uistate, trans): 259 self.uistate = uistate 260 if self.uistate: 261 self.parent_window = self.uistate.window 262 else: 263 self.parent_window = None 264 self.db = dbstate.db 265 self.trans = trans 266 self.bad_photo = [] 267 self.replaced_photo = [] 268 self.removed_photo = [] 269 self.empty_family = [] 270 self.broken_links = [] 271 self.duplicate_links = [] 272 self.broken_parent_links = [] 273 self.fam_rel = [] 274 self.invalid_events = set() 275 self.invalid_birth_events = set() 276 self.invalid_death_events = set() 277 self.invalid_person_references = set() 278 self.invalid_family_references = set() 279 self.invalid_place_references = set() 280 self.invalid_source_references = set() 281 self.invalid_citation_references = set() 282 self.invalid_repo_references = set() 283 self.invalid_media_references = set() 284 self.invalid_note_references = set() 285 self.invalid_tag_references = set() 286 self.invalid_dates = [] 287 self.removed_name_format = [] 288 self.empty_objects = defaultdict(list) 289 self.replaced_sourceref = [] 290 self.place_errors = 0 291 self.duplicated_gramps_ids = 0 292 self.bad_backlinks = 0 293 self.text = StringIO() 294 self.last_img_dir = config.get('behavior.addmedia-image-dir') 295 self.progress = ProgressMeter(_('Checking Database'), '', 296 parent=self.parent_window) 297 self.explanation = Note(_( 298 'Objects referenced by this note were referenced but ' 299 'missing so that is why they have been created ' 300 'when you ran Check and Repair on %s.') % 301 time.strftime('%x %X', time.localtime())) 302 self.explanation.set_handle(create_id()) 303 304 def family_errors(self): 305 return (len(self.broken_parent_links) + 306 len(self.broken_links) + 307 len(self.empty_family) + 308 len(self.duplicate_links)) 309 310 def cleanup_deleted_name_formats(self): 311 """ 312 Permanently remove deleted name formats from db. 313 314 When user deletes custom name format those are not removed only marked 315 as "inactive". This method does the cleanup of the name format table, 316 as well as fixes the display_as, sort_as values for each Name in the 317 db. 318 319 """ 320 self.progress.set_pass(_('Looking for invalid name format references'), 321 self.db.get_number_of_people()) 322 logging.info('Looking for invalid name format references') 323 324 deleted_name_formats = [number for (number, name, dummy, act) 325 in self.db.name_formats if not act] 326 327 # remove the invalid references from all Name objects 328 for person_handle in self.db.get_person_handles(): 329 person = self.db.get_person_from_handle(person_handle) 330 331 p_changed = False 332 name = person.get_primary_name() 333 if name.get_sort_as() in deleted_name_formats: 334 name.set_sort_as(Name.DEF) 335 p_changed = True 336 if name.get_display_as() in deleted_name_formats: 337 name.set_display_as(Name.DEF) 338 p_changed = True 339 if p_changed: 340 person.set_primary_name(name) 341 342 a_changed = False 343 name_list = [] 344 for name in person.get_alternate_names(): 345 if name.get_sort_as() in deleted_name_formats: 346 name.set_sort_as(Name.DEF) 347 a_changed = True 348 if name.get_display_as() in deleted_name_formats: 349 name.set_display_as(Name.DEF) 350 a_changed = True 351 name_list.append(name) 352 if a_changed: 353 person.set_alternate_names(name_list) 354 355 if p_changed or a_changed: 356 self.db.commit_person(person, self.trans) 357 self.removed_name_format.append(person_handle) 358 359 self.progress.step() 360 361 # update the custom name name format table 362 for number in deleted_name_formats: 363 _nd.del_name_format(number) 364 self.db.name_formats = _nd.get_name_format(only_custom=True, 365 only_active=False) 366 367 if len(self.removed_name_format) == 0: 368 logging.info(' OK: no invalid name formats found found') 369 370 def cleanup_duplicate_spouses(self): 371 372 self.progress.set_pass(_('Looking for duplicate spouses'), 373 self.db.get_number_of_people()) 374 logging.info('Looking for duplicate spouses') 375 previous_errors = len(self.duplicate_links) 376 377 for handle in self.db.get_person_handles(): 378 pers = self.db.get_person_from_handle(handle) 379 splist = pers.get_family_handle_list() 380 if len(splist) != len(set(splist)): 381 new_list = [] 382 for value in splist: 383 if value not in new_list: 384 new_list.append(value) 385 self.duplicate_links.append((handle, value)) 386 pers.set_family_handle_list(new_list) 387 self.db.commit_person(pers, self.trans) 388 self.progress.step() 389 390 if previous_errors == len(self.duplicate_links): 391 logging.info(' OK: no duplicate spouses found') 392 393 def fix_encoding(self): 394 self.progress.set_pass(_('Looking for character encoding errors'), 395 self.db.get_number_of_media()) 396 logging.info('Looking for character encoding errors') 397 error_count = 0 398 for handle in self.db.get_media_handles(): 399 data = self.db.get_raw_media_data(handle) 400 if not isinstance(data[2], str) or not isinstance(data[4], str): 401 obj = self.db.get_media_from_handle(handle) 402 if not isinstance(data[2], str): 403 obj.path = obj.path.decode('utf-8') 404 logging.warning(' FAIL: encoding error on media object ' 405 '"%(gid)s" path "%(path)s"', 406 {'gid': obj.gramps_id, 'path': obj.path}) 407 if not isinstance(data[4], str): 408 obj.desc = obj.desc.decode('utf-8') 409 logging.warning(' FAIL: encoding error on media object ' 410 '"%(gid)s" description "%(desc)s"', 411 {'gid': obj.gramps_id, 'desc': obj.desc}) 412 self.db.commit_media(obj, self.trans) 413 error_count += 1 414 # Once we are here, fix the mime string if not str 415 if not isinstance(data[3], str): 416 obj = self.db.get_media_from_handle(handle) 417 try: 418 if data[3] == str(data[3]): 419 obj.mime = str(data[3]) 420 else: 421 obj.mime = "" 422 except: 423 obj.mime = "" 424 self.db.commit_media(obj, self.trans) 425 logging.warning(' FAIL: encoding error on media object ' 426 '"%(desc)s" mime "%(mime)s"', 427 {'desc': obj.desc, 'mime': obj.mime}) 428 error_count += 1 429 self.progress.step() 430 if error_count == 0: 431 logging.info(' OK: no encoding errors found') 432 433 def fix_ctrlchars_in_notes(self): 434 self.progress.set_pass(_('Looking for ctrl characters in notes'), 435 self.db.get_number_of_notes()) 436 logging.info('Looking for ctrl characters in notes') 437 error_count = 0 438 for handle in self.db.get_note_handles(): 439 note = self.db.get_note_from_handle(handle) 440 stext = note.get_styledtext() 441 old_text = str(stext) 442 new_text = old_text.translate(strip_dict) 443 if old_text != new_text: 444 logging.warning(' FAIL: control characters found in note' 445 ' "%s"', note.get_gramps_id()) 446 error_count += 1 447 # Commit only if ctrl char found. 448 note.set_styledtext(StyledText(text=new_text, 449 tags=stext.get_tags())) 450 self.db.commit_note(note, self.trans) 451 self.progress.step() 452 if error_count == 0: 453 logging.info(' OK: no ctrl characters in notes found') 454 455 def fix_alt_place_names(self): 456 """ 457 This scans all places and cleans up alternative names. It removes 458 Blank names, names that are duplicates of the primary name, and 459 duplicates in the alt_names list. 460 """ 461 self.progress.set_pass(_('Looking for bad alternate place names'), 462 self.db.get_number_of_places()) 463 logging.info('Looking for bad alternate place names') 464 for handle in self.db.get_place_handles(): 465 place = self.db.get_place_from_handle(handle) 466 fixed_alt_names = [] 467 fixup = False 468 for name in place.get_alternative_names(): 469 if not name.value or \ 470 name == place.name or \ 471 name in fixed_alt_names: 472 fixup = True 473 continue 474 fixed_alt_names.append(name) 475 if fixup: 476 place.set_alternative_names(fixed_alt_names) 477 self.db.commit_place(place, self.trans) 478 self.place_errors += 1 479 self.progress.step() 480 if self.place_errors == 0: 481 logging.info(' OK: no bad alternate places found') 482 else: 483 logging.info(' %d bad alternate places found and fixed', 484 self.place_errors) 485 486 def check_for_broken_family_links(self): 487 # Check persons referenced by the family objects 488 489 fhandle_list = self.db.get_family_handles() 490 self.progress.set_pass(_('Looking for broken family links'), 491 len(fhandle_list) + 492 self.db.get_number_of_people()) 493 logging.info('Looking for broken family links') 494 previous_errors = len(self.broken_parent_links + self.broken_links) 495 496 for family_handle in fhandle_list: 497 family = self.db.get_family_from_handle(family_handle) 498 father_handle = family.get_father_handle() 499 mother_handle = family.get_mother_handle() 500 if father_handle: 501 try: 502 father = self.db.get_person_from_handle(father_handle) 503 except HandleError: 504 # The person referenced by the father handle does not exist 505 # in the database 506 # This is tested by TestcaseGenerator where the mother is 507 # "Broken6" 508 family.set_father_handle(None) 509 self.db.commit_family(family, self.trans) 510 self.broken_parent_links.append((father_handle, 511 family_handle)) 512 logging.warning(" FAIL: family '%(fam_gid)s' " 513 "father handle '%(hand)s' does not exist", 514 {'fam_gid': family.gramps_id, 515 'hand': father_handle}) 516 father_handle = None 517 if mother_handle: 518 try: 519 mother = self.db.get_person_from_handle(mother_handle) 520 except HandleError: 521 # The person referenced by the mother handle does not exist 522 # in the database 523 # This is tested by TestcaseGenerator where the mother is 524 # "Broken7" 525 family.set_mother_handle(None) 526 self.db.commit_family(family, self.trans) 527 self.broken_parent_links.append((mother_handle, 528 family_handle)) 529 logging.warning(" FAIL: family '%(fam_gid)s' " 530 "mother handle '%(hand)s' does not exist", 531 {'fam_gid': family.gramps_id, 532 'hand': mother_handle}) 533 mother_handle = None 534 535 if father_handle and father and \ 536 family_handle not in father.get_family_handle_list(): 537 # The referenced father has no reference back to the family 538 # This is tested by TestcaseGenerator where the father is 539 # "Broken1" 540 self.broken_parent_links.append((father_handle, family_handle)) 541 father.add_family_handle(family_handle) 542 self.db.commit_person(father, self.trans) 543 logging.warning(" FAIL: family '%(fam_gid)s' father " 544 "'%(hand)s' does not refer back to the family", 545 {'fam_gid': family.gramps_id, 546 'hand': father_handle}) 547 548 if mother_handle and mother and \ 549 family_handle not in mother.get_family_handle_list(): 550 # The referenced mother has no reference back to the family. 551 # This is tested by TestcaseGenerator where the father is 552 # "Broken4" 553 self.broken_parent_links.append((mother_handle, family_handle)) 554 mother.add_family_handle(family_handle) 555 self.db.commit_person(mother, self.trans) 556 logging.warning(" FAIL: family '%(fam_gid)s' mother " 557 "'%(hand)s' does not refer back to the family", 558 {'fam_gid': family.gramps_id, 559 'hand': mother_handle}) 560 561 for child_ref in family.get_child_ref_list(): 562 child_handle = child_ref.ref 563 try: 564 child = self.db.get_person_from_handle(child_handle) 565 except HandleError: 566 # The person referenced by the child handle 567 # does not exist in the database 568 # This is tested by TestcaseGenerator where the father 569 # is "Broken20" 570 logging.warning(" FAIL: family '%(fam_gid)s' child " 571 "'%(hand)s' does not exist in the " 572 "database", 573 {'fam_gid': family.gramps_id, 574 'hand': child_handle}) 575 family.remove_child_ref(child_ref) 576 self.db.commit_family(family, self.trans) 577 self.broken_links.append((child_handle, family_handle)) 578 else: 579 if child_handle in [father_handle, mother_handle]: 580 # The child is one of the parents: impossible Remove 581 # such child from the family 582 # This is tested by TestcaseGenerator where the father 583 # is "Broken19" 584 logging.warning(" FAIL: family '%(fam_gid)s' " 585 "child '%(child_gid)s' is one of the " 586 "parents", 587 {'fam_gid': family.gramps_id, 588 'child_gid': child.gramps_id}) 589 family.remove_child_ref(child_ref) 590 self.db.commit_family(family, self.trans) 591 self.broken_links.append((child_handle, family_handle)) 592 continue 593 if family_handle == child.get_main_parents_family_handle(): 594 continue 595 if family_handle not in \ 596 child.get_parent_family_handle_list(): 597 # The referenced child has no reference to the family 598 # This is tested by TestcaseGenerator where the father 599 # is "Broken8" 600 logging.warning( 601 " FAIL: family '%(fam_gid)s' " 602 "child '%(child_gid)s' has no reference" 603 " to the family. Reference added", 604 {'fam_gid': family.gramps_id, 605 'child_gid': child.gramps_id}) 606 child.add_parent_family_handle(family_handle) 607 self.db.commit_person(child, self.trans) 608 609 new_ref_list = [] 610 new_ref_handles = [] 611 replace = False 612 for child_ref in family.get_child_ref_list(): 613 child_handle = child_ref.ref 614 if child_handle in new_ref_handles: 615 replace = True 616 else: 617 new_ref_list.append(child_ref) 618 new_ref_handles.append(child_handle) 619 620 if replace: 621 family.set_child_ref_list(new_ref_list) 622 self.db.commit_family(family, self.trans) 623 624 self.progress.step() 625 626 # Check persons membership in referenced families 627 for person_handle in self.db.get_person_handles(): 628 person = self.db.get_person_from_handle(person_handle) 629 630 phandle_list = person.get_parent_family_handle_list() 631 new_list = list(set(phandle_list)) 632 if len(phandle_list) != len(new_list): 633 person.set_parent_family_handle_list(new_list) 634 self.db.commit_person(person, self.trans) 635 636 for par_family_handle in person.get_parent_family_handle_list(): 637 try: 638 family = self.db.get_family_from_handle(par_family_handle) 639 except HandleError: 640 person.remove_parent_family_handle(par_family_handle) 641 self.db.commit_person(person, self.trans) 642 continue 643 for child_handle in [child_ref.ref for child_ref 644 in family.get_child_ref_list()]: 645 if child_handle == person_handle: 646 break 647 else: 648 # Person is not a child in the referenced parent family 649 # This is tested by TestcaseGenerator where the father 650 # is "Broken9" 651 logging.warning(" FAIL: family '%(fam_gid)s' person " 652 "'%(pers_gid)s' is not a child in the " 653 "referenced parent family", 654 {'fam_gid': family.gramps_id, 655 'pers_gid': person.gramps_id}) 656 person.remove_parent_family_handle(par_family_handle) 657 self.db.commit_person(person, self.trans) 658 self.broken_links.append((person_handle, family_handle)) 659 for family_handle in person.get_family_handle_list(): 660 try: 661 family = self.db.get_family_from_handle(family_handle) 662 except HandleError: 663 # The referenced family does not exist in database 664 # This is tested by TestcaseGenerator where the father 665 # is "Broken20" 666 logging.warning(" FAIL: person '%(pers_gid)s' refers " 667 "to family '%(hand)s' which is not in the " 668 "database", 669 {'pers_gid': person.gramps_id, 670 'hand': family_handle}) 671 person.remove_family_handle(family_handle) 672 self.db.commit_person(person, self.trans) 673 self.broken_links.append((person_handle, family_handle)) 674 continue 675 if family.get_father_handle() == person_handle: 676 continue 677 if family.get_mother_handle() == person_handle: 678 continue 679 # The person is not a member of the referenced family 680 # This is tested by TestcaseGenerator where the father is 681 # "Broken2" and the family misses the link to the father, and 682 # where the mother is "Broken3" and the family misses the link 683 # to the mother 684 logging.warning(" FAIL: family '%(fam_gid)s' person " 685 "'%(pers_gid)s' is not member of the " 686 "referenced family", 687 {'fam_gid': family.gramps_id, 688 'pers_gid': person.gramps_id}) 689 person.remove_family_handle(family_handle) 690 self.db.commit_person(person, self.trans) 691 self.broken_links.append((person_handle, family_handle)) 692 self.progress.step() 693 694 if previous_errors == len(self.broken_parent_links + 695 self.broken_links): 696 logging.info(' OK: no broken family links found') 697 698 def cleanup_missing_photos(self, cli=0): 699 700 self.progress.set_pass(_('Looking for unused objects'), 701 len(self.db.get_media_handles())) 702 logging.info('Looking for missing photos') 703 704 missmedia_action = 0 705 706 # --------------------------------------------------------------------- 707 def remove_clicked(): 708 # File is lost => remove all references and the object itself 709 710 for handle in self.db.get_person_handles(sort_handles=False): 711 person = self.db.get_person_from_handle(handle) 712 if person.has_media_reference(objectid): 713 person.remove_media_references([objectid]) 714 self.db.commit_person(person, self.trans) 715 716 for handle in self.db.get_family_handles(): 717 family = self.db.get_family_from_handle(handle) 718 if family.has_media_reference(objectid): 719 family.remove_media_references([objectid]) 720 self.db.commit_family(family, self.trans) 721 722 for handle in self.db.get_event_handles(): 723 event = self.db.get_event_from_handle(handle) 724 if event.has_media_reference(objectid): 725 event.remove_media_references([objectid]) 726 self.db.commit_event(event, self.trans) 727 728 for handle in self.db.get_source_handles(): 729 source = self.db.get_source_from_handle(handle) 730 if source.has_media_reference(objectid): 731 source.remove_media_references([objectid]) 732 self.db.commit_source(source, self.trans) 733 734 for handle in self.db.get_citation_handles(): 735 citation = self.db.get_citation_from_handle(handle) 736 if citation.has_media_reference(objectid): 737 citation.remove_media_references([objectid]) 738 self.db.commit_citation(citation, self.trans) 739 740 for handle in self.db.get_place_handles(): 741 place = self.db.get_place_from_handle(handle) 742 if place.has_media_reference(objectid): 743 place.remove_media_references([objectid]) 744 self.db.commit_place(place, self.trans) 745 746 self.removed_photo.append(objectid) 747 self.db.remove_media(objectid, self.trans) 748 logging.warning(' FAIL: media object and all references to ' 749 'it removed') 750 751 def leave_clicked(): 752 self.bad_photo.append(objectid) 753 logging.warning(' FAIL: references to missing file kept') 754 755 def select_clicked(): 756 # File is lost => select a file to replace the lost one 757 def fs_close_window(dummy): 758 self.bad_photo.append(objectid) 759 logging.warning(' FAIL: references to missing file ' 760 'kept') 761 762 def fs_ok_clicked(obj): 763 name = fs_top.get_filename() 764 if os.path.isfile(name): 765 obj = self.db.get_media_from_handle(objectid) 766 obj.set_path(name) 767 self.db.commit_media(obj, self.trans) 768 self.replaced_photo.append(objectid) 769 self.last_img_dir = os.path.dirname(name) 770 logging.warning(' FAIL: media object reselected to ' 771 '"%s"', name) 772 else: 773 self.bad_photo.append(objectid) 774 logging.warning(' FAIL: references to missing file ' 775 'kept') 776 777 fs_top = Gtk.FileChooserDialog( 778 "%s - Gramps" % _("Select file"), 779 parent=self.parent_window, 780 buttons=(_('_Cancel'), Gtk.ResponseType.CANCEL, 781 _('_OK'), Gtk.ResponseType.OK)) 782 fs_top.set_current_folder(self.last_img_dir) 783 response = fs_top.run() 784 if response == Gtk.ResponseType.OK: 785 fs_ok_clicked(fs_top) 786 elif response == Gtk.ResponseType.CANCEL: 787 fs_close_window(fs_top) 788 fs_top.destroy() 789 790 # -------------------------------------------------------------------- 791 792 for objectid in self.db.get_media_handles(): 793 obj = self.db.get_media_from_handle(objectid) 794 photo_name = media_path_full(self.db, obj.get_path()) 795 photo_desc = obj.get_description() 796 if photo_name is not None and photo_name != "" \ 797 and not find_file(photo_name): 798 if cli: 799 logging.warning(" FAIL: media file %s was not found.", 800 photo_name) 801 self.bad_photo.append(objectid) 802 else: 803 if missmedia_action == 0: 804 logging.warning(' FAIL: media object "%(desc)s" ' 805 'reference to missing file "%(name)s" ' 806 'found', 807 {'desc': photo_desc, 808 'name': photo_name}) 809 mmd = MissingMediaDialog( 810 _("Media object could not be found"), 811 _("The file:\n%(file_name)s\nis referenced in " 812 "the database, but no longer exists.\n" 813 "The file may have been deleted or moved to " 814 "a different location.\n" 815 "You may choose to either remove the " 816 "reference from the database,\n" 817 "keep the reference to the missing file, " 818 "or select a new file.") 819 % {'file_name': '<b>%s</b>' % photo_name}, 820 remove_clicked, leave_clicked, select_clicked, 821 parent=self.uistate.window) 822 missmedia_action = mmd.default_action 823 elif missmedia_action == 1: 824 logging.warning(' FAIL: media object "%(desc)s" ' 825 'reference to missing file "%(name)s" ' 826 'found', 827 {'desc': photo_desc, 828 'name': photo_name}) 829 remove_clicked() 830 elif missmedia_action == 2: 831 logging.warning(' FAIL: media object "%(desc)s" ' 832 'reference to missing file "%(name)s" ' 833 'found', 834 {'desc': photo_desc, 835 'name': photo_name}) 836 leave_clicked() 837 elif missmedia_action == 3: 838 logging.warning(' FAIL: media object "%(desc)s" ' 839 'reference to missing file "%(name)s" ' 840 'found', 841 {'desc': photo_desc, 842 'name': photo_name}) 843 select_clicked() 844 self.progress.step() 845 if len(self.bad_photo + self.removed_photo) == 0: 846 logging.info(' OK: no missing photos found') 847 848 def cleanup_empty_objects(self): 849 # the position of the change column in the primary objects 850 CHANGE_PERSON = 17 851 CHANGE_FAMILY = 12 852 CHANGE_EVENT = 10 853 CHANGE_SOURCE = 8 854 CHANGE_CITATION = 9 855 CHANGE_PLACE = 11 856 CHANGE_MEDIA = 8 857 CHANGE_REPOS = 7 858 CHANGE_NOTE = 5 859 860 empty_person_data = Person().serialize() 861 empty_family_data = Family().serialize() 862 empty_event_data = Event().serialize() 863 empty_source_data = Source().serialize() 864 empty_citation_data = Citation().serialize() 865 empty_place_data = Place().serialize() 866 empty_media_data = Media().serialize() 867 empty_repos_data = Repository().serialize() 868 empty_note_data = Note().serialize() 869 870 _db = self.db 871 872 def _empty(empty, flag): 873 ''' Closure for dispatch table, below ''' 874 def _fx(value): 875 return self._check_empty(value, empty, flag) 876 return _fx 877 878 table = ( 879 880 # Dispatch table for cleaning up empty objects. Each entry is 881 # a tuple containing: 882 # 0. Type of object being cleaned up 883 # 1. function to read the object from the database 884 # 2. function returning cursor over the object type 885 # 3. function returning number of objects of this type 886 # 4. text identifying the object being cleaned up 887 # 5. function to check if the data is empty 888 # 6. function to remove the object, if empty 889 890 ('persons', 891 _db.get_person_from_handle, 892 _db.get_person_cursor, 893 _db.get_number_of_people, 894 _('Looking for empty people records'), 895 _empty(empty_person_data, CHANGE_PERSON), 896 _db.remove_person), 897 ('families', 898 _db.get_family_from_handle, 899 _db.get_family_cursor, 900 _db.get_number_of_families, 901 _('Looking for empty family records'), 902 _empty(empty_family_data, CHANGE_FAMILY), 903 _db.remove_family), 904 ('events', 905 _db.get_event_from_handle, 906 _db.get_event_cursor, 907 _db.get_number_of_events, 908 _('Looking for empty event records'), 909 _empty(empty_event_data, CHANGE_EVENT), 910 _db.remove_event), 911 ('sources', 912 _db.get_source_from_handle, 913 _db.get_source_cursor, 914 _db.get_number_of_sources, 915 _('Looking for empty source records'), 916 _empty(empty_source_data, CHANGE_SOURCE), 917 _db.remove_source), 918 ('citations', 919 _db.get_citation_from_handle, 920 _db.get_citation_cursor, 921 _db.get_number_of_citations, 922 _('Looking for empty citation records'), 923 _empty(empty_citation_data, CHANGE_CITATION), 924 _db.remove_citation), 925 ('places', 926 _db.get_place_from_handle, 927 _db.get_place_cursor, 928 _db.get_number_of_places, 929 _('Looking for empty place records'), 930 _empty(empty_place_data, CHANGE_PLACE), 931 _db.remove_place), 932 ('media', 933 _db.get_media_from_handle, 934 _db.get_media_cursor, 935 _db.get_number_of_media, 936 _('Looking for empty media records'), 937 _empty(empty_media_data, CHANGE_MEDIA), 938 _db.remove_media), 939 ('repos', 940 _db.get_repository_from_handle, 941 _db.get_repository_cursor, 942 _db.get_number_of_repositories, 943 _('Looking for empty repository records'), 944 _empty(empty_repos_data, CHANGE_REPOS), 945 _db.remove_repository), 946 ('notes', 947 _db.get_note_from_handle, 948 _db.get_note_cursor, 949 _db.get_number_of_notes, 950 _('Looking for empty note records'), 951 _empty(empty_note_data, CHANGE_NOTE), 952 _db.remove_note), 953 ) 954 955 # Now, iterate over the table, dispatching the functions 956 957 for (the_type, dummy, cursor_func, total_func, 958 text, check_func, remove_func) in table: 959 960 with cursor_func() as cursor: 961 total = total_func() 962 self.progress.set_pass(text, total) 963 logging.info(text) 964 965 for handle, data in cursor: 966 self.progress.step() 967 if check_func(data): 968 # we cannot remove here as that would destroy cursor 969 # so save the handles for later removal 970 logging.warning(' FAIL: empty %(type)s record with ' 971 'handle "%(hand)s" was found', 972 {'type': the_type, 'hand': handle}) 973 self.empty_objects[the_type].append(handle) 974 975 # now remove 976 for handle in self.empty_objects[the_type]: 977 remove_func(handle, self.trans) 978 if len(self.empty_objects[the_type]) == 0: 979 logging.info(' OK: no empty %s found', the_type) 980 981 def _check_empty(self, data, empty_data, changepos): 982 """compare the data with the data of an empty object 983 change, handle and gramps_id are not compared """ 984 if changepos is not None: 985 return (data[2:changepos] == empty_data[2:changepos] and 986 data[changepos + 1:] == empty_data[changepos + 1:]) 987 else: 988 return data[2:] == empty_data[2:] 989 990 def cleanup_empty_families(self, dummy): 991 992 fhandle_list = self.db.get_family_handles() 993 994 self.progress.set_pass(_('Looking for empty families'), 995 len(fhandle_list)) 996 logging.info('Looking for empty families') 997 previous_errors = len(self.empty_family) 998 for family_handle in fhandle_list: 999 self.progress.step() 1000 1001 family = self.db.get_family_from_handle(family_handle) 1002 family_id = family.get_gramps_id() 1003 father_handle = family.get_father_handle() 1004 mother_handle = family.get_mother_handle() 1005 1006 if not father_handle and not mother_handle and \ 1007 len(family.get_child_ref_list()) == 0: 1008 self.empty_family.append(family_id) 1009 self.delete_empty_family(family_handle) 1010 1011 if previous_errors == len(self.empty_family): 1012 logging.info(' OK: no empty families found') 1013 1014 def delete_empty_family(self, family_handle): 1015 for key in self.db.get_person_handles(sort_handles=False): 1016 child = self.db.get_person_from_handle(key) 1017 changed = False 1018 changed |= child.remove_parent_family_handle(family_handle) 1019 changed |= child.remove_family_handle(family_handle) 1020 if changed: 1021 self.db.commit_person(child, self.trans) 1022 self.db.remove_family(family_handle, self.trans) 1023 1024 def check_parent_relationships(self): 1025 """Repair father=female or mother=male in hetero families 1026 """ 1027 1028 fhandle_list = self.db.get_family_handles() 1029 self.progress.set_pass(_('Looking for broken parent relationships'), 1030 len(fhandle_list)) 1031 logging.info('Looking for broken parent relationships') 1032 previous_errors = len(self.fam_rel) 1033 1034 for family_handle in fhandle_list: 1035 self.progress.step() 1036 family = self.db.get_family_from_handle(family_handle) 1037 1038 father_handle = family.get_father_handle() 1039 if father_handle: 1040 fgender = self.db.get_person_from_handle( 1041 father_handle).get_gender() 1042 else: 1043 fgender = None 1044 1045 mother_handle = family.get_mother_handle() 1046 if mother_handle: 1047 mgender = self.db.get_person_from_handle( 1048 mother_handle).get_gender() 1049 else: 1050 mgender = None 1051 1052 if (fgender == Person.FEMALE or 1053 mgender == Person.MALE) and fgender != mgender: 1054 # swap. note: (at most) one handle may be None 1055 logging.warning(' FAIL: the family "%s" has a father=female' 1056 ' or mother=male in a different sex family', 1057 family.gramps_id) 1058 family.set_father_handle(mother_handle) 1059 family.set_mother_handle(father_handle) 1060 self.db.commit_family(family, self.trans) 1061 self.fam_rel.append(family_handle) 1062 1063 if previous_errors == len(self.fam_rel): 1064 logging.info(' OK: no broken parent relationships found') 1065 1066 def check_events(self): 1067 '''Looking for event problems''' 1068 self.progress.set_pass(_('Looking for event problems'), 1069 self.db.get_number_of_people() + 1070 self.db.get_number_of_families()) 1071 logging.info('Looking for event problems') 1072 1073 for key in self.db.get_person_handles(sort_handles=False): 1074 self.progress.step() 1075 1076 person = self.db.get_person_from_handle(key) 1077 birth_ref = person.get_birth_ref() 1078 none_handle = False 1079 if birth_ref: 1080 newref = birth_ref 1081 if not birth_ref.ref: 1082 none_handle = True 1083 birth_ref.ref = create_id() 1084 birth_handle = birth_ref.ref 1085 try: 1086 birth = self.db.get_event_from_handle(birth_handle) 1087 except HandleError: 1088 # The birth event referenced by the birth handle 1089 # does not exist in the database 1090 # This is tested by TestcaseGenerator person "Broken11" 1091 make_unknown(birth_handle, self.explanation.handle, 1092 self.class_event, self.commit_event, 1093 self.trans, type=EventType.BIRTH) 1094 logging.warning(' FAIL: the person "%(gid)s" refers to ' 1095 'a birth event "%(hand)s" which does not ' 1096 'exist in the database', 1097 {'gid': person.gramps_id, 1098 'hand': birth_handle}) 1099 self.invalid_events.add(key) 1100 else: 1101 if int(birth.get_type()) != EventType.BIRTH: 1102 # Birth event was not of the type "Birth" 1103 # This is tested by TestcaseGenerator person "Broken14" 1104 logging.warning(' FAIL: the person "%(gid)s" refers' 1105 ' to a birth event which is of type ' 1106 '"%(type)s" instead of Birth', 1107 {'gid': person.gramps_id, 1108 'type': int(birth.get_type())}) 1109 birth.set_type(EventType(EventType.BIRTH)) 1110 self.db.commit_event(birth, self.trans) 1111 self.invalid_birth_events.add(key) 1112 if none_handle: 1113 person.set_birth_ref(newref) 1114 self.db.commit_person(person, self.trans) 1115 1116 none_handle = False 1117 death_ref = person.get_death_ref() 1118 if death_ref: 1119 newref = death_ref 1120 if not death_ref.ref: 1121 none_handle = True 1122 death_ref.ref = create_id() 1123 death_handle = death_ref.ref 1124 try: 1125 death = self.db.get_event_from_handle(death_handle) 1126 except HandleError: 1127 # The death event referenced by the death handle 1128 # does not exist in the database 1129 # This is tested by TestcaseGenerator person "Broken12" 1130 logging.warning(' FAIL: the person "%(gid)s" refers to ' 1131 'a death event "%(hand)s" which does not ' 1132 'exist in the database', 1133 {'gid': person.gramps_id, 1134 'hand': death_handle}) 1135 make_unknown(death_handle, self.explanation.handle, 1136 self.class_event, self.commit_event, 1137 self.trans, type=EventType.DEATH) 1138 self.invalid_events.add(key) 1139 else: 1140 if int(death.get_type()) != EventType.DEATH: 1141 # Death event was not of the type "Death" 1142 # This is tested by TestcaseGenerator person "Broken15" 1143 logging.warning( 1144 ' FAIL: the person "%(gid)s" refers to a death ' 1145 'event which is of type "%(type)s" instead of ' 1146 'Death', 1147 {'gid': person.gramps_id, 1148 'type': int(death.get_type())}) 1149 death.set_type(EventType(EventType.DEATH)) 1150 self.db.commit_event(death, self.trans) 1151 self.invalid_death_events.add(key) 1152 if none_handle: 1153 person.set_death_ref(newref) 1154 self.db.commit_person(person, self.trans) 1155 1156 none_handle = False 1157 newlist = [] 1158 if person.get_event_ref_list(): 1159 for event_ref in person.get_event_ref_list(): 1160 newlist.append(event_ref) 1161 if not event_ref.ref: 1162 none_handle = True 1163 event_ref.ref = create_id() 1164 event_handle = event_ref.ref 1165 try: 1166 self.db.get_event_from_handle(event_handle) 1167 except HandleError: 1168 # The event referenced by the person 1169 # does not exist in the database 1170 # TODO: There is no better way? 1171 # This is tested by TestcaseGenerator person "Broken11" 1172 # This is tested by TestcaseGenerator person "Broken12" 1173 # This is tested by TestcaseGenerator person "Broken13" 1174 logging.warning( 1175 ' FAIL: the person "%(gid)s" refers to an event' 1176 ' "%(hand)s" which does not exist in the database', 1177 {'gid': person.gramps_id, 1178 'hand': event_handle}) 1179 make_unknown(event_handle, self.explanation.handle, 1180 self.class_event, 1181 self.commit_event, self.trans) 1182 self.invalid_events.add(key) 1183 if none_handle: 1184 person.set_event_ref_list(newlist) 1185 self.db.commit_person(person, self.trans) 1186 elif not isinstance(person.get_event_ref_list(), list): 1187 # event_list is None or other garbage 1188 logging.warning(' FAIL: the person "%s" has an event ref ' 1189 'list which is invalid', (person.gramps_id)) 1190 person.set_event_ref_list([]) 1191 self.db.commit_person(person, self.trans) 1192 self.invalid_events.add(key) 1193 1194 for key in self.db.get_family_handles(): 1195 self.progress.step() 1196 family = self.db.get_family_from_handle(key) 1197 if family.get_event_ref_list(): 1198 none_handle = False 1199 newlist = [] 1200 for event_ref in family.get_event_ref_list(): 1201 newlist.append(event_ref) 1202 if not event_ref.ref: 1203 none_handle = True 1204 event_ref.ref = create_id() 1205 event_handle = event_ref.ref 1206 try: 1207 self.db.get_event_from_handle(event_handle) 1208 except HandleError: 1209 # The event referenced by the family 1210 # does not exist in the database 1211 logging.warning(' FAIL: the family "%(gid)s" refers' 1212 ' to an event "%(hand)s" which does ' 1213 'not exist in the database', 1214 {'gid': family.gramps_id, 1215 'hand': event_handle}) 1216 make_unknown(event_handle, self.explanation.handle, 1217 self.class_event, self.commit_event, 1218 self.trans) 1219 self.invalid_events.add(key) 1220 if none_handle: 1221 family.set_event_ref_list(newlist) 1222 self.db.commit_family(family, self.trans) 1223 elif not isinstance(family.get_event_ref_list(), list): 1224 # event_list is None or other garbage 1225 logging.warning(' FAIL: the family "%s" has an event ref ' 1226 'list which is invalid', (family.gramps_id)) 1227 family.set_event_ref_list([]) 1228 self.db.commit_family(family, self.trans) 1229 self.invalid_events.add(key) 1230 1231 if len(self.invalid_birth_events) + len(self.invalid_death_events) + \ 1232 len(self.invalid_events) == 0: 1233 logging.info(' OK: no event problems found') 1234 1235 def check_backlinks(self): 1236 '''Looking for backlink reference problems''' 1237 1238 total = self.db.get_total() 1239 1240 self.progress.set_pass(_('Looking for backlink reference problems') + 1241 ' (1)', total) 1242 logging.info('Looking for backlink reference problems') 1243 1244 # dict of object handles indexed by forward link created here 1245 my_blinks = defaultdict(list) 1246 my_items = 0 # count of my backlinks for progress meter 1247 # dict of object handles indexed by forward link from db 1248 db_blinks = {} 1249 db_items = 0 # count of db backlinks for progress meter 1250 1251 # first we assemble our own backlinks table, and while we have the 1252 # handle, gather up a second table with the db's backlinks 1253 for obj_class in CLASS_TO_KEY_MAP.keys(): 1254 for handle in self.db.method("iter_%s_handles", obj_class)(): 1255 self.progress.step() 1256 blinks = list(self.db.find_backlink_handles(handle)) 1257 db_blinks[(obj_class, handle)] = blinks 1258 db_items += len(blinks) 1259 pri_obj = self.db.method('get_%s_from_handle', 1260 obj_class)(handle) 1261 handle_list = pri_obj.get_referenced_handles_recursively() 1262 my_items += len(handle_list) 1263 1264 for item in handle_list: 1265 my_blinks[item].append((obj_class, handle)) 1266 1267 # Now we go through our backlinks and the dbs table comparing them 1268 # check that each real reference has a backlink in the db table 1269 self.progress.set_pass(_('Looking for backlink reference problems') + 1270 ' (2)', my_items) 1271 for key, blinks in my_blinks.items(): 1272 for item in blinks: 1273 self.progress.step() 1274 if key not in db_blinks: 1275 # object has reference to something not in db; 1276 # should have been found in previous checks 1277 logging.warning(' Fail: reference to an object %(obj)s' 1278 ' not in the db by %(ref)s!', 1279 {'obj': key, 'ref': item}) 1280 continue 1281 if item not in db_blinks[key]: 1282 # Object has reference with no cooresponding backlink 1283 self.bad_backlinks += 1 1284 pri_obj = self.db.method('get_%s_from_handle', 1285 key[0])(key[1]) 1286 logging.warning(' FAIL: the "%(cls)s" [%(gid)s] ' 1287 'has a "%(cls2)s" reference' 1288 ' with no corresponding backlink.', 1289 {'gid': pri_obj.gramps_id, 1290 'cls': key[0], 'cls2': item[0]}) 1291 1292 # Now we go through the db table and make checks against ours 1293 # Check for db backlinks that don't have a reference object at all 1294 self.progress.set_pass(_('Looking for backlink reference problems') + 1295 ' (3)', db_items) 1296 for key, blinks in db_blinks.items(): 1297 for item in blinks: 1298 self.progress.step() 1299 if item not in db_blinks: 1300 # backlink to object entirely missing 1301 self.bad_backlinks += 1 1302 pri_obj = self.db.method('get_%s_from_handle', 1303 key[0])(key[1]) 1304 logging.warning(' FAIL: the "%(cls)s" [%(gid)s] ' 1305 'has a backlink to a missing' 1306 ' "%(cls2)s" object.', 1307 {'gid': pri_obj.gramps_id, 1308 'cls': key[0], 'cls2': item[0]}) 1309 continue 1310 # Check if the object has a reference to the backlinked one 1311 if key not in my_blinks or item not in my_blinks[key]: 1312 # backlink to object which doesn't have reference 1313 self.bad_backlinks += 1 1314 pri_obj = self.db.method('get_%s_from_handle', 1315 key[0])(key[1]) 1316 logging.warning(' FAIL: the "%(cls)s" [%(gid)s] ' 1317 'has a backlink to a "%(cls2)s"' 1318 ' with no corresponding reference.', 1319 {'gid': pri_obj.gramps_id, 1320 'cls': key[0], 'cls2': item[0]}) 1321 1322 def callback(self, *args): 1323 self.progress.step() 1324 1325 def check_person_references(self): 1326 '''Looking for person reference problems''' 1327 plist = self.db.get_person_handles() 1328 1329 self.progress.set_pass(_('Looking for person reference problems'), 1330 len(plist)) 1331 logging.info('Looking for person reference problems') 1332 1333 for key in plist: 1334 self.progress.step() 1335 none_handle = False 1336 newlist = [] 1337 person = self.db.get_person_from_handle(key) 1338 for pref in person.get_person_ref_list(): 1339 newlist.append(pref) 1340 if not pref.ref: 1341 none_handle = True 1342 pref.ref = create_id() 1343 try: 1344 self.db.get_person_from_handle(pref.ref) 1345 except HandleError: 1346 # The referenced person does not exist in the database 1347 make_unknown(pref.ref, self.explanation.handle, 1348 self.class_person, self.commit_person, 1349 self.trans) 1350 self.invalid_person_references.add(key) 1351 if none_handle: 1352 person.set_person_ref_list(newlist) 1353 self.db.commit_person(person, self.trans) 1354 1355 if len(self.invalid_person_references) == 0: 1356 logging.info(' OK: no event problems found') 1357 1358 def check_family_references(self): 1359 '''Looking for family reference problems''' 1360 plist = self.db.get_person_handles() 1361 1362 self.progress.set_pass(_('Looking for family reference problems'), 1363 len(plist)) 1364 logging.info('Looking for family reference problems') 1365 1366 for key in plist: 1367 self.progress.step() 1368 person = self.db.get_person_from_handle(key) 1369 for ordinance in person.get_lds_ord_list(): 1370 family_handle = ordinance.get_family_handle() 1371 if family_handle: 1372 try: 1373 self.db.get_family_from_handle(family_handle) 1374 except HandleError: 1375 # The referenced family does not exist in the database 1376 make_unknown(family_handle, self.explanation.handle, 1377 self.class_family, self.commit_family, 1378 self.trans, db=self.db) 1379 self.invalid_family_references.add(key) 1380 1381 if len(self.invalid_family_references) == 0: 1382 logging.info(' OK: no event problems found') 1383 1384 def check_repo_references(self): 1385 '''Looking for repository reference problems''' 1386 slist = self.db.get_source_handles() 1387 1388 self.progress.set_pass(_('Looking for repository reference problems'), 1389 len(slist)) 1390 logging.info('Looking for repository reference problems') 1391 1392 for key in slist: 1393 self.progress.step() 1394 none_handle = False 1395 newlist = [] 1396 source = self.db.get_source_from_handle(key) 1397 for reporef in source.get_reporef_list(): 1398 newlist.append(reporef) 1399 if not reporef.ref: 1400 none_handle = True 1401 reporef.ref = create_id() 1402 try: 1403 self.db.get_repository_from_handle(reporef.ref) 1404 except HandleError: 1405 # The referenced repository does not exist in the database 1406 make_unknown(reporef.ref, self.explanation.handle, 1407 self.class_repo, self.commit_repo, self.trans) 1408 self.invalid_repo_references.add(key) 1409 if none_handle: 1410 source.set_reporef_list(newlist) 1411 self.db.commit_source(source, self.trans) 1412 1413 if len(self.invalid_repo_references) == 0: 1414 logging.info(' OK: no repository reference problems found') 1415 1416 def check_place_references(self): 1417 '''Looking for place reference problems''' 1418 plist = self.db.get_person_handles() 1419 flist = self.db.get_family_handles() 1420 elist = self.db.get_event_handles() 1421 llist = self.db.get_place_handles() 1422 self.progress.set_pass( 1423 _('Looking for place reference problems'), 1424 len(elist) + len(plist) + len(flist) + len(llist)) 1425 logging.info('Looking for place reference problems') 1426 1427 for key in llist: 1428 self.progress.step() 1429 none_handle = False 1430 newlist = [] 1431 place = self.db.get_place_from_handle(key) 1432 for placeref in place.get_placeref_list(): 1433 newlist.append(placeref) 1434 if not placeref.ref: 1435 none_handle = True 1436 placeref.ref = create_id() 1437 try: 1438 self.db.get_place_from_handle(placeref.ref) 1439 except HandleError: 1440 # The referenced place does not exist in the database 1441 make_unknown(placeref.ref, self.explanation.handle, 1442 self.class_place, self.commit_place, 1443 self.trans) 1444 logging.warning(' FAIL: the place "%(gid)s" refers ' 1445 'to a parent place "%(hand)s" which ' 1446 'does not exist in the database', 1447 {'gid': place.gramps_id, 1448 'hand': placeref.ref}) 1449 self.invalid_place_references.add(key) 1450 if none_handle: 1451 place.set_placeref_list(newlist) 1452 self.db.commit_place(place, self.trans) 1453 1454 # check persons -> the LdsOrd references a place 1455 for key in plist: 1456 self.progress.step() 1457 person = self.db.get_person_from_handle(key) 1458 for ordinance in person.lds_ord_list: 1459 place_handle = ordinance.get_place_handle() 1460 if place_handle: 1461 try: 1462 place = self.db.get_place_from_handle(place_handle) 1463 except HandleError: 1464 # The referenced place does not exist in the database 1465 # This is tested by TestcaseGenerator person "Broken17" 1466 # This is tested by TestcaseGenerator person "Broken18" 1467 make_unknown(place_handle, self.explanation.handle, 1468 self.class_place, self.commit_place, 1469 self.trans) 1470 logging.warning(' FAIL: the person "%(gid)s" refers' 1471 ' to an LdsOrd place "%(hand)s" which ' 1472 'does not exist in the database', 1473 {'gid': person.gramps_id, 1474 'hand': place_handle}) 1475 self.invalid_place_references.add(key) 1476 # check families -> the LdsOrd references a place 1477 for key in flist: 1478 self.progress.step() 1479 family = self.db.get_family_from_handle(key) 1480 for ordinance in family.lds_ord_list: 1481 place_handle = ordinance.get_place_handle() 1482 if place_handle: 1483 try: 1484 place = self.db.get_place_from_handle(place_handle) 1485 except HandleError: 1486 # The referenced place does not exist in the database 1487 make_unknown(place_handle, self.explanation.handle, 1488 self.class_place, self.commit_place, 1489 self.trans) 1490 logging.warning(' FAIL: the family "%(gid)s" refers' 1491 ' to an LdsOrd place "%(hand)s" which ' 1492 'does not exist in the database', 1493 {'gid': family.gramps_id, 1494 'hand': place_handle}) 1495 self.invalid_place_references.add(key) 1496 # check events 1497 for key in elist: 1498 self.progress.step() 1499 event = self.db.get_event_from_handle(key) 1500 place_handle = event.get_place_handle() 1501 if place_handle: 1502 try: 1503 place = self.db.get_place_from_handle(place_handle) 1504 except HandleError: 1505 # The referenced place does not exist in the database 1506 make_unknown(place_handle, self.explanation.handle, 1507 self.class_place, self.commit_place, 1508 self.trans) 1509 logging.warning(' FAIL: the event "%(gid)s" refers ' 1510 'to an LdsOrd place "%(hand)s" which ' 1511 'does not exist in the database', 1512 {'gid': event.gramps_id, 1513 'hand': place_handle}) 1514 self.invalid_place_references.add(key) 1515 1516 if len(self.invalid_place_references) == 0: 1517 logging.info(' OK: no place reference problems found') 1518 1519 def check_citation_references(self): 1520 '''Looking for citation reference problems''' 1521 known_handles = self.db.get_citation_handles() 1522 1523 total = ( 1524 self.db.get_number_of_people() + 1525 self.db.get_number_of_families() + 1526 self.db.get_number_of_events() + 1527 self.db.get_number_of_places() + 1528 self.db.get_number_of_citations() + 1529 self.db.get_number_of_sources() + 1530 self.db.get_number_of_media() + 1531 self.db.get_number_of_repositories() 1532 ) 1533 1534 self.progress.set_pass(_('Looking for citation reference problems'), 1535 total) 1536 logging.info('Looking for citation reference problems') 1537 1538 for handle in self.db.get_person_handles(): 1539 self.progress.step() 1540 person = self.db.get_person_from_handle(handle) 1541 handle_list = person.get_referenced_handles_recursively() 1542 for item in handle_list: 1543 if item[0] == 'Citation': 1544 if not item[1]: 1545 new_handle = create_id() 1546 person.replace_citation_references(None, new_handle) 1547 self.db.commit_person(person, self.trans) 1548 self.invalid_citation_references.add(new_handle) 1549 elif item[1] not in known_handles: 1550 self.invalid_citation_references.add(item[1]) 1551 1552 for handle in self.db.get_family_handles(): 1553 self.progress.step() 1554 family = self.db.get_family_from_handle(handle) 1555 handle_list = family.get_referenced_handles_recursively() 1556 for item in handle_list: 1557 if item[0] == 'Citation': 1558 if not item[1]: 1559 new_handle = create_id() 1560 family.replace_citation_references(None, new_handle) 1561 self.db.commit_family(family, self.trans) 1562 self.invalid_citation_references.add(new_handle) 1563 elif item[1] not in known_handles: 1564 self.invalid_citation_references.add(item[1]) 1565 1566 for handle in self.db.get_place_handles(): 1567 self.progress.step() 1568 place = self.db.get_place_from_handle(handle) 1569 handle_list = place.get_referenced_handles_recursively() 1570 for item in handle_list: 1571 if item[0] == 'Citation': 1572 if not item[1]: 1573 new_handle = create_id() 1574 place.replace_citation_references(None, new_handle) 1575 self.db.commit_place(place, self.trans) 1576 self.invalid_citation_references.add(new_handle) 1577 elif item[1] not in known_handles: 1578 self.invalid_citation_references.add(item[1]) 1579 1580 for handle in self.db.get_citation_handles(): 1581 self.progress.step() 1582 citation = self.db.get_citation_from_handle(handle) 1583 handle_list = citation.get_referenced_handles_recursively() 1584 for item in handle_list: 1585 if item[0] == 'Citation': 1586 if not item[1]: 1587 new_handle = create_id() 1588 citation.replace_citation_references(None, new_handle) 1589 self.db.commit_citation(citation, self.trans) 1590 self.invalid_citation_references.add(new_handle) 1591 elif item[1] not in known_handles: 1592 self.invalid_citation_references.add(item[1]) 1593 1594 for handle in self.db.get_repository_handles(): 1595 self.progress.step() 1596 repository = self.db.get_repository_from_handle(handle) 1597 handle_list = repository.get_referenced_handles_recursively() 1598 for item in handle_list: 1599 if item[0] == 'Citation': 1600 if not item[1]: 1601 new_handle = create_id() 1602 repository.replace_citation_references(None, 1603 new_handle) 1604 self.db.commit_repository(repository, self.trans) 1605 self.invalid_citation_references.add(new_handle) 1606 elif item[1] not in known_handles: 1607 self.invalid_citation_references.add(item[1]) 1608 1609 for handle in self.db.get_media_handles(): 1610 self.progress.step() 1611 obj = self.db.get_media_from_handle(handle) 1612 handle_list = obj.get_referenced_handles_recursively() 1613 for item in handle_list: 1614 if item[0] == 'Citation': 1615 if not item[1]: 1616 new_handle = create_id() 1617 obj.replace_citation_references(None, new_handle) 1618 self.db.commit_media(obj, self.trans) 1619 self.invalid_citation_references.add(new_handle) 1620 elif item[1] not in known_handles: 1621 self.invalid_citation_references.add(item[1]) 1622 1623 for handle in self.db.get_event_handles(): 1624 self.progress.step() 1625 event = self.db.get_event_from_handle(handle) 1626 handle_list = event.get_referenced_handles_recursively() 1627 for item in handle_list: 1628 if item[0] == 'Citation': 1629 if not item[1]: 1630 new_handle = create_id() 1631 event.replace_citation_references(None, new_handle) 1632 self.db.commit_event(event, self.trans) 1633 self.invalid_citation_references.add(new_handle) 1634 elif item[1] not in known_handles: 1635 self.invalid_citation_references.add(item[1]) 1636 1637 for bad_handle in self.invalid_citation_references: 1638 created = make_unknown(bad_handle, self.explanation.handle, 1639 self.class_citation, self.commit_citation, 1640 self.trans, 1641 source_class_func=self.class_source, 1642 source_commit_func=self.commit_source, 1643 source_class_arg=create_id()) 1644 self.invalid_source_references.add(created[0].handle) 1645 1646 if len(self.invalid_citation_references) == 0: 1647 logging.info(' OK: no citation reference problems found') 1648 1649 def check_source_references(self): 1650 '''Looking for source reference problems''' 1651 clist = self.db.get_citation_handles() 1652 self.progress.set_pass(_('Looking for source reference problems'), 1653 len(clist)) 1654 logging.info('Looking for source reference problems') 1655 1656 for key in clist: 1657 self.progress.step() 1658 citation = self.db.get_citation_from_handle(key) 1659 source_handle = citation.get_reference_handle() 1660 if not source_handle: 1661 source_handle = create_id() 1662 citation.set_reference_handle(source_handle) 1663 self.db.commit_citation(citation, self.trans) 1664 if source_handle: 1665 try: 1666 self.db.get_source_from_handle(source_handle) 1667 except HandleError: 1668 # The referenced source does not exist in the database 1669 make_unknown(source_handle, self.explanation.handle, 1670 self.class_source, self.commit_source, 1671 self.trans) 1672 logging.warning(' FAIL: the citation "%(gid)s" refers ' 1673 'to source "%(hand)s" which does not exist' 1674 ' in the database', 1675 {'gid': citation.gramps_id, 1676 'hand': source_handle}) 1677 self.invalid_source_references.add(key) 1678 if len(self.invalid_source_references) == 0: 1679 logging.info(' OK: no source reference problems found') 1680 1681 def check_media_references(self): 1682 '''Looking for media object reference problems''' 1683 known_handles = self.db.get_media_handles(False) 1684 1685 total = ( 1686 self.db.get_number_of_people() + 1687 self.db.get_number_of_families() + 1688 self.db.get_number_of_events() + 1689 self.db.get_number_of_places() + 1690 self.db.get_number_of_citations() + 1691 self.db.get_number_of_sources() 1692 ) 1693 1694 self.progress.set_pass(_('Looking for media object reference ' 1695 'problems'), total) 1696 logging.info('Looking for media object reference problems') 1697 1698 for handle in self.db.get_person_handles(): 1699 self.progress.step() 1700 person = self.db.get_person_from_handle(handle) 1701 handle_list = person.get_referenced_handles_recursively() 1702 for item in handle_list: 1703 if item[0] == 'Media': 1704 if not item[1]: 1705 new_handle = create_id() 1706 person.replace_media_references(None, new_handle) 1707 self.db.commit_person(person, self.trans) 1708 self.invalid_media_references.add(new_handle) 1709 elif item[1] not in known_handles: 1710 self.invalid_media_references.add(item[1]) 1711 1712 for handle in self.db.get_family_handles(): 1713 self.progress.step() 1714 family = self.db.get_family_from_handle(handle) 1715 handle_list = family.get_referenced_handles_recursively() 1716 for item in handle_list: 1717 if item[0] == 'Media': 1718 if not item[1]: 1719 new_handle = create_id() 1720 family.replace_media_references(None, new_handle) 1721 self.db.commit_family(family, self.trans) 1722 self.invalid_media_references.add(new_handle) 1723 elif item[1] not in known_handles: 1724 self.invalid_media_references.add(item[1]) 1725 1726 for handle in self.db.get_place_handles(): 1727 self.progress.step() 1728 place = self.db.get_place_from_handle(handle) 1729 handle_list = place.get_referenced_handles_recursively() 1730 for item in handle_list: 1731 if item[0] == 'Media': 1732 if not item[1]: 1733 new_handle = create_id() 1734 place.replace_media_references(None, new_handle) 1735 self.db.commit_place(place, self.trans) 1736 self.invalid_media_references.add(new_handle) 1737 elif item[1] not in known_handles: 1738 self.invalid_media_references.add(item[1]) 1739 1740 for handle in self.db.get_event_handles(): 1741 self.progress.step() 1742 event = self.db.get_event_from_handle(handle) 1743 handle_list = event.get_referenced_handles_recursively() 1744 for item in handle_list: 1745 if item[0] == 'Media': 1746 if not item[1]: 1747 new_handle = create_id() 1748 event.replace_media_references(None, new_handle) 1749 self.db.commit_event(event, self.trans) 1750 self.invalid_media_references.add(new_handle) 1751 elif item[1] not in known_handles: 1752 self.invalid_media_references.add(item[1]) 1753 1754 for handle in self.db.get_citation_handles(): 1755 self.progress.step() 1756 citation = self.db.get_citation_from_handle(handle) 1757 handle_list = citation.get_referenced_handles_recursively() 1758 for item in handle_list: 1759 if item[0] == 'Media': 1760 if not item[1]: 1761 new_handle = create_id() 1762 citation.replace_media_references(None, new_handle) 1763 self.db.commit_citation(citation, self.trans) 1764 self.invalid_media_references.add(new_handle) 1765 elif item[1] not in known_handles: 1766 self.invalid_media_references.add(item[1]) 1767 1768 for handle in self.db.get_source_handles(): 1769 self.progress.step() 1770 source = self.db.get_source_from_handle(handle) 1771 handle_list = source.get_referenced_handles_recursively() 1772 for item in handle_list: 1773 if item[0] == 'Media': 1774 if not item[1]: 1775 new_handle = create_id() 1776 source.replace_media_references(None, new_handle) 1777 self.db.commit_source(source, self.trans) 1778 self.invalid_media_references.add(new_handle) 1779 elif item[1] not in known_handles: 1780 self.invalid_media_references.add(item[1]) 1781 1782 for bad_handle in self.invalid_media_references: 1783 make_unknown(bad_handle, self.explanation.handle, self.class_media, 1784 self.commit_media, self.trans) 1785 1786 if len(self.invalid_media_references) == 0: 1787 logging.info(' OK: no media reference problems found') 1788 1789 def check_note_references(self): 1790 '''Looking for note reference problems''' 1791 # Here I assume check note_references runs after all the next checks. 1792 missing_references = (len(self.invalid_person_references) + 1793 len(self.invalid_family_references) + 1794 len(self.invalid_birth_events) + 1795 len(self.invalid_death_events) + 1796 len(self.invalid_events) + 1797 len(self.invalid_place_references) + 1798 len(self.invalid_citation_references) + 1799 len(self.invalid_source_references) + 1800 len(self.invalid_repo_references) + 1801 len(self.invalid_media_references)) 1802 if missing_references: 1803 self.db.add_note(self.explanation, self.trans, set_gid=True) 1804 1805 known_handles = self.db.get_note_handles() 1806 1807 total = (self.db.get_number_of_people() + 1808 self.db.get_number_of_families() + 1809 self.db.get_number_of_events() + 1810 self.db.get_number_of_places() + 1811 self.db.get_number_of_media() + 1812 self.db.get_number_of_citations() + 1813 self.db.get_number_of_sources() + 1814 self.db.get_number_of_repositories()) 1815 1816 self.progress.set_pass(_('Looking for note reference problems'), 1817 total) 1818 logging.info('Looking for note reference problems') 1819 1820 for handle in self.db.get_person_handles(): 1821 self.progress.step() 1822 person = self.db.get_person_from_handle(handle) 1823 handle_list = person.get_referenced_handles_recursively() 1824 for item in handle_list: 1825 if item[0] == 'Note': 1826 if not item[1]: 1827 new_handle = create_id() 1828 person.replace_note_references(None, new_handle) 1829 self.db.commit_person(person, self.trans) 1830 self.invalid_note_references.add(new_handle) 1831 elif item[1] not in known_handles: 1832 self.invalid_note_references.add(item[1]) 1833 1834 for handle in self.db.get_family_handles(): 1835 self.progress.step() 1836 family = self.db.get_family_from_handle(handle) 1837 handle_list = family.get_referenced_handles_recursively() 1838 for item in handle_list: 1839 if item[0] == 'Note': 1840 if not item[1]: 1841 new_handle = create_id() 1842 family.replace_note_references(None, new_handle) 1843 self.db.commit_family(family, self.trans) 1844 self.invalid_note_references.add(new_handle) 1845 elif item[1] not in known_handles: 1846 self.invalid_note_references.add(item[1]) 1847 1848 for handle in self.db.get_place_handles(): 1849 self.progress.step() 1850 place = self.db.get_place_from_handle(handle) 1851 handle_list = place.get_referenced_handles_recursively() 1852 for item in handle_list: 1853 if item[0] == 'Note': 1854 if not item[1]: 1855 new_handle = create_id() 1856 place.replace_note_references(None, new_handle) 1857 self.db.commit_place(place, self.trans) 1858 self.invalid_note_references.add(new_handle) 1859 elif item[1] not in known_handles: 1860 self.invalid_note_references.add(item[1]) 1861 1862 for handle in self.db.get_citation_handles(): 1863 self.progress.step() 1864 citation = self.db.get_citation_from_handle(handle) 1865 handle_list = citation.get_referenced_handles_recursively() 1866 for item in handle_list: 1867 if item[0] == 'Note': 1868 if not item[1]: 1869 new_handle = create_id() 1870 citation.replace_note_references(None, new_handle) 1871 self.db.commit_citation(citation, self.trans) 1872 self.invalid_note_references.add(new_handle) 1873 elif item[1] not in known_handles: 1874 self.invalid_note_references.add(item[1]) 1875 1876 for handle in self.db.get_source_handles(): 1877 self.progress.step() 1878 source = self.db.get_source_from_handle(handle) 1879 handle_list = source.get_referenced_handles_recursively() 1880 for item in handle_list: 1881 if item[0] == 'Note': 1882 if not item[1]: 1883 new_handle = create_id() 1884 source.replace_note_references(None, new_handle) 1885 self.db.commit_source(source, self.trans) 1886 self.invalid_note_references.add(new_handle) 1887 elif item[1] not in known_handles: 1888 self.invalid_note_references.add(item[1]) 1889 1890 for handle in self.db.get_media_handles(): 1891 self.progress.step() 1892 obj = self.db.get_media_from_handle(handle) 1893 handle_list = obj.get_referenced_handles_recursively() 1894 for item in handle_list: 1895 if item[0] == 'Note': 1896 if not item[1]: 1897 new_handle = create_id() 1898 obj.replace_note_references(None, new_handle) 1899 self.db.commit_media(obj, self.trans) 1900 self.invalid_note_references.add(new_handle) 1901 elif item[1] not in known_handles: 1902 self.invalid_note_references.add(item[1]) 1903 1904 for handle in self.db.get_event_handles(): 1905 self.progress.step() 1906 event = self.db.get_event_from_handle(handle) 1907 handle_list = event.get_referenced_handles_recursively() 1908 for item in handle_list: 1909 if item[0] == 'Note': 1910 if not item[1]: 1911 new_handle = create_id() 1912 event.replace_note_references(None, new_handle) 1913 self.db.commit_event(event, self.trans) 1914 self.invalid_note_references.add(new_handle) 1915 elif item[1] not in known_handles: 1916 self.invalid_note_references.add(item[1]) 1917 1918 for handle in self.db.get_repository_handles(): 1919 self.progress.step() 1920 repo = self.db.get_repository_from_handle(handle) 1921 handle_list = repo.get_referenced_handles_recursively() 1922 for item in handle_list: 1923 if item[0] == 'Note': 1924 if not item[1]: 1925 new_handle = create_id() 1926 repo.replace_note_references(None, new_handle) 1927 self.db.commit_repository(repo, self.trans) 1928 self.invalid_note_references.add(new_handle) 1929 elif item[1] not in known_handles: 1930 self.invalid_note_references.add(item[1]) 1931 1932 for bad_handle in self.invalid_note_references: 1933 make_unknown(bad_handle, self.explanation.handle, 1934 self.class_note, self.commit_note, self.trans) 1935 1936 if len(self.invalid_note_references) == 0: 1937 logging.info(' OK: no note reference problems found') 1938 else: 1939 if not missing_references: 1940 self.db.add_note(self.explanation, self.trans, set_gid=True) 1941 1942 def check_checksum(self): 1943 ''' fix media checksums ''' 1944 self.progress.set_pass(_('Updating checksums on media'), 1945 len(self.db.get_media_handles())) 1946 for objectid in self.db.get_media_handles(): 1947 self.progress.step() 1948 obj = self.db.get_media_from_handle(objectid) 1949 full_path = media_path_full(self.db, obj.get_path()) 1950 new_checksum = create_checksum(full_path) 1951 if new_checksum != obj.checksum: 1952 logging.info('checksum: updating ' + obj.gramps_id) 1953 obj.checksum = new_checksum 1954 self.db.commit_media(obj, self.trans) 1955 1956 def check_tag_references(self): 1957 '''Looking for tag reference problems''' 1958 known_handles = self.db.get_tag_handles() 1959 1960 total = (self.db.get_number_of_people() + 1961 self.db.get_number_of_families() + 1962 self.db.get_number_of_media() + 1963 self.db.get_number_of_notes() + 1964 self.db.get_number_of_events() + 1965 self.db.get_number_of_citations() + 1966 self.db.get_number_of_sources() + 1967 self.db.get_number_of_places() + 1968 self.db.get_number_of_repositories()) 1969 1970 self.progress.set_pass(_('Looking for tag reference problems'), 1971 total) 1972 logging.info('Looking for tag reference problems') 1973 1974 for handle in self.db.get_person_handles(): 1975 self.progress.step() 1976 person = self.db.get_person_from_handle(handle) 1977 handle_list = person.get_referenced_handles_recursively() 1978 for item in handle_list: 1979 if item[0] == 'Tag': 1980 if not item[1]: 1981 new_handle = create_id() 1982 person.replace_tag_references(None, new_handle) 1983 self.db.commit_person(person, self.trans) 1984 self.invalid_tag_references.add(new_handle) 1985 elif item[1] not in known_handles: 1986 self.invalid_tag_references.add(item[1]) 1987 1988 for handle in self.db.get_family_handles(): 1989 self.progress.step() 1990 family = self.db.get_family_from_handle(handle) 1991 handle_list = family.get_referenced_handles_recursively() 1992 for item in handle_list: 1993 if item[0] == 'Tag': 1994 if not item[1]: 1995 new_handle = create_id() 1996 family.replace_tag_references(None, new_handle) 1997 self.db.commit_family(family, self.trans) 1998 self.invalid_tag_references.add(new_handle) 1999 elif item[1] not in known_handles: 2000 self.invalid_tag_references.add(item[1]) 2001 2002 for handle in self.db.get_media_handles(): 2003 self.progress.step() 2004 obj = self.db.get_media_from_handle(handle) 2005 handle_list = obj.get_referenced_handles_recursively() 2006 for item in handle_list: 2007 if item[0] == 'Tag': 2008 if not item[1]: 2009 new_handle = create_id() 2010 obj.replace_tag_references(None, new_handle) 2011 self.db.commit_media(obj, self.trans) 2012 self.invalid_tag_references.add(new_handle) 2013 elif item[1] not in known_handles: 2014 self.invalid_tag_references.add(item[1]) 2015 2016 for handle in self.db.get_note_handles(): 2017 self.progress.step() 2018 note = self.db.get_note_from_handle(handle) 2019 handle_list = note.get_referenced_handles_recursively() 2020 for item in handle_list: 2021 if item[0] == 'Tag': 2022 if not item[1]: 2023 new_handle = create_id() 2024 note.replace_tag_references(None, new_handle) 2025 self.db.commit_note(note, self.trans) 2026 self.invalid_tag_references.add(new_handle) 2027 elif item[1] not in known_handles: 2028 self.invalid_tag_references.add(item[1]) 2029 2030 for handle in self.db.get_event_handles(): 2031 self.progress.step() 2032 event = self.db.get_event_from_handle(handle) 2033 handle_list = event.get_referenced_handles_recursively() 2034 for item in handle_list: 2035 if item[0] == 'Tag': 2036 if not item[1]: 2037 new_handle = create_id() 2038 event.replace_tag_references(None, new_handle) 2039 self.db.commit_event(event, self.trans) 2040 self.invalid_tag_references.add(new_handle) 2041 elif item[1] not in known_handles: 2042 self.invalid_tag_references.add(item[1]) 2043 2044 for handle in self.db.get_citation_handles(): 2045 self.progress.step() 2046 citation = self.db.get_citation_from_handle(handle) 2047 handle_list = citation.get_referenced_handles_recursively() 2048 for item in handle_list: 2049 if item[0] == 'Tag': 2050 if not item[1]: 2051 new_handle = create_id() 2052 citation.replace_tag_references(None, new_handle) 2053 self.db.commit_citation(citation, self.trans) 2054 self.invalid_tag_references.add(new_handle) 2055 elif item[1] not in known_handles: 2056 self.invalid_tag_references.add(item[1]) 2057 2058 for handle in self.db.get_source_handles(): 2059 self.progress.step() 2060 source = self.db.get_source_from_handle(handle) 2061 handle_list = source.get_referenced_handles_recursively() 2062 for item in handle_list: 2063 if item[0] == 'Tag': 2064 if not item[1]: 2065 new_handle = create_id() 2066 source.replace_tag_references(None, new_handle) 2067 self.db.commit_source(source, self.trans) 2068 self.invalid_tag_references.add(new_handle) 2069 elif item[1] not in known_handles: 2070 self.invalid_tag_references.add(item[1]) 2071 2072 for handle in self.db.get_place_handles(): 2073 self.progress.step() 2074 place = self.db.get_place_from_handle(handle) 2075 handle_list = place.get_referenced_handles_recursively() 2076 for item in handle_list: 2077 if item[0] == 'Tag': 2078 if not item[1]: 2079 new_handle = create_id() 2080 place.replace_tag_references(None, new_handle) 2081 self.db.commit_place(place, self.trans) 2082 self.invalid_tag_references.add(new_handle) 2083 elif item[1] not in known_handles: 2084 self.invalid_tag_references.add(item[1]) 2085 2086 for handle in self.db.get_repository_handles(): 2087 self.progress.step() 2088 repository = self.db.get_repository_from_handle(handle) 2089 handle_list = repository.get_referenced_handles_recursively() 2090 for item in handle_list: 2091 if item[0] == 'Tag': 2092 if not item[1]: 2093 new_handle = create_id() 2094 repository.replace_tag_references(None, new_handle) 2095 self.db.commit_repository(repository, self.trans) 2096 self.invalid_tag_references.add(new_handle) 2097 elif item[1] not in known_handles: 2098 self.invalid_tag_references.add(item[1]) 2099 2100 for bad_handle in self.invalid_tag_references: 2101 make_unknown(bad_handle, None, self.class_tag, 2102 self.commit_tag, self.trans) 2103 2104 if len(self.invalid_tag_references) == 0: 2105 logging.info(' OK: no tag reference problems found') 2106 2107 def check_media_sourceref(self): 2108 """ 2109 This repairs a problem with database upgrade from database schema 2110 version 15 to 16. Mediarefs on source primary objects can contain 2111 sourcerefs, and these were not converted to citations. 2112 """ 2113 total = (self.db.get_number_of_sources()) 2114 2115 self.progress.set_pass(_('Looking for media source reference ' 2116 'problems'), total) 2117 logging.info('Looking for media source reference problems') 2118 2119 for handle in self.db.get_source_handles(): 2120 self.progress.step() 2121 source = self.db.get_source_from_handle(handle) 2122 new_media_ref_list = [] 2123 citation_changed = False 2124 for media_ref in source.get_media_list(): 2125 citation_list = media_ref.get_citation_list() 2126 new_citation_list = [] 2127 for citation_handle in citation_list: 2128 # Either citation_handle is a handle, in which case it has 2129 # been converted, or it is a 6-tuple, in which case it now 2130 # needs to be converted. 2131 if len(citation_handle) == 6: 2132 if len(citation_handle) == 6: 2133 sourceref = citation_handle 2134 else: 2135 sourceref = eval(citation_handle) 2136 new_citation = Citation() 2137 new_citation.set_date_object(sourceref[0]) 2138 new_citation.set_privacy(sourceref[1]) 2139 new_citation.set_note_list(sourceref[2]) 2140 new_citation.set_confidence_level(sourceref[3]) 2141 new_citation.set_reference_handle(sourceref[4]) 2142 new_citation.set_page(sourceref[5]) 2143 citation_handle = create_id() 2144 new_citation.set_handle(citation_handle) 2145 self.replaced_sourceref.append(handle) 2146 citation_changed = True 2147 logging.warning(' FAIL: the source "%s" has a media' 2148 ' reference with a source citation ' 2149 'which is invalid', (source.gramps_id)) 2150 self.db.add_citation(new_citation, self.trans) 2151 2152 new_citation_list.append(citation_handle) 2153 2154 media_ref.set_citation_list(new_citation_list) 2155 new_media_ref_list.append(media_ref) 2156 2157 if citation_changed: 2158 source.set_media_list(new_media_ref_list) 2159 self.db.commit_source(source, self.trans) 2160 2161 if len(self.replaced_sourceref) > 0: 2162 logging.info(' OK: no broken source citations on mediarefs ' 2163 'found') 2164 2165 def fix_duplicated_grampsid(self): 2166 """ 2167 This searches for duplicated Gramps ID within each of the major 2168 classes. It does not check across classes. If duplicates are 2169 found, a new Gramps ID is assigned. 2170 """ 2171 total = ( 2172 self.db.get_number_of_citations() + 2173 self.db.get_number_of_events() + 2174 self.db.get_number_of_families() + 2175 self.db.get_number_of_media() + 2176 self.db.get_number_of_notes() + 2177 self.db.get_number_of_people() + 2178 self.db.get_number_of_places() + 2179 self.db.get_number_of_repositories() + 2180 self.db.get_number_of_sources() 2181 ) 2182 2183 self.progress.set_pass(_('Looking for Duplicated Gramps ID ' 2184 'problems'), total) 2185 logging.info('Looking for Duplicated Gramps ID problems') 2186 gid_list = [] 2187 for citation in self.db.iter_citations(): 2188 self.progress.step() 2189 ogid = gid = citation.get_gramps_id() 2190 if gid in gid_list: 2191 gid = self.db.find_next_citation_gramps_id() 2192 citation.set_gramps_id(gid) 2193 self.db.commit_citation(citation, self.trans) 2194 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2195 'Original: "%s" changed to: "%s"', ogid, gid) 2196 self.duplicated_gramps_ids += 1 2197 gid_list.append(gid) 2198 gid_list = [] 2199 for event in self.db.iter_events(): 2200 self.progress.step() 2201 ogid = gid = event.get_gramps_id() 2202 if gid in gid_list: 2203 gid = self.db.find_next_event_gramps_id() 2204 event.set_gramps_id(gid) 2205 self.db.commit_event(event, self.trans) 2206 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2207 'Original: "%s" changed to: "%s"', ogid, gid) 2208 self.duplicated_gramps_ids += 1 2209 gid_list.append(gid) 2210 gid_list = [] 2211 for family in self.db.iter_families(): 2212 self.progress.step() 2213 ogid = gid = family.get_gramps_id() 2214 if gid in gid_list: 2215 gid = self.db.find_next_family_gramps_id() 2216 family.set_gramps_id(gid) 2217 self.db.commit_family(family, self.trans) 2218 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2219 'Original: "%s" changed to: "%s"', ogid, gid) 2220 self.duplicated_gramps_ids += 1 2221 gid_list.append(gid) 2222 gid_list = [] 2223 for media in self.db.iter_media(): 2224 self.progress.step() 2225 ogid = gid = media.get_gramps_id() 2226 if gid in gid_list: 2227 gid = self.db.find_next_media_gramps_id() 2228 media.set_gramps_id(gid) 2229 self.db.commit_media(media, self.trans) 2230 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2231 'Original: "%s" changed to: "%s"', ogid, gid) 2232 self.duplicated_gramps_ids += 1 2233 gid_list.append(gid) 2234 gid_list = [] 2235 for note in self.db.iter_notes(): 2236 ogid = gid = note.get_gramps_id() 2237 if gid in gid_list: 2238 gid = self.db.find_next_note_gramps_id() 2239 note.set_gramps_id(gid) 2240 self.db.commit_note(note, self.trans) 2241 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2242 'Original: "%s" changed to: "%s"', ogid, gid) 2243 self.duplicated_gramps_ids += 1 2244 gid_list.append(gid) 2245 gid_list = [] 2246 for person in self.db.iter_people(): 2247 self.progress.step() 2248 ogid = gid = person.get_gramps_id() 2249 if gid in gid_list: 2250 gid = self.db.find_next_person_gramps_id() 2251 person.set_gramps_id(gid) 2252 self.db.commit_person(person, self.trans) 2253 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2254 'Original: "%s" changed to: "%s"', ogid, gid) 2255 self.duplicated_gramps_ids += 1 2256 gid_list.append(gid) 2257 gid_list = [] 2258 for place in self.db.iter_places(): 2259 self.progress.step() 2260 ogid = gid = place.get_gramps_id() 2261 if gid in gid_list: 2262 gid = self.db.find_next_place_gramps_id() 2263 place.set_gramps_id(gid) 2264 self.db.commit_place(place, self.trans) 2265 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2266 'Original: "%s" changed to: "%s"', ogid, gid) 2267 self.duplicated_gramps_ids += 1 2268 gid_list.append(gid) 2269 gid_list = [] 2270 for repository in self.db.iter_repositories(): 2271 self.progress.step() 2272 ogid = gid = repository.get_gramps_id() 2273 if gid in gid_list: 2274 gid = self.db.find_next_repository_gramps_id() 2275 repository.set_gramps_id(gid) 2276 self.db.commit_repository(repository, self.trans) 2277 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2278 'Original: "%s" changed to: "%s"', ogid, gid) 2279 self.duplicated_gramps_ids += 1 2280 gid_list.append(gid) 2281 gid_list = [] 2282 for source in self.db.iter_sources(): 2283 self.progress.step() 2284 ogid = gid = source.get_gramps_id() 2285 if gid in gid_list: 2286 gid = self.db.find_next_source_gramps_id() 2287 source.set_gramps_id(gid) 2288 self.db.commit_source(source, self.trans) 2289 logging.warning(' FAIL: Duplicated Gramps ID found, ' 2290 'Original: "%s" changed to: "%s"', ogid, gid) 2291 self.duplicated_gramps_ids += 1 2292 gid_list.append(gid) 2293 2294 def class_person(self, handle): 2295 person = Person() 2296 person.set_handle(handle) 2297 return person 2298 2299 def commit_person(self, person, trans, dummy): 2300 self.db.add_person(person, trans, set_gid=True) 2301 2302 def class_family(self, handle): 2303 family = Family() 2304 family.set_handle(handle) 2305 return family 2306 2307 def commit_family(self, family, trans, dummy): 2308 self.db.add_family(family, trans, set_gid=True) 2309 2310 def class_event(self, handle): 2311 event = Event() 2312 event.set_handle(handle) 2313 return event 2314 2315 def commit_event(self, event, trans, dummy): 2316 self.db.add_event(event, trans, set_gid=True) 2317 2318 def class_place(self, handle): 2319 place = Place() 2320 place.set_handle(handle) 2321 return place 2322 2323 def commit_place(self, place, trans, dummy): 2324 self.db.add_place(place, trans, set_gid=True) 2325 2326 def class_source(self, handle): 2327 source = Source() 2328 source.set_handle(handle) 2329 return source 2330 2331 def commit_source(self, source, trans, dummy): 2332 self.db.add_source(source, trans, set_gid=True) 2333 2334 def class_citation(self, handle): 2335 citation = Citation() 2336 citation.set_handle(handle) 2337 return citation 2338 2339 def commit_citation(self, citation, trans, dummy): 2340 self.db.add_citation(citation, trans, set_gid=True) 2341 2342 def class_repo(self, handle): 2343 repo = Repository() 2344 repo.set_handle(handle) 2345 return repo 2346 2347 def commit_repo(self, repo, trans, dummy): 2348 self.db.add_repository(repo, trans, set_gid=True) 2349 2350 def class_media(self, handle): 2351 obj = Media() 2352 obj.set_handle(handle) 2353 return obj 2354 2355 def commit_media(self, obj, trans, dummy): 2356 self.db.add_media(obj, trans, set_gid=True) 2357 2358 def class_note(self, handle): 2359 note = Note() 2360 note.set_handle(handle) 2361 return note 2362 2363 def commit_note(self, note, trans, dummy): 2364 self.db.add_note(note, trans, set_gid=True) 2365 2366 def class_tag(self, handle): 2367 tag = Tag() 2368 tag.set_handle(handle) 2369 return tag 2370 2371 def commit_tag(self, tag, trans, dummy): 2372 self.db.add_tag(tag, trans) 2373 2374 def build_report(self, uistate=None): 2375 ''' build the report from various counters''' 2376 self.progress.close() 2377 bad_photos = len(self.bad_photo) 2378 replaced_photos = len(self.replaced_photo) 2379 removed_photos = len(self.removed_photo) 2380 photos = bad_photos + replaced_photos + removed_photos 2381 efam = len(self.empty_family) 2382 blink = len(self.broken_links) 2383 plink = len(self.broken_parent_links) 2384 slink = len(self.duplicate_links) 2385 rel = len(self.fam_rel) 2386 event_invalid = len(self.invalid_events) 2387 birth_invalid = len(self.invalid_birth_events) 2388 death_invalid = len(self.invalid_death_events) 2389 person = birth_invalid + death_invalid 2390 person_references = len(self.invalid_person_references) 2391 family_references = len(self.invalid_family_references) 2392 invalid_dates = len(self.invalid_dates) 2393 place_references = len(self.invalid_place_references) 2394 citation_references = len(self.invalid_citation_references) 2395 source_references = len(self.invalid_source_references) 2396 repo_references = len(self.invalid_repo_references) 2397 media_references = len(self.invalid_media_references) 2398 note_references = len(self.invalid_note_references) 2399 tag_references = len(self.invalid_tag_references) 2400 name_format = len(self.removed_name_format) 2401 replaced_sourcerefs = len(self.replaced_sourceref) 2402 dup_gramps_ids = self.duplicated_gramps_ids 2403 empty_objs = sum(len(obj) for obj in self.empty_objects.values()) 2404 2405 errors = (photos + efam + blink + plink + slink + rel + 2406 event_invalid + person + self.place_errors + 2407 person_references + family_references + place_references + 2408 citation_references + repo_references + media_references + 2409 note_references + tag_references + name_format + empty_objs + 2410 invalid_dates + source_references + dup_gramps_ids + 2411 self.bad_backlinks) 2412 2413 if errors == 0: 2414 if uistate: 2415 OkDialog(_("No errors were found"), 2416 _('The database has passed internal checks'), 2417 parent=uistate.window) 2418 else: 2419 print(_("No errors were found: the database has passed " 2420 "internal checks.")) 2421 return 0 2422 2423 if blink > 0: 2424 self.text.write( 2425 # translators: leave all/any {...} untranslated 2426 ngettext("{quantity} broken child/family link was fixed\n", 2427 "{quantity} broken child/family links were fixed\n", 2428 blink).format(quantity=blink) 2429 ) 2430 for (person_handle, family_handle) in self.broken_links: 2431 try: 2432 person = self.db.get_person_from_handle(person_handle) 2433 except HandleError: 2434 cname = _("Non existing child") 2435 else: 2436 cname = person.get_primary_name().get_name() 2437 try: 2438 family = self.db.get_family_from_handle(family_handle) 2439 except HandleError: 2440 pname = _("Unknown") 2441 else: 2442 pname = family_name(family, self.db) 2443 self.text.write('\t') 2444 self.text.write( 2445 _("%(person)s was removed from the family of %(family)s\n") 2446 % {'person': cname, 'family': pname} 2447 ) 2448 2449 if plink > 0: 2450 self.text.write( 2451 # translators: leave all/any {...} untranslated 2452 ngettext("{quantity} broken spouse/family link was fixed\n", 2453 "{quantity} broken spouse/family links were fixed\n", 2454 plink).format(quantity=plink) 2455 ) 2456 for (person_handle, family_handle) in self.broken_parent_links: 2457 try: 2458 person = self.db.get_person_from_handle(person_handle) 2459 except HandleError: 2460 cname = _("Non existing person") 2461 else: 2462 cname = person.get_primary_name().get_name() 2463 try: 2464 family = self.db.get_family_from_handle(family_handle) 2465 except HandleError: 2466 pname = _("Unknown") 2467 else: 2468 pname = family_name(family, self.db) 2469 self.text.write('\t') 2470 self.text.write( 2471 _("%(person)s was restored to the family of %(family)s\n") 2472 % {'person': cname, 'family': pname} 2473 ) 2474 2475 if slink > 0: 2476 self.text.write( 2477 # translators: leave all/any {...} untranslated 2478 ngettext("{quantity} duplicate " 2479 "spouse/family link was found\n", 2480 "{quantity} duplicate " 2481 "spouse/family links were found\n", 2482 slink).format(quantity=slink) 2483 ) 2484 for (person_handle, family_handle) in self.broken_parent_links: 2485 try: 2486 person = self.db.get_person_from_handle(person_handle) 2487 except HandleError: 2488 cname = _("Non existing person") 2489 else: 2490 cname = person.get_primary_name().get_name() 2491 try: 2492 family = self.db.get_family_from_handle(family_handle) 2493 except HandleError: 2494 pname = _("None") 2495 else: 2496 pname = family_name(family, self.db) 2497 self.text.write('\t') 2498 self.text.write( 2499 _("%(person)s was restored to the family of %(family)s\n") 2500 % {'person': cname, 'family': pname} 2501 ) 2502 2503 if efam: 2504 self.text.write( 2505 # translators: leave all/any {...} untranslated 2506 ngettext("{quantity} family " 2507 "with no parents or children found, removed.\n", 2508 "{quantity} families " 2509 "with no parents or children found, removed.\n", 2510 efam).format(quantity=efam) 2511 ) 2512 if efam == 1: 2513 self.text.write("\t%s\n" % self.empty_family[0]) 2514 2515 if rel: 2516 self.text.write( 2517 # translators: leave all/any {...} untranslated 2518 ngettext("{quantity} corrupted family relationship fixed\n", 2519 "{quantity} corrupted family relationships fixed\n", 2520 rel).format(quantity=rel) 2521 ) 2522 2523 if self.place_errors: 2524 self.text.write( 2525 # translators: leave all/any {...} untranslated 2526 ngettext("{quantity} place alternate name fixed\n", 2527 "{quantity} place alternate names fixed\n", 2528 self.place_errors).format(quantity=self.place_errors) 2529 ) 2530 2531 if person_references: 2532 self.text.write( 2533 # translators: leave all/any {...} untranslated 2534 ngettext( 2535 "{quantity} person was referenced but not found\n", 2536 "{quantity} persons were referenced, but not found\n", 2537 person_references).format(quantity=person_references) 2538 ) 2539 2540 if family_references: 2541 self.text.write( 2542 # translators: leave all/any {...} untranslated 2543 ngettext("{quantity} family was " 2544 "referenced but not found\n", 2545 "{quantity} families were " 2546 "referenced, but not found\n", 2547 family_references).format(quantity=family_references) 2548 ) 2549 2550 if invalid_dates: 2551 self.text.write( 2552 # translators: leave all/any {...} untranslated 2553 ngettext("{quantity} date was corrected\n", 2554 "{quantity} dates were corrected\n", 2555 invalid_dates).format(quantity=invalid_dates) 2556 ) 2557 2558 if repo_references: 2559 self.text.write( 2560 # translators: leave all/any {...} untranslated 2561 ngettext( 2562 "{quantity} repository was " 2563 "referenced but not found\n", 2564 "{quantity} repositories were " 2565 "referenced, but not found\n", 2566 repo_references).format(quantity=repo_references) 2567 ) 2568 2569 if photos: 2570 self.text.write( 2571 # translators: leave all/any {...} untranslated 2572 ngettext("{quantity} media object was " 2573 "referenced but not found\n", 2574 "{quantity} media objects were " 2575 "referenced, but not found\n", 2576 photos).format(quantity=photos) 2577 ) 2578 2579 if bad_photos: 2580 self.text.write( 2581 # translators: leave all/any {...} untranslated 2582 ngettext( 2583 "Reference to {quantity} missing media object was kept\n", 2584 "References to {quantity} media objects were kept\n", 2585 bad_photos).format(quantity=bad_photos) 2586 ) 2587 2588 if replaced_photos: 2589 self.text.write( 2590 # translators: leave all/any {...} untranslated 2591 ngettext("{quantity} missing media object was replaced\n", 2592 "{quantity} missing media objects were replaced\n", 2593 replaced_photos).format(quantity=replaced_photos) 2594 ) 2595 2596 if removed_photos: 2597 self.text.write( 2598 # translators: leave all/any {...} untranslated 2599 ngettext("{quantity} missing media object was removed\n", 2600 "{quantity} missing media objects were removed\n", 2601 removed_photos).format(quantity=removed_photos) 2602 ) 2603 2604 if event_invalid: 2605 self.text.write( 2606 # translators: leave all/any {...} untranslated 2607 ngettext("{quantity} event was referenced but not found\n", 2608 "{quantity} events were referenced, but not found\n", 2609 event_invalid).format(quantity=event_invalid) 2610 ) 2611 2612 if birth_invalid: 2613 self.text.write( 2614 # translators: leave all/any {...} untranslated 2615 ngettext("{quantity} invalid birth event name was fixed\n", 2616 "{quantity} invalid birth event names were fixed\n", 2617 birth_invalid).format(quantity=birth_invalid) 2618 ) 2619 2620 if death_invalid: 2621 self.text.write( 2622 # translators: leave all/any {...} untranslated 2623 ngettext("{quantity} invalid death event name was fixed\n", 2624 "{quantity} invalid death event names were fixed\n", 2625 death_invalid).format(quantity=death_invalid) 2626 ) 2627 2628 if place_references: 2629 self.text.write( 2630 # translators: leave all/any {...} untranslated 2631 ngettext("{quantity} place was referenced but not found\n", 2632 "{quantity} places were referenced, but not found\n", 2633 place_references).format(quantity=place_references) 2634 ) 2635 2636 if citation_references: 2637 self.text.write( 2638 # translators: leave all/any {...} untranslated 2639 ngettext( 2640 "{quantity} citation was referenced but not found\n", 2641 "{quantity} citations were referenced, but not found\n", 2642 citation_references 2643 ).format(quantity=citation_references) 2644 ) 2645 2646 if source_references: 2647 self.text.write( 2648 # translators: leave all/any {...} untranslated 2649 ngettext( 2650 "{quantity} source was referenced but not found\n", 2651 "{quantity} sources were referenced, but not found\n", 2652 source_references).format(quantity=source_references) 2653 ) 2654 2655 if media_references: 2656 self.text.write( 2657 # translators: leave all/any {...} untranslated 2658 ngettext( 2659 "{quantity} media object was referenced but not found\n", 2660 "{quantity} media objects were referenced," 2661 " but not found\n", 2662 media_references).format(quantity=media_references) 2663 ) 2664 2665 if note_references: 2666 self.text.write( 2667 # translators: leave all/any {...} untranslated 2668 ngettext("{quantity} note object was " 2669 "referenced but not found\n", 2670 "{quantity} note objects were " 2671 "referenced, but not found\n", 2672 note_references).format(quantity=note_references) 2673 ) 2674 2675 if tag_references: 2676 self.text.write( 2677 # translators: leave all/any {...} untranslated 2678 ngettext("{quantity} tag object was " 2679 "referenced but not found\n", 2680 "{quantity} tag objects were " 2681 "referenced, but not found\n", 2682 tag_references).format(quantity=tag_references) 2683 ) 2684 2685 if tag_references: 2686 self.text.write( 2687 # translators: leave all/any {...} untranslated 2688 ngettext("{quantity} tag object was " 2689 "referenced but not found\n", 2690 "{quantity} tag objects were " 2691 "referenced, but not found\n", 2692 tag_references).format(quantity=tag_references) 2693 ) 2694 2695 if name_format: 2696 self.text.write( 2697 # translators: leave all/any {...} untranslated 2698 ngettext("{quantity} invalid name format " 2699 "reference was removed\n", 2700 "{quantity} invalid name format " 2701 "references were removed\n", 2702 name_format).format(quantity=name_format) 2703 ) 2704 2705 if replaced_sourcerefs: 2706 self.text.write( 2707 # translators: leave all/any {...} untranslated 2708 ngettext( 2709 "{quantity} invalid source citation was fixed\n", 2710 "{quantity} invalid source citations were fixed\n", 2711 replaced_sourcerefs 2712 ).format(quantity=replaced_sourcerefs) 2713 ) 2714 2715 if dup_gramps_ids > 0: 2716 self.text.write( 2717 # translators: leave all/any {...} untranslated 2718 ngettext("{quantity} Duplicated Gramps ID fixed\n", 2719 "{quantity} Duplicated Gramps IDs fixed\n", 2720 dup_gramps_ids).format(quantity=dup_gramps_ids) 2721 ) 2722 2723 if empty_objs > 0: 2724 self.text.write(_( 2725 "%(empty_obj)d empty objects removed:\n" 2726 " %(person)d person objects\n" 2727 " %(family)d family objects\n" 2728 " %(event)d event objects\n" 2729 " %(source)d source objects\n" 2730 " %(media)d media objects\n" 2731 " %(place)d place objects\n" 2732 " %(repo)d repository objects\n" 2733 " %(note)d note objects\n") % { 2734 'empty_obj': empty_objs, 2735 'person': len(self.empty_objects['persons']), 2736 'family': len(self.empty_objects['families']), 2737 'event': len(self.empty_objects['events']), 2738 'source': len(self.empty_objects['sources']), 2739 'media': len(self.empty_objects['media']), 2740 'place': len(self.empty_objects['places']), 2741 'repo': len(self.empty_objects['repos']), 2742 'note': len(self.empty_objects['notes']) 2743 } 2744 ) 2745 2746 if self.bad_backlinks: 2747 self.text.write(_("%d bad backlinks were fixed;\n") 2748 % self.bad_backlinks + 2749 _("All reference maps have been rebuilt.") + '\n') 2750 2751 return errors 2752 2753 2754# ------------------------------------------------------------------------- 2755# 2756# Display the results 2757# 2758# ------------------------------------------------------------------------- 2759class CheckReport(ManagedWindow): 2760 """ Report out the results """ 2761 def __init__(self, uistate, text, cli=0): 2762 if cli: 2763 print(text) 2764 2765 if uistate: 2766 ManagedWindow.__init__(self, uistate, [], self) 2767 2768 topdialog = Glade() 2769 topdialog.get_object("close").connect('clicked', self.close) 2770 window = topdialog.toplevel 2771 textwindow = topdialog.get_object("textwindow") 2772 textwindow.get_buffer().set_text(text) 2773 2774 self.set_window(window, 2775 # topdialog.get_widget("title"), 2776 topdialog.get_object("title"), 2777 _("Integrity Check Results")) 2778 self.setup_configs('interface.checkreport', 450, 400) 2779 2780 self.show() 2781 2782 def build_menu_names(self, obj): 2783 return (_('Check and Repair'), None) 2784 2785 2786# ------------------------------------------------------------------------ 2787# 2788# 2789# 2790# ------------------------------------------------------------------------ 2791class CheckOptions(tool.ToolOptions): 2792 """ 2793 Defines options and provides handling interface. 2794 """ 2795 2796 def __init__(self, name, person_id=None): 2797 tool.ToolOptions.__init__(self, name, person_id) 2798