view multimerge.py @ 7:f2ecfb3e04ee

Check that the required section exists in configuration.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 04 Jul 2016 12:54:22 +0300
parents ee6bf617f839
children 8367463fe94d
line wrap: on
line source

#!/usr/bin/python
# coding=utf-8
###
### Google Calendar MultiMerge v0.000001
### (C) 2016 Matti 'ccr' Hamalainen <ccr@tnsp.org>
###
### Python 2.7 <= x < 3 required! Please refer to
### README.txt for information on other depencies.
###
import os
import sys
import signal
import re
import time
import datetime
import httplib2
import ConfigParser
import oauth2client
from oauth2client import client
from oauth2client import tools
from oauth2client import file
from googleapiclient import discovery


###
### Misc. helper functions
###

## Wrapper for print() that does not break when redirecting stdin/out
## because of piped output not having a defined encoding. We default
## to UTF-8 encoding in output here.
def gcm_print(smsg):
    gcm_msgbuf.append(smsg.encode("UTF-8"))
    if sys.stdout.encoding != None:
        print(smsg.encode(sys.stdout.encoding))
    else:
        print(smsg.encode("UTF-8"))


## Fatal errors
def gcm_fatal(smsg):
    gcm_print(u"ERROR: "+ smsg)
    sys.exit(1)


## Debug messages
def gcm_debug(smsg):
    if cfg.debug:
        gcm_print(u"DBG: "+ smsg)
    else:
        gcm_msgbuf.append(u"DBG: "+ smsg.encode("UTF-8"))


## Handle SIGINT signals here
def gcm_signal_handler(signal, frame):
    gcm_print("\nQuitting due to SIGINT / Ctrl+C!")
    sys.exit(0)


def gcm_get_credentials(mcfg):
    store = oauth2client.file.Storage(mcfg.credential_file)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(mcfg.secret_file, mcfg.scope)
        flow.user_agent = mcfg.app_name
        credentials = tools.run_flow(flow, store, mcfg)
    if not credentials or credentials.invalid:
        gcm_fatal("Failed to authenticate / invalid credentials.")
    return credentials


def gcm_dump_events(events):
    for event in events:
        ev_start = event["start"].get("dateTime", event["start"].get("date"))
        ev_end = event["end"].get("dateTime", event["end"].get("date"))
        gcm_print(u"{0:25} - {1:25} : {2}".format(ev_start, ev_end, event["summary"]))


def gcm_is_str(mstr):
    return isinstance(mstr, basestring)


def gcm_is_string(mstr):
    return mstr == None or gcm_is_str(mstr)


def gcm_is_log_level(mstr):
    if not gcm_is_str(mstr):
        return False
    else:
        return mstr.upper() in ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]


def gcm_trans_log_level(mstr):
    return mstr.upper()


def gcm_is_filename(mstr):
    if not gcm_is_str(mstr):
        return False
    else:
        return re.match("^[a-z0-9][a-z0-9\.\_\-]+$", mstr, flags=re.IGNORECASE)


def gcm_trans_bool(mbool):
    if gcm_is_str(mbool):
        if re.match("^\s*(true|1|on|yes)\s*$", mbool, re.IGNORECASE):
            mbool = True
        elif re.match("^\s*(false|0|off|no)\s*$", mbool, re.IGNORECASE):
            mbool = False
        else:
            return None
    return mbool

def gcm_is_bool(mbool):
    mval = gcm_trans_bool(mbool)
    if not isinstance(mval, bool):
        gcm_fatal("gcm_is_bool(): Invalid boolean value '{0}', should be true|false|1|0|on|off|yes|no.".format(mbool))
    else:
        return True


def gcm_trans_list(mlist):
    morig = mlist
    if gcm_is_str(mlist):
        mlist = re.split("\s*,\s*", mlist, flags=re.IGNORECASE)
        if not isinstance(mlist, list):
            gcm_fatal("gcm_trans_list(): Could not parse list '{0}'.".format(mlist))
    elif not isinstance(mlist, list):
        gcm_fatal("gcm_trans_list(): Invalid value '{0}'.".format(mlist))
    return mlist

def gcm_is_list(mlist):
    return gcm_trans_list(mlist)


def gcm_is_email(mstr):
    if not gcm_is_string(mstr):
        return False
    else:
        return re.match("^.*?\s+<[a-z0-9]+[a-z0-9\.\+\-]*\@[a-z0-9]+[a-z0-9\.\-]+>\s*$|[a-z0-9]+[a-z0-9\.\+\-]*\@[a-z0-9]+[a-z0-9\.\-]+", mstr, flags=re.IGNORECASE)


def gcm_trans_email_list(mlist):
    if mlist == None:
        return mlist
    else:
        return gcm_trans_list(mlist.strip())

