# -*- coding: utf-8 -*-## whereami.py# whereami#"""Use the Core Location framework."""importosimportsysimporttimeimportoptparsefromoptparseimportOptionValueErrorfromcollectionsimportnamedtuplefromcollectionsimportOrderedDictimportwebbrowsertry:importCoreLocationexceptImportError:# CoreLocation attempts will fail.CoreLocation=NoneimportrequestsimportBeautifulSoupDEFAULT_TIMEOUT=3DEFAULT_RETRIES=10LOCATION_STRATEGIES=OrderedDict()CACHE_FILE=os.path.expanduser("~/.doko_cache")classLocation(namedtuple('Location','latitude longitude source')):precision=None@classmethoddefset_precision(cls,digits):cls.precision=digitsdefsafe_value(self,value):ifself.precision:returnround(value,self.precision)else:returnvaluedefsafe_longitude(self):returnself.safe_value(self.longitude)defsafe_latitude(self):returnself.safe_value(self.latitude)defraw(self):return"%s,%s"%(self.latitude,self.longitude)defrender(self):return"%s,%s"%(self.safe_latitude(),self.safe_longitude())def__repr__(self):return'Location(latitude=%s, longitude=%s, source=%s)'%(self.safe_latitude(),self.safe_longitude(),repr(self.source),)defdump(self,filename):withopen(filename,'w')asostream:ostream.write('%s,%s'%(self.render(),self.source))@classmethoddefload(cls,filename):withopen(filename,'r')asistream:cache=istream.read().strip()lat,lon,source=cache.split(",")returncls(float(lat),float(lon),'cache')# Important, define strategies in default resolution orderdeflocation_strategy(name):def_(fn):LOCATION_STRATEGIES[name]=fnreturn_classLocationServiceException(Exception):pass@location_strategy("cache")defcache_location(timeout=DEFAULT_TIMEOUT):""" Fetch and return current location from a filebacked cache, stored in ~/.doko_cache Cache is considered value for up to 30 minutes, but refreshed each time it is queried """thirty_mins=(60*30)ifnotos.path.exists(CACHE_FILE):returnlast_updated=os.stat(CACHE_FILE).st_mtimeiflast_updated+thirty_mins<time.time():returntry:l=Location.load(CACHE_FILE)exceptValueError:# Invalid content in cache file. Nuke it and start overos.unlink(CACHE_FILE)returnreturnlifCoreLocation:@location_strategy("corelocation")defcorelocation_location(timeout=DEFAULT_TIMEOUT):""" Fetch and return a Location from OS X Core Location, or throw a LocationServiceException trying. """m=CoreLocation.CLLocationManager.new()ifnotm.locationServicesEnabled():raiseLocationServiceException('location services not enabled -- check privacy settings in System Preferences'# noqa)ifnotm.locationServicesAvailable():raiseLocationServiceException('location services not available')m.startUpdatingLocation()CoreLocation.CFRunLoopStop(CoreLocation.CFRunLoopGetCurrent())l=m.location()# retry up to ten times, possibly sleeping between triesforiinxrange(DEFAULT_RETRIES):ifl:breaktime.sleep(float(timeout)/DEFAULT_RETRIES)CoreLocation.CFRunLoopStop(CoreLocation.CFRunLoopGetCurrent())l=m.location()ifnotl:raiseLocationServiceException('location could not be found -- is wifi enabled?')c=l.coordinate()returnLocation(c.latitude,c.longitude,'corelocation')@location_strategy("geoip")defgeobytes_location(timeout=DEFAULT_TIMEOUT):external_ip=requests.get('http://jsonip.com/').json['ip']try:resp=requests.post('http://www.geobytes.com/iplocator.htm?getlocation',data={'ipaddress':external_ip},timeout=timeout,)exceptrequests.exceptions.Timeout:raiseLocationServiceException('timeout fetching geoip location')try:s=BeautifulSoup.BeautifulSoup(resp.content)latitude=float(s.find('td',text='Latitude',).parent.findNext('input')['value'])longitude=float(s.find('td',text='Longitude').parent.findNext('input')['value'])exceptException:raiseLocationServiceException('error parsing geobytes page')returnLocation(latitude,longitude,'geoip')deflocation(strategy=None,timeout=DEFAULT_TIMEOUT,force=False):""" Detect your current location using one the available strategies. If you provide one by name, we use that. If force is True, back off to secondary strategies on failure. """ifnotstrategy:strategy=LOCATION_STRATEGIES.keys()[0]l=Nonelast_error=Noneremaining_strategies=LOCATION_STRATEGIES.copy()strategy_f=remaining_strategies.pop(strategy)try:l=strategy_f(timeout)exceptLocationServiceException,e:ifnotforce:raiselast_error=e.messageifnotl:forstrategy_finremaining_strategies.itervalues():try:l=strategy_f()exceptLocationServiceException,e:last_error=e.messageifnotl:raiseLocationServiceException(last_error)# success!ifl.source!='cache':l.dump(CACHE_FILE)returnldef_create_option_parser():usage= \
"""%prog [options]Use CoreServices to find your current geolocation as latitude and longitudecoordinates. Exits with status code 1 on failure."""# nopep8parser=optparse.OptionParser(usage)parser.add_option('--timeout',action='store',type='float',default=DEFAULT_TIMEOUT,help='Time to keep trying for if no location is found.')parser.add_option('--quiet',action='store_true',help='Suppress any error messages.')parser.add_option('--show',action='store_true',help='Show result on Google Maps in a browser.')parser.add_option('-f','--force',action='store_true',dest='force',help='Continue trying strategies if the first should fail')parser.add_option('--strategy',action='store',dest='strategy',help='Strategy for location lookup (corelocation|geoip)')parser.add_option('--precision',action='store',dest='precision',type=int,help='Store geodata with <precision> significant digits')parser.add_option('--cache',action='store_true',dest='cache',help='Consult a filebacked cache for up to 30 mins')parser.add_option('--show-strategy',action='store_true',help='Include the strategy which succeeded in the output')returnparserdefmain():argv=sys.argv[1:]parser=_create_option_parser()(options,args)=parser.parse_args(argv)ifargs:parser.print_help()sys.exit(1)ifoptions.strategyandoptions.strategynotinLOCATION_STRATEGIES:raiseOptionValueError("%s is not a valid strategy"%options.strategy)ifos.getenv("DOKO_PRECISION"):try:Location.set_precision(os.getenv("DOKO_PRECISION"))exceptValueError:raise"Invalid value in DOKO_PRECISION"ifoptions.precision:Location.set_precision(options.precision)ifnot(os.getenv("DOKO_CACHE")oroptions.cache):delLOCATION_STRATEGIES['cache']try:l=location(options.strategy,timeout=options.timeout,force=options.force)exceptLocationServiceException,e:ifnotoptions.quiet:print>>sys.stderr,e.messagesys.exit(1)ifoptions.show_strategy:printl.render(),'(%s)'%l.sourceelse:printl.render()ifoptions.show:webbrowser.open('https://maps.google.com/?q=%s'%str(l))