├── .gitignore
├── LICENSE
├── README
├── ga.py
├── ga_app
├── __init__.py
├── models.py
├── templatetags
│ ├── __init__.py
│ └── ga_mobile.py
├── urls.py
└── views.py
├── ga_mobile
├── ga_mobile_server.py
├── messaging.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2012 Peter McLachlan, Mobify Research & Development Inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | This project is licensed under the MIT open source license. See the LICENSE file for more details.
2 |
3 | This package includes:
4 |
5 | 1) A direct translation of the Google Analytics for Mobile PHP file ga.php to Python (ga.py). You can probably integrate this into your own setup by importing 'track_page_view' and providing it with the appropriate WSGI request environment. (From Django, you could probably pass in request.META directly, for example.)
6 |
7 | 2) A FLUP based FastCGI implementation in ga_mobile_server.py. Set up nginx or Apache to call this.
8 |
9 | 3) A startup script for the FLUP server in ga_mobile.
10 |
11 | 4) A django app with a template tag you can use on your django site.
12 |
13 | The Django app uses 2 django settings:
14 |
15 | ``GA_MOBILE_PATH`` path (including leading /) to location of your tracking CGI.
16 | ``GA_MOBILE_ACCOUNT``: your GA mobile account number such as MO-XXXXXX-XX
17 |
18 | To use the django app:
19 | add ga_app to your INSTALLED_SETTINGS
20 | add a url to ga_app.urls:
21 | (r'^ga/', include('ga_app.urls')),
--------------------------------------------------------------------------------
/ga.py:
--------------------------------------------------------------------------------
1 | """
2 | Python implementation of ga.php.
3 | """
4 | import re
5 | from hashlib import md5
6 | from random import randint
7 | import struct
8 | import httplib2
9 | import time
10 | from urllib import unquote, quote
11 | from Cookie import SimpleCookie, CookieError
12 | from messaging import stdMsg, dbgMsg, errMsg, setDebugging
13 | import uuid
14 |
15 | try:
16 | # The mod_python version is more efficient, so try importing it first.
17 | from mod_python.util import parse_qsl
18 | except ImportError:
19 | from cgi import parse_qsl
20 |
21 | VERSION = "4.4sh"
22 | COOKIE_NAME = "__utmmobile"
23 | COOKIE_PATH = "/"
24 | COOKIE_USER_PERSISTENCE = 63072000
25 |
26 | GIF_DATA = reduce(lambda x,y: x + struct.pack('B', y),
27 | [0x47,0x49,0x46,0x38,0x39,0x61,
28 | 0x01,0x00,0x01,0x00,0x80,0x00,
29 | 0x00,0x00,0x00,0x00,0xff,0xff,
30 | 0xff,0x21,0xf9,0x04,0x01,0x00,
31 | 0x00,0x00,0x00,0x2c,0x00,0x00,
32 | 0x00,0x00,0x01,0x00,0x01,0x00,
33 | 0x00,0x02,0x01,0x44,0x00,0x3b], '')
34 |
35 | # WHITE GIF:
36 | # 47 49 46 38 39 61
37 | # 01 00 01 00 80 ff
38 | # 00 ff ff ff 00 00
39 | # 00 2c 00 00 00 00
40 | # 01 00 01 00 00 02
41 | # 02 44 01 00 3b
42 |
43 | # TRANSPARENT GIF:
44 | # 47 49 46 38 39 61
45 | # 01 00 01 00 80 00
46 | # 00 00 00 00 ff ff
47 | # ff 21 f9 04 01 00
48 | # 00 00 00 2c 00 00
49 | # 00 00 01 00 01 00
50 | # 00 02 01 44 00 3b
51 |
52 | def get_ip(remote_address):
53 | # dbgMsg("remote_address: " + str(remote_address))
54 | if not remote_address:
55 | return ""
56 | matches = re.match('^([^.]+\.[^.]+\.[^.]+\.).*', remote_address)
57 | if matches:
58 | return matches.groups()[0] + "0"
59 | else:
60 | return ""
61 |
62 | def get_visitor_id(guid, account, user_agent, cookie):
63 | """
64 | // Generate a visitor id for this hit.
65 | // If there is a visitor id in the cookie, use that, otherwise
66 | // use the guid if we have one, otherwise use a random number.
67 | """
68 | if cookie:
69 | return cookie
70 | message = ""
71 | if guid:
72 | # Create the visitor id using the guid.
73 | message = guid + account
74 | else:
75 | # otherwise this is a new user, create a new random id.
76 | message = user_agent + str(uuid.uuid4())
77 | md5String = md5(message).hexdigest()
78 | return "0x" + md5String[:16]
79 |
80 | def get_random_number():
81 | """
82 | // Get a random number string.
83 | """
84 | return str(randint(0, 0x7fffffff))
85 |
86 | def write_gif_data():
87 | """
88 | // Writes the bytes of a 1x1 transparent gif into the response.
89 |
90 | Returns a dictionary with the following values:
91 |
92 | { 'response_code': '200 OK',
93 | 'response_headers': [(Header_key, Header_value), ...]
94 | 'response_body': 'binary data'
95 | }
96 | """
97 | response = {'response_code': '204 No Content',
98 | 'response_headers': [('Content-Type', 'image/gif'),
99 | ('Cache-Control', 'private, no-cache, no-cache=Set-Cookie, proxy-revalidate'),
100 | ('Pragma', 'no-cache'),
101 | ('Expires', 'Wed, 17 Sep 1975 21:32:10 GMT'),
102 | ],
103 | # 'response_body': GIF_DATA,
104 | 'response_body': '',
105 | }
106 | return response
107 |
108 | def send_request_to_google_analytics(utm_url, environ):
109 | """
110 | // Make a tracking request to Google Analytics from this server.
111 | // Copies the headers from the original request to the new one.
112 | // If request containg utmdebug parameter, exceptions encountered
113 | // communicating with Google Analytics are thown.
114 | """
115 | http = httplib2.Http()
116 | try:
117 | resp, content = http.request(utm_url,
118 | "GET",
119 | headers={'User-Agent': environ.get('HTTP_USER_AGENT', 'Unknown'),
120 | 'Accepts-Language:': environ.get("HTTP_ACCEPT_LANGUAGE",'')}
121 | )
122 | # dbgMsg("success")
123 | except httplib2.HttpLib2Error, e:
124 | errMsg("fail: %s" % utm_url)
125 | if environ['GET'].get('utmdebug'):
126 | raise Exception("Error opening: %s" % utm_url)
127 | else:
128 | pass
129 |
130 |
131 | def parse_cookie(cookie):
132 | """ borrowed from django.http """
133 | if cookie == '':
134 | return {}
135 | try:
136 | c = SimpleCookie()
137 | c.load(cookie)
138 | except CookieError:
139 | # Invalid cookie
140 | return {}
141 |
142 | cookiedict = {}
143 | for key in c.keys():
144 | cookiedict[key] = c.get(key).value
145 | return cookiedict
146 |
147 | def track_page_view(environ):
148 | """
149 | // Track a page view, updates all the cookies and campaign tracker,
150 | // makes a server side request to Google Analytics and writes the transparent
151 | // gif byte data to the response.
152 | """
153 | time_tup = time.localtime(time.time() + COOKIE_USER_PERSISTENCE)
154 |
155 | # set some useful items in environ:
156 | environ['COOKIES'] = parse_cookie(environ.get('HTTP_COOKIE', ''))
157 | environ['GET'] = {}
158 | for key, value in parse_qsl(environ.get('QUERY_STRING', ''), True):
159 | environ['GET'][key] = value # we only have one value per key name, right? :)
160 | x_utmac = environ['GET'].get('x_utmac', None)
161 |
162 | domain = environ.get('HTTP_HOST', '')
163 |
164 | # Get the referrer from the utmr parameter, this is the referrer to the
165 | # page that contains the tracking pixel, not the referrer for tracking
166 | # pixel.
167 | document_referer = environ['GET'].get("utmr", "")
168 | if not document_referer or document_referer == "0":
169 | document_referer = "-"
170 | else:
171 | document_referer = unquote(document_referer)
172 |
173 | document_path = environ['GET'].get('utmp', "")
174 | if document_path:
175 | document_path = unquote(document_path)
176 |
177 | account = environ['GET'].get('utmac', '')
178 | user_agent = environ.get("HTTP_USER_AGENT", '')
179 |
180 | # // Try and get visitor cookie from the request.
181 | cookie = environ['COOKIES'].get(COOKIE_NAME)
182 |
183 | visitor_id = get_visitor_id(environ.get("HTTP_X_DCMGUID", ''), account, user_agent, cookie)
184 |
185 | # // Always try and add the cookie to the response.
186 | cookie = SimpleCookie()
187 | cookie[COOKIE_NAME] = visitor_id
188 | morsel = cookie[COOKIE_NAME]
189 | morsel['expires'] = time.strftime('%a, %d-%b-%Y %H:%M:%S %Z', time_tup)
190 | morsel['path'] = COOKIE_PATH
191 |
192 | utm_gif_location = "http://www.google-analytics.com/__utm.gif"
193 |
194 | for utmac in [account, x_utmac]:
195 | if not utmac:
196 | continue # ignore empty utmacs
197 | # // Construct the gif hit url.
198 | utm_url = utm_gif_location + "?" + \
199 | "utmwv=" + VERSION + \
200 | "&utmn=" + get_random_number() + \
201 | "&utmhn=" + quote(domain) + \
202 | "&utmsr=" + environ['GET'].get('utmsr', '') + \
203 | "&utme=" + environ['GET'].get('utme', '') + \
204 | "&utmr=" + quote(document_referer) + \
205 | "&utmp=" + quote(document_path) + \
206 | "&utmac=" + utmac + \
207 | "&utmcc=__utma%3D999.999.999.999.999.1%3B" + \
208 | "&utmvid=" + visitor_id + \
209 | "&utmip=" + get_ip(environ.get("REMOTE_ADDR",''))
210 | # dbgMsg("utm_url: " + utm_url)
211 | send_request_to_google_analytics(utm_url, environ)
212 |
213 | # // If the debug parameter is on, add a header to the response that contains
214 | # // the url that was used to contact Google Analytics.
215 | headers = [('Set-Cookie', str(cookie).split(': ')[1])]
216 | if environ['GET'].get('utmdebug', False):
217 | headers.append(('X-GA-MOBILE-URL', utm_url))
218 |
219 | # Finally write the gif data to the response
220 | response = write_gif_data()
221 | response_headers = response['response_headers']
222 | response_headers.extend(headers)
223 | return response
--------------------------------------------------------------------------------
/ga_app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/b1tr0t/Google-Analytics-for-Mobile--python-/806899892ed568ee16b35c09c015dd95b167d62a/ga_app/__init__.py
--------------------------------------------------------------------------------
/ga_app/models.py:
--------------------------------------------------------------------------------
1 | #Blank model so django can understand we want to be an app.
2 |
--------------------------------------------------------------------------------
/ga_app/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/b1tr0t/Google-Analytics-for-Mobile--python-/806899892ed568ee16b35c09c015dd95b167d62a/ga_app/templatetags/__init__.py
--------------------------------------------------------------------------------
/ga_app/templatetags/ga_mobile.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.conf import settings
4 | from django import template
5 | from random import randint
6 | from urllib import quote_plus
7 |
8 | register = template.Library()
9 |
10 | @register.simple_tag
11 | def ga_mobile(request):
12 | """
13 | Returns the image link for tracking this mobile request.
14 |
15 | Retrieves two configurations from django.settings:
16 |
17 | GA_MOBILE_PATH: path (including leading /) to location of your tracking CGI.
18 | GA_MOBILE_ACCOUNT: your GA mobile account number such as MO-XXXXXX-XX
19 |
20 | Note: the host for the request is by default the same as the HTTP_HOST of the request.
21 | Override this by setting GA_MOBILE_HOST in settings.
22 | """
23 |
24 | ga_mobile_path = settings.GA_MOBILE_PATH
25 | ga_mobile_account = settings.GA_MOBILE_ACCOUNT
26 | r = str(randint(0, 0x7fffffff))
27 |
28 | if hasattr(settings, 'GA_MOBILE_HOST'):
29 | host = settings.GA_MOBILE_HOST
30 | else:
31 | host = request.META.get('HTTP_HOST', 'localhost')
32 | referer = quote_plus(request.META.get('HTTP_REFERER', ''))
33 | path = quote_plus(request.META.get('REQUEST_URI', ''))
34 |
35 | src = 'http://' + host + ga_mobile_path + \
36 | "?utmac=" + ga_mobile_account + \
37 | "&utmn=" + r + \
38 | "&utmr=" + referer + \
39 | "&utmp=" + path + \
40 | "&guid=ON"
41 |
42 | return '
' % src
--------------------------------------------------------------------------------
/ga_app/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | urlpatterns = patterns('',
4 | url(r'^track/$', 'ga_app.views.track', name='webcube_home'),
5 | )
--------------------------------------------------------------------------------
/ga_app/views.py:
--------------------------------------------------------------------------------
1 | from ga import send_request_to_google_analytics, get_random_number, get_visitor_id, get_ip, VERSION, COOKIE_NAME, COOKIE_PATH, COOKIE_USER_PERSISTENCE, GIF_DATA
2 |
3 | import httplib2
4 | import time
5 | from urllib import unquote, quote
6 |
7 | from urllib import unquote, quote
8 | from django.http import HttpResponse
9 |
10 | def track(request):
11 | """
12 | Track a page view, updates all the cookies and campaign tracker,
13 | makes a server side request to Google Analytics and writes the transparent
14 | gif byte data to the response.
15 | """
16 | response = HttpResponse()
17 |
18 | time_tup = time.localtime(time.time() + COOKIE_USER_PERSISTENCE)
19 |
20 | # set some useful items in environ:
21 | x_utmac = request.GET.get('x_utmac', None)
22 |
23 | domain = request.META.get('HTTP_HOST', '')
24 |
25 | # Get the referrer from the utmr parameter, this is the referrer to the
26 | # page that contains the tracking pixel, not the referrer for tracking
27 | # pixel.
28 | document_referer = request.GET.get("utmr", "")
29 | if not document_referer or document_referer == "0":
30 | document_referer = "-"
31 | else:
32 | document_referer = unquote(document_referer)
33 |
34 | document_path = request.GET.get('utmp', "")
35 | if document_path:
36 | document_path = unquote(document_path)
37 |
38 | account = request.GET.get('utmac', '')
39 | user_agent = request.META.get("HTTP_USER_AGENT", '')
40 |
41 | # Try and get visitor cookie from the request.
42 | cookie = request.COOKIES.get(COOKIE_NAME, None)
43 |
44 | visitor_id = get_visitor_id(request.META.get("HTTP_X_DCMGUID", ''), account, user_agent, cookie)
45 |
46 | utm_gif_location = "http://www.google-analytics.com/__utm.gif"
47 |
48 | for utmac in [account, x_utmac]:
49 | if not utmac:
50 | continue # ignore empty utmacs
51 | # Construct the gif hit url.
52 | utm_url = utm_gif_location + "?" + \
53 | "utmwv=" + VERSION + \
54 | "&utmn=" + get_random_number() + \
55 | "&utmhn=" + quote(domain) + \
56 | "&utmsr=" + request.GET.get('utmsr', '') + \
57 | "&utme=" + request.GET.get('utme', '') + \
58 | "&utmr=" + quote(document_referer) + \
59 | "&utmp=" + quote(document_path) + \
60 | "&utmac=" + utmac + \
61 | "&utmcc=__utma%3D999.999.999.999.999.1%3B" + \
62 | "&utmvid=" + visitor_id + \
63 | "&utmip=" + get_ip(request.META.get("REMOTE_ADDR",''))
64 | send_request_to_google_analytics(utm_url, request.META)
65 |
66 | # add the cookie to the response.
67 | response.set_cookie(COOKIE_NAME, value=visitor_id, path=COOKIE_PATH)
68 | # If the debug parameter is on, add a header to the response that contains
69 | # the url that was used to contact Google Analytics.
70 | if request.GET.get('utmdebug', False):
71 | response['X-GA-MOBILE-URL'] = utm_url
72 |
73 | response_headers =[('Content-Type', 'image/gif'),
74 | ('Cache-Control', 'private, no-cache, no-cache=Set-Cookie, proxy-revalidate'),
75 | ('Pragma', 'no-cache'),
76 | ('Expires', 'Wed, 17 Sep 1975 21:32:10 GMT')]
77 | for header in response_headers:
78 | key, value = header
79 | response[key] = value
80 | response.content = GIF_DATA
81 |
82 | return response
--------------------------------------------------------------------------------
/ga_mobile:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | # set -x
3 | ### BEGIN INIT INFO
4 | # Provides: GA Mobile FastCGI server using FLUP providing Google analytics transparent GIF
5 | # Required-Start: networking
6 | # Required-Stop: networking
7 | # Default-Start: 2 3 4 5
8 | # Default-Stop: S 0 1 6
9 | # Short-Description: Google analytics mobile
10 | # Description: GA Mobile FastCGI server using FLUP providing Google analytics mobile. Make sure the user specified
11 | # in 'RUN_AS' can WRITE to the directories specified for: PIDFILE,LOGDIR,PYTHON_EGG_CACHE
12 | #
13 | #
14 | ### END INIT INFO
15 | #
16 | # Author: Peter McLachlan
17 | # .
18 | #
19 |
20 | #### SERVER SPECIFIC CONFIGURATION
21 | SERVER_ROOT="/services/ga_mobile"
22 | HOST=`hostname`-i
23 | PORT=8009
24 | RUN_AS=www-data
25 | PIDFILE=$SERVER_ROOT/pid/ga_mobile.pid
26 | LOGDIR=$SERVER_ROOT/logs
27 | STDOUTLOG=$LOGDIR/ga_mobile_serv.log
28 | STDERRLOG=$LOGDIR/ga_mobile_serv.err.log
29 | MAXREQUESTS=30
30 | MINSPARE=4
31 | MAXSPARE=10
32 | MAXCHILDREN=20
33 |
34 | export PYTHON_EGG_CACHE=$SERVER_ROOT/cache
35 |
36 | #### You shouldn't need to mess with stuff after this line
37 | EXEC=$SERVER_ROOT/ga_mobile_server.py
38 |
39 | set -e
40 |
41 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
42 | DESC="GA Mobile FastCGI servers"
43 | NAME=$0
44 | SCRIPTNAME=/etc/init.d/$NAME
45 |
46 | SERVER=$2
47 | #
48 | # Function that starts the daemon/service.
49 | #
50 | d_start()
51 | {
52 | echo -n "Google Mobile GIF Server : $HOST:$PORT"
53 | if [[ -n `ps aux | grep manage.py | grep port=$PORT | head -1` ]]; then
54 | echo -n " already running"
55 | else
56 | su - www-data -c "$EXEC --host $HOST --port $PORT --pidfile $PIDFILE --maxchildren $MAXCHILDREN --maxrequests $MAXREQUESTS --minspare $MINSPARE --maxspare $MAXSPARE 2>$STDERRLOG >$STDOUTLOG &"
57 | fi
58 | }
59 |
60 | #
61 | # Function that stops the daemon/service.
62 | #
63 | d_stop() {
64 | PID=`cat $PIDFILE | tr -d "\n"`
65 | echo "-- Killing Google Analytics FastCGI, PID: $PID"
66 | kill $PID
67 | }
68 |
69 | ACTION="$1"
70 | case "$ACTION" in
71 | start)
72 | echo "Starting $DESC: $NAME"
73 | d_start
74 | echo "."
75 | ;;
76 |
77 | stop)
78 | echo "Stopping $DESC: $NAME"
79 | d_stop
80 | echo "."
81 | ;;
82 |
83 | restart|force-reload)
84 | echo "Restarting $DESC: $NAME"
85 | d_stop
86 | sleep 5
87 | d_start
88 | echo "-- DONE"
89 | ;;
90 | *)
91 | echo "Usage: $NAME {start|stop|restart|force-reload}" >&2
92 | exit 3
93 | ;;
94 | esac
95 |
96 |
97 | exit 0
98 |
--------------------------------------------------------------------------------
/ga_mobile_server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # encoding: utf-8
3 | """
4 | server.py
5 |
6 | Serves up google analytics 1x1.gif transparent tracking images and notifies Google Analytics of clicks.
7 |
8 | Created by Peter McLachlan on 2009-07-19.
9 | Copyright (c) 2009 Mobify. All rights reserved.
10 | """
11 |
12 | import sys
13 | import os
14 | import getopt
15 | from urlparse import urlparse
16 | from flup.server.fcgi_fork import WSGIServer
17 | from socket import gethostname
18 | from datetime import datetime, timedelta
19 | from ga import track_page_view
20 |
21 | from messaging import stdMsg, dbgMsg, errMsg, setDebugging
22 | setDebugging(1)
23 |
24 | MINSPARE = 3
25 | MAXSPARE = 7
26 | MAXCHILDREN = 50
27 | MAXREQUESTS = 500
28 | HOST = '127.0.0.1'
29 | PORT = 8009
30 | PIDFILE = '/tmp/g_analytic_server.pid'
31 | HELP_MESSAGE = """
32 |
33 | This is some help.
34 |
35 | """
36 |
37 | class Usage(Exception):
38 | def __init__(self, msg):
39 | self.msg = msg
40 |
41 | def gserve(environ, start_response):
42 | try:
43 | response = track_page_view(environ)
44 | except Exception, e:
45 | print e
46 | start_response("503 Service Unavailable", [])
47 | return ["Exception loading GA code
%s
" % str(e)]
48 | start_response(response['response_code'], response['response_headers'])
49 | return [response['response_body']]
50 |
51 | def main(argv=None):
52 | host = HOST
53 | port = PORT
54 | pidfile = PIDFILE
55 | maxchildren = MAXCHILDREN
56 | maxrequests = MAXREQUESTS
57 | minspare = MINSPARE
58 | maxspare = MAXSPARE
59 |
60 | if argv is None:
61 | argv = sys.argv
62 | try:
63 | try:
64 | opts, args = getopt.getopt(argv[1:], "h", ["host=", "port=", 'pidfile=', 'maxchildren=',
65 | 'maxrequests=', 'minspare=', 'maxspare=', 'help'])
66 | except getopt.error, msg:
67 | raise Usage(msg)
68 | # option processing
69 | for option, value in opts:
70 | if option in ("-h", "--help"):
71 | raise Usage(HELP_MESSAGE)
72 | elif "--host" == option:
73 | host = value
74 | elif "--port" == option:
75 | port = int(value)
76 | elif "--pidfile" == option:
77 | pidfile = value
78 | elif "--maxchildren" == option:
79 | maxchildren = int(value)
80 | elif "--maxrequests" == option:
81 | maxrequests = int(value)
82 | elif "--minspare" == option:
83 | minspare = int(value)
84 | elif "--maxspare" == option:
85 | maxspare = int(value)
86 | except Usage, err:
87 | print >> sys.stderr, sys.argv[0].split("/")[-1] + ": " + str(err.msg)
88 | print >> sys.stderr, "\t for help use --help"
89 | return -2
90 |
91 | try:
92 | f = open(pidfile, 'w')
93 | f.write(str(os.getpid()) + '\n')
94 | f.close()
95 | except IOError, e:
96 | print "!! Error writing to pid file, check pid file path: %s" % pidfile
97 | return -1
98 |
99 | try:
100 | WSGIServer(gserve, bindAddress=(host, port), minSpare=minspare, maxSpare=maxspare, maxChildren=maxchildren, maxRequests=maxrequests).run()
101 | except Exception, e:
102 | print "!! WSGIServer raised exception"
103 | print e
104 |
105 |
106 | if __name__ == "__main__":
107 | sys.exit(main())
--------------------------------------------------------------------------------
/messaging.py:
--------------------------------------------------------------------------------
1 | #messaging.py
2 | #this is a module used for messaging. It allows multiple classes
3 | #to handle various types of messages. It should work on all python
4 | #versions >= 1.5.2
5 |
6 | import sys, string, exceptions
7 |
8 | #this flag determines whether debug output is sent to debug handlers themselves
9 | debug = 1
10 |
11 | def setDebugging(debugging):
12 | global debug
13 | debug = debugging
14 |
15 | class MessagingException(exceptions.Exception):
16 | """an exception class for any errors that may occur in
17 | a messaging function"""
18 | def __init__(self, args=None):
19 | self.args = args
20 |
21 | class FakeException(exceptions.Exception):
22 | """an exception that is thrown and then caught
23 | to get a reference to the current execution frame"""
24 | pass
25 |
26 |
27 | class MessageHandler:
28 | """All message handlers should inherit this class. Each method will be
29 | passed a string when the executing program passes calls a messaging function"""
30 | def handleStdMsg(self, msg):
31 | """do something with a standard message from the program"""
32 | pass
33 | def handleErrMsg(self, msg):
34 | """do something with an error message. This will already include the
35 | class, method, and line of the call"""
36 | pass
37 | def handleDbgMsg(self, msg):
38 | """do something with a debug message. This will already include the
39 | class, method, and line of the call"""
40 | pass
41 |
42 | class defaultMessageHandler(MessageHandler):
43 | """This is a default message handler. It simply spits all strings to
44 | standard out"""
45 | def handleStdMsg(self, msg):
46 | sys.stdout.write(msg + "\n")
47 | def handleErrMsg(self, msg):
48 | sys.stderr.write(msg + "\n")
49 | def handleDbgMsg(self, msg):
50 | sys.stdout.write(msg + "\n")
51 |
52 | #this keeps track of the handlers
53 | _messageHandlers = []
54 |
55 | #call this with the handler to register it for receiving messages
56 | def registerMessageHandler(handler):
57 | """we're not going to check for inheritance, but we should check to make
58 | sure that it has the correct methods"""
59 | for methodName in ["handleStdMsg", "handleErrMsg", "handleDbgMsg"]:
60 | try:
61 | getattr(handler, methodName)
62 | except:
63 | raise MessagingException, "The class " + handler.__class__.__name__ + " is missing a " + methodName + " method"
64 | _messageHandlers.append(handler)
65 |
66 |
67 | def getCallString(level):
68 | #this gets us the frame of the caller and will work
69 | #in python versions 1.5.2 and greater (there are better
70 | #ways starting in 2.1
71 | try:
72 | raise FakeException("this is fake")
73 | except Exception, e:
74 | #get the current execution frame
75 | f = sys.exc_info()[2].tb_frame
76 | #go back as many call-frames as was specified
77 | while level >= 0:
78 | f = f.f_back
79 | level = level-1
80 | #if there is a self variable in the caller's local namespace then
81 | #we'll make the assumption that the caller is a class method
82 | obj = f.f_locals.get("self", None)
83 | functionName = f.f_code.co_name
84 | if obj:
85 | callStr = obj.__class__.__name__+"::"+f.f_code.co_name+" (line "+str(f.f_lineno)+")"
86 | else:
87 | callStr = f.f_code.co_name+" (line "+str(f.f_lineno)+")"
88 | return callStr
89 |
90 | #send this message to all handlers of std messages
91 | def stdMsg(*args):
92 | stdStr = string.join(map(str, args), " ")
93 | for handler in _messageHandlers:
94 | handler.handleStdMsg(stdStr)
95 |
96 | #send this message to all handlers of error messages
97 | def errMsg(*args):
98 | errStr = "Error in "+getCallString(1)+" : "+string.join(map(str, args), " ")
99 | for handler in _messageHandlers:
100 | handler.handleErrMsg(errStr)
101 |
102 | #send this message to all handlers of debug messages
103 | def dbgMsg(*args):
104 | if not debug:
105 | return
106 | errStr = getCallString(1)+" : "+string.join(map(str, args), " ")
107 | for handler in _messageHandlers:
108 | handler.handleDbgMsg(errStr)
109 |
110 |
111 | registerMessageHandler(defaultMessageHandler())
112 | #end of messaging.py
113 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from distutils.core import setup
4 |
5 | setup(name='mobile_google_analytics',
6 | version='1.0',
7 | description='Google Analytics Python Module',
8 | author='b1tr0t',
9 | author_email='',
10 | url='https://github.com/b1tr0t/Google-Analytics-for-Mobile--python-',
11 | packages=['ga_app',],
12 | py_modules=['ga',]
13 | )
14 |
--------------------------------------------------------------------------------