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

Revision 546, 16.5 KB (checked in by jjr8, 3 years ago)

Merged [518-545] from Jason branch into the Trunk. This will be released as MGET 0.8a9.

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
35import ConfigParser
36import os
37import re
38import socket
39import sys
40import tempfile
41import time
42import traceback
43import urllib
44import xmlrpclib
45
46import pywintypes
47import win32api
48import win32con
49
50def 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
343if __name__ == '__main__':
344    main()
Note: See TracBrowser for help on using the browser.