Example #1
0
 def __init__(self, **kwargs):
     super().__init__()
     self.library = PicroscopyLibrary(**kwargs)
     self.helpers = WebHelpers(self.library)
     self.clients = kwargs.get('clients', IPv4Network('0.0.0.0/0'))
     logging.info('Clients must be on network %s', self.clients)
     self.static_dir = os.path.abspath(
         os.path.normpath(
             kwargs.get('static_dir', os.path.join(HERE, 'static'))))
     logging.info('Static files: %s', self.static_dir)
     self.templates_dir = os.path.abspath(
         os.path.normpath(
             kwargs.get('templates_dir', os.path.join(HERE, 'templates'))))
     logging.info('Chameleon templates: %s', self.templates_dir)
     self.templates = PageTemplateLoader(self.templates_dir,
                                         default_extension='.pt')
     self.layout = self.templates['layout']
     # No need to make flashes a per-session thing - it's a single user app!
     self.flashes = []
     self.router = PathRouter()
     # XXX Add handler for exiting system
     # XXX Make exit code conditional? (upgrade/reboot/shutdown/etc.)
     self.router.add_routes([
         url('/', self.do_template, kwargs={'page': 'library'},
             name='home'),
         url('/{page}.html', self.do_template, name='template'),
         url('/view/{image}.html',
             self.do_template,
             kwargs={'page': 'image'},
             name='view'),
         url('/static/{path:any}', self.do_static, name='static'),
         url('/images/{image}', self.do_image, name='image'),
         url('/thumbs/{image}', self.do_thumb, name='thumb'),
         url('/delete/{image}', self.do_delete, name='delete'),
         url('/config', self.do_config, name='config'),
         url('/reset', self.do_reset, name='reset'),
         url('/capture', self.do_capture, name='capture'),
         url('/download', self.do_download, name='download'),
         url('/send', self.do_send, name='send'),
         url('/logout', self.do_logout, name='logout'),
     ])
Example #2
0
 def __init__(self, **kwargs):
     super().__init__()
     self.library = PicroscopyLibrary(**kwargs)
     self.helpers = WebHelpers(self.library)
     self.clients = kwargs.get('clients', IPv4Network('0.0.0.0/0'))
     logging.info('Clients must be on network %s', self.clients)
     self.static_dir = os.path.abspath(os.path.normpath(kwargs.get(
         'static_dir', os.path.join(HERE, 'static')
         )))
     logging.info('Static files: %s', self.static_dir)
     self.templates_dir = os.path.abspath(os.path.normpath(kwargs.get(
         'templates_dir', os.path.join(HERE, 'templates')
         )))
     logging.info('Chameleon templates: %s', self.templates_dir)
     self.templates = PageTemplateLoader(
         self.templates_dir, default_extension='.pt')
     self.layout = self.templates['layout']
     # No need to make flashes a per-session thing - it's a single user app!
     self.flashes = []
     self.router = PathRouter()
     # XXX Add handler for exiting system
     # XXX Make exit code conditional? (upgrade/reboot/shutdown/etc.)
     self.router.add_routes([
         url('/',                   self.do_template, kwargs={'page': 'library'}, name='home'),
         url('/{page}.html',        self.do_template, name='template'),
         url('/view/{image}.html',  self.do_template, kwargs={'page': 'image'}, name='view'),
         url('/static/{path:any}',  self.do_static,   name='static'),
         url('/images/{image}',     self.do_image,    name='image'),
         url('/thumbs/{image}',     self.do_thumb,    name='thumb'),
         url('/delete/{image}',     self.do_delete,   name='delete'),
         url('/config',             self.do_config,   name='config'),
         url('/reset',              self.do_reset,    name='reset'),
         url('/capture',            self.do_capture,  name='capture'),
         url('/download',           self.do_download, name='download'),
         url('/send',               self.do_send,     name='send'),
         url('/logout',             self.do_logout,   name='logout'),
         ])
