# HG changeset patch
# User Mike Pittaro <mikeyp@lhrc.com>
# Date 1385560494 28800
# Node ID 8681bbcdbd4485e5e8c3b20694465a5a4fcc7425
# Parent  a9e808e15821105f27bac81dedff7ed469f309a3
Feature #1047: Chirp support for Python virtualenv's

This change adds support for virtualenv's to Chirp.

The bulk of the changes involve updating the platform classes to reliably
determine the location of the runtime directories for locales, pixmaps,
xml schemas, and the stock configs. The other changes are updates to use the
new platform class functions, and to remove some absolute paths.

Technically, these changes aren't virtualenv specific - they just make
Chirp more 'location aware', so virtualenv's can be used.

Change summary:

chirp/platform.py

    1. adds five new functions to return runtime directories -  locale_dir(),
       share_dir(), stock_configs_dir(), pixmaps_dir(), schema_dir()

    2. updates the logic to determine the runtime locations of the runtime
       directories.

    3. adds tests for the new functions.

chirpui/mainapp.py

    1. updated to use the new platform functions for the stock_configs_dir
       and the pixmaps_dir.

chirp/generic_xml.py

    1. updated to use the new platform function for the schema_dir (xml schema files)

chirpw

    1. updated to use the #!/usr/bin/env idiom, so it picks up python from the path
       instead of a hard coded location.

setup.py

    1. change the rpttool path to relative instead of absolute.

diff -r a9e808e15821 -r 8681bbcdbd44 chirp/generic_xml.py
--- a/chirp/generic_xml.py	Tue Nov 26 19:10:37 2013 -0800
+++ b/chirp/generic_xml.py	Wed Nov 27 05:54:54 2013 -0800
@@ -20,11 +20,8 @@
 
 def validate_doc(doc):
     """Validate the document"""
-    basepath = platform.get_platform().executable_path()
-    path = os.path.abspath(os.path.join(basepath, "chirp.xsd"))
-    if not os.path.exists(path):
-        path = "/usr/share/chirp/chirp.xsd"         
 
+    path = os.path.join(platform.get_platform().schema_dir(), "chirp.xsd")
     try:
         ctx = libxml2.schemaNewParserCtxt(path)
         schema = ctx.schemaParse()
diff -r a9e808e15821 -r 8681bbcdbd44 chirp/platform.py
--- a/chirp/platform.py	Tue Nov 26 19:10:37 2013 -0800
+++ b/chirp/platform.py	Wed Nov 27 05:54:54 2013 -0800
@@ -1,4 +1,4 @@
-# Copyright 2008 Dan Smith <dsmith@danplanet.com>
+# Copyright 2008, 2013 Dan Smith <dsmith@danplanet.com>
 #
 # 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
@@ -13,6 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import errno
 import os
 import sys
 import glob
@@ -49,9 +50,6 @@
 except:
     comports = win32_comports_bruteforce
 
-def _find_me():
-    return sys.modules["chirp.platform"].__file__
-
 def natural_sorted(l):
     convert = lambda text: int(text) if text.isdigit() else text.lower()
     natural_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
@@ -63,6 +61,79 @@
     def __init__(self, basepath):
         self._base = basepath
         self._last_dir = self.default_dir()
