diff --git a/samples/tz/README b/samples/tz/README new file mode 100644 index 0000000..ecee023 --- /dev/null +++ b/samples/tz/README @@ -0,0 +1,33 @@ +This is an example program that can run as a power +management hook to set the timezone on the computer +based on the user's location, as determined by Google +Latitude. To use this application you will need Google +Latitude running on a mobile device. + +Installation +============ + The google-api-python-client library will need to +be installed. + +$ sudo python setup.py install + +Then you will need to install the tznever application: + +$ sudo cp tznever /usr/sbin/tznever + +And then add it in as a power management hook: + +$ sudo ln -s /usr/sbin/tznever /etc/pm/sleep.d/45tznever + +Once that is done you need to run tznever once from the +the command line to tie it to your Latitude account: + +$ sudo tznever + +After that, every time your laptop resumes it will +check you Latitude location and set the timezone +accordingly. + +TODO +==== +1. What about stale Latitude data? diff --git a/samples/tz/tznever b/samples/tz/tznever new file mode 100755 index 0000000..b9331bb --- /dev/null +++ b/samples/tz/tznever @@ -0,0 +1,289 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright 2010 Google Inc. All Rights Reserved. +# Portions copyright PSF License +# http://code.activestate.com/recipes/278731-creating-a-daemon-the-python-way/ + +"""A pm-action hook for setting timezone. + +Uses the Google Latitude API and the geonames.org +API to find your cellphones latitude and longitude +and from the determine the timezone you are in, +and then sets the computer's timezone to that. +""" + +__author__ = 'jcgregorio@google.com (Joe Gregorio)' + + +from apiclient.discovery import build + +import httplib2 +import os +import pickle +import pprint +import subprocess +import sys +import time +import uritemplate + +from apiclient.anyjson import simplejson +from apiclient.discovery import build +from apiclient.oauth import FlowThreeLegged +from apiclient.ext.authtools import run +from apiclient.ext.file import Storage + +# Uncomment to get detailed logging +# httplib2.debuglevel = 4 + +# URI Template to convert latitude and longitude into a timezone +GEONAMES = 'http://api.geonames.org/timezoneJSON?lat={lat}&lng={long}&username=jcgregorio' +PID_FILE = '/var/lock/tznever.pid' +CACHE = '/var/local/tznever/.cache' + +# Default daemon parameters. +# File mode creation mask of the daemon. +UMASK = 0 + +# Default working directory for the daemon. +WORKDIR = "/" + +# Default maximum for the number of available file descriptors. +MAXFD = 1024 + +# The standard I/O file descriptors are redirected to /dev/null by default. +if (hasattr(os, "devnull")): + REDIRECT_TO = os.devnull +else: + REDIRECT_TO = "/dev/null" + + +def main(): + storage = Storage('/var/local/tznever/latitude_credentials.dat') + credentials = storage.get() + if len(sys.argv) == 1: + if credentials is None or credentials.invalid == True: + auth_discovery = build('latitude', 'v1').auth_discovery() + flow = FlowThreeLegged(auth_discovery, + consumer_key='m-buzz.appspot.com', + consumer_secret='NQEHb4eU6GkjjFGe1MD5W6IC', + user_agent='tz-never/1.0', + domain='m-buzz.appspot.com', + scope='https://www.googleapis.com/auth/latitude', + xoauth_displayname='TZ Never Again', + location='current', + granularity='city' + ) + + credentials = run(flow, storage) + else: + print "You are already authorized" + else: + if credentials is None or credentials.invalid == True: + print "This app, tznever, is not authorized. Run from the command-line to re-authorize." + os.exit(1) + + if len(sys.argv) > 1 and sys.argv[1] in ['hibernate', 'suspend']: + print "Hibernating" + # Kill off the possibly still running process by its pid + if os.path.isfile(PID_FILE): + f = file(PID_FILE, 'r') + pid = f.read() + f.close() + cmdline = ['/bin/kill', '-2', pid] + subprocess.Popen(cmdline) + os.unlink(PID_FILE) + elif len(sys.argv) > 1 and sys.argv[1] in ['thaw', 'resume']: + print "Resuming" + # write our pid out + f = file(PID_FILE, 'w') + f.write(str(os.getpid())) + f.close() + + success = False + first_time = True + while not success: + try: + if not first_time: + time.sleep(5) + else: + first_time = False + print "Daemonizing so as not to gum up the works." + createDaemon() + # rewrite the PID file with our new PID + f = file(PID_FILE, 'w') + f.write(str(os.getpid())) + f.close() + http = httplib2.Http(CACHE) + http = credentials.authorize(http) + + service = build('latitude', 'v1', http=http) + + location = service.currentLocation().get(granularity='city').execute() + position = { + 'lat': str(location['latitude']), + 'long': str(location['longitude']) + } + http2 = httplib2.Http(CACHE) + resp, content = http2.request(uritemplate.expand(GEONAMES, position)) + geodata = simplejson.loads(content) + tz = geodata['timezoneId'] + f = file('/etc/timezone', 'w') + f.write(tz) + f.close() + cmdline = 'dpkg-reconfigure -f noninteractive tzdata'.split(' ') + subprocess.Popen(cmdline) + success = True + except httplib2.ServerNotFoundError, e: + print "still not connected, sleeping" + except KeyboardInterrupt, e: + if os.path.isfile(PID_FILE): + os.unlink(PID_FILE) + success = True + # clean up pid file + if os.path.isfile(PID_FILE): + os.unlink(PID_FILE) + + +def createDaemon(): + """Detach a process from the controlling terminal and run it in the + background as a daemon. + """ + + try: + # Fork a child process so the parent can exit. This returns control to + # the command-line or shell. It also guarantees that the child will not + # be a process group leader, since the child receives a new process ID + # and inherits the parent's process group ID. This step is required + # to insure that the next call to os.setsid is successful. + pid = os.fork() + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if (pid == 0): # The first child. + # To become the session leader of this new session and the process group + # leader of the new process group, we call os.setsid(). The process is + # also guaranteed not to have a controlling terminal. + os.setsid() + + # Is ignoring SIGHUP necessary? + # + # It's often suggested that the SIGHUP signal should be ignored before + # the second fork to avoid premature termination of the process. The + # reason is that when the first child terminates, all processes, e.g. + # the second child, in the orphaned group will be sent a SIGHUP. + # + # "However, as part of the session management system, there are exactly + # two cases where SIGHUP is sent on the death of a process: + # + # 1) When the process that dies is the session leader of a session that + # is attached to a terminal device, SIGHUP is sent to all processes + # in the foreground process group of that terminal device. + # 2) When the death of a process causes a process group to become + # orphaned, and one or more processes in the orphaned group are + # stopped, then SIGHUP and SIGCONT are sent to all members of the + # orphaned group." [2] + # + # The first case can be ignored since the child is guaranteed not to have + # a controlling terminal. The second case isn't so easy to dismiss. + # The process group is orphaned when the first child terminates and + # POSIX.1 requires that every STOPPED process in an orphaned process + # group be sent a SIGHUP signal followed by a SIGCONT signal. Since the + # second child is not STOPPED though, we can safely forego ignoring the + # SIGHUP signal. In any case, there are no ill-effects if it is ignored. + # + # import signal # Set handlers for asynchronous events. + # signal.signal(signal.SIGHUP, signal.SIG_IGN) + + try: + # Fork a second child and exit immediately to prevent zombies. This + # causes the second child process to be orphaned, making the init + # process responsible for its cleanup. And, since the first child is + # a session leader without a controlling terminal, it's possible for + # it to acquire one by opening a terminal in the future (System V- + # based systems). This second fork guarantees that the child is no + # longer a session leader, preventing the daemon from ever acquiring + # a controlling terminal. + pid = os.fork() # Fork a second child. + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if (pid == 0): # The second child. + # Since the current working directory may be a mounted filesystem, we + # avoid the issue of not being able to unmount the filesystem at + # shutdown time by changing it to the root directory. + os.chdir(WORKDIR) + # We probably don't want the file mode creation mask inherited from + # the parent, so we give the child complete control over permissions. + os.umask(UMASK) + else: + # exit() or _exit()? See below. + os._exit(0) # Exit parent (the first child) of the second child. + else: + # exit() or _exit()? + # _exit is like exit(), but it doesn't call any functions registered + # with atexit (and on_exit) or any registered signal handlers. It also + # closes any open file descriptors. Using exit() may cause all stdio + # streams to be flushed twice and any temporary files may be unexpectedly + # removed. It's therefore recommended that child branches of a fork() + # and the parent branch(es) of a daemon use _exit(). + os._exit(0) # Exit parent of the first child. + + # Close all open file descriptors. This prevents the child from keeping + # open any file descriptors inherited from the parent. There is a variety + # of methods to accomplish this task. Three are listed below. + # + # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum + # number of open file descriptors to close. If it doesn't exists, use + # the default value (configurable). + # + # try: + # maxfd = os.sysconf("SC_OPEN_MAX") + # except (AttributeError, ValueError): + # maxfd = MAXFD + # + # OR + # + # if (os.sysconf_names.has_key("SC_OPEN_MAX")): + # maxfd = os.sysconf("SC_OPEN_MAX") + # else: + # maxfd = MAXFD + # + # OR + # + # Use the getrlimit method to retrieve the maximum file descriptor number + # that can be opened by this process. If there is not limit on the + # resource, use the default value. + # + import resource # Resource usage information. + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if (maxfd == resource.RLIM_INFINITY): + maxfd = MAXFD + + # Iterate through and close all file descriptors. + for fd in range(0, maxfd): + try: + os.close(fd) + except OSError: # ERROR, fd wasn't open to begin with (ignored) + pass + + # Redirect the standard I/O file descriptors to the specified file. Since + # the daemon has no controlling terminal, most daemons redirect stdin, + # stdout, and stderr to /dev/null. This is done to prevent side-effects + # from reads and writes to the standard I/O file descriptors. + + # This call to open is guaranteed to return the lowest file descriptor, + # which will be 0 (stdin), since it was closed above. + os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) + + # Duplicate standard input to standard output and standard error. + os.dup2(0, 1) # standard output (1) + os.dup2(0, 2) # standard error (2) + + return(0) + +if __name__ == '__main__': + main() + + +