def gcm_is_email_list(mlist):
    mlist = gcm_trans_email_list(mlist)
    if mlist != None:
        for email in mlist:
            if not gcm_is_email(email):
                gcm_fatal("Invalid e-mail address '{0}' in list {1}.".format(email, ", ".join(mlist)))
    return True



class GCMSettings(dict):
    def __init__(self):
        self.m_data = {}
        self.m_saveable = {}
        self.m_validate = {}
        self.m_translate = {}

    def __getattr__(self, name):
        if name in self.m_data:
            return self.m_data[name]
        else:
            gcm_fatal("GCMSettings.__getattr__(): No such attribute '"+ name +"'.")

    def mvalidate(self, name, value):
        if name in self.m_validate and self.m_validate[name]:
            if not self.m_validate[name](value):
                gcm_fatal("GCMSettings.mvalidate(): Invalid value for attribute '{0}': {1}".format(name, value))

    def mtranslate(self, name, value):
        if name in self.m_translate and self.m_translate[name]:
            return self.m_translate[name](value)
        else:
            return value

    def mdef(self, name, saveable, validate, translate, value):
        self.mvalidate(name, value)
        self.m_saveable[name] = saveable
        self.m_validate[name] = validate
        self.m_translate[name] = translate
        self.m_data[name] = self.mtranslate(name, value)

    def mset(self, name, value):
        self.mvalidate(name, value)
        if name in self.m_data:
            self.m_data[name] = self.mtranslate(name, value)
        else:
            gcm_fatal("GCMSettings.mset(): No such attribute '"+ name +"'.")

    def mget(self, name):
        if name in self.m_data:
            return self.m_data[name]
        else:
            return None

    def mread(self, cfgparser, sect):
        for name in self.m_saveable:
            if cfgparser.has_option(sect, name):
                value = cfgparser.get(sect, name)
                self.mset(name, value)
                gcm_debug("{0} -> '{1}' == {2}".format(name, value, self.mget(name)))


###
### Main program starts
###
gcm_msgbuf = []
signal.signal(signal.SIGINT, gcm_signal_handler)


## Settings
cfg = GCMSettings()

cfg.mdef("debug", True, gcm_is_bool, gcm_trans_bool, False)

cfg.mdef("source_regex", True, gcm_is_string, None, "^R:\s*(.*?)\s*\(\s*(.+?)\s*\)\s*$")
cfg.mdef("source_regmap", False, gcm_is_list, gcm_trans_list, [1, 2])
cfg.mdef("source_regmap_len", False, None, None, len(cfg.source_regmap))

cfg.mdef("dest_name", True, gcm_is_string, None, u"Raahen kansainvälisyystoiminta")
cfg.mdef("dest_id", True, gcm_is_string, None, None)

cfg.mdef("noauth_local_webserver", False, None, None, True)
#cfg.mdef("auth_host_name", False, None, None, "localhost")
#cfg.mdef("auth_host_port", False, None, None, [8080, 8090])
cfg.mdef("logging_level", True, gcm_is_log_level, gcm_trans_log_level, "ERROR")

# No need to touch these
cfg.mdef("app_name", False, None, None, "Google Calendar MultiMerge")
cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar")
#cfg.mdef("scope", False, None, None, "https://www.googleapis.com/auth/calendar.readonly")
cfg.mdef("secret_file", True, gcm_is_filename, None, "client_secret.json")
cfg.mdef("credential_file", True, gcm_is_filename, None, "client_credentials.json")


## Read, parse and validate configuration file
if len(sys.argv) > 1:
    gcm_debug("Reading configuration from '{0}'.".format(sys.argv[1]))
    try:
        cfgparser = ConfigParser.RawConfigParser()
        cfgparser.read(sys.argv[1])
    except Exception as e:
        gcm_fatal("Failed to read configuration file '{0}': {1}".format(sys.argv[1], str(e)))

    # Check that the required section exists
    section = "gcm"
    if not cfgparser.has_section(section):
        gcm_fatal("Invalid configuration, missing '{0}' section.".format(section))

    # Parse the settings and validate
    cfg.mread(cfgparser, section)

## Initialize and authorize API connection
credentials = gcm_get_credentials(cfg)
http = credentials.authorize(httplib2.Http())
service = discovery.build("calendar", "v3", http=http)


## Fetch complete calendar list
gcm_debug("Fetching available calendars ..")
calendars = []
calPageToken = None
while True:
    # We want everything except deleted and hidden calendars
    calResult = service.calendarList().list(
        showHidden=False,
        showDeleted=False,
        pageToken=calPageToken
        ).execute()

    calendars.extend(calResult.get("items", []))
    calPageToken = calResult.get("nextPageToken")
    if not calPageToken:
        break

if len(calendars) == 0:
    gcm_fatal("No calendars found?")