From 4f6ab2245e9ad1028815c09aed6f2cbadd3bdf69 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 18:37:26 -0700 Subject: [PATCH 001/146] cleanup: reorganize code Change-Id: Iabaca3f2fca624a5545bc367596e200e9f7ef73a --- find_rooms.py | 79 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/find_rooms.py b/find_rooms.py index cf7fae6..c26a3ac 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -15,13 +15,20 @@ import csv import operator -def findRooms(prefix): +URL = 'https://mail.cisco.com/ews/exchange.asmx' + +def find_room_with_prefix(prefix, xml, user, password): rooms={} data = unicode(xml.substitute(name=prefix)) - header = "\"content-type: text/xml;charset=utf-8\"" - command = "curl --silent --header " + header +" --data '" + data + "' --ntlm "+"--negotiate "+ "-u "+ user+":"+password+" "+ url + header = '"content-type: text/xml;charset=utf-8"' + command = "curl --silent --header " + header + " --data '" + data + "' --ntlm " + "-u "+ user + ":" + password + " " + URL response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + + if not response.strip(): + # something went wrong + return rooms + tree = ET.fromstring(response) elems=tree.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}Resolution") @@ -30,38 +37,52 @@ def findRooms(prefix): name = elem.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}DisplayName") if len(email) > 0 and len(name) > 0: rooms[email[0].text] = name[0].text - return rooms + return rooms -parser = argparse.ArgumentParser() -parser.add_argument("prefix", nargs='+',help="A list of prefixes to search for. E.g. 'conference confi'") -parser.add_argument("-url","--url", help="url for exhange server, e.g. 'https://mail.domain.com/ews/exchange.asmx'.",required=True) -parser.add_argument("-u","--user", help="user name for exchange/outlook", required=True) -parser.add_argument("-d","--deep", help="Attemp a deep search (takes longer).", action="store_true") -args=parser.parse_args() +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("prefix", nargs='+',help="A list of prefixes to search for. E.g. 'conference confi'") + parser.add_argument("-u","--user", help="user name for exchange/outlook", required=True) + parser.add_argument("-d","--deep", help="Attemp a deep search (takes longer).", action="store_true") + args=parser.parse_args() -url = args.url -user = args.user -password = getpass.getpass("Password:") + password = getpass.getpass("Password:") + return args.user, password, args.prefix, args.deep -xml_template = open("resolvenames_template.xml", "r").read() -xml = Template(xml_template) +def find_rooms(): + user, password, prefixes, deep = parse_args() + query_suceeded = False -rooms={} + xml_template = open("resolvenames_template.xml", "r").read() + xml = Template(xml_template) + + rooms={} -for prefix in args.prefix: - rooms.update(findRooms(prefix)) - print "After searching for prefix '" + prefix + "' we found " + str(len(rooms)) + " rooms." + for prefix in prefixes: + rooms.update(find_room_with_prefix(prefix, xml, user, password)) + print "Search for prefix '" + prefix + "' yielded " + str(len(rooms)) + " rooms." + if len(rooms): + query_suceeded = True + else: + print "Check your arguments for mistakes" + return 1 - deep = args.deep + if deep: + symbols = letters + digits + for symbol in symbols: + prefix_deep = prefix + " " + symbol + rooms.update(find_room_with_prefix(prefix_deep, xml, user, password)) + query_suceeded = True - if deep: - symbols = letters + digits - for symbol in symbols: - prefix_deep = prefix + " " + symbol - rooms.update(findRooms(prefix_deep)) + print "Deep search for prefix '" + prefix + "' yielded " + str(len(rooms)) + " rooms." - print "After deep search for prefix '" + prefix + "' we found " + str(len(rooms)) + " rooms." + writer = csv.writer(open("rooms.csv", "wb")) + for item in sorted(rooms.iteritems(), key=operator.itemgetter(1)): + writer.writerow([item[1],item[0]]) + return query_suceeded -writer = csv.writer(open("rooms.csv", "wb")) -for item in sorted(rooms.iteritems(), key=operator.itemgetter(1)): - writer.writerow([item[1],item[0]]) +if __name__ == '__main__': + if find_rooms(): + exit(0) + else: + exit(1) From 1fb3c9f8c4e91ba74cc59d22c7d7caeb2bed459a Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 20:34:41 -0700 Subject: [PATCH 002/146] cleanup: Organize into a class Change-Id: Idecb3ad72b55c609adeef969c11f7158bfa805b9 --- find_rooms.py | 164 +++++++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 75 deletions(-) diff --git a/find_rooms.py b/find_rooms.py index c26a3ac..41adc1d 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -1,88 +1,102 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import sys -reload(sys) -sys.setdefaultencoding("utf-8") - -from string import Template -from string import letters -from string import digits -import subprocess import getpass import xml.etree.ElementTree as ET import argparse import csv import operator +import subprocess +import sys + +from string import Template +from string import letters +from string import digits URL = 'https://mail.cisco.com/ews/exchange.asmx' +SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' + +reload(sys) +sys.setdefaultencoding("utf-8") -def find_room_with_prefix(prefix, xml, user, password): - rooms={} - data = unicode(xml.substitute(name=prefix)) - - header = '"content-type: text/xml;charset=utf-8"' - command = "curl --silent --header " + header + " --data '" + data + "' --ntlm " + "-u "+ user + ":" + password + " " + URL - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - - if not response.strip(): - # something went wrong - return rooms - - tree = ET.fromstring(response) - - elems=tree.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}Resolution") - for elem in elems: - email = elem.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}EmailAddress") - name = elem.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}DisplayName") - if len(email) > 0 and len(name) > 0: - rooms[email[0].text] = name[0].text - return rooms - -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument("prefix", nargs='+',help="A list of prefixes to search for. E.g. 'conference confi'") - parser.add_argument("-u","--user", help="user name for exchange/outlook", required=True) - parser.add_argument("-d","--deep", help="Attemp a deep search (takes longer).", action="store_true") - args=parser.parse_args() - - password = getpass.getpass("Password:") - return args.user, password, args.prefix, args.deep - -def find_rooms(): - user, password, prefixes, deep = parse_args() - query_suceeded = False - - xml_template = open("resolvenames_template.xml", "r").read() - xml = Template(xml_template) - - rooms={} - - for prefix in prefixes: - rooms.update(find_room_with_prefix(prefix, xml, user, password)) - print "Search for prefix '" + prefix + "' yielded " + str(len(rooms)) + " rooms." - if len(rooms): - query_suceeded = True - else: - print "Check your arguments for mistakes" - return 1 - - if deep: - symbols = letters + digits - for symbol in symbols: - prefix_deep = prefix + " " + symbol - rooms.update(find_room_with_prefix(prefix_deep, xml, user, password)) - query_suceeded = True - - print "Deep search for prefix '" + prefix + "' yielded " + str(len(rooms)) + " rooms." - - writer = csv.writer(open("rooms.csv", "wb")) - for item in sorted(rooms.iteritems(), key=operator.itemgetter(1)): - writer.writerow([item[1],item[0]]) - return query_suceeded +class RoomFinder(object): + + def __init__(self, user, password): + self.user = user + self.password = password + xml_template = open("resolvenames_template.xml", "r").read() + self.xml = Template(xml_template) + self.rooms = {} + + def _search(self, prefix): + tmp_rooms = {} + data = unicode(self.xml.substitute(name=prefix)) + + header = '"content-type: text/xml;charset=utf-8"' + command = "curl --silent --header " + header \ + + " --data '" + data \ + + "' --ntlm " \ + + "-u "+ self.user + ":" + self.password \ + + " " + URL + response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + + if not response.strip(): + # something went wrong + return tmp_rooms + + tree = ET.fromstring(response) + + elems = tree.findall(SCHEME_TYPES + "Resolution") + for elem in elems: + email = elem.findall(SCHEME_TYPES + "EmailAddress") + name = elem.findall(SCHEME_TYPES + "DisplayName") + if len(email) > 0 and len(name) > 0: + tmp_rooms[email[0].text] = name[0].text + return tmp_rooms + + def search(self, prefix, deep=False): + rooms_found = self._search(prefix) + + if deep: + symbols = letters + digits + for symbol in symbols: + prefix_deep = prefix + " " + symbol + rooms_found.update(self._search(prefix_deep)) + + print "Search for prefix '" + prefix + "' yielded " + str(len(rooms_found)) + " rooms." + self.rooms.update(self._search(prefix)) + + def dump(self, filename): + if not len(self.rooms): + print "Check your arguments for mistakes" + return 0 + + with open(filename, "wb") as fhandle: + writer = csv.writer(fhandle) + for email, name in sorted(self.rooms.iteritems(), key=operator.itemgetter(1)): + writer.writerow([name, email]) + + return len(self.rooms) + +def run(): + parser = argparse.ArgumentParser() + parser.add_argument("prefix", nargs='+', help="A prefix to search for. e.g. 'SJC19- SJC18-'") + parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) + parser.add_argument("-d", "--deep", help="Attemp a deep slow search", action="store_true") + args = parser.parse_args() + + args.password = getpass.getpass("Password:") + + finder = RoomFinder(args.user, args.password) + + for prefix in args.prefix: + print prefix, args.deep + finder.search(prefix, args.deep) + + if finder.dump("rooms.csv"): + exit(0) + else: + exit(1) if __name__ == '__main__': - if find_rooms(): - exit(0) - else: - exit(1) + run() From 804b3ce5c64f44680cfd96594afebb8c4033f7fa Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 20:53:39 -0700 Subject: [PATCH 003/146] cleanup: Add room count to CSV Change-Id: I1aaa8b369ae84d8615ef4c5422be44bc022b8467 --- find_rooms.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/find_rooms.py b/find_rooms.py index 41adc1d..31d93b2 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -50,8 +50,10 @@ def _search(self, prefix): for elem in elems: email = elem.findall(SCHEME_TYPES + "EmailAddress") name = elem.findall(SCHEME_TYPES + "DisplayName") - if len(email) > 0 and len(name) > 0: - tmp_rooms[email[0].text] = name[0].text + if len(email) and len(name): + roomsize = self.room_size(name[0].text) + if roomsize: + tmp_rooms[email[0].text] = (name[0].text, roomsize) return tmp_rooms def search(self, prefix, deep=False): @@ -66,6 +68,12 @@ def search(self, prefix, deep=False): print "Search for prefix '" + prefix + "' yielded " + str(len(rooms_found)) + " rooms." self.rooms.update(self._search(prefix)) + def room_size(self, roomname): + try: + return int(roomname[roomname.find('(') + 1 : roomname.find(')')]) + except ValueError: + return 0 + def dump(self, filename): if not len(self.rooms): print "Check your arguments for mistakes" @@ -73,8 +81,9 @@ def dump(self, filename): with open(filename, "wb") as fhandle: writer = csv.writer(fhandle) - for email, name in sorted(self.rooms.iteritems(), key=operator.itemgetter(1)): - writer.writerow([name, email]) + for email, room_info in sorted(self.rooms.iteritems(), key=operator.itemgetter(1)): + name, size = room_info + writer.writerow([name, email, size]) return len(self.rooms) @@ -90,7 +99,6 @@ def run(): finder = RoomFinder(args.user, args.password) for prefix in args.prefix: - print prefix, args.deep finder.search(prefix, args.deep) if finder.dump("rooms.csv"): From 7bd91ea180abb78f5193be5037e5303d0e8e640d Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 21:41:28 -0700 Subject: [PATCH 004/146] cleanup Change-Id: I200aef06bc61969e21443f2319b6f9efc3f3957a --- find_available_room.py | 112 ++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 47 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 205115f..61eb0e0 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -1,67 +1,85 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import argparse +import csv +import datetime +import getpass +import subprocess import sys +import xml.etree.ElementTree as ET + +from string import Template + +URL = 'https://mail.cisco.com/ews/exchange.asmx' +SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' +TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() +TIME_1H_FROM_NOW = None + reload(sys) sys.setdefaultencoding("utf-8") -import subprocess -import getpass -from string import Template -import xml.etree.ElementTree as ET -import csv, codecs -import argparse -import datetime +def read_room_list(filename='rooms.csv'): + rooms = {} + + with open(filename, 'r') as fhandle: + reader = csv.reader(fhandle) + for row in reader: + rooms[unicode(row[1])] = unicode(row[0]) + + return rooms -now = datetime.datetime.now().replace(microsecond=0) -starttime_default = now.isoformat() -end_time_default = None -parser = argparse.ArgumentParser() -parser.add_argument("-url","--url", help="url for exhange server, e.g. 'https://mail.domain.com/ews/exchange.asmx'.",required=True) -parser.add_argument("-u","--user", help="user name for exchange/outlook",required=True) -parser.add_argument("-start","--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", default=starttime_default) -parser.add_argument("-end","--endtime", help="Endtime e.g. 2014-07-02T12:00:00 (default = now+1h)", default=end_time_default) -#parser.add_argument("-n","--now", help="Will set starttime to now and endtime to now+1h", action="store_true") -parser.add_argument("-f","--file", help="csv filename with rooms to check (default=rooms.csv). Format: Name,email",default="rooms.csv") +def find_available_rooms(rooms, user, password, start_time=TIME_NOW, end_time=None): + if end_time is None: + start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") + end_time = (start + datetime.timedelta(hours=1)).isoformat() -args=parser.parse_args() + print "Searching for a room from " + start_time + " to " + end_time + ":" + print "{0:10s} {1:64s} {2:64s}".format("Status", "Room", "Email") -url = args.url + xml_template = open("getavailibility_template.xml", "r").read() + xml = Template(xml_template) + for room in rooms: + data = unicode(xml.substitute(email=room, starttime=start_time, endtime=end_time)) -rooms={} -reader = csv.reader(codecs.open(args.file, 'r', encoding='utf-8')) -for row in reader: - rooms[unicode(row[1])]=unicode(row[0]) + header = "\"content-type: text/xml;charset=utf-8\"" + command = "curl --silent --header " + header \ + + " --data '" + data \ + + "' --ntlm " \ + + "-u "+ user + ":" + password \ + + " " + URL + response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] -start_time = args.starttime -if not args.endtime: - start = datetime.datetime.strptime( start_time, "%Y-%m-%dT%H:%M:%S" ) - end_time = (start + datetime.timedelta(hours=1)).isoformat() -else: - end_time = args.endtime + tree = ET.fromstring(response) -user = args.user -password = getpass.getpass("Password:") + status = "Free" + # arrgh, namespaces!! + elems = tree.findall(SCHEME_TYPES + "BusyType") + for elem in elems: + status = elem.text -print "Searching for a room from " + start_time + " to " + end_time + ":" -print "{0:10s} {1:64s} {2:64s}".format("Status", "Room", "Email") + print "{0:10s} {1:64s} {2:64s}".format(status, rooms[room], room) -xml_template = open("getavailibility_template.xml", "r").read() -xml = Template(xml_template) -for room in rooms: - data = unicode(xml.substitute(email=room,starttime=start_time,endtime=end_time)) +def run(): + parser = argparse.ArgumentParser() + parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) + parser.add_argument("-start", "--starttime", + help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", + default=TIME_NOW) + parser.add_argument("-end", "--endtime", + help="Endtime e.g. 2014-07-02T12:00:00 (default = now+1h)", + default=TIME_1H_FROM_NOW) + parser.add_argument("-f", "--file", + help="csv filename with room info (default=rooms.csv).", + default="rooms.csv") - header = "\"content-type: text/xml;charset=utf-8\"" - command = "curl --silent --header " + header +" --data '" + data + "' --ntlm "+"--negotiate "+ "-u "+ user+":"+password+" "+ url - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + args = parser.parse_args() + args.password = getpass.getpass("Password:") - tree = ET.fromstring(response) + rooms = read_room_list() + find_available_rooms(rooms, args.user, args.password, args.starttime, args.endtime) - status = "Free" - # arrgh, namespaces!! - elems=tree.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}BusyType") - for elem in elems: - status=elem.text - print "{0:10s} {1:64s} {2:64s}".format(status, rooms[room], room) +if __name__ == '__main__': + run() From f03b830c76bd6319fa6af733c58940654cb3e473 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 21:41:52 -0700 Subject: [PATCH 005/146] cleanup: set default value in function arg Change-Id: I594a8114742c53c56b26933150d8326873e300ed --- find_rooms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/find_rooms.py b/find_rooms.py index 31d93b2..3f5f520 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -74,7 +74,7 @@ def room_size(self, roomname): except ValueError: return 0 - def dump(self, filename): + def dump(self, filename='rooms.csv'): if not len(self.rooms): print "Check your arguments for mistakes" return 0 @@ -101,7 +101,7 @@ def run(): for prefix in args.prefix: finder.search(prefix, args.deep) - if finder.dump("rooms.csv"): + if finder.dump(): exit(0) else: exit(1) From 418a2da9a1bb66c158298bb4d7ca49b58d00a28f Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 22:01:19 -0700 Subject: [PATCH 006/146] cleanup: make object oriented Change-Id: I098ed926ae054c6ee63c1cf1a30663c8a843fb0e --- find_available_room.py | 85 ++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 61eb0e0..4149713 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -19,47 +19,66 @@ reload(sys) sys.setdefaultencoding("utf-8") -def read_room_list(filename='rooms.csv'): - rooms = {} +class AvailRoomFinder(object): - with open(filename, 'r') as fhandle: - reader = csv.reader(fhandle) - for row in reader: - rooms[unicode(row[1])] = unicode(row[0]) + def __init__(self, user, password, + start_time=TIME_NOW, end_time=None, + roominfo='rooms.csv'): + self.rooms = self._read_room_list(roominfo) + self.user = user + self.password = password + self.start_time = start_time + if end_time is None: + start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") + self.end_time = (start + datetime.timedelta(hours=1)).isoformat() - return rooms + def _read_room_list(self, filename): + rooms = {} + with open(filename, 'r') as fhandle: + reader = csv.reader(fhandle) + for row in reader: + rooms[unicode(row[1])] = (unicode(row[0]), int(row[2])) -def find_available_rooms(rooms, user, password, start_time=TIME_NOW, end_time=None): - if end_time is None: - start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") - end_time = (start + datetime.timedelta(hours=1)).isoformat() + return rooms - print "Searching for a room from " + start_time + " to " + end_time + ":" - print "{0:10s} {1:64s} {2:64s}".format("Status", "Room", "Email") + def search(self, min_size=1, print_to_stdout=False): + room_info = {} - xml_template = open("getavailibility_template.xml", "r").read() - xml = Template(xml_template) - for room in rooms: - data = unicode(xml.substitute(email=room, starttime=start_time, endtime=end_time)) + if print_to_stdout: + print "Searching for a room from " + self.start_time + " to " + self.end_time + ":" + print "{0:10s} {1:64s} {2:64s}".format("Status", "Room", "Email") - header = "\"content-type: text/xml;charset=utf-8\"" - command = "curl --silent --header " + header \ - + " --data '" + data \ - + "' --ntlm " \ - + "-u "+ user + ":" + password \ - + " " + URL - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + xml_template = open("getavailibility_template.xml", "r").read() + xml = Template(xml_template) - tree = ET.fromstring(response) + for email in self.rooms: + data = unicode(xml.substitute(email=email, starttime=self.start_time, endtime=self.end_time)) - status = "Free" - # arrgh, namespaces!! - elems = tree.findall(SCHEME_TYPES + "BusyType") - for elem in elems: - status = elem.text + header = "\"content-type: text/xml;charset=utf-8\"" + command = "curl --silent --header " + header \ + + " --data '" + data \ + + "' --ntlm " \ + + "-u "+ self.user + ":" + self.password \ + + " " + URL + response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - print "{0:10s} {1:64s} {2:64s}".format(status, rooms[room], room) + tree = ET.fromstring(response) + + status = "Free" + elems = tree.findall(SCHEME_TYPES + "BusyType") + for elem in elems: + status = elem.text + + name, size = self.rooms[email] + + if status == 'Free' and size > min_size: + room_info[name] = {'size': size} + + if print_to_stdout: + print "{0:10s} {1:64s} {2:64s}".format(status, self.rooms[email], email) + + return room_info def run(): parser = argparse.ArgumentParser() @@ -77,8 +96,8 @@ def run(): args = parser.parse_args() args.password = getpass.getpass("Password:") - rooms = read_room_list() - find_available_rooms(rooms, args.user, args.password, args.starttime, args.endtime) + room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.endtime) + print room_finder.search(print_to_stdout=True) if __name__ == '__main__': From 9397486a7ec86bb6760c65585aa2e7bacc45a380 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 22:05:16 -0700 Subject: [PATCH 007/146] cleanup: timezone hack Change-Id: I4e309e0ef920cc0ea986966ce5d11bdd20951b3f --- find_available_room.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/find_available_room.py b/find_available_room.py index 4149713..6aafbd1 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -15,6 +15,7 @@ SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() TIME_1H_FROM_NOW = None +TIME_ZONE = '-13:00' #PST HACK reload(sys) sys.setdefaultencoding("utf-8") @@ -53,7 +54,9 @@ def search(self, min_size=1, print_to_stdout=False): xml = Template(xml_template) for email in self.rooms: - data = unicode(xml.substitute(email=email, starttime=self.start_time, endtime=self.end_time)) + data = unicode(xml.substitute(email=email, + starttime=self.start_time + TIME_ZONE, + endtime=self.end_time + TIME_ZONE)) header = "\"content-type: text/xml;charset=utf-8\"" command = "curl --silent --header " + header \ From 1abe22b0642d67ce5f52f333b5164ae9ffc2d510 Mon Sep 17 00:00:00 2001 From: Monika Date: Wed, 24 Aug 2016 22:08:07 -0700 Subject: [PATCH 008/146] mrathor: Add WebService APIs Change-Id: I76d026aef1c603835f04a53e0b27bfdd37a1b230 --- DockerReadme.md | 3 ++ Dockerfile | 8 +++++ service/webserver.py | 86 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 DockerReadme.md create mode 100644 Dockerfile create mode 100755 service/webserver.py diff --git a/DockerReadme.md b/DockerReadme.md new file mode 100644 index 0000000..8503aeb --- /dev/null +++ b/DockerReadme.md @@ -0,0 +1,3 @@ +Make sure rooms.csv file contains list of rooms. +docker build -t room-finder . +docker run -d -p 5000:5000 room-finder diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e7e8c2d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM ubuntu:latest +RUN apt-get update -y +RUN apt-get install -y python-pip python-dev build-essential +COPY . /app +WORKDIR /app +RUN pip install Flask==0.10.1 +ENTRYPOINT ["python"] +CMD ["service/webserver.py"] diff --git a/service/webserver.py b/service/webserver.py new file mode 100755 index 0000000..d3a93ac --- /dev/null +++ b/service/webserver.py @@ -0,0 +1,86 @@ +import codecs +import csv +import datetime +import json +import os +import subprocess +import xml.etree.ElementTree as ET + +from collections import namedtuple +from flask import Flask +from shutil import copyfile +from string import Template + +PWD = os.getcwd() + +CONFIG = { + 'home' : PWD, + 'roomscsv' : PWD + '/rooms.csv', + 'roomssearchcsv' : PWD + '/roomssearch.csv', + 'availibility_template' : PWD + '/getavailibility_template.xml', + 'URL': "https://mail.cisco.com/ews/exchange.asmx" + } + +app = Flask(__name__) + +@app.route('/') +def index(): + return "Welcome to Room Finder Web Service" + + +from flask import request + +QueryParam = namedtuple('QueryParam', 'roomname, starttime, endtime, user, password') + +# Example Query +# http://127.0.0.1:5000/showrooms?roomname=ABC&starttime=2016-08-25T09:00:00-13:00&endtime=2016-08-25T10:00:00-13:00&user=USER&password=password +@app.route('/showrooms', methods=['GET']) +def show_rooms(): + queryparam = QueryParam( + roomname=request.args.get('roomname'), + starttime=request.args.get('starttime'), + endtime=request.args.get('endtime'), + user = request.args.get('user'), + password = request.args.get('password'), + ) + + _create_tmp_rooms_file(queryparam.roomname) + + rooms = {} + for row in csv.reader(open(CONFIG['roomssearchcsv'], 'r')): + rooms[row[1]]=(row[0]) + + xml_template = open(CONFIG['availibility_template'], "r").read() + xml = Template(xml_template) + resp = [] + + for room in rooms: + data = unicode(xml.substitute(email=room, + starttime=queryparam.starttime, + endtime=queryparam.endtime)) + header = "\"content-type: text/xml;charset=utf-8\"" + command = "curl --silent --header " + header +" --data '" + data + "' --ntlm "\ + + "-u "+ queryparam.user+":"+queryparam.password+" "+ CONFIG['URL'] + response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + tree = ET.fromstring(response) + status = "Free" + elems = tree.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}BusyType") + for elem in elems: + status = elem.text + subject = "" + elems = tree.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}Subject") + for elem in elems: + subject += elem.text + + resp.append({'status':status, 'room' : rooms[room], 'roomid' : room, 'subject' : subject}) + + return json.dumps(resp) + +def _create_tmp_rooms_file(roomname): + if 'all' in roomname: + copyfile(CONFIG['roomscsv'], CONFIG['roomssearchcsv']) + else: + open(CONFIG['roomssearchcsv'],'w').writelines([ line for line in open(CONFIG['roomscsv']) if roomname in line]) + +if __name__ == '__main__': + app.run(debug=True) From e8c5492d0a7204b9b49101b9f897cb806da75c91 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 22:14:17 -0700 Subject: [PATCH 009/146] cleanup: Fix search for free rooms, using 15 min intervals Change-Id: I0680e140d598f287b44ec2acbdc7511e78a5d621 --- find_available_room.py | 16 +++++++++++----- getavailibility_template.xml | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 6aafbd1..321e911 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -48,7 +48,7 @@ def search(self, min_size=1, print_to_stdout=False): if print_to_stdout: print "Searching for a room from " + self.start_time + " to " + self.end_time + ":" - print "{0:10s} {1:64s} {2:64s}".format("Status", "Room", "Email") + print "{0:20s} {1:64s} {2:64s}".format("Status", "Room", "Email") xml_template = open("getavailibility_template.xml", "r").read() xml = Template(xml_template) @@ -69,17 +69,23 @@ def search(self, min_size=1, print_to_stdout=False): tree = ET.fromstring(response) status = "Free" - elems = tree.findall(SCHEME_TYPES + "BusyType") + elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy") for elem in elems: - status = elem.text + freebusy = elem.text + if '2' in freebusy: + status = "Busy" + elif '3' in freebusy: + status = "Unavailable" + elif '1' in freebusy: + status = "Tentative" name, size = self.rooms[email] if status == 'Free' and size > min_size: - room_info[name] = {'size': size} + room_info[name] = {'size': size, 'freebusy': freebusy} if print_to_stdout: - print "{0:10s} {1:64s} {2:64s}".format(status, self.rooms[email], email) + print "{0:20s} {1:64s} {2:64s}".format(status + '-' + freebusy, self.rooms[email], email) return room_info diff --git a/getavailibility_template.xml b/getavailibility_template.xml index ebee671..9e20ad1 100644 --- a/getavailibility_template.xml +++ b/getavailibility_template.xml @@ -36,7 +36,7 @@ $starttime $endtime - 60 + 15 DetailedMerged From acd12ee0de26cf03925df2362fd0dc8d803c7508 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 22:22:59 -0700 Subject: [PATCH 010/146] cleanup: add ability to specify rooms file Change-Id: Ic3f72d68abda6aeb86ae297d988bb02da7e1af17 --- find_available_room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/find_available_room.py b/find_available_room.py index 321e911..4d0b4fa 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -105,7 +105,7 @@ def run(): args = parser.parse_args() args.password = getpass.getpass("Password:") - room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.endtime) + room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.endtime, args.file) print room_finder.search(print_to_stdout=True) From ec8896fa2daa98647d2123dc7d3a6fb69ba9bf87 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 22:32:22 -0700 Subject: [PATCH 011/146] cleanup: end time fix in constructor Change-Id: Ifeecc15c768a7e61df422a115d5397addb880a33 --- find_available_room.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/find_available_room.py b/find_available_room.py index 4d0b4fa..fe99330 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -32,6 +32,8 @@ def __init__(self, user, password, if end_time is None: start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") self.end_time = (start + datetime.timedelta(hours=1)).isoformat() + else: + self.end_time = end_time def _read_room_list(self, filename): rooms = {} From 89386df2242925bb4f6f3639681b4a227116c017 Mon Sep 17 00:00:00 2001 From: Monika Date: Wed, 24 Aug 2016 22:47:08 -0700 Subject: [PATCH 012/146] mrathor: Use API call Change-Id: I0a85da8b1314eb625a9e136f1ce5620156f302f0 --- Dockerfile | 1 + service/webserver.py | 32 ++++---------------------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/Dockerfile b/Dockerfile index e7e8c2d..77240a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ RUN apt-get update -y RUN apt-get install -y python-pip python-dev build-essential COPY . /app WORKDIR /app +ENV PYTHONPATH $PYTHONPATH:/app/roomfinder RUN pip install Flask==0.10.1 ENTRYPOINT ["python"] CMD ["service/webserver.py"] diff --git a/service/webserver.py b/service/webserver.py index d3a93ac..195f503 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -10,6 +10,7 @@ from flask import Flask from shutil import copyfile from string import Template +from find_available_room import AvailRoomFinder PWD = os.getcwd() @@ -46,35 +47,10 @@ def show_rooms(): _create_tmp_rooms_file(queryparam.roomname) - rooms = {} - for row in csv.reader(open(CONFIG['roomssearchcsv'], 'r')): - rooms[row[1]]=(row[0]) + room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, queryparam.endtime, CONFIG['roomssearchcsv']) + rooms_info = room_finder.search(print_to_stdout=True) - xml_template = open(CONFIG['availibility_template'], "r").read() - xml = Template(xml_template) - resp = [] - - for room in rooms: - data = unicode(xml.substitute(email=room, - starttime=queryparam.starttime, - endtime=queryparam.endtime)) - header = "\"content-type: text/xml;charset=utf-8\"" - command = "curl --silent --header " + header +" --data '" + data + "' --ntlm "\ - + "-u "+ queryparam.user+":"+queryparam.password+" "+ CONFIG['URL'] - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - tree = ET.fromstring(response) - status = "Free" - elems = tree.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}BusyType") - for elem in elems: - status = elem.text - subject = "" - elems = tree.findall(".//{http://schemas.microsoft.com/exchange/services/2006/types}Subject") - for elem in elems: - subject += elem.text - - resp.append({'status':status, 'room' : rooms[room], 'roomid' : room, 'subject' : subject}) - - return json.dumps(resp) + return json.dumps(rooms_info) def _create_tmp_rooms_file(roomname): if 'all' in roomname: From 06439d82e7b624a316a27fef0f2a05fdefa39f15 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 22:50:17 -0700 Subject: [PATCH 013/146] cleanup: add support for prefix Change-Id: Icdc55f274e3de5d6fa68116272d4f6877f98c51d --- find_available_room.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index fe99330..5d65ba4 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -45,7 +45,23 @@ def _read_room_list(self, filename): return rooms - def search(self, min_size=1, print_to_stdout=False): + def search_free(self, prefix, min_size=1, print_to_stdout=False): + selected_rooms = {} + for email in self.rooms: + name, size = self.rooms[email] + if name.startswith(prefix) and size > min_size: + selected_rooms[email] = (name, size) + + selected_room_info = self.search(selected_rooms, print_to_stdout) + free_room_info = {} + for email in selected_room_info: + if selected_room_info[email]['status'] == 'Free': + free_room_info[email] = selected_room_info[email] + return free_room_info + + def search(self, selected_rooms=None, print_to_stdout=False): + if selected_rooms is None: + selected_rooms = self.rooms() room_info = {} if print_to_stdout: @@ -55,7 +71,7 @@ def search(self, min_size=1, print_to_stdout=False): xml_template = open("getavailibility_template.xml", "r").read() xml = Template(xml_template) - for email in self.rooms: + for email in selected_rooms: data = unicode(xml.substitute(email=email, starttime=self.start_time + TIME_ZONE, endtime=self.end_time + TIME_ZONE)) @@ -67,6 +83,8 @@ def search(self, min_size=1, print_to_stdout=False): + "-u "+ self.user + ":" + self.password \ + " " + URL response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + if not response: + return room_info tree = ET.fromstring(response) @@ -82,9 +100,7 @@ def search(self, min_size=1, print_to_stdout=False): status = "Tentative" name, size = self.rooms[email] - - if status == 'Free' and size > min_size: - room_info[name] = {'size': size, 'freebusy': freebusy} + room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status} if print_to_stdout: print "{0:20s} {1:64s} {2:64s}".format(status + '-' + freebusy, self.rooms[email], email) @@ -94,6 +110,9 @@ def search(self, min_size=1, print_to_stdout=False): def run(): parser = argparse.ArgumentParser() parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) + parser.add_argument("-prefix", "--prefix", + help="A prefix to search for. e.g. 'SJC19- SJC18-'", + default='') parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", default=TIME_NOW) @@ -108,7 +127,7 @@ def run(): args.password = getpass.getpass("Password:") room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.endtime, args.file) - print room_finder.search(print_to_stdout=True) + print room_finder.search_free(prefix=args.prefix, print_to_stdout=True) if __name__ == '__main__': From 1e3503b9514ef066ee914dff87c7cf612f2bf6ef Mon Sep 17 00:00:00 2001 From: Monika Date: Wed, 24 Aug 2016 23:03:45 -0700 Subject: [PATCH 014/146] mrathor: Call Search Free API Change-Id: I8fff5073006f5240ba0b23f860a2463b2b4abf99 --- service/webserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/webserver.py b/service/webserver.py index 195f503..df8df65 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -48,7 +48,7 @@ def show_rooms(): _create_tmp_rooms_file(queryparam.roomname) room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, queryparam.endtime, CONFIG['roomssearchcsv']) - rooms_info = room_finder.search(print_to_stdout=True) + rooms_info = room_finder.search_free(prefix=queryparam.roomname, print_to_stdout=True) return json.dumps(rooms_info) From bfab827c4a2adbfeb39e5385c84f8993ad1c3dac Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 23:27:33 -0700 Subject: [PATCH 015/146] cleanup: Switch to using duration Change-Id: I99f938dbeccacf552945e34b2705c401ac1ea16f --- find_available_room.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 5d65ba4..bc7eb6c 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -14,7 +14,6 @@ URL = 'https://mail.cisco.com/ews/exchange.asmx' SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() -TIME_1H_FROM_NOW = None TIME_ZONE = '-13:00' #PST HACK reload(sys) @@ -23,17 +22,29 @@ class AvailRoomFinder(object): def __init__(self, user, password, - start_time=TIME_NOW, end_time=None, + start_time=TIME_NOW, duration='1h', roominfo='rooms.csv'): self.rooms = self._read_room_list(roominfo) self.user = user self.password = password self.start_time = start_time - if end_time is None: - start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") - self.end_time = (start + datetime.timedelta(hours=1)).isoformat() - else: - self.end_time = end_time + + try: + if duration.endswith('h'): + hours, mins = int(duration[:-1]), 0 + elif duration.endswith('m'): + hours, mins = 0, int(duration[:-1]) + else: + duration = int(duration) + if duration < 15: + hours, mins = duration, 0 + else: + hours, mins = 0, duration + except ValueError: + self.duration = (1, 0) + + start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") + self.end_time = (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() def _read_room_list(self, filename): rooms = {} @@ -61,7 +72,7 @@ def search_free(self, prefix, min_size=1, print_to_stdout=False): def search(self, selected_rooms=None, print_to_stdout=False): if selected_rooms is None: - selected_rooms = self.rooms() + selected_rooms = self.rooms room_info = {} if print_to_stdout: @@ -116,9 +127,9 @@ def run(): parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", default=TIME_NOW) - parser.add_argument("-end", "--endtime", - help="Endtime e.g. 2014-07-02T12:00:00 (default = now+1h)", - default=TIME_1H_FROM_NOW) + parser.add_argument("-duration", "--duration", + help="Duration e.g. 1h or 15m (default = 1h)", + default='1h') parser.add_argument("-f", "--file", help="csv filename with room info (default=rooms.csv).", default="rooms.csv") @@ -126,7 +137,7 @@ def run(): args = parser.parse_args() args.password = getpass.getpass("Password:") - room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.endtime, args.file) + room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.duration, args.file) print room_finder.search_free(prefix=args.prefix, print_to_stdout=True) From 718b8a954f07614d8c1a159234409b5e69493e69 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 24 Aug 2016 23:34:29 -0700 Subject: [PATCH 016/146] cleanup: change to use duration Change-Id: If5cd1b4b0c2619e1448f3277675c95124fb9e5b2 --- service/webserver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/service/webserver.py b/service/webserver.py index df8df65..05bf13a 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -31,23 +31,23 @@ def index(): from flask import request -QueryParam = namedtuple('QueryParam', 'roomname, starttime, endtime, user, password') +QueryParam = namedtuple('QueryParam', 'roomname, starttime, duration, user, password') # Example Query -# http://127.0.0.1:5000/showrooms?roomname=ABC&starttime=2016-08-25T09:00:00-13:00&endtime=2016-08-25T10:00:00-13:00&user=USER&password=password +# http://127.0.0.1:5000/showrooms?roomname=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password @app.route('/showrooms', methods=['GET']) def show_rooms(): queryparam = QueryParam( roomname=request.args.get('roomname'), starttime=request.args.get('starttime'), - endtime=request.args.get('endtime'), + duration=request.args.get('duration'), user = request.args.get('user'), password = request.args.get('password'), ) _create_tmp_rooms_file(queryparam.roomname) - room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, queryparam.endtime, CONFIG['roomssearchcsv']) + room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, queryparam.duration, CONFIG['roomssearchcsv']) rooms_info = room_finder.search_free(prefix=queryparam.roomname, print_to_stdout=True) return json.dumps(rooms_info) From 0b61888404f1f23d3c2faae7a89433675ae027b4 Mon Sep 17 00:00:00 2001 From: mobaqar Date: Thu, 25 Aug 2016 09:38:22 -0700 Subject: [PATCH 017/146] UI --- ui/index.html | 36 ++++++++++++++++++++++++++++++++++++ ui/main.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 ui/index.html create mode 100644 ui/main.js diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..52f82bb --- /dev/null +++ b/ui/index.html @@ -0,0 +1,36 @@ + + + + + + +Select Building, Floor & Time +

+
+
+ + + + +
+
+
+ + + + +
+
+ + +

+
+ User Name: + Password: +
+
+ + + + + \ No newline at end of file diff --git a/ui/main.js b/ui/main.js new file mode 100644 index 0000000..2080ad5 --- /dev/null +++ b/ui/main.js @@ -0,0 +1,43 @@ +var buildings = ["SJC-1", "SJC-2" , "SJC-3" , "SJC-4", "SJC-5"]; +var floors = ["1","2","3","4"]; +var times = ["8:00", "9:00","10:00","11:00","12:00","13:00","14:00","15:00","16:00","17:00",]; + +function init(){ + + createCombo(buildingSelect,buildings); + createCombo(floorSelect,floors); + createCombo(startTimeSelect,times); + createCombo(endTimeSelect,times); + +} + +function createCombo(container, data) { + var options = ''; + for (var i = 0; i < data.length; i++) { + container.options.add(new Option(data[i], data[i])); + } +} + +function submitClickHandler() { + + var queryString = `?userName=${userNameInput.value}&password=${passwordInput.value}&buildingName=${buildingSelect.value}&floor=${floorSelect.value}&startTime=${startTimeSelect.value}&endTime=${endTimeSelect.value}`; + loadRooms(queryString); + //console.log(queryString); +} + +function loadRooms(postData) { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (xhttp.readyState == 4 && xhttp.status == 200) { + //document.getElementById("demo").innerHTML = xhttp.responseText; + console.log(xhttp.responseText) + } + }; + xhttp.open("POST", "http://www.w3schools.com/ajax/demo_post2.asp", true); + xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhttp.send(postData); +} + + + + From c0c54bbf6cf5ae1b66875ae0c8eeb84e6d0638ef Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 09:52:03 -0700 Subject: [PATCH 018/146] mrathor: Add API to get list of buildings Change-Id: Ice657acf00be9871baf03be988c098cc2a1ba7b7 --- service/webserver.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/service/webserver.py b/service/webserver.py index df8df65..98f70cc 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -19,7 +19,8 @@ 'roomscsv' : PWD + '/rooms.csv', 'roomssearchcsv' : PWD + '/roomssearch.csv', 'availibility_template' : PWD + '/getavailibility_template.xml', - 'URL': "https://mail.cisco.com/ews/exchange.asmx" + 'URL': "https://mail.cisco.com/ews/exchange.asmx", + 'allrooms' : PWD + '/allrooms.csv', } app = Flask(__name__) @@ -33,6 +34,17 @@ def index(): QueryParam = namedtuple('QueryParam', 'roomname, starttime, endtime, user, password') +@app.route('/showbuldings', methods=['GET']) +def show_buldings(): + buldings = [] + with open(CONFIG['allrooms'],'r') as f: + for line in f.readlines(): + buldingname = line.split('-')[0] + if buldingname not in buldings: + buldings.append(buldingname) + return json.dumps(buldings) + + # Example Query # http://127.0.0.1:5000/showrooms?roomname=ABC&starttime=2016-08-25T09:00:00-13:00&endtime=2016-08-25T10:00:00-13:00&user=USER&password=password @app.route('/showrooms', methods=['GET']) From 6cc58cbee6532be08449f5ff1b069aea146fe07f Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 10:08:37 -0700 Subject: [PATCH 019/146] cleanup: UI fixes Change-Id: I4682b9adc4744ef272cc1c82fea516b9913dda0a --- ui/index.html | 38 +++++++++++++++++++++++++------------- ui/main.js | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/ui/index.html b/ui/index.html index 52f82bb..31801b2 100644 --- a/ui/index.html +++ b/ui/index.html @@ -4,33 +4,45 @@ -Select Building, Floor & Time -

+ +
+
+ +
- + User Name: + Password: +
+ +

+
+ - + + +

- - - + + + + +


-
- User Name: - Password: -
+ +

+
+
- - \ No newline at end of file + diff --git a/ui/main.js b/ui/main.js index 2080ad5..78d4ebc 100644 --- a/ui/main.js +++ b/ui/main.js @@ -1,13 +1,36 @@ -var buildings = ["SJC-1", "SJC-2" , "SJC-3" , "SJC-4", "SJC-5"]; -var floors = ["1","2","3","4"]; -var times = ["8:00", "9:00","10:00","11:00","12:00","13:00","14:00","15:00","16:00","17:00",]; +var buildings = ["SJC02", "SJC03", "SJC04", "SJC05", + "SJC06", "SJC07", "SJC08", "SJC09", + "SJC10", "SJC11", "SJC12", "SJC13", + "SJC14", "SJC15", "SJC16", "SJC17", + "SJC18", "SJC19", "SJC20", "SJC21", + "SJC22", "SJC23", "SJC24", "SJC28", + "SJC29", "SJC30", "SJC31", "SJC32", + "SJCA", "SJCB", "SJCD", "SJCI", "SJCJ", "SJCK", + "SJCL", "SJCM", "SJCMR1", "SJCMR3", "SJCN", + "SJCO", "SJCP", "SJCPLM01", "SJCQ", "SJCSYC02"] +var floors = ["1", "2", "3", "4", "5"]; +var times_hours = ["00", "01", "02","03", + "04", "05", "06", "07", + "08", "09", "10", "11", + "12", "13", "14", "15", + "16", "17", "18", "19", + "20", "21", "22", "23"]; +var times_mins = ["00", "15", "30","45"]; +var duration_hours = ["0h", "1h", "2h","3h", + "4h", "5h", "6h", "7h"]; +var duration_mins = ["", "15m", "30m","45m"]; +var sizes = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "20", "23", "24", "25", "30", "36", "50", "60", "65", "68", "70", "100", "120", "410"] + function init(){ - createCombo(buildingSelect,buildings); - createCombo(floorSelect,floors); - createCombo(startTimeSelect,times); - createCombo(endTimeSelect,times); + createCombo(buildingSelect, buildings); + createCombo(floorSelect, floors); + createCombo(startTimeHourSelect, times_hours); + createCombo(startTimeMinSelect, times_mins); + createCombo(durationHourSelect, duration_hours); + createCombo(durationMinSelect, duration_mins); + createCombo(roomSizeSelect, sizes); } From 99d015681963a600ba078577a18099c0fa3484ae Mon Sep 17 00:00:00 2001 From: Ashish Sawalkar Date: Thu, 25 Aug 2016 10:11:28 -0700 Subject: [PATCH 020/146] Fix timezone issue --- find_available_room.py | 2 +- getavailibility_template.xml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index bc7eb6c..d9c5be7 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -14,7 +14,7 @@ URL = 'https://mail.cisco.com/ews/exchange.asmx' SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() -TIME_ZONE = '-13:00' #PST HACK +TIME_ZONE = '' reload(sys) sys.setdefaultencoding("utf-8") diff --git a/getavailibility_template.xml b/getavailibility_template.xml index 9e20ad1..356d946 100644 --- a/getavailibility_template.xml +++ b/getavailibility_template.xml @@ -4,18 +4,18 @@ - -60 + -420 - 0 - - 5 - 10 + -360 + + 6 + 11 Sunday - -60 + -420 - 5 + 13 3 Sunday From 7aad4e2eeed4eefd543a003dbceac162b07488c0 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 12:18:29 -0700 Subject: [PATCH 021/146] cleanup: Fix timezone for real Change-Id: Id03e221a11c5ad0f1521a67d664b633d8b5ee7fc --- getavailibility_template.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/getavailibility_template.xml b/getavailibility_template.xml index 356d946..265b7a1 100644 --- a/getavailibility_template.xml +++ b/getavailibility_template.xml @@ -4,18 +4,18 @@ - -420 + 420 - -360 - - 6 - 11 + -60 + + 5 + 10 Sunday - -420 + 0 - 13 + 5 3 Sunday From 06701faa764bf64213a2c1583de0cdca78a54cae Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 12:53:42 -0700 Subject: [PATCH 022/146] cleanup: Add date Change-Id: I41d2887d4274279638c9bc62c5e844917e78e7c7 --- ui/index.html | 6 +++++- ui/main.js | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 31801b2..3fdfbb1 100644 --- a/ui/index.html +++ b/ui/index.html @@ -26,7 +26,11 @@
- + + + + + diff --git a/ui/main.js b/ui/main.js index 78d4ebc..e5cc82b 100644 --- a/ui/main.js +++ b/ui/main.js @@ -20,6 +20,12 @@ var duration_hours = ["0h", "1h", "2h","3h", "4h", "5h", "6h", "7h"]; var duration_mins = ["", "15m", "30m","45m"]; var sizes = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "20", "23", "24", "25", "30", "36", "50", "60", "65", "68", "70", "100", "120", "410"] +var date_years = ["2016", "2017"]; +var date_months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; +var date_days = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", + "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", + "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", + "31"]; function init(){ @@ -31,6 +37,9 @@ function init(){ createCombo(durationHourSelect, duration_hours); createCombo(durationMinSelect, duration_mins); createCombo(roomSizeSelect, sizes); + createCombo(dateYearSelect, date_years); + createCombo(dateMonthSelect, date_months); + createCombo(dateDaySelect, date_days); } From 9ec8a4a0c42455cf93cec98a7e012b0dbb523991 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 14:06:06 -0700 Subject: [PATCH 023/146] cleanup: render result json as a table Change-Id: I5a22cdab81df5b09fe5cd8ec3a5edea5acb7fb42 --- ui/index.html | 4 +++- ui/main.js | 12 +++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ui/index.html b/ui/index.html index 3fdfbb1..a7265a1 100644 --- a/ui/index.html +++ b/ui/index.html @@ -44,7 +44,9 @@

- + + + diff --git a/ui/main.js b/ui/main.js index e5cc82b..fa65e43 100644 --- a/ui/main.js +++ b/ui/main.js @@ -28,6 +28,7 @@ var date_days = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "31"]; + function init(){ createCombo(buildingSelect, buildings); @@ -40,7 +41,6 @@ function init(){ createCombo(dateYearSelect, date_years); createCombo(dateMonthSelect, date_months); createCombo(dateDaySelect, date_days); - } function createCombo(container, data) { @@ -61,8 +61,8 @@ function loadRooms(postData) { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (xhttp.readyState == 4 && xhttp.status == 200) { - //document.getElementById("demo").innerHTML = xhttp.responseText; - console.log(xhttp.responseText) + showFreeRooms(xhttp.responseText); + console.log(xhttp.responseText); } }; xhttp.open("POST", "http://www.w3schools.com/ajax/demo_post2.asp", true); @@ -70,6 +70,12 @@ function loadRooms(postData) { xhttp.send(postData); } +function showFreeRooms(rooms_json) { + var tbl = document.getElementById('mytable'); + for (var key in rooms_json) { + tbl.innerHTML += ""; + } +} From 9e0dce946844fbad4b10cae5149de76f4f07d997 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 14:19:00 -0700 Subject: [PATCH 024/146] sgersapp: Handle duration with both h and m Change-Id: I703aab21f338751a869ba561b8fad95d3b65c3d5 --- find_available_room.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index d9c5be7..093b69a 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -30,7 +30,9 @@ def __init__(self, user, password, self.start_time = start_time try: - if duration.endswith('h'): + if 'h' in duration and duration.endswith('m'): + hours, mins = map(int, duration[:-1].split('h')) + elif duration.endswith('h'): hours, mins = int(duration[:-1]), 0 elif duration.endswith('m'): hours, mins = 0, int(duration[:-1]) @@ -41,7 +43,7 @@ def __init__(self, user, password, else: hours, mins = 0, duration except ValueError: - self.duration = (1, 0) + hours, mins = 1, 0 start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") self.end_time = (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() @@ -101,6 +103,7 @@ def search(self, selected_rooms=None, print_to_stdout=False): status = "Free" elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy") + freebusy = '' for elem in elems: freebusy = elem.text if '2' in freebusy: From 7e52bb279798ad662e744b8c8f84fd1124b6c2bb Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 14:30:14 -0700 Subject: [PATCH 025/146] mrathor: Add GUI and JS Change-Id: I0635609c2107275ec99803c0a38039bbea302797 --- service/static/js/main.js | 81 ++++++++++++++++++++++++++++++++++++ service/templates/index.html | 53 +++++++++++++++++++++++ service/webserver.py | 43 ++++++++++--------- 3 files changed, 157 insertions(+), 20 deletions(-) create mode 100644 service/static/js/main.js create mode 100644 service/templates/index.html diff --git a/service/static/js/main.js b/service/static/js/main.js new file mode 100644 index 0000000..4a8eca1 --- /dev/null +++ b/service/static/js/main.js @@ -0,0 +1,81 @@ +var buildings = [] +var floors = ["1", "2", "3", "4", "5"]; +var times_hours = ["00", "01", "02","03", + "04", "05", "06", "07", + "08", "09", "10", "11", + "12", "13", "14", "15", + "16", "17", "18", "19", + "20", "21", "22", "23"]; +var times_mins = ["00", "15", "30","45"]; +var duration_hours = ["0h", "1h", "2h","3h", + "4h", "5h", "6h", "7h"]; +var duration_mins = ["00m", "15m", "30m","45m"]; +var sizes = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "20", "23", "24", "25", "30", "36", "50", "60", "65", "68", "70", "100", "120", "410"] +var date_years = ["2016", "2017"]; +var date_months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; +var date_days = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", + "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", + "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", + "31"]; + +function init(){ + loadBuildingNamesList(); + createCombo(buildingSelect, buildings); + createCombo(floorSelect, floors); + createCombo(startTimeHourSelect, times_hours); + createCombo(startTimeMinSelect, times_mins); + createCombo(durationHourSelect, duration_hours); + createCombo(durationMinSelect, duration_mins); + createCombo(roomSizeSelect, sizes); + createCombo(dateYearSelect, date_years); + createCombo(dateMonthSelect, date_months); + createCombo(dateDaySelect, date_days); +} + +function createCombo(container, data) { + var options = ''; + for (var i = 0; i < data.length; i++) { + container.options.add(new Option(data[i], data[i])); + } +} + +function loadBuildingNamesList() { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open( "GET", "http://localhost:5000/showbuldings", false ); // false for synchronous request + xmlHttp.send( null ); + buildings = JSON.parse(xmlHttp.responseText); + console.log(buildings); +} + +http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** + +function submitClickHandler() { + var queryString = `\?user=${userNameInput.value}\&password=${passwordInput.value}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${dateYearSelect.value}-${dateMonthSelect.value}-${dateDaySelect.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; + loadRooms(queryString); + //console.log(queryString); +} + +function loadRooms(queryString) { + var xmlHttp = new XMLHttpRequest(); + url = "http://localhost:5000/showrooms"; + url = url.concat(queryString); + + xmlHttp.open( "GET", url, false ); // false for synchronous request + xmlHttp.send( null ); + avaibale_rooms = JSON.parse(xmlHttp.responseText); + console.log(avaibale_rooms); +// var xhttp = new XMLHttpRequest(); +// xhttp.onreadystatechange = function() { +// if (xhttp.readyState == 4 && xhttp.status == 200) { +// //document.getElementById("demo").innerHTML = xhttp.responseText; +// console.log(xhttp.responseText) +// } +// }; +// xhttp.open("POST", "http://www.w3schools.com/ajax/demo_post2.asp", true); +// xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); +// xhttp.send(postData); +} + + + + diff --git a/service/templates/index.html b/service/templates/index.html new file mode 100644 index 0000000..6dc4348 --- /dev/null +++ b/service/templates/index.html @@ -0,0 +1,53 @@ + + + + + + + +
+
+ +
+
+
+ User Name: + Password: +
+ +

+
+ + + + + + +
+
+
+ + + + + + + + + + +
+
+ + +

+ +
+
+ + +
+
+ + diff --git a/service/webserver.py b/service/webserver.py index 979aa80..db5493f 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -1,16 +1,17 @@ import codecs +from collections import namedtuple import csv import datetime +from find_available_room import AvailRoomFinder +from flask import Flask +import flask import json import os -import subprocess -import xml.etree.ElementTree as ET - -from collections import namedtuple -from flask import Flask from shutil import copyfile from string import Template -from find_available_room import AvailRoomFinder +import subprocess +import xml.etree.ElementTree as ET +from flask import Flask, request PWD = os.getcwd() @@ -23,16 +24,13 @@ 'allrooms' : PWD + '/allrooms.csv', } -app = Flask(__name__) +app = Flask(__name__, template_folder=CONFIG['home'] + '/service/templates') @app.route('/') def index(): - return "Welcome to Room Finder Web Service" + return flask.render_template('index.html') - -from flask import request - -QueryParam = namedtuple('QueryParam', 'roomname, starttime, duration, user, password') +QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password') @app.route('/showbuldings', methods=['GET']) def show_buldings(): @@ -46,29 +44,34 @@ def show_buldings(): # Example Query -# http://127.0.0.1:5000/showrooms?roomname=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password +# http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password @app.route('/showrooms', methods=['GET']) def show_rooms(): queryparam = QueryParam( - roomname=request.args.get('roomname'), +# roomname=request.args.get('roomname'), + buildingname=request.args.get('buildingname'), + floor=request.args.get('floor'), starttime=request.args.get('starttime'), duration=request.args.get('duration'), user = request.args.get('user'), password = request.args.get('password'), ) - _create_tmp_rooms_file(queryparam.roomname) + prefix = queryparam.buildingname + '-' + queryparam.floor + _create_tmp_rooms_file(prefix) room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, queryparam.duration, CONFIG['roomssearchcsv']) - rooms_info = room_finder.search_free(prefix=queryparam.roomname, print_to_stdout=True) - + rooms_info = room_finder.search_free(prefix, print_to_stdout=True) + print queryparam + print prefix + print rooms_info return json.dumps(rooms_info) -def _create_tmp_rooms_file(roomname): - if 'all' in roomname: +def _create_tmp_rooms_file(building_floor_name): + if 'all' in building_floor_name: copyfile(CONFIG['roomscsv'], CONFIG['roomssearchcsv']) else: - open(CONFIG['roomssearchcsv'],'w').writelines([ line for line in open(CONFIG['roomscsv']) if roomname in line]) + open(CONFIG['roomssearchcsv'],'w').writelines([ line for line in open(CONFIG['roomscsv']) if building_floor_name in line]) if __name__ == '__main__': app.run(debug=True) From 3ce56670b52ff7de88723395593a84f3594e1a90 Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 14:33:23 -0700 Subject: [PATCH 026/146] mrathor: Update Index.html Change-Id: Ibbc38f4c69f19b0950103f1195391644fcbfd303 --- ui/index.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ui/index.html b/ui/index.html index a7265a1..6dc4348 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1,7 +1,7 @@ - + @@ -21,8 +21,9 @@ - - + +
@@ -44,9 +45,7 @@

- - -
" + key + "
+ From b8982a1c1d88512af07bc846d5ceedfd78dba7e8 Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 14:54:31 -0700 Subject: [PATCH 027/146] mrathor: Remove Room Size list Change-Id: I6228b5252b9ff3e52b2467d512d9f714d7d4b783 --- service/static/js/main.js | 21 ++++------ service/templates/index.html | 6 ++- ui/index.html | 53 ----------------------- ui/main.js | 81 ------------------------------------ 4 files changed, 13 insertions(+), 148 deletions(-) delete mode 100644 ui/index.html delete mode 100644 ui/main.js diff --git a/service/static/js/main.js b/service/static/js/main.js index 4a8eca1..19c75c6 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -26,7 +26,6 @@ function init(){ createCombo(startTimeMinSelect, times_mins); createCombo(durationHourSelect, duration_hours); createCombo(durationMinSelect, duration_mins); - createCombo(roomSizeSelect, sizes); createCombo(dateYearSelect, date_years); createCombo(dateMonthSelect, date_months); createCombo(dateDaySelect, date_days); @@ -50,6 +49,7 @@ function loadBuildingNamesList() { http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** function submitClickHandler() { + document.getElementById('mytable').innerHTML = ""; var queryString = `\?user=${userNameInput.value}\&password=${passwordInput.value}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${dateYearSelect.value}-${dateMonthSelect.value}-${dateDaySelect.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; loadRooms(queryString); //console.log(queryString); @@ -63,19 +63,16 @@ function loadRooms(queryString) { xmlHttp.open( "GET", url, false ); // false for synchronous request xmlHttp.send( null ); avaibale_rooms = JSON.parse(xmlHttp.responseText); - console.log(avaibale_rooms); -// var xhttp = new XMLHttpRequest(); -// xhttp.onreadystatechange = function() { -// if (xhttp.readyState == 4 && xhttp.status == 200) { -// //document.getElementById("demo").innerHTML = xhttp.responseText; -// console.log(xhttp.responseText) -// } -// }; -// xhttp.open("POST", "http://www.w3schools.com/ajax/demo_post2.asp", true); -// xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); -// xhttp.send(postData); + showFreeRooms(avaibale_rooms); + } +function showFreeRooms(rooms_json) { + var tbl = document.getElementById('mytable'); + for (var key in rooms_json) { + tbl.innerHTML += ""; + } +} diff --git a/service/templates/index.html b/service/templates/index.html index 6dc4348..a835873 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -22,8 +22,7 @@ - + -->
@@ -47,6 +46,9 @@
+
" + key + "
+
+ diff --git a/ui/index.html b/ui/index.html deleted file mode 100644 index 6dc4348..0000000 --- a/ui/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - -
-
- -
-
-
- User Name: - Password: -
- -

-
- - - - - - -
-
-
- - - - - - - - - - -
-
- - -

- -
-
- - -
-
- - diff --git a/ui/main.js b/ui/main.js deleted file mode 100644 index fa65e43..0000000 --- a/ui/main.js +++ /dev/null @@ -1,81 +0,0 @@ -var buildings = ["SJC02", "SJC03", "SJC04", "SJC05", - "SJC06", "SJC07", "SJC08", "SJC09", - "SJC10", "SJC11", "SJC12", "SJC13", - "SJC14", "SJC15", "SJC16", "SJC17", - "SJC18", "SJC19", "SJC20", "SJC21", - "SJC22", "SJC23", "SJC24", "SJC28", - "SJC29", "SJC30", "SJC31", "SJC32", - "SJCA", "SJCB", "SJCD", "SJCI", "SJCJ", "SJCK", - "SJCL", "SJCM", "SJCMR1", "SJCMR3", "SJCN", - "SJCO", "SJCP", "SJCPLM01", "SJCQ", "SJCSYC02"] -var floors = ["1", "2", "3", "4", "5"]; -var times_hours = ["00", "01", "02","03", - "04", "05", "06", "07", - "08", "09", "10", "11", - "12", "13", "14", "15", - "16", "17", "18", "19", - "20", "21", "22", "23"]; -var times_mins = ["00", "15", "30","45"]; -var duration_hours = ["0h", "1h", "2h","3h", - "4h", "5h", "6h", "7h"]; -var duration_mins = ["", "15m", "30m","45m"]; -var sizes = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "20", "23", "24", "25", "30", "36", "50", "60", "65", "68", "70", "100", "120", "410"] -var date_years = ["2016", "2017"]; -var date_months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; -var date_days = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", - "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", - "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", - "31"]; - - - -function init(){ - - createCombo(buildingSelect, buildings); - createCombo(floorSelect, floors); - createCombo(startTimeHourSelect, times_hours); - createCombo(startTimeMinSelect, times_mins); - createCombo(durationHourSelect, duration_hours); - createCombo(durationMinSelect, duration_mins); - createCombo(roomSizeSelect, sizes); - createCombo(dateYearSelect, date_years); - createCombo(dateMonthSelect, date_months); - createCombo(dateDaySelect, date_days); -} - -function createCombo(container, data) { - var options = ''; - for (var i = 0; i < data.length; i++) { - container.options.add(new Option(data[i], data[i])); - } -} - -function submitClickHandler() { - - var queryString = `?userName=${userNameInput.value}&password=${passwordInput.value}&buildingName=${buildingSelect.value}&floor=${floorSelect.value}&startTime=${startTimeSelect.value}&endTime=${endTimeSelect.value}`; - loadRooms(queryString); - //console.log(queryString); -} - -function loadRooms(postData) { - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function() { - if (xhttp.readyState == 4 && xhttp.status == 200) { - showFreeRooms(xhttp.responseText); - console.log(xhttp.responseText); - } - }; - xhttp.open("POST", "http://www.w3schools.com/ajax/demo_post2.asp", true); - xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhttp.send(postData); -} - -function showFreeRooms(rooms_json) { - var tbl = document.getElementById('mytable'); - for (var key in rooms_json) { - tbl.innerHTML += "" + key + ""; - } -} - - - From 142512bdd306afc648ac9c1f9f54886e734b0198 Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 14:55:27 -0700 Subject: [PATCH 028/146] mrathor: Remove Room Size list Change-Id: I075d98fc1c38f9a2c2f2195796faf63a29e20c4a --- service/templates/index.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/service/templates/index.html b/service/templates/index.html index a835873..97512ba 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -21,8 +21,6 @@ -

From 80cec0ff8c344ae3bd61110a94e5a92721f83059 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 15:10:14 -0700 Subject: [PATCH 029/146] sgersapp: threading to speed up queries Change-Id: If4338d22d79013dad6e0ecaea8a8e008656cd2f5 --- find_available_room.py | 57 +++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 093b69a..e2273b1 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -7,6 +7,7 @@ import getpass import subprocess import sys +import threading import xml.etree.ElementTree as ET from string import Template @@ -28,6 +29,7 @@ def __init__(self, user, password, self.user = user self.password = password self.start_time = start_time + self.room_info = {} try: if 'h' in duration and duration.endswith('m'): @@ -72,10 +74,35 @@ def search_free(self, prefix, min_size=1, print_to_stdout=False): free_room_info[email] = selected_room_info[email] return free_room_info + def _query(self, command, email, print_to_stdout=False): + response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + if not response: + return + + tree = ET.fromstring(response) + + status = "Free" + elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy") + freebusy = '' + for elem in elems: + freebusy = elem.text + if '2' in freebusy: + status = "Busy" + elif '3' in freebusy: + status = "Unavailable" + elif '1' in freebusy: + status = "Tentative" + + name, size = self.rooms[email] + self.room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status} + + if print_to_stdout: + print "{0:20s} {1:64s} {2:64s}".format(status + '-' + freebusy, self.rooms[email], email) + def search(self, selected_rooms=None, print_to_stdout=False): if selected_rooms is None: selected_rooms = self.rooms - room_info = {} + worker_threads = [] if print_to_stdout: print "Searching for a room from " + self.start_time + " to " + self.end_time + ":" @@ -95,31 +122,15 @@ def search(self, selected_rooms=None, print_to_stdout=False): + "' --ntlm " \ + "-u "+ self.user + ":" + self.password \ + " " + URL - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - if not response: - return room_info - - tree = ET.fromstring(response) - - status = "Free" - elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy") - freebusy = '' - for elem in elems: - freebusy = elem.text - if '2' in freebusy: - status = "Busy" - elif '3' in freebusy: - status = "Unavailable" - elif '1' in freebusy: - status = "Tentative" - name, size = self.rooms[email] - room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status} + thread = threading.Thread(target=self._query, args=(command, email, print_to_stdout)) + thread.start() + worker_threads.append(thread) - if print_to_stdout: - print "{0:20s} {1:64s} {2:64s}".format(status + '-' + freebusy, self.rooms[email], email) + for thread in worker_threads: + thread.join() - return room_info + return self.room_info def run(): parser = argparse.ArgumentParser() From bee89c5dc28fa333bcf6718711bf3df40f090f56 Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 15:14:18 -0700 Subject: [PATCH 030/146] mrathor: Reserve room API Change-Id: I2894102cee9322a334b8b2e0d7e218bd7277b494 --- book_room.py | 110 ++++++++++++++++++++++++++++++++++ reserve_resource_template.xml | 32 ++++++++++ 2 files changed, 142 insertions(+) create mode 100644 book_room.py create mode 100644 reserve_resource_template.xml diff --git a/book_room.py b/book_room.py new file mode 100644 index 0000000..8240010 --- /dev/null +++ b/book_room.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import argparse +import csv +import datetime +import getpass +import os +import subprocess +import sys +import xml.etree.ElementTree as ET + +from string import Template + +PWD = os.getcwd() +URL = 'https://mail.cisco.com/ews/exchange.asmx' +SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' +TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() +TIME_1H_FROM_NOW = None +TIME_ZONE = '-13:00' #PST HACK + +reload(sys) +sys.setdefaultencoding("utf-8") + +CONFIG = { + 'home' : PWD, + 'roomscsv' : PWD + '/rooms.csv', + 'roomssearchcsv' : PWD + '/roomssearch.csv', + 'availibility_template' : PWD + '/getavailibility_template.xml', + 'URL': "https://mail.cisco.com/ews/exchange.asmx", + 'allrooms' : PWD + '/allrooms.csv', + } + + +class ReserveAvailRoom(object): + + def __init__(self, user, password, + start_time=TIME_NOW, end_time=None, + roominfo='rooms.csv'): + self.user = user + self.password = password + self.start_time = start_time + if end_time is None: + start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") + self.end_time = (start + datetime.timedelta(hours=1)).isoformat() + else: + self.end_time = end_time + + def reserve_room(self, selected_room, print_to_stdout=False): + room_info = {} + + xml_template = open("reserve_resource_template.xml", "r").read() + xml = Template(xml_template) + + resourceemail = "" + with open(CONFIG['allrooms'],'r') as f: + for line in f.readlines(): + if selected_room in line: + resourceemail = line.split(',')[1] + + useremail = self.user + '@cisco.com' + data = unicode(xml.substitute(resourceemail=resourceemail, + useremail=useremail, + subject="RoomFinderApp", + starttime=self.start_time + TIME_ZONE, + endtime=self.end_time + TIME_ZONE)) + + header = "\"content-type: text/xml;charset=utf-8\"" + command = "curl --silent --header " + header \ + + " --data '" + data \ + + "' --ntlm " \ + + "-u "+ self.user + ":" + self.password \ + + " " + URL + response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + + tree = ET.fromstring(response) + if print_to_stdout: + print response + + return response + +def run(): + parser = argparse.ArgumentParser() + parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) + parser.add_argument("-prefix", "--prefix", + help="A prefix to search for. e.g. 'SJC19- SJC18-'", + default='') + parser.add_argument("-start", "--starttime", + help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", + default=TIME_NOW) + parser.add_argument("-end", "--endtime", + help="Endtime e.g. 2014-07-02T12:00:00 (default = now+1h)", + default=TIME_1H_FROM_NOW) + parser.add_argument("-f", "--file", + help="csv filename with room info (default=rooms.csv).", + default="rooms.csv") + + parser.add_argument("-r", "--room", + help="Name of conf room", + required=True) + + args = parser.parse_args() + args.password = getpass.getpass("Password:") + + room_finder = ReserveAvailRoom(args.user, args.password, args.starttime, args.endtime, args.file) + print room_finder.reserve_room(args.room, print_to_stdout=True) + + +if __name__ == '__main__': + run() diff --git a/reserve_resource_template.xml b/reserve_resource_template.xml new file mode 100644 index 0000000..989e327 --- /dev/null +++ b/reserve_resource_template.xml @@ -0,0 +1,32 @@ + + + + + + $subject + $subject + $starttime + $endtime + Conf Room + + + + $resourceemail + + + + + + + $useremail + + + + + + + + From 86bac996e55b4a12aba3c1c8417f71c535a7f3f8 Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 15:41:28 -0700 Subject: [PATCH 031/146] mrathor: Update Date Field Change-Id: Iadbda24a3b7e1b7629b23e58ee5314f9a80d4a0b --- service/static/js/main.js | 28 +++++++++++++++++++++------- service/templates/index.html | 4 +--- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index 19c75c6..27ff63f 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -26,9 +26,13 @@ function init(){ createCombo(startTimeMinSelect, times_mins); createCombo(durationHourSelect, duration_hours); createCombo(durationMinSelect, duration_mins); - createCombo(dateYearSelect, date_years); - createCombo(dateMonthSelect, date_months); - createCombo(dateDaySelect, date_days); + //createCombo(dateYearSelect, date_years); + //createCombo(dateMonthSelect, date_months); + //createCombo(dateDaySelect, date_days); + buildingSelect.value = "SJC19"; + floorSelect.value="3"; + startTimeHourSelect.value = "09"; + durationMinSelect.value = "30m"; } function createCombo(container, data) { @@ -49,10 +53,19 @@ function loadBuildingNamesList() { http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** function submitClickHandler() { - document.getElementById('mytable').innerHTML = ""; - var queryString = `\?user=${userNameInput.value}\&password=${passwordInput.value}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${dateYearSelect.value}-${dateMonthSelect.value}-${dateDaySelect.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; - loadRooms(queryString); + + var tableHeaderRowCount = 1; + var rowCount = mytable.rows.length; + for (var i = tableHeaderRowCount; i < rowCount; i++) { + mytable.deleteRow(tableHeaderRowCount); + console.log("clearing row number:"+i); + } + mytable.innerHTML = ""; + mytable.visiblity = false; + var queryString = `\?user=${userNameInput.value}\&password=${passwordInput.value}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${confereneceRoomDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; //console.log(queryString); + loadRooms(queryString); + } function loadRooms(queryString) { @@ -71,8 +84,9 @@ function loadRooms(queryString) { function showFreeRooms(rooms_json) { var tbl = document.getElementById('mytable'); for (var key in rooms_json) { - tbl.innerHTML += "" + key + ""; + tbl.innerHTML += "" + key + ""; } + mytable.visiblity = true; } diff --git a/service/templates/index.html b/service/templates/index.html index 97512ba..d6dfb4c 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -25,9 +25,7 @@
- - - + From b7cc92494649581c16e13102a09e4604f923f627 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 16:04:12 -0700 Subject: [PATCH 032/146] sgersapp: Use base64 password Change-Id: I8d3ad29d024a0ad4b7ae5bd51a057419b6a79b67 --- find_available_room.py | 6 +++-- service/static/js/main.js | 46 +++++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index e2273b1..64238d6 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -2,12 +2,14 @@ # -*- coding: utf-8 -*- import argparse +import base64 import csv import datetime import getpass import subprocess import sys import threading +import urllib import xml.etree.ElementTree as ET from string import Template @@ -120,7 +122,7 @@ def search(self, selected_rooms=None, print_to_stdout=False): command = "curl --silent --header " + header \ + " --data '" + data \ + "' --ntlm " \ - + "-u "+ self.user + ":" + self.password \ + + "-u "+ self.user + ":" + base64.b64decode(urllib.unquote(self.password)) \ + " " + URL thread = threading.Thread(target=self._query, args=(command, email, print_to_stdout)) @@ -149,7 +151,7 @@ def run(): default="rooms.csv") args = parser.parse_args() - args.password = getpass.getpass("Password:") + args.password = base64.b64encode(getpass.getpass("Password:")) room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.duration, args.file) print room_finder.search_free(prefix=args.prefix, print_to_stdout=True) diff --git a/service/static/js/main.js b/service/static/js/main.js index 27ff63f..e8dbf0d 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -26,9 +26,6 @@ function init(){ createCombo(startTimeMinSelect, times_mins); createCombo(durationHourSelect, duration_hours); createCombo(durationMinSelect, duration_mins); - //createCombo(dateYearSelect, date_years); - //createCombo(dateMonthSelect, date_months); - //createCombo(dateDaySelect, date_days); buildingSelect.value = "SJC19"; floorSelect.value="3"; startTimeHourSelect.value = "09"; @@ -47,37 +44,38 @@ function loadBuildingNamesList() { xmlHttp.open( "GET", "http://localhost:5000/showbuldings", false ); // false for synchronous request xmlHttp.send( null ); buildings = JSON.parse(xmlHttp.responseText); - console.log(buildings); + //console.log(buildings); } -http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** - +//Example: http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** + function submitClickHandler() { - - var tableHeaderRowCount = 1; - var rowCount = mytable.rows.length; - for (var i = tableHeaderRowCount; i < rowCount; i++) { - mytable.deleteRow(tableHeaderRowCount); - console.log("clearing row number:"+i); - } - mytable.innerHTML = ""; - mytable.visiblity = false; - var queryString = `\?user=${userNameInput.value}\&password=${passwordInput.value}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${confereneceRoomDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; - //console.log(queryString); + var tableHeaderRowCount = 1; + var rowCount = mytable.rows.length; + for (var i = tableHeaderRowCount; i < rowCount; i++) { + mytable.deleteRow(tableHeaderRowCount); + console.log("clearing row number:"+i); + } + mytable.innerHTML = ""; + mytable.visiblity = false; + + var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${confereneceRoomDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; loadRooms(queryString); - + } function loadRooms(queryString) { var xmlHttp = new XMLHttpRequest(); url = "http://localhost:5000/showrooms"; url = url.concat(queryString); - - xmlHttp.open( "GET", url, false ); // false for synchronous request - xmlHttp.send( null ); - avaibale_rooms = JSON.parse(xmlHttp.responseText); - showFreeRooms(avaibale_rooms); - + + xmlHttp.open("GET", url, false); // false for synchronous request + xmlHttp.send(null); + if (xmlHttp.responseText != "") { + avaiable_rooms = JSON.parse(xmlHttp.responseText); + showFreeRooms(avaiable_rooms); + } } From 57c1279fa4c0beb7fee8f260055f729d8f895beb Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 16:08:47 -0700 Subject: [PATCH 033/146] sgersapp: Handle exceptions from room finder Change-Id: I93616f9db189e8a9daabc30cde71445190898d2b --- service/webserver.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/service/webserver.py b/service/webserver.py index db5493f..c87c10c 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -59,9 +59,14 @@ def show_rooms(): prefix = queryparam.buildingname + '-' + queryparam.floor _create_tmp_rooms_file(prefix) - - room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, queryparam.duration, CONFIG['roomssearchcsv']) - rooms_info = room_finder.search_free(prefix, print_to_stdout=True) + + try: + room_finder = AvailRoomFinder(queryparam.user, queryparam.password, + queryparam.starttime, queryparam.duration, + CONFIG['roomssearchcsv']) + rooms_info = room_finder.search_free(prefix, print_to_stdout=True) + except Exception as e: + rooms_info = {"Error:" + str(e) : ""} print queryparam print prefix print rooms_info From 3a2c17b12d4ca918e0a9ab379726b41393e5d595 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 16:24:38 -0700 Subject: [PATCH 034/146] sgersapp: Auto select today's date and hour Change-Id: I97c4fe782a907af6ac5cd55b937aeb34d0c697ad --- service/static/js/main.js | 14 +++++++++----- service/templates/index.html | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index e8dbf0d..dcab7cb 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -28,8 +28,12 @@ function init(){ createCombo(durationMinSelect, duration_mins); buildingSelect.value = "SJC19"; floorSelect.value="3"; - startTimeHourSelect.value = "09"; durationMinSelect.value = "30m"; + + var date = new Date(); + var today = date.toISOString().split('T')[0]; + document.getElementById("bookDate").setAttribute('value', today); + document.getElementById("startTimeHourSelect").value = date.getHours(); } function createCombo(container, data) { @@ -60,7 +64,7 @@ function submitClickHandler() { mytable.visiblity = false; var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${confereneceRoomDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; loadRooms(queryString); } @@ -72,9 +76,9 @@ function loadRooms(queryString) { xmlHttp.open("GET", url, false); // false for synchronous request xmlHttp.send(null); - if (xmlHttp.responseText != "") { - avaiable_rooms = JSON.parse(xmlHttp.responseText); - showFreeRooms(avaiable_rooms); + if (xmlHttp.responseText.trim() != "") { + available_rooms = JSON.parse(xmlHttp.responseText); + showFreeRooms(available_rooms); } } diff --git a/service/templates/index.html b/service/templates/index.html index d6dfb4c..de34d05 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -25,7 +25,7 @@
- + From 71221bcc901212a34680218af184c70af2c088b0 Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 16:34:09 -0700 Subject: [PATCH 035/146] mrathor: Update find rooms Change-Id: I586e416a9bdd6fae0ee2b16c2f33fed8339e4df3 --- find_available_room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/find_available_room.py b/find_available_room.py index e2273b1..e658d02 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -94,7 +94,7 @@ def _query(self, command, email, print_to_stdout=False): status = "Tentative" name, size = self.rooms[email] - self.room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status} + self.room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status, 'email' : email} if print_to_stdout: print "{0:20s} {1:64s} {2:64s}".format(status + '-' + freebusy, self.rooms[email], email) From 29a3507893c36018992aa493e7e3d4fb4983b8e5 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 16:42:18 -0700 Subject: [PATCH 036/146] sgersapp: cleanup Change-Id: Ib416f7f584af6ea30758ed272e5334175a0ecbf5 --- service/static/js/main.js | 3 +-- service/templates/index.html | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index dcab7cb..056b198 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -84,9 +84,8 @@ function loadRooms(queryString) { function showFreeRooms(rooms_json) { - var tbl = document.getElementById('mytable'); for (var key in rooms_json) { - tbl.innerHTML += "" + key + ""; + mytable.innerHTML += "" + key + ""; } mytable.visiblity = true; } diff --git a/service/templates/index.html b/service/templates/index.html index de34d05..4269076 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -37,11 +37,10 @@

+

- -
From 4419c13a077d10964a62a639983fce15dbe48c97 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 16:52:39 -0700 Subject: [PATCH 037/146] sgersapp: support min room size Change-Id: Ia3d748853bbaada517bb512756f8042edb2cef9f --- .gitignore | 3 +++ find_available_room.py | 2 +- service/static/js/main.js | 3 ++- service/templates/index.html | 6 ++++-- service/webserver.py | 6 ++++-- 5 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6388d4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.bak +*.csv diff --git a/find_available_room.py b/find_available_room.py index 64238d6..7dc7063 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -66,7 +66,7 @@ def search_free(self, prefix, min_size=1, print_to_stdout=False): selected_rooms = {} for email in self.rooms: name, size = self.rooms[email] - if name.startswith(prefix) and size > min_size: + if name.startswith(prefix) and size >= min_size: selected_rooms[email] = (name, size) selected_room_info = self.search(selected_rooms, print_to_stdout) diff --git a/service/static/js/main.js b/service/static/js/main.js index 056b198..79e6cb0 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -22,6 +22,7 @@ function init(){ loadBuildingNamesList(); createCombo(buildingSelect, buildings); createCombo(floorSelect, floors); + createCombo(roomSizeSelect, sizes); createCombo(startTimeHourSelect, times_hours); createCombo(startTimeMinSelect, times_mins); createCombo(durationHourSelect, duration_hours); @@ -64,7 +65,7 @@ function submitClickHandler() { mytable.visiblity = false; var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}&attendees=${roomSizeSelect.value}`; loadRooms(queryString); } diff --git a/service/templates/index.html b/service/templates/index.html index 4269076..2202f98 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -17,10 +17,12 @@

- + - + + +

diff --git a/service/webserver.py b/service/webserver.py index c87c10c..9346037 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -30,7 +30,7 @@ def index(): return flask.render_template('index.html') -QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password') +QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees') @app.route('/showbuldings', methods=['GET']) def show_buldings(): @@ -55,6 +55,7 @@ def show_rooms(): duration=request.args.get('duration'), user = request.args.get('user'), password = request.args.get('password'), + attendees = request.args.get('attendees'), ) prefix = queryparam.buildingname + '-' + queryparam.floor @@ -64,7 +65,8 @@ def show_rooms(): room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, queryparam.duration, CONFIG['roomssearchcsv']) - rooms_info = room_finder.search_free(prefix, print_to_stdout=True) + rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees), + print_to_stdout=True) except Exception as e: rooms_info = {"Error:" + str(e) : ""} print queryparam From 06d318fb007b9283c303961166632af3313cade6 Mon Sep 17 00:00:00 2001 From: Ashish Sawalkar Date: Thu, 25 Aug 2016 16:57:28 -0700 Subject: [PATCH 038/146] Fix room reservation --- book_room.py | 33 ++++++++--------- reserve_resource_template.xml | 69 +++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/book_room.py b/book_room.py index 8240010..cac5d90 100644 --- a/book_room.py +++ b/book_room.py @@ -8,6 +8,7 @@ import os import subprocess import sys +import time import xml.etree.ElementTree as ET from string import Template @@ -17,16 +18,15 @@ SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() TIME_1H_FROM_NOW = None -TIME_ZONE = '-13:00' #PST HACK reload(sys) sys.setdefaultencoding("utf-8") -CONFIG = { +CONFIG = { 'home' : PWD, 'roomscsv' : PWD + '/rooms.csv', - 'roomssearchcsv' : PWD + '/roomssearch.csv', - 'availibility_template' : PWD + '/getavailibility_template.xml', + 'roomssearchcsv' : PWD + '/roomssearch.csv', + 'availibility_template' : PWD + '/getavailibility_template.xml', 'URL': "https://mail.cisco.com/ews/exchange.asmx", 'allrooms' : PWD + '/allrooms.csv', } @@ -34,9 +34,7 @@ class ReserveAvailRoom(object): - def __init__(self, user, password, - start_time=TIME_NOW, end_time=None, - roominfo='rooms.csv'): + def __init__(self, user, password, start_time=TIME_NOW, end_time=None, roominfo='rooms.csv'): self.user = user self.password = password self.start_time = start_time @@ -51,19 +49,23 @@ def reserve_room(self, selected_room, print_to_stdout=False): xml_template = open("reserve_resource_template.xml", "r").read() xml = Template(xml_template) - + resourceemail = "" - with open(CONFIG['allrooms'],'r') as f: + with open(CONFIG['allrooms'], 'r') as f: for line in f.readlines(): if selected_room in line: resourceemail = line.split(',')[1] - + useremail = self.user + '@cisco.com' + meeting_body = 'Meeting booked via Room Finder App by {0}'.format(useremail) data = unicode(xml.substitute(resourceemail=resourceemail, useremail=useremail, - subject="RoomFinderApp", - starttime=self.start_time + TIME_ZONE, - endtime=self.end_time + TIME_ZONE)) + subject="Room Finder Meeting", + starttime=self.start_time, + endtime=self.end_time, + meeting_body=meeting_body, + conf_room=selected_room, + )) header = "\"content-type: text/xml;charset=utf-8\"" command = "curl --silent --header " + header \ @@ -72,7 +74,7 @@ def reserve_room(self, selected_room, print_to_stdout=False): + "-u "+ self.user + ":" + self.password \ + " " + URL response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - + tree = ET.fromstring(response) if print_to_stdout: print response @@ -82,9 +84,6 @@ def reserve_room(self, selected_room, print_to_stdout=False): def run(): parser = argparse.ArgumentParser() parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) - parser.add_argument("-prefix", "--prefix", - help="A prefix to search for. e.g. 'SJC19- SJC18-'", - default='') parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", default=TIME_NOW) diff --git a/reserve_resource_template.xml b/reserve_resource_template.xml index 989e327..3498c3a 100644 --- a/reserve_resource_template.xml +++ b/reserve_resource_template.xml @@ -1,32 +1,39 @@ - - - - - - $subject - $subject - $starttime - $endtime - Conf Room - - - - $resourceemail - - - - - - - $useremail - - - - - - - + + + + + + + + + + + + + $subject + $meeting_body + 15 + $starttime + $endtime + $conf_room + + + + $resourceemail + + + + + + + $useremail + + + + + + + + From 1b475680013d319c2be59c3a24408ad5f77b340a Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 17:14:32 -0700 Subject: [PATCH 039/146] mrathor: Fix Book Room Change-Id: Icc4e88bac747bdd10d6ad8ddc3bb189f6aab8666 --- book_room.py | 53 ++++++++++++++++++++++++--------------- service/static/js/main.js | 20 ++++++++++++++- service/webserver.py | 25 ++++++++++++++---- 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/book_room.py b/book_room.py index 8240010..2de5aee 100644 --- a/book_room.py +++ b/book_room.py @@ -1,13 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - import argparse +import base64 import csv import datetime import getpass import os import subprocess import sys +import urllib import xml.etree.ElementTree as ET from string import Template @@ -17,7 +18,6 @@ SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() TIME_1H_FROM_NOW = None -TIME_ZONE = '-13:00' #PST HACK reload(sys) sys.setdefaultencoding("utf-8") @@ -34,17 +34,33 @@ class ReserveAvailRoom(object): - def __init__(self, user, password, - start_time=TIME_NOW, end_time=None, - roominfo='rooms.csv'): + def __init__(self, roomid, user, password, + start_time=TIME_NOW, + roominfo='rooms.csv', + duration='1h'): + self.roomid = roomid self.user = user - self.password = password + self.password = base64.b64decode(urllib.unquote(password)) self.start_time = start_time - if end_time is None: - start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") - self.end_time = (start + datetime.timedelta(hours=1)).isoformat() - else: - self.end_time = end_time + + try: + if 'h' in duration and duration.endswith('m'): + hours, mins = map(int, duration[:-1].split('h')) + elif duration.endswith('h'): + hours, mins = int(duration[:-1]), 0 + elif duration.endswith('m'): + hours, mins = 0, int(duration[:-1]) + else: + duration = int(duration) + if duration < 15: + hours, mins = duration, 0 + else: + hours, mins = 0, duration + except ValueError: + hours, mins = 1, 0 + + start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") + self.end_time = (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() def reserve_room(self, selected_room, print_to_stdout=False): room_info = {} @@ -52,18 +68,14 @@ def reserve_room(self, selected_room, print_to_stdout=False): xml_template = open("reserve_resource_template.xml", "r").read() xml = Template(xml_template) - resourceemail = "" - with open(CONFIG['allrooms'],'r') as f: - for line in f.readlines(): - if selected_room in line: - resourceemail = line.split(',')[1] + roomid = self.roomid useremail = self.user + '@cisco.com' - data = unicode(xml.substitute(resourceemail=resourceemail, + data = unicode(xml.substitute(resourceemail=roomid, useremail=useremail, subject="RoomFinderApp", - starttime=self.start_time + TIME_ZONE, - endtime=self.end_time + TIME_ZONE)) + starttime=self.start_time, + endtime=self.end_time)) header = "\"content-type: text/xml;charset=utf-8\"" command = "curl --silent --header " + header \ @@ -72,7 +84,8 @@ def reserve_room(self, selected_room, print_to_stdout=False): + "-u "+ self.user + ":" + self.password \ + " " + URL response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - + print command + print "******", response tree = ET.fromstring(response) if print_to_stdout: print response diff --git a/service/static/js/main.js b/service/static/js/main.js index dcab7cb..483a786 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -85,10 +85,28 @@ function loadRooms(queryString) { function showFreeRooms(rooms_json) { var tbl = document.getElementById('mytable'); + for (var key in rooms_json) { - tbl.innerHTML += "" + key + ""; + tbl.innerHTML += ''+key+''; + } + console.log(tbl.innerHTML) mytable.visiblity = true; } +function bookRoom(roomId) { + var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); + + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomid=${roomId}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; + var xmlHttp = new XMLHttpRequest(); + + url = "http://localhost:5000/bookroom"; + url = url.concat(queryString); + + xmlHttp.open("GET", url, false); // false for synchronous request + xmlHttp.send(null); + + console.log(roomId) + +} diff --git a/service/webserver.py b/service/webserver.py index c87c10c..8bf61b7 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -2,7 +2,8 @@ from collections import namedtuple import csv import datetime -from find_available_room import AvailRoomFinder +from find_available_room import AvailRoomFinder +from book_room import ReserveAvailRoom from flask import Flask import flask import json @@ -31,6 +32,7 @@ def index(): return flask.render_template('index.html') QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password') +BookRoomQueryParam = namedtuple('QueryParam', 'roomid, starttime, duration, user, password') @app.route('/showbuldings', methods=['GET']) def show_buldings(): @@ -48,7 +50,6 @@ def show_buldings(): @app.route('/showrooms', methods=['GET']) def show_rooms(): queryparam = QueryParam( -# roomname=request.args.get('roomname'), buildingname=request.args.get('buildingname'), floor=request.args.get('floor'), starttime=request.args.get('starttime'), @@ -67,11 +68,25 @@ def show_rooms(): rooms_info = room_finder.search_free(prefix, print_to_stdout=True) except Exception as e: rooms_info = {"Error:" + str(e) : ""} - print queryparam - print prefix - print rooms_info return json.dumps(rooms_info) +@app.route('/bookroom', methods=['GET']) +def book_room(): + queryparam = BookRoomQueryParam( + roomid=request.args.get('roomid'), + starttime=request.args.get('starttime'), + duration=request.args.get('duration'), + user = request.args.get('user'), + password = request.args.get('password'), + ) + + room_finder = ReserveAvailRoom(queryparam.roomid, queryparam.user, queryparam.password, + queryparam.starttime, queryparam.duration, + CONFIG['roomssearchcsv']) + rooms_info = room_finder.reserve_room(queryparam.roomid, print_to_stdout=True) + return json.dumps(rooms_info) + + def _create_tmp_rooms_file(building_floor_name): if 'all' in building_floor_name: copyfile(CONFIG['roomscsv'], CONFIG['roomssearchcsv']) From 9d695647dd18315f970ea8de76e23c32b0c8ce92 Mon Sep 17 00:00:00 2001 From: Monika Date: Thu, 25 Aug 2016 17:48:22 -0700 Subject: [PATCH 040/146] mrathor: Fix Book Room Change-Id: Ia2595a2855eb44605fda41a00fa5395d1119f036 --- service/webserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/webserver.py b/service/webserver.py index 6060825..ab2b126 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -31,7 +31,7 @@ def index(): return flask.render_template('index.html') -QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password attendees' ) +QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees' ) BookRoomQueryParam = namedtuple('QueryParam', 'roomid, starttime, duration, user, password') @app.route('/showbuldings', methods=['GET']) From 48e6d174e6f75df2bb19ec08b677c69daf8959e1 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 22:02:09 -0700 Subject: [PATCH 041/146] sgersapp: Fix room booking Change-Id: I5eadb09ea81dd614fab396dc8d4983d963143aaa --- .gitignore | 1 + book_room.py | 16 ++++------------ service/static/js/main.js | 18 ++++++------------ service/webserver.py | 17 ++++++++++------- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 6388d4d..bd163a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc *.bak *.csv +*.swp diff --git a/book_room.py b/book_room.py index 996d8bc..055ce6c 100644 --- a/book_room.py +++ b/book_room.py @@ -2,15 +2,12 @@ # -*- coding: utf-8 -*- import argparse import base64 -import csv import datetime import getpass import os import subprocess import sys import urllib -import time -import xml.etree.ElementTree as ET from string import Template @@ -35,11 +32,10 @@ class ReserveAvailRoom(object): - def __init__(self, roomid, user, password, + def __init__(self, roomname, roomemail, user, password, start_time=TIME_NOW, - roominfo='rooms.csv', duration='1h'): - self.roomid = roomid + self.roomemail = roomemail self.user = user self.password = base64.b64decode(urllib.unquote(password)) self.start_time = start_time @@ -65,14 +61,13 @@ def __init__(self, roomid, user, password, self.end_time = (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() def reserve_room(self, selected_room, print_to_stdout=False): - room_info = {} - xml_template = open("reserve_resource_template.xml", "r").read() xml = Template(xml_template) useremail = self.user + '@cisco.com' meeting_body = 'Meeting booked via Room Finder App by {0}'.format(useremail) - data = unicode(xml.substitute(resourceemail=self.roomid, + + data = unicode(xml.substitute(resourceemail=self.roomemail, useremail=useremail, subject="Room Finder Meeting", starttime=self.start_time, @@ -88,9 +83,6 @@ def reserve_room(self, selected_room, print_to_stdout=False): + "-u "+ self.user + ":" + self.password \ + " " + URL response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - - tree = ET.fromstring(response) - return response def run(): diff --git a/service/static/js/main.js b/service/static/js/main.js index e280149..15aad4b 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -85,18 +85,17 @@ function loadRooms(queryString) { function showFreeRooms(rooms_json) { - var tbl = document.getElementById('mytable'); - for (var key in rooms_json) { - tbl.innerHTML += ''+key+''; + var roomemail = rooms_json[key]["email"]; + mytable.innerHTML += '' + key + ''; } - console.log(tbl.innerHTML) mytable.visiblity = true; } -function bookRoom(roomId) { + +function bookRoom(roomname, roomemail) { var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomid=${roomId}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${roomname}&roomemail=${roomemail}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; var xmlHttp = new XMLHttpRequest(); url = "http://localhost:5000/bookroom"; @@ -104,12 +103,7 @@ function bookRoom(roomId) { xmlHttp.open("GET", url, false); // false for synchronous request xmlHttp.send(null); - - console.log(roomId) - - var tbl = document.getElementById('mytable'); - tbl.innerHTML += '' - tbl.innerHTML += '' + roomId + " " + xmlHttp.responseText + '' + mytable.innerHTML = '' + roomname + " " + xmlHttp.responseText + ''; } diff --git a/service/webserver.py b/service/webserver.py index ab2b126..ac34079 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -32,7 +32,7 @@ def index(): return flask.render_template('index.html') QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees' ) -BookRoomQueryParam = namedtuple('QueryParam', 'roomid, starttime, duration, user, password') +BookRoomQueryParam = namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password') @app.route('/showbuldings', methods=['GET']) def show_buldings(): @@ -75,17 +75,20 @@ def show_rooms(): @app.route('/bookroom', methods=['GET']) def book_room(): queryparam = BookRoomQueryParam( - roomid=request.args.get('roomid'), + roomname=request.args.get('roomname'), + roomemail=request.args.get('roomemail'), starttime=request.args.get('starttime'), duration=request.args.get('duration'), user = request.args.get('user'), password = request.args.get('password'), ) - room_finder = ReserveAvailRoom(queryparam.roomid, queryparam.user, queryparam.password, - queryparam.starttime, - CONFIG['roomssearchcsv'], - queryparam.duration) - rooms_info = room_finder.reserve_room(queryparam.roomid, print_to_stdout=True) + room_finder = ReserveAvailRoom(queryparam.roomname, + queryparam.roomemail, + queryparam.user, + queryparam.password, + queryparam.starttime, + queryparam.duration) + rooms_info = room_finder.reserve_room(queryparam.roomemail, print_to_stdout=True) if 'Success' in rooms_info: return "Room Reserved Successfully" From bd46adbdc5340fb2093dde514e028769b16c4cc4 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 22:23:27 -0700 Subject: [PATCH 042/146] sgersapp: Fix room name in booking Change-Id: I125639307f327a94b89a735888b23ae8660ff75d --- book_room.py | 14 ++++++++------ service/webserver.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/book_room.py b/book_room.py index 055ce6c..51742c0 100644 --- a/book_room.py +++ b/book_room.py @@ -35,6 +35,7 @@ class ReserveAvailRoom(object): def __init__(self, roomname, roomemail, user, password, start_time=TIME_NOW, duration='1h'): + self.roomname = roomname self.roomemail = roomemail self.user = user self.password = base64.b64decode(urllib.unquote(password)) @@ -60,20 +61,21 @@ def __init__(self, roomname, roomemail, user, password, start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") self.end_time = (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() - def reserve_room(self, selected_room, print_to_stdout=False): + def reserve_room(self, print_to_stdout=False): xml_template = open("reserve_resource_template.xml", "r").read() xml = Template(xml_template) useremail = self.user + '@cisco.com' - meeting_body = 'Meeting booked via Room Finder App by {0}'.format(useremail) + meeting_body = '{0} booked via RoomFinder by {1}'.format(self.roomname, useremail) + subject = 'RoomFinder: {0}'.format(self.roomname) data = unicode(xml.substitute(resourceemail=self.roomemail, useremail=useremail, - subject="Room Finder Meeting", + subject=subject, starttime=self.start_time, endtime=self.end_time, meeting_body=meeting_body, - conf_room=selected_room, + conf_room=self.roomname, )) header = "\"content-type: text/xml;charset=utf-8\"" @@ -105,8 +107,8 @@ def run(): args = parser.parse_args() args.password = getpass.getpass("Password:") - room_finder = ReserveAvailRoom(args.user, args.password, args.starttime, args.endtime, args.file) - print room_finder.reserve_room(args.room, print_to_stdout=True) + room_finder = ReserveAvailRoom(args.room, args.user, args.password, args.starttime, args.endtime, args.file) + print room_finder.reserve_room(print_to_stdout=True) if __name__ == '__main__': diff --git a/service/webserver.py b/service/webserver.py index ac34079..f74c8f5 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -88,7 +88,7 @@ def book_room(): queryparam.password, queryparam.starttime, queryparam.duration) - rooms_info = room_finder.reserve_room(queryparam.roomemail, print_to_stdout=True) + rooms_info = room_finder.reserve_room(print_to_stdout=True) if 'Success' in rooms_info: return "Room Reserved Successfully" From 2cebc1f6af931d50a7b795fa3f48ced74453ac6f Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 25 Aug 2016 22:31:52 -0700 Subject: [PATCH 043/146] sgersapp: Change Reserve from link to button Change-Id: I80e33abbe8c162c97764dc54561efb6cb8b29765 --- service/static/js/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index 15aad4b..4f6b4bb 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -87,7 +87,8 @@ function loadRooms(queryString) { function showFreeRooms(rooms_json) { for (var key in rooms_json) { var roomemail = rooms_json[key]["email"]; - mytable.innerHTML += '' + key + ''; +// mytable.innerHTML += '' + key + ''; + mytable.innerHTML += '' + key + ''; } mytable.visiblity = true; } From 792282684733801e77d31c19268bce3571d53215 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 26 Aug 2016 10:36:13 -0700 Subject: [PATCH 044/146] sgersapp: Fix room size comparison Change-Id: I512a5d4d49b89909319d233480b201f289573d58 --- find_available_room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/find_available_room.py b/find_available_room.py index 1649b93..14e4a59 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -66,7 +66,7 @@ def search_free(self, prefix, min_size=1, print_to_stdout=False): selected_rooms = {} for email in self.rooms: name, size = self.rooms[email] - if name.startswith(prefix) and size > min_size: + if name.startswith(prefix) and size >= min_size: selected_rooms[email] = (name, size) selected_room_info = self.search(selected_rooms, print_to_stdout) From 2ec2345acb49a79bf751609a1f7cb341e8e77130 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 26 Aug 2016 10:36:50 -0700 Subject: [PATCH 045/146] sgersapp: Fix default time selection for early morning Change-Id: Ic3681ce974890987f6b7ce8cb1f8cb825712754c --- service/static/js/main.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index 4f6b4bb..45be17e 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -1,11 +1,11 @@ var buildings = [] var floors = ["1", "2", "3", "4", "5"]; var times_hours = ["00", "01", "02","03", - "04", "05", "06", "07", - "08", "09", "10", "11", - "12", "13", "14", "15", - "16", "17", "18", "19", - "20", "21", "22", "23"]; + "4", "05", "06", "07", + "08", "09", "10", "11", + "12", "13", "14", "15", + "16", "17", "18", "19", + "20", "21", "22", "23"]; var times_mins = ["00", "15", "30","45"]; var duration_hours = ["0h", "1h", "2h","3h", "4h", "5h", "6h", "7h"]; @@ -34,7 +34,11 @@ function init(){ var date = new Date(); var today = date.toISOString().split('T')[0]; document.getElementById("bookDate").setAttribute('value', today); - document.getElementById("startTimeHourSelect").value = date.getHours(); + var current_hour = date.getHours(); + if (current_hour.length < 2) { + current_hour = "0" + current_hour + } + document.getElementById("startTimeHourSelect").value = current_hour; } function createCombo(container, data) { From 9963dfa26d37b51335429718537eec43aa169f1f Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 26 Aug 2016 11:25:23 -0700 Subject: [PATCH 046/146] sgersapp: Get user timezone for querying available rooms Change-Id: Ic8e48676dd5e58e53f456a160cbe32517ae12429 --- find_available_room.py | 12 +++++++----- getavailibility_template.xml | 4 ++-- service/static/js/main.js | 7 +++++-- service/webserver.py | 9 ++++++--- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 14e4a59..cc18f74 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -17,7 +17,7 @@ URL = 'https://mail.cisco.com/ews/exchange.asmx' SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() -TIME_ZONE = '' +SJ_TIME_ZONE = "420" reload(sys) sys.setdefaultencoding("utf-8") @@ -26,12 +26,13 @@ class AvailRoomFinder(object): def __init__(self, user, password, start_time=TIME_NOW, duration='1h', - roominfo='rooms.csv'): + roominfo='rooms.csv', timezone=SJ_TIME_ZONE): self.rooms = self._read_room_list(roominfo) self.user = user self.password = password self.start_time = start_time self.room_info = {} + self.timezone = timezone try: if 'h' in duration and duration.endswith('m'): @@ -114,9 +115,10 @@ def search(self, selected_rooms=None, print_to_stdout=False): xml = Template(xml_template) for email in selected_rooms: - data = unicode(xml.substitute(email=email, - starttime=self.start_time + TIME_ZONE, - endtime=self.end_time + TIME_ZONE)) + data = unicode(xml.substitute(timezone=self.timezone, + email=email, + starttime=self.start_time, + endtime=self.end_time)) header = "\"content-type: text/xml;charset=utf-8\"" command = "curl --silent --header " + header \ diff --git a/getavailibility_template.xml b/getavailibility_template.xml index 265b7a1..f057160 100644 --- a/getavailibility_template.xml +++ b/getavailibility_template.xml @@ -4,9 +4,9 @@ - 420 + $timezone - -60 + 0 5 10 diff --git a/service/static/js/main.js b/service/static/js/main.js index 45be17e..d8b487c 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -69,7 +69,9 @@ function submitClickHandler() { mytable.visiblity = false; var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}&attendees=${roomSizeSelect.value}`; + var timezone = new Date().getTimezoneOffset(); + + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}&attendees=${roomSizeSelect.value}&timezone=${timezone}`; loadRooms(queryString); } @@ -99,8 +101,9 @@ function showFreeRooms(rooms_json) { function bookRoom(roomname, roomemail) { var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); + var timezone = new Date().getTimezoneOffset(); - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${roomname}&roomemail=${roomemail}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}`; + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${roomname}&roomemail=${roomemail}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}&timezone=${timezone}`; var xmlHttp = new XMLHttpRequest(); url = "http://localhost:5000/bookroom"; diff --git a/service/webserver.py b/service/webserver.py index f74c8f5..3cb5736 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -31,7 +31,7 @@ def index(): return flask.render_template('index.html') -QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees' ) +QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees, timezone' ) BookRoomQueryParam = namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password') @app.route('/showbuldings', methods=['GET']) @@ -57,6 +57,7 @@ def show_rooms(): user = request.args.get('user'), password = request.args.get('password'), attendees = request.args.get('attendees'), + timezone = request.args.get('timezone'), ) prefix = queryparam.buildingname + '-' + queryparam.floor @@ -64,8 +65,10 @@ def show_rooms(): try: room_finder = AvailRoomFinder(queryparam.user, queryparam.password, - queryparam.starttime, queryparam.duration, - CONFIG['roomssearchcsv']) + queryparam.starttime, + duration=queryparam.duration, + roominfo=CONFIG['roomssearchcsv'], + timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees), print_to_stdout=True) except Exception as e: From 18f398a8372b6651f75e0c5a1ce1f9592e1f3033 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 26 Aug 2016 11:33:28 -0700 Subject: [PATCH 047/146] sgersapp: Make all the code timezone aware Change-Id: I5c8ea38a9b1f8c0e0b6948558cb9780c1389b9d3 --- book_room.py | 5 +- find_available_room.py | 2 +- service/templates/index.html | 132 ++++++++++++++++++++++++++--------- service/webserver.py | 16 ++--- 4 files changed, 109 insertions(+), 46 deletions(-) diff --git a/book_room.py b/book_room.py index 51742c0..4f199d5 100644 --- a/book_room.py +++ b/book_room.py @@ -16,6 +16,7 @@ SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() TIME_1H_FROM_NOW = None +SJ_TIME_ZONE = "420" reload(sys) sys.setdefaultencoding("utf-8") @@ -34,12 +35,13 @@ class ReserveAvailRoom(object): def __init__(self, roomname, roomemail, user, password, start_time=TIME_NOW, - duration='1h'): + duration='1h', timezone=SJ_TIME_ZONE): self.roomname = roomname self.roomemail = roomemail self.user = user self.password = base64.b64decode(urllib.unquote(password)) self.start_time = start_time + self.timezone = timezone or SJ_TIME_ZONE try: if 'h' in duration and duration.endswith('m'): @@ -76,6 +78,7 @@ def reserve_room(self, print_to_stdout=False): endtime=self.end_time, meeting_body=meeting_body, conf_room=self.roomname, + timezone=self.timezone, )) header = "\"content-type: text/xml;charset=utf-8\"" diff --git a/find_available_room.py b/find_available_room.py index cc18f74..3d09241 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -32,7 +32,7 @@ def __init__(self, user, password, self.password = password self.start_time = start_time self.room_info = {} - self.timezone = timezone + self.timezone = timezone or SJ_TIME_ZONE try: if 'h' in duration and duration.endswith('m'): diff --git a/service/templates/index.html b/service/templates/index.html index 2202f98..478059d 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -2,51 +2,115 @@ + - -
-
- -
-
- User Name: - Password: -
- -

-
- - - - - - + -
- - - - - - - - +
+
+
+ + +
+

+ + + + + +
+ +
-

- - -
-
-
-
diff --git a/service/webserver.py b/service/webserver.py index 3cb5736..25bb974 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -1,17 +1,11 @@ -import codecs from collections import namedtuple -import csv -import datetime from find_available_room import AvailRoomFinder from book_room import ReserveAvailRoom -from flask import Flask import flask import json import os + from shutil import copyfile -from string import Template -import subprocess -import xml.etree.ElementTree as ET from flask import Flask, request PWD = os.getcwd() @@ -32,7 +26,7 @@ def index(): return flask.render_template('index.html') QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees, timezone' ) -BookRoomQueryParam = namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password') +BookRoomQueryParam = namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password, timezone') @app.route('/showbuldings', methods=['GET']) def show_buldings(): @@ -72,7 +66,7 @@ def show_rooms(): rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees), print_to_stdout=True) except Exception as e: - rooms_info = {"Error:" + str(e) : ""} + rooms_info = {"Error: " + str(e) : ""} return json.dumps(rooms_info) @app.route('/bookroom', methods=['GET']) @@ -84,13 +78,15 @@ def book_room(): duration=request.args.get('duration'), user = request.args.get('user'), password = request.args.get('password'), + timezone = request.args.get('timezone'), ) room_finder = ReserveAvailRoom(queryparam.roomname, queryparam.roomemail, queryparam.user, queryparam.password, queryparam.starttime, - queryparam.duration) + duration=queryparam.duration, + timezone=queryparam.timezone) rooms_info = room_finder.reserve_room(print_to_stdout=True) if 'Success' in rooms_info: From 7a4783f16644d608a33e39a14f8cc6996ddd2a03 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 29 Aug 2016 11:29:25 -0700 Subject: [PATCH 048/146] sgersapp: Use user's timezone Change-Id: If4ce2de84cc896f5c68d98308023fa0c309dc3ae --- book_room.py | 13 +++++++++++-- reserve_resource_template.xml | 22 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/book_room.py b/book_room.py index 4f199d5..8103976 100644 --- a/book_room.py +++ b/book_room.py @@ -41,7 +41,7 @@ def __init__(self, roomname, roomemail, user, password, self.user = user self.password = base64.b64decode(urllib.unquote(password)) self.start_time = start_time - self.timezone = timezone or SJ_TIME_ZONE + self.timezone = self._calc_timezone_str(timezone) try: if 'h' in duration and duration.endswith('m'): @@ -57,12 +57,21 @@ def __init__(self, roomname, roomemail, user, password, else: hours, mins = 0, duration except ValueError: - print "**************" , duration hours, mins = 1, 0 start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") self.end_time = (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() + def _calc_timezone_str(self, timezone): + try: + timezone = int(timezone) + except ValueError: + timezone = SJ_TIME_ZONE + hours_offset = timezone / 60 + minutes_offset = timezone % 60 + sign = "-" if hours_offset < 0 else "" + return "{}PT{}H{}M".format(sign, abs(hours_offset), abs(minutes_offset)) + def reserve_room(self, print_to_stdout=False): xml_template = open("reserve_resource_template.xml", "r").read() xml = Template(xml_template) diff --git a/reserve_resource_template.xml b/reserve_resource_template.xml index 3498c3a..0efcb87 100644 --- a/reserve_resource_template.xml +++ b/reserve_resource_template.xml @@ -31,7 +31,27 @@ - + + $timezone + + P0D + + Sunday + Second + September + + 01:59:59 + + + P0D + + Friday + First + April + + 03:00:00 + + From d183bf3c83771f27618d8a220eb19cfa15c069bc Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 29 Aug 2016 11:48:02 -0700 Subject: [PATCH 049/146] sgersapp: Better logs, error handling Change-Id: I7ddfebf46c66209fd0a1f14f905e66f6ca1c99f4 --- find_available_room.py | 52 +++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 3d09241..7e3f846 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -77,30 +77,38 @@ def search_free(self, prefix, min_size=1, print_to_stdout=False): free_room_info[email] = selected_room_info[email] return free_room_info + def room_name(self, email): + return self.rooms[email][0] + def _query(self, command, email, print_to_stdout=False): + if print_to_stdout: + print "Querying for {}".format(self.room_name(email)) + response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] if not response: + print "No response for room {}".format(self.room_name(email)) return - tree = ET.fromstring(response) - - status = "Free" - elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy") - freebusy = '' - for elem in elems: - freebusy = elem.text - if '2' in freebusy: - status = "Busy" - elif '3' in freebusy: - status = "Unavailable" - elif '1' in freebusy: - status = "Tentative" + try: + tree = ET.fromstring(response) + + status = "Free" + elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy") + freebusy = '' + for elem in elems: + freebusy = elem.text + if '2' in freebusy: + status = "Busy" + elif '3' in freebusy: + status = "Unavailable" + elif '1' in freebusy: + status = "Tentative" - name, size = self.rooms[email] - self.room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status, 'email' : email} + name, size = self.rooms[email] + self.room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status, 'email' : email} - if print_to_stdout: - print "{0:20s} {1:64s} {2:64s}".format(status + '-' + freebusy, self.rooms[email], email) + except Exception as e: + print "Exception querying room {}: {}".format(self.room_name(email), str(e)) def search(self, selected_rooms=None, print_to_stdout=False): if selected_rooms is None: @@ -108,8 +116,7 @@ def search(self, selected_rooms=None, print_to_stdout=False): worker_threads = [] if print_to_stdout: - print "Searching for a room from " + self.start_time + " to " + self.end_time + ":" - print "{0:20s} {1:64s} {2:64s}".format("Status", "Room", "Email") + print self.user + " searching for a room from " + self.start_time + " to " + self.end_time + ":" xml_template = open("getavailibility_template.xml", "r").read() xml = Template(xml_template) @@ -134,6 +141,13 @@ def search(self, selected_rooms=None, print_to_stdout=False): for thread in worker_threads: thread.join() + if print_to_stdout: + print "-" * 120 + print "{0:40s} {1:64s} {2:20s}".format("Status", "Room", "Email") + print "-" * 120 + for name, info in self.room_info.iteritems(): + print "{0:40s} {1:64s} {2:20s}".format(info['status'] + '-' + info['freebusy'], name, info['email']) + print "-" * 120 return self.room_info def run(): From 58ac489773e5c679cd97bd85a9880b2bd91a7418 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 29 Aug 2016 14:06:45 -0700 Subject: [PATCH 050/146] sgersapp: better error and success messages Change-Id: I4487479e58bdc0d4cb6a7b39ac34dc358f080af8 --- service/static/js/main.js | 10 +++++++--- service/webserver.py | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index d8b487c..f3a244b 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -53,7 +53,6 @@ function loadBuildingNamesList() { xmlHttp.open( "GET", "http://localhost:5000/showbuldings", false ); // false for synchronous request xmlHttp.send( null ); buildings = JSON.parse(xmlHttp.responseText); - //console.log(buildings); } //Example: http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** @@ -91,10 +90,15 @@ function loadRooms(queryString) { function showFreeRooms(rooms_json) { + mytable.innerHTML += "Found " + Object.keys(rooms_json).length + " rooms"; for (var key in rooms_json) { var roomemail = rooms_json[key]["email"]; -// mytable.innerHTML += '' + key + ''; - mytable.innerHTML += '' + key + ''; + if (typeof roomemail != "undefined") { + mytable.innerHTML += '' + key + ''; + } + else { + mytable.innerHTML += '' + key + ''; + } } mytable.visiblity = true; } diff --git a/service/webserver.py b/service/webserver.py index 25bb974..4112cda 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -90,9 +90,9 @@ def book_room(): rooms_info = room_finder.reserve_room(print_to_stdout=True) if 'Success' in rooms_info: - return "Room Reserved Successfully" + return "reservation requested" - return "Reserve Room failed" + return "reservation failed" def _create_tmp_rooms_file(building_floor_name): if 'all' in building_floor_name: @@ -101,4 +101,4 @@ def _create_tmp_rooms_file(building_floor_name): open(CONFIG['roomssearchcsv'],'w').writelines([ line for line in open(CONFIG['roomscsv']) if building_floor_name in line]) if __name__ == '__main__': - app.run(debug=True) + app.run(debug=True, threaded=True) From 77acb8654de55e460c9285ceb3f8f7defd7e75da Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 29 Aug 2016 14:24:24 -0700 Subject: [PATCH 051/146] sgersapp: Support for HTTPS Change-Id: I38381a1fd9b7f99179cd9ac1a56c6cb587a2a70a --- service/static/js/main.js | 6 +++--- service/webserver.py | 30 ++++++++++++++++++------------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index f3a244b..085bec3 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -50,7 +50,7 @@ function createCombo(container, data) { function loadBuildingNamesList() { var xmlHttp = new XMLHttpRequest(); - xmlHttp.open( "GET", "http://localhost:5000/showbuldings", false ); // false for synchronous request + xmlHttp.open( "GET", "/showbuldings", false ); // false for synchronous request xmlHttp.send( null ); buildings = JSON.parse(xmlHttp.responseText); } @@ -77,7 +77,7 @@ function submitClickHandler() { function loadRooms(queryString) { var xmlHttp = new XMLHttpRequest(); - url = "http://localhost:5000/showrooms"; + url = "/showrooms"; url = url.concat(queryString); xmlHttp.open("GET", url, false); // false for synchronous request @@ -110,7 +110,7 @@ function bookRoom(roomname, roomemail) { var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${roomname}&roomemail=${roomemail}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}&timezone=${timezone}`; var xmlHttp = new XMLHttpRequest(); - url = "http://localhost:5000/bookroom"; + url = "/bookroom"; url = url.concat(queryString); xmlHttp.open("GET", url, false); // false for synchronous request diff --git a/service/webserver.py b/service/webserver.py index 4112cda..038a54a 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -1,22 +1,24 @@ from collections import namedtuple -from find_available_room import AvailRoomFinder +from find_available_room import AvailRoomFinder from book_room import ReserveAvailRoom import flask import json import os +import socket from shutil import copyfile from flask import Flask, request PWD = os.getcwd() -CONFIG = { +CONFIG = { 'home' : PWD, - 'roomscsv' : PWD + '/rooms.csv', - 'roomssearchcsv' : PWD + '/roomssearch.csv', - 'availibility_template' : PWD + '/getavailibility_template.xml', + 'roomscsv' : os.path.join(PWD, 'rooms.csv'), + 'roomssearchcsv' : os.path.join(PWD, 'roomssearch.csv'), + 'availibility_template' : os.path.join(PWD, 'getavailibility_template.xml'), 'URL': "https://mail.cisco.com/ews/exchange.asmx", - 'allrooms' : PWD + '/allrooms.csv', + 'allrooms' : os.path.join(PWD, 'allrooms.csv'), + 'certdir' : os.path.join(PWD, 'certdir') } app = Flask(__name__, template_folder=CONFIG['home'] + '/service/templates') @@ -39,7 +41,7 @@ def show_buldings(): return json.dumps(buldings) -# Example Query +# Example Query # http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password @app.route('/showrooms', methods=['GET']) def show_rooms(): @@ -56,8 +58,8 @@ def show_rooms(): prefix = queryparam.buildingname + '-' + queryparam.floor _create_tmp_rooms_file(prefix) - - try: + + try: room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, duration=queryparam.duration, @@ -84,11 +86,11 @@ def book_room(): queryparam.roomemail, queryparam.user, queryparam.password, - queryparam.starttime, + queryparam.starttime, duration=queryparam.duration, timezone=queryparam.timezone) rooms_info = room_finder.reserve_room(print_to_stdout=True) - + if 'Success' in rooms_info: return "reservation requested" @@ -100,5 +102,9 @@ def _create_tmp_rooms_file(building_floor_name): else: open(CONFIG['roomssearchcsv'],'w').writelines([ line for line in open(CONFIG['roomscsv']) if building_floor_name in line]) +def create_context(): + context = (os.path.join(CONFIG['certdir'], 'roomfinder.cert'), os.path.join(CONFIG['certdir'], 'roomfinder.key')) + return context + if __name__ == '__main__': - app.run(debug=True, threaded=True) + app.run(debug=True, threaded=True, host=socket.gethostname(), ssl_context=create_context(), port=5001) From b314baef3dfa74ee27cc04c0bd7e5e2de694267c Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 29 Aug 2016 14:44:48 -0700 Subject: [PATCH 052/146] sgersapp: Make https optional Change-Id: I0aa5e522b0ad355b2454adf0c7c94826ab7849e8 --- service/webserver.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/service/webserver.py b/service/webserver.py index 038a54a..5eeac40 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -18,7 +18,9 @@ 'availibility_template' : os.path.join(PWD, 'getavailibility_template.xml'), 'URL': "https://mail.cisco.com/ews/exchange.asmx", 'allrooms' : os.path.join(PWD, 'allrooms.csv'), - 'certdir' : os.path.join(PWD, 'certdir') + 'certdir' : os.path.join(PWD, 'certdir'), + 'https' : True, + 'port' : 5000, } app = Flask(__name__, template_folder=CONFIG['home'] + '/service/templates') @@ -107,4 +109,7 @@ def create_context(): return context if __name__ == '__main__': - app.run(debug=True, threaded=True, host=socket.gethostname(), ssl_context=create_context(), port=5001) + if CONFIG['https']: + app.run(debug=True, threaded=True, host=socket.gethostname(), ssl_context=create_context(), port=CONFIG['port']) + else: + app.run(debug=True, threaded=True, host=socket.gethostname(), port=CONFIG['port']) From 3a0f9219e947834f427b2b705cce30a918c1a7c4 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 30 Aug 2016 15:23:11 -0700 Subject: [PATCH 053/146] sgersapp: use only one CSV for rooms Change-Id: I05a90f0d804763ac8176a19b21bfb602d4d29b36 --- book_room.py | 2 +- service/webserver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/book_room.py b/book_room.py index 8103976..70c7169 100644 --- a/book_room.py +++ b/book_room.py @@ -27,7 +27,7 @@ 'roomssearchcsv' : PWD + '/roomssearch.csv', 'availibility_template' : PWD + '/getavailibility_template.xml', 'URL': "https://mail.cisco.com/ews/exchange.asmx", - 'allrooms' : PWD + '/allrooms.csv', + 'rooms' : PWD + '/allrooms.csv', } diff --git a/service/webserver.py b/service/webserver.py index 5eeac40..2d7a06c 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -17,7 +17,7 @@ 'roomssearchcsv' : os.path.join(PWD, 'roomssearch.csv'), 'availibility_template' : os.path.join(PWD, 'getavailibility_template.xml'), 'URL': "https://mail.cisco.com/ews/exchange.asmx", - 'allrooms' : os.path.join(PWD, 'allrooms.csv'), + 'allrooms' : os.path.join(PWD, 'rooms.csv'), 'certdir' : os.path.join(PWD, 'certdir'), 'https' : True, 'port' : 5000, From 8eb277de28c9e75e6a10f96b290a64ddf694d707 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 30 Aug 2016 16:45:33 -0700 Subject: [PATCH 054/146] sgersapp: Move common defaults to common.py Change-Id: I20a61162c57b2c8e84cf2c3d67dc2eb0b1ef0b4e --- book_room.py | 31 +++++++------------------------ common.py | 22 ++++++++++++++++++++++ find_available_room.py | 18 +++++++----------- find_rooms.py | 16 +++++++--------- service/webserver.py | 39 +++++++++++++-------------------------- 5 files changed, 56 insertions(+), 70 deletions(-) create mode 100644 common.py diff --git a/book_room.py b/book_room.py index 70c7169..aec05a4 100644 --- a/book_room.py +++ b/book_room.py @@ -2,40 +2,23 @@ # -*- coding: utf-8 -*- import argparse import base64 +import common import datetime import getpass -import os import subprocess import sys import urllib from string import Template -PWD = os.getcwd() -URL = 'https://mail.cisco.com/ews/exchange.asmx' -SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' -TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() -TIME_1H_FROM_NOW = None -SJ_TIME_ZONE = "420" - reload(sys) sys.setdefaultencoding("utf-8") -CONFIG = { - 'home' : PWD, - 'roomscsv' : PWD + '/rooms.csv', - 'roomssearchcsv' : PWD + '/roomssearch.csv', - 'availibility_template' : PWD + '/getavailibility_template.xml', - 'URL': "https://mail.cisco.com/ews/exchange.asmx", - 'rooms' : PWD + '/allrooms.csv', - } - - class ReserveAvailRoom(object): def __init__(self, roomname, roomemail, user, password, - start_time=TIME_NOW, - duration='1h', timezone=SJ_TIME_ZONE): + start_time=common.TIME_NOW, + duration='1h', timezone=common.SJ_TIME_ZONE): self.roomname = roomname self.roomemail = roomemail self.user = user @@ -66,7 +49,7 @@ def _calc_timezone_str(self, timezone): try: timezone = int(timezone) except ValueError: - timezone = SJ_TIME_ZONE + timezone = common.SJ_TIME_ZONE hours_offset = timezone / 60 minutes_offset = timezone % 60 sign = "-" if hours_offset < 0 else "" @@ -95,7 +78,7 @@ def reserve_room(self, print_to_stdout=False): + " --data '" + data \ + "' --ntlm " \ + "-u "+ self.user + ":" + self.password \ - + " " + URL + + " " + common.URL response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] return response @@ -104,10 +87,10 @@ def run(): parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", - default=TIME_NOW) + default=common.TIME_NOW) parser.add_argument("-end", "--endtime", help="Endtime e.g. 2014-07-02T12:00:00 (default = now+1h)", - default=TIME_1H_FROM_NOW) + default=common.TIME_1H_FROM_NOW) parser.add_argument("-f", "--file", help="csv filename with room info (default=rooms.csv).", default="rooms.csv") diff --git a/common.py b/common.py new file mode 100644 index 0000000..4a9795b --- /dev/null +++ b/common.py @@ -0,0 +1,22 @@ +import datetime +import os + +URL = 'https://mail.cisco.com/ews/exchange.asmx' +SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' +HTTPS_ENABLED = True +HTTP_PORT = 8080 +HTTPS_PORT = 8443 + +PWD = os.getcwd() +ROOMS_CSV = os.path.join(PWD, 'rooms.csv') +ROOMS_SEARCH_CSV = os.path.join(PWD, 'roomssearch.csv') +AVAILIBILITY_TEMPLATE = os.path.join(PWD, 'getavailibility_template.xml') +SERVICE_DIR = os.path.join(PWD, 'service') +CERT_DIR = os.path.join(PWD, 'certdir') +TEMPLATE_FOLDER = os.path.join(SERVICE_DIR, 'templates') + +TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() +TIME_1H_FROM_NOW = None +SJ_TIME_ZONE = "420" + + diff --git a/find_available_room.py b/find_available_room.py index 7e3f846..de72793 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -3,6 +3,7 @@ import argparse import base64 +import common import csv import datetime import getpass @@ -14,25 +15,20 @@ from string import Template -URL = 'https://mail.cisco.com/ews/exchange.asmx' -SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' -TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() -SJ_TIME_ZONE = "420" - reload(sys) sys.setdefaultencoding("utf-8") class AvailRoomFinder(object): def __init__(self, user, password, - start_time=TIME_NOW, duration='1h', - roominfo='rooms.csv', timezone=SJ_TIME_ZONE): + start_time=common.TIME_NOW, duration='1h', + roominfo='rooms.csv', timezone=common.SJ_TIME_ZONE): self.rooms = self._read_room_list(roominfo) self.user = user self.password = password self.start_time = start_time self.room_info = {} - self.timezone = timezone or SJ_TIME_ZONE + self.timezone = timezone or common.SJ_TIME_ZONE try: if 'h' in duration and duration.endswith('m'): @@ -93,7 +89,7 @@ def _query(self, command, email, print_to_stdout=False): tree = ET.fromstring(response) status = "Free" - elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy") + elems = tree.findall(common.SCHEME_TYPES + "MergedFreeBusy") freebusy = '' for elem in elems: freebusy = elem.text @@ -132,7 +128,7 @@ def search(self, selected_rooms=None, print_to_stdout=False): + " --data '" + data \ + "' --ntlm " \ + "-u "+ self.user + ":" + base64.b64decode(urllib.unquote(self.password)) \ - + " " + URL + + " " + common.URL thread = threading.Thread(target=self._query, args=(command, email, print_to_stdout)) thread.start() @@ -158,7 +154,7 @@ def run(): default='') parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", - default=TIME_NOW) + default=common.TIME_NOW) parser.add_argument("-duration", "--duration", help="Duration e.g. 1h or 15m (default = 1h)", default='1h') diff --git a/find_rooms.py b/find_rooms.py index 3f5f520..797f9a8 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -1,21 +1,19 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import getpass -import xml.etree.ElementTree as ET import argparse +import common import csv +import getpass import operator import subprocess import sys +import xml.etree.ElementTree as ET from string import Template from string import letters from string import digits -URL = 'https://mail.cisco.com/ews/exchange.asmx' -SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' - reload(sys) sys.setdefaultencoding("utf-8") @@ -37,7 +35,7 @@ def _search(self, prefix): + " --data '" + data \ + "' --ntlm " \ + "-u "+ self.user + ":" + self.password \ - + " " + URL + + " " + common.URL response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] if not response.strip(): @@ -46,10 +44,10 @@ def _search(self, prefix): tree = ET.fromstring(response) - elems = tree.findall(SCHEME_TYPES + "Resolution") + elems = tree.findall(common.SCHEME_TYPES + "Resolution") for elem in elems: - email = elem.findall(SCHEME_TYPES + "EmailAddress") - name = elem.findall(SCHEME_TYPES + "DisplayName") + email = elem.findall(common.SCHEME_TYPES + "EmailAddress") + name = elem.findall(common.SCHEME_TYPES + "DisplayName") if len(email) and len(name): roomsize = self.room_size(name[0].text) if roomsize: diff --git a/service/webserver.py b/service/webserver.py index 2d7a06c..f20ba6b 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -1,29 +1,16 @@ -from collections import namedtuple -from find_available_room import AvailRoomFinder -from book_room import ReserveAvailRoom +import common import flask import json import os import socket +from book_room import ReserveAvailRoom +from collections import namedtuple +from find_available_room import AvailRoomFinder from shutil import copyfile from flask import Flask, request -PWD = os.getcwd() - -CONFIG = { - 'home' : PWD, - 'roomscsv' : os.path.join(PWD, 'rooms.csv'), - 'roomssearchcsv' : os.path.join(PWD, 'roomssearch.csv'), - 'availibility_template' : os.path.join(PWD, 'getavailibility_template.xml'), - 'URL': "https://mail.cisco.com/ews/exchange.asmx", - 'allrooms' : os.path.join(PWD, 'rooms.csv'), - 'certdir' : os.path.join(PWD, 'certdir'), - 'https' : True, - 'port' : 5000, - } - -app = Flask(__name__, template_folder=CONFIG['home'] + '/service/templates') +app = Flask(__name__, template_folder=common.TEMPLATE_FOLDER) @app.route('/') def index(): @@ -35,7 +22,7 @@ def index(): @app.route('/showbuldings', methods=['GET']) def show_buldings(): buldings = [] - with open(CONFIG['allrooms'],'r') as f: + with open(common.ROOMS_CSV, 'r') as f: for line in f.readlines(): buldingname = line.split('-')[0] if buldingname not in buldings: @@ -65,7 +52,7 @@ def show_rooms(): room_finder = AvailRoomFinder(queryparam.user, queryparam.password, queryparam.starttime, duration=queryparam.duration, - roominfo=CONFIG['roomssearchcsv'], + roominfo=common.ROOMS_SEARCH_CSV, timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees), print_to_stdout=True) @@ -100,16 +87,16 @@ def book_room(): def _create_tmp_rooms_file(building_floor_name): if 'all' in building_floor_name: - copyfile(CONFIG['roomscsv'], CONFIG['roomssearchcsv']) + copyfile(common.ROOMS_CSV, common.ROOMS_SEARCH_CSV) else: - open(CONFIG['roomssearchcsv'],'w').writelines([ line for line in open(CONFIG['roomscsv']) if building_floor_name in line]) + open(common.ROOMS_SEARCH_CSV, 'w').writelines([ line for line in open(common.ROOMS_CSV) if building_floor_name in line]) def create_context(): - context = (os.path.join(CONFIG['certdir'], 'roomfinder.cert'), os.path.join(CONFIG['certdir'], 'roomfinder.key')) + context = (os.path.join(common.CERT_DIR, 'roomfinder.cert'), os.path.join(common.CERT_DIR, 'roomfinder.key')) return context if __name__ == '__main__': - if CONFIG['https']: - app.run(debug=True, threaded=True, host=socket.gethostname(), ssl_context=create_context(), port=CONFIG['port']) + if common.HTTPS_ENABLED: + app.run(debug=True, threaded=True, host=socket.gethostname(), ssl_context=create_context(), port=common.HTTPS_PORT) else: - app.run(debug=True, threaded=True, host=socket.gethostname(), port=CONFIG['port']) + app.run(debug=True, threaded=True, host=socket.gethostname(), port=common.HTTP_PORT) From 09dfcad359de6fb25cc89785502bf819dd433378 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 30 Aug 2016 17:01:35 -0700 Subject: [PATCH 055/146] sgersapp: logging support Change-Id: Ieca668bd53b0ba73a9ccf40fb0957010146db4bc --- book_room.py | 4 ++-- common.py | 4 ++++ find_available_room.py | 39 ++++++++++++++++++++------------------- find_rooms.py | 4 ++-- service/webserver.py | 5 ++--- 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/book_room.py b/book_room.py index aec05a4..f89769a 100644 --- a/book_room.py +++ b/book_room.py @@ -55,7 +55,7 @@ def _calc_timezone_str(self, timezone): sign = "-" if hours_offset < 0 else "" return "{}PT{}H{}M".format(sign, abs(hours_offset), abs(minutes_offset)) - def reserve_room(self, print_to_stdout=False): + def reserve_room(self): xml_template = open("reserve_resource_template.xml", "r").read() xml = Template(xml_template) @@ -103,7 +103,7 @@ def run(): args.password = getpass.getpass("Password:") room_finder = ReserveAvailRoom(args.room, args.user, args.password, args.starttime, args.endtime, args.file) - print room_finder.reserve_room(print_to_stdout=True) + room_finder.reserve_room() if __name__ == '__main__': diff --git a/common.py b/common.py index 4a9795b..23dcef8 100644 --- a/common.py +++ b/common.py @@ -1,4 +1,5 @@ import datetime +import logging import os URL = 'https://mail.cisco.com/ews/exchange.asmx' @@ -19,4 +20,7 @@ TIME_1H_FROM_NOW = None SJ_TIME_ZONE = "420" +logging.basicConfig(filename='access.log',level=logging.DEBUG) +LOGGER = logging.getLogger('roomfinder') + diff --git a/find_available_room.py b/find_available_room.py index de72793..959a6a1 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -59,14 +59,14 @@ def _read_room_list(self, filename): return rooms - def search_free(self, prefix, min_size=1, print_to_stdout=False): + def search_free(self, prefix, min_size=1): selected_rooms = {} for email in self.rooms: name, size = self.rooms[email] if name.startswith(prefix) and size >= min_size: selected_rooms[email] = (name, size) - selected_room_info = self.search(selected_rooms, print_to_stdout) + selected_room_info = self.search(selected_rooms) free_room_info = {} for email in selected_room_info: if selected_room_info[email]['status'] == 'Free': @@ -76,13 +76,12 @@ def search_free(self, prefix, min_size=1, print_to_stdout=False): def room_name(self, email): return self.rooms[email][0] - def _query(self, command, email, print_to_stdout=False): - if print_to_stdout: - print "Querying for {}".format(self.room_name(email)) + def _query(self, command, email): + common.LOGGER.debug("Querying for %s", self.room_name(email)) response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] if not response: - print "No response for room {}".format(self.room_name(email)) + common.LOGGER.info("No response for room %s", self.room_name(email)) return try: @@ -104,15 +103,14 @@ def _query(self, command, email, print_to_stdout=False): self.room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status, 'email' : email} except Exception as e: - print "Exception querying room {}: {}".format(self.room_name(email), str(e)) + common.LOGGER.warning("Exception querying room %s: %s", self.room_name(email), str(e)) - def search(self, selected_rooms=None, print_to_stdout=False): + def search(self, selected_rooms=None): if selected_rooms is None: selected_rooms = self.rooms worker_threads = [] - if print_to_stdout: - print self.user + " searching for a room from " + self.start_time + " to " + self.end_time + ":" + common.LOGGER.info("User %s searching for a room from %s to %s", self.user, self.start_time, self.end_time) xml_template = open("getavailibility_template.xml", "r").read() xml = Template(xml_template) @@ -130,20 +128,23 @@ def search(self, selected_rooms=None, print_to_stdout=False): + "-u "+ self.user + ":" + base64.b64decode(urllib.unquote(self.password)) \ + " " + common.URL - thread = threading.Thread(target=self._query, args=(command, email, print_to_stdout)) + thread = threading.Thread(target=self._query, args=(command, email)) thread.start() worker_threads.append(thread) for thread in worker_threads: thread.join() - if print_to_stdout: - print "-" * 120 - print "{0:40s} {1:64s} {2:20s}".format("Status", "Room", "Email") - print "-" * 120 - for name, info in self.room_info.iteritems(): - print "{0:40s} {1:64s} {2:20s}".format(info['status'] + '-' + info['freebusy'], name, info['email']) - print "-" * 120 + LINE_SEPARATOR = "-" * 120 + "\n" + OUTPUT_TABLE = LINE_SEPARATOR + \ + "{0:40s} {1:64s} {2:20s}\n".format("Status", "Room", "Email") + \ + LINE_SEPARATOR + for name, info in self.room_info.iteritems(): + OUTPUT_TABLE += "{0:40s} {1:64s} {2:20s}\n".format(info['status'] + '-' + info['freebusy'], name, info['email']) + OUTPUT_TABLE += LINE_SEPARATOR + + common.LOGGER.debug("%s", OUTPUT_TABLE) + return self.room_info def run(): @@ -166,7 +167,7 @@ def run(): args.password = base64.b64encode(getpass.getpass("Password:")) room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.duration, args.file) - print room_finder.search_free(prefix=args.prefix, print_to_stdout=True) + room_finder.search_free(prefix=args.prefix) if __name__ == '__main__': diff --git a/find_rooms.py b/find_rooms.py index 797f9a8..202002d 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -63,7 +63,7 @@ def search(self, prefix, deep=False): prefix_deep = prefix + " " + symbol rooms_found.update(self._search(prefix_deep)) - print "Search for prefix '" + prefix + "' yielded " + str(len(rooms_found)) + " rooms." + common.LOGGER.info("Search for prefix '%s' yielded %d rooms.", prefix, str(len(rooms_found))) self.rooms.update(self._search(prefix)) def room_size(self, roomname): @@ -74,7 +74,7 @@ def room_size(self, roomname): def dump(self, filename='rooms.csv'): if not len(self.rooms): - print "Check your arguments for mistakes" + common.LOGGER.warning("No results found, check your arguments for mistakes") return 0 with open(filename, "wb") as fhandle: diff --git a/service/webserver.py b/service/webserver.py index f20ba6b..c6ecd0b 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -54,8 +54,7 @@ def show_rooms(): duration=queryparam.duration, roominfo=common.ROOMS_SEARCH_CSV, timezone=queryparam.timezone) - rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees), - print_to_stdout=True) + rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) except Exception as e: rooms_info = {"Error: " + str(e) : ""} return json.dumps(rooms_info) @@ -78,7 +77,7 @@ def book_room(): queryparam.starttime, duration=queryparam.duration, timezone=queryparam.timezone) - rooms_info = room_finder.reserve_room(print_to_stdout=True) + rooms_info = room_finder.reserve_room() if 'Success' in rooms_info: return "reservation requested" From 17f331139448b4c0cd0f59e79b30e160cf41093d Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 30 Aug 2016 17:21:40 -0700 Subject: [PATCH 056/146] sgersapp: Improve logging, disable logging of sensitive information Change-Id: I6ac2088195bd60f7cf79633de68ea13d1d677ffb --- common.py | 3 +-- find_available_room.py | 2 +- service/webserver.py | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/common.py b/common.py index 23dcef8..a069821 100644 --- a/common.py +++ b/common.py @@ -22,5 +22,4 @@ logging.basicConfig(filename='access.log',level=logging.DEBUG) LOGGER = logging.getLogger('roomfinder') - - +logging.getLogger('werkzeug').setLevel(logging.ERROR) diff --git a/find_available_room.py b/find_available_room.py index 959a6a1..7d23d26 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -143,7 +143,7 @@ def search(self, selected_rooms=None): OUTPUT_TABLE += "{0:40s} {1:64s} {2:20s}\n".format(info['status'] + '-' + info['freebusy'], name, info['email']) OUTPUT_TABLE += LINE_SEPARATOR - common.LOGGER.debug("%s", OUTPUT_TABLE) + common.LOGGER.debug("\n%s", OUTPUT_TABLE) return self.room_info diff --git a/service/webserver.py b/service/webserver.py index c6ecd0b..0e6bab7 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -96,6 +96,6 @@ def create_context(): if __name__ == '__main__': if common.HTTPS_ENABLED: - app.run(debug=True, threaded=True, host=socket.gethostname(), ssl_context=create_context(), port=common.HTTPS_PORT) + app.run(threaded=True, host=socket.gethostname(), ssl_context=create_context(), port=common.HTTPS_PORT) else: - app.run(debug=True, threaded=True, host=socket.gethostname(), port=common.HTTP_PORT) + app.run(threaded=True, host=socket.gethostname(), port=common.HTTP_PORT) From 6661cbc8cdaee3b9f193abb6593d2120c4840bf8 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 31 Aug 2016 14:58:33 -0700 Subject: [PATCH 057/146] sgersapp: handle auth errors Change-Id: I3514d2ecbf234f9b049f46c8af6ccd163751e241 --- find_available_room.py | 7 ++++++- service/static/js/main.js | 7 +++++++ service/webserver.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 7d23d26..3024dc9 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -29,6 +29,7 @@ def __init__(self, user, password, self.start_time = start_time self.room_info = {} self.timezone = timezone or common.SJ_TIME_ZONE + self.error = None try: if 'h' in duration and duration.endswith('m'): @@ -81,7 +82,8 @@ def _query(self, command, email): response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] if not response: - common.LOGGER.info("No response for room %s", self.room_name(email)) + common.LOGGER.warning("No response for room %s", self.room_name(email)) + self.error = Exception("Authentication failure") return try: @@ -135,6 +137,9 @@ def search(self, selected_rooms=None): for thread in worker_threads: thread.join() + if self.error is not None: + raise self.error + LINE_SEPARATOR = "-" * 120 + "\n" OUTPUT_TABLE = LINE_SEPARATOR + \ "{0:40s} {1:64s} {2:20s}\n".format("Status", "Room", "Email") + \ diff --git a/service/static/js/main.js b/service/static/js/main.js index 085bec3..f8aea8c 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -90,6 +90,13 @@ function loadRooms(queryString) { function showFreeRooms(rooms_json) { + error = rooms_json["Error"]; + console.log(error); + if (typeof error != "undefined") { + mytable.innerHTML += "Error: " + error + ""; + return; + } + mytable.innerHTML += "Found " + Object.keys(rooms_json).length + " rooms"; for (var key in rooms_json) { var roomemail = rooms_json[key]["email"]; diff --git a/service/webserver.py b/service/webserver.py index 0e6bab7..2af7c03 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -56,7 +56,7 @@ def show_rooms(): timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) except Exception as e: - rooms_info = {"Error: " + str(e) : ""} + rooms_info = {"Error" : str(e)} return json.dumps(rooms_info) @app.route('/bookroom', methods=['GET']) From d3013b500603da7d5b79d67d668f9ddde1044ad9 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 1 Sep 2016 15:42:41 -0700 Subject: [PATCH 058/146] sgersapp: Use gunicorn to run app Change-Id: I32d1d357ac430ce746ba5d1324bc5c5ed6be0f11 --- CONFIG | 8 ++++++++ run.sh | 3 +++ service/__init__.py | 0 setup.py | 13 +++++++++++++ 4 files changed, 24 insertions(+) create mode 100644 CONFIG create mode 100755 run.sh create mode 100644 service/__init__.py create mode 100644 setup.py diff --git a/CONFIG b/CONFIG new file mode 100644 index 0000000..df07eca --- /dev/null +++ b/CONFIG @@ -0,0 +1,8 @@ + + + +workers = 10 +bind = "0.0.0.0:8443" +certfile = "certdir/roomfinder.cert" +keyfile = "certdir/roomfinder.key" + diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..b32237b --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +PYTHONPATH=. gunicorn --config=CONFIG service.webserver:app diff --git a/service/__init__.py b/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7919446 --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup, find_packages + +setup(name='roomfinder', + author='Saish Gersappa', + author_email='sgersapp@cisco.com', + version='0.9', + packages=find_packages(), + install_requires = [ + 'argparse', + 'flask', + 'gunicorn', + ] + ) From 34a4fd0b2cc56ba4c884cbc56936ffdb77687b61 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 11:38:05 -0700 Subject: [PATCH 059/146] sgersapp: Separte out exchange functionality into separate API Change-Id: Ie5bc0daa78b2056d4e5a10347dd1fba88b3c512d --- exchange_api.py | 60 ++++++++++++++++++++++++++++++++++++++++++ find_available_room.py | 56 +++++++++++---------------------------- 2 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 exchange_api.py diff --git a/exchange_api.py b/exchange_api.py new file mode 100644 index 0000000..1940363 --- /dev/null +++ b/exchange_api.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import common +import pipes +import string +import subprocess +import sys +import xml.etree.ElementTree as ET + +reload(sys) +sys.setdefaultencoding("utf-8") + +AVAILABILITY_XML = None + +class ExchangeApi(object): + + def __init__(self, user, password): + self.user = user + self.password = password + + def _read_template(self, filename): + with open(filename, "r") as fhandle: + return string.Template(fhandle.read()) + + def is_room_available(self, room_email, start_time, end_time, timezone_offset): + global AVAILABILITY_XML + if AVAILABILITY_XML is None: + AVAILABILITY_XML = self._read_template("getavailibility_template.xml") + + data = unicode(AVAILABILITY_XML.substitute(timezone=timezone_offset, + email=room_email, + starttime=start_time, + endtime=end_time)) + + command = 'curl --silent --header "content-type: text/xml;charset=utf-8"' \ + + " --data '" + data \ + + "' --ntlm " \ + + "-u " + pipes.quote(self.user) + ":" + pipes.quote(self.password) \ + + " " + common.URL + + response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + if not response: + raise Exception("Authentication failure") + + tree = ET.fromstring(response) + + status = "Free" + elems = tree.findall(common.SCHEME_TYPES + "MergedFreeBusy") + freebusy = '' + for elem in elems: + freebusy = elem.text + if '2' in freebusy: + status = "Busy" + elif '3' in freebusy: + status = "Unavailable" + elif '1' in freebusy: + status = "Tentative" + + return {'freebusy': freebusy, 'status': status, 'email' : room_email,} diff --git a/find_available_room.py b/find_available_room.py index 3024dc9..7836d38 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -6,6 +6,7 @@ import common import csv import datetime +import exchange_api import getpass import subprocess import sys @@ -25,11 +26,11 @@ def __init__(self, user, password, roominfo='rooms.csv', timezone=common.SJ_TIME_ZONE): self.rooms = self._read_room_list(roominfo) self.user = user - self.password = password self.start_time = start_time self.room_info = {} self.timezone = timezone or common.SJ_TIME_ZONE self.error = None + self.exchange_api = exchange_api.ExchangeApi(user, base64.b64decode(urllib.unquote(password))) try: if 'h' in duration and duration.endswith('m'): @@ -74,38 +75,23 @@ def search_free(self, prefix, min_size=1): free_room_info[email] = selected_room_info[email] return free_room_info - def room_name(self, email): - return self.rooms[email][0] - - def _query(self, command, email): - common.LOGGER.debug("Querying for %s", self.room_name(email)) - - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - if not response: - common.LOGGER.warning("No response for room %s", self.room_name(email)) - self.error = Exception("Authentication failure") - return + def _query(self, email): + room_name, room_size = self.rooms[email] + common.LOGGER.debug("Querying for %s", room_name) try: - tree = ET.fromstring(response) - - status = "Free" - elems = tree.findall(common.SCHEME_TYPES + "MergedFreeBusy") - freebusy = '' - for elem in elems: - freebusy = elem.text - if '2' in freebusy: - status = "Busy" - elif '3' in freebusy: - status = "Unavailable" - elif '1' in freebusy: - status = "Tentative" + room_info = self.exchange_api.is_room_available( \ + room_email=email, + start_time=self.start_time, + end_time=self.end_time, + timezone_offset=self.timezone) - name, size = self.rooms[email] - self.room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status, 'email' : email} + room_info['size'] = room_size + room_info['name'] = room_name + self.room_info[room_name] = room_info except Exception as e: - common.LOGGER.warning("Exception querying room %s: %s", self.room_name(email), str(e)) + common.LOGGER.warning("Exception querying room %s: %s", room_name, str(e)) def search(self, selected_rooms=None): if selected_rooms is None: @@ -118,19 +104,7 @@ def search(self, selected_rooms=None): xml = Template(xml_template) for email in selected_rooms: - data = unicode(xml.substitute(timezone=self.timezone, - email=email, - starttime=self.start_time, - endtime=self.end_time)) - - header = "\"content-type: text/xml;charset=utf-8\"" - command = "curl --silent --header " + header \ - + " --data '" + data \ - + "' --ntlm " \ - + "-u "+ self.user + ":" + base64.b64decode(urllib.unquote(self.password)) \ - + " " + common.URL - - thread = threading.Thread(target=self._query, args=(command, email)) + thread = threading.Thread(target=self._query, args=(email, )) thread.start() worker_threads.append(thread) From 2824dd07366bba52c8a3d64d0f5f066074e4257b Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 11:58:31 -0700 Subject: [PATCH 060/146] sgersapp: Slight change to API Change-Id: I38d06e452ee8726a0d107477197cfbc88915df9e --- find_available_room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/find_available_room.py b/find_available_room.py index 7836d38..19941b6 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -80,7 +80,7 @@ def _query(self, email): common.LOGGER.debug("Querying for %s", room_name) try: - room_info = self.exchange_api.is_room_available( \ + room_info = self.exchange_api.room_status( \ room_email=email, start_time=self.start_time, end_time=self.end_time, From 38f09c75b6e87419c6d659ac023f4d71838c989c Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 11:59:27 -0700 Subject: [PATCH 061/146] sgersapp: Move reservation support to exchange API Change-Id: I927d516ee649d45e057b07ee0099bda096301b45 --- book_room.py | 36 ++++++++---------------------------- exchange_api.py | 43 ++++++++++++++++++++++++++++++++++--------- service/webserver.py | 8 +++----- 3 files changed, 45 insertions(+), 42 deletions(-) diff --git a/book_room.py b/book_room.py index f89769a..1ce6ff6 100644 --- a/book_room.py +++ b/book_room.py @@ -4,13 +4,11 @@ import base64 import common import datetime +import exchange_api import getpass -import subprocess import sys import urllib -from string import Template - reload(sys) sys.setdefaultencoding("utf-8") @@ -22,9 +20,9 @@ def __init__(self, roomname, roomemail, user, password, self.roomname = roomname self.roomemail = roomemail self.user = user - self.password = base64.b64decode(urllib.unquote(password)) self.start_time = start_time self.timezone = self._calc_timezone_str(timezone) + self.exchange_api = exchange_api.ExchangeApi(user, base64.b64decode(urllib.unquote(password))) try: if 'h' in duration and duration.endswith('m'): @@ -56,30 +54,12 @@ def _calc_timezone_str(self, timezone): return "{}PT{}H{}M".format(sign, abs(hours_offset), abs(minutes_offset)) def reserve_room(self): - xml_template = open("reserve_resource_template.xml", "r").read() - xml = Template(xml_template) - - useremail = self.user + '@cisco.com' - meeting_body = '{0} booked via RoomFinder by {1}'.format(self.roomname, useremail) - subject = 'RoomFinder: {0}'.format(self.roomname) - - data = unicode(xml.substitute(resourceemail=self.roomemail, - useremail=useremail, - subject=subject, - starttime=self.start_time, - endtime=self.end_time, - meeting_body=meeting_body, - conf_room=self.roomname, - timezone=self.timezone, - )) - - header = "\"content-type: text/xml;charset=utf-8\"" - command = "curl --silent --header " + header \ - + " --data '" + data \ - + "' --ntlm " \ - + "-u "+ self.user + ":" + self.password \ - + " " + common.URL - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + response = self.exchange_api.reserve_room( \ + room_email=self.roomemail, + room_name=self.roomname, + start_time=self.start_time, + end_time=self.end_time, + timezone_offset=self.timezone) return response def run(): diff --git a/exchange_api.py b/exchange_api.py index 1940363..cda4584 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -12,18 +12,27 @@ sys.setdefaultencoding("utf-8") AVAILABILITY_XML = None +RESERVE_XML = None class ExchangeApi(object): def __init__(self, user, password): self.user = user - self.password = password + self.command = 'curl --silent --header "content-type: text/xml;charset=utf-8"' \ + + " --data '{}'" \ + + " --ntlm " \ + + "-u " + pipes.quote(self.user) + ":" + pipes.quote(password) \ + + " " + common.URL def _read_template(self, filename): with open(filename, "r") as fhandle: return string.Template(fhandle.read()) - def is_room_available(self, room_email, start_time, end_time, timezone_offset): + def _curl(self, post_data): + curl_command = self.command.format(post_data) + return subprocess.Popen(curl_command, stdout=subprocess.PIPE, shell=True).communicate()[0] + + def room_status(self, room_email, start_time, end_time, timezone_offset): global AVAILABILITY_XML if AVAILABILITY_XML is None: AVAILABILITY_XML = self._read_template("getavailibility_template.xml") @@ -33,13 +42,7 @@ def is_room_available(self, room_email, start_time, end_time, timezone_offset): starttime=start_time, endtime=end_time)) - command = 'curl --silent --header "content-type: text/xml;charset=utf-8"' \ - + " --data '" + data \ - + "' --ntlm " \ - + "-u " + pipes.quote(self.user) + ":" + pipes.quote(self.password) \ - + " " + common.URL - - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] + response = self._curl(data) if not response: raise Exception("Authentication failure") @@ -58,3 +61,25 @@ def is_room_available(self, room_email, start_time, end_time, timezone_offset): status = "Tentative" return {'freebusy': freebusy, 'status': status, 'email' : room_email,} + + def reserve_room(self, room_email, room_name, start_time, end_time, timezone_offset): + global RESERVE_XML + if RESERVE_XML is None: + RESERVE_XML = self._read_template("reserve_resource_template.xml") + + user_email = self.user + '@cisco.com' + meeting_body = '{0} booked via RoomFinder by {1}'.format(room_name, user_email) + subject = 'RoomFinder: {0}'.format(room_name) + + data = unicode(RESERVE_XML.substitute(resourceemail=room_email, + useremail=user_email, + subject=subject, + starttime=start_time, + endtime=end_time, + meeting_body=meeting_body, + conf_room=room_name, + timezone=timezone_offset, + )) + + response = self._curl(data) + return 'Success' in response diff --git a/service/webserver.py b/service/webserver.py index 2af7c03..98138c8 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -77,12 +77,10 @@ def book_room(): queryparam.starttime, duration=queryparam.duration, timezone=queryparam.timezone) - rooms_info = room_finder.reserve_room() - - if 'Success' in rooms_info: + if room_finder.reserve_room(): return "reservation requested" - - return "reservation failed" + else: + return "reservation failed" def _create_tmp_rooms_file(building_floor_name): if 'all' in building_floor_name: From 74430d7f43ad7779899537c3192e6485f49a7478 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 12:36:50 -0700 Subject: [PATCH 062/146] sgersapp: Fix bug in diplaying error on WebUI Change-Id: I10ba4c63cabab742683a52d64dc8d3f60a5f43a4 --- find_available_room.py | 1 + 1 file changed, 1 insertion(+) diff --git a/find_available_room.py b/find_available_room.py index 19941b6..61431a3 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -91,6 +91,7 @@ def _query(self, email): self.room_info[room_name] = room_info except Exception as e: + self.error = e common.LOGGER.warning("Exception querying room %s: %s", room_name, str(e)) def search(self, selected_rooms=None): From 970560848633776954ef55262a677998968210da Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 12:52:08 -0700 Subject: [PATCH 063/146] sgersapp: Move common code to common.py Change-Id: I8e72aa5ac8c610e0de2cc01847ecb01865ce03a7 --- book_room.py | 20 +------------------- common.py | 26 +++++++++++++++++++++++++- find_available_room.py | 20 +------------------- 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/book_room.py b/book_room.py index 1ce6ff6..cafbc14 100644 --- a/book_room.py +++ b/book_room.py @@ -23,25 +23,7 @@ def __init__(self, roomname, roomemail, user, password, self.start_time = start_time self.timezone = self._calc_timezone_str(timezone) self.exchange_api = exchange_api.ExchangeApi(user, base64.b64decode(urllib.unquote(password))) - - try: - if 'h' in duration and duration.endswith('m'): - hours, mins = map(int, duration[:-1].split('h')) - elif duration.endswith('h'): - hours, mins = int(duration[:-1]), 0 - elif duration.endswith('m'): - hours, mins = 0, int(duration[:-1]) - else: - duration = int(duration) - if duration < 15: - hours, mins = duration, 0 - else: - hours, mins = 0, duration - except ValueError: - hours, mins = 1, 0 - - start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") - self.end_time = (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() + self.end_time = common.end_time(self.start_time, duration) def _calc_timezone_str(self, timezone): try: diff --git a/common.py b/common.py index a069821..1474823 100644 --- a/common.py +++ b/common.py @@ -20,6 +20,30 @@ TIME_1H_FROM_NOW = None SJ_TIME_ZONE = "420" -logging.basicConfig(filename='access.log',level=logging.DEBUG) +logging.basicConfig(filename='access.log', + level=logging.DEBUG, + format='%(asctime)s %(levelname)s: (%(name)s) %(message)s', + datefmt='%a %b %d %Y %H:%M:%S') LOGGER = logging.getLogger('roomfinder') logging.getLogger('werkzeug').setLevel(logging.ERROR) + + +def end_time(start_time, duration): + try: + if 'h' in duration and duration.endswith('m'): + hours, mins = map(int, duration[:-1].split('h')) + elif duration.endswith('h'): + hours, mins = int(duration[:-1]), 0 + elif duration.endswith('m'): + hours, mins = 0, int(duration[:-1]) + else: + duration = int(duration) + if duration < 15: + hours, mins = duration, 0 + else: + hours, mins = 0, duration + except ValueError: + hours, mins = 1, 0 + + start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") + return (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() diff --git a/find_available_room.py b/find_available_room.py index 61431a3..cbb62a3 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -31,25 +31,7 @@ def __init__(self, user, password, self.timezone = timezone or common.SJ_TIME_ZONE self.error = None self.exchange_api = exchange_api.ExchangeApi(user, base64.b64decode(urllib.unquote(password))) - - try: - if 'h' in duration and duration.endswith('m'): - hours, mins = map(int, duration[:-1].split('h')) - elif duration.endswith('h'): - hours, mins = int(duration[:-1]), 0 - elif duration.endswith('m'): - hours, mins = 0, int(duration[:-1]) - else: - duration = int(duration) - if duration < 15: - hours, mins = duration, 0 - else: - hours, mins = 0, duration - except ValueError: - hours, mins = 1, 0 - - start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") - self.end_time = (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() + self.end_time = common.end_time(self.start_time, duration) def _read_room_list(self, filename): rooms = {} From b0d17f0560c4c0383456fe409311419b0b6b4315 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 13:10:50 -0700 Subject: [PATCH 064/146] sgersapp: Move find room functionality to exchange API Change-Id: Idc29a2261f4cfd8ddffe0483d630eaaa2245f256 --- common.py | 2 -- exchange_api.py | 42 +++++++++++++++++++++++++++++++++++------- find_rooms.py | 49 ++++++------------------------------------------- 3 files changed, 41 insertions(+), 52 deletions(-) diff --git a/common.py b/common.py index 1474823..b28a7f2 100644 --- a/common.py +++ b/common.py @@ -2,8 +2,6 @@ import logging import os -URL = 'https://mail.cisco.com/ews/exchange.asmx' -SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' HTTPS_ENABLED = True HTTP_PORT = 8080 HTTPS_PORT = 8443 diff --git a/exchange_api.py b/exchange_api.py index cda4584..7a47a26 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import common import pipes import string import subprocess @@ -11,8 +10,11 @@ reload(sys) sys.setdefaultencoding("utf-8") +URL = 'https://mail.cisco.com/ews/exchange.asmx' +SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' AVAILABILITY_XML = None RESERVE_XML = None +FIND_XML = None class ExchangeApi(object): @@ -22,7 +24,7 @@ def __init__(self, user, password): + " --data '{}'" \ + " --ntlm " \ + "-u " + pipes.quote(self.user) + ":" + pipes.quote(password) \ - + " " + common.URL + + " " + URL def _read_template(self, filename): with open(filename, "r") as fhandle: @@ -30,7 +32,10 @@ def _read_template(self, filename): def _curl(self, post_data): curl_command = self.command.format(post_data) - return subprocess.Popen(curl_command, stdout=subprocess.PIPE, shell=True).communicate()[0] + response = subprocess.Popen(curl_command, stdout=subprocess.PIPE, shell=True).communicate()[0] + if not response: + raise Exception("Authentication failure") + return response def room_status(self, room_email, start_time, end_time, timezone_offset): global AVAILABILITY_XML @@ -43,13 +48,10 @@ def room_status(self, room_email, start_time, end_time, timezone_offset): endtime=end_time)) response = self._curl(data) - if not response: - raise Exception("Authentication failure") tree = ET.fromstring(response) - status = "Free" - elems = tree.findall(common.SCHEME_TYPES + "MergedFreeBusy") + elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy") freebusy = '' for elem in elems: freebusy = elem.text @@ -83,3 +85,29 @@ def reserve_room(self, room_email, room_name, start_time, end_time, timezone_off response = self._curl(data) return 'Success' in response + + def _parse_room_size(self, roomname): + try: + return int(roomname[roomname.find('(') + 1 : roomname.find(')')]) + except ValueError: + return 0 + + def find_rooms(self, prefix): + global FIND_XML + if FIND_XML is None: + FIND_XML = self._read_template("resolvenames_template.xml") + + room_info = {} + data = unicode(FIND_XML.substitute(name=prefix)) + response = self._curl(data) + + tree = ET.fromstring(response) + elems = tree.findall(SCHEME_TYPES + "Resolution") + for elem in elems: + email = elem.findall(SCHEME_TYPES + "EmailAddress") + name = elem.findall(SCHEME_TYPES + "DisplayName") + if len(email) and len(name): + roomsize = self._parse_room_size(name[0].text) + if roomsize: + room_info[email[0].text] = (name[0].text, roomsize) + return room_info diff --git a/find_rooms.py b/find_rooms.py index 202002d..abcb95a 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -5,14 +5,10 @@ import common import csv import getpass +import exchange_api import operator -import subprocess +import string import sys -import xml.etree.ElementTree as ET - -from string import Template -from string import letters -from string import digits reload(sys) sys.setdefaultencoding("utf-8") @@ -21,57 +17,24 @@ class RoomFinder(object): def __init__(self, user, password): self.user = user - self.password = password - xml_template = open("resolvenames_template.xml", "r").read() - self.xml = Template(xml_template) + self.exchange_api = exchange_api.ExchangeApi(self.user, password) self.rooms = {} def _search(self, prefix): - tmp_rooms = {} - data = unicode(self.xml.substitute(name=prefix)) - - header = '"content-type: text/xml;charset=utf-8"' - command = "curl --silent --header " + header \ - + " --data '" + data \ - + "' --ntlm " \ - + "-u "+ self.user + ":" + self.password \ - + " " + common.URL - response = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0] - - if not response.strip(): - # something went wrong - return tmp_rooms - - tree = ET.fromstring(response) - - elems = tree.findall(common.SCHEME_TYPES + "Resolution") - for elem in elems: - email = elem.findall(common.SCHEME_TYPES + "EmailAddress") - name = elem.findall(common.SCHEME_TYPES + "DisplayName") - if len(email) and len(name): - roomsize = self.room_size(name[0].text) - if roomsize: - tmp_rooms[email[0].text] = (name[0].text, roomsize) - return tmp_rooms + return self.exchange_api.find_rooms(prefix=prefix) def search(self, prefix, deep=False): rooms_found = self._search(prefix) if deep: - symbols = letters + digits + symbols = string.letters + string.digits for symbol in symbols: prefix_deep = prefix + " " + symbol rooms_found.update(self._search(prefix_deep)) - common.LOGGER.info("Search for prefix '%s' yielded %d rooms.", prefix, str(len(rooms_found))) + common.LOGGER.info("Search for prefix '%s' yielded %d rooms.", prefix, len(rooms_found)) self.rooms.update(self._search(prefix)) - def room_size(self, roomname): - try: - return int(roomname[roomname.find('(') + 1 : roomname.find(')')]) - except ValueError: - return 0 - def dump(self, filename='rooms.csv'): if not len(self.rooms): common.LOGGER.warning("No results found, check your arguments for mistakes") From c3b7140f04bc2baac03888a6c68cba4de48009d7 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 13:19:36 -0700 Subject: [PATCH 065/146] sgersapp: Ability to append new rooms to existing list Change-Id: I0f9bde061d1f70e68967a95a0469bd672e9ed636 --- find_rooms.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/find_rooms.py b/find_rooms.py index abcb95a..da91538 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -15,10 +15,19 @@ class RoomFinder(object): - def __init__(self, user, password): + def __init__(self, user, password, filename='rooms.csv', append=True): self.user = user + self.filename = filename self.exchange_api = exchange_api.ExchangeApi(self.user, password) self.rooms = {} + if append: + self._load() + + def _load(self): + with open(self.filename, 'r') as fhandle: + reader = csv.reader(fhandle) + for row in reader: + self.rooms[row[1]] = row[0], int(row[2]) def _search(self, prefix): return self.exchange_api.find_rooms(prefix=prefix) @@ -35,12 +44,12 @@ def search(self, prefix, deep=False): common.LOGGER.info("Search for prefix '%s' yielded %d rooms.", prefix, len(rooms_found)) self.rooms.update(self._search(prefix)) - def dump(self, filename='rooms.csv'): + def dump(self): if not len(self.rooms): common.LOGGER.warning("No results found, check your arguments for mistakes") return 0 - with open(filename, "wb") as fhandle: + with open(self.filename, "wb") as fhandle: writer = csv.writer(fhandle) for email, room_info in sorted(self.rooms.iteritems(), key=operator.itemgetter(1)): name, size = room_info From 800f726eff963f69387a6cfcd744817829a64478 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 13:23:38 -0700 Subject: [PATCH 066/146] sgersapp: fix pylint warnings Change-Id: Ibcf33707ee57d4efe490351e225f6760d02dc620 --- find_available_room.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index cbb62a3..48b5334 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -5,16 +5,11 @@ import base64 import common import csv -import datetime import exchange_api import getpass -import subprocess import sys import threading import urllib -import xml.etree.ElementTree as ET - -from string import Template reload(sys) sys.setdefaultencoding("utf-8") @@ -83,9 +78,6 @@ def search(self, selected_rooms=None): common.LOGGER.info("User %s searching for a room from %s to %s", self.user, self.start_time, self.end_time) - xml_template = open("getavailibility_template.xml", "r").read() - xml = Template(xml_template) - for email in selected_rooms: thread = threading.Thread(target=self._query, args=(email, )) thread.start() @@ -95,7 +87,7 @@ def search(self, selected_rooms=None): thread.join() if self.error is not None: - raise self.error + raise self.error # pylint: disable=E0702 LINE_SEPARATOR = "-" * 120 + "\n" OUTPUT_TABLE = LINE_SEPARATOR + \ From 20217637a397a0fdb694a74c8de1c42876803222 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 13:27:16 -0700 Subject: [PATCH 067/146] sgersapp: Handle auth failure while reserving room Change-Id: Ia9509e5444ee7131d883e57186dd24c3b1360bd0 --- service/webserver.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/service/webserver.py b/service/webserver.py index 98138c8..0b4c89a 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -77,10 +77,13 @@ def book_room(): queryparam.starttime, duration=queryparam.duration, timezone=queryparam.timezone) - if room_finder.reserve_room(): - return "reservation requested" - else: - return "reservation failed" + try: + if room_finder.reserve_room(): + return "reservation requested" + else: + return "reservation failed" + except Exception as e: + return "reservation failed: " + str(e) def _create_tmp_rooms_file(building_floor_name): if 'all' in building_floor_name: From 8845f44febb58af038fb134d21a89f979772a811 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 15:13:10 -0700 Subject: [PATCH 068/146] sgersapp: Update docs Change-Id: Ib42b2a2e955c8e1eda22569e1fd6005115458240 --- README.md | 63 ++++++++++++++++++++++--------------------------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 2868f19..6eff041 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,13 @@ Requirements: - Python 2.7 - Access to Exchange Web Service (EWS) API of a Microsoft Exchange Server 2010 -Usage: +Before running on the command-line, edit 'exchange_api.py' and point the URL to +your Microsoft Exchange Server, e.g. 'https://mail.example.com/ews/exchange.asmx' + +Command-line Usage: $ python find_rooms.py -h - usage: find_rooms.py [-h] -url URL -u USER [-d] prefix [prefix ...] + usage: find_rooms.py [-h] -u USER [-d] prefix [prefix ...] positional arguments: prefix A list of prefixes to search for. E.g. 'conference @@ -20,61 +23,45 @@ Usage: optional arguments: -h, --help show this help message and exit - -url URL, --url URL url for exhange server, e.g. - 'https://mail.domain.com/ews/exchange.asmx'. -u USER, --user USER user name for exchange/outlook -d, --deep Attemp a deep search (takes longer). + Example: - $ python find_rooms.py Konferenzr. Konfi -url https://mail.mycompany.com/ews/exchange.asmx -u maier --deep + $ python find_rooms.py SJC19 -u sgersapp Password: - After searching for prefix 'Konferenzr.' we found 100 rooms. - After deep search for prefix 'Konferenzr.' we found 143 rooms. - After searching for prefix 'Konfi' we found 151 rooms. - After deep search for prefix 'Konfi' we found 151 rooms. -This will create a CSV file `rooms.csv` holding a list of all rooms found with the prefix `Konfi` and `Konferenzr.` in their display names. +This will create a CSV file `rooms.csv` holding a list of all rooms found with the prefix `SJC19` in their display names. After doing so, you can get the status for each of the rooms by calling $ python find_available_room.py -h - usage: find_available_room.py [-h] -url URL -u USER [-start STARTTIME] + usage: find_available_room.py [-h] -u USER [-start STARTTIME] [-end ENDTIME] [-f FILE] - optional arguments: - -h, --help show this help message and exit - -url URL, --url URL url for exhange server, e.g. - 'https://mail.domain.com/ews/exchange.asmx'. - -u USER, --user USER user name for exchange/outlook - -start STARTTIME, --starttime STARTTIME - Starttime e.g. 2014-07-02T11:00:00 (default = now) - -end ENDTIME, --endtime ENDTIME - Endtime e.g. 2014-07-02T12:00:00 (default = now+1h) - -f FILE, --file FILE csv filename with rooms to check (default=rooms.csv). - Format: Name,email + optional arguments: + -h, --help show this help message and exit + -u USER, --user USER user name for exchange/outlook + -prefix PREFIX, --prefix PREFIX + A prefix to search for. e.g. 'SJC19 SJC18' + -start STARTTIME, --starttime STARTTIME + Starttime e.g. 2014-07-02T11:00:00 (default = now) + -duration DURATION, --duration DURATION + Duration e.g. 1h or 15m (default = 1h) + -f FILE, --file FILE csv filename with room info (default=rooms.csv). Example: - $ python find_available_room.py -url https://mail.mycompany.com/ews/exchange.asmx -u maier -start 2014-07-03T13:00:00 -end 2014-07-03T17:00:00 - Password: - Busy Konferenzr. Asterix konf.asterix@mycompany.com - Tentative Konferenzr. Personal Konferenzr.Personal@mycompany.com - Busy Konferenzr. Obelix konferenzr.obelix@mycompany.com - Busy Konfi Idefix konfi_idefix@mycompany.com - Free Konferenzr. Miraculix miraculix@mycompany.com - ... + $ python find_available_room.py -u sgersapp -start 2014-07-03T13:00:00 -duration 1h -Since the auto-generated list `rooms.csv` can be very huge it is recommended to copy that list to another file, e.g. `favorite_rooms.csv` and edit that file so that it only holds the meetings rooms you are interested in. After doing so, you can get the status for you favorite rooms very quickly using: - - $ python find_available_room.py -url https://mail.mycompany.com/ews/exchange.asmx -u maier -start 2014-07-03T13:00:00 -end 2014-07-03T17:00:00 -f favorite_rooms.csv - Password: - Free Konferenzr. 007 007@mycompany.com - Busy Konferenzr. Asterix konf.asterix@mycompany.com - Busy Konferenzr. Obelix konferenzr.obelix@mycompany.com +Results are logged to the log file (default: access.log) - +Before starting the web-app, edit 'CONFIG' and pick the number of worker threads. +Generate a certifcate and private key and point CONFIG to their locations. +Web-App: + $ ./run.sh From b8f5ca052167f73c3b90b70939731110aa695ff7 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 15:37:56 -0700 Subject: [PATCH 069/146] sgersapp: Fix command-line usage Change-Id: I807a5191a0957c3bddf985209a3bdc6b0dbadcff --- README.md | 37 +++++++++++++++++++++++++++++++------ book_room.py | 37 ++++++++++++++++++++----------------- common.py | 1 - find_available_room.py | 8 +++++--- service/webserver.py | 17 +++++++++-------- 5 files changed, 65 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 6eff041..ab7c17f 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ roomfinder Python scripts for finding free conference rooms from a Microsoft Exchange Server. Requirements: - + - curl - Python 2.7 - - Access to Exchange Web Service (EWS) API of a Microsoft Exchange Server 2010 + - Access to Exchange Web Service (EWS) API of a Microsoft Exchange Server 2010 Before running on the command-line, edit 'exchange_api.py' and point the URL to your Microsoft Exchange Server, e.g. 'https://mail.example.com/ews/exchange.asmx' @@ -28,18 +28,18 @@ Command-line Usage: Example: - + $ python find_rooms.py SJC19 -u sgersapp Password: This will create a CSV file `rooms.csv` holding a list of all rooms found with the prefix `SJC19` in their display names. -After doing so, you can get the status for each of the rooms by calling +After doing so, you can check the status for each of the rooms by calling $ python find_available_room.py -h usage: find_available_room.py [-h] -u USER [-start STARTTIME] [-end ENDTIME] [-f FILE] - + optional arguments: -h, --help show this help message and exit -u USER, --user USER user name for exchange/outlook @@ -53,11 +53,36 @@ After doing so, you can get the status for each of the rooms by calling Example: - + $ python find_available_room.py -u sgersapp -start 2014-07-03T13:00:00 -duration 1h Results are logged to the log file (default: access.log) +Eventually, you can reserve a rooms by calling + + $ python book_room.py -h + usage: book_room.py [-h] -u USER [-start STARTTIME] [-d DURATION] -e ROOMEMAIL + -r ROOMNAME + + optional arguments: + -h, --help show this help message and exit + -u USER, --user USER user name for exchange/outlook + -start STARTTIME, --starttime STARTTIME + Starttime e.g. 2014-07-02T11:00:00 (default = now) + -d DURATION, --duration DURATION + Duration e.g. 1h or 15m (default = 1h) + -e ROOMEMAIL, --roomemail ROOMEMAIL + Email address of the room + -r ROOMNAME, --roomname ROOMNAME + Name of room + + +Example: + + $ python book_room.py -u sgersapp -start 2014-07-03T13:00:00 -duration 1h -r SJC19-3-SAISH -e ROOM_SAISH@example.com + +You will receive a confirmation email from Exchange if the reservation is accepted or rejected. + Before starting the web-app, edit 'CONFIG' and pick the number of worker threads. Generate a certifcate and private key and point CONFIG to their locations. diff --git a/book_room.py b/book_room.py index cafbc14..c81f45d 100644 --- a/book_room.py +++ b/book_room.py @@ -3,7 +3,6 @@ import argparse import base64 import common -import datetime import exchange_api import getpass import sys @@ -14,15 +13,18 @@ class ReserveAvailRoom(object): - def __init__(self, roomname, roomemail, user, password, - start_time=common.TIME_NOW, - duration='1h', timezone=common.SJ_TIME_ZONE): + def __init__(self, user, password, + roomname, roomemail, + start_time=common.TIME_NOW, duration='1h', + raw_password=None, + timezone=common.SJ_TIME_ZONE): + self.user = user self.roomname = roomname self.roomemail = roomemail - self.user = user self.start_time = start_time self.timezone = self._calc_timezone_str(timezone) - self.exchange_api = exchange_api.ExchangeApi(user, base64.b64decode(urllib.unquote(password))) + password = raw_password or base64.b64decode(urllib.unquote(password)) + self.exchange_api = exchange_api.ExchangeApi(user, password) self.end_time = common.end_time(self.start_time, duration) def _calc_timezone_str(self, timezone): @@ -50,21 +52,22 @@ def run(): parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", default=common.TIME_NOW) - parser.add_argument("-end", "--endtime", - help="Endtime e.g. 2014-07-02T12:00:00 (default = now+1h)", - default=common.TIME_1H_FROM_NOW) - parser.add_argument("-f", "--file", - help="csv filename with room info (default=rooms.csv).", - default="rooms.csv") - - parser.add_argument("-r", "--room", - help="Name of conf room", + parser.add_argument("-d", "--duration", + help="Duration e.g. 30m (default = 1h)", + default='1h') + parser.add_argument("-e", "--roomemail", + help="Email address of room", + required=True) + parser.add_argument("-r", "--roomname", + help="Name of room", required=True) args = parser.parse_args() - args.password = getpass.getpass("Password:") + args.password = base64.b64encode(getpass.getpass("Password:")) - room_finder = ReserveAvailRoom(args.room, args.user, args.password, args.starttime, args.endtime, args.file) + room_finder = ReserveAvailRoom(user=args.user, password=args.password, + roomname=args.roomname, roomemail=args.roomemail, + start_time=args.starttime, duration=args.duration) room_finder.reserve_room() diff --git a/common.py b/common.py index b28a7f2..76c112f 100644 --- a/common.py +++ b/common.py @@ -15,7 +15,6 @@ TEMPLATE_FOLDER = os.path.join(SERVICE_DIR, 'templates') TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() -TIME_1H_FROM_NOW = None SJ_TIME_ZONE = "420" logging.basicConfig(filename='access.log', diff --git a/find_available_room.py b/find_available_room.py index 48b5334..c70b597 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -18,8 +18,8 @@ class AvailRoomFinder(object): def __init__(self, user, password, start_time=common.TIME_NOW, duration='1h', - roominfo='rooms.csv', timezone=common.SJ_TIME_ZONE): - self.rooms = self._read_room_list(roominfo) + filename='rooms.csv', timezone=common.SJ_TIME_ZONE): + self.rooms = self._read_room_list(filename) self.user = user self.start_time = start_time self.room_info = {} @@ -120,7 +120,9 @@ def run(): args = parser.parse_args() args.password = base64.b64encode(getpass.getpass("Password:")) - room_finder = AvailRoomFinder(args.user, args.password, args.starttime, args.duration, args.file) + room_finder = AvailRoomFinder(user=args.user, password=args.password, + start_time=args.starttime, duration=args.duration, + filename=args.file) room_finder.search_free(prefix=args.prefix) diff --git a/service/webserver.py b/service/webserver.py index 0b4c89a..81cd2fe 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -49,10 +49,11 @@ def show_rooms(): _create_tmp_rooms_file(prefix) try: - room_finder = AvailRoomFinder(queryparam.user, queryparam.password, - queryparam.starttime, + room_finder = AvailRoomFinder(user=queryparam.user, + password=queryparam.password, + start_time=queryparam.starttime, duration=queryparam.duration, - roominfo=common.ROOMS_SEARCH_CSV, + filename=common.ROOMS_SEARCH_CSV, timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) except Exception as e: @@ -70,11 +71,11 @@ def book_room(): password = request.args.get('password'), timezone = request.args.get('timezone'), ) - room_finder = ReserveAvailRoom(queryparam.roomname, - queryparam.roomemail, - queryparam.user, - queryparam.password, - queryparam.starttime, + room_finder = ReserveAvailRoom(user=queryparam.user, + password=queryparam.password, + roomname=queryparam.roomname, + roomemail=queryparam.roomemail, + start_time=queryparam.starttime, duration=queryparam.duration, timezone=queryparam.timezone) try: From b406b9239c1c041726d130e590de55c813c240ee Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 17:29:04 -0700 Subject: [PATCH 070/146] sgersapp: First few unit tests Change-Id: I0c324020194bbcb0610a0ba2919786fe62576817 --- test_exchange_api.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 test_exchange_api.py diff --git a/test_exchange_api.py b/test_exchange_api.py new file mode 100644 index 0000000..0c3a94f --- /dev/null +++ b/test_exchange_api.py @@ -0,0 +1,56 @@ +import exchange_api +import mock +import nose +import string + +ROOM_AVAIL_XML = string.Template('NoErrorDetailedMerged$freebusy4800111Sunday-6023SundaySunday Monday Tuesday Wednesday Thursday Friday Saturday01439') + +def get_popen_mock_room_avail(mock_popen, freebusy): + process_mock = mock.Mock() + attrs = {'communicate.return_value': (ROOM_AVAIL_XML.substitute(freebusy=freebusy), '')} + process_mock.configure_mock(**attrs) + mock_popen.return_value = process_mock + +@nose.tools.raises(Exception) +@mock.patch('exchange_api.subprocess.Popen') +def test_auth_failure(mock_popen): + get_popen_mock_room_avail(mock_popen, '') + api = exchange_api.ExchangeApi(user="testuser", password="testpassword") + api.find_rooms(prefix="BLAH") + assert False + +@mock.patch('exchange_api.subprocess.Popen') +def test_room_available(mock_popen): + FREEBUSY = '0000' + ROOM_EMAIL = 'ROOM@example.com' + get_popen_mock_room_avail(mock_popen, FREEBUSY) + + api = exchange_api.ExchangeApi(user="testuser", password="testpassword") + response = api.room_status(room_email=ROOM_EMAIL, + start_time="2014-07-02T11:00:00", + end_time="2014-07-02T11:00:00", + timezone_offset="480") + + # Keys:'freebusy', 'status', 'email' + assert response['status'] == 'Free' + assert response['freebusy'] == FREEBUSY + assert response['email'] == ROOM_EMAIL + +@mock.patch('exchange_api.subprocess.Popen') +def test_room_unavailable(mock_popen): + FREEBUSY = '0022' + ROOM_EMAIL = 'ROOM@example.com' + get_popen_mock_room_avail(mock_popen, FREEBUSY) + + api = exchange_api.ExchangeApi(user="testuser", password="testpassword") + response = api.room_status(room_email=ROOM_EMAIL, + start_time="2014-07-02T11:00:00", + end_time="2014-07-02T11:00:00", + timezone_offset="480") + + # Keys:'freebusy', 'status', 'email' + assert response['status'] != 'Free' + assert response['freebusy'] == FREEBUSY + assert response['email'] == ROOM_EMAIL + + From 0d8c963c118d0e07e9aa6658be672302a96228c5 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 17:31:51 -0700 Subject: [PATCH 071/146] sgersapp: Use DOMAIN to infer URL and email addresses Change-Id: I2360ce1dcbeb8e0c0fb3b059f0d8aef4336b5df8 --- README.md | 5 +++-- exchange_api.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab7c17f..249003e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ Requirements: - Python 2.7 - Access to Exchange Web Service (EWS) API of a Microsoft Exchange Server 2010 -Before running on the command-line, edit 'exchange_api.py' and point the URL to +Before running on the command-line, edit 'exchange_api.py' and modify your DOMAIN +to your organization's domain e.g. 'example.com', so that the URL points to your Microsoft Exchange Server, e.g. 'https://mail.example.com/ews/exchange.asmx' Command-line Usage: @@ -84,7 +85,7 @@ Example: You will receive a confirmation email from Exchange if the reservation is accepted or rejected. Before starting the web-app, edit 'CONFIG' and pick the number of worker threads. -Generate a certifcate and private key and point CONFIG to their locations. +Generate a certificate and private key and point CONFIG to their locations. Web-App: diff --git a/exchange_api.py b/exchange_api.py index 7a47a26..75866c2 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -10,7 +10,8 @@ reload(sys) sys.setdefaultencoding("utf-8") -URL = 'https://mail.cisco.com/ews/exchange.asmx' +DOMAIN = 'cisco.com' +URL = 'https://mail.{}/ews/exchange.asmx'.format(DOMAIN) SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' AVAILABILITY_XML = None RESERVE_XML = None @@ -69,7 +70,7 @@ def reserve_room(self, room_email, room_name, start_time, end_time, timezone_off if RESERVE_XML is None: RESERVE_XML = self._read_template("reserve_resource_template.xml") - user_email = self.user + '@cisco.com' + user_email = self.user + '@' + DOMAIN meeting_body = '{0} booked via RoomFinder by {1}'.format(room_name, user_email) subject = 'RoomFinder: {0}'.format(room_name) From b87d9460d3438cc1fafb7abc9bd341612b0f6424 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 20:45:25 -0700 Subject: [PATCH 072/146] sgersapp: Fix minor pylint issues Change-Id: Id7d2bf33954bbd4e122ff19fedbd00115305178b --- book_room.py | 14 +++++-- exchange_api.py | 16 ++++++-- find_available_room.py | 40 ++++++++++++------- find_rooms.py | 13 +++++- run.sh | 2 +- service/webserver.py | 90 ++++++++++++++++++++++-------------------- 6 files changed, 110 insertions(+), 65 deletions(-) diff --git a/book_room.py b/book_room.py index c81f45d..ff27e60 100644 --- a/book_room.py +++ b/book_room.py @@ -1,17 +1,23 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +APIs to request an Exchange Server reserve a room +""" + import argparse import base64 -import common -import exchange_api import getpass import sys import urllib +import common +from exchange_api import ExchangeApi + reload(sys) sys.setdefaultencoding("utf-8") class ReserveAvailRoom(object): + """ Class to request an Exchange Server to reserve a room """ def __init__(self, user, password, roomname, roomemail, @@ -24,7 +30,7 @@ def __init__(self, user, password, self.start_time = start_time self.timezone = self._calc_timezone_str(timezone) password = raw_password or base64.b64decode(urllib.unquote(password)) - self.exchange_api = exchange_api.ExchangeApi(user, password) + self.exchange_api = ExchangeApi(user, password) self.end_time = common.end_time(self.start_time, duration) def _calc_timezone_str(self, timezone): @@ -38,6 +44,7 @@ def _calc_timezone_str(self, timezone): return "{}PT{}H{}M".format(sign, abs(hours_offset), abs(minutes_offset)) def reserve_room(self): + """ Request reservation of specified room """ response = self.exchange_api.reserve_room( \ room_email=self.roomemail, room_name=self.roomname, @@ -47,6 +54,7 @@ def reserve_room(self): return response def run(): + """ Parse command-line arguments and request room reservation """ parser = argparse.ArgumentParser() parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) parser.add_argument("-start", "--starttime", diff --git a/exchange_api.py b/exchange_api.py index 75866c2..d7a68a0 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -1,5 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +APIs to communicate with the Exchange Server +""" import pipes import string @@ -18,6 +21,7 @@ FIND_XML = None class ExchangeApi(object): + """ Class to communicate with the Exchange Server """ def __init__(self, user, password): self.user = user @@ -33,12 +37,16 @@ def _read_template(self, filename): def _curl(self, post_data): curl_command = self.command.format(post_data) - response = subprocess.Popen(curl_command, stdout=subprocess.PIPE, shell=True).communicate()[0] + curl_process = subprocess.Popen(curl_command, + stdout=subprocess.PIPE, + shell=True) + response = curl_process.communicate()[0] if not response: raise Exception("Authentication failure") return response def room_status(self, room_email, start_time, end_time, timezone_offset): + """ Lookup availability status of specified room """ global AVAILABILITY_XML if AVAILABILITY_XML is None: AVAILABILITY_XML = self._read_template("getavailibility_template.xml") @@ -66,10 +74,11 @@ def room_status(self, room_email, start_time, end_time, timezone_offset): return {'freebusy': freebusy, 'status': status, 'email' : room_email,} def reserve_room(self, room_email, room_name, start_time, end_time, timezone_offset): + """ Request reservation of specified room """ global RESERVE_XML if RESERVE_XML is None: RESERVE_XML = self._read_template("reserve_resource_template.xml") - + user_email = self.user + '@' + DOMAIN meeting_body = '{0} booked via RoomFinder by {1}'.format(room_name, user_email) subject = 'RoomFinder: {0}'.format(room_name) @@ -82,7 +91,7 @@ def reserve_room(self, room_email, room_name, start_time, end_time, timezone_off meeting_body=meeting_body, conf_room=room_name, timezone=timezone_offset, - )) + )) response = self._curl(data) return 'Success' in response @@ -94,6 +103,7 @@ def _parse_room_size(self, roomname): return 0 def find_rooms(self, prefix): + """ Search for rooms with names starting with specified prefix """ global FIND_XML if FIND_XML is None: FIND_XML = self._read_template("resolvenames_template.xml") diff --git a/find_available_room.py b/find_available_room.py index c70b597..faf0269 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -1,20 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +APIs to query an Exchange Server for availability status of rooms +""" import argparse import base64 -import common import csv -import exchange_api import getpass import sys import threading import urllib +import common +from exchange_api import ExchangeApi + reload(sys) sys.setdefaultencoding("utf-8") +SEPARATOR = "-" * 120 + "\n" +TABLE_FORMAT = "{0:40s} {1:64s} {2:20s}\n" +TABLE_HEADER = SEPARATOR + TABLE_FORMAT.format("Status", "Room", "Email") + SEPARATOR + class AvailRoomFinder(object): + """ Class to query an Exchange Server for availability status of rooms """ def __init__(self, user, password, start_time=common.TIME_NOW, duration='1h', @@ -25,7 +34,7 @@ def __init__(self, user, password, self.room_info = {} self.timezone = timezone or common.SJ_TIME_ZONE self.error = None - self.exchange_api = exchange_api.ExchangeApi(user, base64.b64decode(urllib.unquote(password))) + self.exchange_api = ExchangeApi(user, base64.b64decode(urllib.unquote(password))) self.end_time = common.end_time(self.start_time, duration) def _read_room_list(self, filename): @@ -39,6 +48,7 @@ def _read_room_list(self, filename): return rooms def search_free(self, prefix, min_size=1): + """ Look for available rooms from the list of selected rooms """ selected_rooms = {} for email in self.rooms: name, size = self.rooms[email] @@ -67,16 +77,18 @@ def _query(self, email): room_info['name'] = room_name self.room_info[room_name] = room_info - except Exception as e: - self.error = e - common.LOGGER.warning("Exception querying room %s: %s", room_name, str(e)) + except Exception as exception: + self.error = exception + common.LOGGER.warning("Exception querying room %s: %s", room_name, str(exception)) def search(self, selected_rooms=None): + """ Lookup availability status of rooms from the list of selected rooms """ if selected_rooms is None: selected_rooms = self.rooms worker_threads = [] - common.LOGGER.info("User %s searching for a room from %s to %s", self.user, self.start_time, self.end_time) + common.LOGGER.info("User %s searching for a room from %s to %s", + self.user, self.start_time, self.end_time) for email in selected_rooms: thread = threading.Thread(target=self._query, args=(email, )) @@ -89,19 +101,19 @@ def search(self, selected_rooms=None): if self.error is not None: raise self.error # pylint: disable=E0702 - LINE_SEPARATOR = "-" * 120 + "\n" - OUTPUT_TABLE = LINE_SEPARATOR + \ - "{0:40s} {1:64s} {2:20s}\n".format("Status", "Room", "Email") + \ - LINE_SEPARATOR + output_table = TABLE_HEADER for name, info in self.room_info.iteritems(): - OUTPUT_TABLE += "{0:40s} {1:64s} {2:20s}\n".format(info['status'] + '-' + info['freebusy'], name, info['email']) - OUTPUT_TABLE += LINE_SEPARATOR + output_table += TABLE_FORMAT.format( + info['status'] + '-' + info['freebusy'], + name, info['email']) + output_table += SEPARATOR - common.LOGGER.debug("\n%s", OUTPUT_TABLE) + common.LOGGER.debug("\n%s", output_table) return self.room_info def run(): + """ Parse command-line arguments and invoke room availability finder """ parser = argparse.ArgumentParser() parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) parser.add_argument("-prefix", "--prefix", diff --git a/find_rooms.py b/find_rooms.py index da91538..da84dff 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -1,19 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +APIs to query an Exchange Server for list of rooms +""" + import argparse -import common import csv import getpass -import exchange_api import operator import string import sys +import common +import exchange_api + reload(sys) sys.setdefaultencoding("utf-8") class RoomFinder(object): + """ Class to query an Exchange Server for list of rooms """ def __init__(self, user, password, filename='rooms.csv', append=True): self.user = user @@ -33,6 +39,7 @@ def _search(self, prefix): return self.exchange_api.find_rooms(prefix=prefix) def search(self, prefix, deep=False): + """ Search for rooms with names starting with specified prefix """ rooms_found = self._search(prefix) if deep: @@ -45,6 +52,7 @@ def search(self, prefix, deep=False): self.rooms.update(self._search(prefix)) def dump(self): + """ Dump the results to specified file """ if not len(self.rooms): common.LOGGER.warning("No results found, check your arguments for mistakes") return 0 @@ -58,6 +66,7 @@ def dump(self): return len(self.rooms) def run(): + """ Parse command-line arguments and invoke room finder """ parser = argparse.ArgumentParser() parser.add_argument("prefix", nargs='+', help="A prefix to search for. e.g. 'SJC19- SJC18-'") parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) diff --git a/run.sh b/run.sh index b32237b..80b6189 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -PYTHONPATH=. gunicorn --config=CONFIG service.webserver:app +PYTHONPATH=. gunicorn --config=CONFIG service.webserver:APP diff --git a/service/webserver.py b/service/webserver.py index 81cd2fe..49bad28 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -1,29 +1,36 @@ -import common -import flask +""" +Webservice APIs for room finder backend +""" + +import collections import json import os +import shutil import socket + +import common +import flask + from book_room import ReserveAvailRoom -from collections import namedtuple from find_available_room import AvailRoomFinder -from shutil import copyfile -from flask import Flask, request -app = Flask(__name__, template_folder=common.TEMPLATE_FOLDER) +APP = flask.Flask(__name__, template_folder=common.TEMPLATE_FOLDER) -@app.route('/') +@APP.route('/') def index(): + """ Serve static index file """ return flask.render_template('index.html') -QueryParam = namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees, timezone' ) -BookRoomQueryParam = namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password, timezone') +QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees, timezone') +BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password, timezone') -@app.route('/showbuldings', methods=['GET']) +@APP.route('/showbuldings', methods=['GET']) def show_buldings(): + """ Serve list of buildings in JSON """ buldings = [] - with open(common.ROOMS_CSV, 'r') as f: - for line in f.readlines(): + with open(common.ROOMS_CSV, 'r') as fhandle: + for line in fhandle.readlines(): buldingname = line.split('-')[0] if buldingname not in buldings: buldings.append(buldingname) @@ -32,18 +39,17 @@ def show_buldings(): # Example Query # http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password -@app.route('/showrooms', methods=['GET']) +@APP.route('/showrooms', methods=['GET']) def show_rooms(): - queryparam = QueryParam( - buildingname=request.args.get('buildingname'), - floor=request.args.get('floor'), - starttime=request.args.get('starttime'), - duration=request.args.get('duration'), - user = request.args.get('user'), - password = request.args.get('password'), - attendees = request.args.get('attendees'), - timezone = request.args.get('timezone'), - ) + """ Serve list of buildings in JSON """ + queryparam = QueryParam(buildingname=flask.request.args.get('buildingname'), + floor=flask.request.args.get('floor'), + starttime=flask.request.args.get('starttime'), + duration=flask.request.args.get('duration'), + user=flask.request.args.get('user'), + password=flask.request.args.get('password'), + attendees=flask.request.args.get('attendees'), + timezone=flask.request.args.get('timezone')) prefix = queryparam.buildingname + '-' + queryparam.floor _create_tmp_rooms_file(prefix) @@ -56,21 +62,20 @@ def show_rooms(): filename=common.ROOMS_SEARCH_CSV, timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) - except Exception as e: - rooms_info = {"Error" : str(e)} + except Exception as exception: + rooms_info = {"Error" : str(exception)} return json.dumps(rooms_info) -@app.route('/bookroom', methods=['GET']) +@APP.route('/bookroom', methods=['GET']) def book_room(): - queryparam = BookRoomQueryParam( - roomname=request.args.get('roomname'), - roomemail=request.args.get('roomemail'), - starttime=request.args.get('starttime'), - duration=request.args.get('duration'), - user = request.args.get('user'), - password = request.args.get('password'), - timezone = request.args.get('timezone'), - ) + """ Reserve specified room """ + queryparam = BookRoomQueryParam(roomname=flask.request.args.get('roomname'), + roomemail=flask.request.args.get('roomemail'), + starttime=flask.request.args.get('starttime'), + duration=flask.request.args.get('duration'), + user=flask.request.args.get('user'), + password=flask.request.args.get('password'), + timezone=flask.request.args.get('timezone')) room_finder = ReserveAvailRoom(user=queryparam.user, password=queryparam.password, roomname=queryparam.roomname, @@ -83,21 +88,22 @@ def book_room(): return "reservation requested" else: return "reservation failed" - except Exception as e: - return "reservation failed: " + str(e) + except Exception as excpetion: + return "reservation failed: " + str(excpetion) def _create_tmp_rooms_file(building_floor_name): if 'all' in building_floor_name: - copyfile(common.ROOMS_CSV, common.ROOMS_SEARCH_CSV) + shutil.copyfile(common.ROOMS_CSV, common.ROOMS_SEARCH_CSV) else: - open(common.ROOMS_SEARCH_CSV, 'w').writelines([ line for line in open(common.ROOMS_CSV) if building_floor_name in line]) + open(common.ROOMS_SEARCH_CSV, 'w').writelines([line for line in open(common.ROOMS_CSV) if building_floor_name in line]) -def create_context(): +def create_ssl_context(): + """ Create SSL context """ context = (os.path.join(common.CERT_DIR, 'roomfinder.cert'), os.path.join(common.CERT_DIR, 'roomfinder.key')) return context if __name__ == '__main__': if common.HTTPS_ENABLED: - app.run(threaded=True, host=socket.gethostname(), ssl_context=create_context(), port=common.HTTPS_PORT) + APP.run(threaded=True, host=socket.gethostname(), ssl_context=create_ssl_context(), port=common.HTTPS_PORT) else: - app.run(threaded=True, host=socket.gethostname(), port=common.HTTP_PORT) + APP.run(threaded=True, host=socket.gethostname(), port=common.HTTP_PORT) From 82fa40affb31ec4cf97ab7963ae4c72855d84d87 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 20:50:18 -0700 Subject: [PATCH 073/146] sgersapp: Remove unneeded code Change-Id: I4d7514d8600af453e6a839535c0e67fcb93436cf --- service/webserver.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/service/webserver.py b/service/webserver.py index 49bad28..dcf283a 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -5,10 +5,8 @@ import collections import json import os -import shutil import socket - import common import flask @@ -52,14 +50,13 @@ def show_rooms(): timezone=flask.request.args.get('timezone')) prefix = queryparam.buildingname + '-' + queryparam.floor - _create_tmp_rooms_file(prefix) try: room_finder = AvailRoomFinder(user=queryparam.user, password=queryparam.password, start_time=queryparam.starttime, duration=queryparam.duration, - filename=common.ROOMS_SEARCH_CSV, + filename=common.ROOMS_CSV, timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) except Exception as exception: @@ -91,12 +88,6 @@ def book_room(): except Exception as excpetion: return "reservation failed: " + str(excpetion) -def _create_tmp_rooms_file(building_floor_name): - if 'all' in building_floor_name: - shutil.copyfile(common.ROOMS_CSV, common.ROOMS_SEARCH_CSV) - else: - open(common.ROOMS_SEARCH_CSV, 'w').writelines([line for line in open(common.ROOMS_CSV) if building_floor_name in line]) - def create_ssl_context(): """ Create SSL context """ context = (os.path.join(common.CERT_DIR, 'roomfinder.cert'), os.path.join(common.CERT_DIR, 'roomfinder.key')) From 691a57aa89c855316b25f3155d7fcdaf284d156f Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 2 Sep 2016 20:50:31 -0700 Subject: [PATCH 074/146] sgersapp: Fix minor pylint issues Change-Id: I63768bad7d17d2566d608cfb8abc5c4da3212298 --- common.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common.py b/common.py index 76c112f..fc9f30f 100644 --- a/common.py +++ b/common.py @@ -1,3 +1,7 @@ +""" +Common declarations and functions +""" + import datetime import logging import os @@ -8,7 +12,6 @@ PWD = os.getcwd() ROOMS_CSV = os.path.join(PWD, 'rooms.csv') -ROOMS_SEARCH_CSV = os.path.join(PWD, 'roomssearch.csv') AVAILIBILITY_TEMPLATE = os.path.join(PWD, 'getavailibility_template.xml') SERVICE_DIR = os.path.join(PWD, 'service') CERT_DIR = os.path.join(PWD, 'certdir') @@ -26,6 +29,7 @@ def end_time(start_time, duration): + """ Calculate end time, given start time and duration """ try: if 'h' in duration and duration.endswith('m'): hours, mins = map(int, duration[:-1].split('h')) From 70e2161f699220ed8f0cae82d65e97ac2ce0c5f4 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Sun, 4 Sep 2016 10:47:17 -0700 Subject: [PATCH 075/146] sgersapp: Add tests for room reservation Change-Id: I95e15e2ed2ad0a0ca246a8d4a7bda4e8f39a0d3e --- test_exchange_api.py | 51 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/test_exchange_api.py b/test_exchange_api.py index 0c3a94f..2c74700 100644 --- a/test_exchange_api.py +++ b/test_exchange_api.py @@ -5,6 +5,15 @@ ROOM_AVAIL_XML = string.Template('NoErrorDetailedMerged$freebusy4800111Sunday-6023SundaySunday Monday Tuesday Wednesday Thursday Friday Saturday01439') +ROOM_RESERVE_XML = string.Template('NoError') + +ROOM_EMAIL = 'ROOM@example.com' +ROOM_NAME = 'ROOM' +START_TIME = "2016-07-02T11:00:00" +END_TIME = "2016-07-02T11:00:00" +TIME_ZONE_OFFSET = "480" + + def get_popen_mock_room_avail(mock_popen, freebusy): process_mock = mock.Mock() attrs = {'communicate.return_value': (ROOM_AVAIL_XML.substitute(freebusy=freebusy), '')} @@ -22,7 +31,6 @@ def test_auth_failure(mock_popen): @mock.patch('exchange_api.subprocess.Popen') def test_room_available(mock_popen): FREEBUSY = '0000' - ROOM_EMAIL = 'ROOM@example.com' get_popen_mock_room_avail(mock_popen, FREEBUSY) api = exchange_api.ExchangeApi(user="testuser", password="testpassword") @@ -39,13 +47,12 @@ def test_room_available(mock_popen): @mock.patch('exchange_api.subprocess.Popen') def test_room_unavailable(mock_popen): FREEBUSY = '0022' - ROOM_EMAIL = 'ROOM@example.com' get_popen_mock_room_avail(mock_popen, FREEBUSY) api = exchange_api.ExchangeApi(user="testuser", password="testpassword") response = api.room_status(room_email=ROOM_EMAIL, - start_time="2014-07-02T11:00:00", - end_time="2014-07-02T11:00:00", + start_time=START_TIME, + end_time=END_TIME, timezone_offset="480") # Keys:'freebusy', 'status', 'email' @@ -54,3 +61,39 @@ def test_room_unavailable(mock_popen): assert response['email'] == ROOM_EMAIL +def get_popen_mock_room_reserve(mock_popen, result): + process_mock = mock.Mock() + attrs = {'communicate.return_value': (ROOM_RESERVE_XML.substitute(result=result), '')} + process_mock.configure_mock(**attrs) + mock_popen.return_value = process_mock + +@mock.patch('exchange_api.subprocess.Popen') +def test_room_reserve_success(mock_popen): + RESULT = 'Success' + get_popen_mock_room_reserve(mock_popen, RESULT) + + api = exchange_api.ExchangeApi(user="testuser", password="testpassword") + response = api.reserve_room(room_email=ROOM_EMAIL, + room_name=ROOM_NAME, + start_time=START_TIME, + end_time=END_TIME, + timezone_offset=TIME_ZONE_OFFSET) + + assert response is True + +@mock.patch('exchange_api.subprocess.Popen') +def test_room_reserve_failure(mock_popen): + RESULT = 'Failure' + get_popen_mock_room_reserve(mock_popen, RESULT) + + api = exchange_api.ExchangeApi(user="testuser", password="testpassword") + response = api.reserve_room(room_email=ROOM_EMAIL, + room_name=ROOM_NAME, + start_time=START_TIME, + end_time=END_TIME, + timezone_offset=TIME_ZONE_OFFSET) + + assert response is False + + + From 071948f9e309f06a8e55a2becd72c87c7478f5e5 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Sun, 4 Sep 2016 10:48:34 -0700 Subject: [PATCH 076/146] sgersapp: Add tests for room reservation Change-Id: I3173ae66599ad6cf7ac67261a87086fbc1dfc74d --- test_exchange_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test_exchange_api.py b/test_exchange_api.py index 2c74700..9b165bf 100644 --- a/test_exchange_api.py +++ b/test_exchange_api.py @@ -35,9 +35,9 @@ def test_room_available(mock_popen): api = exchange_api.ExchangeApi(user="testuser", password="testpassword") response = api.room_status(room_email=ROOM_EMAIL, - start_time="2014-07-02T11:00:00", - end_time="2014-07-02T11:00:00", - timezone_offset="480") + start_time=END_TIME, + end_time=END_TIME, + timezone_offset=TIME_ZONE_OFFSET) # Keys:'freebusy', 'status', 'email' assert response['status'] == 'Free' @@ -53,7 +53,7 @@ def test_room_unavailable(mock_popen): response = api.room_status(room_email=ROOM_EMAIL, start_time=START_TIME, end_time=END_TIME, - timezone_offset="480") + timezone_offset=TIME_ZONE_OFFSET) # Keys:'freebusy', 'status', 'email' assert response['status'] != 'Free' From ae4632547b23830a304628a50acbac49b5df3811 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Sun, 4 Sep 2016 11:10:19 -0700 Subject: [PATCH 077/146] sgersapp: Fix a bug that made multiple identical calls to find_rooms Change-Id: Ib0790bae3b61e5d9652a5249d566ca6e6a945a9b --- find_rooms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/find_rooms.py b/find_rooms.py index da84dff..39783ec 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -49,7 +49,7 @@ def search(self, prefix, deep=False): rooms_found.update(self._search(prefix_deep)) common.LOGGER.info("Search for prefix '%s' yielded %d rooms.", prefix, len(rooms_found)) - self.rooms.update(self._search(prefix)) + self.rooms.update(rooms_found) def dump(self): """ Dump the results to specified file """ From cdb3efe381fafd6a9e6c59f3719ef33719bc56f3 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Sun, 4 Sep 2016 11:50:30 -0700 Subject: [PATCH 078/146] sgersapp: Unit tests for finding rooms Change-Id: I5f39599d38e588a2755b51fc8a0da336c2c9537b --- test_exchange_api.py | 59 +++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/test_exchange_api.py b/test_exchange_api.py index 9b165bf..290a186 100644 --- a/test_exchange_api.py +++ b/test_exchange_api.py @@ -3,31 +3,63 @@ import nose import string -ROOM_AVAIL_XML = string.Template('NoErrorDetailedMerged$freebusy4800111Sunday-6023SundaySunday Monday Tuesday Wednesday Thursday Friday Saturday01439') +XML_KEYS = {'XML_SOAP_ENVELOPE' : 'http://schemas.xmlsoap.org/soap/envelope/', + 'XML_SCHEMA' : 'http://schemas.microsoft.com/exchange/services/2006', + 'XML_W3_SCHEMA' : 'http://www.w3.org/2001/XMLSchema', + } -ROOM_RESERVE_XML = string.Template('NoError') +ROOM_INFO_XML = string.Template('$room_name$room_name@example.comSMTPMailbox$room_name (12)ROOMActiveDirectory$room_name') + +FIND_ROOMS_XML = string.Template('Multiple results were found.ErrorNameResolutionMultipleResults0$room_info'.format(**XML_KEYS)) + +ROOM_AVAIL_XML = string.Template('NoErrorDetailedMerged$freebusy4800111Sunday-6023SundaySunday Monday Tuesday Wednesday Thursday Friday Saturday01439'.format(**XML_KEYS)) + +ROOM_RESERVE_XML = string.Template('NoError'.format(**XML_KEYS)) ROOM_EMAIL = 'ROOM@example.com' ROOM_NAME = 'ROOM' -START_TIME = "2016-07-02T11:00:00" -END_TIME = "2016-07-02T11:00:00" +START_TIME = "2016-11-09T11:00:00" +END_TIME = "2016-11-09T11:30:00" TIME_ZONE_OFFSET = "480" - -def get_popen_mock_room_avail(mock_popen, freebusy): - process_mock = mock.Mock() - attrs = {'communicate.return_value': (ROOM_AVAIL_XML.substitute(freebusy=freebusy), '')} - process_mock.configure_mock(**attrs) - mock_popen.return_value = process_mock - @nose.tools.raises(Exception) @mock.patch('exchange_api.subprocess.Popen') def test_auth_failure(mock_popen): get_popen_mock_room_avail(mock_popen, '') api = exchange_api.ExchangeApi(user="testuser", password="testpassword") - api.find_rooms(prefix="BLAH") + api.find_rooms(prefix="ROOM") assert False +def get_popen_mock_find_rooms(mock_popen, room_info): + process_mock = mock.Mock() + attrs = {'communicate.return_value': + (FIND_ROOMS_XML.substitute(num_rooms=len(room_info), + room_info=''.join(room_info)), + '')} + process_mock.configure_mock(**attrs) + mock_popen.return_value = process_mock + +@mock.patch('exchange_api.subprocess.Popen') +def test_find_rooms(mock_popen): + ROOM1 = ROOM_INFO_XML.substitute(room_name='ROOM1') + ROOM2 = ROOM_INFO_XML.substitute(room_name='ROOM2') + ROOM3 = ROOM_INFO_XML.substitute(room_name='ROOM3') + + get_popen_mock_find_rooms(mock_popen, room_info=[ROOM1, ROOM2, ROOM3]) + api = exchange_api.ExchangeApi(user="testuser", password="testpassword") + response = api.find_rooms(prefix="ROOM") + assert len(response) == 3 + assert response.get('ROOM1@example.com') + assert response.get('ROOM2@example.com') + assert response.get('ROOM3@example.com') + assert response.get('ROOM4@example.com') is None + +def get_popen_mock_room_avail(mock_popen, freebusy): + process_mock = mock.Mock() + attrs = {'communicate.return_value': (ROOM_AVAIL_XML.substitute(freebusy=freebusy), '')} + process_mock.configure_mock(**attrs) + mock_popen.return_value = process_mock + @mock.patch('exchange_api.subprocess.Popen') def test_room_available(mock_popen): FREEBUSY = '0000' @@ -60,12 +92,11 @@ def test_room_unavailable(mock_popen): assert response['freebusy'] == FREEBUSY assert response['email'] == ROOM_EMAIL - def get_popen_mock_room_reserve(mock_popen, result): process_mock = mock.Mock() attrs = {'communicate.return_value': (ROOM_RESERVE_XML.substitute(result=result), '')} process_mock.configure_mock(**attrs) - mock_popen.return_value = process_mock + mock_popen.return_value = process_mock @mock.patch('exchange_api.subprocess.Popen') def test_room_reserve_success(mock_popen): From 32bb8a412f15f2bf34e3d156e6da074e2762b731 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Sun, 4 Sep 2016 11:51:01 -0700 Subject: [PATCH 079/146] sgersapp: Min room size is 1 Change-Id: I987c9de3b41ac8b16ade6e0ef909484b7004d287 --- exchange_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchange_api.py b/exchange_api.py index d7a68a0..b06fef7 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -100,7 +100,7 @@ def _parse_room_size(self, roomname): try: return int(roomname[roomname.find('(') + 1 : roomname.find(')')]) except ValueError: - return 0 + return 1 def find_rooms(self, prefix): """ Search for rooms with names starting with specified prefix """ From f7f5c94596b5276ebe9bcac567796e9d54c2f32e Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Sun, 4 Sep 2016 17:41:58 -0700 Subject: [PATCH 080/146] sgersapp: Fix pylint issues Change-Id: I6daa3844b0ef28191376c647730c1f51b4262b57 --- test_exchange_api.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/test_exchange_api.py b/test_exchange_api.py index 290a186..dd3be3b 100644 --- a/test_exchange_api.py +++ b/test_exchange_api.py @@ -1,7 +1,12 @@ +""" +Unit Tests for exchange_api.py +""" + +import string + import exchange_api import mock import nose -import string XML_KEYS = {'XML_SOAP_ENVELOPE' : 'http://schemas.xmlsoap.org/soap/envelope/', 'XML_SCHEMA' : 'http://schemas.microsoft.com/exchange/services/2006', @@ -41,11 +46,11 @@ def get_popen_mock_find_rooms(mock_popen, room_info): @mock.patch('exchange_api.subprocess.Popen') def test_find_rooms(mock_popen): - ROOM1 = ROOM_INFO_XML.substitute(room_name='ROOM1') - ROOM2 = ROOM_INFO_XML.substitute(room_name='ROOM2') - ROOM3 = ROOM_INFO_XML.substitute(room_name='ROOM3') + room1 = ROOM_INFO_XML.substitute(room_name='ROOM1') + room2 = ROOM_INFO_XML.substitute(room_name='ROOM2') + room3 = ROOM_INFO_XML.substitute(room_name='ROOM3') - get_popen_mock_find_rooms(mock_popen, room_info=[ROOM1, ROOM2, ROOM3]) + get_popen_mock_find_rooms(mock_popen, room_info=[room1, room2, room3]) api = exchange_api.ExchangeApi(user="testuser", password="testpassword") response = api.find_rooms(prefix="ROOM") assert len(response) == 3 @@ -62,8 +67,8 @@ def get_popen_mock_room_avail(mock_popen, freebusy): @mock.patch('exchange_api.subprocess.Popen') def test_room_available(mock_popen): - FREEBUSY = '0000' - get_popen_mock_room_avail(mock_popen, FREEBUSY) + freebusy = '0000' + get_popen_mock_room_avail(mock_popen, freebusy=freebusy) api = exchange_api.ExchangeApi(user="testuser", password="testpassword") response = api.room_status(room_email=ROOM_EMAIL, @@ -73,13 +78,13 @@ def test_room_available(mock_popen): # Keys:'freebusy', 'status', 'email' assert response['status'] == 'Free' - assert response['freebusy'] == FREEBUSY + assert response['freebusy'] == freebusy assert response['email'] == ROOM_EMAIL @mock.patch('exchange_api.subprocess.Popen') def test_room_unavailable(mock_popen): - FREEBUSY = '0022' - get_popen_mock_room_avail(mock_popen, FREEBUSY) + freebusy = '0022' + get_popen_mock_room_avail(mock_popen, freebusy=freebusy) api = exchange_api.ExchangeApi(user="testuser", password="testpassword") response = api.room_status(room_email=ROOM_EMAIL, @@ -89,7 +94,7 @@ def test_room_unavailable(mock_popen): # Keys:'freebusy', 'status', 'email' assert response['status'] != 'Free' - assert response['freebusy'] == FREEBUSY + assert response['freebusy'] == freebusy assert response['email'] == ROOM_EMAIL def get_popen_mock_room_reserve(mock_popen, result): @@ -100,8 +105,8 @@ def get_popen_mock_room_reserve(mock_popen, result): @mock.patch('exchange_api.subprocess.Popen') def test_room_reserve_success(mock_popen): - RESULT = 'Success' - get_popen_mock_room_reserve(mock_popen, RESULT) + result = 'Success' + get_popen_mock_room_reserve(mock_popen, result) api = exchange_api.ExchangeApi(user="testuser", password="testpassword") response = api.reserve_room(room_email=ROOM_EMAIL, @@ -114,8 +119,8 @@ def test_room_reserve_success(mock_popen): @mock.patch('exchange_api.subprocess.Popen') def test_room_reserve_failure(mock_popen): - RESULT = 'Failure' - get_popen_mock_room_reserve(mock_popen, RESULT) + result = 'Failure' + get_popen_mock_room_reserve(mock_popen, result) api = exchange_api.ExchangeApi(user="testuser", password="testpassword") response = api.reserve_room(room_email=ROOM_EMAIL, @@ -125,6 +130,3 @@ def test_room_reserve_failure(mock_popen): timezone_offset=TIME_ZONE_OFFSET) assert response is False - - - From bfb542b857fcda669689663b614f3076d8352dab Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 6 Sep 2016 16:57:19 -0700 Subject: [PATCH 081/146] sgersapp: Move file handling functionality to common.py Change-Id: Id35f92718a3e9b9362c41416ecd904544a3c94fd --- common.py | 23 ++++++++++++++++++++++- find_available_room.py | 13 +------------ find_rooms.py | 17 ++--------------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/common.py b/common.py index fc9f30f..54eeefb 100644 --- a/common.py +++ b/common.py @@ -1,9 +1,10 @@ """ Common declarations and functions """ - +import csv import datetime import logging +import operator import os HTTPS_ENABLED = True @@ -48,3 +49,23 @@ def end_time(start_time, duration): start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") return (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() + +def read_room_list(filename): + rooms = {} + + try: + with open(filename, 'r') as fhandle: + reader = csv.reader(fhandle) + for room_name, room_email, room_size in reader: + rooms[room_email] = room_name, int(room_size) + except IOError as exception: + LOGGER.warning("Error openeing %s: %s", filename, str(exception)) + + return rooms + +def write_room_list(filename, rooms): + with open(filename, "wb") as fhandle: + writer = csv.writer(fhandle) + for email, room_info in sorted(rooms.iteritems(), key=operator.itemgetter(1)): + name, size = room_info + writer.writerow([name, email, size]) diff --git a/find_available_room.py b/find_available_room.py index faf0269..d886974 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -6,7 +6,6 @@ import argparse import base64 -import csv import getpass import sys import threading @@ -28,7 +27,7 @@ class AvailRoomFinder(object): def __init__(self, user, password, start_time=common.TIME_NOW, duration='1h', filename='rooms.csv', timezone=common.SJ_TIME_ZONE): - self.rooms = self._read_room_list(filename) + self.rooms = common.read_room_list(filename) self.user = user self.start_time = start_time self.room_info = {} @@ -37,16 +36,6 @@ def __init__(self, user, password, self.exchange_api = ExchangeApi(user, base64.b64decode(urllib.unquote(password))) self.end_time = common.end_time(self.start_time, duration) - def _read_room_list(self, filename): - rooms = {} - - with open(filename, 'r') as fhandle: - reader = csv.reader(fhandle) - for row in reader: - rooms[unicode(row[1])] = (unicode(row[0]), int(row[2])) - - return rooms - def search_free(self, prefix, min_size=1): """ Look for available rooms from the list of selected rooms """ selected_rooms = {} diff --git a/find_rooms.py b/find_rooms.py index 39783ec..58de91d 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -6,9 +6,7 @@ import argparse -import csv import getpass -import operator import string import sys @@ -27,13 +25,7 @@ def __init__(self, user, password, filename='rooms.csv', append=True): self.exchange_api = exchange_api.ExchangeApi(self.user, password) self.rooms = {} if append: - self._load() - - def _load(self): - with open(self.filename, 'r') as fhandle: - reader = csv.reader(fhandle) - for row in reader: - self.rooms[row[1]] = row[0], int(row[2]) + common.read_room_list(self.filename) def _search(self, prefix): return self.exchange_api.find_rooms(prefix=prefix) @@ -57,12 +49,7 @@ def dump(self): common.LOGGER.warning("No results found, check your arguments for mistakes") return 0 - with open(self.filename, "wb") as fhandle: - writer = csv.writer(fhandle) - for email, room_info in sorted(self.rooms.iteritems(), key=operator.itemgetter(1)): - name, size = room_info - writer.writerow([name, email, size]) - + common.write_room_list(self.filename, self.rooms) return len(self.rooms) def run(): From 487e2d9f22649d7964d5d02e20cc140066ff39f3 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 6 Sep 2016 17:43:59 -0700 Subject: [PATCH 082/146] sgersapp: Refer to the global in common.py Change-Id: Ia7412b5a34e081cedc7a38641ab161f76314f3e6 --- find_available_room.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index d886974..2ebcfd2 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -26,7 +26,7 @@ class AvailRoomFinder(object): def __init__(self, user, password, start_time=common.TIME_NOW, duration='1h', - filename='rooms.csv', timezone=common.SJ_TIME_ZONE): + filename=common.ROOMS_CSV, timezone=common.SJ_TIME_ZONE): self.rooms = common.read_room_list(filename) self.user = user self.start_time = start_time @@ -115,8 +115,8 @@ def run(): help="Duration e.g. 1h or 15m (default = 1h)", default='1h') parser.add_argument("-f", "--file", - help="csv filename with room info (default=rooms.csv).", - default="rooms.csv") + help="csv filename with room info (default={}).".format(common.ROOMS_CSV), + default=common.ROOMS_CSV) args = parser.parse_args() args.password = base64.b64encode(getpass.getpass("Password:")) From 79cf778d3d9e30a022e423b5ad9cb093e5d2581e Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 6 Sep 2016 17:49:49 -0700 Subject: [PATCH 083/146] sgersapp: Minor fixes Change-Id: I2dad1b2a9ba7effaaa8bc4fa9edb8aa5c50b4df1 --- common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common.py b/common.py index 54eeefb..d2c2020 100644 --- a/common.py +++ b/common.py @@ -50,7 +50,7 @@ def end_time(start_time, duration): start = datetime.datetime.strptime(start_time, "%Y-%m-%dT%H:%M:%S") return (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() -def read_room_list(filename): +def read_room_list(filename=ROOMS_CSV): rooms = {} try: @@ -59,7 +59,7 @@ def read_room_list(filename): for room_name, room_email, room_size in reader: rooms[room_email] = room_name, int(room_size) except IOError as exception: - LOGGER.warning("Error openeing %s: %s", filename, str(exception)) + LOGGER.warning("Error opening %s: %s", filename, str(exception)) return rooms From daf69eb1a48e0d0f746cc9a0dd6a50665b6875a5 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 6 Sep 2016 17:51:14 -0700 Subject: [PATCH 084/146] sgersapp: Fetch floors when building is selected Change-Id: I45dede143a79715038be341f28477c5cd820ab4a --- service/static/js/main.js | 18 +++++++++++++++--- service/templates/index.html | 2 +- service/webserver.py | 20 +++++++++++++++----- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index f8aea8c..50cd96e 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -43,6 +43,7 @@ function init(){ function createCombo(container, data) { var options = ''; + container.options.length = 0; for (var i = 0; i < data.length; i++) { container.options.add(new Option(data[i], data[i])); } @@ -50,11 +51,22 @@ function createCombo(container, data) { function loadBuildingNamesList() { var xmlHttp = new XMLHttpRequest(); - xmlHttp.open( "GET", "/showbuldings", false ); // false for synchronous request - xmlHttp.send( null ); + xmlHttp.open("GET", "/showbuldings", false); + xmlHttp.send(null); buildings = JSON.parse(xmlHttp.responseText); } +function loadFloorList(buildingname) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "/showfloors?buildingname=" + buildingname, false); + xmlHttp.send(null); + floors = JSON.parse(xmlHttp.responseText); + createCombo(floorSelect, floors); +} + +//Example: http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** + + //Example: http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** function submitClickHandler() { @@ -62,7 +74,7 @@ function submitClickHandler() { var rowCount = mytable.rows.length; for (var i = tableHeaderRowCount; i < rowCount; i++) { mytable.deleteRow(tableHeaderRowCount); - console.log("clearing row number:"+i); + console.log("clearing row number:" + i); } mytable.innerHTML = ""; mytable.visiblity = false; diff --git a/service/templates/index.html b/service/templates/index.html index 478059d..74311d2 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -93,7 +93,7 @@


- +


diff --git a/service/webserver.py b/service/webserver.py index dcf283a..32c158a 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -26,13 +26,24 @@ def index(): @APP.route('/showbuldings', methods=['GET']) def show_buldings(): """ Serve list of buildings in JSON """ - buldings = [] + buldings = set() with open(common.ROOMS_CSV, 'r') as fhandle: for line in fhandle.readlines(): buldingname = line.split('-')[0] - if buldingname not in buldings: - buldings.append(buldingname) - return json.dumps(buldings) + buldings.add(buldingname) + return json.dumps(sorted(buldings)) + +@APP.route('/showfloors', methods=['GET']) +def show_floors(): + """ Serve list of buildings in JSON """ + floors = set() + with open(common.ROOMS_CSV, 'r') as fhandle: + for line in fhandle.readlines(): + buildingname = flask.request.args.get('buildingname') + if line.startswith(buildingname): + floors.add(line.split('-')[1]) + return json.dumps(sorted(floors)) + # Example Query @@ -56,7 +67,6 @@ def show_rooms(): password=queryparam.password, start_time=queryparam.starttime, duration=queryparam.duration, - filename=common.ROOMS_CSV, timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) except Exception as exception: From 606305670c7bcad70d56fbf3e1d2b3865f056ca1 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 6 Sep 2016 21:40:51 -0700 Subject: [PATCH 085/146] sgersapp: Fix dropdown options Change-Id: I0f25b961d8ace0e4a8b81adbc02beb94155b74d5 --- service/static/js/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index 50cd96e..6d01e92 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -1,5 +1,5 @@ var buildings = [] -var floors = ["1", "2", "3", "4", "5"]; +var floors = ["1", "2", "3", "4", "5", "Any"]; var times_hours = ["00", "01", "02","03", "4", "05", "06", "07", "08", "09", "10", "11", @@ -10,7 +10,7 @@ var times_mins = ["00", "15", "30","45"]; var duration_hours = ["0h", "1h", "2h","3h", "4h", "5h", "6h", "7h"]; var duration_mins = ["00m", "15m", "30m","45m"]; -var sizes = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "20", "23", "24", "25", "30", "36", "50", "60", "65", "68", "70", "100", "120", "410"] +var sizes = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "15", "20", "25", "30", "50", "70", "100"]; var date_years = ["2016", "2017"]; var date_months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; var date_days = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", From 8e701b7e353144efcb5671df6ef526f89d74cc04 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 6 Sep 2016 21:41:25 -0700 Subject: [PATCH 086/146] sgersapp: Add support for choosing any floor in a building Change-Id: Ib1140611e45c3209e9810fad6e5b9d7b5d9c5c02 --- service/webserver.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/service/webserver.py b/service/webserver.py index 32c158a..7536180 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -42,9 +42,11 @@ def show_floors(): buildingname = flask.request.args.get('buildingname') if line.startswith(buildingname): floors.add(line.split('-')[1]) - return json.dumps(sorted(floors)) - + if len(floors) > 1: + return json.dumps(sorted(floors) + ["Any"]) + else: + return json.dumps(list(floors)) # Example Query # http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password @@ -60,7 +62,10 @@ def show_rooms(): attendees=flask.request.args.get('attendees'), timezone=flask.request.args.get('timezone')) - prefix = queryparam.buildingname + '-' + queryparam.floor + if queryparam.floor.startswith("Any"): + prefix = queryparam.buildingname + else: + prefix = queryparam.buildingname + '-' + queryparam.floor try: room_finder = AvailRoomFinder(user=queryparam.user, From b73acec2e1518634ae6f5ae1cdc80231c11282eb Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 6 Sep 2016 21:59:21 -0700 Subject: [PATCH 087/146] sgersapp: Re-align inputs Change-Id: I837399a49ce09d7cda1399394c963db32d143e6f --- service/templates/index.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/service/templates/index.html b/service/templates/index.html index 74311d2..11492a5 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -86,17 +86,16 @@
- + +
-
-
+ +

-

+ From d1c87ff89e229ccdc63f03e95a77bed984da38d3 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 7 Sep 2016 11:45:44 -0700 Subject: [PATCH 088/146] sgersapp: Add caching; move building and floor parsing code to common Change-Id: Ib433444fc4c1435e32304d7adfa21e1ffdb9d146 --- common.py | 57 +++++++++++++++++++++++++++++++++++++++++++- find_rooms.py | 2 +- service/webserver.py | 22 ++++------------- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/common.py b/common.py index d2c2020..407f49e 100644 --- a/common.py +++ b/common.py @@ -18,6 +18,11 @@ CERT_DIR = os.path.join(PWD, 'certdir') TEMPLATE_FOLDER = os.path.join(SERVICE_DIR, 'templates') +ROOMS_CACHE = None +ROOMNAMES_CACHE = None +BUILDINGS_CACHE = None +FLOORS_CACHE = {} + TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() SJ_TIME_ZONE = "420" @@ -51,6 +56,10 @@ def end_time(start_time, duration): return (start + datetime.timedelta(hours=hours, minutes=mins)).isoformat() def read_room_list(filename=ROOMS_CSV): + global ROOMS_CACHE + + if ROOMS_CACHE is not None: + return ROOMS_CACHE rooms = {} try: @@ -61,9 +70,55 @@ def read_room_list(filename=ROOMS_CSV): except IOError as exception: LOGGER.warning("Error opening %s: %s", filename, str(exception)) + ROOMS_CACHE = rooms return rooms -def write_room_list(filename, rooms): +def get_roomname_list(filename=ROOMS_CSV): + global ROOMNAMES_CACHE + + if ROOMNAMES_CACHE is not None: + return ROOMNAMES_CACHE + + rooms = read_room_list(filename=filename) + roomnames = [] + + for roominfo in rooms.values(): + roomnames.append(roominfo[0]) + + ROOMNAMES_CACHE = sorted(roomnames) + return ROOMNAMES_CACHE + +def get_building_list(filename=ROOMS_CSV): + global BUILDINGS_CACHE + + if BUILDINGS_CACHE is not None: + return BUILDINGS_CACHE + + roomnames = get_roomname_list(filename=filename) + buildings = set() + for roomname in roomnames: + buildings.add(roomname.split('-')[0]) + + BUILDINGS_CACHE = sorted(buildings) + return BUILDINGS_CACHE + +def get_floor_list(buildingname, filename=ROOMS_CSV): + if buildingname in FLOORS_CACHE: + return FLOORS_CACHE[buildingname] + + roomnames = get_roomname_list(filename=filename) + floors = set() + for roomname in roomnames: + if roomname.startswith(buildingname): + floors.add(roomname.split('-')[1]) + + FLOORS_CACHE[buildingname] = sorted(floors) + return FLOORS_CACHE[buildingname] + +def write_room_list(rooms, filename=ROOMS_CSV): + global ROOMS_CACHE + ROOMS_CACHE = rooms + with open(filename, "wb") as fhandle: writer = csv.writer(fhandle) for email, room_info in sorted(rooms.iteritems(), key=operator.itemgetter(1)): diff --git a/find_rooms.py b/find_rooms.py index 58de91d..399a7d8 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -49,7 +49,7 @@ def dump(self): common.LOGGER.warning("No results found, check your arguments for mistakes") return 0 - common.write_room_list(self.filename, self.rooms) + common.write_room_list(self.rooms, filename=self.filename) return len(self.rooms) def run(): diff --git a/service/webserver.py b/service/webserver.py index 7536180..bd74b5d 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -26,27 +26,15 @@ def index(): @APP.route('/showbuldings', methods=['GET']) def show_buldings(): """ Serve list of buildings in JSON """ - buldings = set() - with open(common.ROOMS_CSV, 'r') as fhandle: - for line in fhandle.readlines(): - buldingname = line.split('-')[0] - buldings.add(buldingname) - return json.dumps(sorted(buldings)) + buildings = common.get_building_list() + return json.dumps(buildings) @APP.route('/showfloors', methods=['GET']) def show_floors(): """ Serve list of buildings in JSON """ - floors = set() - with open(common.ROOMS_CSV, 'r') as fhandle: - for line in fhandle.readlines(): - buildingname = flask.request.args.get('buildingname') - if line.startswith(buildingname): - floors.add(line.split('-')[1]) - - if len(floors) > 1: - return json.dumps(sorted(floors) + ["Any"]) - else: - return json.dumps(list(floors)) + buildingname = flask.request.args.get('buildingname') + floors = common.get_floor_list(buildingname) + return json.dumps(floors + ["Any"]) # Example Query # http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password From e2ef4245e68816185fb01feae6d899b0490a8f5b Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 7 Sep 2016 12:25:20 -0700 Subject: [PATCH 089/146] sgersapp: Fix append Change-Id: I90080125df7a2feb6f2e3da740f5578dffdef32e --- find_rooms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/find_rooms.py b/find_rooms.py index 399a7d8..9fd780c 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -25,7 +25,7 @@ def __init__(self, user, password, filename='rooms.csv', append=True): self.exchange_api = exchange_api.ExchangeApi(self.user, password) self.rooms = {} if append: - common.read_room_list(self.filename) + self.rooms = common.read_room_list(self.filename) def _search(self, prefix): return self.exchange_api.find_rooms(prefix=prefix) From e819bed876e6db2614950dcb8a31e4be6e7748cc Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 7 Sep 2016 12:26:40 -0700 Subject: [PATCH 090/146] sgersapp: Parse city and country Change-Id: I7d1e998faa169d3b20fc54254e2f83cb3a42948b --- exchange_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exchange_api.py b/exchange_api.py index b06fef7..50574eb 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -117,6 +117,8 @@ def find_rooms(self, prefix): for elem in elems: email = elem.findall(SCHEME_TYPES + "EmailAddress") name = elem.findall(SCHEME_TYPES + "DisplayName") + city = elem.findall(SCHEME_TYPES + "City") + country = elem.findall(SCHEME_TYPES + "CountryOrRegion") if len(email) and len(name): roomsize = self._parse_room_size(name[0].text) if roomsize: From 676b796e4131df8addf343802f2fad7335e02a29 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 7 Sep 2016 14:05:55 -0700 Subject: [PATCH 091/146] sgersapp: Add city and country information for each room Change-Id: I558b4b9a63b8bf939168b9e2349dce77b2f84c06 --- common.py | 26 +++++++++++++++----------- exchange_api.py | 26 +++++++++++++++++++------- find_available_room.py | 31 ++++++++++++++++--------------- test_exchange_api.py | 6 +----- 4 files changed, 51 insertions(+), 38 deletions(-) diff --git a/common.py b/common.py index 407f49e..1a06fd4 100644 --- a/common.py +++ b/common.py @@ -65,8 +65,13 @@ def read_room_list(filename=ROOMS_CSV): try: with open(filename, 'r') as fhandle: reader = csv.reader(fhandle) - for room_name, room_email, room_size in reader: - rooms[room_email] = room_name, int(room_size) + for room_name, room_email, room_size, city, country in reader: + rooms[room_name] = {"name" : room_name, + "size" : int(room_size), + "email" : room_email, + "city" : city, + "country" : country, + } except IOError as exception: LOGGER.warning("Error opening %s: %s", filename, str(exception)) @@ -80,12 +85,7 @@ def get_roomname_list(filename=ROOMS_CSV): return ROOMNAMES_CACHE rooms = read_room_list(filename=filename) - roomnames = [] - - for roominfo in rooms.values(): - roomnames.append(roominfo[0]) - - ROOMNAMES_CACHE = sorted(roomnames) + ROOMNAMES_CACHE = sorted(rooms) return ROOMNAMES_CACHE def get_building_list(filename=ROOMS_CSV): @@ -121,6 +121,10 @@ def write_room_list(rooms, filename=ROOMS_CSV): with open(filename, "wb") as fhandle: writer = csv.writer(fhandle) - for email, room_info in sorted(rooms.iteritems(), key=operator.itemgetter(1)): - name, size = room_info - writer.writerow([name, email, size]) + for name in sorted(rooms): + room_info = rooms[name] + email = room_info["email"] + size = room_info["size"] + city = room_info["city"] + country = room_info["country"] + writer.writerow([name, email, size, city, country]) diff --git a/exchange_api.py b/exchange_api.py index 50574eb..f969a48 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -102,6 +102,13 @@ def _parse_room_size(self, roomname): except ValueError: return 1 + def _polish(self, elem_list): + if len(elem_list) > 0 and elem_list[0] is not None: + return elem_list[0].text + else: + return "" + + def find_rooms(self, prefix): """ Search for rooms with names starting with specified prefix """ global FIND_XML @@ -115,12 +122,17 @@ def find_rooms(self, prefix): tree = ET.fromstring(response) elems = tree.findall(SCHEME_TYPES + "Resolution") for elem in elems: - email = elem.findall(SCHEME_TYPES + "EmailAddress") - name = elem.findall(SCHEME_TYPES + "DisplayName") - city = elem.findall(SCHEME_TYPES + "City") - country = elem.findall(SCHEME_TYPES + "CountryOrRegion") - if len(email) and len(name): - roomsize = self._parse_room_size(name[0].text) + email = self._polish(elem.findall(SCHEME_TYPES + "EmailAddress")) + name = self._polish(elem.findall(SCHEME_TYPES + "DisplayName")) + city = self._polish(elem.findall(SCHEME_TYPES + "City")) + country = self._polish(elem.findall(SCHEME_TYPES + "CountryOrRegion")) + if len(name) > 0 and len(email) > 0: + roomsize = self._parse_room_size(name) if roomsize: - room_info[email[0].text] = (name[0].text, roomsize) + room_info[name] = {"name" : name, + "size" : roomsize, + "email" : email, + "city" : city.title(), + "country" : country.title(), + } return room_info diff --git a/find_available_room.py b/find_available_room.py index 2ebcfd2..2d49639 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -39,21 +39,22 @@ def __init__(self, user, password, def search_free(self, prefix, min_size=1): """ Look for available rooms from the list of selected rooms """ selected_rooms = {} - for email in self.rooms: - name, size = self.rooms[email] - if name.startswith(prefix) and size >= min_size: - selected_rooms[email] = (name, size) + for roomname, room_info in self.rooms.iteritems(): + size = room_info["size"] + if roomname.startswith(prefix) and size >= min_size: + selected_rooms[roomname] = room_info selected_room_info = self.search(selected_rooms) free_room_info = {} - for email in selected_room_info: - if selected_room_info[email]['status'] == 'Free': - free_room_info[email] = selected_room_info[email] + for roomname, roominfo in selected_room_info.iteritems(): + if roominfo['status'] == 'Free': + free_room_info[roomname] = selected_room_info[roomname] return free_room_info - def _query(self, email): - room_name, room_size = self.rooms[email] - common.LOGGER.debug("Querying for %s", room_name) + def _query(self, roomname): + room_size = self.rooms[roomname]["size"] + email = self.rooms[roomname]["email"] + common.LOGGER.debug("Querying for %s", roomname) try: room_info = self.exchange_api.room_status( \ @@ -63,12 +64,12 @@ def _query(self, email): timezone_offset=self.timezone) room_info['size'] = room_size - room_info['name'] = room_name - self.room_info[room_name] = room_info + room_info['name'] = roomname + self.room_info[roomname] = room_info except Exception as exception: self.error = exception - common.LOGGER.warning("Exception querying room %s: %s", room_name, str(exception)) + common.LOGGER.warning("Exception querying room %s: %s", roomname, str(exception)) def search(self, selected_rooms=None): """ Lookup availability status of rooms from the list of selected rooms """ @@ -79,8 +80,8 @@ def search(self, selected_rooms=None): common.LOGGER.info("User %s searching for a room from %s to %s", self.user, self.start_time, self.end_time) - for email in selected_rooms: - thread = threading.Thread(target=self._query, args=(email, )) + for roomname in selected_rooms: + thread = threading.Thread(target=self._query, args=(roomname, )) thread.start() worker_threads.append(thread) diff --git a/test_exchange_api.py b/test_exchange_api.py index dd3be3b..18eeaad 100644 --- a/test_exchange_api.py +++ b/test_exchange_api.py @@ -13,7 +13,7 @@ 'XML_W3_SCHEMA' : 'http://www.w3.org/2001/XMLSchema', } -ROOM_INFO_XML = string.Template('$room_name$room_name@example.comSMTPMailbox$room_name (12)ROOMActiveDirectory$room_name') +ROOM_INFO_XML = string.Template('$room_name$room_name@example.comSMTPMailbox$room_name (12)ROOM0 Street St.Los GatosCaliforniaUnited StatesActiveDirectory$room_name') FIND_ROOMS_XML = string.Template('Multiple results were found.ErrorNameResolutionMultipleResults0$room_info'.format(**XML_KEYS)) @@ -54,10 +54,6 @@ def test_find_rooms(mock_popen): api = exchange_api.ExchangeApi(user="testuser", password="testpassword") response = api.find_rooms(prefix="ROOM") assert len(response) == 3 - assert response.get('ROOM1@example.com') - assert response.get('ROOM2@example.com') - assert response.get('ROOM3@example.com') - assert response.get('ROOM4@example.com') is None def get_popen_mock_room_avail(mock_popen, freebusy): process_mock = mock.Mock() From 3a46444e070dd1e4d933f134deca9acc541f365c Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 7 Sep 2016 14:43:22 -0700 Subject: [PATCH 092/146] sgersapp: UI changes for users to pick city Change-Id: I88ef1b945a1b11521f9bf8e9a77c04b40d607e57 --- common.py | 34 +++++++++++++++++++++++++--------- service/static/js/main.js | 24 +++++++++++++++++++----- service/templates/index.html | 1 + service/webserver.py | 20 +++++++++++++++----- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/common.py b/common.py index 1a06fd4..64398ee 100644 --- a/common.py +++ b/common.py @@ -20,7 +20,8 @@ ROOMS_CACHE = None ROOMNAMES_CACHE = None -BUILDINGS_CACHE = None +CITIES_CACHE = None +BUILDINGS_CACHE = {} FLOORS_CACHE = {} TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() @@ -88,19 +89,20 @@ def get_roomname_list(filename=ROOMS_CSV): ROOMNAMES_CACHE = sorted(rooms) return ROOMNAMES_CACHE -def get_building_list(filename=ROOMS_CSV): +def get_building_list(city, filename=ROOMS_CSV): global BUILDINGS_CACHE - if BUILDINGS_CACHE is not None: - return BUILDINGS_CACHE + if city in BUILDINGS_CACHE: + return BUILDINGS_CACHE[city] - roomnames = get_roomname_list(filename=filename) + rooms = read_room_list(filename=filename) buildings = set() - for roomname in roomnames: - buildings.add(roomname.split('-')[0]) + for roomname, roominfo in rooms.iteritems(): + if roominfo["city"] == city: + buildings.add(roomname.split('-')[0]) - BUILDINGS_CACHE = sorted(buildings) - return BUILDINGS_CACHE + BUILDINGS_CACHE[city] = sorted(buildings) + return BUILDINGS_CACHE[city] def get_floor_list(buildingname, filename=ROOMS_CSV): if buildingname in FLOORS_CACHE: @@ -115,6 +117,20 @@ def get_floor_list(buildingname, filename=ROOMS_CSV): FLOORS_CACHE[buildingname] = sorted(floors) return FLOORS_CACHE[buildingname] +def get_city_list(filename=ROOMS_CSV): + global CITIES_CACHE + + if CITIES_CACHE is not None: + return CITIES_CACHE + + rooms = read_room_list(filename=filename) + cities = set() + for roominfo in rooms.itervalues(): + cities.add(roominfo["city"]) + + CITIES_CACHE = sorted(cities) + return CITIES_CACHE + def write_room_list(rooms, filename=ROOMS_CSV): global ROOMS_CACHE ROOMS_CACHE = rooms diff --git a/service/static/js/main.js b/service/static/js/main.js index 6d01e92..032a338 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -1,4 +1,5 @@ var buildings = [] +var cities = [] var floors = ["1", "2", "3", "4", "5", "Any"]; var times_hours = ["00", "01", "02","03", "4", "05", "06", "07", @@ -19,16 +20,21 @@ var date_days = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "31"]; function init(){ - loadBuildingNamesList(); + loadCitiesList(); createCombo(buildingSelect, buildings); + createCombo(citySelect, cities); createCombo(floorSelect, floors); createCombo(roomSizeSelect, sizes); createCombo(startTimeHourSelect, times_hours); createCombo(startTimeMinSelect, times_mins); createCombo(durationHourSelect, duration_hours); createCombo(durationMinSelect, duration_mins); + + citySelect.value = "San Jose"; + loadBuildingList(citySelect.value); buildingSelect.value = "SJC19"; - floorSelect.value="3"; + loadFloorList(buildingSelect.value); + floorSelect.value = "3"; durationMinSelect.value = "30m"; var date = new Date(); @@ -49,11 +55,11 @@ function createCombo(container, data) { } } -function loadBuildingNamesList() { +function loadCitiesList() { var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("GET", "/showbuldings", false); + xmlHttp.open("GET", "/showcities", false); xmlHttp.send(null); - buildings = JSON.parse(xmlHttp.responseText); + cities = JSON.parse(xmlHttp.responseText); } function loadFloorList(buildingname) { @@ -64,6 +70,14 @@ function loadFloorList(buildingname) { createCombo(floorSelect, floors); } +function loadBuildingList(city) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "/showbuildings?city=" + city, false); + xmlHttp.send(null); + buildings = JSON.parse(xmlHttp.responseText); + createCombo(buildingSelect, buildings); +} + //Example: http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** diff --git a/service/templates/index.html b/service/templates/index.html index 11492a5..7e68e9d 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -92,6 +92,7 @@

+
diff --git a/service/webserver.py b/service/webserver.py index bd74b5d..917e855 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -23,24 +23,34 @@ def index(): QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees, timezone') BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password, timezone') -@APP.route('/showbuldings', methods=['GET']) +@APP.route('/showcities', methods=['GET']) +def show_cities(): + """ Serve list of cities in JSON """ + cities = common.get_city_list() + return json.dumps(cities) + +@APP.route('/showbuildings', methods=['GET']) def show_buldings(): """ Serve list of buildings in JSON """ - buildings = common.get_building_list() + city = flask.request.args.get('city') + buildings = common.get_building_list(city) return json.dumps(buildings) @APP.route('/showfloors', methods=['GET']) def show_floors(): - """ Serve list of buildings in JSON """ + """ Serve list of floors in JSON """ buildingname = flask.request.args.get('buildingname') floors = common.get_floor_list(buildingname) - return json.dumps(floors + ["Any"]) + if len(floors) > 1: + return json.dumps(floors + ["Any"]) + else: + return json.dumps(floors) # Example Query # http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password @APP.route('/showrooms', methods=['GET']) def show_rooms(): - """ Serve list of buildings in JSON """ + """ Serve list of rooms in JSON """ queryparam = QueryParam(buildingname=flask.request.args.get('buildingname'), floor=flask.request.args.get('floor'), starttime=flask.request.args.get('starttime'), From 542dadbe67138c72f7926176db00d762c543b73f Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 7 Sep 2016 14:58:06 -0700 Subject: [PATCH 093/146] sgersapp: Ignore rooms with no city information Change-Id: Ic48bfb0b87fef062178a3d49087fd664575eaff8 --- exchange_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exchange_api.py b/exchange_api.py index f969a48..60a0809 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -103,10 +103,10 @@ def _parse_room_size(self, roomname): return 1 def _polish(self, elem_list): - if len(elem_list) > 0 and elem_list[0] is not None: - return elem_list[0].text - else: + if len(elem_list) == 0 or elem_list[0] is None or elem_list[0].text is None: return "" + else: + return elem_list[0].text def find_rooms(self, prefix): @@ -126,7 +126,7 @@ def find_rooms(self, prefix): name = self._polish(elem.findall(SCHEME_TYPES + "DisplayName")) city = self._polish(elem.findall(SCHEME_TYPES + "City")) country = self._polish(elem.findall(SCHEME_TYPES + "CountryOrRegion")) - if len(name) > 0 and len(email) > 0: + if len(name) > 0 and len(email) > 0 and len(city) > 0: roomsize = self._parse_room_size(name) if roomsize: room_info[name] = {"name" : name, From 07dc840349525949e2527d5e5c825df8b4b6d1ef Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 7 Sep 2016 15:00:20 -0700 Subject: [PATCH 094/146] sgersapp: Fix pylint warnings Change-Id: I271202868623aa87d1412c15e3bc7ebb55fbdd5c --- common.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/common.py b/common.py index 64398ee..83bfe27 100644 --- a/common.py +++ b/common.py @@ -4,7 +4,6 @@ import csv import datetime import logging -import operator import os HTTPS_ENABLED = True @@ -90,8 +89,6 @@ def get_roomname_list(filename=ROOMS_CSV): return ROOMNAMES_CACHE def get_building_list(city, filename=ROOMS_CSV): - global BUILDINGS_CACHE - if city in BUILDINGS_CACHE: return BUILDINGS_CACHE[city] From 6100c5a0e5be9e75f96d605cfac1474fc56350a1 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 7 Sep 2016 16:53:23 -0700 Subject: [PATCH 095/146] sgersapp: Fix typo Change-Id: Icdd78c984fd962030ff3dea80996fa7f3bc9f6f4 --- service/static/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index 032a338..412a9bd 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -2,7 +2,7 @@ var buildings = [] var cities = [] var floors = ["1", "2", "3", "4", "5", "Any"]; var times_hours = ["00", "01", "02","03", - "4", "05", "06", "07", + "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", From 4e116f299ec28731da74aebbce96fd7ddf2d723b Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 8 Sep 2016 16:40:26 -0700 Subject: [PATCH 096/146] sgersapp: Rename CONFIG to gunicorn.cfg Change-Id: Id09dc2d1c8a9b4d773336019b309babb484265e4 --- CONFIG => gunicorn.cfg | 0 run.sh | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename CONFIG => gunicorn.cfg (100%) diff --git a/CONFIG b/gunicorn.cfg similarity index 100% rename from CONFIG rename to gunicorn.cfg diff --git a/run.sh b/run.sh index 80b6189..3737203 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -PYTHONPATH=. gunicorn --config=CONFIG service.webserver:APP +PYTHONPATH=. gunicorn --config=gunicorn.cfg service.webserver:APP From 4a11cff64de4b1a07ae4f05143ba8add89654c83 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 8 Sep 2016 17:05:34 -0700 Subject: [PATCH 097/146] sgersapp: Sample exchange configuration Change-Id: Iaa1112f4d373c248c9198561087e95dc858cabd1 --- exchange.cfg | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 exchange.cfg diff --git a/exchange.cfg b/exchange.cfg new file mode 100644 index 0000000..875e944 --- /dev/null +++ b/exchange.cfg @@ -0,0 +1,5 @@ +[exchange] +domain = cisco.com +anon_user = roomfinder +anon_password = ******* + From 961eacaec6bb93583b0614d137aceefd613075f1 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 8 Sep 2016 17:07:43 -0700 Subject: [PATCH 098/146] sgersapp: Gunicorn configuration Change-Id: I0870e06c068c578b657fa0dafa4339799eec6bba --- gunicorn.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/gunicorn.cfg b/gunicorn.cfg index df07eca..b37f3db 100644 --- a/gunicorn.cfg +++ b/gunicorn.cfg @@ -1,6 +1,3 @@ - - - workers = 10 bind = "0.0.0.0:8443" certfile = "certdir/roomfinder.cert" From bc76ac994b311bebe9ea31646cfd78ce051357f6 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 8 Sep 2016 17:08:15 -0700 Subject: [PATCH 099/146] sgersapp: Support for anonymous access when searching Change-Id: I7d55ef3cadd48e3eebc9e5e7e34793aae5a2fc31 --- exchange_api.py | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/exchange_api.py b/exchange_api.py index 60a0809..f16290a 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -4,6 +4,7 @@ APIs to communicate with the Exchange Server """ +import ConfigParser import pipes import string import subprocess @@ -13,9 +14,9 @@ reload(sys) sys.setdefaultencoding("utf-8") -DOMAIN = 'cisco.com' -URL = 'https://mail.{}/ews/exchange.asmx'.format(DOMAIN) +URL = 'https://mail.{}/ews/exchange.asmx' SCHEME_TYPES = './/{http://schemas.microsoft.com/exchange/services/2006/types}' +CURL_COMMAND = "curl --silent --header 'content-type: text/xml;charset=utf-8' --data '{data}' --ntlm -u {user}:{password} {url}" AVAILABILITY_XML = None RESERVE_XML = None FIND_XML = None @@ -23,20 +24,36 @@ class ExchangeApi(object): """ Class to communicate with the Exchange Server """ - def __init__(self, user, password): + def __init__(self, user, password, cfg='exchange.cfg'): self.user = user + self.password = password + + config = ConfigParser.ConfigParser() + if config.read(cfg): + self.domain = config.get('exchange', 'domain') + self.anon_user = config.get('exchange', 'anon_user') + self.anon_password = config.get('exchange', 'anon_password') + else: + self.domain = 'example.com' + self.anon_user = self.user + self.anon_password = self.password + + self.url = URL.format(self.domain) self.command = 'curl --silent --header "content-type: text/xml;charset=utf-8"' \ - + " --data '{}'" \ + + " --data '{data}'" \ + " --ntlm " \ - + "-u " + pipes.quote(self.user) + ":" + pipes.quote(password) \ - + " " + URL + + "-u {user}:{password}" \ + + " {url}" def _read_template(self, filename): with open(filename, "r") as fhandle: return string.Template(fhandle.read()) - def _curl(self, post_data): - curl_command = self.command.format(post_data) + def _curl(self, post_data, user, password): + curl_command = CURL_COMMAND.format(data=post_data, + user=pipes.quote(user), + password=pipes.quote(password), + url=self.url) curl_process = subprocess.Popen(curl_command, stdout=subprocess.PIPE, shell=True) @@ -56,7 +73,10 @@ def room_status(self, room_email, start_time, end_time, timezone_offset): starttime=start_time, endtime=end_time)) - response = self._curl(data) + if len(self.user) and len(self.password): + response = self._curl(data, self.user, self.password) + else: + response = self._curl(data, self.anon_user, self.anon_password) tree = ET.fromstring(response) status = "Free" @@ -79,7 +99,7 @@ def reserve_room(self, room_email, room_name, start_time, end_time, timezone_off if RESERVE_XML is None: RESERVE_XML = self._read_template("reserve_resource_template.xml") - user_email = self.user + '@' + DOMAIN + user_email = self.user + '@' + self.domain meeting_body = '{0} booked via RoomFinder by {1}'.format(room_name, user_email) subject = 'RoomFinder: {0}'.format(room_name) @@ -93,7 +113,7 @@ def reserve_room(self, room_email, room_name, start_time, end_time, timezone_off timezone=timezone_offset, )) - response = self._curl(data) + response = self._curl(data, self.user, self.password) return 'Success' in response def _parse_room_size(self, roomname): @@ -117,7 +137,10 @@ def find_rooms(self, prefix): room_info = {} data = unicode(FIND_XML.substitute(name=prefix)) - response = self._curl(data) + if len(self.user) and len(self.password): + response = self._curl(data, self.user, self.password) + else: + response = self._curl(data, self.anon_user, self.anon_password) tree = ET.fromstring(response) elems = tree.findall(SCHEME_TYPES + "Resolution") From 77e92935e1c806dc18a995978899b8f98b396228 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Thu, 8 Sep 2016 21:06:09 -0700 Subject: [PATCH 100/146] sgersapp: Hide username/password fields when not required Change-Id: Ib51fc09b74801f37e6e93030ec61a7e7ecff0894 --- service/static/js/main.js | 16 +++++++++++++++- service/templates/index.html | 6 +++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/service/static/js/main.js b/service/static/js/main.js index 412a9bd..8232616 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -20,6 +20,7 @@ var date_days = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "31"]; function init(){ + hideUserPassword(); loadCitiesList(); createCombo(buildingSelect, buildings); createCombo(citySelect, cities); @@ -47,6 +48,13 @@ function init(){ document.getElementById("startTimeHourSelect").value = current_hour; } +function hideUserPassword() { + userLabel.style.visibility = "hidden"; + userNameInput.style.visibility = "hidden"; + passwordLabel.style.visibility = "hidden"; + passwordInput.style.visibility = "hidden"; +} + function createCombo(container, data) { var options = ''; container.options.length = 0; @@ -92,6 +100,7 @@ function submitClickHandler() { } mytable.innerHTML = ""; mytable.visiblity = false; + hideUserPassword(); var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); var timezone = new Date().getTimezoneOffset(); @@ -123,6 +132,11 @@ function showFreeRooms(rooms_json) { return; } + userLabel.style.visibility = "visible"; + userNameInput.style.visibility = "visible"; + passwordLabel.style.visibility = "visible"; + passwordInput.style.visibility = "visible"; + mytable.innerHTML += "Found " + Object.keys(rooms_json).length + " rooms"; for (var key in rooms_json) { var roomemail = rooms_json[key]["email"]; @@ -149,7 +163,7 @@ function bookRoom(roomname, roomemail) { xmlHttp.open("GET", url, false); // false for synchronous request xmlHttp.send(null); mytable.innerHTML = '' + roomname + " " + xmlHttp.responseText + ''; - + hideUserPassword(); } diff --git a/service/templates/index.html b/service/templates/index.html index 7e68e9d..90b174c 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -90,8 +90,6 @@
- -
@@ -103,7 +101,9 @@
- +

+ +
From 86ad8f50a8b7f114e357eff5c69db3c18fad6d7e Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 12 Sep 2016 16:42:32 -0700 Subject: [PATCH 101/146] sgersapp: Determine closest city using geolocation Change-Id: I632acc60672b0d0cd551c4b036b2698b4672b96e --- common.py | 34 ++++++++++++++++++++++++++++++++++ service/static/js/main.js | 15 +++++++++++++++ service/webserver.py | 10 ++++++++++ 3 files changed, 59 insertions(+) diff --git a/common.py b/common.py index 83bfe27..142fc9f 100644 --- a/common.py +++ b/common.py @@ -12,6 +12,7 @@ PWD = os.getcwd() ROOMS_CSV = os.path.join(PWD, 'rooms.csv') +COORDS_CSV = os.path.join(PWD, 'coords.csv') AVAILIBILITY_TEMPLATE = os.path.join(PWD, 'getavailibility_template.xml') SERVICE_DIR = os.path.join(PWD, 'service') CERT_DIR = os.path.join(PWD, 'certdir') @@ -20,6 +21,7 @@ ROOMS_CACHE = None ROOMNAMES_CACHE = None CITIES_CACHE = None +COORDS_CACHE = {} BUILDINGS_CACHE = {} FLOORS_CACHE = {} @@ -128,6 +130,38 @@ def get_city_list(filename=ROOMS_CSV): CITIES_CACHE = sorted(cities) return CITIES_CACHE +def get_city_coords(filename=COORDS_CSV): + if len(COORDS_CACHE) > 0: + return COORDS_CACHE + + cities = get_city_list() + + with open(filename, "r") as fhandle: + reader = csv.reader(fhandle) + for city, latitude, longitude in reader: + if city in cities: + COORDS_CACHE[city] = float(latitude), float(longitude) + + return COORDS_CACHE + +def get_closest_city(latitude, longitude): + coords_dict = get_city_coords() + + closest_city = None + closest_distance = None + + def distance(coords): + city_lat, city_long = coords + return (city_lat - latitude) ** 2 + (city_long - longitude) ** 2 + + for city, city_coords in coords_dict.iteritems(): + city_distance = distance(city_coords) + if closest_city is None or city_distance < closest_distance: + closest_city = city + closest_distance = city_distance + + return closest_city + def write_room_list(rooms, filename=ROOMS_CSV): global ROOMS_CACHE ROOMS_CACHE = rooms diff --git a/service/static/js/main.js b/service/static/js/main.js index 8232616..c340205 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -37,6 +37,9 @@ function init(){ loadFloorList(buildingSelect.value); floorSelect.value = "3"; durationMinSelect.value = "30m"; + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(getCity); + } var date = new Date(); var today = date.toISOString().split('T')[0]; @@ -70,6 +73,18 @@ function loadCitiesList() { cities = JSON.parse(xmlHttp.responseText); } +function getCity(position) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "/getcity?latitude=" + position.coords.latitude + "&longitude=" + position.coords.longitude, false); + xmlHttp.send(null); + closestCity = JSON.parse(xmlHttp.responseText); + if (citySelect.value != closestCity) { + citySelect.value = closestCity; + loadBuildingList(citySelect.value); + loadFloorList(buildingSelect.value); + } +} + function loadFloorList(buildingname) { var xmlHttp = new XMLHttpRequest(); xmlHttp.open("GET", "/showfloors?buildingname=" + buildingname, false); diff --git a/service/webserver.py b/service/webserver.py index 917e855..71d09a6 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -23,6 +23,16 @@ def index(): QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees, timezone') BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password, timezone') +@APP.route('/getcity', methods=['GET']) +def get_city(): + """ Get closest city in JSON """ + latitude = flask.request.args.get('latitude') + longitude = flask.request.args.get('longitude') + city = common.get_closest_city(float(latitude), float(longitude)) + common.LOGGER.info("Closest city is %s based on coordinates: %s, %s", + city, latitude, longitude) + return json.dumps(city) + @APP.route('/showcities', methods=['GET']) def show_cities(): """ Serve list of cities in JSON """ From 6ff20ad7d7fd3e38a284476c4879673e16da7ef6 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 12 Sep 2016 16:50:50 -0700 Subject: [PATCH 102/146] sgersapp: Add more logging to track user activities Change-Id: Ic8639dd0a2a59975f495591171874e8a960edcba --- service/webserver.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/service/webserver.py b/service/webserver.py index 71d09a6..78a44cc 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -37,6 +37,7 @@ def get_city(): def show_cities(): """ Serve list of cities in JSON """ cities = common.get_city_list() + common.LOGGER.debug("Read list of %d cities from database", len(cities)) return json.dumps(cities) @APP.route('/showbuildings', methods=['GET']) @@ -44,6 +45,7 @@ def show_buldings(): """ Serve list of buildings in JSON """ city = flask.request.args.get('city') buildings = common.get_building_list(city) + common.LOGGER.debug("%d buildings in %s", len(buildings), city) return json.dumps(buildings) @APP.route('/showfloors', methods=['GET']) @@ -51,6 +53,7 @@ def show_floors(): """ Serve list of floors in JSON """ buildingname = flask.request.args.get('buildingname') floors = common.get_floor_list(buildingname) + common.LOGGER.debug("%d floors in %s", len(floors), buildingname) if len(floors) > 1: return json.dumps(floors + ["Any"]) else: @@ -83,6 +86,8 @@ def show_rooms(): timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) except Exception as exception: + common.LOGGER.warning("User %s query resulted in an error: %s", + queryparam.user, str(exception)) rooms_info = {"Error" : str(exception)} return json.dumps(rooms_info) @@ -105,19 +110,28 @@ def book_room(): timezone=queryparam.timezone) try: if room_finder.reserve_room(): + common.LOGGER.warning("User %s reservation of %s succeeded", + queryparam.user, queryparam.roomname) return "reservation requested" else: + common.LOGGER.warning("User %s reservation of %s failed", + queryparam.user, queryparam.roomname) return "reservation failed" - except Exception as excpetion: - return "reservation failed: " + str(excpetion) + except Exception as exception: + common.LOGGER.warning("User %s reservation of %s resulted in an error: %s", + queryparam.user, queryparam.roomname, str(exception)) + return "reservation failed: " + str(exception) def create_ssl_context(): """ Create SSL context """ - context = (os.path.join(common.CERT_DIR, 'roomfinder.cert'), os.path.join(common.CERT_DIR, 'roomfinder.key')) + context = (os.path.join(common.CERT_DIR, 'roomfinder.cert'), + os.path.join(common.CERT_DIR, 'roomfinder.key')) return context if __name__ == '__main__': if common.HTTPS_ENABLED: - APP.run(threaded=True, host=socket.gethostname(), ssl_context=create_ssl_context(), port=common.HTTPS_PORT) + APP.run(threaded=True, host=socket.gethostname(), + ssl_context=create_ssl_context(), port=common.HTTPS_PORT) else: - APP.run(threaded=True, host=socket.gethostname(), port=common.HTTP_PORT) + APP.run(threaded=True, host=socket.gethostname(), + port=common.HTTP_PORT) From 42fed0ca6f59030738db786a431cb289411bb6a9 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 12 Sep 2016 16:52:47 -0700 Subject: [PATCH 103/146] sgersapp: City Coordinates Change-Id: Iefe0df38c2e9a120946cccdd9a0c29c336e875f9 --- coords.csv | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 coords.csv diff --git a/coords.csv b/coords.csv new file mode 100644 index 0000000..4f3a342 --- /dev/null +++ b/coords.csv @@ -0,0 +1,9 @@ +Bangalore,12.9716,77.5946 +London,51.5074,-0.1278 +New York,40.7128,-74.0059 +San Francisco,37.7749,-122.4194 +San Jose,37.3382,-121.8863 +Seattle,47.6062,-122.3321 +Sydney,-33.8688,151.2093 +Tokyo,35.6895,139.6917 +Vancouver,49.2827,-123.1207 From 64a17a265e05153940b98a1736496c8c487dc149 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Fri, 23 Sep 2016 16:39:15 -0700 Subject: [PATCH 104/146] sgersapp: Contact addresses Change-Id: I5d02ccb26bd077f8808b266a21ecbd30f51a54ac --- service/templates/index.html | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/service/templates/index.html b/service/templates/index.html index 90b174c..66ba159 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -8,6 +8,16 @@ color: #555555; font-family: "Verdana","Calibri",Geneva,sans-serif; } + #contact{ + color: black; + font-size: 10px; + text-align: right; + } + #search{ + color: black; + font-size: 15px; + text-align: center; + } #banner{ background-color: #AAAAAA; color: white; @@ -87,8 +97,12 @@
-
+
+ Report a bug       + Contact Us      
+
+
From edccbf4477d79ba76c4a8d42cccd76f40d17ecc3 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 10 Oct 2016 20:51:19 -0700 Subject: [PATCH 123/146] sgersapp: Webserver for the UI revamp Change-Id: I1b456905c090d83cb0d6389484889e727b29a2e8 --- service/webserver2.py | 138 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100755 service/webserver2.py diff --git a/service/webserver2.py b/service/webserver2.py new file mode 100755 index 0000000..27eadee --- /dev/null +++ b/service/webserver2.py @@ -0,0 +1,138 @@ +""" +Webservice APIs for room finder backend +""" + +import collections +import json +import os +import socket + +import common +import flask + +from book_room import ReserveAvailRoom +from find_available_room import AvailRoomFinder + +APP = flask.Flask(__name__, template_folder=common.TEMPLATE_FOLDER) + +@APP.route('/') +def index(): + """ Serve static index file """ + return flask.render_template('index3.html') + +QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, date, starttime, endtime, user, password, attendees, timezone') +BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, date, starttime, endtime, user, password, timezone') + +@APP.route('/getcity', methods=['GET']) +def get_city(): + """ Get closest city in JSON """ + latitude = flask.request.args.get('latitude') + longitude = flask.request.args.get('longitude') + city = common.get_closest_city(float(latitude), float(longitude)) + common.LOGGER.info("Closest city is %s based on coordinates: %s, %s", + city, latitude, longitude) + return json.dumps(city) + +@APP.route('/showcities', methods=['GET']) +def show_cities(): + """ Serve list of cities in JSON """ + cities = common.get_city_list() + common.LOGGER.debug("Read list of %d cities from database", len(cities)) + return json.dumps(cities) + +@APP.route('/showbuildings', methods=['GET']) +def show_buldings(): + """ Serve list of buildings in JSON """ + city = flask.request.args.get('city') + buildings = common.get_building_list(city) + common.LOGGER.debug("%d buildings in %s", len(buildings), city) + return json.dumps(buildings) + +@APP.route('/showfloors', methods=['GET']) +def show_floors(): + """ Serve list of floors in JSON """ + buildingname = flask.request.args.get('buildingname') + floors = common.get_floor_list(buildingname) + common.LOGGER.debug("%d floors in %s", len(floors), buildingname) + if len(floors) > 1: + return json.dumps(floors + ["Any"]) + else: + return json.dumps(floors) + +# Example Query +# http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password +@APP.route('/showrooms', methods=['GET']) +def show_rooms(): + """ Serve list of rooms in JSON """ + queryparam = QueryParam(buildingname=flask.request.args.get('buildingname'), + floor=flask.request.args.get('floor'), + date=flask.request.args.get('date'), + starttime=flask.request.args.get('starttime'), + endtime=flask.request.args.get('endtime'), + user=flask.request.args.get('user'), + password=flask.request.args.get('password'), + attendees=flask.request.args.get('attendees'), + timezone=flask.request.args.get('timezone')) + + if queryparam.floor.startswith("Any"): + prefix = queryparam.buildingname + else: + prefix = queryparam.buildingname + '-' + queryparam.floor + + try: + room_finder = AvailRoomFinder(user=queryparam.user, + password=queryparam.password, + start_time=queryparam.date + "T" + queryparam.starttime + ":00", + end_time=queryparam.date + "T" + queryparam.endtime + ":00", + timezone=queryparam.timezone) + rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) + except Exception as exception: + common.LOGGER.warning("User %s query resulted in an error: %s", + queryparam.user, str(exception)) + rooms_info = {"Error" : str(exception)} + return json.dumps(rooms_info) + +@APP.route('/bookroom', methods=['GET']) +def book_room(): + """ Reserve specified room """ + queryparam = BookRoomQueryParam(roomname=flask.request.args.get('roomname'), + date=flask.request.args.get('date'), + starttime=flask.request.args.get('starttime'), + endtime=flask.request.args.get('endtime'), + user=flask.request.args.get('user'), + password=flask.request.args.get('password'), + timezone=flask.request.args.get('timezone')) + + room_finder = ReserveAvailRoom(user=queryparam.user, + password=queryparam.password, + roomname=queryparam.roomname, + start_time=queryparam.date + "T" + queryparam.starttime + ":00", + end_time=queryparam.date + "T" + queryparam.endtime + ":00", + timezone=queryparam.timezone) + try: + if room_finder.reserve_room(): + common.LOGGER.warning("User %s reservation of %s succeeded", + queryparam.user, queryparam.roomname) + return "reservation requested" + else: + common.LOGGER.warning("User %s reservation of %s failed", + queryparam.user, queryparam.roomname) + return "reservation failed" + except Exception as exception: + common.LOGGER.warning("User %s reservation of %s resulted in an error: %s", + queryparam.user, queryparam.roomname, str(exception)) + return "reservation failed: " + str(exception) + +def create_ssl_context(): + """ Create SSL context """ + context = (os.path.join(common.CERT_DIR, 'roomfinder.cert'), + os.path.join(common.CERT_DIR, 'roomfinder.key')) + return context + +if __name__ == '__main__': + if common.HTTPS_ENABLED: + APP.run(threaded=True, host=socket.gethostname(), + ssl_context=create_ssl_context(), port=common.HTTPS_PORT) + else: + APP.run(threaded=True, host=socket.gethostname(), + port=common.HTTP_PORT) From f4cdaf8b23f2c0cee0b0556b86aef21b83788968 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 10 Oct 2016 23:21:15 -0700 Subject: [PATCH 124/146] sgersapp: Make time selection more intuitive Change-Id: I5c075b33ec6afa73056c768a7e407bb239becd34 --- service/static/js/main3.js | 65 ++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/service/static/js/main3.js b/service/static/js/main3.js index fb723b2..be23459 100644 --- a/service/static/js/main3.js +++ b/service/static/js/main3.js @@ -3,7 +3,10 @@ var buildings = []; var floors = [];; var attendees = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "15", "20", "25", "30", "50", "70", "100"]; -var times = ["08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00"]; +var times = ["00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", + "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", + "14:00", "15:00", "16:00", "17:00", "18:00", "18:00", "19:00", + "20:00", "21:00", "22:00", "23:00"]; //var times = ["00:00", "00:30", "01:00", "01:30", "02:00", "02:30", "03:00", // "03:30", "04:00", "04:30", "05:00", "05:30", "06:00", "06:30", // "07:00", "07:30", "08:00", "08:30", "09:00", "09:30", "10:00", @@ -11,8 +14,9 @@ var times = ["08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15: // "14:00", "14:30", "15:00", "15:30", "16:00", "16:30", "17:00", // "17:30", "18:00", "18:30", "19:00", "19:30", "20:00", "20:30", // "21:00", "21:30", "22:00", "22:30", "23:00", "23:30",]; -var startTimeBtn; -var endTimeBtn; +var startTimeIndex = null; +var endTimeIndex = null; + var selectedRoom; function init(){ @@ -38,9 +42,6 @@ function init(){ setTodayDate(); createTimeRows(times); - - startTimeBtn = timeBtn0; - endTimeBtn = timeBtn0; } function createCombo(container, data) { @@ -87,38 +88,43 @@ function loadBuildingList(city) { } function createTimeRows(data) { - - for (var i = 0; i < data.length; i++) { - tableContainer.innerHTML += "
"; + var i = 0; + for (i = 0; i < data.length; i++) { + tableContainer.innerHTML += "
"; + tableContainer.innerHTML += ""; + tableContainer.innerHTML += ""; + tableContainer.innerHTML += "
"; } + + tableContainer.innerHTML += "
"; + tableContainer.innerHTML += ""; + tableContainer.innerHTML += ""; + tableContainer.innerHTML += "
"; } -function handleSelectTimeBtn (btn) { - if (startTimeBtn == null) { - startTimeBtn = btn; - endTimeBtn = btn; +function handleTime(index) { + if ((startTimeIndex == null) || (index < startTimeIndex)) { + startTimeIndex = index; + endTimeIndex = index; } - else if (startTimeBtn == endTimeBtn) { - endTimeBtn = btn; + else if (startTimeIndex == endTimeIndex) { + endTimeIndex = index; } else { - startTimeBtn = btn; - endTimeBtn = btn; + startTimeIndex = index; + endTimeIndex = index; } + console.log(index + " " + startTimeIndex + " " + endTimeIndex); + console.log("start====" + (eval('timeBtn' + startTimeIndex)).value); + console.log("end====" + (eval('timeBtn' + (endTimeIndex + 1))).value); + console.log("selected====" + (eval('timeBtn' + index)).value); - var startIndex = (startTimeBtn.id).charAt(startTimeBtn.id.length-1); - var endIndex = (endTimeBtn.id).charAt(endTimeBtn.id.length-1); - if (startIndex > endIndex) { - var temp = startIndex; - startIndex = endIndex; - endIndex = temp; - } var btn; var lbl; for(var i=0 ; i= startIndex && i<=endIndex) { + if (i>= startTimeIndex && i<=endTimeIndex) { btn.classList.add('tableBtnEnabled'); lbl.classList.add('timeLabelEnabled'); } @@ -126,7 +132,6 @@ function handleSelectTimeBtn (btn) { btn.classList.remove('tableBtnEnabled'); lbl.classList.remove('timeLabelEnabled'); } - } } @@ -150,7 +155,9 @@ function setTodayDate() { function handleSearchBtnClick() { var timezone = new Date().getTimezoneOffset(); - var queryString = `\?user=\&password=&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&date=${dateInput.value}&starttime=${startTimeBtn.value}&endtime=${endTimeBtn.value}&attendees=${roomSizeSelect.value}&timezone=${timezone}`; + var starttime = (eval('timeBtn' + startTimeIndex)).value; + var endtime = (eval('timeBtn' + (endTimeIndex + 1))).value; + var queryString = `\?user=\&password=&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&date=${dateInput.value}&starttime=${starttime}&endtime=${endtime}&attendees=${roomSizeSelect.value}&timezone=${timezone}`; loadRooms(queryString); } @@ -195,7 +202,9 @@ function handleSelectRoomBtn (radioBtn) { function handleReserveBtnClick() { var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); var timezone = new Date().getTimezoneOffset(); - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${selectedRoom}&starttime=${startTimeBtn.value}&endtime=${endTimeBtn.value}&date=${dateInput.value}&timezone=${timezone}`; + var starttime = (eval('timeBtn' + startTimeIndex)).value; + var endtime = (eval('timeBtn' + (endTimeIndex + 1))).value; + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${selectedRoom}&starttime=${starttime}&endtime=${endtime}&date=${dateInput.value}&timezone=${timezone}`; var xmlHttp = new XMLHttpRequest(); if (userNameInput.value == "") { From 622caafaf9db6c1fe372278a04f17afca625cc70 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 10 Oct 2016 23:28:32 -0700 Subject: [PATCH 125/146] sgersapp: Add 30 min intervals Change-Id: I1cb697071d692739fd383fb1d2041196dae71948 --- service/static/css/style.css | 16 ++++++++++++---- service/static/js/main3.js | 35 +++++++++++++++++------------------ 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/service/static/css/style.css b/service/static/css/style.css index 1d8f65e..2bc9135 100644 --- a/service/static/css/style.css +++ b/service/static/css/style.css @@ -130,14 +130,22 @@ hr { float: left; } +.timeLabelHidden { + width: 0px; + display:block; + text-align:right; + float: left; + visibility: hidden; +} + .timeTableRow { margin: 5px 0px 0px 0px; } .tableButton { - width: 80%; - height: 40%; - margin-left: 20px; + width: 40%; + height: 10%; + margin-left: 10px; background-color: white; border: 0.5px solid #888888; box-shadow: 1px 2px 3px #888888; @@ -182,4 +190,4 @@ hr { background-color: lightgray; box-shadow: 1px 0px 1px 1px #888888; -} \ No newline at end of file +} diff --git a/service/static/js/main3.js b/service/static/js/main3.js index be23459..6df3a14 100644 --- a/service/static/js/main3.js +++ b/service/static/js/main3.js @@ -3,17 +3,17 @@ var buildings = []; var floors = [];; var attendees = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "15", "20", "25", "30", "50", "70", "100"]; -var times = ["00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", - "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", - "14:00", "15:00", "16:00", "17:00", "18:00", "18:00", "19:00", - "20:00", "21:00", "22:00", "23:00"]; -//var times = ["00:00", "00:30", "01:00", "01:30", "02:00", "02:30", "03:00", -// "03:30", "04:00", "04:30", "05:00", "05:30", "06:00", "06:30", -// "07:00", "07:30", "08:00", "08:30", "09:00", "09:30", "10:00", -// "10:30", "11:00", "11:30", "12:00", "12:30", "13:00", "13:30", -// "14:00", "14:30", "15:00", "15:30", "16:00", "16:30", "17:00", -// "17:30", "18:00", "18:30", "19:00", "19:30", "20:00", "20:30", -// "21:00", "21:30", "22:00", "22:30", "23:00", "23:30",]; +//var times = ["00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", +// "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", +// "14:00", "15:00", "16:00", "17:00", "18:00", "18:00", "19:00", +// "20:00", "21:00", "22:00", "23:00"]; +var times = ["00:00", "00:30", "01:00", "01:30", "02:00", "02:30", "03:00", + "03:30", "04:00", "04:30", "05:00", "05:30", "06:00", "06:30", + "07:00", "07:30", "08:00", "08:30", "09:00", "09:30", "10:00", + "10:30", "11:00", "11:30", "12:00", "12:30", "13:00", "13:30", + "14:00", "14:30", "15:00", "15:30", "16:00", "16:30", "17:00", + "17:30", "18:00", "18:30", "19:00", "19:30", "20:00", "20:30", + "21:00", "21:30", "22:00", "22:30", "23:00", "23:30",]; var startTimeIndex = null; var endTimeIndex = null; @@ -92,13 +92,16 @@ function createTimeRows(data) { for (i = 0; i < data.length; i++) { tableContainer.innerHTML += "
"; tableContainer.innerHTML += ""; - tableContainer.innerHTML += ""; + tableContainer.innerHTML += ""; + i++; + tableContainer.innerHTML += ""; + tableContainer.innerHTML += ""; tableContainer.innerHTML += "
"; } tableContainer.innerHTML += "
"; - tableContainer.innerHTML += ""; - tableContainer.innerHTML += ""; + tableContainer.innerHTML += "
"; } @@ -114,10 +117,6 @@ function handleTime(index) { startTimeIndex = index; endTimeIndex = index; } - console.log(index + " " + startTimeIndex + " " + endTimeIndex); - console.log("start====" + (eval('timeBtn' + startTimeIndex)).value); - console.log("end====" + (eval('timeBtn' + (endTimeIndex + 1))).value); - console.log("selected====" + (eval('timeBtn' + index)).value); var btn; var lbl; From cb158bfe3deb7fd4bb8e239d95263efecae44a31 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 10 Oct 2016 23:30:51 -0700 Subject: [PATCH 126/146] sgersapp: Hide experimental work Change-Id: I2a5fdbd0b3713b948162fd0f2f554e4786e19096 --- service/templates/index3.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/templates/index3.html b/service/templates/index3.html index 01d092b..6ff767f 100644 --- a/service/templates/index3.html +++ b/service/templates/index3.html @@ -49,7 +49,7 @@

Conference Room Finder

-

Site Map

+
From b7e4315d33bef381e135cec2976d214515b3685f Mon Sep 17 00:00:00 2001 From: Monika Date: Tue, 11 Oct 2016 14:13:38 -0700 Subject: [PATCH 127/146] monika: Display floor map in new UI. Change-Id: Iaf2a129f83bfb8ed62ff37bc2354dc4008ec079b --- common.py | 2 +- service/static/css/style.css | 6 ++++++ service/static/js/main.js | 6 +++--- service/static/js/main3.js | 18 ++++++++++++------ service/templates/index3.html | 3 ++- service/webserver2.py | 16 +++++++++++++++- 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/common.py b/common.py index 3b04c8e..7d16873 100644 --- a/common.py +++ b/common.py @@ -17,7 +17,7 @@ SERVICE_DIR = os.path.join(PWD, 'service') CERT_DIR = os.path.join(PWD, 'certdir') TEMPLATE_FOLDER = os.path.join(SERVICE_DIR, 'templates') -FLOORMAP_DIR = os.path.join(TEMPLATE_FOLDER, 'floorplan') +FLOORMAP_DIR = os.path.join(TEMPLATE_FOLDER, 'floormap') ROOMS_CACHE = None ROOMNAMES_CACHE = None diff --git a/service/static/css/style.css b/service/static/css/style.css index 2bc9135..b2b51c1 100644 --- a/service/static/css/style.css +++ b/service/static/css/style.css @@ -191,3 +191,9 @@ hr { box-shadow: 1px 0px 1px 1px #888888; } + +.mapContainer { + margin: 5px 0px 0px 40px; + display: none; + height: 400px; +} diff --git a/service/static/js/main.js b/service/static/js/main.js index 446d629..38f790c 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -109,7 +109,7 @@ function loadFloorList(buildingname) { xmlHttp.send(null); floors = JSON.parse(xmlHttp.responseText); createCombo(floorSelect, floors); - result.innerHTML = "" + resultMessage.innerHTML = ""; } function loadBuildingList(city) { @@ -169,7 +169,7 @@ function showFreeRooms(rooms_json) { userNameInput.style.visibility = "visible"; passwordLabel.style.visibility = "visible"; passwordInput.style.visibility = "visible"; - result.innerHTML = "" + resultMessage.innerHTML = ""; mytable.innerHTML += "Found " + Object.keys(rooms_json).length + " rooms"; for (var key in rooms_json) { var roomemail = rooms_json[key]["email"]; @@ -215,7 +215,7 @@ function bookRoom(roomname, roomemail) { xmlHttp.send(null); mytable.innerHTML = ""; mytable.visiblity = false; - result.innerHTML = roomname + " " + xmlHttp.responseText; + resultMessage.innerHTML = roomname + " " + xmlHttp.responseText; hideUserPassword(); } diff --git a/service/static/js/main3.js b/service/static/js/main3.js index 6df3a14..fce101f 100644 --- a/service/static/js/main3.js +++ b/service/static/js/main3.js @@ -85,6 +85,8 @@ function loadBuildingList(city) { xmlHttp.send(null); buildings = JSON.parse(xmlHttp.responseText); createCombo(buildingSelect, buildings); + resultMessage.style.display = "none"; + resultMap.style.display = "none"; } function createTimeRows(data) { @@ -180,9 +182,11 @@ function showFreeRooms(rooms_json) { return; } else { - resultMessage.innerHTML = ""; + resultMessage.style.display = "none"; + resultMap.style.display = "none"; } + roomNamesContainer.style.display = "block"; roomNamesContainer.innerHTML = ""; roomNamesContainer.innerHTML += "

" + Object.keys(rooms_json).length + " room(s) available

"; for (var key in rooms_json) { @@ -231,12 +235,15 @@ function handleReserveBtnClick() { xmlHttp.send(null); hideRoomList(); hideUsernamePasswordFields(); - resultMessage.innerHTML = selectedRoom + " " + xmlHttp.responseText; + resultMessage.style.display = "block"; + resultMap.style.display = "block"; + resultMessage.innerHTML = selectedRoom; + resultMap.innerHTML = xmlHttp.responseText; } function hideRoomList() { roomNamesContainer.innerHTML = ''; - roomNamesContainer.style.visibility = false; + roomNamesContainer.style.display = "none"; } function showUsernamePasswordFields() { @@ -247,12 +254,11 @@ function showUsernamePasswordFields() { userpassHTML += ""; userpassHTML += ""; - usernamePasswordContainer.style.visibility = true; + usernamePasswordContainer.style.display = "block"; usernamePasswordContainer.innerHTML = userpassHTML; } function hideUsernamePasswordFields() { - usernamePasswordContainer.style.visibility = false; - usernamePasswordContainer.innerHTML = ""; + usernamePasswordContainer.style.display = "none"; } diff --git a/service/templates/index3.html b/service/templates/index3.html index 6ff767f..ace3e9c 100644 --- a/service/templates/index3.html +++ b/service/templates/index3.html @@ -46,9 +46,10 @@

Conference Room Finder

+
+
-
diff --git a/service/webserver2.py b/service/webserver2.py index 27eadee..ff84069 100755 --- a/service/webserver2.py +++ b/service/webserver2.py @@ -2,10 +2,13 @@ Webservice APIs for room finder backend """ +import base64 import collections import json import os +import pipes import socket +import subprocess import common import flask @@ -20,6 +23,11 @@ def index(): """ Serve static index file """ return flask.render_template('index3.html') +@APP.route('/getfloormap', methods=['GET']) +def get_floor_map(): + bldgfloorname = flask.request.args.get('bldgfloorname') + return flask.send_file(os.path.join(common.FLOORMAP_DIR, bldgfloorname)) + QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, date, starttime, endtime, user, password, attendees, timezone') BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, date, starttime, endtime, user, password, timezone') @@ -113,7 +121,13 @@ def book_room(): if room_finder.reserve_room(): common.LOGGER.warning("User %s reservation of %s succeeded", queryparam.user, queryparam.roomname) - return "reservation requested" + bldg, floor, unused = queryparam.roomname.split('-', 2) + URL = "https://wwwin.cisco.com/c/dam/cec/organizations/gbs/wpr/FloorPlans/{}-AFP-{}.pdf".format(bldg, floor) + curl_command = "curl --location-trusted -L --ntlm -c cookies.txt -u " + pipes.quote(queryparam.user) + ":" + pipes.quote(base64.b64decode(queryparam.password)) + " " + URL + " -o " + common.FLOORMAP_DIR + "/{}-AFP-{}.pdf".format(bldg, floor) + curl_process = subprocess.Popen(curl_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + response = curl_process.communicate()[0] + common.LOGGER.warning("Floor map for building %s floor %s downloaded", bldg, floor) + return "Reservation Requested


".format(bldg, floor) else: common.LOGGER.warning("User %s reservation of %s failed", queryparam.user, queryparam.roomname) From 26556a9e9ba5d8321d4114e37b2d377466e63e02 Mon Sep 17 00:00:00 2001 From: Monika Date: Tue, 11 Oct 2016 14:25:45 -0700 Subject: [PATCH 128/146] monika: Add direcory floormap in template folder. Change-Id: I7acd8ca4b6a9c8729545d3c1f82ce5761f713d02 --- service/templates/floormap/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 service/templates/floormap/.gitignore diff --git a/service/templates/floormap/.gitignore b/service/templates/floormap/.gitignore new file mode 100644 index 0000000..5e7d273 --- /dev/null +++ b/service/templates/floormap/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore From 4ddc8d3eaec9afc4cd63b62842032c1a1a6f8706 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 12 Oct 2016 12:31:06 -0700 Subject: [PATCH 129/146] sgersapp: Auto select current time Change-Id: I8cd64ab0253e436a63283bcb730c82ab23244a35 --- service/static/js/main3.js | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/service/static/js/main3.js b/service/static/js/main3.js index 6df3a14..610d0b8 100644 --- a/service/static/js/main3.js +++ b/service/static/js/main3.js @@ -42,12 +42,13 @@ function init(){ setTodayDate(); createTimeRows(times); + selectCurrentTime(); } function createCombo(container, data) { var options = ''; container.options.length = 0; - for (var i = 0; i < data.length; i++) { + for (var i=0; i < data.length; i++) { container.options.add(new Option(data[i], data[i])); } } @@ -120,7 +121,7 @@ function handleTime(index) { var btn; var lbl; - for(var i=0 ; i= startTimeIndex && i<=endTimeIndex) { @@ -135,6 +136,35 @@ function handleTime(index) { } +function selectCurrentTime() { + var date = new Date(); + var current_hour = date.getHours(); + var current_min = date.getMinutes(); + + if (current_min < 30) { + current_min = "00"; + } + else { + current_min = "30"; + } + + if (current_hour < 10) { + current_hour = "0" + current_hour; + } + else { + current_hour = "" + current_hour; + } + + var selectTime = current_hour + ":" + current_min; + + for (var i=0 ; i Date: Wed, 12 Oct 2016 13:45:32 -0700 Subject: [PATCH 130/146] sgersapp: Improve default time selection accuracy and add auto scroll to default time Change-Id: Iaea3853a1b09b9752e9b30db13f037566c30a98b --- service/static/js/main3.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/service/static/js/main3.js b/service/static/js/main3.js index 610d0b8..c8cfa6a 100644 --- a/service/static/js/main3.js +++ b/service/static/js/main3.js @@ -124,7 +124,7 @@ function handleTime(index) { for (var i=0 ; i= startTimeIndex && i<=endTimeIndex) { + if (i >= startTimeIndex && i <= endTimeIndex) { btn.classList.add('tableBtnEnabled'); lbl.classList.add('timeLabelEnabled'); } @@ -133,7 +133,6 @@ function handleTime(index) { lbl.classList.remove('timeLabelEnabled'); } } - } function selectCurrentTime() { @@ -141,12 +140,16 @@ function selectCurrentTime() { var current_hour = date.getHours(); var current_min = date.getMinutes(); - if (current_min < 30) { + if (current_min < 15) { current_min = "00"; } - else { + else if (current_min < 45) { current_min = "30"; } + else { + current_hour++; + current_min = "00"; + } if (current_hour < 10) { current_hour = "0" + current_hour; @@ -160,9 +163,10 @@ function selectCurrentTime() { for (var i=0 ; i Date: Sun, 16 Oct 2016 22:18:21 -0700 Subject: [PATCH 131/146] sgersapp: Add default self-signed certificate and key to ease setup Change-Id: I0dda4e78a30c8e4c3dce551222d68cdee11a36e9 --- certs/cert.pem | 39 ++++++++++++++++++++++++++++++++++++++ certs/key.pem | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ gunicorn.cfg | 4 ++-- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 certs/cert.pem create mode 100644 certs/key.pem diff --git a/certs/cert.pem b/certs/cert.pem new file mode 100644 index 0000000..fb30e56 --- /dev/null +++ b/certs/cert.pem @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIGxjCCBK6gAwIBAgIJAOUp2FQbFrEcMA0GCSqGSIb3DQEBBQUAMIGcMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRcwFQYDVQQK +Ew5TYWlzaCBHZXJzYXBwYTEUMBIGA1UECxMLUm9vbSBGaW5kZXIxHTAbBgNVBAMT +FHJvb21maW5kZXIuY2lzY28uY29tMR8wHQYJKoZIhvcNAQkBFhBzYWlzaGdAZ21h +aWwuY29tMCAXDTE2MTAxNzA1MTUzMloYDzIxMTYwOTIzMDUxNTMyWjCBnDELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAkNBMREwDwYDVQQHEwhTYW4gSm9zZTEXMBUGA1UE +ChMOU2Fpc2ggR2Vyc2FwcGExFDASBgNVBAsTC1Jvb20gRmluZGVyMR0wGwYDVQQD +ExRyb29tZmluZGVyLmNpc2NvLmNvbTEfMB0GCSqGSIb3DQEJARYQc2Fpc2hnQGdt +YWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALHWAKrOAWFh +cEtCNc0a5Bb24Ij1BQhop/laS8bvvREWXNOyqU4aQlbO/g5eDzVieqrwQmXY3YPk +A53MEqp3pXlNQnV9W+BjMPPAIlBS9R6+1BcuGudss/mgEglqKmdCDiAAjI0X8kC5 +lpeNGT+JAhyoNUK5yfPgdNyJaBA8u0oasTbw+Tn2RQLdP5sdPCQahJC28GEOYivU +TewM2vIEHh+p2uZZovQ/fA+NxSdXeS7Oa96HzPcqOEBEkgtNvogKuxuAvP+P/nUd +hz2RtA5Hl1nIvqGKAfqErVgbEBmiPDuKCC9Zmg/d4ifaLbbN0e2NqYyahQXamJ4z +2LcSQS2jXt1pFGNF7RHkeNhTiSCs3KLCdBLdeDK3k6sGb1pw5BZLLxBEGrmSwaLR +2CrPPC0sDr+HbOljAkmuDth+AGlYwCPVjq+urCwKz9TFaZErcF5y2JLwEbBQ02bB +2gUyfLuEKOSLAs96Xb7m0eoTHR26JLYmvdH9nexepPA/YovaIKdk0q9Dtcqj9RqC +ecNeiy+Lo3izxv06b7s0Iu27WzoXhUrT1bOc9uRWfmnLQczM1rwv7RRRC+5irAQz +sn3damHV9lqJE/zW+ZRGvKb4QEt5awg6h9//7+CmftBaTO4RixZru8pLMSrrZ308 +9JdI5z4q7s3AnwU5oJj8/WRMnE25MzRJAgMBAAGjggEFMIIBATAdBgNVHQ4EFgQU +FvMqU84chLjsHOWLcFBrbMODUkIwgdEGA1UdIwSByTCBxoAUFvMqU84chLjsHOWL +cFBrbMODUkKhgaKkgZ8wgZwxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTERMA8G +A1UEBxMIU2FuIEpvc2UxFzAVBgNVBAoTDlNhaXNoIEdlcnNhcHBhMRQwEgYDVQQL +EwtSb29tIEZpbmRlcjEdMBsGA1UEAxMUcm9vbWZpbmRlci5jaXNjby5jb20xHzAd +BgkqhkiG9w0BCQEWEHNhaXNoZ0BnbWFpbC5jb22CCQDlKdhUGxaxHDAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQA+A2Qn1eEC6Euucw6Pz2TWayq58M8x +iklr1YKFJshW/gjvfoUM2ZMVvaeUMy/AJSfhn13KzmWnbC67Z+DzykMhCESF+VAK +T71AAQrPG9DqlrbIYA78+ClMLtzfhp+KXkQRNZqGvBeaDHbYmaYVmDHYkg8OqoR0 +waehcllJjGzXs39dp9ogulRdNL/LL7xT+lJTz/LkIwipQcd76MZMJP5b8erJgoe1 +t1QxDVn/8JWl98W5E49Xl0QxGPytx2hXLettkKJaj43Ebm66G4pNQm0KMQxUDehT +B/SjTsacHIZakijDN0GPco3aVlDb5cBVW3lKYLG3U6O/eUVBnb9BE1TtaDzwz/vN +7wFoibSfSMHPEnVWFr72lG5GoCSxmJXJ+7adPk97yRzZb73wEmacJ5F5ZxCScDRm +4DNM1FB821as5Wiz6/7HsDrrQjMbi1yH3aqjU3Aj5rPbLDJ3ith/2GzEXBfuTXh3 +I3klTJOmopkej3mB5JYF0SZ2l1+X9Zh8+XmkX3o/5XR+PyKLIuVSs/qnfpoaGVdG +yUN8yvj1PPgwQc1wpRPycJPb6ZMzaCFxCSJNrrIgASXiGUZdH4pjBD3n/Hxoix+f +H2+zU9i9rTF7EZEdmFe5Q6B76trbxAraZR+LcDIm9H+cGNGEVvzMbtHk+4u1giji +S524huW9+tHqLw== +-----END CERTIFICATE----- diff --git a/certs/key.pem b/certs/key.pem new file mode 100644 index 0000000..59edd8f --- /dev/null +++ b/certs/key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJJwIBAAKCAgEAsdYAqs4BYWFwS0I1zRrkFvbgiPUFCGin+VpLxu+9ERZc07Kp +ThpCVs7+Dl4PNWJ6qvBCZdjdg+QDncwSqneleU1CdX1b4GMw88AiUFL1Hr7UFy4a +52yz+aASCWoqZ0IOIACMjRfyQLmWl40ZP4kCHKg1QrnJ8+B03IloEDy7ShqxNvD5 +OfZFAt0/mx08JBqEkLbwYQ5iK9RN7Aza8gQeH6na5lmi9D98D43FJ1d5Ls5r3ofM +9yo4QESSC02+iAq7G4C8/4/+dR2HPZG0DkeXWci+oYoB+oStWBsQGaI8O4oIL1ma +D93iJ9otts3R7Y2pjJqFBdqYnjPYtxJBLaNe3WkUY0XtEeR42FOJIKzcosJ0Et14 +MreTqwZvWnDkFksvEEQauZLBotHYKs88LSwOv4ds6WMCSa4O2H4AaVjAI9WOr66s +LArP1MVpkStwXnLYkvARsFDTZsHaBTJ8u4Qo5IsCz3pdvubR6hMdHboktia90f2d +7F6k8D9ii9ogp2TSr0O1yqP1GoJ5w16LL4ujeLPG/TpvuzQi7btbOheFStPVs5z2 +5FZ+actBzMzWvC/tFFEL7mKsBDOyfd1qYdX2WokT/Nb5lEa8pvhAS3lrCDqH3//v +4KZ+0FpM7hGLFmu7yksxKutnfTz0l0jnPiruzcCfBTmgmPz9ZEycTbkzNEkCAwEA +AQKCAgBpEqp+QQ2rveidbtdfAl51+xQbl7mLiFqHCATx28B4EiByrINANF+x7sdJ +MeYGgtM7oI16o7HuNZC1cVguBFdu3mlABft9Dt5jhsg/cWSG7/VcZM9coWuNODiv ++1xmei7iVbC1xMpL19vUW3fphEgNKo2diSx7vckObNlhjqCSXkcK0UJQLuQDlzn3 +qkRYiJp+7rgEgH0crGoF6GqMyEYMK029AIU5jzD796XfYt2k/C3b450FBJsLzfgE +WcETnFOFIoGI9klAZVv80tPyA/a3A9cult4oaLAK+KKAosy32QyQ/X37lfwD0/Ni +qSU6GJNvEfU6yjeWccfAEzcTgg2QBBII1zDAdbkuvZI/7lk+Gsgcn8hQYtzSGTlK +aFioY17rEvOGgAwoT6/znGpAQq3rrBMazzH/xhhq3Ldl3nuSVO/JTEMMwSFYnCCx +/nGkmavLtRhDj0r3wXwXXOLsKUCdVx2TgOTJ2kY0TFppP4EfU4cWOrpJM/0pI81t +vonyRDju76MDOBCl1Dd5o3Diz/SQVCBAcrXe4OKLoJPklTqgg7aHhs5gqs9Pip+4 +J/befXG/mmB/8aSudKWTUrHkJlPnBltsYnuel6eIoHCfIqchxen0DOTkwSvkjlXJ +h/Q9YSK1154zlCOmMBUwTcZpzv/BakpwFLeEFy5MShIiIJyuAQKCAQEA3+S94C9X +UyYyAWRZKP3CyyyWQIJo2eBRJU0nGYhS3dVP+8XcNknRc4zTnh+HkoGbvvqXfqB/ +4jkA+R8/yhjLLvhRtgfU+aXqXuw7voaCrsAswlF2FOApjM+NYrrIpTupn06wGnES +wB+1arrPOvd6ygBLGt8YjRtSfmwftNH+hjDWKuFl38xxSY97rjyHVSzIZPFE5Z8w +yH1EuDU213/NYBYhZTQanvkLLETSiKfnI6Q4JyLS+l/cRj/AcAZt6xvNVpqQcUCP +NNFaD6KurR5jLWa9Yd+Hf4yXLRXYBStfboY6oBoQ6zGpf4O69YOr4lcTDaN5mMAQ +jXcGqfVBn9Do6QKCAQEAy1Z2EO9a36RO+0dfYmIyg3m8bOdLrSPGeaU8z5iwB5T4 +t4PjKy7/2Kc3AYZy0cWjE1wgc4ymKTzMf0xGCglA0gx+tbHBEf7/S5mF7l0f/DVz +9r7jPx676j4R1WARtFT+/5ImZImGihooKKnh5ruGx2PTzU7z4ZLxnhCUV5uHmdCK +xs+kHwLfN5WT8ySv9J6yrvVRY1tyAcCT5t5rK9Kf/Kjz21mKacKFaooiqbzkLCLM +gesniA2+zNrJpKA3FgZSeXnXddB+63RFHuze5uNTNejIBlfYKqhMYykY/6fIFwFx +7ci1/0Nq6gSUUrW5rVnHguuBDlk1f/HQoO5J3prUYQKCAQA6geX0faqOOf77SiPa +iGWs/lvNQ8bumKXb34uGKo+tFJ8wJgZj0WqAjZ6HRaoB6QiwIYARQRPqJAdTEo1y +3IPMJGwF64oGKwtR/t2l7jScQe/wX6VB00pIV7yUvkbMlwi+bquqXT4PIrofx+17 +dUyLGQSHYyFhTnCCRPMMJ4whuQVec0RR9XTtSieB4qNi6K79YeclMjJnUgTxNka8 +jdM3dtEHR1RlkqMO0HVL7MSEFdfusjT75K0FVoeNPsDenYdNSFrSnZJOtR6Z02Ne +LgCwzpZSyzz3Yd/nkju/LhRkJ4OObwFY1MN8ZQooOl5iaWq7N6sA9b/dl+sP4t1h +TBBxAoIBAGeY34J1UIlM/2iKzpAjk7TkmxmpJidKaN6lTzw9gMH8JlPpgB4KThOl +7iJ6y5kQ5qsAbxAwAqBT96SLyctnN31NHGmZ7NIsZwmvaEsvaxJmcXSvgLwx/m+z +vAZIcfy8qUawwZrLbp6CAR/mnc+ej2aa99hMd3jgEvYDYHDaLtYxJ+Nu+yFJp0x8 +iuqAMJ2jFUqKdjL27jjyUuh3PYcQQq7JraR+FEUZ9Dt5sXtlX6MU/7jZhESPLDzW +45Fah3ZTNkXpy9qcpW10yZqd+FsOSuDWfsKsktf48yI6WCA47Xq7I76QWhl50cj1 +GFSjfbxSV5HeRtx2mwlavH6hqUUfAUECggEAP4DWQ9qSELimqJCmnY17uW1iio59 +d01FzYdXzrANxZzIV/tkYgnMCtB7hj9zUpA1+xvBEnuPwioe51PQmCsOyx8hbkqT +oJyMMDT2kaIwRnRGriunoprbcz2w0VlhByfe4OqZKIv8TCl9bpZS0XbsFPiHQtdX +0pI76PxSh44LQmDoQPLi71/nprGnPAmE27G/HOCNSz8ecuOSUBuXe1jQ9bDMXWNg +fewjp9vMu/o34wUphYWQX1KN2deueCe7thi3T5TRlrZSWjWZRAeGJFi0IgGlCeDV +SHhkV8trxD5tst9VffQnfApP6AUG/xBG3zlqdLNqnuNlSGRpjGJ04XjEzg== +-----END RSA PRIVATE KEY----- diff --git a/gunicorn.cfg b/gunicorn.cfg index b37f3db..5f1bf36 100644 --- a/gunicorn.cfg +++ b/gunicorn.cfg @@ -1,5 +1,5 @@ workers = 10 bind = "0.0.0.0:8443" -certfile = "certdir/roomfinder.cert" -keyfile = "certdir/roomfinder.key" +certfile = "certs/cert.pem" +keyfile = "certs/key.pem" From 76afc895a2b6867d14f32fe1d2bdd9ba4431c5ea Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 17 Oct 2016 11:24:14 -0700 Subject: [PATCH 132/146] sgersapp: Move experimental code to mainstream Change-Id: I23d0fa9a6d61468e5b4f7a15f4f10b603ee49122 --- service/static/js/main.js | 276 +++++++++++++++++++------------ service/static/js/main3.js | 298 ---------------------------------- service/templates/index.html | 179 ++++++-------------- service/templates/index2.html | 147 ----------------- service/templates/index3.html | 60 ------- service/webserver.py | 35 ++-- service/webserver2.py | 152 ----------------- 7 files changed, 245 insertions(+), 902 deletions(-) delete mode 100644 service/static/js/main3.js delete mode 100644 service/templates/index2.html delete mode 100644 service/templates/index3.html delete mode 100755 service/webserver2.py diff --git a/service/static/js/main.js b/service/static/js/main.js index 38f790c..891f00b 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -1,85 +1,54 @@ -var buildings = [] -var cities = [] -var floors = ["1", "2", "3", "4", "5", "Any"]; -var times_hours = ["00", "01", "02","03", - "04", "05", "06", "07", - "08", "09", "10", "11", - "12", "13", "14", "15", - "16", "17", "18", "19", - "20", "21", "22", "23"]; -var times_mins = ["00", "15", "30","45"]; -var duration_hours = ["0h", "1h", "2h","3h", - "4h", "5h", "6h", "7h"]; -var duration_mins = ["00m", "15m", "30m","45m"]; -var sizes = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "15", "20", "25", "30", "50", "70", "100"]; -var date_years = ["2016", "2017"]; -var date_months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; -var date_days = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", - "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", - "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", - "31"]; - -var DAYS_AHEAD = 0; +var cities = []; +var buildings = []; +var floors = [];; +var attendees = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "15", "20", + "25", "30", "50", "70", "100"]; +//var times = ["00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", +// "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", +// "14:00", "15:00", "16:00", "17:00", "18:00", "18:00", "19:00", +// "20:00", "21:00", "22:00", "23:00"]; +var times = ["00:00", "00:30", "01:00", "01:30", "02:00", "02:30", "03:00", + "03:30", "04:00", "04:30", "05:00", "05:30", "06:00", "06:30", + "07:00", "07:30", "08:00", "08:30", "09:00", "09:30", "10:00", + "10:30", "11:00", "11:30", "12:00", "12:30", "13:00", "13:30", + "14:00", "14:30", "15:00", "15:30", "16:00", "16:30", "17:00", + "17:30", "18:00", "18:30", "19:00", "19:30", "20:00", "20:30", + "21:00", "21:30", "22:00", "22:30", "23:00", "23:30",]; +var startTimeIndex = null; +var endTimeIndex = null; + +var selectedRoom; function init(){ - hideUserPassword(); loadCitiesList(); - createCombo(buildingSelect, buildings); - createCombo(citySelect, cities); - createCombo(floorSelect, floors); - createCombo(roomSizeSelect, sizes); - createCombo(startTimeHourSelect, times_hours); - createCombo(startTimeMinSelect, times_mins); - createCombo(durationHourSelect, duration_hours); - createCombo(durationMinSelect, duration_mins); + + hideUsernamePasswordFields(); + hideRoomList(); + + createCombo(buildingSelect,buildings); + createCombo(floorSelect,floors); + createCombo(citySelect,cities); + createCombo(roomSizeSelect,attendees); citySelect.value = "San Jose"; loadBuildingList(citySelect.value); buildingSelect.value = "SJC19"; loadFloorList(buildingSelect.value); floorSelect.value = "3"; - durationMinSelect.value = "30m"; + if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(getCity); } - var date = new Date(); - var today = date.toISOString().split('T')[0]; - bookDate.value = today; - var current_hour = date.getHours(); - if (current_hour < 9) { - current_hour = "0" + current_hour - } - startTimeHourSelect.value = current_hour; -} - -function nextDate() { - var nextDate = new Date(); - DAYS_AHEAD += 1; - nextDate.setDate((new Date()).getDate() + DAYS_AHEAD); - bookDate.value = nextDate.toISOString().split('T')[0]; -} - -function prevDate() { - var nextDate = new Date(); - if (DAYS_AHEAD > 0) { - DAYS_AHEAD -= 1; - } - nextDate.setDate((new Date()).getDate() + DAYS_AHEAD); - bookDate.value = nextDate.toISOString().split('T')[0]; -} - -function hideUserPassword() { - userLabel.style.visibility = "hidden"; - userNameInput.style.visibility = "hidden"; - passwordLabel.style.visibility = "hidden"; - passwordInput.style.visibility = "hidden"; + setTodayDate(); + createTimeRows(times); + selectCurrentTime(); } function createCombo(container, data) { var options = ''; container.options.length = 0; - for (var i = 0; i < data.length; i++) { + for (var i=0; i < data.length; i++) { container.options.add(new Option(data[i], data[i])); } } @@ -109,7 +78,6 @@ function loadFloorList(buildingname) { xmlHttp.send(null); floors = JSON.parse(xmlHttp.responseText); createCombo(floorSelect, floors); - resultMessage.innerHTML = ""; } function loadBuildingList(city) { @@ -118,30 +86,114 @@ function loadBuildingList(city) { xmlHttp.send(null); buildings = JSON.parse(xmlHttp.responseText); createCombo(buildingSelect, buildings); + resultMessage.style.display = "none"; + resultMap.style.display = "none"; } -//Example: http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** +function createTimeRows(data) { + var i = 0; + for (i = 0; i < data.length; i++) { + tableContainer.innerHTML += "
"; + tableContainer.innerHTML += ""; + tableContainer.innerHTML += ""; + i++; + tableContainer.innerHTML += ""; + tableContainer.innerHTML += ""; + tableContainer.innerHTML += "
"; + } + tableContainer.innerHTML += "
"; + tableContainer.innerHTML += "
"; +} -//Example: http://127.0.0.1:5000/showrooms?roomname=SJC19-3&starttime=2016-08-25T09:00:00&endtime=2016-08-25T19:00:00&user=mrathor&password=**** +function handleTime(index) { + if ((startTimeIndex == null) || (index < startTimeIndex)) { + startTimeIndex = index; + endTimeIndex = index; + } + else if (startTimeIndex == endTimeIndex) { + endTimeIndex = index; + } + else { + startTimeIndex = index; + endTimeIndex = index; + } -function submitClickHandler() { - var tableHeaderRowCount = 1; - var rowCount = mytable.rows.length; - for (var i = tableHeaderRowCount; i < rowCount; i++) { - mytable.deleteRow(tableHeaderRowCount); - console.log("clearing row number:" + i); + var btn; + var lbl; + for (var i=0 ; i= startTimeIndex && i <= endTimeIndex) { + btn.classList.add('tableBtnEnabled'); + lbl.classList.add('timeLabelEnabled'); + } + else { + btn.classList.remove('tableBtnEnabled'); + lbl.classList.remove('timeLabelEnabled'); + } } - mytable.innerHTML = ""; - mytable.visiblity = false; - hideUserPassword(); +} - var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); - var timezone = new Date().getTimezoneOffset(); +function selectCurrentTime() { + var date = new Date(); + var current_hour = date.getHours(); + var current_min = date.getMinutes(); - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&buildingname=${buildingSelect.value}&floor=${floorSelect.value}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}&attendees=${roomSizeSelect.value}&timezone=${timezone}`; - loadRooms(queryString); + if (current_min < 15) { + current_min = "00"; + } + else if (current_min < 45) { + current_min = "30"; + } + else { + current_hour++; + current_min = "00"; + } + + if (current_hour < 10) { + current_hour = "0" + current_hour; + } + else { + current_hour = "" + current_hour; + } + + var selectTime = current_hour + ":" + current_min; + + for (var i=0 ; i"; + resultMessage.innerHTML = "Error: " + error; return; } + else { + resultMessage.style.display = "none"; + resultMap.style.display = "none"; + } - userLabel.style.visibility = "visible"; - userNameInput.style.visibility = "visible"; - passwordLabel.style.visibility = "visible"; - passwordInput.style.visibility = "visible"; - resultMessage.innerHTML = ""; - mytable.innerHTML += "Found " + Object.keys(rooms_json).length + " rooms"; + roomNamesContainer.style.display = "block"; + roomNamesContainer.innerHTML = ""; + roomNamesContainer.innerHTML += "

" + Object.keys(rooms_json).length + " room(s) available

"; for (var key in rooms_json) { var roomemail = rooms_json[key]["email"]; if (typeof roomemail != "undefined") { - mytable.innerHTML += '' + key + ''; - } - else { - mytable.innerHTML += '' + key + ''; + roomNamesContainer.innerHTML += "
"; } } - mytable.visiblity = true; } -function bookRoom(roomname, roomemail) { +function handleSelectRoomBtn (radioBtn) { + selectedRoom = radioBtn.value; + showUsernamePasswordFields(); +} + +function handleReserveBtnClick() { var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); var timezone = new Date().getTimezoneOffset(); - - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${roomname}&roomemail=${roomemail}&starttime=${bookDate.value}T${startTimeHourSelect.value}:${startTimeMinSelect.value}:00&duration=${durationHourSelect.value}${durationMinSelect.value}&timezone=${timezone}`; + var starttime = (eval('timeBtn' + startTimeIndex)).value; + var endtime = (eval('timeBtn' + (endTimeIndex + 1))).value; + var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${selectedRoom}&starttime=${starttime}&endtime=${endtime}&date=${dateInput.value}&timezone=${timezone}`; var xmlHttp = new XMLHttpRequest(); if (userNameInput.value == "") { @@ -213,10 +267,32 @@ function bookRoom(roomname, roomemail) { xmlHttp.open("GET", url, false); // false for synchronous request xmlHttp.send(null); - mytable.innerHTML = ""; - mytable.visiblity = false; - resultMessage.innerHTML = roomname + " " + xmlHttp.responseText; - hideUserPassword(); + hideRoomList(); + hideUsernamePasswordFields(); + resultMessage.style.display = "block"; + resultMap.style.display = "block"; + resultMessage.innerHTML = selectedRoom; + resultMap.innerHTML = xmlHttp.responseText; +} + +function hideRoomList() { + roomNamesContainer.innerHTML = ''; + roomNamesContainer.style.display = "none"; } +function showUsernamePasswordFields() { + var userpassHTML = ""; + userpassHTML += "
"; + userpassHTML += ""; + userpassHTML += "
"; + userpassHTML += ""; + userpassHTML += ""; + + usernamePasswordContainer.style.display = "block"; + usernamePasswordContainer.innerHTML = userpassHTML; +} + +function hideUsernamePasswordFields() { + usernamePasswordContainer.style.display = "none"; +} diff --git a/service/static/js/main3.js b/service/static/js/main3.js deleted file mode 100644 index 891f00b..0000000 --- a/service/static/js/main3.js +++ /dev/null @@ -1,298 +0,0 @@ -var cities = []; -var buildings = []; -var floors = [];; -var attendees = ["1", "2", "4", "5", "6", "7", "8", "9", "10", "15", "20", - "25", "30", "50", "70", "100"]; -//var times = ["00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", -// "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", -// "14:00", "15:00", "16:00", "17:00", "18:00", "18:00", "19:00", -// "20:00", "21:00", "22:00", "23:00"]; -var times = ["00:00", "00:30", "01:00", "01:30", "02:00", "02:30", "03:00", - "03:30", "04:00", "04:30", "05:00", "05:30", "06:00", "06:30", - "07:00", "07:30", "08:00", "08:30", "09:00", "09:30", "10:00", - "10:30", "11:00", "11:30", "12:00", "12:30", "13:00", "13:30", - "14:00", "14:30", "15:00", "15:30", "16:00", "16:30", "17:00", - "17:30", "18:00", "18:30", "19:00", "19:30", "20:00", "20:30", - "21:00", "21:30", "22:00", "22:30", "23:00", "23:30",]; -var startTimeIndex = null; -var endTimeIndex = null; - -var selectedRoom; - -function init(){ - loadCitiesList(); - - hideUsernamePasswordFields(); - hideRoomList(); - - createCombo(buildingSelect,buildings); - createCombo(floorSelect,floors); - createCombo(citySelect,cities); - createCombo(roomSizeSelect,attendees); - - citySelect.value = "San Jose"; - loadBuildingList(citySelect.value); - buildingSelect.value = "SJC19"; - loadFloorList(buildingSelect.value); - floorSelect.value = "3"; - - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition(getCity); - } - - setTodayDate(); - createTimeRows(times); - selectCurrentTime(); -} - -function createCombo(container, data) { - var options = ''; - container.options.length = 0; - for (var i=0; i < data.length; i++) { - container.options.add(new Option(data[i], data[i])); - } -} - -function loadCitiesList() { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("GET", "/showcities", false); - xmlHttp.send(null); - cities = JSON.parse(xmlHttp.responseText); -} - -function getCity(position) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("GET", "/getcity?latitude=" + position.coords.latitude + "&longitude=" + position.coords.longitude, false); - xmlHttp.send(null); - closestCity = JSON.parse(xmlHttp.responseText); - if (citySelect.value != closestCity) { - citySelect.value = closestCity; - loadBuildingList(citySelect.value); - loadFloorList(buildingSelect.value); - } -} - -function loadFloorList(buildingname) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("GET", "/showfloors?buildingname=" + buildingname, false); - xmlHttp.send(null); - floors = JSON.parse(xmlHttp.responseText); - createCombo(floorSelect, floors); -} - -function loadBuildingList(city) { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("GET", "/showbuildings?city=" + city, false); - xmlHttp.send(null); - buildings = JSON.parse(xmlHttp.responseText); - createCombo(buildingSelect, buildings); - resultMessage.style.display = "none"; - resultMap.style.display = "none"; -} - -function createTimeRows(data) { - var i = 0; - for (i = 0; i < data.length; i++) { - tableContainer.innerHTML += "
"; - tableContainer.innerHTML += ""; - tableContainer.innerHTML += ""; - i++; - tableContainer.innerHTML += ""; - tableContainer.innerHTML += ""; - tableContainer.innerHTML += "
"; - } - - tableContainer.innerHTML += "
"; - tableContainer.innerHTML += "
"; -} - -function handleTime(index) { - if ((startTimeIndex == null) || (index < startTimeIndex)) { - startTimeIndex = index; - endTimeIndex = index; - } - else if (startTimeIndex == endTimeIndex) { - endTimeIndex = index; - } - else { - startTimeIndex = index; - endTimeIndex = index; - } - - var btn; - var lbl; - for (var i=0 ; i= startTimeIndex && i <= endTimeIndex) { - btn.classList.add('tableBtnEnabled'); - lbl.classList.add('timeLabelEnabled'); - } - else { - btn.classList.remove('tableBtnEnabled'); - lbl.classList.remove('timeLabelEnabled'); - } - } -} - -function selectCurrentTime() { - var date = new Date(); - var current_hour = date.getHours(); - var current_min = date.getMinutes(); - - if (current_min < 15) { - current_min = "00"; - } - else if (current_min < 45) { - current_min = "30"; - } - else { - current_hour++; - current_min = "00"; - } - - if (current_hour < 10) { - current_hour = "0" + current_hour; - } - else { - current_hour = "" + current_hour; - } - - var selectTime = current_hour + ":" + current_min; - - for (var i=0 ; i
"; - for (var key in rooms_json) { - var roomemail = rooms_json[key]["email"]; - if (typeof roomemail != "undefined") { - roomNamesContainer.innerHTML += "
"; - } - } -} - -function handleSelectRoomBtn (radioBtn) { - selectedRoom = radioBtn.value; - showUsernamePasswordFields(); -} - -function handleReserveBtnClick() { - var passwordb64 = encodeURIComponent(btoa(passwordInput.value)); - var timezone = new Date().getTimezoneOffset(); - var starttime = (eval('timeBtn' + startTimeIndex)).value; - var endtime = (eval('timeBtn' + (endTimeIndex + 1))).value; - var queryString = `\?user=${userNameInput.value}\&password=${passwordb64}&roomname=${selectedRoom}&starttime=${starttime}&endtime=${endtime}&date=${dateInput.value}&timezone=${timezone}`; - var xmlHttp = new XMLHttpRequest(); - - if (userNameInput.value == "") { - userNameInput.style.backgroundColor = "yellow"; - } - else { - userNameInput.style.backgroundColor = ""; - } - - if (passwordInput.value == "") { - passwordInput.style.backgroundColor = "yellow"; - } - else { - passwordInput.style.backgroundColor = ""; - } - - if (userNameInput.value == "" || passwordInput.value == "") { - return; - } - - url = "/bookroom"; - url = url.concat(queryString); - - xmlHttp.open("GET", url, false); // false for synchronous request - xmlHttp.send(null); - hideRoomList(); - hideUsernamePasswordFields(); - resultMessage.style.display = "block"; - resultMap.style.display = "block"; - resultMessage.innerHTML = selectedRoom; - resultMap.innerHTML = xmlHttp.responseText; -} - -function hideRoomList() { - roomNamesContainer.innerHTML = ''; - roomNamesContainer.style.display = "none"; -} - -function showUsernamePasswordFields() { - var userpassHTML = ""; - userpassHTML += "
"; - userpassHTML += ""; - userpassHTML += "
"; - userpassHTML += ""; - userpassHTML += ""; - - usernamePasswordContainer.style.display = "block"; - usernamePasswordContainer.innerHTML = userpassHTML; -} - -function hideUsernamePasswordFields() { - usernamePasswordContainer.style.display = "none"; -} - diff --git a/service/templates/index.html b/service/templates/index.html index a9f21b2..ace3e9c 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -1,137 +1,60 @@ - - + + - - + Room finder + + + + + + -
- -
- Report a bug       - Contact Us       -
-
- -
+
+
+

Conference Room Finder

+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
- -
+ +
-
+
+
+
+
+
+ +
+
+
+ diff --git a/service/templates/index2.html b/service/templates/index2.html deleted file mode 100644 index 1587585..0000000 --- a/service/templates/index2.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - -
- -
- Report a bug       - Contact Us       -
-
- -
-
- - - - - - - - - - - - - - - - - - -
-
- -
-
- -
-
-
-
-
- -
-
- - - -
-
- - -
-
- - -
- -

-
- - -
- - - - -
-
- - - diff --git a/service/templates/index3.html b/service/templates/index3.html deleted file mode 100644 index ace3e9c..0000000 --- a/service/templates/index3.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - Room finder - - - - - - - - - -
-
-

Conference Room Finder

-
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
-
- - -
- -
-
-
-
-
- -
-
-
-
- - - diff --git a/service/webserver.py b/service/webserver.py index e538ea0..ff84069 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -21,15 +21,15 @@ @APP.route('/') def index(): """ Serve static index file """ - return flask.render_template('index.html') + return flask.render_template('index3.html') @APP.route('/getfloormap', methods=['GET']) def get_floor_map(): bldgfloorname = flask.request.args.get('bldgfloorname') return flask.send_file(os.path.join(common.FLOORMAP_DIR, bldgfloorname)) -QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, starttime, duration, user, password, attendees, timezone') -BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, roomemail, starttime, duration, user, password, timezone') +QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, date, starttime, endtime, user, password, attendees, timezone') +BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, date, starttime, endtime, user, password, timezone') @APP.route('/getcity', methods=['GET']) def get_city(): @@ -74,8 +74,9 @@ def show_rooms(): """ Serve list of rooms in JSON """ queryparam = QueryParam(buildingname=flask.request.args.get('buildingname'), floor=flask.request.args.get('floor'), + date=flask.request.args.get('date'), starttime=flask.request.args.get('starttime'), - duration=flask.request.args.get('duration'), + endtime=flask.request.args.get('endtime'), user=flask.request.args.get('user'), password=flask.request.args.get('password'), attendees=flask.request.args.get('attendees'), @@ -89,8 +90,8 @@ def show_rooms(): try: room_finder = AvailRoomFinder(user=queryparam.user, password=queryparam.password, - start_time=queryparam.starttime, - duration=queryparam.duration, + start_time=queryparam.date + "T" + queryparam.starttime + ":00", + end_time=queryparam.date + "T" + queryparam.endtime + ":00", timezone=queryparam.timezone) rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) except Exception as exception: @@ -103,30 +104,30 @@ def show_rooms(): def book_room(): """ Reserve specified room """ queryparam = BookRoomQueryParam(roomname=flask.request.args.get('roomname'), - roomemail=flask.request.args.get('roomemail'), + date=flask.request.args.get('date'), starttime=flask.request.args.get('starttime'), - duration=flask.request.args.get('duration'), + endtime=flask.request.args.get('endtime'), user=flask.request.args.get('user'), password=flask.request.args.get('password'), timezone=flask.request.args.get('timezone')) + room_finder = ReserveAvailRoom(user=queryparam.user, password=queryparam.password, roomname=queryparam.roomname, - roomemail=queryparam.roomemail, - start_time=queryparam.starttime, - duration=queryparam.duration, + start_time=queryparam.date + "T" + queryparam.starttime + ":00", + end_time=queryparam.date + "T" + queryparam.endtime + ":00", timezone=queryparam.timezone) try: if room_finder.reserve_room(): common.LOGGER.warning("User %s reservation of %s succeeded", queryparam.user, queryparam.roomname) - bldg, floor, unused = queryparam.roomname.split('-', 2) - URL = "https://wwwin.cisco.com/c/dam/cec/organizations/gbs/wpr/FloorPlans/{}-AFP-{}.pdf".format(bldg, floor) + bldg, floor, unused = queryparam.roomname.split('-', 2) + URL = "https://wwwin.cisco.com/c/dam/cec/organizations/gbs/wpr/FloorPlans/{}-AFP-{}.pdf".format(bldg, floor) curl_command = "curl --location-trusted -L --ntlm -c cookies.txt -u " + pipes.quote(queryparam.user) + ":" + pipes.quote(base64.b64decode(queryparam.password)) + " " + URL + " -o " + common.FLOORMAP_DIR + "/{}-AFP-{}.pdf".format(bldg, floor) - curl_process = subprocess.Popen(curl_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - response = curl_process.communicate()[0] - common.LOGGER.warning("Floor map for building %s floor %s downloaded", bldg, floor) - return "Reservation Requested


".format(bldg, floor) + curl_process = subprocess.Popen(curl_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + response = curl_process.communicate()[0] + common.LOGGER.warning("Floor map for building %s floor %s downloaded", bldg, floor) + return "Reservation Requested


".format(bldg, floor) else: common.LOGGER.warning("User %s reservation of %s failed", queryparam.user, queryparam.roomname) diff --git a/service/webserver2.py b/service/webserver2.py deleted file mode 100755 index ff84069..0000000 --- a/service/webserver2.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -Webservice APIs for room finder backend -""" - -import base64 -import collections -import json -import os -import pipes -import socket -import subprocess - -import common -import flask - -from book_room import ReserveAvailRoom -from find_available_room import AvailRoomFinder - -APP = flask.Flask(__name__, template_folder=common.TEMPLATE_FOLDER) - -@APP.route('/') -def index(): - """ Serve static index file """ - return flask.render_template('index3.html') - -@APP.route('/getfloormap', methods=['GET']) -def get_floor_map(): - bldgfloorname = flask.request.args.get('bldgfloorname') - return flask.send_file(os.path.join(common.FLOORMAP_DIR, bldgfloorname)) - -QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, date, starttime, endtime, user, password, attendees, timezone') -BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, date, starttime, endtime, user, password, timezone') - -@APP.route('/getcity', methods=['GET']) -def get_city(): - """ Get closest city in JSON """ - latitude = flask.request.args.get('latitude') - longitude = flask.request.args.get('longitude') - city = common.get_closest_city(float(latitude), float(longitude)) - common.LOGGER.info("Closest city is %s based on coordinates: %s, %s", - city, latitude, longitude) - return json.dumps(city) - -@APP.route('/showcities', methods=['GET']) -def show_cities(): - """ Serve list of cities in JSON """ - cities = common.get_city_list() - common.LOGGER.debug("Read list of %d cities from database", len(cities)) - return json.dumps(cities) - -@APP.route('/showbuildings', methods=['GET']) -def show_buldings(): - """ Serve list of buildings in JSON """ - city = flask.request.args.get('city') - buildings = common.get_building_list(city) - common.LOGGER.debug("%d buildings in %s", len(buildings), city) - return json.dumps(buildings) - -@APP.route('/showfloors', methods=['GET']) -def show_floors(): - """ Serve list of floors in JSON """ - buildingname = flask.request.args.get('buildingname') - floors = common.get_floor_list(buildingname) - common.LOGGER.debug("%d floors in %s", len(floors), buildingname) - if len(floors) > 1: - return json.dumps(floors + ["Any"]) - else: - return json.dumps(floors) - -# Example Query -# http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password -@APP.route('/showrooms', methods=['GET']) -def show_rooms(): - """ Serve list of rooms in JSON """ - queryparam = QueryParam(buildingname=flask.request.args.get('buildingname'), - floor=flask.request.args.get('floor'), - date=flask.request.args.get('date'), - starttime=flask.request.args.get('starttime'), - endtime=flask.request.args.get('endtime'), - user=flask.request.args.get('user'), - password=flask.request.args.get('password'), - attendees=flask.request.args.get('attendees'), - timezone=flask.request.args.get('timezone')) - - if queryparam.floor.startswith("Any"): - prefix = queryparam.buildingname - else: - prefix = queryparam.buildingname + '-' + queryparam.floor - - try: - room_finder = AvailRoomFinder(user=queryparam.user, - password=queryparam.password, - start_time=queryparam.date + "T" + queryparam.starttime + ":00", - end_time=queryparam.date + "T" + queryparam.endtime + ":00", - timezone=queryparam.timezone) - rooms_info = room_finder.search_free(prefix, min_size=int(queryparam.attendees)) - except Exception as exception: - common.LOGGER.warning("User %s query resulted in an error: %s", - queryparam.user, str(exception)) - rooms_info = {"Error" : str(exception)} - return json.dumps(rooms_info) - -@APP.route('/bookroom', methods=['GET']) -def book_room(): - """ Reserve specified room """ - queryparam = BookRoomQueryParam(roomname=flask.request.args.get('roomname'), - date=flask.request.args.get('date'), - starttime=flask.request.args.get('starttime'), - endtime=flask.request.args.get('endtime'), - user=flask.request.args.get('user'), - password=flask.request.args.get('password'), - timezone=flask.request.args.get('timezone')) - - room_finder = ReserveAvailRoom(user=queryparam.user, - password=queryparam.password, - roomname=queryparam.roomname, - start_time=queryparam.date + "T" + queryparam.starttime + ":00", - end_time=queryparam.date + "T" + queryparam.endtime + ":00", - timezone=queryparam.timezone) - try: - if room_finder.reserve_room(): - common.LOGGER.warning("User %s reservation of %s succeeded", - queryparam.user, queryparam.roomname) - bldg, floor, unused = queryparam.roomname.split('-', 2) - URL = "https://wwwin.cisco.com/c/dam/cec/organizations/gbs/wpr/FloorPlans/{}-AFP-{}.pdf".format(bldg, floor) - curl_command = "curl --location-trusted -L --ntlm -c cookies.txt -u " + pipes.quote(queryparam.user) + ":" + pipes.quote(base64.b64decode(queryparam.password)) + " " + URL + " -o " + common.FLOORMAP_DIR + "/{}-AFP-{}.pdf".format(bldg, floor) - curl_process = subprocess.Popen(curl_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - response = curl_process.communicate()[0] - common.LOGGER.warning("Floor map for building %s floor %s downloaded", bldg, floor) - return "Reservation Requested


".format(bldg, floor) - else: - common.LOGGER.warning("User %s reservation of %s failed", - queryparam.user, queryparam.roomname) - return "reservation failed" - except Exception as exception: - common.LOGGER.warning("User %s reservation of %s resulted in an error: %s", - queryparam.user, queryparam.roomname, str(exception)) - return "reservation failed: " + str(exception) - -def create_ssl_context(): - """ Create SSL context """ - context = (os.path.join(common.CERT_DIR, 'roomfinder.cert'), - os.path.join(common.CERT_DIR, 'roomfinder.key')) - return context - -if __name__ == '__main__': - if common.HTTPS_ENABLED: - APP.run(threaded=True, host=socket.gethostname(), - ssl_context=create_ssl_context(), port=common.HTTPS_PORT) - else: - APP.run(threaded=True, host=socket.gethostname(), - port=common.HTTP_PORT) From e9138f6d0ea92e88e24b134b080d4d0fea1c71af Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 17 Oct 2016 13:46:44 -0700 Subject: [PATCH 133/146] sgersapp: Refer to the correct files Change-Id: I8821da6f173881cc77c6555f5b5a8f99d678d570 --- service/templates/index.html | 2 +- service/webserver.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/service/templates/index.html b/service/templates/index.html index ace3e9c..f407e91 100644 --- a/service/templates/index.html +++ b/service/templates/index.html @@ -6,7 +6,7 @@ - + diff --git a/service/webserver.py b/service/webserver.py index ff84069..a1a1a81 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -21,7 +21,7 @@ @APP.route('/') def index(): """ Serve static index file """ - return flask.render_template('index3.html') + return flask.render_template('index.html') @APP.route('/getfloormap', methods=['GET']) def get_floor_map(): From 3c2cf51eb2c50e5293eb8e552130b2ef10829a82 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 1 May 2017 11:59:18 -0700 Subject: [PATCH 134/146] sgersapp: Replace old rooms with new ones Change-Id: If20d657e6722d10789b343431ccd9223ee6e5cc8 --- find_rooms.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/find_rooms.py b/find_rooms.py index 9fd780c..11006a2 100644 --- a/find_rooms.py +++ b/find_rooms.py @@ -30,9 +30,18 @@ def __init__(self, user, password, filename='rooms.csv', append=True): def _search(self, prefix): return self.exchange_api.find_rooms(prefix=prefix) + def _search_to_be_deleted(self, prefix, newrooms): + to_be_deleted = [] + for room in self.rooms: + if room not in newrooms and room.startswith(prefix): + to_be_deleted.append(room) + print "--", room + return to_be_deleted + def search(self, prefix, deep=False): """ Search for rooms with names starting with specified prefix """ rooms_found = self._search(prefix) + to_be_deleted = self._search_to_be_deleted(prefix, rooms_found) if deep: symbols = string.letters + string.digits @@ -42,6 +51,9 @@ def search(self, prefix, deep=False): common.LOGGER.info("Search for prefix '%s' yielded %d rooms.", prefix, len(rooms_found)) self.rooms.update(rooms_found) + for room in to_be_deleted: + common.LOGGER.info("Deleting room '%s' room for prefix '%s'.", room, prefix) + del self.rooms[room] def dump(self): """ Dump the results to specified file """ From b686a9038ccee8e253a1e6824e25ffc55cb723ad Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Mon, 25 Sep 2017 18:09:53 -0700 Subject: [PATCH 135/146] sgersapp: Add API to check current room status Change-Id: I0504c1d89d86bb5e53ae86efb036e1885752cbc8 --- room_status.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 room_status.py diff --git a/room_status.py b/room_status.py new file mode 100644 index 0000000..e1df9ef --- /dev/null +++ b/room_status.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +APIs to query an Exchange Server for availability status of rooms +""" + +import argparse +import base64 +import getpass +import sys +import urllib + +import common +from exchange_api import ExchangeApi + +reload(sys) +sys.setdefaultencoding("utf-8") + +SEPARATOR = "-" * 120 + "\n" +TABLE_FORMAT = "{0:40s} {1:64s} {2:20s}\n" +TABLE_HEADER = SEPARATOR + TABLE_FORMAT.format("Status", "Room", "Email") + SEPARATOR + +class RoomStatus(object): + """ Class to query an Exchange Server for status of specified room """ + + def __init__(self, user, password): + self.user = user + self.start_time = common.TIME_NOW + self.end_time = common.end_time(self.start_time, "15m") + self.room_info = {} + self.timezone = common.SJ_TIME_ZONE + self.error = None + self.exchange_api = ExchangeApi(user, base64.b64decode(urllib.unquote(password))) + + def status(self, room_email): + common.LOGGER.debug("Querying for %s", room_email) + + try: + if '@' not in room_email: + room_info = self.exchange_api.find_rooms(prefix=room_email) + if not room_info: + raise Exception("No room with that name") + room_email = room_info.keys()[0]["email"] + + room_info = self.exchange_api.room_status( \ + room_email=room_email, + start_time=self.start_time, + end_time=self.end_time, + timezone_offset=self.timezone) + + return room_info + except Exception as exception: + self.error = exception + common.LOGGER.warning("Exception querying room %s: %s", room_email, str(exception)) + return {} + +def run(): + """ Parse command-line arguments and invoke room availability finder """ + parser = argparse.ArgumentParser() + parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) + parser.add_argument("-r", "--roomemail", help="e-mail address of the room", required=True) + + args = parser.parse_args() + if args.user == 'anon': + args.password = '' + else: + args.password = base64.b64encode(getpass.getpass("Password:")) + room_finder = RoomStatus(user=args.user, password=args.password) + + print room_finder.status(args.roomemail) + + +if __name__ == '__main__': + run() From 8856778a732bdd3abf23aebf1f9cb843cd6f4a76 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 27 Sep 2017 11:29:07 -0700 Subject: [PATCH 136/146] sgersapp: Query room status anonymously Change-Id: Id4650cfb896a6c39317700c85a19381476de5fd3 --- room_status.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/room_status.py b/room_status.py index e1df9ef..5ceea20 100644 --- a/room_status.py +++ b/room_status.py @@ -23,14 +23,13 @@ class RoomStatus(object): """ Class to query an Exchange Server for status of specified room """ - def __init__(self, user, password): - self.user = user + def __init__(self): self.start_time = common.TIME_NOW self.end_time = common.end_time(self.start_time, "15m") self.room_info = {} self.timezone = common.SJ_TIME_ZONE self.error = None - self.exchange_api = ExchangeApi(user, base64.b64decode(urllib.unquote(password))) + self.exchange_api = ExchangeApi('', '') def status(self, room_email): common.LOGGER.debug("Querying for %s", room_email) @@ -57,17 +56,11 @@ def status(self, room_email): def run(): """ Parse command-line arguments and invoke room availability finder """ parser = argparse.ArgumentParser() - parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) parser.add_argument("-r", "--roomemail", help="e-mail address of the room", required=True) args = parser.parse_args() - if args.user == 'anon': - args.password = '' - else: - args.password = base64.b64encode(getpass.getpass("Password:")) - room_finder = RoomStatus(user=args.user, password=args.password) - - print room_finder.status(args.roomemail) + room = RoomStatus() + print room.status(args.roomemail) if __name__ == '__main__': From 9a7d486173b4031546e635935f07c560bc549290 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 27 Sep 2017 11:30:21 -0700 Subject: [PATCH 137/146] sgersapp: Fix pylint issues Change-Id: Id4576d7a9dc307d2f3472353728764f484e73314 --- room_status.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/room_status.py b/room_status.py index 5ceea20..33a39c9 100644 --- a/room_status.py +++ b/room_status.py @@ -5,10 +5,7 @@ """ import argparse -import base64 -import getpass import sys -import urllib import common from exchange_api import ExchangeApi From c8a833e5220957556e0612e794106c7cd5f16f81 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 27 Sep 2017 11:50:21 -0700 Subject: [PATCH 138/146] sgersapp: Better error handling Change-Id: I3b8077e2bc80589ed96d01964b15bda4c1c37edc --- room_status.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/room_status.py b/room_status.py index 33a39c9..5ecc890 100644 --- a/room_status.py +++ b/room_status.py @@ -34,8 +34,8 @@ def status(self, room_email): try: if '@' not in room_email: room_info = self.exchange_api.find_rooms(prefix=room_email) - if not room_info: - raise Exception("No room with that name") + if len(room_info) != 1: + raise Exception('No room with that name') room_email = room_info.keys()[0]["email"] room_info = self.exchange_api.room_status( \ @@ -44,11 +44,14 @@ def status(self, room_email): end_time=self.end_time, timezone_offset=self.timezone) + if not room_info.get('freebusy'): + raise Exception('Room not found') + return room_info except Exception as exception: self.error = exception - common.LOGGER.warning("Exception querying room %s: %s", room_email, str(exception)) - return {} + common.LOGGER.warning("Exception querying room %s: %s", room_email, exception.message) + return {'error': exception.message} def run(): """ Parse command-line arguments and invoke room availability finder """ From 87e0022a5a6dc25c206b9e36592f6fbc16156272 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 27 Sep 2017 11:51:04 -0700 Subject: [PATCH 139/146] sgersapp: Add endpoint for querying room status Change-Id: Iab629ea90562142af58d7ba62119e636f9511292 --- service/webserver.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/service/webserver.py b/service/webserver.py index a1a1a81..6cb84e1 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -15,6 +15,7 @@ from book_room import ReserveAvailRoom from find_available_room import AvailRoomFinder +from room_status import RoomStatus APP = flask.Flask(__name__, template_folder=common.TEMPLATE_FOLDER) @@ -29,6 +30,7 @@ def get_floor_map(): return flask.send_file(os.path.join(common.FLOORMAP_DIR, bldgfloorname)) QueryParam = collections.namedtuple('QueryParam', 'buildingname, floor, date, starttime, endtime, user, password, attendees, timezone') +RoomStatusQueryParam = collections.namedtuple('QueryParam', 'roomemail') BookRoomQueryParam = collections.namedtuple('QueryParam', 'roomname, date, starttime, endtime, user, password, timezone') @APP.route('/getcity', methods=['GET']) @@ -68,7 +70,18 @@ def show_floors(): return json.dumps(floors) # Example Query -# http://127.0.0.1:5000/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password +# http://127.0.0.1/showrooms?room_email=a@a.com +@APP.route('/roomstatus', methods=['GET']) +def room_status(): + """ Checks if room is free for next 15 mins """ + queryparam = RoomStatusQueryParam(roomemail=flask.request.args.get('roomemail')) + room = RoomStatus() + status = room.status(queryparam.roomemail) + return json.dumps(status) + + +# Example Query +# http://127.0.0.1/showrooms?building_floor_name=ABC&starttime=2016-08-25T09:00:00-13:00&duration=1h&user=USER&password=password @APP.route('/showrooms', methods=['GET']) def show_rooms(): """ Serve list of rooms in JSON """ From 9f8df965928c70208441dee4085a70a354d99186 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 27 Sep 2017 13:53:21 -0700 Subject: [PATCH 140/146] sgersapp: Fix errors caused by qoutes Change-Id: I005c0b724c43f89ab3a6386a557f4b29d2283744 --- exchange_api.py | 1 + service/static/js/main.js | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/exchange_api.py b/exchange_api.py index f16290a..781da16 100644 --- a/exchange_api.py +++ b/exchange_api.py @@ -100,6 +100,7 @@ def reserve_room(self, room_email, room_name, start_time, end_time, timezone_off RESERVE_XML = self._read_template("reserve_resource_template.xml") user_email = self.user + '@' + self.domain + room_name = room_name.replace("'", "") # Quotes cause all sorts of errors meeting_body = '{0} booked via RoomFinder by {1}'.format(room_name, user_email) subject = 'RoomFinder: {0}'.format(room_name) diff --git a/service/static/js/main.js b/service/static/js/main.js index 891f00b..e30edfe 100644 --- a/service/static/js/main.js +++ b/service/static/js/main.js @@ -34,7 +34,7 @@ function init(){ loadBuildingList(citySelect.value); buildingSelect.value = "SJC19"; loadFloorList(buildingSelect.value); - floorSelect.value = "3"; + floorSelect.value = "2"; if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(getCity); @@ -221,12 +221,12 @@ function showFreeRooms(rooms_json) { } roomNamesContainer.style.display = "block"; - roomNamesContainer.innerHTML = ""; - roomNamesContainer.innerHTML += "

" + Object.keys(rooms_json).length + " room(s) available

"; + roomNamesContainer.innerHTML = ''; + roomNamesContainer.innerHTML += '

' + Object.keys(rooms_json).length + ' room(s) available

'; for (var key in rooms_json) { var roomemail = rooms_json[key]["email"]; if (typeof roomemail != "undefined") { - roomNamesContainer.innerHTML += "
"; + roomNamesContainer.innerHTML += '
'; } } } From 355bc6056a0c0af2dbe290a5fef7f5ad3527643a Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 27 Sep 2017 15:26:23 -0700 Subject: [PATCH 141/146] sgersapp: Make time_now a function instead of a constant Change-Id: I7ee4cfbe6897710fc0070fe5330e12d2832682fb --- book_room.py | 4 ++-- common.py | 3 ++- find_available_room.py | 4 ++-- room_status.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/book_room.py b/book_room.py index ca28215..07008c5 100644 --- a/book_room.py +++ b/book_room.py @@ -21,7 +21,7 @@ class ReserveAvailRoom(object): def __init__(self, user, password, roomname, roomemail=None, - start_time=common.TIME_NOW, duration=None, end_time=None, + start_time=common.time_now(), duration=None, end_time=None, raw_password=None, timezone=common.SJ_TIME_ZONE): self.user = user @@ -65,7 +65,7 @@ def run(): parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", - default=common.TIME_NOW) + default=common.time_now()) parser.add_argument("-d", "--duration", help="Duration e.g. 30m (default = 1h)", default='1h') diff --git a/common.py b/common.py index 7d16873..ec8fb6d 100644 --- a/common.py +++ b/common.py @@ -26,7 +26,6 @@ BUILDINGS_CACHE = {} FLOORS_CACHE = {} -TIME_NOW = datetime.datetime.now().replace(microsecond=0).isoformat() SJ_TIME_ZONE = "420" logging.basicConfig(filename='access.log', @@ -36,6 +35,8 @@ LOGGER = logging.getLogger('roomfinder') logging.getLogger('werkzeug').setLevel(logging.ERROR) +def time_now(): + return datetime.datetime.now().replace(microsecond=0).isoformat() def end_time(start_time, duration): """ Calculate end time, given start time and duration """ diff --git a/find_available_room.py b/find_available_room.py index 13a928e..245bf49 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -25,7 +25,7 @@ class AvailRoomFinder(object): """ Class to query an Exchange Server for availability status of rooms """ def __init__(self, user, password, - start_time=common.TIME_NOW, duration=None, end_time=None, + start_time=common.time_now(), duration=None, end_time=None, filename=common.ROOMS_CSV, timezone=common.SJ_TIME_ZONE): self.rooms = common.read_room_list(filename) self.user = user @@ -114,7 +114,7 @@ def run(): default='') parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", - default=common.TIME_NOW) + default=common.time_now()) parser.add_argument("-duration", "--duration", help="Duration e.g. 1h or 15m (default = 1h)", default='1h') diff --git a/room_status.py b/room_status.py index 5ecc890..c7755be 100644 --- a/room_status.py +++ b/room_status.py @@ -21,7 +21,7 @@ class RoomStatus(object): """ Class to query an Exchange Server for status of specified room """ def __init__(self): - self.start_time = common.TIME_NOW + self.start_time = common.time_now() self.end_time = common.end_time(self.start_time, "15m") self.room_info = {} self.timezone = common.SJ_TIME_ZONE From 81595c6d4ac432c9aa09d6eb8c4ef4f0006dbd35 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 24 Oct 2017 15:10:20 -0700 Subject: [PATCH 142/146] sgersapp: Anonymously running find_available_room Change-Id: I9faf6045b998b2c8058098e2fcd1c64b7799fff8 --- find_available_room.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index 245bf49..c040ec3 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -54,6 +54,9 @@ def search_free(self, prefix, min_size=1): free_room_info[roomname] = selected_room_info[roomname] return free_room_info + def search_common_free(self, emails): + pass + def _query(self, roomname): room_size = self.rooms[roomname]["size"] email = self.rooms[roomname]["email"] @@ -108,10 +111,10 @@ def search(self, selected_rooms=None): def run(): """ Parse command-line arguments and invoke room availability finder """ parser = argparse.ArgumentParser() - parser.add_argument("-u", "--user", help="user name for exchange/outlook", required=True) + parser.add_argument("-u", "--user", help="user name for exchange/outlook") parser.add_argument("-prefix", "--prefix", help="A prefix to search for. e.g. 'SJC19- SJC18-'", - default='') + default='SJC19-2-') parser.add_argument("-start", "--starttime", help="Starttime e.g. 2014-07-02T11:00:00 (default = now)", default=common.time_now()) @@ -123,12 +126,17 @@ def run(): default=common.ROOMS_CSV) args = parser.parse_args() - args.password = base64.b64encode(getpass.getpass("Password:")) + + if args.user: + args.password = base64.b64encode(getpass.getpass("Password:")) + else: + args.user = 'anon' + args.password = '' room_finder = AvailRoomFinder(user=args.user, password=args.password, start_time=args.starttime, duration=args.duration, filename=args.file) - room_finder.search_free(prefix=args.prefix) + print room_finder.search_free(prefix=args.prefix) if __name__ == '__main__': From a7bc1ee493ff8a3d19b1f7b22a03ad0e8394968f Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 24 Oct 2017 15:53:53 -0700 Subject: [PATCH 143/146] sgersapp: Add API to query schedule for emails Change-Id: I12e5a14a8d383f0a9f9c4c243e043cb82f2659ad --- find_available_room.py | 17 ++++++++++++----- service/webserver.py | 26 +++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/find_available_room.py b/find_available_room.py index c040ec3..4f8b308 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -55,12 +55,18 @@ def search_free(self, prefix, min_size=1): return free_room_info def search_common_free(self, emails): - pass + """ Look for common free times for selected emails """ + return self.search(emails) def _query(self, roomname): - room_size = self.rooms[roomname]["size"] - email = self.rooms[roomname]["email"] - common.LOGGER.debug("Querying for %s", roomname) + if '@' not in roomname: + room_size = self.rooms[roomname]["size"] + email = self.rooms[roomname]["email"] + common.LOGGER.debug("Querying for room %s", roomname) + else: + room_size = 0 + email = roomname + common.LOGGER.debug("Querying for email %s", roomname) try: room_info = self.exchange_api.room_status( \ @@ -136,7 +142,8 @@ def run(): room_finder = AvailRoomFinder(user=args.user, password=args.password, start_time=args.starttime, duration=args.duration, filename=args.file) - print room_finder.search_free(prefix=args.prefix) +# print room_finder.search_free(prefix=args.prefix) + print room_finder.search_common_free(['sgersapp@cisco.com', 'ratri@cisco.com']) if __name__ == '__main__': diff --git a/service/webserver.py b/service/webserver.py index 6cb84e1..6382440 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -70,7 +70,7 @@ def show_floors(): return json.dumps(floors) # Example Query -# http://127.0.0.1/showrooms?room_email=a@a.com +# http://127.0.0.1/roomstatus?room_email=a@a.com @APP.route('/roomstatus', methods=['GET']) def room_status(): """ Checks if room is free for next 15 mins """ @@ -113,6 +113,30 @@ def show_rooms(): rooms_info = {"Error" : str(exception)} return json.dumps(rooms_info) +# Example Query +# http://127.0.0.1/schedule?emails=a@a.com,b@b.com&date=2016-08-25&timezone=420 +@APP.route('/schedule', methods=['GET']) +def show_schedule(): + """ Serve schedule of users in JSON """ + date = date=flask.request.args.get('date') + emails = flask.request.args.get('emails').split(',') + timezone = flask.request.args.get('timezone') + + try: + room_finder = AvailRoomFinder(user='anon', + password='', + start_time=date + "T00:00:00", + end_time=date + "T23:59:59", + timezone=timezone) + rooms_info = room_finder.search_common_free(emails) + except Exception as exception: + common.LOGGER.warning("User %s query resulted in an error: %s", + queryparam.user, str(exception)) + rooms_info = {"Error" : str(exception)} + return json.dumps(rooms_info) + + + @APP.route('/bookroom', methods=['GET']) def book_room(): """ Reserve specified room """ From 5ab540512219751d6b22921d3dbfd9061f69627a Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 24 Oct 2017 16:41:08 -0700 Subject: [PATCH 144/146] sgersapp: Return combined schedule Change-Id: I42a0c3132cb14007d917e959f7955ac800c12d06 --- find_available_room.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/find_available_room.py b/find_available_room.py index 4f8b308..91ee6c5 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -12,6 +12,7 @@ import urllib import common +from operator import add from exchange_api import ExchangeApi reload(sys) @@ -56,7 +57,18 @@ def search_free(self, prefix, min_size=1): def search_common_free(self, emails): """ Look for common free times for selected emails """ - return self.search(emails) + def clean_free_busy(schedule): + freebusy = list(schedule['freebusy']) + return map(lambda x: int(int(x) > 0) * 100, freebusy) + + schedules = self.search(emails).values() + all_freebusy = map(clean_free_busy, schedules) + valid_freebusy = filter(lambda x: len(x) > 1, all_freebusy) + combined_freebusy = map(add, *valid_freebusy) + + N = len(valid_freebusy) + percent_combined_freebusy = map(lambda x: x/N, combined_freebusy) + return percent_combined_freebusy def _query(self, roomname): if '@' not in roomname: From d8ff7f3a0b902805183ace76ed3b76eb84537db9 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Tue, 24 Oct 2017 16:41:25 -0700 Subject: [PATCH 145/146] sgersapp: Fix logging error Change-Id: Ifd3a3ad872b876c7c7a44c8e5745a1966c209c73 --- service/webserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/webserver.py b/service/webserver.py index 6382440..1fd9632 100755 --- a/service/webserver.py +++ b/service/webserver.py @@ -130,8 +130,8 @@ def show_schedule(): timezone=timezone) rooms_info = room_finder.search_common_free(emails) except Exception as exception: - common.LOGGER.warning("User %s query resulted in an error: %s", - queryparam.user, str(exception)) + common.LOGGER.warning("Query for emails %s resulted in an error: %s", + ', '.join(emails), str(exception)) rooms_info = {"Error" : str(exception)} return json.dumps(rooms_info) From c75c60ffa98d76e95139231dba0707280edc7bc8 Mon Sep 17 00:00:00 2001 From: Saish Gersappa Date: Wed, 25 Oct 2017 11:26:23 -0700 Subject: [PATCH 146/146] sgersapp: Fix summation when there are more than 2 emails Change-Id: Icea079af2e10c35c5ba41e3872f8b42fd66038df --- find_available_room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/find_available_room.py b/find_available_room.py index 91ee6c5..53e9310 100644 --- a/find_available_room.py +++ b/find_available_room.py @@ -64,7 +64,7 @@ def clean_free_busy(schedule): schedules = self.search(emails).values() all_freebusy = map(clean_free_busy, schedules) valid_freebusy = filter(lambda x: len(x) > 1, all_freebusy) - combined_freebusy = map(add, *valid_freebusy) + combined_freebusy = map(sum, zip(*valid_freebusy)) N = len(valid_freebusy) percent_combined_freebusy = map(lambda x: x/N, combined_freebusy)