def timestamp():
    '''Get current unix timestamp
    '''
    return int(datetime.now(tzutc()).timestamp()) def try_mkdir(directory):
    '''Try to create a directory. Pass without error if it already exists.
    '''
    try:
        os.mkdir(directory)
    except OSError as err:
        if err.errno != errno.EEXIST:
            raise err def terminate(shutdown=None):
    '''Mark process as to be terminated.
    '''
    global _terminate
    if shutdown is not None:
        _terminate = shutdown
    return '_terminate' in globals() and _terminate @db.with_session
def set_service_status(dbs, service, status):
    '''Update the status of a particular service in the database.
    '''
    srv = db.ServiceStates()
    srv.type = service
    srv.status = status

    dbs.merge(srv)
    dbs.commit() def set_service_status_immediate(service, status):
    '''Update the status of a particular service in the database and send an
    immediate signal to Opencast.
    '''
    set_service_status(service, status)
    update_agent_state() def recording_state(recording_id, status):
    '''Send the state of the current recording to the Matterhorn core.

    :param recording_id: ID of the current recording
    :param status: Status of the recording
    '''
    # If this is a backup CA we do not update the recording state since the
    # actual CA does that and we want to interfere.  We will just run silently
    # in the background:
    if config('agent', 'backup_mode'):
        return
    params = [('state', status)]
    url = service('capture.admin')[0]
    url += f'/recordings/{recording_id}'
    try:
        result = http_request(url, params).decode('utf-8')
        logger.info(result)
    except pycurl.error as e:
        logger.warning('Could not set recording state to %s: %s', status, e) @db.with_session
def update_event_status(dbs, event, status):
    '''Update the status of a particular event in the database.
    '''
    dbs.query(db.RecordedEvent).filter(db.RecordedEvent.start == event.start)\
                               .update({'status': status})
    event.status = status
    dbs.commit() def config(*args):
    '''Get a specific configuration value or the whole configuration, loading
    the configuration file if it was not before.

    :param key: optional configuration key to return
    :type key: string
    :return: Part of the configuration object containing the configuration
             or configuration value.
             If a part of the configuration object (e.g. configobj.Section) is
             returned, it can be treated like a dictionary.
             Returning None, if the configuration value is not found.
    '''
    cfg = __config or update_configuration()
    for key in args:
        if cfg is None:
            return
        cfg = cfg.get(key)
    return cfg def get_session():
    '''Get a session for database communication. If necessary a new connection
    to the database will be established.

    :return:  Database session
    '''
    if 'engine' not in globals():
        init()
    Session = sessionmaker(bind=engine)
    return Session() class RecordedEvent(Base, BaseEvent):
    '''List of events pyca tried to record.'''

    __tablename__ = 'recorded_event'

    def __init__(self, event=None):
        if event:
            self.uid = event.uid
            self.start = event.start
            self.end = event.end
            self.title = event.title
            self.data = event.data
            self.status = event.status class UpcomingEvent(Base, BaseEvent):
    '''List of upcoming events'''

    __tablename__ = 'upcoming_event' class Status(Constants):
    '''Event status definitions
    '''
    UPCOMING = 1
    RECORDING = 2
    FAILED_RECORDING = 3
    FINISHED_RECORDING = 4
    UPLOADING = 5
    FAILED_UPLOADING = 6
    FINISHED_UPLOADING = 7
    PARTIAL_RECORDING = 8
    PAUSED_AFTER_RECORDING = 9 class Service(Constants):
    '''Service type definitions
    '''
    AGENTSTATE = 1
    CAPTURE = 2
    INGEST = 3
    SCHEDULE = 4 class ServiceStatus(Constants):
    '''Service status type definitions
    '''
    STOPPED = 1
    IDLE = 2
    BUSY = 3 def with_session(f):
    """Wrapper for f to make a SQLAlchemy session present within the function

    :param f: Function to call
    :type f: Function
    :raises e: Possible exception of f
    :return: Result of f
    """
    @wraps(f)
    def decorated(*args, **kwargs):
        session = get_session()
        try:
            result = f(session, *args, **kwargs)
        except Exception as e:
            session.rollback()
            raise e
        finally:
            session.close()
        return result
    return decorated
from pyca.utils import timestamp, try_mkdir, terminate
from pyca.utils import set_service_status, set_service_status_immediate
from pyca.utils import recording_state, update_event_status
from pyca.config import config
from pyca.db import get_session, RecordedEvent, UpcomingEvent, Status,\
                    Service, ServiceStatus, with_session
import glob
import logging
import os
import os.path
import sdnotify
import shlex
import signal
import subprocess
import sys
import time
# -*- coding: utf-8 -*-
'''
    python-capture-agent
    ~~~~~~~~~~~~~~~~~~~~

    :copyright: 2014-2017, Lars Kiesow <lkiesow@uos.de>
    :license: LGPL – see license.lgpl for more details.
'''


logger = logging.getLogger(__name__)
notify = sdnotify.SystemdNotifier()
captureproc = None


def sigterm_handler(signum, frame):
    '''Intercept sigterm and terminate all processes.
    '''
    if captureproc and captureproc.poll() is None:
        captureproc.terminate()
    terminate(True)
    sys.exit(0)


@with_session
def start_capture(db, upcoming_event):
    '''Start the capture process, creating all necessary files and directories
    as well as ingesting the captured files if no backup mode is configured.
    '''
    logger.info('Start recording')

    # First move event to recording_event table
    event = db.query(RecordedEvent)\
              .filter(RecordedEvent.uid == upcoming_event.uid)\
              .filter(RecordedEvent.start == upcoming_event.start)\
              .first()
    if not event:
        event = RecordedEvent(upcoming_event)
        db.add(event)
        db.commit()

    try_mkdir(config('capture', 'directory'))

    try_mkdir(event.directory())