From 5d654d402a716030dbb0e0716d100b9aa6ad5cdb Mon Sep 17 00:00:00 2001 From: belissent Date: Wed, 19 Aug 2015 16:26:59 +0200 Subject: [PATCH] Environment variables in mediapath The mediapath could now be a relative path from database location, or use variables ($GRAMPSHOME, $GRAMPS_RESOURCES, etc.) --- example/gramps/example.gramps | 2 +- gramps/gen/const.py | 87 +++++++++++++++++----------- gramps/gen/utils/file.py | 50 +++++++++++++--- gramps/gen/utils/test/file_test.py | 84 +++++++++++++++++++++++++++ gramps/gui/configure.py | 5 +- gramps/plugins/importer/importxml.py | 42 ++------------ 6 files changed, 187 insertions(+), 83 deletions(-) create mode 100644 gramps/gen/utils/test/file_test.py diff --git a/example/gramps/example.gramps b/example/gramps/example.gramps index b2433a548..23fcd15e9 100644 --- a/example/gramps/example.gramps +++ b/example/gramps/example.gramps @@ -7,7 +7,7 @@ Alex Roitman,,, - /home/pierre/Gramps/master/example/gramps + $GRAMPS_RESOURCES/example/gramps diff --git a/gramps/gen/const.py b/gramps/gen/const.py index 66e954a4a..fcb8d3bc1 100644 --- a/gramps/gen/const.py +++ b/gramps/gen/const.py @@ -11,7 +11,7 @@ # 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, +# 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. @@ -85,16 +85,16 @@ APP_VCARD = ["text/x-vcard", "text/x-vcalendar"] # #------------------------------------------------------------------------- if 'GRAMPSHOME' in os.environ: - USER_HOME = get_env_var('GRAMPSHOME') + USER_HOME = get_env_var('GRAMPSHOME') HOME_DIR = os.path.join(USER_HOME, 'gramps') elif 'USERPROFILE' in os.environ: - USER_HOME = get_env_var('USERPROFILE') + USER_HOME = get_env_var('USERPROFILE') if 'APPDATA' in os.environ: HOME_DIR = os.path.join(get_env_var('APPDATA'), 'gramps') else: HOME_DIR = os.path.join(USER_HOME, 'gramps') else: - USER_HOME = get_env_var('HOME') + USER_HOME = get_env_var('HOME') HOME_DIR = os.path.join(USER_HOME, '.gramps') @@ -115,9 +115,26 @@ USER_PLUGINS = os.path.join(VERSION_DIR, "plugins") USER_DIRLIST = (USER_HOME, HOME_DIR, VERSION_DIR, ENV_DIR, TEMP_DIR, THUMB_DIR, THUMB_NORMAL, THUMB_LARGE, USER_PLUGINS) + +# Update environment +# This could be of general use, for example when using os.path.expandvars +os.environ['GRAMPS_VERSION'] = VERSION +os.environ['GRAMPS_VERSION_MAJOR'] = major_version +os.environ['GRAMPS_VERSION_DIR'] = VERSION_DIR +os.environ['GRAMPS_ENV_DIR'] = ENV_DIR +os.environ['GRAMPS_TEMP_DIR'] = TEMP_DIR +os.environ['GRAMPS_THUMB_DIR'] = THUMB_DIR +os.environ['GRAMPS_THUMB_NORMAL'] = THUMB_NORMAL +os.environ['GRAMPS_THUMB_LARGE'] = THUMB_LARGE +os.environ['GRAMPS_USER_PLUGINS'] = USER_PLUGINS +# Attention: $GRAMPSHOME should NOT be set, because it redefines the HOME_DIR behavior +# This leads to bugs, especially when a test calls GRAMPS again: +# the HOME_DIR is then re-computed with a wrong $GRAMPSHOME + + #------------------------------------------------------------------------- # -# Paths to python modules - assumes that the root directory is one level +# Paths to python modules - assumes that the root directory is one level # above this one, and that the plugins directory is below the root directory. # #------------------------------------------------------------------------- @@ -192,21 +209,21 @@ COMMENTS = _("Gramps\n (Genealogical Research and Analysis " "is a personal genealogy program.") AUTHORS = [ "Alexander Roitman", - "Benny Malengier", + "Benny Malengier", "Brian Matherly", - "Donald A. Peterson", - "Donald N. Allingham", - "David Hampton", + "Donald A. Peterson", + "Donald N. Allingham", + "David Hampton", "Martin Hawlisch", - "Richard Taylor", + "Richard Taylor", "Tim Waugh", "John Ralls" ] - + AUTHORS_FILE = os.path.join(DATA_DIR, "authors.xml") DOCUMENTERS = [ - 'Alexander Roitman', + 'Alexander Roitman', ] #------------------------------------------------------------------------- @@ -232,14 +249,14 @@ ARABIC_SEMICOLON = "؛" # (longName, shortName, type , default, flags, descrip , argDescrip) POPT_TABLE = [ ("config", 'c', str, None, 0, "Set config setting(s) and start Gramps", ""), - ("open", 'O', str, None, 0, "Open family tree", "FAMILY_TREE"), - ("create", 'C', str, None, 0, "Create or Open family tree", "FAMILY_TREE"), - ("import", 'i', str, None, 0, "Import file", "FILENAME"), + ("open", 'O', str, None, 0, "Open family tree", "FAMILY_TREE"), + ("create", 'C', str, None, 0, "Create or Open family tree", "FAMILY_TREE"), + ("import", 'i', str, None, 0, "Import file", "FILENAME"), ("export", 'e', str, None, 0, "Export file", "FILENAME"), - ("format", 'f', str, None, 0, 'Specify format', "FORMAT"), - ("action", 'a', str, None, 0, 'Specify action', "ACTION"), - ("options", 'p', str, None, 0, 'Specify options', "OPTIONS_STRING"), - ("debug", 'd', str, None, 0, 'Enable debug logs', "LOGGER_NAME"), + ("format", 'f', str, None, 0, 'Specify format', "FORMAT"), + ("action", 'a', str, None, 0, 'Specify action', "ACTION"), + ("options", 'p', str, None, 0, 'Specify options', "OPTIONS_STRING"), + ("debug", 'd', str, None, 0, 'Enable debug logs', "LOGGER_NAME"), ("", 'l', None, None, 0, 'List Family Trees', ""), ("", 'L', None, None, 0, 'List Family Tree Details', ""), ("show", 's', None, None, 0, "Show config settings", ""), @@ -248,42 +265,42 @@ POPT_TABLE = [ ] LONGOPTS = [ - "action=", + "action=", "class=", "config=", "debug=", "display=", - "disable-sound", - "disable-crash-dialog", + "disable-sound", + "disable-crash-dialog", "enable-sound", "espeaker=", "export=", "force-unlock", "format=", - "gdk-debug=", - "gdk-no-debug=", - "gtk-debug=", - "gtk-no-debug=", - "gtk-module=", + "gdk-debug=", + "gdk-no-debug=", + "gtk-debug=", + "gtk-no-debug=", + "gtk-module=", "g-fatal-warnings", "help", - "import=", + "import=", "load-modules=", - "list" + "list" "name=", - "oaf-activate-iid=", - "oaf-ior-fd=", + "oaf-activate-iid=", + "oaf-ior-fd=", "oaf-private", "open=", "create=", "options=", "screen=", - "show", - "sm-client-id=", - "sm-config-prefix=", + "show", + "sm-client-id=", + "sm-config-prefix=", "sm-disable", "sync", - "usage", + "usage", "version", "qml", "yes", diff --git a/gramps/gen/utils/file.py b/gramps/gen/utils/file.py index 6d36d466d..f2ecb64ac 100644 --- a/gramps/gen/utils/file.py +++ b/gramps/gen/utils/file.py @@ -10,7 +10,7 @@ # 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, +# 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. @@ -91,7 +91,7 @@ def get_empty_tempdir(dirname): """ Return path to TEMP_DIR/dirname, a guaranteed empty directory makes intervening directories if required - fails if _file_ by that name already exists, + fails if _file_ by that name already exists, or for inadequate permissions to delete dir/files or create dir(s) """ @@ -121,10 +121,10 @@ def relative_path(original, base): return original original = os.path.normpath(original) base = os.path.normpath(base) - + # If the db_dir and obj_dir are on different drives (win only) # then there cannot be a relative path. Return original obj_path - (base_drive, base) = os.path.splitdrive(base) + (base_drive, base) = os.path.splitdrive(base) (orig_drive, orig_name) = os.path.splitdrive(original) if base_drive.upper() != orig_drive.upper(): return original @@ -133,7 +133,7 @@ def relative_path(original, base): # shared by base and target. base_list = (base).split(os.sep) target_list = (orig_name).split(os.sep) - # make sure '/home/person' and 'c:/home/person' both give + # make sure '/home/person' and 'c:/home/person' both give # list ['home', 'person'] base_list = [_f for _f in base_list if _f] target_list = [_f for _f in target_list if _f] @@ -146,14 +146,48 @@ def relative_path(original, base): rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:] return os.path.join(*rel_list) +def expanded_vars_path(path): + """ + Expand environment variables in a path + $GRAMPSHOME is set and restored afterwards, + because undefined $GRAMPSHOME has a special meaning (see const.py). + """ + if not 'GRAMPSHOME' in os.environ: + os.environ['GRAMPSHOME'] = USER_HOME + grampshome_added = True + path = os.path.expandvars(path) + if (grampshome_added): + del os.environ['GRAMPSHOME'] + return path + def media_path(db): """ Given a database, return the mediapath to use as basedir for media """ mpath = db.get_mediapath() + return norm_media_path(mpath, db) + +def norm_media_path(mpath, db): + """ + Normalize a mediapath: + - Relative mediapath are considered as relative to the database + - Expand variables ($GRAMPSHOME, $GRAMPS_RESOURCES, etc.) + - Convert to absolute path + - Convert slashes and case (on Windows) + """ + # Use home dir if no media_path specified if mpath is None: - #use home dir - mpath = USER_HOME + mpath = os.path.abspath(USER_HOME) + # Expand environment variables + mpath = expanded_vars_path(mpath) + # Relative mediapath are considered as relative to the database + if not os.path.isabs(mpath): + basepath = db.get_save_path() + if not basepath: + basepath = USER_HOME + mpath = os.path.join(os.path.abspath(basepath), mpath) + # Normalize path + mpath = os.path.normcase(os.path.normpath(os.path.abspath(mpath))) return mpath def media_path_full(db, filename): @@ -178,7 +212,7 @@ def search_for(name): return 1 if os.access(name, os.X_OK) and not os.path.isdir(name): return 1 - else: + else: for i in os.environ['PATH'].split(':'): #not win() fname = os.path.join(i, name) if os.access(fname, os.X_OK) and not os.path.isdir(fname): diff --git a/gramps/gen/utils/test/file_test.py b/gramps/gen/utils/test/file_test.py new file mode 100644 index 000000000..d6812b675 --- /dev/null +++ b/gramps/gen/utils/test/file_test.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007-2009 B. Malengier +# Copyright (C) 2009 Swoon on bug tracker +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import os +import shutil +import unittest + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import TEMP_DIR, USER_HOME, USER_PLUGINS +from gramps.gen.constfunc import get_env_var +from gramps.gen.utils.file import media_path, get_empty_tempdir +from gramps.gen.dbstate import DbState +from gramps.version import VERSION + +#------------------------------------------------------------------------- +# +# FileTest class +# +#------------------------------------------------------------------------- +class FileTest(unittest.TestCase): + + def test_mediapath(self): + # Create database + dbstate = DbState() + db = dbstate.make_database("bsddb") + path = get_empty_tempdir("utils_file_test") + db.write_version(path) + db.load(path) + dbstate.change_database(db) + # Test without db.mediapath set + self.assertEqual(media_path(db), os.path.normcase(os.path.normpath(os.path.abspath(USER_HOME)))) + self.assertTrue(os.path.exists(media_path(db))) + # Test with absolute db.mediapath + db.set_mediapath(os.path.abspath(USER_HOME) + "/test_abs") + self.assertEqual(media_path(db), os.path.normcase(os.path.normpath(os.path.abspath(USER_HOME + "/test_abs")))) + # Test with relative db.mediapath + db.set_mediapath("test_rel") + self.assertEqual(media_path(db), os.path.normcase(os.path.normpath(os.path.abspath(TEMP_DIR + "/utils_file_test/test_rel")))) + # Test with environment variable + db.set_mediapath("$GRAMPSHOME/test_var") + self.assertEqual(media_path(db), os.path.normcase(os.path.normpath(os.path.abspath(USER_HOME + "/test_var")))) + db.set_mediapath("/test/$GRAMPS_VERSION/test_var") + self.assertEqual(media_path(db), os.path.normcase(os.path.normpath(os.path.abspath("/test/" + VERSION + "/test_var")))) + db.set_mediapath("${GRAMPS_USER_PLUGINS}/test_var") + self.assertEqual(media_path(db), os.path.normcase(os.path.normpath(os.path.abspath(USER_PLUGINS + "/test_var")))) + + +#------------------------------------------------------------------------- +# +# main +# +#------------------------------------------------------------------------- + +if __name__ == "__main__": + unittest.main() diff --git a/gramps/gui/configure.py b/gramps/gui/configure.py index 0a71b6ffe..e56d02f8f 100644 --- a/gramps/gui/configure.py +++ b/gramps/gui/configure.py @@ -53,6 +53,7 @@ from gramps.gen.datehandler import get_date_formats from gramps.gen.display.name import displayer as _nd from gramps.gen.display.name import NameDisplayError from gramps.gen.utils.alive import update_constants +from gramps.gen.utils.file import media_path from gramps.gen.utils.keyword import (get_keywords, get_translation_from_keyword, get_translations, get_keyword_from_translation) from gramps.gen.lib import Date, FamilyRelType @@ -1460,9 +1461,7 @@ class GrampsPreferences(ConfigureDialog): _('_Apply'), Gtk.ResponseType.OK) ) - mpath = self.dbstate.db.get_mediapath() - if not mpath: - mpath = HOME_DIR + mpath = media_path(self.dbstate.db) f.set_current_folder(os.path.dirname(mpath)) status = f.run() diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index f76a37246..3802f0100 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -62,7 +62,7 @@ from gramps.gen.errors import GrampsImportError from gramps.gen.utils.id import create_id from gramps.gen.utils.db import family_name from gramps.gen.utils.unknown import make_unknown, create_explanation_note -from gramps.gen.utils.file import create_checksum +from gramps.gen.utils.file import create_checksum, media_path, norm_media_path from gramps.gen.datehandler import parser, set_date from gramps.gen.display.name import displayer as name_displayer from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, @@ -165,35 +165,6 @@ def importData(database, filename, user): database.readonly = read_only return info -## TODO - WITH MEDIA PATH, IS THIS STILL NEEDED? -## BETTER LEAVE ALL RELATIVE TO NEW RELATIVE PATH -## save_path is in .gramps/dbbase, no good place ! -## # copy all local images into .images directory -## db_dir = os.path.abspath(os.path.dirname(database.get_save_path())) -## db_base = os.path.basename(database.get_save_path()) -## img_dir = os.path.join(db_dir, db_base) -## first = not os.path.exists(img_dir) -## -## for m_id in database.get_media_object_handles(): -## mobject = database.get_object_from_handle(m_id) -## oldfile = mobject.get_path() -## if oldfile and not os.path.isabs(oldfile): -## if first: -## os.mkdir(img_dir) -## first = 0 -## newfile = os.path.join(img_dir, oldfile) -## -## try: -## oldfilename = os.path.join(basefile, oldfile) -## shutil.copyfile(oldfilename, newfile) -## try: -## shutil.copystat(oldfilename, newfile) -## except: -## pass -## mobject.set_path(newfile) -## database.commit_media_object(mobject, None, change) -## except (IOError, OSError), msg: -## ErrorDialog(_('Could not copy file'), str(msg)) #------------------------------------------------------------------------- # @@ -965,14 +936,13 @@ class GrampsParser(UpdateCallback): person = self.db.get_person_from_handle(self.home) self.db.set_default_person_handle(person.handle) - #set media path, this should really do some parsing to convert eg - # windows path to unix ? + # Set media path + # The paths are normalized before being compared. if self.mediapath: - oldpath = self.db.get_mediapath() - if not oldpath: + if not self.db.get_mediapath(): self.db.set_mediapath(self.mediapath) - elif not oldpath == self.mediapath: - self.user.notify_error(_("Could not change media path"), + elif not media_path(self.db) == norm_media_path(self.mediapath, self.db): + self.user.notify_error(_("Could not change media path"), _("The opened file has media path %s, which conflicts with" " the media path of the Family Tree you import into. " "The original media path has been retained. Copy the "