diff --git a/api/ECNQueue.py b/ECNQueue.py similarity index 100% rename from api/ECNQueue.py rename to ECNQueue.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api.py b/api.py similarity index 100% rename from api/api.py rename to api.py diff --git a/api/ECNQueue_old.py b/api/ECNQueue_old.py deleted file mode 100755 index 8bef24b..0000000 --- a/api/ECNQueue_old.py +++ /dev/null @@ -1,473 +0,0 @@ -#!/usr/local/bin/python - -#------------------------------------------------------------# -# Summary: Generates pages viewed by end users -#------------------------------------------------------------# - - -#------------------------------------------------------------# -# Import Libraries -#------------------------------------------------------------# -import email -import base64 -import os -import sys -import time -from string import digits -from email.Utils import parseaddr - - -#------------------------------------------------------------# -# Configure Script Environment -#------------------------------------------------------------# -c_queue_command_dir = '/usr/local/etc/ecn/queue/' - -# Use Live Queue: -# c_queue_dir = '/home/pier/e/queue/Mail/' - -# Use Test Queue: -c_queue_dir = '/Users/justincampbell/GitHub/ecn-queue/webqueue/q-snapshot' - -c_queues_to_skip = ['archives', 'drafts', 'inbox'] - -""" -c_header_map = { - 'priority':'qpriority', - 'status':'qstatus', - 'building':'qbuilding', - 'status-updated-by':'qstatus-updated-by', - 'time':'qtime', - 'assigned-to':'qassigned-to' -} -""" - - -class QueueItem: - - def __init__(self, queue_name, number, archive=''): - " Initialize a new Queue Item " - self.queue_name = queue_name # name of the queue containing the item - self.number = int(number) # queue item number - self.attributes = {'number': int(number), 'queue_name': queue_name} # dictionary of headers, attributes, etc. - self.headers = '' # text version of e-mail headers - self.content = '' # text version of e-mail content - self.body = {} # text version of the body of the e-mail - self.attachments = {} # dictionary of attachments keyed on filename - self.message = None # python e-mail representation of the message - self.archive = archive - - def load(self, headers_only=0): - " Load the content of this queue item " - - # Get an open file ready to read the queue item - # file = qCmd('qshow', self.queue_name, self.number, file=1) - if self.archive: - queue_dir = '%sarchives/%s/%s/' % (c_queue_dir, - self.queue_name, self.archive) - else: - queue_dir = '%s%s/' % (c_queue_dir, self.queue_name) - - if os.access(queue_dir + str(self.number), os.R_OK): - file = open(queue_dir + str(self.number)) - else: - return False - - self.attributes['last_updated'] = os.path.getmtime( - queue_dir + str(self.number)) - - in_headers = True - self.headers = '' - self.content = '' - - sys.stdout.flush() - line = 1 - num_lines = 0 - while line: - if in_headers: - line = file.readline() - num_lines += 1 - - # Skip lines beginning with [ or ( in the headers - if len(line) and line[0] in '[(': - if line.find('[%s] ' % self.queue_name) == 0: - line = line.replace('[%s] ' % self.queue_name, '') - else: - continue - - # a newline designates the end of the headers - if len(line) and line[0] == '\n': - in_headers = False - if headers_only: break - - self.headers += line - - else: - self.content += file.read() - break - - file.close() - - self.content = self.stripAttachments(self.content) - - self.message = email.message_from_string(self.headers + self.content) - - # Populate the message attributes from the - # message headers - for key in self.message.keys(): - self[key.lower()] = self.message[key] - - self['from_username'] = parseaddr(self['from'])[1].split('@')[0] - self['subject_status'] = '
%(subject)s
%(qstatus)s
' % self - self['for_username'] = self['qassigned-to'].lower() - - if not headers_only: - # Populate the message attachments from the - # message objects - self.body = {} - - if self.message.is_multipart(): - for part in self.message.walk(): - if part.get_filename(): - self.attachments[part.get_filename()] = {'content-type':part.get_content_type(), 'file':part.get_payload(decode=True)} - - elif part.get_content_type()[:5] == 'text/': - content_type = part.get_content_type() - - if not self.body.has_key(content_type): - self.body[content_type] = '' - - self.body[content_type] += part.get_payload(decode=True) - - else: - # If the message is not multipart then - # this is the body of the document - self.body[self.message.get_content_type()] = self.message.get_payload(decode=True) - - return True - - - def locked(self): - if self.archive: - queue_dir = '%sarchives/%s/%s/' % (c_queue_dir, self.queue_name, self.archive) - else: - queue_dir = '%s%s/' % (c_queue_dir, self.queue_name) - - if os.access(queue_dir + str(self.number) + '.lck', os.R_OK): - file = open(queue_dir + str(self.number) + '.lck') - parts = file.read().replace('\n', '').split(' ') - lock_info = { - 'file':parts[0], - 'program':parts[1], - 'user':parts[-1] - } - return lock_info - return False - - - def stripAttachments(self, message): - if not message: return '' - - lines = message.split('\n') - - attachments = [] - attachment_boundary = '' - attachment_data = '' - message_data = '' - - for line_index, line in enumerate(lines): - if attachment_boundary: - if line.find(attachment_boundary) == 0: - attachments.append(attachment_data) - attachment_boundary = '' - attachment_data = '' - else: - attachment_data += line + '\n' - - elif line.lower().find('content-type:') == 0: - header, value = line.split(':', 1) - value = value.strip().lower() - if value.find('text') == 0 or \ - value.find('multipart') == 0 or \ - value.find('message') == 0: - message_data += line + '\n' - else: - for x in range(line_index-1, 0, -1): - if lines[x].find('--') == 0: - attachment_boundary = lines[x] - for y in range(x+1, line_index): - attachment_data += lines[y] + '\n' - break - attachment_data += line + '\n' - - else: - message_data += line + '\n' - - for attachment in attachments: - message = email.message_from_string(attachment) - self.message = message - self.attachments[str(message.get_filename())] = {'content-type':message.get_content_type(), 'file':message.get_payload(decode=True)} - - return message_data - - def loadHeaders(self): - """ Helper function which calls load with headers_only=1 """ - self.load(headers_only=1) - - def getHeaders(self): - " Return the HTML Headers for this queue item " - if not self.headers: self.loadHeaders() - return self.headers - - def getBody(self, content_types=['text/plain','text/html']): - """ - Return the body of the e-mail using the content_types - to specify a prefered content type. If no content type - matching the prefered type is found it returns the first - body element - """ - - if not self.content: self.load() - - body_text = '' - for content_type in content_types: - if content_type in self.body: - body_text += '\n' + self.body[content_type] - if self.message.epilogue: - body_text += '\n' + self.message.epilogue - if body_text: - return body_text - """ - for content_type in content_types: - if content_type in self.body: - return self.body[content_type] - """ - - if len(self.body.keys()): - return self.body[self.body.keys()[0]] - else: - return '' - - def getAttachments(self): - " Return a list of filenames for all attachments " - if not self.content: self.load() - return self.attachments.keys() - - def getAttachment(self, filename): - " Return the content of a specific attachment " - if not self.content: self.load() - return self.attachments[filename]['file'] - - def getAttachmentContentType(self, filename): - " Return the content-type of a specific attachment " - if not self.content: self.load() - return self.attachments[filename]['content-type'] - - def getNumber(self): - " Returns the number of the queue item " - return self.number - - def numAttachments(self): - " Returns the number of attachments " - if not self.content: self.load() - return len(self.getAttachments()) - - def lastUpdated(self): - return self.attributes['last_updated'] - - def __contains__(self, item): - return item in self.attributes - - def __getitem__(self, key): - if not self.headers: - try: - self.loadHeaders() - except: - raise Exception('Error In Headers', 'Queue Item #%s' % self.number) - - key = key.lower() - # key = c_header_map.get(key, key) - return self.attributes.get(key,'') - - def __setitem__(self, key, value): - self.attributes[key.lower()] = value - - def __str__(self): - return "%14s:%-4s %-10s %-40s" % (self.queue_name, self.number, self['from'], self['subject']) - -class Queue: - - - def __init__(self, queue_name, archive=''): - self.loaded = False - self.queue_name = queue_name - self.archive = archive - self.num_items = None - self.items = [] - self.filtered_items = [] - self.filters = {} - self.sort_on = '' - self.sort_direction = 'ascending' - - def loadItems(self): - self.loaded = True - self.items = [] - - if self.archive: - # Where self.archive = 'YM0502' - queue_dir = c_queue_dir + 'archives/' + self.queue_name + '/' + self.archive + '/' - else: - # lines = qCmd('qscan', self.queue_name).split('\n')[1:] - queue_dir = c_queue_dir + self.queue_name + '/' - - if not os.access(queue_dir, os.F_OK): - return - - for file in os.listdir(queue_dir): - valid = True - for letter in file: - if letter not in digits: - valid = False - break - - if valid and os.access(queue_dir + file, os.R_OK): - item_num = file - item = QueueItem(self.queue_name, file, self.archive) - self.items.append(item) - - self.num_items = len(self.items) - - def sort(self, sort_on, sort_direction='ascending'): - if not self.loaded: self.loadItems() - self.sort_on = sort_on - self.sort_direction = sort_direction - if self.sort_on == 'qpriority': - self.items.sort(lambda a,b:cmp(a[sort_on].upper(), b[sort_on].upper())) - elif self.sort_on == 'date': - self.items.sort(lambda a,b:cmp(time.mktime(email.utils.parsedate(a[sort_on])), time.mktime(email.utils.parsedate(b[sort_on])))) - else: - self.items.sort(lambda a,b:cmp(a[sort_on], b[sort_on])) - - if sort_direction == 'descending': - self.items.reverse() - - self.filtered_items = [] - - def setFilter(self, name, value): - self.filters[name.lower()] = value.lower() - self.filtered_items = [] - - def addItems(self, items): - self.items.extend(items) - self.num_items = len(self.items) - self.loaded = True - - def setItems(self, items): - self.items = items[:] - self.num_items = len(self.items) - self.loaded = True - - def getItems(self, exact_match=False): - if not self.loaded: self.loadItems() - - if not self.filters: - return self.items - - elif self.filtered_items: - return self.filtered_items - - for item in self.items: - matches = False - for filter in self.filters: - for word in self.filters[filter].split(' or '): - word = word.strip() - - if not word: - continue - - if word[0] == '!': - if item[filter].lower().find(word[1:]) < 0: - matches = True - - elif exact_match and item[filter].lower() == word: - matches = True - break - elif not exact_match and item[filter].lower().find(word) >= 0: - matches = True - break - - if matches: - self.filtered_items.append(item) - - return self.filtered_items - - def getName(self): - return self.queue_name - - def setNumItems(self, num): - self.num_items = num - - def getNumItems(self): - if self.num_items is None: - self.num_items = 0 - - if self.archive: - queue_dir = c_queue_dir + 'archives/' + self.queue_name + '/' + self.archive - else: - queue_dir = c_queue_dir + self.queue_name - - files = os.listdir(queue_dir) - for file in files: - valid = 1 - for c in str(file): - if c not in digits: - valid = 0 - break - if valid: - self.num_items += 1 - - return self.num_items - - def lastUpdated(self): - update_times = [] - for item in self.getItems(): - update_times.append(item['last_updated']) - update_times.sort() - update_times.reverse() - if len(update_times): - return update_times[0] - return -1 - - def __len__(self): - return self.getNumItems() - - def __str__(self): - return "%-20s %s" % (self.getName(), self.getNumItems()) - - def __add__(self, other): - new_queue = Queue(self.getName() + '+' + other.getName()) - new_queue.addItems(self.getItems()) - new_queue.addItems(other.getItems()) - return new_queue - - def __cmp__(self, other): - return cmp(self.getName(), other.getName()) - - def __getitem__(self, index): - return self.getItems()[index] - -def getQueues(): - queues = [] - - for file in os.listdir(c_queue_dir): - if os.access(c_queue_dir + file, os.R_OK) and os.path.isdir(c_queue_dir + file) and file not in c_queues_to_skip: - queue = Queue(file) - queues.append(queue) - - return queues - -if __name__ == '__main__': - - # Create a combined Queue - item = QueueItem('webmaster', 22) - body = item.getBody() - diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5efafde --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +import setuptools + +VERSION = "2.1.0" + +setuptools.setup( + name="webqueue-api", + version=VERSION, + description="A library for managing Purdue ECN's queue system.", + py_modules=['api', 'ECNQueue'], + python_requires='>=3.6', + install_requires = [ + # General Utilities + "pipdeptree", + "gunicorn", + "pylint", + + # API + "python-dotenv", + "Flask-RESTful", + "python-dateutil", + "Flask-JWT-Extended", + # Prevent upgrade to 2.x until Flask-JWT-Extended is updated to support it + "PyJWT == 1.*", + + # API Documentation + "mkdocs", + "mkdocs-material", + "mkautodoc" + ] +) \ No newline at end of file diff --git a/utils/venv-manager.py b/venv-manager.py similarity index 100% rename from utils/venv-manager.py rename to venv-manager.py