Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4f6ab22
cleanup: reorganize code
sgersapp Aug 25, 2016
9c4a560
Merge pull request #1 from saishg/cleanup
saishg Aug 25, 2016
1fb3c9f
cleanup: Organize into a class
sgersapp Aug 25, 2016
1b949f3
Merge pull request #2 from saishg/cleanup
saishg Aug 25, 2016
804b3ce
cleanup: Add room count to CSV
sgersapp Aug 25, 2016
7bd91ea
cleanup
sgersapp Aug 25, 2016
f03b830
cleanup: set default value in function arg
sgersapp Aug 25, 2016
418a2da
cleanup: make object oriented
sgersapp Aug 25, 2016
9397486
cleanup: timezone hack
sgersapp Aug 25, 2016
1abe22b
mrathor: Add WebService APIs
Aug 25, 2016
e8c5492
cleanup: Fix search for free rooms, using 15 min intervals
sgersapp Aug 25, 2016
4f602d9
Merge pull request #3 from saishg/cleanup
saishg Aug 25, 2016
ced47bf
mrathor: Merge branch 'master' of https://github.com/saishg/roomfinde…
Aug 25, 2016
acd12ee
cleanup: add ability to specify rooms file
sgersapp Aug 25, 2016
86f6697
Merge pull request #4 from saishg/cleanup
saishg Aug 25, 2016
5e217b8
mrathor: Merge branch 'master' of https://github.com/saishg/roomfinde…
Aug 25, 2016
ec8896f
cleanup: end time fix in constructor
sgersapp Aug 25, 2016
6ae2426
Merge pull request #5 from saishg/cleanup
saishg Aug 25, 2016
8e4b584
mrathor: Merge branch 'master' of https://github.com/saishg/roomfinde…
Aug 25, 2016
89386df
mrathor: Use API call
Aug 25, 2016
f2b3f2c
Merge pull request #6 from saishg/mrathor
Aug 25, 2016
06439d8
cleanup: add support for prefix
sgersapp Aug 25, 2016
9f33d6d
Merge pull request #7 from saishg/cleanup
saishg Aug 25, 2016
1e3503b
mrathor: Call Search Free API
Aug 25, 2016
26a098d
Merge pull request #8 from saishg/mrathor
Aug 25, 2016
bfab827
cleanup: Switch to using duration
sgersapp Aug 25, 2016
ee3cdea
cleanup: Merge remote-tracking branch 'origin' into cleanup
sgersapp Aug 25, 2016
718b8a9
cleanup: change to use duration
sgersapp Aug 25, 2016
59142ce
Merge pull request #9 from saishg/cleanup
saishg Aug 25, 2016
0b61888
UI
mobaqar Aug 25, 2016
c0c54bb
mrathor: Add API to get list of buildings
Aug 25, 2016
a849e2c
Merge pull request #10 from saishg/mrathor
Aug 25, 2016
6cc58cb
cleanup: UI fixes
sgersapp Aug 25, 2016
99d0156
Fix timezone issue
asawalka Aug 25, 2016
a43e0b1
Merge remote-tracking branch 'origin' into cleanup
asawalka Aug 25, 2016
719dfa1
Merge branch 'cleanup' of https://github.com/saishg/roomfinder into c…
asawalka Aug 25, 2016
c920522
Merge pull request #11 from saishg/cleanup
saishg Aug 25, 2016
7aad4e2
cleanup: Fix timezone for real
sgersapp Aug 25, 2016
e2461c8
Merge pull request #12 from saishg/cleanup
saishg Aug 25, 2016
06701fa
cleanup: Add date
sgersapp Aug 25, 2016
1a6ebee
Merge pull request #13 from saishg/cleanup
saishg Aug 25, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions DockerReadme.md
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM ubuntu:latest
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"]
172 changes: 125 additions & 47 deletions find_available_room.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,145 @@
#!/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_ZONE = ''

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
class AvailRoomFinder(object):

