├── .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 | --------------------------------------------------------------------------------