#!/usr/bin/env python """Install RCrane so that it runs every time Coot launches.""" # Copyright 2011 Kevin Keating # # Licensed under the Educational Community License, Version 2.0 (the # "License"); you may not use this file except in compliance with the # License. You may obtain a copy of the License at # # http://www.osedu.org/licenses/ECL-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an "AS IS" # BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express # or implied. See the License for the specific language governing # permissions and limitations under the License. import sys import os import os.path from os.path import basename, abspath, dirname import inspect from shutil import copy def main(): """Install RCrane so that it runs every time Coot launches""" try: filename = determineFilename() checkFile(filename) launchLoc = writeFile(filename) except RCraneInstallError, error: print error if withinCoot(): #if we're running in Coot, use GTK to display any error messages in a message box import gtk errorDialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format=error) errorDialog.set_title("RCrane Installation Error") errorDialog.run() errorDialog.destroy() else: successMessage = "RCrane installed successfully. " + filename + " created." print successMessage if withinCoot(): #if we're running in Coot #if RCrane hasn't yet been launched, launch it if not sys.modules.has_key("rcrane"): run_python_script(launchLoc) successMessage += "\n\nRcrane launched." #report successful install (and possibly launch) import gtk msg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format=successMessage) msg.set_title("RCrane Installation") msg.run() msg.destroy() def determineFilename(): """Determine the appropriate filename for an RCrane launch script ARGUMENTS: None RETURNS: None EXCEPTIONS: RCraneInstallError - if the appropriate filename cannot be determined or if the .coot-preferences directory does not exist and cannot be created """ #NOTE: this function is nearly identical to settingsFilename() in settings.py, but in order to import that #function in place of this one, we'd need to know the RCrane directory #and most of this function is dedicated to finding the RCrane directory, so we'd still need this #function in order to import that function (i.e. we're not actually going to save any code by doing it that way) homedir = None #check for a HOME environment variable first #this should only exist under a posix environment (i.e. Linux, OS X, msys) #This may break if we're running under Windows and the user has manually set a %HOME% environment variable #but that seems unlikely. We could avoid that problem by checking os.name first (i.e. don't look for HOME #if os.name == 'nt'), but that would fail under MSys/MinGW try: homedir = os.environ["HOME"] except KeyError: pass #if we can find HOME, look for COOT_HOME (which is used in WinCoot <= 0.6.2) if homedir == None: try: homedir = os.environ["COOT_HOME"] except KeyError: pass if homedir == None: #if we can't find HOME or COOT_HOME, then the user is probably running this from the command line in Windows #(or possibly we're running WinCoot > 0.6.2, but that doesn't exist yet) #in a last ditch effort, look for the WinCoot binary if os.path.getsize("C:\WinCoot\bin\coot") and os.path.getsize("C:\WinCoot\bin\coot-real.exe"): #first check C:\WinCoot, the default install location homedir == "C:\WinCoot" elif os.path.getsize("C:\Program Files\WinCoot\bin\coot") and os.path.getsize("C:\Program Files\WinCoot\bin\coot-real.exe"): #then check C:\Program Files\WinCoot, which could be a common alternate path homedir == "C:\Program Files\WinCoot" elif os.path.getsize("C:\Program Files (x86)\WinCoot\bin\coot") and os.path.getsize("C:\Program Files (x86)\WinCoot\bin\coot-real.exe"): #then check C:\Program Files (x86)\WinCoot (we're really grasping at straws here) homedir == "C:\Program Files (x86)\WinCoot" #if none of those paths work, then give up #short of searching the entire hard drive, we're not going to find the Coot installation directory if homedir == None: #if we still haven't found the appropriate directory, give up #this probably means we're running in Windows from the command line and WinCoot is installed in a non-standard directory raise RCraneInstallError("Can't determine home directory ($HOME or %COOT_HOME%). Cannot install RCrane.") return #make sure the .coot-preferences directory exists, and try to create it if it doesn't preferencesDir = os.path.join(homedir, ".coot-preferences") if os.path.exists(preferencesDir): if not os.path.isdir(preferencesDir): raise RCraneInstallError("Could not create Coot preferences directory: " + preferencesDir + "\nA file of the same name already exists") else: try: os.mkdir(preferencesDir) except OSError, error: raise RCraneInstallError("Could not create Coot preferences directory: " + preferencesDir + "\nError: " + str(error)) #return the name of the file to create return os.path.join(homedir, ".coot-preferences", "rcrane.py") def checkFile(filename): """Make sure that it's safe to overwrite any file at filename ARGUMENTS: filename - the filename to check RETURNS: None EFFECTS: will create a backup of filename if neccesary (if filename contains something other than the standard launch script) EXCEPTIONS: RCraneInstallError - if a backup is necessary and cannot be completed """ if not os.path.exists(filename): return try: input = open(filename) except IOError, error: raise RCraneInstallerError("Could not open " + filename + " for reading\nError: " + str(error)) for curline in input: import re #figure out if this file contains anything other than the launch command (or blank lines) if not re.match(r"""^run_python_script\(["'].*launch.py["']\)\s*$""", curline) or re.match("^\s*$", curline): break else: return #if we get here, then the file contains something we didn't recognize, so create a backup of it backupFilename = filename + ".bak" #try to find an rcrane.py.bak filename that we can use for curSuffix in [""] + map(str, range(1,99)): #print "trying suffix:", curSuffix if not os.path.exists(backupFilename + curSuffix): break else: raiseRCraneInstallError("Could not find a suitable backup filename for the existing rcrane.py.\nPlease check " + dirname(filename)) backupFilename += curSuffix try: copy(filename, backupFilename) except IOError, error: raise RCraneInstallError("Could not backup existing " + filename + "\nError: " + str(error)) def writeFile(filename): """Create an RCrane launch script ARGUMENTS: filename - the filename for the launch script RETURNS: launchLoc - the location of RCrane's launch.py EXCEPTIONS: RCraneInstallError - if the launch script cannot be created """ try: out = open(filename, 'w') except IOError, error: raise RCraneInstallerError("Could not open " + filename + "\nError: " + str(error)) #figure out where the RCrane directory is (i.e. the directory where this file is located) #since this script may be run from within Coot, we must use the inspect module instead of something simpler fileloc = inspect.currentframe().f_code.co_filename fileloc = abspath(fileloc) rcranedir = dirname(fileloc) launchLoc = os.path.join(rcranedir, "launch.py") out.write('run_python_script(r"' + launchLoc + '")\n') #the path must be a raw string if we are on Windows and any of the directories in the launchLoc path #start with an "n" or a "t" (or any other escapable character) out.close() return launchLoc def withinCoot(): """Figure out if we're running from within Coot ARGUMENTS: None RETURNS: None """ #do this by looking for the coot_version function, which won't exist if we're running this script from the command line #we could also look at sys.argv[0] and make sure we're running coot-real try: coot_version() except NameError: return False else: return True class RCraneInstallError(Exception): """Errors associated with the installation of RCrane""" def __init__(self, value): self.value = value def __str__(self): return self.value main()