def __init__(self, user, password,
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

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 = {}

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, 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:
print "Searching for a room from " + self.start_time + " to " + self.end_time + ":"
print "{0:20s} {1:64s} {2:64s}".format("Status", "Room", "Email")

now = datetime.datetime.now().replace(microsecond=0)
starttime_default = now.isoformat()
end_time_default = None
xml_template = open("getavailibility_template.xml", "r").read()
xml = Template(xml_template)

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")
for email in selected_rooms:
data = unicode(xml.substitute(email=email,
starttime=self.start_time + TIME_ZONE,
endtime=self.end_time + TIME_ZONE))

args=parser.parse_args()
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:
return room_info

url = args.url
tree = ET.fromstring(response)

rooms={}
reader = csv.reader(codecs.open(args.file, 'r', encoding='utf-8'))
for row in reader:
rooms[unicode(row[1])]=unicode(row[0])
status = "Free"
elems = tree.findall(SCHEME_TYPES + "MergedFreeBusy")
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"

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
name, size = self.rooms[email]
room_info[name] = {'size': size, 'freebusy': freebusy, 'status': status}

user = args.user
password = getpass.getpass("Password:")
if print_to_stdout:
print "{0:20s} {1:64s} {2:64s}".format(status + '-' + freebusy, self.rooms[email], email)

print "Searching for a room from " + start_time + " to " + end_time + ":"
print "{0:10s} {1:64s} {2:64s}".format("Status", "Room", "Email")
return 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))
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("-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")

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)
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)

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()
137 changes: 90 additions & 47 deletions find_rooms.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,110 @@
#!/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")

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) 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):
rooms_found = self._search(prefix)

def findRooms(prefix):
rooms={}
data = unicode(xml.substitute(name=prefix))
if deep:
symbols = letters + digits
for symbol in symbols:
prefix_deep = prefix + " " + symbol
rooms_found.update(self._search(prefix_deep))

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]
tree = ET.fromstring(response)
print "Search for prefix '" + prefix + "' yielded " + str(len(rooms_found)) + " rooms."
self.rooms.update(self._search(prefix))

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 room_size(self, roomname):
try:
return int(roomname[roomname.find('(') + 1 : roomname.find(')')])
except ValueError:
return 0

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 dump(self, filename='rooms.csv'):
if not len(self.rooms):
print "Check your arguments for mistakes"
return 0

url = args.url
user = args.user
password = getpass.getpass("Password:")
with open(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])

xml_template = open("resolvenames_template.xml", "r").read()
xml = Template(xml_template)
return len(self.rooms)

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()

for prefix in args.prefix:
rooms.update(findRooms(prefix))
print "After searching for prefix '" + prefix + "' we found " + str(len(rooms)) + " rooms."
args.password = getpass.getpass("Password:")

deep = args.deep
finder = RoomFinder(args.user, args.password)

if deep:
symbols = letters + digits
for symbol in symbols:
prefix_deep = prefix + " " + symbol
rooms.update(findRooms(prefix_deep))
for prefix in args.prefix:
finder.search(prefix, args.deep)

print "After deep search for prefix '" + prefix + "' we found " + str(len(rooms)) + " rooms."
if finder.dump():
exit(0)
else:
exit(1)

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__':
run()
8 changes: 4 additions & 4 deletions getavailibility_template.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
<soap:Body>
<GetUserAvailabilityRequest xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
<TimeZone xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
<Bias>-60</Bias>
<Bias>420</Bias>
<StandardTime>
<Bias>0</Bias>
<Bias>-60</Bias>
<Time>03:00:00</Time>
<DayOrder>5</DayOrder>
<Month>10</Month>
<DayOfWeek>Sunday</DayOfWeek>
</StandardTime>
<DaylightTime>
<Bias>-60</Bias>
<Bias>0</Bias>
<Time>02:00:00</Time>
<DayOrder>5</DayOrder>
<Month>3</Month>
Expand All @@ -36,7 +36,7 @@
<StartTime>$starttime</StartTime>
<EndTime>$endtime</EndTime>
</TimeWindow>
<MergedFreeBusyIntervalInMinutes>60</MergedFreeBusyIntervalInMinutes>
<MergedFreeBusyIntervalInMinutes>15</MergedFreeBusyIntervalInMinutes>
<RequestedView>DetailedMerged</RequestedView>
</FreeBusyViewOptions>
</GetUserAvailabilityRequest>
Expand Down
Loading