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