| 1 | # ReportInstallation.py - reports an MGET installation to the MGET statistics server
|
|---|
| 2 | #
|
|---|
| 3 | # This program is only intended to be invoked by the GeoEco setup
|
|---|
| 4 | # program. The setup program uses it to report a successful or failed
|
|---|
| 5 | # installation to the server that tracks installation statistics.
|
|---|
| 6 | # Please do not invoke this program yourself. Doing so will corrupt
|
|---|
| 7 | # the statistics. We use these statistics to assess the software
|
|---|
| 8 | # quality of the setup program, identify common installation problems
|
|---|
| 9 | # encountered by MGET users, and justify our requests for additional
|
|---|
| 10 | # funding.
|
|---|
| 11 | #
|
|---|
| 12 | # For more information about this program, including a discussion of
|
|---|
| 13 | # what data are reported, what we do with the data, and how to disable
|
|---|
| 14 | # the reporting, please see the MGET web site. Alternatively, just
|
|---|
| 15 | # look at the code below. If you have any questions, please contact
|
|---|
| 16 | # Jason Roberts (jason.roberts@duke.edu).
|
|---|
| 17 | #
|
|---|
| 18 | # Copyright (C) 2009 Jason J. Roberts
|
|---|
| 19 | #
|
|---|
| 20 | # This program is free software; you can redistribute it and/or
|
|---|
| 21 | # modify it under the terms of the GNU General Public License
|
|---|
| 22 | # as published by the Free Software Foundation; either version 2
|
|---|
| 23 | # of the License, or (at your option) any later version.
|
|---|
| 24 | #
|
|---|
| 25 | # This program is distributed in the hope that it will be useful,
|
|---|
| 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|---|
| 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|---|
| 28 | # GNU General Public License (available in the file LICENSE.TXT)
|
|---|
| 29 | # for more details.
|
|---|
| 30 | #
|
|---|
| 31 | # You should have received a copy of the GNU General Public License
|
|---|
| 32 | # along with this program; if not, write to the Free Software
|
|---|
| 33 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|---|
| 34 |
|
|---|
| 35 | import ConfigParser
|
|---|
| 36 | import os
|
|---|
| 37 | import re
|
|---|
| 38 | import socket
|
|---|
| 39 | import sys
|
|---|
| 40 | import tempfile
|
|---|
| 41 | import time
|
|---|
| 42 | import traceback
|
|---|
| 43 | import urllib
|
|---|
| 44 | import xmlrpclib
|
|---|
| 45 |
|
|---|
| 46 | import pywintypes
|
|---|
| 47 | import win32api
|
|---|
| 48 | import win32con
|
|---|
| 49 |
|
|---|
| 50 | def main():
|
|---|
| 51 |
|
|---|
| 52 | # Validate that we have been invoked from the command line.
|
|---|
| 53 |
|
|---|
| 54 | if sys.argv[0] != __file__:
|
|---|
| 55 | raise RuntimeError('This function is only intended to be invoked by the GeoEco setup program. The setup program uses it to report a successful or failed installation to the server that tracks installation statistics. Please do not invoke this function yourself. Doing so will corrupt our statistics. Thank you.')
|
|---|
| 56 |
|
|---|
| 57 | # Open a log file in the user's temp directory.
|
|---|
| 58 |
|
|---|
| 59 | logFileHandle, logFilePath = tempfile.mkstemp('.txt', 'GeoEcoSetupFailure_')
|
|---|
| 60 | logFile = os.fdopen(logFileHandle, 'wb')
|
|---|
| 61 | try:
|
|---|
| 62 | try:
|
|---|
| 63 | logFile.write('Installation completed at %s GMT.\n' % time.asctime(time.gmtime(time.time())))
|
|---|
| 64 |
|
|---|
| 65 | # Parse and validate the command line arguments.
|
|---|
| 66 |
|
|---|
| 67 | logFile.write('Parsing command line arguments.\n')
|
|---|
| 68 |
|
|---|
| 69 | try:
|
|---|
| 70 | if len(sys.argv) != 4:
|
|---|
| 71 | raise RuntimeError('Invalid number of arguments.')
|
|---|
| 72 |
|
|---|
| 73 | if sys.argv[1] not in ['False', 'True']:
|
|---|
| 74 | raise RuntimeError('Invalid arguments.')
|
|---|
| 75 | shortcuts = sys.argv[1] == 'True'
|
|---|
| 76 | logFile.write('shortcuts = %s\n' % str(shortcuts))
|
|---|
| 77 |
|
|---|
| 78 | if sys.argv[2] not in ['False', 'True']:
|
|---|
| 79 | raise RuntimeError('Invalid arguments.')
|
|---|
| 80 | arcToolbox = sys.argv[2] == 'True'
|
|---|
| 81 | logFile.write('arcToolbox = %s\n' % str(arcToolbox))
|
|---|
| 82 |
|
|---|
| 83 | if sys.argv[3] not in ['False', 'True']:
|
|---|
| 84 | raise RuntimeError('Invalid arguments.')
|
|---|
| 85 | comClasses = sys.argv[3] == 'True'
|
|---|
| 86 | logFile.write('comClasses = %s\n' % str(comClasses))
|
|---|
| 87 |
|
|---|
| 88 | except Exception, e:
|
|---|
| 89 | sys.stderr.write('%s: %s\n' % (str(e.__class__.__name__), str(e)))
|
|---|
| 90 | print('')
|
|---|
| 91 | print('ReportInstallation.py - reports an MGET installation to the MGET statistics server')
|
|---|
| 92 | print('')
|
|---|
| 93 | print('This program is only intended to be invoked by the GeoEco setup program. The')
|
|---|
| 94 | print('setup program uses it to report a successful or failed installation to the')
|
|---|
| 95 | print('server that tracks installation statistics. Please do not invoke this program')
|
|---|
| 96 | print('yourself. Doing so will corrupt the statistics. We use these statistics to')
|
|---|
| 97 | print('assess the software quality of the setup program, identify common installation')
|
|---|
| 98 | print('problems encountered by MGET users, and justify our requests for additional')
|
|---|
| 99 | print('funding.')
|
|---|
| 100 | print('')
|
|---|
| 101 | print('For more information about this program, including a discussion of what data are')
|
|---|
| 102 | print('reported, what we do with the data, and how to disable the reporting, please see')
|
|---|
| 103 | print('the MGET web site. You are also welcome to examine the program source code. If')
|
|---|
| 104 | print('you have any questions, please contact Jason Roberts (jason.roberts@duke.edu).')
|
|---|
| 105 | print('')
|
|---|
| 106 | print('Thanks for your support,')
|
|---|
| 107 | print('')
|
|---|
| 108 | print(' - The MGET Development Team')
|
|---|
| 109 | sys.exit(1)
|
|---|
| 110 |
|
|---|
| 111 | # Look up the ID of this client MGET's registry key. If no client
|
|---|
| 112 | # ID is there, generate one randomly. The client ID is a random
|
|---|
| 113 | # 128-bit number (a GUID) that is guaranteed by the operating
|
|---|
| 114 | # system to be unique globally.
|
|---|
| 115 |
|
|---|
| 116 | logFile.write('Creating/opening registry key HLKM\\SOFTWARE\\GeoEco.\n')
|
|---|
| 117 | hKey = win32api.RegCreateKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\GeoEco')
|
|---|
| 118 | try:
|
|---|
| 119 | # First see if there is a value called
|
|---|
| 120 | # DisableInstallationReporting. If so, it means the user
|
|---|
| 121 | # requested that no installation report be sent to the server
|
|---|
| 122 | # and we must exit immediately.
|
|---|
| 123 |
|
|---|
| 124 | try:
|
|---|
| 125 | logFile.write('Querying for the DisableInstallationReporting value.\n')
|
|---|
| 126 | win32api.RegQueryValueEx(hKey, 'DisableInstallationReporting')
|
|---|
| 127 | except:
|
|---|
| 128 | logFile.write('The DisableInstallationReporting value could not be queried. Installation reporting is not disabled.\n')
|
|---|
| 129 | else:
|
|---|
| 130 | logFile.write('The DisableInstallationReporting was successfully queried. Installation reporting is disabled. Exiting without sending a report.\n')
|
|---|
| 131 | print('Installation reporting has been disabled. Exiting without sending a report.')
|
|---|
| 132 | return
|
|---|
| 133 |
|
|---|
| 134 | # Now try to read the ClientID value.
|
|---|
| 135 |
|
|---|
| 136 | clientID = None
|
|---|
| 137 |
|
|---|
| 138 | try:
|
|---|
| 139 | logFile.write('Querying for the ClientID value.\n')
|
|---|
| 140 | clientID = win32api.RegQueryValueEx(hKey, 'ClientID')[0]
|
|---|
| 141 | except:
|
|---|
| 142 | logFile.write('Failed to query the ClientID value.\n')
|
|---|
| 143 |
|
|---|
| 144 | # If we failed to read a ClientID value or we got one that is
|
|---|
| 145 | # an invalid format, generate a new one.
|
|---|
| 146 |
|
|---|
| 147 | if not isinstance(clientID, basestring) or re.match('\{[0123456789AaBbCcDdEeFf]{8}-[0123456789AaBbCcDdEeFf]{4}-[0123456789AaBbCcDdEeFf]{4}-[0123456789AaBbCcDdEeFf]{4}-[0123456789AaBbCcDdEeFf]{12}\}', clientID) is None:
|
|---|
| 148 | clientID = str(pywintypes.CreateGuid())
|
|---|
| 149 | logFile.write('Writing ClientID value "%s".\n' % clientID)
|
|---|
| 150 | win32api.RegSetValueEx(hKey, 'ClientID', 0, win32con.REG_SZ, clientID)
|
|---|
| 151 | else:
|
|---|
| 152 | clientID = clientID.upper()
|
|---|
| 153 | logFile.write('clientID = %s.\n' % clientID)
|
|---|
| 154 |
|
|---|
| 155 | finally:
|
|---|
| 156 | logFile.write('Closing the registry key.\n')
|
|---|
| 157 | win32api.RegCloseKey(hKey)
|
|---|
| 158 |
|
|---|
| 159 | # Get the MGET version number. This should always succeed, because
|
|---|
| 160 | # we are only invoked from the MGET post-installation script,
|
|---|
| 161 | # which distutils should only invoke if it was able to copy the
|
|---|
| 162 | # Python files onto the machine and compile them.
|
|---|
| 163 |
|
|---|
| 164 | mgetVer = None
|
|---|
| 165 | try:
|
|---|
| 166 | logFile.write('Importing GeoEco.\n')
|
|---|
| 167 | import GeoEco
|
|---|
| 168 | mgetVer = GeoEco.__version__
|
|---|
| 169 | except Exception, e:
|
|---|
| 170 | logFile.write('Failed to import GeoEco: %s: %s\n' % (str(e.__class__.__name__), str(e)))
|
|---|
| 171 | else:
|
|---|
| 172 | logFile.write('mgetVer = %s\n' % mgetVer)
|
|---|
| 173 |
|
|---|
| 174 | # Get the ArcGIS version number from the registry. This will fail
|
|---|
| 175 | # if ArcGIS is not installed, in which case we send None to the
|
|---|
| 176 | # server.
|
|---|
| 177 |
|
|---|
| 178 | arcVer = None
|
|---|
| 179 | try:
|
|---|
| 180 | logFile.write('Opening registry key HLKM\\SOFTWARE\\ESRI\\ArcInfo\\Desktop\\8.0.\n')
|
|---|
| 181 | try:
|
|---|
| 182 | hkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\ESRI\\ArcInfo\\Desktop\\8.0')
|
|---|
| 183 | except:
|
|---|
| 184 | logFile.write('Failed to open the registry key.\n')
|
|---|
| 185 | logFile.write('Opening registry key HLKM\\SOFTWARE\\ESRI\\Desktop10.0.\n')
|
|---|
| 186 | hkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\ESRI\\Desktop10.0')
|
|---|
| 187 | try:
|
|---|
| 188 | logFile.write('Querying for the RealVersion value.\n')
|
|---|
| 189 | realVersion = win32api.RegQueryValueEx(hkey, 'RealVersion')[0].strip()
|
|---|
| 190 | try:
|
|---|
| 191 | logFile.write('Querying for the SPNumber value.\n')
|
|---|
| 192 | spNumber = win32api.RegQueryValueEx(hkey, 'SPNumber')[0].strip()
|
|---|
| 193 | except:
|
|---|
| 194 | spNumber = None
|
|---|
| 195 | finally:
|
|---|
| 196 | logFile.write('Closing the registry key.\n')
|
|---|
| 197 | win32api.RegCloseKey(hkey)
|
|---|
| 198 |
|
|---|
| 199 | if spNumber is None or spNumber == '0':
|
|---|
| 200 | arcVer = realVersion
|
|---|
| 201 | elif realVersion == '9.3' and spNumber == '2':
|
|---|
| 202 | arcVer = '9.3.1'
|
|---|
| 203 | elif realVersion == '9.3' and spNumber > '2':
|
|---|
| 204 | arcVer = '9.3.1 SP%i' % (int(spNumber) - 2)
|
|---|
| 205 | else:
|
|---|
| 206 | arcVer = '%s SP%s' % (realVersion, spNumber)
|
|---|
| 207 |
|
|---|
| 208 | except Exception, e:
|
|---|
| 209 | logFile.write('Failed to get ArcGIS version number: %s: %s\n' % (str(e.__class__.__name__), str(e)))
|
|---|
| 210 | else:
|
|---|
| 211 | logFile.write('arcVer = %s\n' % arcVer)
|
|---|
| 212 |
|
|---|
| 213 | # Get the R version using logic that imitates what rpy's
|
|---|
| 214 | # get_R_VERSION function does. (I originally tried to use that
|
|---|
| 215 | # function but it would not work if the registry key did not exist
|
|---|
| 216 | # and it tried to run R.exe instead.)
|
|---|
| 217 |
|
|---|
| 218 | rVer = None
|
|---|
| 219 | try:
|
|---|
| 220 | # First try to read the "Current Version" value of the
|
|---|
| 221 | # HKLM\SOFTWARE\R-core\R registry key.
|
|---|
| 222 |
|
|---|
| 223 | try:
|
|---|
| 224 | logFile.write('Opening registry key HLKM\\SOFTWARE\\R-core\\R.\n')
|
|---|
| 225 | hkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\R-core\\R')
|
|---|
| 226 | try:
|
|---|
| 227 | logFile.write('Querying for the Current Version value.\n')
|
|---|
| 228 | rVer = win32api.RegQueryValueEx(hkey, 'Current Version')[0]
|
|---|
| 229 | finally:
|
|---|
| 230 | logFile.write('Closing the registry key.\n')
|
|---|
| 231 | win32api.RegCloseKey(hkey)
|
|---|
| 232 |
|
|---|
| 233 | # If that did not work, try to parse the R version from the
|
|---|
| 234 | # R_HOME environment variable.
|
|---|
| 235 |
|
|---|
| 236 | except:
|
|---|
| 237 | logFile.write('Failed. Trying the R_HOME environment variable instead.\n')
|
|---|
| 238 | if os.environ.has_key('R_HOME'):
|
|---|
| 239 | rHome = os.environ['R_HOME']
|
|---|
| 240 | if re.match('^[Rr]-\d+\.\d+\.\d+$', os.path.basename(rHome)):
|
|---|
| 241 | rVer = os.path.basename(rHome)[2:]
|
|---|
| 242 |
|
|---|
| 243 | # If that did not work, look for a file called
|
|---|
| 244 | # README.R-X.Y.Z in the R_HOME directory, where X.Y.Z
|
|---|
| 245 | # is the version number.
|
|---|
| 246 |
|
|---|
| 247 | else:
|
|---|
| 248 | import glob
|
|---|
| 249 | files = glob.glob(os.path.join(rHome, 'README.R-[0123456789].*.*'))
|
|---|
| 250 | if len(files) > 0 and re.match('^\d+\.\d+\.\d+$', os.path.basename(files[0])[9:]) is not None:
|
|---|
| 251 | rVer = os.path.basename(files[0])[9:]
|
|---|
| 252 |
|
|---|
| 253 | except Exception, e:
|
|---|
| 254 | logFile.write('Failed to get R version number: %s: %s\n' % (str(e.__class__.__name__), str(e)))
|
|---|
| 255 | else:
|
|---|
| 256 | logFile.write('rVer = %s\n' % rVer)
|
|---|
| 257 |
|
|---|
| 258 | # Download the ClientConfiguration.ini file from the well-known
|
|---|
| 259 | # location and look up the URL to the XML-RPC web service that
|
|---|
| 260 | # will accept the installation report.
|
|---|
| 261 | #
|
|---|
| 262 | # In order to prevent this script from hanging if the Duke
|
|---|
| 263 | # servers are not working, we set the default socket timeout
|
|---|
| 264 | # to 60 seconds. (With Python, there is no way to set it on a
|
|---|
| 265 | # per-connection basis, at least using urllib and xmlrpclib.)
|
|---|
| 266 | # But Python 2.4 contains a bug that causes this to break SSL
|
|---|
| 267 | # support (see http://bugs.python.org/issue1153016). So, on
|
|---|
| 268 | # 2.4, convert https URLs to http URLs.
|
|---|
| 269 |
|
|---|
| 270 | logFile.write('Setting the socket default timeout to 60.\n')
|
|---|
| 271 |
|
|---|
| 272 | socket.setdefaulttimeout(60)
|
|---|
| 273 |
|
|---|
| 274 | if sys.version_info[0] == 2 and sys.version_info[1] == 4:
|
|---|
| 275 | clientINIURL = 'http://code.nicholas.duke.edu/projects/mget/browser/Statistics/ClientConfiguration.ini?format=txt'
|
|---|
| 276 | else:
|
|---|
| 277 | clientINIURL = 'https://code.nicholas.duke.edu/projects/mget/browser/Statistics/ClientConfiguration.ini?format=txt'
|
|---|
| 278 |
|
|---|
| 279 | logFile.write('clientINIURL = %s\n' % clientINIURL)
|
|---|
| 280 |
|
|---|
| 281 | try:
|
|---|
| 282 | iniFileError = 'Step 1: '
|
|---|
| 283 | config = ConfigParser.ConfigParser()
|
|---|
| 284 | logFile.write('Opening the clientINIURL.\n')
|
|---|
| 285 | iniFileError = 'Step 2: '
|
|---|
| 286 | f = urllib.urlopen(clientINIURL)
|
|---|
| 287 | try:
|
|---|
| 288 | logFile.write('Parsing the INI file.\n')
|
|---|
| 289 | iniFileError = 'Step 3: '
|
|---|
| 290 | config.readfp(f)
|
|---|
| 291 | finally:
|
|---|
| 292 | iniFileError = 'Step 4: '
|
|---|
| 293 | f.close()
|
|---|
| 294 | logFile.write('Getting the URL from the parsed INI file.\n')
|
|---|
| 295 | iniFileError = 'Step 5: '
|
|---|
| 296 | serverURL = config.get('V1', 'URL')
|
|---|
| 297 | except Exception, e:
|
|---|
| 298 | logFile.write('Failed to get the MGET server URL: %s: %s. The default URL will be used.\n' % (str(e.__class__.__name__), str(e)))
|
|---|
| 299 | serverURL = 'http://mgetstats.nicholas.duke.edu:8001'
|
|---|
| 300 | iniFileError = '%s%s: %s' % (iniFileError, str(e.__class__.__name__), str(e))
|
|---|
| 301 | else:
|
|---|
| 302 | iniFileError = None
|
|---|
| 303 |
|
|---|
| 304 | logFile.write('serverURL = %s\n' % serverURL)
|
|---|
| 305 |
|
|---|
| 306 | if sys.version_info[0] == 2 and sys.version_info[1] == 4 and serverURL.startswith('https:'):
|
|---|
| 307 | logFile.write('Changing serverURL to http from https because we\'re running on Python 2.4.\n')
|
|---|
| 308 | serverURL = 'http:' + serverURL[6:]
|
|---|
| 309 |
|
|---|
| 310 | # Send the report to the server by calling its XML-RPC web
|
|---|
| 311 | # service.
|
|---|
| 312 |
|
|---|
| 313 | logFile.write('Instantiating the xmlrpclib.Server object.\n')
|
|---|
| 314 | s = xmlrpclib.Server(serverURL, allow_none=True)
|
|---|
| 315 |
|
|---|
| 316 | logFile.write('Calling ReportMGETInstallationV2 on the server.\n')
|
|---|
| 317 | s.ReportMGETInstallationV2(clientID, shortcuts, arcToolbox, comClasses, mgetVer, '.'.join(map(str, sys.version_info[0:3])), arcVer, rVer, iniFileError)
|
|---|
| 318 |
|
|---|
| 319 | # If we got any kind of exception, write it to the log file.
|
|---|
| 320 |
|
|---|
| 321 | except:
|
|---|
| 322 | try:
|
|---|
| 323 | logFile.write('Caught an exception:\n')
|
|---|
| 324 | logFile.write('\n')
|
|---|
| 325 | logFile.write(traceback.format_exc())
|
|---|
| 326 | except:
|
|---|
| 327 | pass
|
|---|
| 328 | raise
|
|---|
| 329 |
|
|---|
| 330 | # Close the log file.
|
|---|
| 331 |
|
|---|
| 332 | finally:
|
|---|
| 333 | try:
|
|---|
| 334 | logFile.close()
|
|---|
| 335 | except:
|
|---|
| 336 | pass
|
|---|
| 337 |
|
|---|
| 338 | # If we got to here, we succesfully sent the report to the server.
|
|---|
| 339 | # Delete the log file.
|
|---|
| 340 |
|
|---|
| 341 | os.remove(logFilePath)
|
|---|
| 342 |
|
|---|
| 343 | if __name__ == '__main__':
|
|---|
| 344 | main()
|
|---|