Split CLI from GUI. These changes allow CLI to work without GTK

Part 1. To do: pylint on new files.


svn: r12674
This commit is contained in:
Benny Malengier
2009-06-18 21:56:37 +00:00
parent c71367ed54
commit 4b7692708c
23 changed files with 1304 additions and 1189 deletions

View File

@@ -6,7 +6,11 @@
pkgdatadir = $(datadir)/@PACKAGE@/cli
pkgdata_PYTHON = \
__init__.py
__init__.py \
arghandler.py \
argparser.py \
clidbman.py \
grampscli.py
pkgpyexecdir = @pkgpyexecdir@/cli
pkgpythondir = @pkgpythondir@/cli

View File

@@ -22,3 +22,8 @@
"""
Package init for the cli package.
"""
from grampscli import startcli, CLIDbLoader, CLIManager
from argparser import ArgParser
from arghandler import ArgHandler
from clidbman import CLIDbManager

497
src/cli/arghandler.py Normal file
View File

@@ -0,0 +1,497 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham, A. Roitman
# Copyright (C) 2007-2009 B. Malengier
# Copyright (C) 2008 Lukasz Rymarczyk
# Copyright (C) 2008 Raphael Ackermann
# Copyright (C) 2008 Brian G. Matherly
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: ArgHandler.py 12559 2009-05-21 17:19:50Z gbritton $
"""
Module responsible for handling the command line arguments for GRAMPS.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import os
import sys
import getopt
from gettext import gettext as _
import logging
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
import const
import Config
import RecentFiles
import Utils
import gen
from clidbman import CLIDbManager, NAME_FILE, find_locker_name
from PluginUtils import Tool
from gen.plug import PluginManager
from ReportBase import CATEGORY_BOOK, CATEGORY_CODE, cl_report
#-------------------------------------------------------------------------
# ArgHandler
#-------------------------------------------------------------------------
class ArgHandler(object):
"""
This class is responsible for the non GUI handling of commands
The handler is passed a parser object, sanitizes it, and can execute the
actions requested working on a DbState
"""
def __init__(self, dbstate, parser, sessionmanager,
errorfunc=None, gui=False):
self.dbstate = dbstate
self.sm = sessionmanager
self.errorfunc = errorfunc
self.gui = gui
self.dbman = CLIDbManager(self.dbstate)
self.force_unlock = parser.force_unlock
self.open = self.__handle_open_option(parser.open)
self.cl = 0
self.imports = []
self.exports = []
self.sanitize_args(parser.imports, parser.exports)
self.open_gui = parser.open_gui
if self.gui:
self.actions = []
self.list = False
self.list_more = False
self.open_gui = None
else:
self.actions = parser.actions
self.list = parser.list
self.list_more = parser.list_more
self.imp_db_path = None
def error(self, string):
if self.errorfunc:
self.errorfunc(string)
else:
print string
#-------------------------------------------------------------------------
# Argument parser: sorts out given arguments
#-------------------------------------------------------------------------
def sanitize_args(self, importlist, exportlist):
"""
check the lists with open, exports, imports, and actions options.
"""
for (value, format) in importlist:
self.__handle_import_option(value, format)
for (value, format) in exportlist:
self.__handle_export_option(value, format)
def __handle_open_option(self, value):
"""
Handle the "-O" or "--open" option.
"""
if value is None:
return None
db_path = self.__deduce_db_path(value)
if db_path:
# We have a potential database path.
# Check if it is good.
if not self.check_db(db_path, self.force_unlock):
sys.exit(0)
return db_path
else:
self.error( _('Error: Input family tree "%s" does not exist.\n'
"If gedcom, gramps-xml or grdb, use the -i option to "
"import into a family tree instead.") % value)
sys.exit(0)
def __handle_import_option(self, value, format):
"""
Handle the "-i" or "--import" option.
"""
fname = value
fullpath = os.path.abspath(os.path.expanduser(fname))
if not os.path.exists(fullpath):
self.error(_('Error: Import file %s not found.') % fname)
sys.exit(0)
if format is None:
# Guess the file format based on the file extension.
# This will get the lower case extension without a period,
# or an empty string.
format = os.path.splitext(fname)[-1][1:].lower()
pmgr = PluginManager.get_instance()
plugin_found = False
for plugin in pmgr.get_import_plugins():
if format == plugin.get_extension():
plugin_found = True
if plugin_found:
self.imports.append((fname, format))
else:
self.error(_('Error: Unrecognized type: "%(format)s" for '
'import file: %(filename)s') \
% {'format' : format,
'filename' : fname})
sys.exit(0)
def __handle_export_option(self, value, format):
"""
Handle the "-e" or "--export" option.
Note: only the CLI version has export
"""
if self.gui:
return
fname = value
fullpath = os.path.abspath(os.path.expanduser(fname))
if os.path.exists(fullpath):
self.error(_("WARNING: Output file already exist!\n"
"WARNING: It will be overwritten:\n %(name)s") % \
{'name' : fullpath})
answer = None
while not answer:
answer = raw_input(_('OK to overwrite? (yes/no) '))
if answer.upper() in ('Y','YES', _('YES')):
self.error( _("Will overwrite the existing file: %s")
% fullpath)
else:
sys.exit(0)
if format is None:
# Guess the file format based on the file extension.
# This will get the lower case extension without a period,
# or an empty string.
format = os.path.splitext(fname)[-1][1:].lower()
pmgr = PluginManager.get_instance()
plugin_found = False
for plugin in pmgr.get_export_plugins():
if format == plugin.get_extension():
plugin_found = True
if plugin_found:
self.exports.append((fullpath, format))
else:
self.error(_("ERROR: Unrecognized format for export file %s")
% fname)
sys.exit(0)
def __deduce_db_path(self, db_name_or_path):
"""
Attempt to find a database path for the given parameter.
@return: The path to a Gramps DB
or None if a database can not be deduced.
"""
# First, check if this is the name of a family tree
db_path = self.dbman.get_family_tree_path(db_name_or_path)
if db_path is None:
# This is not a known database name.
# Check if the user provided a db path instead.
fullpath = os.path.abspath(os.path.expanduser(db_name_or_path))
if os.path.isdir(fullpath):
# The user provided a directory. Check if it is a valid tree.
name_file_path = os.path.join(fullpath, NAME_FILE)
if os.path.isfile(name_file_path):
db_path = fullpath
return db_path
#-------------------------------------------------------------------------
# Overall argument handler:
# sorts out the sequence and details of operations
#-------------------------------------------------------------------------
def handle_args_gui(self, dbman):
"""
method to handle the arguments that can be given for a GUI session.
Returns the filename of the family tree that should be openend
1/no options: a family tree can be given, if so, this name is tested
and returned. If a filename, it is imported in a new db
and name of new db returned
2/an open option can have been given
"""
if self.open_gui:
# First check if a Gramps database was provided
# (either a database path or a database name)
db_path = self.__deduce_db_path(self.open_gui)
if not db_path:
# Apparently it is not a database. See if it is a file that
# can be imported.
db_path, title = self.dbman.import_new_db(self.open_gui, None)
if db_path:
# Test if not locked or problematic
if not self.check_db(db_path, self.force_unlock):
sys.exit(0)
# Add the file to the recent items
path = os.path.join(db_path, "name.txt")
try:
ifile = open(path)
title = ifile.readline().strip()
ifile.close()
except:
title = db_path
RecentFiles.recent_files(db_path, title)
else:
sys.exit(0)
return db_path
# if not open_gui, parse any command line args. We can only have one
# open argument, and perhaps some import arguments
self.__open_action()
self.__import_action()
def handle_args_cli(self, climan):
"""
Depending on the given arguments, import or open data, launch
session, write files, and/or perform actions.
@param: climan: the manager of a CLI session
@type: CLIManager object
"""
if self.list:
print 'List of known family trees in your database path\n'
for name, dirname in self.dbman.family_tree_list():
print dirname, ', with name ', name
sys.exit(0)
if self.list_more:
print 'GRAMPS Family Trees:'
summary_list = self.dbman.family_tree_summary()
for summary in summary_list:
print "Family Tree \"%s\":" % summary["Family tree"]
for item in summary:
if item != "Family tree":
print " %s: %s" % (item, summary[item])
sys.exit(0)
self.__open_action()
self.__import_action()
for (action, options_str) in self.actions:
print "Performing action: %s." % action
if options_str:
print "Using options string: %s" % options_str
self.cl_action(action, options_str)
for expt in self.exports:
print "Exporting: file %s, format %s." % expt
self.cl_export(expt[0], expt[1])
print "Cleaning up."
# remove files in import db subdir after use
self.dbstate.db.close()
if self.imp_db_path:
Utils.rm_tempdir(self.imp_db_path)
print "Exiting."
sys.exit(0)
def __import_action(self):
if self.imports:
self.cl = bool(self.exports or self.actions or self.cl)
if not self.open:
# Create empty dir for imported database(s)
if self.gui:
self.imp_db_path, title = self.dbman._create_new_db_cli()
else:
self.imp_db_path = Utils.get_empty_tempdir("import_dbdir")
newdb = gen.db.GrampsDBDir()
newdb.write_version(self.imp_db_path)
try:
self.sm.open_activate(self.imp_db_path)
print "Created empty fam tree successfully"
except:
print "Error opening the file."
print "Exiting..."
sys.exit(0)
for imp in self.imports:
print "Importing: file %s, format %s." % imp
self.cl_import(imp[0], imp[1])
def __open_action(self):
if self.open:
# Family Tree to open was given. Open it
# Then go on and process the rest of the command line arguments.
self.cl = bool(self.exports or self.actions)
# we load this file for use
try:
self.sm.open_activate(self.open)
print "Opened successfully!"
except:
print "Error opening the file."
print "Exiting..."
sys.exit(0)
def check_db(self, dbpath, force_unlock = False):
# Test if not locked or problematic
if force_unlock:
self.dbman.break_lock(dbpath)
if self.dbman.is_locked(dbpath):
print _("Database is locked, cannot open it!")
print _(" Info: %s") % find_locker_name(dbpath)
return False
if self.dbman.needs_recovery(dbpath):
print _("Database needs recovery, cannot open it!")
return False
return True
#-------------------------------------------------------------------------
#
# Import handler
#
#-------------------------------------------------------------------------
def cl_import(self, filename, format):
"""
Command-line import routine. Try to import filename using the format.
"""
pmgr = PluginManager.get_instance()
for plugin in pmgr.get_import_plugins():
if format == plugin.get_extension():
import_function = plugin.get_import_function()
import_function(self.dbstate.db, filename, None)
if not self.cl:
if self.imp_db_path:
return self.sm.open_activate(self.imp_db_path)
else:
return self.sm.open_activate(self.open)
#-------------------------------------------------------------------------
#
# Export handler
#
#-------------------------------------------------------------------------
def cl_export(self, filename, format):
"""
Command-line export routine.
Try to write into filename using the format.
"""
pmgr = PluginManager.get_instance()
for plugin in pmgr.get_export_plugins():
if format == plugin.get_extension():
export_function = plugin.get_export_function()
export_function(self.dbstate.db, filename)
#-------------------------------------------------------------------------
#
# Action handler
#
#-------------------------------------------------------------------------
def cl_action(self, action, options_str):
"""
Command-line action routine. Try to perform specified action.
"""
pmgr = PluginManager.get_instance()
if action == 'check':
import Check
checker = Check.CheckIntegrity(self.dbstate.db, None, None)
checker.check_for_broken_family_links()
checker.cleanup_missing_photos(1)
checker.check_parent_relationships()
checker.cleanup_empty_families(0)
errs = checker.build_report(1)
if errs:
checker.report(1)
elif action == 'summary':
import Summary
text = Summary.build_report(self.dbstate.db, None)
print text
elif action == "report":
try:
options_str_dict = dict( [ tuple(chunk.split('='))
for chunk in options_str.split(',') ] )
except:
options_str_dict = {}
print "Ignoring invalid options string."
name = options_str_dict.pop('name', None)
_cl_list = pmgr.get_cl_list()
if name:
for item in _cl_list:
if name == item[0]:
category = item[1]
report_class = item[2]
options_class = item[3]
if category in (CATEGORY_BOOK, CATEGORY_CODE):
options_class(self.dbstate.db, name, category,
options_str_dict)
else:
cl_report(self.dbstate.db, name, category, report_class,
options_class, options_str_dict)
return
# name exists, but is not in the list of valid report names
msg = "Unknown report name."
else:
msg = "Report name not given. Please use -p name=reportname."
print "%s\n Available names are:" % msg
for item in _cl_list:
# Print cli report name ([item[0]) and GUI report name (item[4])
if len(item[0]) <= 25:
print " %s%s- %s" % (item[0],
" " * (26 - len(item[0])), item[4])
else:
print " %s\t- %s" % (item[0], item[4])
elif action == "tool":
try:
options_str_dict = dict( [ tuple(chunk.split('=')) for
chunk in options_str.split(',') ] )
except:
options_str_dict = {}
print "Ignoring invalid options string."
name = options_str_dict.pop('name', None)
_cli_tool_list = pmgr.get_cl_tool_list()
if name:
for item in _cli_tool_list:
if name == item[0]:
category = item[1]
tool_class = item[2]
options_class = item[3]
Tool.cli_tool(self.dbstate, name, category, tool_class,
options_class, options_str_dict)
return
msg = "Unknown tool name."
else:
msg = "Tool name not given. Please use -p name=toolname."
print "%s\n Available names are:" % msg
for item in _cli_tool_list:
print " %s" % item[0]
else:
print "Unknown action: %s." % action
sys.exit(0)

256
src/cli/argparser.py Normal file
View File

@@ -0,0 +1,256 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham, A. Roitman
# Copyright (C) 2007-2009 B. Malengier
# Copyright (C) 2008 Lukasz Rymarczyk
# Copyright (C) 2008 Raphael Ackermann
# Copyright (C) 2008 Brian G. Matherly
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: ArgHandler.py 12559 2009-05-21 17:19:50Z gbritton $
"""
Module responsible for handling the command line arguments for GRAMPS.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import os
import sys
import getopt
from gettext import gettext as _
import logging
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
import const
# Note: Make sure to edit const.py POPT_TABLE too!
_HELP = _("""
Usage: gramps.py [OPTION...]
--load-modules=MODULE1,MODULE2,... Dynamic modules to load
Help options
-?, --help Show this help message
--usage Display brief usage message
Application options
-O, --open=FAMILY_TREE Open family tree
-i, --import=FILENAME Import file
-e, --export=FILENAME Export file
-f, --format=FORMAT Specify format
-a, --action=ACTION Specify action
-p, --options=OPTIONS_STRING Specify options
-d, --debug=LOGGER_NAME Enable debug logs
-l List Family Trees
-L List Family Trees in Detail
-u, --force-unlock Force unlock of family tree
""")
#-------------------------------------------------------------------------
# ArgParser
#-------------------------------------------------------------------------
class ArgParser(object):
"""
This class is responsible for parsing the command line arguments (if any)
given to gramps, and determining if a GUI or a CLI session must be started.
The valid arguments are:
Possible:
1/ FAMTREE : Just the family tree (name or database dir)
2/ -O, --open=FAMTREE, Open of a family tree
3/ -i, --import=FILE, Import of any format understood by an importer, optionally
provide- f to indicate format
4/ -e, --export=FILE, export a family tree in required format, optionally provide
-f to indicate format
5/ -f, --format=FORMAT : format after a -i or -e option
5/ -a, --action: An action (possible: 'check', 'summary', 'report',
'tool')
6/ -u, --force-unlock: A locked database can be unlocked by giving this
argument when opening it
If the filename (no flags) is specified, the interactive session is
launched using data from filename.
In this mode (filename, no flags), the rest of the arguments is ignored.
This is a mode suitable by default for GUI launchers, mime type handlers,
and the like
If no filename or -i option is given, a new interactive session (empty
database) is launched, since no data is given anyway.
If -O or -i option is given, but no -e or -a options are given, an
interactive session is launched with the FILE (specified with -i).
If both input (-O or -i) and processing (-e or -a) options are given,
interactive session will not be launched.
"""
def __init__(self, args):
"""
pass the command line arguments on creation
"""
self.args = args
self.open_gui = None
self.open = None
self.exports = []
self.actions = []
self.imports = []
self.imp_db_path = None
self.list = False
self.list_more = False
self.help = False
self.force_unlock = False
self.errors = []
self.parse_args()
#-------------------------------------------------------------------------
# Argument parser: sorts out given arguments
#-------------------------------------------------------------------------
def parse_args(self):
"""
Fill in lists with open, exports, imports, and actions options.
Any errors are added to self.errors
Possible:
1/ Just the family tree (name or database dir)
2/ -O, Open of a family tree
3/ -i, Import of any format understood by an importer, optionally
provide-f to indicate format
4/ -e, export a family tree in required format, optionally provide
-f to indicate format
5/ -a, --action: An action (possible: 'check', 'summary', 'report',
'tool')
6/ -u, --force-unlock: A locked database can be unlocked by giving this
argument when opening it
"""
try:
options, leftargs = getopt.getopt(self.args[1:],
const.SHORTOPTS, const.LONGOPTS)
except getopt.GetoptError, msg:
self.errors += [(_('Error parsing the arguments'),
str(msg) + '\n' +
_("Error parsing the arguments: %s \n"
"Type gramps --help for an overview of commands, or "
"read the manual pages.") % self.args[1:])]
return
if leftargs:
# if there were an argument without option,
# use it as a file to open and return
self.open_gui = leftargs[0]
print "Trying to open: %s ..." % leftargs[0]
#see if force open is on
for opt_ix in range(len(options)):
option, value = options[opt_ix]
if option in ('-u', '--force-unlock'):
self.force_unlock = True
break
return
# Go over all given option and place them into appropriate lists
for opt_ix in range(len(options)):
option, value = options[opt_ix]
if option in ( '-O', '--open'):
self.open = value
elif option in ( '-i', '--import'):
format = None
if opt_ix < len(options) - 1 \
and options[opt_ix + 1][0] in ( '-f', '--format'):
format = options[opt_ix + 1][1]
self.imports.append((value, format))
elif option in ( '-e', '--export' ):
format = None
if opt_ix < len(options) - 1 \
and options[opt_ix + 1][0] in ( '-f', '--format'):
format = options[opt_ix + 1][1]
self.exports.append((value, format))
elif option in ( '-a', '--action' ):
action = value
if action not in ( 'check', 'summary', 'report', 'tool' ):
print "Unknown action: %s. Ignoring." % action
continue
options_str = ""
if opt_ix < len(options)-1 \
and options[opt_ix+1][0] in ( '-p', '--options' ):
options_str = options[opt_ix+1][1]
self.actions.append((action, options_str))
elif option in ('-d', '--debug'):
logger = logging.getLogger(value)
logger.setLevel(logging.DEBUG)
elif option in ('-l',):
self.list = True
elif option in ('-L',):
self.list_more = True
elif option in ('-h', '-?', '--help'):
self.help = True
elif option in ('-u', '--force-unlock'):
self.force_unlock = True
if len(options) > 0 and self.open is None and self.imports == [] \
and not (self.list or self.list_more or self.help):
self.errors += [(_('Error parsing the arguments'),
_("Error parsing the arguments: %s \n"
"To use in the command-line mode," \
"supply at least one input file to process.") % self.args[1:])]
#-------------------------------------------------------------------------
# Determine the need for GUI
#-------------------------------------------------------------------------
def need_gui(self):
"""
Determine whether we need a GUI session for the given tasks.
"""
if self.errors:
#errors in argument parsing ==> give cli error, no gui needed
return False
if self.list or self.list_more or self.help:
return False
if self.open_gui:
# No-option argument, definitely GUI
return True
# If we have data to work with:
if (self.open or self.imports):
if (self.exports or self.actions):
# have both data and what to do with it => no GUI
return False
else:
# data given, but no action/export => GUI
return True
# No data, can only do GUI here
return True
def print_help(self):
if self.help:
print _HELP
sys.exit(0)

