pylokid/main.py

307 lines
12 KiB
Python
Raw Normal View History

2017-12-21 21:12:44 +00:00
#!/usr/bin/env python3
""" Thy pylokid main program """
import os
import re
2017-12-24 14:35:16 +00:00
from datetime import datetime
2017-12-21 21:12:44 +00:00
import asyncio
import logging
import time
2017-12-26 13:27:02 +00:00
import requests
2017-12-21 21:12:44 +00:00
import email
import email.parser
import imaplib
import aioeasywebdav
from dotenv import load_dotenv, find_dotenv
2017-12-23 16:55:02 +00:00
import paho.mqtt.client as mqtt
2017-12-30 16:01:13 +00:00
from lodur_connect import create_einsatzrapport, upload_alarmdepesche, update_einsatzrapport
2017-12-28 19:07:56 +00:00
import pdf_extract
2017-12-21 21:12:44 +00:00
2017-12-25 12:52:43 +00:00
_EMAIL_SUBJECTS = '(OR SUBJECT "Einsatzausdruck_FW" SUBJECT "Einsatzprotokoll" UNSEEN)'
_INTERVAL = 10
2017-12-21 21:12:44 +00:00
load_dotenv(find_dotenv())
imap_server = os.getenv("IMAP_SERVER")
imap_username = os.getenv("IMAP_USERNAME")
imap_password = os.getenv("IMAP_PASSWORD")
imap_mailbox = os.getenv("IMAP_MAILBOX", "INBOX")
2017-12-23 17:31:40 +00:00
imap_mailbox_archive = os.getenv("IMAP_MAILBOX_ARCHIVE", "Archive")
2017-12-21 21:12:44 +00:00
webdav_url = os.getenv("WEBDAV_URL")
webdav_username = os.getenv("WEBDAV_USERNAME")
webdav_password = os.getenv("WEBDAV_PASSWORD")
webdav_basedir = os.getenv("WEBDAV_BASEDIR")
tmp_dir = os.getenv("TMP_DIR", "/tmp")
2017-12-23 16:55:02 +00:00
mqtt_server = os.getenv("MQTT_SERVER")
mqtt_user = os.getenv("MQTT_USER")
mqtt_password = os.getenv("MQTT_PASSWORD")
2017-12-24 14:35:16 +00:00
lodur_user = os.getenv("LODUR_USER")
lodur_password = os.getenv("LODUR_PASSWORD")
lodur_base_url = os.getenv("LODUR_BASE_URL")
2017-12-26 13:27:02 +00:00
heartbeat_url = os.getenv("HEARTBEAT_URL")
2017-12-21 21:12:44 +00:00
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
2017-12-25 11:39:37 +00:00
def search_emails(imap):
""" searches for emails matching the configured subject """
2017-12-21 21:12:44 +00:00
# search for matching messages
logger.info('Searching for messages matching the subject')
typ, msg_ids = imap.search(
None,
_EMAIL_SUBJECTS,
)
if typ != 'OK':
2017-12-24 14:35:16 +00:00
logger.error('Error searching for matching messages')
2017-12-21 21:12:44 +00:00
raise
2017-12-25 11:39:37 +00:00
num_messages = len(msg_ids[0].split())
logger.info('Found ' + str(num_messages) + ' matching messages')
return num_messages, msg_ids
2017-12-23 17:31:40 +00:00
2017-12-25 11:39:37 +00:00
def store_attachments(imap, msg_ids):
""" stores the attachments to filesystem and marks message as read """
data = {}
2017-12-21 21:12:44 +00:00
for msg_id in msg_ids[0].split():
2017-12-25 11:39:37 +00:00
# download message from imap
2017-12-21 21:12:44 +00:00
typ, msg_data = imap.fetch(msg_id, '(RFC822)')
2017-12-25 12:52:43 +00:00
if typ != 'OK':
logger.error('Error fetching message')
raise
# extract attachment
2017-12-21 21:12:44 +00:00
for response_part in msg_data:
if isinstance(response_part, tuple):
2017-12-25 12:52:43 +00:00
mail = email.message_from_string(str(response_part[1], 'utf-8'))
subject = mail['subject']
logger.info('Getting attachment from: ' + subject)
for part in mail.walk():
file_name = part.get_filename()
2017-12-26 13:23:34 +00:00
if not file_name:
logger.debug('Most probably not an attachment as no filename found')
continue
2017-12-25 12:52:43 +00:00
logger.info('Extracting attachment: ' + file_name)
if bool(file_name):
f_type, f_id = parse_subject(subject)
renamed_file_name = f_type + '_' + file_name
2017-12-25 12:52:43 +00:00
# save attachment to filesystem
file_path = os.path.join(tmp_dir, renamed_file_name)
2017-12-25 12:52:43 +00:00
logger.info('Saving attachment to ' + file_path)
if not os.path.isfile(file_path):
file = open(file_path, 'wb')
file.write(part.get_payload(decode=True))
file.close()
data[subject] = renamed_file_name
2017-12-21 21:12:44 +00:00
2017-12-25 11:39:37 +00:00
# mark as seen
imap.store(msg_id, '+FLAGS', '(\\Seen)')
2017-12-24 14:35:16 +00:00
2017-12-25 11:39:37 +00:00
return data
2017-12-21 21:12:44 +00:00
2017-12-25 11:39:37 +00:00
def upload_webdav(loop, webdav, file_name, f_id):
""" uploads a file to webdav - checks for existence before doing so """
2017-12-21 21:12:44 +00:00
# upload with webdav
2017-12-25 11:39:37 +00:00
remote_upload_dir = webdav_basedir + "/" + str(datetime.now().year) + "/" + f_id
2017-12-25 12:52:43 +00:00
logger.info('Uploading file to WebDAV:' + remote_upload_dir)
# create directory if not yet there
2017-12-25 11:39:37 +00:00
if not loop.run_until_complete(webdav.exists(remote_upload_dir)):
logger.info('Creating directory ' + remote_upload_dir)
loop.run_until_complete(webdav.mkdir(remote_upload_dir))
2017-12-21 21:12:44 +00:00
2017-12-25 11:39:37 +00:00
remote_file_path = remote_upload_dir + "/" + file_name
if loop.run_until_complete(webdav.exists(remote_file_path)):
2017-12-25 11:39:37 +00:00
logger.info('File ' + file_name + ' already exists on webdav')
else:
loop.run_until_complete(
2017-12-25 11:39:37 +00:00
webdav.upload(os.path.join(tmp_dir, file_name), remote_file_path)
)
logger.info('File ' + file_name + ' uploaded')
2017-12-21 21:12:44 +00:00
2017-12-25 11:39:37 +00:00
def einsatz_exists(loop, webdav, f_id):
""" check if an einsatz is already created """
remote_upload_dir = webdav_basedir + "/" + str(datetime.now().year) + "/" + f_id
if loop.run_until_complete(webdav.exists(remote_upload_dir)):
logger.info('Einsatz exists ' + f_id)
return True
else:
return False
def parse_subject(subject):
""" extract f id and type from subject """
parsed = re.search('(.*): (F[0-9].*)', subject)
f_type = parsed.group(1)
f_id = parsed.group(2)
return f_type, f_id
2017-12-25 12:52:43 +00:00
def store_lodur_id(loop, webdav, lodur_id, f_id):
""" stores assigned lodur_id on webdav """
file_name = f_id + '_lodurid.txt'
file_path = os.path.join(tmp_dir, file_name)
if not os.path.isfile(file_path):
file = open(file_path, 'w')
file.write(str(lodur_id))
file.close()
logger.info('Stored Lodur ID locally in: ' + file_path)
upload_webdav(loop, webdav, file_name, f_id)
else:
logger.info('Lodur ID already available locally in: ' + file_path)
def get_lodur_id(loop, webdav, f_id):
""" gets lodur_id if it exists """
file_name = f_id + '_lodurid.txt'
file_path = os.path.join(tmp_dir, file_name)
# first check if we already have it locally - then check on webdav
if os.path.isfile(file_path):
with open(file_path, 'r') as content:
lodur_id = content.read()
logger.info('Found Lodur ID for ' + f_id + ' locally: ' + lodur_id)
return lodur_id
else:
remote_upload_dir = webdav_basedir + "/" + str(datetime.now().year) + "/" + f_id
remote_file_path = remote_upload_dir + '/' + file_name
if loop.run_until_complete(webdav.exists(remote_file_path)):
loop.run_until_complete(webdav.download(remote_file_path, file_path))
with open(file_path, 'r') as content:
lodur_id = content.read()
logger.info('Found Lodur ID for ' + f_id + ' on WebDAV: ' + lodur_id)
return lodur_id
else:
logger.info('No Lodur ID found for ' + f_id)
return False
2017-12-23 16:55:02 +00:00
2017-12-21 21:12:44 +00:00
def main():
""" main """
2017-12-25 11:39:37 +00:00
# MQTT connection
2017-12-25 12:52:43 +00:00
logger.info('Connecting to MQTT broker ' + mqtt_server)
mqtt_client = mqtt.Client('pylokid')
mqtt_client.username_pw_set(mqtt_user, password=mqtt_password)
mqtt_client.tls_set()
mqtt_client.connect(mqtt_server, 8883, 60)
mqtt_client.loop_start()
2017-12-25 11:39:37 +00:00
# imap connection
logger.info('Connecting to IMAP server ' + imap_server)
imap = imaplib.IMAP4_SSL(imap_server)
imap.login(imap_username, imap_password)
imap.select(imap_mailbox, readonly=False)
2017-12-23 16:55:02 +00:00
2017-12-25 11:39:37 +00:00
# webdav connection
logger.info('Connecting to WebDAV server ' + webdav_url)
loop = asyncio.get_event_loop()
webdav = aioeasywebdav.connect(
webdav_url,
username=webdav_username,
password=webdav_password,
)
2017-12-23 16:55:02 +00:00
2017-12-21 21:12:44 +00:00
while True:
2017-12-25 11:39:37 +00:00
attachments = {}
num_messages, msg_ids = search_emails(imap)
if num_messages > 0:
attachments = store_attachments(imap, msg_ids)
if len(attachments) > 0:
for subject in attachments:
f_type, f_id = parse_subject(subject)
file_name = attachments[subject]
upload_webdav(loop, webdav, file_name, f_id)
# Take actions - depending on the type
if f_type == 'Einsatzausdruck_FW':
2017-12-25 12:52:43 +00:00
lodur_id = get_lodur_id(loop, webdav, f_id)
if lodur_id:
logger.info(
'Einsatzrapport ' + f_id + ' already created in Lodur: ' + lodur_id
)
2017-12-26 13:23:34 +00:00
# Upload Alarmdepesche as it could contain more information than the first one
upload_alarmdepesche(
lodur_user,
lodur_password,
lodur_base_url,
lodur_id,
file_name,
os.path.join(tmp_dir, file_name),
)
2017-12-25 12:52:43 +00:00
else:
# this is real - publish Einsatz on MQTT
2017-12-30 16:01:13 +00:00
# TODO publish more information about the einsatz - coming from the PDF
2017-12-25 12:52:43 +00:00
mqtt_client.publish('pylokid/' + f_type, f_id)
2017-12-28 19:07:56 +00:00
# get as many information from PDF as possible
2017-12-30 16:01:13 +00:00
pdf_data = pdf_extract.extract_einsatzausdruck(
os.path.join(tmp_dir, file_name),
f_id,
)
2017-12-28 19:07:56 +00:00
2017-12-25 12:52:43 +00:00
# create new Einsatzrapport in Lodur
logger.info('Creating Einsatzrapport in Lodur for ' + f_id)
lodur_id = create_einsatzrapport(
lodur_user,
lodur_password,
lodur_base_url,
f_id,
2017-12-28 19:07:56 +00:00
pdf_data,
2017-12-25 12:52:43 +00:00
)
logger.info('Sent data to Lodur. Assigned Lodur ID: ' + lodur_id)
# store lodur id in webdav
store_lodur_id(loop, webdav, lodur_id, f_id)
logger.info(
'Uploading PDF for ' + f_id + ' to Lodur Einsatzrapport ' + lodur_id
)
upload_alarmdepesche(
lodur_user,
lodur_password,
lodur_base_url,
lodur_id,
file_name,
os.path.join(tmp_dir, file_name),
)
2017-12-25 11:39:37 +00:00
elif f_type == 'Einsatzprotokoll':
2017-12-25 12:52:43 +00:00
# Einsatz finished - publish on MQTT
mqtt_client.publish('pylokid/' + f_type, f_id)
lodur_id = get_lodur_id(loop, webdav, f_id)
if lodur_id:
logger.info('Uploading Einsatzprotokoll to Lodur')
upload_alarmdepesche(
lodur_user,
lodur_password,
lodur_base_url,
lodur_id,
file_name,
os.path.join(tmp_dir, file_name),
)
2017-12-30 16:01:13 +00:00
pdf_data = pdf_extract.extract_einsatzprotokoll(
os.path.join(tmp_dir, file_name),
f_id,
)
# only update when parsing was successfull
if pdf_data:
logger.info('Updating Einsatzrapport with data from PDF - not yet implemented')
else:
logger.info('Updating Einsatzrapport not possible - PDF parsing failed')
2017-12-25 12:52:43 +00:00
else:
logger.error('Cannot process Einsatzprotokoll as there is no Lodur ID')
2017-12-25 11:39:37 +00:00
else:
logger.error('Unknown type: ' + f_type)
2017-12-26 13:27:02 +00:00
# send heartbeat
requests.get(heartbeat_url)
2017-12-25 11:39:37 +00:00
# repeat every
time.sleep(_INTERVAL)
2017-12-21 21:12:44 +00:00
if __name__ == '__main__':
main()