+        self._prefix = None
+        self._locale_path = None
+        self._share_path = None
+        self._stock_configs_path = None
+        self._pixmaps_path = None
+        # check if we are packaged with PyToExe, PyToApp
+        self._frozen = hasattr(sys, "frozen") 
+        # check if we are we installed or running from a source tree 
+        self._running_from_source = \
+            "site-packages" not in os.path.abspath(__file__)
+
+    def init_dirs(self):        
+        # calculate the locations of runtime directories
+        # relative to our install location (self._prefix). 
+        # These are also different if we're running from a 
+        # source tree or we are packaged with PyToExe / PyToApp
+        # This function should be called by derived classes after 
+        # self._prefix is set
+
+        # TODO are these the same across platforms ?
+
+        if self._running_from_source:
+            # running from the source tree
+            self._share_path = os.path.abspath(os.path.join(
+                self._prefix, "share"))
+            self._locale_path = os.path.abspath(os.path.join(
+                self._prefix, "locale"))
+            self._stock_configs_path = os.path.abspath(os.path.join(
+                self._prefix, "stock_configs"))
+            self._pixmaps_path = self._share_path
+            self._schema_path = self._prefix
+        elif self._frozen: 
+            # PytoExe, PyToApp
+            self._share_path = os.path.abspath(os.path.join(
+                self._prefix, "share"))
+            self._locale_path = os.path.abspath(os.path.join(
+                self._prefix, "locale"))
+            self._stock_configs_path = os.path.abspath(os.path.join(
+                self._prefix, "stock_configs"))
+            self._pixmaps_path = self._share_path
+            self._schema_path = self._prefix
+        else: 
+            # python site-packages installation
+            self._share_path = os.path.abspath(os.path.join(
+                self._prefix, "share", "chirp"))
+            self._locale_path = os.path.abspath(os.path.join(
+                self._share_path, "locale"))
+            self._stock_configs_path = os.path.abspath(os.path.join(
+                self._share_path, "stock_configs"))
+            self._pixmaps_path = os.path.abspath(os.path.join(
+                self._prefix, "share", "pixmaps")) 
+            self._schema_path = os.path.abspath(os.path.join(
+                self._prefix, "share", "chirp")) 
+
+        if not os.path.exists(self._share_path):
+            raise IOError(errno.ENOENT, 
+                "The directory %s does not exist." % self._share_path) 
+
+        if not os.path.exists(self._locale_path):
+            raise IOError(errno.ENOENT, 
+                "The directory %s does not exist." % self._locale_path) 
+
+        if not os.path.exists(self._stock_configs_path):
+            raise IOError(errno.ENOENT, 
+                "The directory %s does not exist." % self._stock_configs_path) 
+
+        if not os.path.exists(self._pixmaps_path):
+            raise IOError(errno.ENOENT, 
+                "The directory %s does not exist." % self._pixmaps_path) 
+
+        if not os.path.exists(self._schema_path):
+            raise IOError(errno.ENOENT, 
+                "The directory %s does not exist." % self._schema_path) 
 
     def get_last_dir(self):
         """Return the last directory used"""
@@ -98,6 +169,30 @@
         return os.path.join(self.config_dir(),
                             self.filter_filename(filename))
 
+    def executable_path(self):
+        """Return the base of the installation"""
+        return self._prefix
+            
+    def locale_dir(self):
+        """Return the locale directory for the installation""" 
+        return self._locale_path 
+
+    def share_dir(self):
+        """Return the shared files directory for the installation""" 
+        return self._share_path 
+
+    def stock_configs_dir(self):
+        """Return the stock configs directory for the installation""" 
+        return self._stock_configs_path 
+
+    def pixmaps_dir(self):
+        """Return the pixelmaps directory for the installation""" 
+        return self._pixmaps_path
+
+    def schema_dir(self):
+        """Return the xml schema directory for the installation""" 
+        return self._schema_path
+
     def open_text_file(self, path):
         """Spawn the necessary program to open a text file at @path"""
         raise NotImplementedError("The base class can't do that")
@@ -215,19 +310,23 @@
         """Return a string that describes the OS/platform version"""
         return "Unknown Operating System"
 
-    def executable_path(self):
-        """Return a full path to the program executable"""
-        def we_are_frozen():
-            return hasattr(sys, "frozen")
+    def _get_prefix(self):
+        """Returns the python prefix
 
-        if we_are_frozen():
-            # Win32, find the directory of the executable
-            return os.path.dirname(unicode(sys.executable,
-                                           sys.getfilesystemencoding()))
+        this is the equivalent of 'python-config --prefix' , accounting 
+        for any virtual environment,  and accounting for running from source 
+
+        """ 
+        # find our current location 
+        if self._running_from_source:
+            # running from source tree, only two levels up
+            return os.path.abspath(os.path.join(__file__, 
+                "..", ".."))
         else:
-            # UNIX: Find the parent directory of this module
-            return os.path.dirname(os.path.abspath(os.path.join(_find_me(),
-                                                                "..")))
+            # we're 'installed', so prefix is five levels up 
+            return os.path.abspath(os.path.join(__file__, 
+                "..", "..", "..", "..", ".."))
+
 
 def _unix_editor():
     macos_textedit = "/Applications/TextEdit.app/Contents/MacOS/TextEdit"
@@ -249,9 +348,24 @@
 
         Platform.__init__(self, basepath)
 
-	# This is a hack that needs to be properly fixed by importing the
-	# latest changes to this module from d-rats.  In the interest of
-	# time, however, I'll throw it here
+        # set the installation prefix
+        # see http://stackoverflow.com/questions/1511461/py2exe-and-the-file-system
+        # py2app:
+        # Notes on how to find stuff on MAC, by an expert (Bob Ippolito):
+        # http://mail.python.org/pipermail/pythonmac-sig/2004-November/012121.html
+        # TODO verify this logic for OSX 
+        if self._frozen:
+            self._prefix = os.environ['RESOURCEPATH']
+        else:
+            self._prefix = self._get_prefix()
+        if not os.path.exists(self._prefix):
+            raise IOError(errno.ENOENT, " The directory  %s does not exist." % self._prefix) 
+
+        self.init_dirs()
+
+        # This is a hack that needs to be properly fixed by importing the
+        # latest changes to this module from d-rats.  In the interest of
+        # time, however, I'll throw it here
         if sys.platform == "darwin":
             if not os.environ.has_key("DISPLAY"):
                 print "Forcing DISPLAY for MacOS"