Example #3
0
class PicroscopyWsgiApp(object):
    def __init__(self, **kwargs):
        super().__init__()
        self.library = PicroscopyLibrary(**kwargs)
        self.helpers = WebHelpers(self.library)
        self.clients = kwargs.get('clients', IPv4Network('0.0.0.0/0'))
        logging.info('Clients must be on network %s', self.clients)
        self.static_dir = os.path.abspath(
            os.path.normpath(
                kwargs.get('static_dir', os.path.join(HERE, 'static'))))
        logging.info('Static files: %s', self.static_dir)
        self.templates_dir = os.path.abspath(
            os.path.normpath(
                kwargs.get('templates_dir', os.path.join(HERE, 'templates'))))
        logging.info('Chameleon templates: %s', self.templates_dir)
        self.templates = PageTemplateLoader(self.templates_dir,
                                            default_extension='.pt')
        self.layout = self.templates['layout']
        # No need to make flashes a per-session thing - it's a single user app!
        self.flashes = []
        self.router = PathRouter()
        # XXX Add handler for exiting system
        # XXX Make exit code conditional? (upgrade/reboot/shutdown/etc.)
        self.router.add_routes([
            url('/', self.do_template, kwargs={'page': 'library'},
                name='home'),
            url('/{page}.html', self.do_template, name='template'),
            url('/view/{image}.html',
                self.do_template,
                kwargs={'page': 'image'},
                name='view'),
            url('/static/{path:any}', self.do_static, name='static'),
            url('/images/{image}', self.do_image, name='image'),
            url('/thumbs/{image}', self.do_thumb, name='thumb'),
            url('/delete/{image}', self.do_delete, name='delete'),
            url('/config', self.do_config, name='config'),
            url('/reset', self.do_reset, name='reset'),
            url('/capture', self.do_capture, name='capture'),
            url('/download', self.do_download, name='download'),
            url('/send', self.do_send, name='send'),
            url('/logout', self.do_logout, name='logout'),
        ])

    def __call__(self, environ, start_response):
        req = Request(environ)
        try:
            if not IPv4Address(req.remote_addr) in self.clients:
                raise exc.HTTPForbidden()
            handler, kwargs = self.router.match(req.path_info)
            if handler:
                # XXX Why does route_name only appear in kwargs sometimes?!
                if 'route_name' in kwargs:
                    del kwargs['route_name']
                resp = handler(req, **kwargs)
            else:
                self.not_found(req)
        except exc.HTTPException as e:
            # The exception itself is a WSGI response
            resp = e
        return resp(environ, start_response)

    def not_found(self, req):
        """
        Handler for unknown locations (404)
        """
        raise exc.HTTPNotFound('The resource at %s could not be found' %
                               req.path_info)

    def do_reset(self, req):
        """
        Reset all settings to their defaults
        """
        self.library.camera_reset()
        self.flashes.append('Camera settings reset to defaults')
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_config(self, req):
        """
        Configure the library and camera settings
        """
        # Resolution is handled specially as the camera needs to stop the
        # preview in order to change it
        try:
            new_resolution = tuple(
                int(i) for i in req.params['resolution'].split('x', 1))
            if len(new_resolution) != 2:
                raise ValueError()
        except ValueError:
            self.flashes.append('Invalid resolution: %s' %
                                req.params['resolution'])
        if self.library.camera.resolution != new_resolution:
            try:
                self.library.camera.stop_preview()
                try:
                    self.library.camera.resolution = new_resolution
                finally:
                    self.library.camera.start_preview()
            except PiCameraError:
                self.flashes.append('Unable to change camera resolution '
                                    'to %s' % req.params['resolution'])
        # Everything else is handled generically...
        for setting in (
                'sharpness',
                'contrast',
                'brightness',
                'saturation',  #'ISO',
                'exposure-compensation'):
            try:
                setattr(self.library.camera, setting.replace('-', '_'),
                        int(req.params[setting]))
            except ValueError:
                self.flashes.append('Invalid %s: %s' %
                                    (setting, req.params[setting]))
        for setting in ('hflip', 'vflip'):
            try:
                setattr(self.library.camera, setting.replace('-', '_'),
                        bool(req.params.get(setting, 0)))
            except ValueError:
                self.flashes.append('Invalid %s: %s' %
                                    (setting, req.params[setting]))
        for setting in ('meter-mode', 'awb-mode', 'exposure-mode'):
            try:
                setattr(self.library.camera, setting.replace('-', '_'),
                        req.params[setting])
            except ValueError:
                self.flashes.append('Invalid %s: %s' %
                                    (setting, req.params[setting]))
        for setting in ('artist', 'email', 'copyright', 'description',
                        'filename-template', 'format'):
            try:
                setattr(self.library, setting.replace('-', '_'),
                        req.params[setting])
            except ValueError:
                self.flashes.append('Invalid %s: %s' %
                                    (setting, req.params[setting]))
        # If any settings failed, re-render the settings form
        if self.flashes:
            return self.do_template(req, 'settings')
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_capture(self, req):
        """
        Take a new image with the camera and add it to the library
        """
        self.library.capture()
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_download(self, req):
        """
        Send the library as a .zip archive
        """
        archive = self.library.archive()
        size = archive.seek(0, io.SEEK_END)
        archive.seek(0)
        resp = Response()
        resp.content_type = 'application/zip'
        resp.content_length = size
        resp.content_disposition = 'attachment; filename=images.zip'
        resp.app_iter = FileWrapper(archive)
        return resp

    def do_send(self, req):
        """
        Send the library as a set of attachments to an email
        """
        self.library.send()
        self.flashes.append('Email sent to %s' % library.email)
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_delete(self, req, image):
        """
        Delete the selected images from library
        """
        self.library.remove(image)
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_logout(self, req):
        """
        Clear the library of all images, reset all settings
        """
        self.library.clear()
        self.library.user_reset()
        self.library.camera_reset()
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='settings'))

    def do_image(self, req, image):
        """
        Serve an image from the library
        """
        if not image in self.library:
            self.not_found(req)
        resp = Response()
        resp.content_type, resp.content_encoding = mimetypes.guess_type(
            image, strict=False)
        resp.content_length = self.library.stat_image(image).st_size
        resp.app_iter = FileWrapper(self.library.open_image(image))
        return resp

    def do_thumb(self, req, image):
        """
        Serve a thumbnail of an image from the library
        """
        if not image in self.library:
            self.not_found(req)
        resp = Response()
        resp.content_type = 'image/jpeg'
        resp.content_length = self.library.stat_thumbnail(image).st_size
        resp.app_iter = FileWrapper(self.library.open_thumbnail(image))
        return resp

    def do_static(self, req, path):
        """
        Serve static files from disk
        """
        path = os.path.normpath(os.path.join(self.static_dir, path))
        if not path.startswith(self.static_dir):
            self.not_found(req)
        resp = Response()
        resp.content_type, resp.content_encoding = mimetypes.guess_type(
            path, strict=False)
        if resp.content_type is None:
            resp.content_type = 'application/octet-stream'
        resp.content_length = os.stat(path).st_size
        resp.app_iter = FileWrapper(io.open(path, 'rb'))
        return resp

    def do_template(self, req, page, image=None):
        """
        Serve a Chameleon template-based page
        """
        resp = Response()
        resp.content_type = 'text/html'
        resp.content_encoding = 'utf-8'
        try:
            template = self.templates[page]
        except ValueError:
            self.not_found(req)
        resp.text = template(req=req,
                             page=page,
                             image=image,
                             helpers=self.helpers,
                             layout=self.layout,
                             flashes=self.flashes,
                             library=self.library,
                             camera=self.library.camera,
                             router=self.router)
        del self.flashes[:]
        return resp
