root/MGET/Trunk/PythonPackage/src/GeoEco/ReportInstallation.py

Revision 442, 16.1 kB (checked in by jjr8, 4 months ago)

Merged [430-441] from Jason branch into Trunk. This will be released as the final build of MGET 0.7.

Line 
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                 hkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\ESRI\\ArcInfo\\Desktop\\8.0')
182                 try:
183                     logFile.write('Querying for the RealVersion value.\n')
184                     realVersion = win32api.RegQueryValueEx(hkey, 'RealVersion')[0].strip()
185                     try:
186                         logFile.write('Querying for the SPNumber value.\n')
187                         spNumber = win32api.RegQueryValueEx(hkey, 'SPNumber')[0].strip()
188                     except:
189                         spNumber = None
190                 finally:
191                     logFile.write('Closing the registry key.\n')
192                     win32api.RegCloseKey(hkey)
193
194                 if spNumber is None or spNumber == '0':
195                     arcVer = realVersion
196                 elif realVersion == '9.3' and spNumber == '2':
197                     arcVer = '9.3.1'
198                 elif realVersion == '9.3' and spNumber > '2':
199                     arcVer = '9.3.1 SP%i' % (int(spNumber) - 2)
200                 else:
201                     arcVer = '%s SP%s' % (realVersion, spNumber)
202
203             except Exception, e:
204                 logFile.write('Failed to get ArcGIS version number: %s: %s\n' % (str(e.__class__.__name__), str(e)))
205             else:
206                 logFile.write('arcVer = %s\n' % arcVer)
207
208             # Get the R version using logic that imitates what rpy's
209             # get_R_VERSION function does. (I originally tried to use that
210             # function but it would not work if the registry key did not exist
211             # and it tried to run R.exe instead.)
212
213             rVer = None
214             try:
215                 # First try to read the "Current Version" value of the
216                 # HKLM\SOFTWARE\R-core\R registry key.
217                 
218                 try:
219                     logFile.write('Opening registry key HLKM\\SOFTWARE\\R-core\\R.\n')
220                     hkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\R-core\\R')
221                     try:
222                         logFile.write('Querying for the Current Version value.\n')
223                         rVer = win32api.RegQueryValueEx(hkey, 'Current Version')[0]
224                     finally:
225                         logFile.write('Closing the registry key.\n')
226                         win32api.RegCloseKey(hkey)
227
228                 # If that did not work, try to parse the R version from the
229                 # R_HOME environment variable.
230                 
231                 except:
232                     logFile.write('Failed. Trying the R_HOME environment variable instead.\n')
233                     if os.environ.has_key('R_HOME'):
234                         rHome = os.environ['R_HOME']
235                         if re.match('^[Rr]-\d+\.\d+\.\d+$', os.path.basename(rHome)):
236                             rVer = os.path.basename(rHome)[2:]
237
238                         # If that did not work, look for a file called
239                         # README.R-X.Y.Z in the R_HOME directory, where X.Y.Z
240                         # is the version number.
241                         
242                         else:
243                             import glob
244                             files = glob.glob(os.path.join(rHome, 'README.R-[0123456789].*.*'))
245                             if len(files) > 0 and re.match('^\d+\.\d+\.\d+$', os.path.basename(files[0])[9:]) is not None:
246                                 rVer = os.path.basename(files[0])[9:]
247
248             except Exception, e:
249                 logFile.write('Failed to get R version number: %s: %s\n' % (str(e.__class__.__name__), str(e)))
250             else:
251                 logFile.write('rVer = %s\n' % rVer)
252
253             # Download the ClientConfiguration.ini file from the well-known
254             # location and look up the URL to the XML-RPC web service that
255             # will accept the installation report.
256             #
257             # In order to prevent this script from hanging if the Duke
258             # servers are not working, we set the default socket timeout
259             # to 60 seconds. (With Python, there is no way to set it on a
260             # per-connection basis, at least using urllib and xmlrpclib.)
261             # But Python 2.4 contains a bug that causes this to break SSL
262             # support (see http://bugs.python.org/issue1153016). So, on
263             # 2.4, convert https URLs to http URLs.
264
265             logFile.write('Setting the socket default timeout to 60.\n')
266
267             socket.setdefaulttimeout(60)
268
269             if sys.version_info[0] == 2 and sys.version_info[1] == 4:
270                 clientINIURL = 'http://code.env.duke.edu/projects/mget/statistics/client.ini'
271             else:
272                 clientINIURL = 'https://code.env.duke.edu/projects/mget/statistics/client.ini'
273
274             logFile.write('clientINIURL = %s\n' % clientINIURL)
275
276             try:
277                 iniFileError = 'Step 1: '
278                 config = ConfigParser.ConfigParser()
279                 logFile.write('Opening the clientINIURL.\n')
280                 iniFileError = 'Step 2: '
281                 f = urllib.urlopen(clientINIURL)
282                 try:
283                     logFile.write('Parsing the INI file.\n')
284                     iniFileError = 'Step 3: '
285                     config.readfp(f)
286                 finally:
287                     iniFileError = 'Step 4: '
288                     f.close()
289                 logFile.write('Getting the URL from the parsed INI file.\n')
290                 iniFileError = 'Step 5: '
291                 serverURL = config.get('V1', 'URL')
292             except Exception, e:
293                 logFile.write('Failed to get the MGET server URL: %s: %s. The default URL will be used.\n' % (str(e.__class__.__name__), str(e)))
294                 serverURL = 'http://mgetstats.nicholas.duke.edu:8001'
295                 iniFileError = '%s%s: %s' % (iniFileError, str(e.__class__.__name__), str(e))
296             else:
297                 iniFileError = None
298
299             logFile.write('serverURL = %s\n' % serverURL)
300
301             if sys.version_info[0] == 2 and sys.version_info[1] == 4 and serverURL.startswith('https:'):
302                 logFile.write('Changing serverURL to http from https because we\'re running on Python 2.4.\n')
303                 serverURL = 'http:' + serverURL[6:]
304
305             # Send the report to the server by calling its XML-RPC web
306             # service.
307
308             logFile.write('Instantiating the xmlrpclib.Server object.\n')
309             s = xmlrpclib.Server(serverURL, allow_none=True)
310            
311             logFile.write('Calling ReportMGETInstallationV2 on the server.\n')
312             s.ReportMGETInstallationV2(clientID, shortcuts, arcToolbox, comClasses, mgetVer, '.'.join(map(str, sys.version_info[0:3])), arcVer, rVer, iniFileError)
313
314         # If we got any kind of exception, write it to the log file.
315
316         except:
317             try:
318                 logFile.write('Caught an exception:\n')
319                 logFile.write('\n')
320                 logFile.write(traceback.format_exc())
321             except:
322                 pass
323             raise
324
325     # Close the log file.
326
327     finally:
328         try:
329             logFile.close()
330         except:
331             pass
332
333     # If we got to here, we succesfully sent the report to the server.
334     # Delete the log file.
335
336     os.remove(logFilePath)
337
338 if __name__ == '__main__':
339     main()
Note: See TracBrowser for help on using the browser.