@@ -302,6 +416,7 @@
 
         return ver
 
+
 class Win32Platform(Platform):
     """A platform module suitable for Windows systems"""
     def __init__(self, basepath=None):
@@ -316,6 +431,16 @@
 
         Platform.__init__(self, basepath)
 
+        # set the installation prefix
+        if self._frozen:
+            # Win32, find the directory of the executable
+            self._prefix = os.path.dirname(unicode(sys.executable,
+                                           sys.getfilesystemencoding()))
+        else:
+            self._prefix = self._get_prefix()
+
+        self.init_dirs()
+
     def default_dir(self):
         return os.path.abspath(os.path.join(os.getenv("USERPROFILE"),
                                             "Desktop"))
@@ -431,6 +556,7 @@
     return PLATFORM
 
 def _do_test():
+
     __pform = get_platform()
 
     print "Config dir: %s" % __pform.config_dir()
@@ -438,6 +564,17 @@
     print "Log file (foo): %s" % __pform.log_file("foo")
     print "Serial ports: %s" % __pform.list_serial_ports()
     print "OS Version: %s" % __pform.os_version_string()
+
+    print "Frozen : %s" % __pform._frozen
+    print "Running from Source : %s" % __pform._running_from_source 
+
+    print "Executable path : %s" % __pform.executable_path() 
+    print "Share directory : %s" % __pform.share_dir() 
+    print "Locale directory : %s" % __pform.locale_dir() 
+    print "Stock configs directory : %s" % __pform.stock_configs_dir() 
+    print "pixmaps directory : %s" % __pform.pixmaps_dir() 
+    print "schema directory : %s" % __pform.schema_dir() 
+
     #__pform.open_text_file("d-rats.py")
 
     #print "Open file: %s" % __pform.gui_open_file()
diff -r a9e808e15821 -r 8681bbcdbd44 chirpui/mainapp.py
--- a/chirpui/mainapp.py	Tue Nov 26 19:10:37 2013 -0800
+++ b/chirpui/mainapp.py	Wed Nov 27 05:54:54 2013 -0800
@@ -490,10 +490,7 @@
         count = eset.do_import(config)
 
     def copy_shipped_stock_configs(self, stock_dir):
-        execpath = platform.get_platform().executable_path()
-        basepath = os.path.abspath(os.path.join(execpath, "stock_configs"))
-        if not os.path.exists(basepath):
-            basepath = "/usr/share/chirp/stock_configs"
+        basepath = platform.get_platform().stock_configs_dir()
 
         files = glob(os.path.join(basepath, "*.csv"))
         for fn in files:
@@ -1598,10 +1595,8 @@
             a.connect_accelerator()
         
     def _set_icon(self):
-        execpath = platform.get_platform().executable_path()
-        path = os.path.abspath(os.path.join(execpath, "share", "chirp.png"))
-        if not os.path.exists(path):
-            path = "/usr/share/pixmaps/chirp.png"
+        path = os.path.join(
+            platform.get_platform().pixmaps_dir(), "chirp.png")
 
         if os.path.exists(path):
             self.set_icon_from_file(path)
diff -r a9e808e15821 -r 8681bbcdbd44 chirpw
--- a/chirpw	Tue Nov 26 19:10:37 2013 -0800
+++ b/chirpw	Wed Nov 27 05:54:54 2013 -0800
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 #
 # Copyright 2008 Dan Smith <dsmith@danplanet.com>
 #
@@ -43,10 +43,7 @@
                                       platform.get_platform().os_version_string(),
                                       sys.version.split()[0])
 
-execpath = platform.get_platform().executable_path()
-localepath = os.path.abspath(os.path.join(execpath, "locale"))
-if not os.path.exists(localepath):
-    localepath = "/usr/share/chirp/locale"
+localepath = platform.get_platform().locale_dir()
 
 conf = config.get()
 manual_language = conf.get("language", "state")
diff -r a9e808e15821 -r 8681bbcdbd44 setup.py
--- a/setup.py	Tue Nov 26 19:10:37 2013 -0800
+++ b/setup.py	Wed Nov 27 05:54:54 2013 -0800
@@ -125,7 +125,7 @@
           version="0.3",
           scripts=["rpttool"],
           description="A frequency tool for ICOM D-STAR Repeaters",
-          data_files=[('/usr/sbin', ["tools/icomsio.sh"])],
+          data_files=[('usr/sbin', ["tools/icomsio.sh"])],
           )
 
 def nuke_manifest(*files):