Example #4
0
class PicroscopyWsgiApp(object):
    def __init__(self, **kwargs):
        super().__init__()
        self.library = PicroscopyLibrary(**kwargs)
        self.helpers = WebHelpers(self.library)
        self.clients = kwargs.get('clients', IPv4Network('0.0.0.0/0'))
        logging.info('Clients must be on network %s', self.clients)
        self.static_dir = os.path.abspath(os.path.normpath(kwargs.get(
            'static_dir', os.path.join(HERE, 'static')
            )))
        logging.info('Static files: %s', self.static_dir)
        self.templates_dir = os.path.abspath(os.path.normpath(kwargs.get(
            'templates_dir', os.path.join(HERE, 'templates')
            )))
        logging.info('Chameleon templates: %s', self.templates_dir)
        self.templates = PageTemplateLoader(
            self.templates_dir, default_extension='.pt')
        self.layout = self.templates['layout']
        # No need to make flashes a per-session thing - it's a single user app!
        self.flashes = []
        self.router = PathRouter()
        # XXX Add handler for exiting system
        # XXX Make exit code conditional? (upgrade/reboot/shutdown/etc.)
        self.router.add_routes([
            url('/',                   self.do_template, kwargs={'page': 'library'}, name='home'),
            url('/{page}.html',        self.do_template, name='template'),
            url('/view/{image}.html',  self.do_template, kwargs={'page': 'image'}, name='view'),
            url('/static/{path:any}',  self.do_static,   name='static'),
            url('/images/{image}',     self.do_image,    name='image'),
            url('/thumbs/{image}',     self.do_thumb,    name='thumb'),
            url('/delete/{image}',     self.do_delete,   name='delete'),
            url('/config',             self.do_config,   name='config'),
            url('/reset',              self.do_reset,    name='reset'),
            url('/capture',            self.do_capture,  name='capture'),
            url('/download',           self.do_download, name='download'),
            url('/send',               self.do_send,     name='send'),
            url('/logout',             self.do_logout,   name='logout'),
            ])

    def __call__(self, environ, start_response):
        req = Request(environ)
        try:
            if not IPv4Address(req.remote_addr) in self.clients:
                raise exc.HTTPForbidden()
            handler, kwargs = self.router.match(req.path_info)
            if handler:
                # XXX Why does route_name only appear in kwargs sometimes?!
                if 'route_name' in kwargs:
                    del kwargs['route_name']
                resp = handler(req, **kwargs)
            else:
                self.not_found(req)
        except exc.HTTPException as e:
            # The exception itself is a WSGI response
            resp = e
        return resp(environ, start_response)

    def not_found(self, req):
        """
        Handler for unknown locations (404)
        """
        raise exc.HTTPNotFound(
            'The resource at %s could not be found' % req.path_info)

    def do_reset(self, req):
        """
        Reset all settings to their defaults
        """
        self.library.camera_reset()
        self.flashes.append('Camera settings reset to defaults')
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_config(self, req):
        """
        Configure the library and camera settings
        """
        # Resolution is handled specially as the camera needs to stop the
        # preview in order to change it
        try:
            new_resolution = tuple(
                int(i) for i in req.params['resolution'].split('x', 1))
            if len(new_resolution) != 2:
                raise ValueError()
        except ValueError:
            self.flashes.append(
                'Invalid resolution: %s' % req.params['resolution'])
        if self.library.camera.resolution != new_resolution:
            try:
                self.library.camera.stop_preview()
                try:
                    self.library.camera.resolution = new_resolution
                finally:
                    self.library.camera.start_preview()
            except PiCameraError:
                self.flashes.append(
                    'Unable to change camera resolution '
                    'to %s' % req.params['resolution'])
        # Everything else is handled generically...
        for setting in (
                'sharpness', 'contrast', 'brightness', 'saturation', #'ISO',
                'exposure-compensation'):
            try:
                setattr(
                    self.library.camera, setting.replace('-', '_'),
                    int(req.params[setting])
                    )
            except ValueError:
                self.flashes.append(
                    'Invalid %s: %s' % (setting, req.params[setting]))
        for setting in ('hflip', 'vflip'):
            try:
                setattr(
                    self.library.camera, setting.replace('-', '_'),
                    bool(req.params.get(setting, 0))
                    )
            except ValueError:
                self.flashes.append(
                    'Invalid %s: %s' % (setting, req.params[setting]))
        for setting in ('meter-mode', 'awb-mode', 'exposure-mode'):
            try:
                setattr(
                    self.library.camera, setting.replace('-', '_'),
                    req.params[setting]
                    )
            except ValueError:
                self.flashes.append(
                    'Invalid %s: %s' % (setting, req.params[setting]))
        for setting in (
                'artist', 'email', 'copyright', 'description',
                'filename-template', 'format'):
            try:
                setattr(
                    self.library, setting.replace('-', '_'),
                    req.params[setting]
                    )
            except ValueError:
                self.flashes.append(
                    'Invalid %s: %s' % (setting, req.params[setting]))
        # If any settings failed, re-render the settings form
        if self.flashes:
            return self.do_template(req, 'settings')
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_capture(self, req):
        """
        Take a new image with the camera and add it to the library
        """
        self.library.capture()
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_download(self, req):
        """
        Send the library as a .zip archive
        """
        archive = self.library.archive()
        size = archive.seek(0, io.SEEK_END)
        archive.seek(0)
        resp = Response()
        resp.content_type = 'application/zip'
        resp.content_length = size
        resp.content_disposition = 'attachment; filename=images.zip'
        resp.app_iter = FileWrapper(archive)
        return resp

    def do_send(self, req):
        """
        Send the library as a set of attachments to an email
        """
        self.library.send()
        self.flashes.append('Email sent to %s' % library.email)
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_delete(self, req, image):
        """
        Delete the selected images from library
        """
        self.library.remove(image)
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='library'))

    def do_logout(self, req):
        """
        Clear the library of all images, reset all settings
        """
        self.library.clear()
        self.library.user_reset()
        self.library.camera_reset()
        raise exc.HTTPFound(
            location=self.router.path_for('template', page='settings'))

    def do_image(self, req, image):
        """
        Serve an image from the library
        """
        if not image in self.library:
            self.not_found(req)
        resp = Response()
        resp.content_type, resp.content_encoding = mimetypes.guess_type(
                image, strict=False)
        resp.content_length = self.library.stat_image(image).st_size
        resp.app_iter = FileWrapper(self.library.open_image(image))
        return resp

    def do_thumb(self, req, image):
        """
        Serve a thumbnail of an image from the library
        """
        if not image in self.library:
            self.not_found(req)
        resp = Response()
        resp.content_type = 'image/jpeg'
        resp.content_length = self.library.stat_thumbnail(image).st_size
        resp.app_iter = FileWrapper(self.library.open_thumbnail(image))
        return resp

    def do_static(self, req, path):
        """
        Serve static files from disk
        """
        path = os.path.normpath(os.path.join(self.static_dir, path))
        if not path.startswith(self.static_dir):
            self.not_found(req)
        resp = Response()
        resp.content_type, resp.content_encoding = mimetypes.guess_type(
                path, strict=False)
        if resp.content_type is None:
            resp.content_type = 'application/octet-stream'
        resp.content_length = os.stat(path).st_size
        resp.app_iter = FileWrapper(io.open(path, 'rb'))
        return resp

    def do_template(self, req, page, image=None):
        """
        Serve a Chameleon template-based page
        """
        resp = Response()
        resp.content_type = 'text/html'
        resp.content_encoding = 'utf-8'
        try:
            template = self.templates[page]
        except ValueError:
            self.not_found(req)
        resp.text = template(
            req=req,
            page=page,
            image=image,
            helpers=self.helpers,
            layout=self.layout,
            flashes=self.flashes,
            library=self.library,
            camera=self.library.camera,
            router=self.router)
        del self.flashes[:]
        return resp