From 94af30b913462a83091ee319df5321dfdc6d36c2 Mon Sep 17 00:00:00 2001 From: Balthasar Reuter Date: Fri, 21 Jun 2019 23:57:21 +0200 Subject: [PATCH] Added experimental Webdav upload --- photobooth/defaults.cfg | 12 ++++++ photobooth/worker/PictureList.py | 13 +++---- photobooth/worker/PictureMailer.py | 13 ++++--- photobooth/worker/PictureSaver.py | 10 +++-- photobooth/worker/PictureUploadWebdav.py | 49 ++++++++++++++++++++++++ photobooth/worker/__init__.py | 40 ++++++++++++------- setup.py | 3 +- 7 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 photobooth/worker/PictureUploadWebdav.py diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg index 9c57f792..5e87b307 100644 --- a/photobooth/defaults.cfg +++ b/photobooth/defaults.cfg @@ -116,3 +116,15 @@ user = password = # SSL connection use_tls = False + +[UploadWebdav] +# Enable/disable webdav upload +enable = False +# URL at webdav server where files should be uploaded +url = https://example.com/remote.php/webdav/Photobooth/ +# Webdav server requires authentication +use_auth = True +# Webdav username +user = +# Webdav password +password = diff --git a/photobooth/worker/PictureList.py b/photobooth/worker/PictureList.py index 73a3ed5f..50b5b83d 100644 --- a/photobooth/worker/PictureList.py +++ b/photobooth/worker/PictureList.py @@ -18,7 +18,6 @@ # along with this program. If not, see . import logging -import os from glob import glob @@ -36,15 +35,10 @@ def __init__(self, basename): """ # Set basename and suffix - self.basename = basename + self._basename = basename self.suffix = '.jpg' self.count_width = 5 - # Ensure directory exists - dirname = os.path.dirname(self.basename) - if not os.path.exists(dirname): - os.makedirs(dirname) - self.findExistingFiles() def findExistingFiles(self): @@ -68,6 +62,11 @@ def findExistingFiles(self): logging.info('Saving pictures as "%s%s.%s"', self.basename, self.count_width * 'X', 'jpg') + @property + def basename(self): + """Return the basename for the files""" + return self._basename + def getFilename(self, count): """Return the file name for a given file number""" return self.basename + str(count).zfill(self.count_width) + self.suffix diff --git a/photobooth/worker/PictureMailer.py b/photobooth/worker/PictureMailer.py index 0377040a..6f647195 100644 --- a/photobooth/worker/PictureMailer.py +++ b/photobooth/worker/PictureMailer.py @@ -26,10 +26,12 @@ from email.utils import formatdate from email import encoders +from pathlib import Path + from .WorkerTask import WorkerTask -def send_mail(send_from, send_to, subject, message, picture, +def send_mail(send_from, send_to, subject, message, picture, filename, server, port, is_auth, username, password, is_tls): """Compose and send email with provided info and attachments. @@ -41,6 +43,7 @@ def send_mail(send_from, send_to, subject, message, picture, subject (str): message title message (str): message body picture (jpg byte_data): ByteIO data of the JPG picture + filename (str): Filename of picture server (str): mail server host name port (int): port number is_auth (bool): server requires authentication @@ -60,7 +63,7 @@ def send_mail(send_from, send_to, subject, message, picture, part.set_payload(picture.getbuffer()) encoders.encode_base64(part) part.add_header('Content-Disposition', - 'attachment; filename="photobooth.jpg"') + 'attachment; filename="{}"'.format(filename)) msg.attach(part) smtp = smtplib.SMTP(server, port) @@ -90,9 +93,9 @@ def __init__(self, config): self._password = config.get('Mailer', 'password') self._is_tls = config.getBool('Mailer', 'use_tls') - def do(self, picture): + def do(self, picture, filename): logging.info('Sending picture to %s', self._recipient) send_mail(self._sender, self._recipient, self._subject, self._message, - picture, self._server, self._port, self._is_auth, self._user, - self._password, self._is_tls) + picture, Path(filename).name, self._server, self._port, + self._is_auth, self._user, self._password, self._is_tls) diff --git a/photobooth/worker/PictureSaver.py b/photobooth/worker/PictureSaver.py index 23e45f38..a84c1216 100644 --- a/photobooth/worker/PictureSaver.py +++ b/photobooth/worker/PictureSaver.py @@ -18,9 +18,9 @@ # along with this program. If not, see . import logging +import os from .WorkerTask import WorkerTask -from .PictureList import PictureList class PictureSaver(WorkerTask): @@ -29,11 +29,13 @@ def __init__(self, basename): super().__init__() - self._pic_list = PictureList(basename) + # Ensure directory exists + dirname = os.path.dirname(basename) + if not os.path.exists(dirname): + os.makedirs(dirname) - def do(self, picture): + def do(self, picture, filename): - filename = self._pic_list.getNext() logging.info('Saving picture as %s', filename) with open(filename, 'wb') as f: f.write(picture.getbuffer()) diff --git a/photobooth/worker/PictureUploadWebdav.py b/photobooth/worker/PictureUploadWebdav.py new file mode 100644 index 00000000..79bc6661 --- /dev/null +++ b/photobooth/worker/PictureUploadWebdav.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Photobooth - a flexible photo booth software +# Copyright (C) 2018 Balthasar Reuter +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +import requests + +from pathlib import Path + +from .WorkerTask import WorkerTask + + +class PictureUploadWebdav(WorkerTask): + + def __init__(self, config): + + super().__init__() + + self._baseurl = config.get('UploadWebdav', 'url') + if config.getBool('UploadWebdav', 'use_auth'): + self._auth = (config.get('UploadWebdav', 'user'), + config.get('UploadWebdav', 'password')) + else: + self._auth = None + + def do(self, picture, filename): + + url = self._baseurl + '/' + Path(filename).name + logging.info('Uploading picture as %s', url) + + r = requests.put(url, data=picture.getbuffer(), auth=self._auth) + if r.status_code != requests.codes.created: + logging.warn(('PictureUploadWebdav: Upload failed with ' + 'status code {}').format(r.status_code)) diff --git a/photobooth/worker/__init__.py b/photobooth/worker/__init__.py index b25dd555..81e03bbb 100644 --- a/photobooth/worker/__init__.py +++ b/photobooth/worker/__init__.py @@ -24,8 +24,10 @@ from .. import StateMachine from ..Threading import Workers +from .PictureList import PictureList from .PictureMailer import PictureMailer from .PictureSaver import PictureSaver +from .PictureUploadWebdav import PictureUploadWebdav class Worker: @@ -34,6 +36,18 @@ def __init__(self, config, comm): self._comm = comm + # Picture list for assembled pictures + path = os.path.join(config.get('Storage', 'basedir'), + config.get('Storage', 'basename')) + basename = strftime(path, localtime()) + self._pic_list = PictureList(basename) + + # Picture list for individual shots + path = os.path.join(config.get('Storage', 'basedir'), + config.get('Storage', 'basename') + '_shot_') + basename = strftime(path, localtime()) + self._shot_list = PictureList(basename) + self.initPostprocessTasks(config) self.initPictureTasks(config) @@ -42,24 +56,22 @@ def initPostprocessTasks(self, config): self._postprocess_tasks = [] # PictureSaver for assembled pictures - path = os.path.join(config.get('Storage', 'basedir'), - config.get('Storage', 'basename')) - basename = strftime(path, localtime()) - self._postprocess_tasks.append(PictureSaver(basename)) + self._postprocess_tasks.append(PictureSaver(self._pic_list.basename)) # PictureMailer for assembled pictures if config.getBool('Mailer', 'enable'): self._postprocess_tasks.append(PictureMailer(config)) + # PictureUploadWebdav to upload pictures to a webdav storage + if config.getBool('UploadWebdav', 'enable'): + self._postprocess_tasks.append(PictureUploadWebdav(config)) + def initPictureTasks(self, config): self._picture_tasks = [] # PictureSaver for single shots - path = os.path.join(config.get('Storage', 'basedir'), - config.get('Storage', 'basename') + '_shot_') - basename = strftime(path, localtime()) - self._picture_tasks.append(PictureSaver(basename)) + self._picture_tasks.append(PictureSaver(self._shot_list.basename)) def run(self): @@ -73,10 +85,10 @@ def handleState(self, state): if isinstance(state, StateMachine.TeardownState): self.teardown(state) elif isinstance(state, StateMachine.ReviewState): - self.doPostprocessTasks(state.picture) + self.doPostprocessTasks(state.picture, self._pic_list.getNext()) elif isinstance(state, StateMachine.CameraEvent): if state.name == 'capture': - self.doPictureTasks(state.picture) + self.doPictureTasks(state.picture, self._shot_list.getNext()) else: raise ValueError('Unknown CameraEvent "{}"'.format(state)) @@ -84,12 +96,12 @@ def teardown(self, state): pass - def doPostprocessTasks(self, picture): + def doPostprocessTasks(self, picture, filename): for task in self._postprocess_tasks: - task.do(picture) + task.do(picture, filename) - def doPictureTasks(self, picture): + def doPictureTasks(self, picture, filename): for task in self._picture_tasks: - task.do(picture) + task.do(picture, filename) diff --git a/setup.py b/setup.py index 8c194530..29bbfd74 100644 --- a/setup.py +++ b/setup.py @@ -188,7 +188,8 @@ def get_mo_files(basedir, installdir): 'Pillow', 'gpiozero', 'gphoto2', - 'pycups' + 'pycups', + 'requests' ], # List additional groups of dependencies here (e.g. development