380
src/cli/clidbman.py Normal file
View File

@@ -0,0 +1,380 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2009 Brian G. Matherly
# Copyright (C) 2009 Gary Burton
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: DbManager.py 12621 2009-06-03 18:39:24Z ldnp $
"""
Provide the management of databases from CLI. This includes opening, renaming,
creating, and deleting of databases.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import os
import time
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# set up logging
#
#-------------------------------------------------------------------------
import logging
LOG = logging.getLogger(".clidbman")
from gtk import STOCK_DIALOG_ERROR, STOCK_OPEN
#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
import gen.db
from gen.plug import PluginManager
import Config
#-------------------------------------------------------------------------
#
# constants
#
#-------------------------------------------------------------------------
DEFAULT_TITLE = _("Family Tree")
NAME_FILE = "name.txt"
META_NAME = "meta_data.db"
#-------------------------------------------------------------------------
#
# CLIDbManager
#
#-------------------------------------------------------------------------
class CLIDbManager(object):
"""
Database manager without GTK functionality, allows users to create and
open databases
"""
def __init__(self, dbstate):
self.dbstate = dbstate
self.msg = None
if dbstate:
self.active = dbstate.db.get_save_path()
else:
self.active = None
self.current_names = []
self._populate_cli()
def empty(self, val):
"""Callback that does nothing
"""
pass
def get_dbdir_summary(self, file_name):
"""
Returns (people_count, version_number) of current DB.
Returns ("Unknown", "Unknown") if invalid DB or other error.
"""
from bsddb import dbshelve, db
from gen.db import META, PERSON_TBL
env = db.DBEnv()
flags = db.DB_CREATE | db.DB_PRIVATE |\
db.DB_INIT_MPOOL | db.DB_INIT_LOCK |\
db.DB_INIT_LOG | db.DB_INIT_TXN | db.DB_THREAD
try:
env.open(file_name, flags)
except:
return "Unknown", "Unknown"
dbmap1 = dbshelve.DBShelf(env)
fname = os.path.join(file_name, META + ".db")
try:
dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY)
except:
return "Unknown", "Unknown"
version = dbmap1.get('version', default=None)
dbmap1.close()
dbmap2 = dbshelve.DBShelf(env)
fname = os.path.join(file_name, PERSON_TBL + ".db")
try:
dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY)
except:
env.close()
return "Unknown", "Unknown"
count = len(dbmap2)
dbmap2.close()
env.close()
return (count, version)
def family_tree_summary(self):
"""
Return a list of dictionaries of the known family trees.
"""
# make the default directory if it does not exist
list = []
for item in self.current_names:
(name, dirpath, path_name, last,
tval, enable, stock_id) = item
count, version = self.get_dbdir_summary(dirpath)
retval = {}
retval["Number of people"] = count
if enable:
retval["Locked?"] = "yes"
else:
retval["Locked?"] = "no"
retval["DB version"] = version
retval["Family tree"] = name
retval["Path"] = dirpath
retval["Last accessed"] = time.strftime('%x %X',
time.localtime(tval))
list.append( retval )
return list
def _populate_cli(self):
""" Get the list of current names in the database dir
"""
# make the default directory if it does not exist
dbdir = os.path.expanduser(Config.get(Config.DATABASE_PATH))
make_dbdir(dbdir)
self.current_names = []
for dpath in os.listdir(dbdir):
dirpath = os.path.join(dbdir, dpath)
path_name = os.path.join(dirpath, NAME_FILE)
if os.path.isfile(path_name):
name = file(path_name).readline().strip()
(tval, last) = time_val(dirpath)
(enable, stock_id) = icon_values(dirpath, self.active,
self.dbstate.db.is_open())
if (stock_id == 'gramps-lock'):
last = find_locker_name(dirpath)
self.current_names.append(
(name, os.path.join(dbdir, dpath), path_name,
last, tval, enable, stock_id))
self.current_names.sort()
def get_family_tree_path(self, name):
"""
Given a name, return None if name not existing or the path to the
database if it is a known database name.
"""
for data in self.current_names:
if data[0] == name:
return data[1]
return None
def family_tree_list(self):
"""Return a list of name, dirname of the known family trees
"""
lst = [(x[0], x[1]) for x in self.current_names]
return lst
def __start_cursor(self, msg):
"""
Do needed things to start import visually, eg busy cursor
"""
print _('Starting Import, %s') % msg
def __end_cursor(self):
"""
Set end of a busy cursor
"""
print _('Import finished...')
def _create_new_db_cli(self, title=None):
"""
Create a new database.
"""
new_path = find_next_db_dir()
os.mkdir(new_path)
path_name = os.path.join(new_path, NAME_FILE)
if title is None:
name_list = [ name[0] for name in self.current_names ]
title = find_next_db_name(name_list)
name_file = open(path_name, "w")
name_file.write(title)
name_file.close()
# write the version number into metadata
newdb = gen.db.GrampsDBDir()
newdb.write_version(new_path)
(tval, last) = time_val(new_path)
self.current_names.append((title, new_path, path_name,
last, tval, False, ""))
return new_path, title
def _create_new_db(self, title=None):
"""
Create a new database, do extra stuff needed
"""
return self._create_new_db_cli(title)
def import_new_db(self, filename, callback):
"""
Attempt to import the provided file into a new database.
A new database will only be created if an appropriate importer was
found.
@return: A tuple of (new_path, name) for the new database
or (None, None) if no import was performed.
"""
pmgr = PluginManager.get_instance()
(name, ext) = os.path.splitext(os.path.basename(filename))
format = ext[1:].lower()
for plugin in pmgr.get_import_plugins():
if format == plugin.get_extension():
new_path, name = self._create_new_db(name)
# Create a new database
self.__start_cursor(_("Importing data..."))
dbclass = gen.db.GrampsDBDir
dbase = dbclass()
dbase.load(new_path, callback)
import_function = plugin.get_import_function()
import_function(dbase, filename, callback)
# finish up
self.__end_cursor()
dbase.close()
return new_path, name
return None, None
def is_locked(self, dbpath):
"""
returns True if there is a lock file in the dirpath
"""
if os.path.isfile(os.path.join(dbpath,"lock")):
return True
return False
def needs_recovery(self, dbpath):
"""
returns True if the database in dirpath needs recovery
"""
if os.path.isfile(os.path.join(dbpath,"need_recover")):
return True
return False
def break_lock(self, dbpath):
"""
Breaks the lock on a database
"""
if os.path.exists(os.path.join(dbpath, "lock")):
os.unlink(os.path.join(dbpath, "lock"))
def make_dbdir(dbdir):
"""
Create the default database directory, as defined by dbdir
"""
try:
if not os.path.isdir(dbdir):
os.makedirs(dbdir)
except (IOError, OSError), msg:
LOG.error(_("Could not make database directory: ") + str(msg))
def find_next_db_name(name_list):
"""
Scan the name list, looking for names that do not yet exist.
Use the DEFAULT_TITLE as the basis for the database name.
"""
i = 1
while True:
title = "%s %d" % (DEFAULT_TITLE, i)
if title not in name_list:
return title
i += 1
def find_next_db_dir():
"""
Searches the default directory for the first available default
database name. Base the name off the current time. In all actuality,
the first should be valid.
"""
while True:
base = "%x" % int(time.time())
dbdir = os.path.expanduser(Config.get(Config.DATABASE_PATH))
new_path = os.path.join(dbdir, base)
if not os.path.isdir(new_path):
break
return new_path
def time_val(dirpath):
"""
Return the last modified time of the database. We do this by looking
at the modification time of the meta db file. If this file does not
exist, we indicate that database as never modified.
"""
meta = os.path.join(dirpath, META_NAME)
if os.path.isfile(meta):
tval = os.stat(meta)[9]
last = time.strftime('%x %X', time.localtime(tval))
else:
tval = 0
last = _("Never")
return (tval, last)
def icon_values(dirpath, active, is_open):
"""
If the directory path is the active path, then return values
that indicate to use the icon, and which icon to use.
"""
if os.path.isfile(os.path.join(dirpath,"need_recover")):
return (True, STOCK_DIALOG_ERROR)
elif dirpath == active and is_open:
return (True, STOCK_OPEN)
elif os.path.isfile(os.path.join(dirpath,"lock")):
return (True, 'gramps-lock')
else:
return (False, "")
def find_locker_name(dirpath):
"""
Opens the lock file if it exists, reads the contexts which is "USERNAME"
and returns the contents, with correct string before "USERNAME",
so the message can be printed with correct locale.
If a file is encountered with errors, we return 'Unknown'
This data can eg be displayed in the time column of the manager
"""
try:
fname = os.path.join(dirpath, "lock")
ifile = open(fname)
username = ifile.read().strip()
last = _("Locked by %s") % username
ifile.close()
except (OSError, IOError):
last = _("Unknown")
return last

284
src/cli/grampscli.py Normal file
View File

@@ -0,0 +1,284 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2001-2006 Donald N. Allingham
# Copyright (C) 2009 Benny Malengier
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id:gramps_main.py 9912 2008-01-22 09:17:46Z acraphae $
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
from gettext import gettext as _
import os
import sys
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from BasicUtils import name_displayer
import Config
import const
import Errors
import DbState
from gen.db import GrampsDBDir
from gen.plug import PluginManager
import GrampsCfg
import RecentFiles
#-------------------------------------------------------------------------
#
# CLI DbLoader class
#
#-------------------------------------------------------------------------
class CLIDbLoader(object):
def __init__(self, dbstate):
self.dbstate = dbstate
def _warn(title, warnmessage):
print _('WARNING: %s') %warnmessage
def _errordialog(title, errormessage):
"""
Show the error. A title for the error and an errormessage
"""
print _('ERROR: %s') % errormessage
sys.exit(1)
def _dberrordialog(self, msg):
self._errordialog( '', _("Low level database corruption detected")
+ '\n' +
_("GRAMPS has detected a problem in the underlying "
"Berkeley database. This can be repaired by from "
"the Family Tree Manager. Select the database and "
'click on the Repair button') + '\n\n' + str(msg))
def _begin_progress(self):
pass
def _pulse_progress(self, value):
pass
def read_file(self, filename):
"""
This method takes care of changing database, and loading the data.
In 3.0 we only allow reading of real databases of filetype
'x-directory/normal'
This method should only return on success.
Returning on failure makes no sense, because we cannot recover,
since database has already beeen changed.
Therefore, any errors should raise exceptions.
On success, return with the disabled signals. The post-load routine
should enable signals, as well as finish up with other UI goodies.
"""
if os.path.exists(filename):
if not os.access(filename, os.W_OK):
mode = "r"
self._warn(_('Read only database'),
_('You do not have write access '
'to the selected file.'))
else:
mode = "w"
else:
mode = 'w'
dbclass = GrampsDBDir
self.dbstate.change_database(dbclass())
self.dbstate.db.disable_signals()
self._begin_progress()
try:
self.dbstate.db.load(filename, self._pulse_progress, mode)
self.dbstate.db.set_save_path(filename)
except gen.db.FileVersionDeclineToUpgrade:
self.dbstate.no_database()
except gen.db.exceptions.FileVersionError, msg:
self.dbstate.no_database()
self._errordialog( _("Cannot open database"), str(msg))
except OSError, msg:
self.dbstate.no_database()
self._errordialog(
_("Could not open file: %s") % filename, str(msg))
except Errors.DbError, msg:
self.dbstate.no_database()
self._dberrordialog(msg)
except Exception:
self.dbstate.no_database()
_LOG.error("Failed to open database.", exc_info=True)
return True
#-------------------------------------------------------------------------
#
# CLIManager class
#
#-------------------------------------------------------------------------
class CLIManager(object):
"""
A reduced viewmanager suitable for cli actions.
Aim is to manage a database on which to work
"""
def __init__(self, dbstate, setloader):
self.dbstate = dbstate
if setloader:
self.db_loader = CLIDbLoader(self.dbstate)
else:
self.db_loader = None
self.file_loaded = False
self._pmgr = PluginManager.get_instance()
def open_activate(self, path):
"""
Open and make a family tree active
"""
self._read_recent_file(path)
def _errordialog(title, errormessage):
"""
Show the error. A title for the error and an errormessage
"""
print _('ERROR: %s') % errormessage
sys.exit(1)
def _read_recent_file(self, filename):
"""
Called when a file needs to be loaded
"""
# A recent database should already have a directory
# If not, do nothing, just return. This can be handled better if family tree
# delete/rename also updated the recent file menu info in DisplayState.py
if not os.path.isdir(filename):
self.errordialog(
_("Could not load a recent Family Tree."),
_("Family Tree does not exist, as it has been deleted."))
return
if self.db_loader.read_file(filename):
# Attempt to figure out the database title
path = os.path.join(filename, "name.txt")
try:
ifile = open(path)
title = ifile.readline().strip()
ifile.close()
except:
title = filename
self._post_load_newdb(filename, 'x-directory/normal', title)
def _post_load_newdb(self, filename, filetype, title=None):
"""
The method called after load of a new database.
Here only CLI stuff is done, inherit this method to add extra stuff
"""
self._post_load_newdb_nongui(filename, filetype, title)
def _post_load_newdb_nongui(self, filename, filetype, title=None):
"""
Called after a new database is loaded.
"""
if not filename:
return
if filename[-1] == os.path.sep:
filename = filename[:-1]
name = os.path.basename(filename)
if title:
name = title
# This method is for UI stuff when the database has changed.
# Window title, recent files, etc related to new file.
self.dbstate.db.set_save_path(filename)
# apply preferred researcher if loaded file has none
res = self.dbstate.db.get_researcher()
owner = GrampsCfg.get_researcher()
if res.get_name() == "" and owner.get_name() != "":
self.dbstate.db.set_researcher(owner)
name_displayer.set_name_format(self.dbstate.db.name_formats)
fmt_default = Config.get(Config.NAME_FORMAT)
if fmt_default < 0:
name_displayer.set_default_format(fmt_default)
self.dbstate.db.enable_signals()
self.dbstate.signal_change()
Config.set(Config.RECENT_FILE, filename)
try:
self.dbstate.change_active_person(self.dbstate.db.find_initial_person())
except:
pass
RecentFiles.recent_files(filename, name)
self.file_loaded = True
def do_load_plugins(self):
"""
Loads the plugins at initialization time. The plugin status window is
opened on an error if the user has requested.
"""
# load plugins
error = self._pmgr.load_plugins(const.PLUGINS_DIR)
error |= self._pmgr.load_plugins(const.USER_PLUGINS)
return error
def startcli(errors, argparser):
"""
Starts a cli session of GRAMPS.
errors : errors already encountered
argparser : ArgParser instance
"""
if errors:
#already errors encountered. Show first one on terminal and exit
print _('Error encountered: %s') % errors[0][0]
print _(' Details: %s') % errors[0][1]
sys.exit(1)
if argparser.errors:
print _('Error encountered in argument parsing: %s') \
% argparser.errors[0][0]
print _(' Details: %s') % argparser.errors[0][1]
sys.exit(1)
#we need to keep track of the db state
dbstate = DbState.DbState()
#we need a manager for the CLI session
climanager = CLIManager(dbstate, True)
#load the plugins
climanager.do_load_plugins()
# handle the arguments
from arghandler import ArgHandler
handler = ArgHandler(dbstate, argparser, climanager)
# create a manager to manage the database
handler.handle_args_cli(climanager)
sys.exit(0)