2007-06-11 Don Allingham <don@gramps-project.org>

* src/ViewManager.py: Improve backup strategy
	* src/GrampsDb/_GrampsDBDir.py: Improve backup strategy
	* src/DbManager.py: Improve backup strategy
	* src/glade/gramps.glade: Improve backup strategy
	* src/Errors.py: Improve backup strategy
	* src/GrampsDbUtils/_Backup.py: Improve backup strategy



svn: r8538
This commit is contained in:
Don Allingham 2007-06-12 04:29:15 +00:00
parent 6bf09da13d
commit e05e6b4edd
9 changed files with 189 additions and 95 deletions

View File

@ -1,3 +1,11 @@
2007-06-11 Don Allingham <don@gramps-project.org>
* src/ViewManager.py: Improve backup strategy
* src/GrampsDb/_GrampsDBDir.py: Improve backup strategy
* src/DbManager.py: Improve backup strategy
* src/glade/gramps.glade: Improve backup strategy
* src/Errors.py: Improve backup strategy
* src/GrampsDbUtils/_Backup.py: Improve backup strategy
2007-06-06 Alex Roitman <shura@gramps-project.org> 2007-06-06 Alex Roitman <shura@gramps-project.org>
* src/DisplayState.py (DisplayState.__signals__): Port fixes from * src/DisplayState.py (DisplayState.__signals__): Port fixes from
2.2 tree. 2.2 tree.

View File

@ -58,6 +58,9 @@ import gtk.glade
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
import QuestionDialog import QuestionDialog
import GrampsDb
import GrampsDbUtils
import Config
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
@ -98,6 +101,7 @@ class DbManager:
self.remove = self.glade.get_widget('remove') self.remove = self.glade.get_widget('remove')
self.dblist = self.glade.get_widget('dblist') self.dblist = self.glade.get_widget('dblist')
self.rename = self.glade.get_widget('rename') self.rename = self.glade.get_widget('rename')
self.repair = self.glade.get_widget('repair')
self.model = None self.model = None
self.dbstate = dbstate self.dbstate = dbstate
self.column = None self.column = None
@ -123,6 +127,7 @@ class DbManager:
self.remove.connect('clicked', self.remove_db) self.remove.connect('clicked', self.remove_db)
self.new.connect('clicked', self.new_db) self.new.connect('clicked', self.new_db)
self.rename.connect('clicked', self.rename_db) self.rename.connect('clicked', self.rename_db)
self.repair.connect('clicked', self.repair_db)
self.selection.connect('changed', self.selection_changed) self.selection.connect('changed', self.selection_changed)
self.dblist.connect('button-press-event', self.button_press) self.dblist.connect('button-press-event', self.button_press)
@ -155,6 +160,7 @@ class DbManager:
if not node: if not node:
self.connect.set_sensitive(False) self.connect.set_sensitive(False)
self.rename.set_sensitive(False) self.rename.set_sensitive(False)
self.repair.set_sensitive(False)
self.remove.set_sensitive(False) self.remove.set_sensitive(False)
else: else:
if store.get_value(node, OPEN_COL): if store.get_value(node, OPEN_COL):
@ -162,6 +168,7 @@ class DbManager:
else: else:
self.connect.set_sensitive(True) self.connect.set_sensitive(True)
self.rename.set_sensitive(True) self.rename.set_sensitive(True)
self.repair.set_sensitive(True)
self.remove.set_sensitive(True) self.remove.set_sensitive(True)
def build_interface(self): def build_interface(self):
@ -320,6 +327,29 @@ class DbManager:
self.dblist.set_cursor(path, focus_column=self.column, self.dblist.set_cursor(path, focus_column=self.column,
start_editing=True) start_editing=True)
def repair_db(self, obj):
"""
Start the rename process by calling the start_editing option on
the line with the cursor.
"""
store, node = self.selection.get_selected()
dirname = store[node][1]
opened = store[node][5]
if opened:
self.dbstate.no_database()
# delete files that are not backup files or the .txt file
for filename in os.listdir(dirname):
if os.path.splitext(filename)[1] not in (".gbkp", ".txt"):
os.unlink(os.path.join(dirname,filename))
dbclass = GrampsDb.gramps_db_factory(db_type = "x-directory/normal")
db = dbclass(Config.get(Config.TRANSACTIONS))
db.set_save_path(dirname)
db.load(dirname, None)
GrampsDbUtils.Backup.restore(db)
db.close()
def new_db(self, obj): def new_db(self, obj):
""" """
Callback wrapper around the actual routine that creates the Callback wrapper around the actual routine that creates the

View File

@ -139,3 +139,15 @@ class MaskError(Exception):
class ValidationError(Exception): class ValidationError(Exception):
pass pass
class DbError(Exception):
"""Error used to report that the request window is already displayed."""
def __init__(self, value):
Exception.__init__(self)
if type(value) == tuple:
self.value = value[1]
else:
self.value = value
def __str__(self):
"Return string representation"
return self.value

View File

@ -34,6 +34,7 @@ import os
import shutil import shutil
import re import re
import time import time
from gettext import gettext as _ from gettext import gettext as _
from bsddb import dbshelve, db from bsddb import dbshelve, db
import logging import logging
@ -161,7 +162,6 @@ class GrampsDBDirDupCursor(GrampsDBDirAssocCursor):
def next_dup(self): def next_dup(self):
return self.cursor.next_dup() return self.cursor.next_dup()
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# GrampsDBDir # GrampsDBDir
@ -179,13 +179,13 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
self.secondary_connected = False self.secondary_connected = False
self.UseTXN = use_txn self.UseTXN = use_txn
def open_flags(self): def __open_flags(self):
if self.UseTXN: if self.UseTXN:
return db.DB_CREATE | db.DB_AUTO_COMMIT return db.DB_CREATE | db.DB_AUTO_COMMIT
else: else:
return db.DB_CREATE return db.DB_CREATE
def open_table(self, file_name, table_name, dbtype=db.DB_HASH): def __open_table(self, file_name, table_name, dbtype=db.DB_HASH):
dbmap = dbshelve.DBShelf(self.env) dbmap = dbshelve.DBShelf(self.env)
dbmap.db.set_pagesize(16384) dbmap.db.set_pagesize(16384)
@ -194,7 +194,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
if self.readonly: if self.readonly:
dbmap.open(fname, table_name, dbtype, db.DB_RDONLY) dbmap.open(fname, table_name, dbtype, db.DB_RDONLY)
else: else:
dbmap.open(fname, table_name, dbtype, self.open_flags(), 0666) dbmap.open(fname, table_name, dbtype, self.__open_flags(), 0666)
return dbmap return dbmap
def _all_handles(self,table): def _all_handles(self,table):
@ -367,7 +367,8 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
dbversion = self.metadata.get('version',default=0) dbversion = self.metadata.get('version',default=0)
return not self.readonly and dbversion < _DBVERSION return not self.readonly and dbversion < _DBVERSION
def load(self, name, callback,mode="w"): def load(self, name, callback, mode="w"):
if self.db_is_open: if self.db_is_open:
self.close() self.close()
@ -375,7 +376,8 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
if self.readonly: if self.readonly:
self.UseTXN = False self.UseTXN = False
callback(12) if callback:
callback(12)
self.full_name = os.path.abspath(name) self.full_name = os.path.abspath(name)
self.brief_name = os.path.basename(name) self.brief_name = os.path.basename(name)
@ -404,31 +406,33 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
env_flags = db.DB_CREATE | db.DB_PRIVATE | db.DB_INIT_MPOOL env_flags = db.DB_CREATE | db.DB_PRIVATE | db.DB_INIT_MPOOL
env_name = os.path.expanduser('~') env_name = os.path.expanduser('~')
self.env.open(env_name,env_flags) self.env.open(env_name, env_flags)
if self.UseTXN: if self.UseTXN:
self.env.txn_checkpoint() self.env.txn_checkpoint()
callback(25) if callback:
self.metadata = self.open_table(self.full_name, META) callback(25)
self.metadata = self.__open_table(self.full_name, META)
# If we cannot work with this DB version, # If we cannot work with this DB version,
# it makes no sense to go further # it makes no sense to go further
if not self.version_supported: if not self.version_supported:
self._close_early() self._close_early()
self.family_map = self.open_table(self.full_name, FAMILY_TBL) self.family_map = self.__open_table(self.full_name, FAMILY_TBL)
self.place_map = self.open_table(self.full_name, PLACES_TBL) self.place_map = self.__open_table(self.full_name, PLACES_TBL)
self.source_map = self.open_table(self.full_name, SOURCES_TBL) self.source_map = self.__open_table(self.full_name, SOURCES_TBL)
self.media_map = self.open_table(self.full_name, MEDIA_TBL) self.media_map = self.__open_table(self.full_name, MEDIA_TBL)
self.event_map = self.open_table(self.full_name, EVENTS_TBL) self.event_map = self.__open_table(self.full_name, EVENTS_TBL)
self.person_map = self.open_table(self.full_name, PERSON_TBL) self.person_map = self.__open_table(self.full_name, PERSON_TBL)
self.repository_map = self.open_table(self.full_name, REPO_TBL) self.repository_map = self.__open_table(self.full_name, REPO_TBL)
self.note_map = self.open_table(self.full_name, NOTE_TBL) self.note_map = self.__open_table(self.full_name, NOTE_TBL)
self.reference_map = self.open_table(self.full_name, REF_MAP, self.reference_map = self.__open_table(self.full_name, REF_MAP,
dbtype=db.DB_BTREE) dbtype=db.DB_BTREE)
callback(37) if callback:
callback(37)
self._load_metadata() self.__load_metadata()
gstats = self.metadata.get('gender_stats', default=None) gstats = self.metadata.get('gender_stats', default=None)
@ -462,17 +466,20 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
if self.need_upgrade(): if self.need_upgrade():
self.gramps_upgrade(callback) self.gramps_upgrade(callback)
callback(50) if callback:
callback(50)
if not self.secondary_connected: if not self.secondary_connected:
self.connect_secondary() self.__connect_secondary()
callback(75) if callback:
callback(75)
self.open_undodb() self.open_undodb()
self.db_is_open = True self.db_is_open = True
callback(87) if callback:
callback(87)
# Re-set the undo history to a fresh session start # Re-set the undo history to a fresh session start
self.undoindex = -1 self.undoindex = -1
@ -487,7 +494,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
db_copy(other_database,self,callback) db_copy(other_database,self,callback)
return 1 return 1
def _load_metadata(self): def __load_metadata(self):
# name display formats # name display formats
self.name_formats = self.metadata.get('name_formats', default=[]) self.name_formats = self.metadata.get('name_formats', default=[])
# upgrade formats if they were saved in the old way # upgrade formats if they were saved in the old way
@ -547,7 +554,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
# surname list # surname list
self.surname_list = self.metadata.get('surname_list', default=[]) self.surname_list = self.metadata.get('surname_list', default=[])
def connect_secondary(self): def __connect_secondary(self):
""" """
This method connects or creates secondary index tables. This method connects or creates secondary index tables.
It assumes that the tables either exist and are in the right It assumes that the tables either exist and are in the right
@ -561,7 +568,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
if self.readonly: if self.readonly:
table_flags = db.DB_RDONLY table_flags = db.DB_RDONLY
else: else:
table_flags = self.open_flags() table_flags = self.__open_flags()
self.surnames = db.DB(self.env) self.surnames = db.DB(self.env)
self.surnames.set_flags(db.DB_DUP | db.DB_DUPSORT) self.surnames.set_flags(db.DB_DUP | db.DB_DUPSORT)
@ -653,11 +660,11 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
self.rmap_index = len(self.repository_map) self.rmap_index = len(self.repository_map)
self.nmap_index = len(self.note_map) self.nmap_index = len(self.note_map)
def rebuild_secondary(self,callback): def rebuild_secondary(self,callback=None):
if self.readonly: if self.readonly:
return return
table_flags = self.open_flags() table_flags = self.__open_flags()
# remove existing secondary indices # remove existing secondary indices
@ -680,16 +687,19 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
db.close() db.close()
env = db.DB(self.env) env = db.DB(self.env)
env.remove(_mkname(self.full_name, name), name) env.remove(_mkname(self.full_name, name), name)
callback(index) if callback:
callback(index)
index += 1 index += 1
callback(11) if callback:
callback(11)
# Set flag saying that we have removed secondary indices # Set flag saying that we have removed secondary indices
# and then call the creating routine # and then call the creating routine
self.secondary_connected = False self.secondary_connected = False
self.connect_secondary() self.__connect_secondary()
callback(12) if callback:
callback(12)
def find_backlink_handles(self, handle, include_classes=None): def find_backlink_handles(self, handle, include_classes=None):
""" """
@ -744,7 +754,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
return return
def _delete_primary_from_reference_map(self,handle,transaction,txn=None): def __delete_primary_from_reference_map(self,handle,transaction,txn=None):
""" """
Remove all references to the primary object from the reference_map. Remove all references to the primary object from the reference_map.
""" """
@ -915,10 +925,10 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
callback(3) callback(3)
# Open reference_map and primapry map # Open reference_map and primapry map
self.reference_map = self.open_table( self.reference_map = self.__open_table(
_mkname(self.full_name, REF_MAP), REF_MAP, dbtype=db.DB_BTREE) _mkname(self.full_name, REF_MAP), REF_MAP, dbtype=db.DB_BTREE)
open_flags = self.open_flags() open_flags = self.__open_flags()
self.reference_map_primary_map = db.DB(self.env) self.reference_map_primary_map = db.DB(self.env)
self.reference_map_primary_map.set_flags(db.DB_DUP) self.reference_map_primary_map.set_flags(db.DB_DUP)
self.reference_map_primary_map.open( self.reference_map_primary_map.open(
@ -1146,7 +1156,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
self.metadata = None self.metadata = None
self.db_is_open = False self.db_is_open = False
def _do_remove_object(self,handle,transaction,data_map,key,del_list): def __do_remove_object(self,handle,transaction,data_map,key,del_list):
if self.readonly or not handle: if self.readonly or not handle:
return return
@ -1156,7 +1166,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
the_txn = self.env.txn_begin() the_txn = self.env.txn_begin()
else: else:
the_txn = None the_txn = None
self._delete_primary_from_reference_map(handle,transaction, self.__delete_primary_from_reference_map(handle,transaction,
txn=the_txn) txn=the_txn)
data_map.delete(handle,txn=the_txn) data_map.delete(handle,txn=the_txn)
if not self.UseTXN: if not self.UseTXN:
@ -1164,7 +1174,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
if the_txn: if the_txn:
the_txn.commit() the_txn.commit()
else: else:
self._delete_primary_from_reference_map(handle,transaction) self.__delete_primary_from_reference_map(handle,transaction)
old_data = data_map.get(handle,txn=self.txn) old_data = data_map.get(handle,txn=self.txn)
transaction.add(key,handle,old_data,None) transaction.add(key,handle,old_data,None)
del_list.append(handle) del_list.append(handle)
@ -1364,7 +1374,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
add_list.append((handle,new_data)) add_list.append((handle,new_data))
return old_data return old_data
def _do_commit(self, add_list, db_map): def __do_commit(self, add_list, db_map):
retlist = [] retlist = []
for (handle, data) in add_list: for (handle, data) in add_list:
db_map.put(handle, data, self.txn) db_map.put(handle, data, self.txn)
@ -1461,7 +1471,7 @@ class GrampsDBDir(GrampsDbBase,UpdateCallback):
if not transaction.no_magic: if not transaction.no_magic:
# create new secondary indices to replace the ones removed # create new secondary indices to replace the ones removed
open_flags = self.open_flags() open_flags = self.__open_flags()
dupe_flags = db.DB_DUP|db.DB_DUPSORT dupe_flags = db.DB_DUP|db.DB_DUPSORT
self.surnames = db.DB(self.env) self.surnames = db.DB(self.env)

View File

@ -1,7 +1,7 @@
# #
# Gramps - a GTK+/GNOME based genealogy program # Gramps - a GTK+/GNOME based genealogy program
# #
# Copyright (C) 2000-2006 Donald N. Allingham # Copyright (C) 2007 Donald N. Allingham
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -18,11 +18,8 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# #
# $Id: _WriteXML.py 8144 2007-02-17 22:12:56Z hippy $
""" """
Contains the interface to allow a database to get written using Provides backup and restore functions for a database
GRAMPS' XML file format.
""" """
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
@ -46,72 +43,98 @@ from QuestionDialog import ErrorDialog
#------------------------------------------------------------------------ #------------------------------------------------------------------------
import logging import logging
import os import os
from GrampsDb import _GrampsDBDir as GrampsDBDir
import cPickle as pickle import cPickle as pickle
LOG = logging.getLogger(".Backukp") LOG = logging.getLogger(".Backup")
def export(database): def export(database):
"""
Exports the database to a set of backup files. These files consist
of the pickled database tables, one file for each table.
The heavy lifting is done by the private __do__export function. The
purpose of this function is to catch any exceptions that occur.
@param database: database instance to backup
@type database: GrampsDbDir
"""
try: try:
do_export(database) __do_export(database)
except (OSError, IOError), msg: except (OSError, IOError), msg:
ErrorDialog( ErrorDialog(_("Error saving backup data"), str(msg))
_("Error saving backup data"),
str(msg))
def do_export(database): def __do_export(database):
"""
Loop through each table of the database, saving the pickled data
a file.
tables = [ @param database: database instance to backup
('person', database.person_map.db), @type database: GrampsDbDir
('family', database.family_map.db), """
('place', database.place_map.db), for (base, tbl) in __build_tbl_map(database):
('source', database.source_map.db),
('repo', database.repository_map.db),
('note', database.note_map.db),
('media', database.media_map.db),
('event', database.event_map.db),
('meta_data', database.metadata.db),
]
for (base, db) in tables:
backup_name = os.path.join(database.get_save_path(), base + ".gbkp") backup_name = os.path.join(database.get_save_path(), base + ".gbkp")
backup_table = open(backup_name, 'w') backup_table = open(backup_name, 'wb')
cursor = db.cursor() cursor = tbl.cursor()
d = cursor.first() data = cursor.first()
while d: while data:
pickle.dump(d[1], backup_table, 2) pickle.dump(data, backup_table, 2)
d = cursor.next() data = cursor.next()
cursor.close() cursor.close()
backup_table.close() backup_table.close()
def restore(database): def restore(database):
"""
Restores the database to a set of backup files. These files consist
of the pickled database tables, one file for each table.
The heavy lifting is done by the private __do__restore function. The
purpose of this function is to catch any exceptions that occur.
@param database: database instance to restore
@type database: GrampsDbDir
"""
try: try:
do_restore(database) __do_restore(database)
except (OSError, IOError), msg: except (OSError, IOError), msg:
ErrorDialog( ErrorDialog(_("Error restoring backup data"), str(msg))
_("Error restoring backup data"),
str(msg))
def do_restore(database): def __do_restore(database):
"""
Loop through each table of the database, restoring the pickled data
to the appropriate database file.
tables = [ @param database: database instance to backup
('person', database.person_map), @type database: GrampsDbDir
('family', database.family_map), """
('place', database.place_map), for (base, tbl) in __build_tbl_map(database):
('source', database.place_map),
('repo', database.repository_map),
('note', database.note_map),
('media', database.media_map),
('event', database.media_map),
]
for (base, db) in tables:
backup_name = os.path.join(database.get_save_path(), base + ".gbkp") backup_name = os.path.join(database.get_save_path(), base + ".gbkp")
backup_table = open(backup_name, 'r') backup_table = open(backup_name, 'rb')
try: try:
while True: while True:
db[data[0]] = pickle.load(backup_table) data = pickle.load(backup_table)
tbl[data[0]] = data[1]
except EOFError: except EOFError:
backup_table.close() backup_table.close()
database.rebuild_secondary()
def __build_tbl_map(database):
"""
Builds a table map of names to database tables.
@param database: database instance to backup
@type database: GrampsDbDir
"""
return [
( GrampsDBDir.PERSON_TBL, database.person_map.db),
( GrampsDBDir.FAMILY_TBL, database.family_map.db),
( GrampsDBDir.PLACES_TBL, database.place_map.db),
( GrampsDBDir.SOURCES_TBL, database.source_map.db),
( GrampsDBDir.REPO_TBL, database.repository_map.db),
( GrampsDBDir.NOTE_TBL, database.note_map.db),
( GrampsDBDir.MEDIA_TBL, database.media_map.db),
( GrampsDBDir.EVENTS_TBL, database.event_map.db),
( GrampsDBDir.META, database.metadata.db),
]

View File

@ -84,7 +84,7 @@ gdir_PYTHON = \
MOSTLYCLEANFILES = *pyc *pyo MOSTLYCLEANFILES = *pyc *pyo
# Which modules to document # Which modules to document
docmodules = RelLib DateHandler GrampsDb Simple #Filters ReportBase GrampsDbUtils docmodules = RelLib DateHandler GrampsDb Simple BaseDoc #Filters ReportBase GrampsDbUtils
pycheck: pycheck:
for d in $(SUBDIRS) ; do \ for d in $(SUBDIRS) ; do \

View File

@ -528,7 +528,7 @@ class ViewManager:
def backup(self): def backup(self):
""" """
Backup the current file as an XML file. Backup the current file as a backup file.
""" """
import GrampsDbUtils import GrampsDbUtils
@ -959,7 +959,6 @@ class ViewManager:
"\n" + str(msg)) "\n" + str(msg))
return return
self.state.change_database(dbclass(Config.get(Config.TRANSACTIONS))) self.state.change_database(dbclass(Config.get(Config.TRANSACTIONS)))
self.state.db.disable_signals() self.state.db.disable_signals()

View File

@ -122,7 +122,8 @@ class PreviewWindow(gtk.Window):
print_context, print_context,
parent): parent):
gtk.Window.__init__(self) gtk.Window.__init__(self)
self.set_default_size(640, 480)
self._operation = operation self._operation = operation
self._preview_operation = preview_operation self._preview_operation = preview_operation
@ -318,7 +319,6 @@ class CairoJob(object):
y = 20 y = 20
x = 30 x = 30
print "self._doc: ",
text="\n".join(self._doc) text="\n".join(self._doc)
# Draw some text # Draw some text

View File

@ -15702,6 +15702,18 @@ Very High</property>
<property name="focus_on_click">True</property> <property name="focus_on_click">True</property>
</widget> </widget>
</child> </child>
<child>
<widget class="GtkButton" id="repair">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Repair</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
</widget>
</child>
</widget> </widget>
</child> </child>
</widget> </widget>