def handle(self, *args, **options):
        self._commit = options['commit']
        self._courses = {}
        self._session = SessionManagement()
        self._recorder = RemoteRecorderManagement()

        if options['stdin']:
            for line in sys.stdin:
                self._process_course(line.rstrip('\n'))
        else:
            for session in args:
                self._process_course(session)
class SessionRecordingTime(RESTDispatch):
    def __init__(self):
        self._recorder_api = RemoteRecorderManagement()
        self._audit_log = logging.getLogger('audit')

    def GET(self, request, **kwargs):
        session_id = kwargs.get('session_id')
        if session_id:
            raw_session = self._session_api.getSessionsById([session_id])[0][0]
            start_utc = raw_session.StartTime.astimezone(pytz.utc)
            end_utc = start_utc + datetime.timedelta(
                seconds=int(raw_session.Duration))
            recording_time = {
                'start': start_utc.isoformat(),
                'end': end_utc.isoformat()
            }
        else:
            recording_time = {}

        return self.json_response(recording_time)

    def PUT(self, request, **kwargs):
        try:
            session_id = kwargs.get('session_id')
            data = json.loads(request.body)
            start_time = self._valid_time(data.get("start", "").strip())
            end_time = self._valid_time(data.get("end", "").strip())

            self._recorder_api.updateRecordingTime(
                session_id, start_time, end_time)

            self._audit_log.info('%s set %s start/stop to %s and %s' % (
                request.user, session_id, start_time, end_time))

            return self.json_response({
                'recording_id': session_id
            })
        except InvalidParamException as ex:
            return self.error_response(400, "%s" % ex)
        except Exception as ex:
            return self.error_response(500, "Unable to save session: %s" % ex)

    def _valid_time(self, time):
        if time and len(time):
            return time

        raise InvalidParamException('bad time value')
    def handle(self, *args, **options):
        self._commit = options['commit']
        self._courses = {}
        self._session = SessionManagement()
        self._recorder = RemoteRecorderManagement()

        if options['stdin']:
            for line in sys.stdin:
                self._process_course(line.rstrip('\n'))
        else:
            for session in args:
                self._process_course(session)
class Command(BaseCommand):
    help = "Matchrecording session dates to SWS meeting times"

    option_list = BaseCommand.option_list + (
        make_option('--commit',
                    dest='commit',
                    action="store_true",
                    default=False,
                    help='Update Panopto recording with SWS meeting time'),
        make_option('--stdin',
                    dest='stdin',
                    action="store_true",
                    default=False,
                    help='get Panopto session external ids on standard input'),
    )

    def handle(self, *args, **options):
        self._commit = options['commit']
        self._courses = {}
        self._session = SessionManagement()
        self._recorder = RemoteRecorderManagement()

        if options['stdin']:
            for line in sys.stdin:
                self._process_course(line.rstrip('\n'))
        else:
            for session in args:
                self._process_course(session)

    def _process_course(self, session_id):
        # 2015-spring-PSYCH-202-A-2015-06-04
        course = re.match(
            r'^(20[0-9]{2})-(winter|spring|summer|autumn)'
            r'-([A-Z ]+)-([0-9]{3})-([A-Z][A-Z0-9]*)-2*', session_id)

        if course:
            label = "%s,%s,%s,%s/%s" % (course.group(1), course.group(2),
                                        course.group(3), course.group(4),
                                        course.group(5))

            if label not in self._courses:
                now = datetime.datetime.now(tz.tzlocal()).replace(
                    second=0, microsecond=0)
                section = get_section_by_label(
                    label, include_instructor_not_on_time_schedule=False)
                (start, end) = self._lecture_times(section)
                self._courses[label] = {
                    'start': start.split(':'),
                    'end': end.split(':')
                }

            offered = self._courses[label]
        else:
            print >> sys.stderr, "unrecognized session id: %s" % session_id
            return

        pan_session = self._session.getSessionsByExternalId([session_id])
        if 'Session' in pan_session and len(pan_session.Session) == 1:
            # broken-ass suds.
            fsuds = re.match(r'.*\<a\:StartTime\>([^<]+)\<\/a\:StartTime\>.*',
                             self._session._api.last_received().plain())
            if not fsuds:
                Exception('Untrustable time')

            pan_start = parser.parse(fsuds.group(1))
            pan_start_local = pan_start.astimezone(tz.tzlocal())
            sws_start_local = pan_start_local.replace(
                hour=int(offered['start'][0]), minute=int(offered['start'][1]))
            sws_end_local = pan_start_local.replace(
                hour=int(offered['end'][0]), minute=int(offered['end'][1]))

            schedule_delta = sws_start_local - pan_start_local

            duration_delta = (sws_end_local - sws_start_local).seconds - int(
                pan_session.Session[0].Duration)

            if schedule_delta or duration_delta:
                pan_start = (pan_start_local + schedule_delta).astimezone(
                    tz.tzutc())

                duration = pan_session.Session[0].Duration
                if duration_delta:
                    duration += duration_delta

                pan_end = pan_start + datetime.timedelta(0, duration)

                adjustment = [
                    session_id,
                    '(%s)' % pan_session.Session[0].Id,
                    '' if self._commit else 'WOULD', 'RESCHEDULE',
                    fsuds.group(1), 'TO',
                    pan_start.isoformat(), ':'
                ]

                if schedule_delta.days < 0:
                    adjustment.append("(-%s shift)" %
                                      (datetime.timedelta() - schedule_delta))
                else:
                    adjustment.append("(%s shift)" % schedule_delta)

                if duration_delta:
                    adjustment.append('AND DURATION')
                    adjustment.append("%s" % duration_delta)
                    adjustment.append('seconds')

                print >> sys.stderr, ' '.join(adjustment)

                if self._commit:
                    result = self._recorder.updateRecordingTime(
                        pan_session.Session[0].Id, pan_start.isoformat(),
                        pan_end.isoformat())
                    if not result:
                        print >> sys.stderr, "FAIL: null return value"
                    elif result.ConflictsExist:
                        print >> sys.stderr, "CONFLICT: %s" % (
                            result.ConflictingSessions[0][0].SessionName)
                    else:
                        print >> sys.stderr, "UPDATED %s" % (
                            result.SessionIDs[0][0])
            else:
                print >> sys.stderr, "%s: UNCHANGED" % (session_id)

        else:
            print >> sys.stderr, "unrecognized session id: %s" % session_id

    def _lecture_times(self, section):
        for meeting in section.meetings:
            if (meeting.meeting_type in ['lecture', 'quiz', 'seminar']
                    and meeting.start_time and meeting.end_time):
                return meeting.start_time, meeting.end_time

        Exception("no lecture times set")
 def __init__(self):
     self._recorder_api = RemoteRecorderManagement()
     self._audit_log = logging.getLogger('audit')
 def __init__(self):
     self._session_api = SessionManagement()
     self._recorder_api = RemoteRecorderManagement()
     self._access_api = AccessManagement()
     self._user_api = UserManagement()
     self._audit_log = logging.getLogger('audit')
class Session(RESTDispatch):
    def __init__(self):
        self._session_api = SessionManagement()
        self._recorder_api = RemoteRecorderManagement()
        self._access_api = AccessManagement()
        self._user_api = UserManagement()
        self._audit_log = logging.getLogger('audit')

    def GET(self, request, **kwargs):
        session_id = kwargs.get('session_id')
        if session_id:
            raw_session = self._session_api.getSessionsById(
                [session_id])[0][0]
            raw_access = self._access_api.getSessionAccessDetails(
                session_id)
            start_utc = pytz.utc.localize(
                raw_session['StartTime']).astimezone(tz.tzutc())
            session = {
                'creator_id': raw_session['CreatorId'],
                'description': raw_session['Description'],
                'duration': raw_session['Duration'],
                'external_id': raw_session['ExternalId'],
                'folder_id': raw_session['FolderId'],
                'folder_name': raw_session['FolderName'],
                'folder_creators': [],
                'id': raw_session['Id'],
                'is_video_url': raw_session['IosVideoUrl'],
                'is_broadcast': raw_session['IsBroadcast'],
                'is_public': raw_access['IsPublic'],
                'is_downloadable': raw_session['IsDownloadable'],
                'name': raw_session['Name'],
                'remote_recorder_ids': raw_session['RemoteRecorderIds'].get(
                    'guid', None),
                'share_page_url': raw_session['SharePageUrl'],
                'start_time': start_utc.isoformat(),
                'state': raw_session['State'],
                'status_message': raw_session['StatusMessage'],
                'thumb_url': raw_session['ThumbUrl'],
                'viewer_url': raw_session['ViewerUrl'],
            }
        else:
            session = {}

        return self.json_response(session)

    def POST(self, request, **kwargs):
        try:
            new_session = self._validate_session(request.body)

            session = self._recorder_api.scheduleRecording(new_session.get('name'),
                                                           new_session.get('folder_id'),
                                                           new_session.get('is_broadcast'),
                                                           new_session.get('start_time'),
                                                           new_session.get('end_time'),
                                                           new_session.get('recorder_id'))
            if session.ConflictsExist:
                conflict = session.ConflictingSessions[0][0]
                start_time = conflict.StartTime
                end_time = conflict.EndTime
                content = {
                    'conflict_name': conflict.SessionName,
                    'conflict_start': start_time.isoformat(),
                    'conflict_end': end_time.isoformat()
                }
                return self.error_response(409, "Schedule Conflict Exists",
                                           content=content)

            session_id = session.SessionIDs[0][0]

            self._session_api.updateSessionExternalId(
                session_id, new_session.get('external_id'))

            if new_session.get('is_public'):
                self._access_api.updateSessionIsPublic(session_id, True)

            messages = []
            creators = new_session.get('folder_creators')
            if creators and type(creators) is list:
                messages = self._sync_creators(
                    new_session.get('folder_id'), creators)

            self._audit_log.info('%s scheduled %s for %s from %s to %s' % (
                request.user, new_session.get('external_id'),
                new_session.get('uwnetid'), new_session.get('start_time'),
                new_session.get('end_time')))

            return self.json_response({
                'recording_id': session_id,
                'messages': messages
            })
        except InvalidParamException as ex:
            return self.error_response(400, "%s" % ex)
        except Exception as ex:
            return self.error_response(500, "Unable to save session: %s" % ex)

    def PUT(self, request, **kwargs):
        try:
            session_update = self._validate_session(request.body)
            session = self._session_api.getSessionsById(
                session_update.get('recording_id'))[0][0]

            start_utc = session.StartTime.astimezone(pytz.utc)
            end_utc = start_utc + datetime.timedelta(
                seconds=int(session.Duration))

            session_update_start = self._valid_time(session_update.get('start_time'))
            session_update_end = self._valid_time(session_update.get('end_time'))

            if not (start_utc.isoformat() == session_update_start
                    and end_utc.isoformat() == session_update_end):
                self._recorder_api.updateRecordingTime(
                    session.Id, session_update_start, session_update_end)

            access = self._access_api.getSessionAccessDetails(session.Id)
            if access.IsPublic != session_update.get('is_public'):
                self._access_api.updateSessionIsPublic(
                    session.Id, session_update.get('is_public'))

            if session.IsBroadcast != session_update.get('is_broadcast'):
                self._session_api.updateSessionIsBroadcast(
                    session.Id, session_update.get('is_broadcast'))

            folder_name = session_update.get('folder_name')
            if session.FolderName != folder_name:
                self._session_api.moveSessions(
                    [session.Id], session_update.get('folder_id'))

            messages = []
            creators = session_update.get('folder_creators')
            if creators and type(creators) is list:
                messages = self._sync_creators(
                    session_update.get('folder_id'), creators)

            self._audit_log.info('%s modified %s for %s from %s to %s in %s' % (
                request.user, session_update.get('external_id'),
                session_update.get('uwnetid'), session_update.get('start_time'),
                session_update.get('end_time'), session_update.get('folder_name')))

            return self.json_response({
                'recording_id': session.Id,
                'messages': messages
            })
        except InvalidParamException as ex:
            return self.error_response(400, "%s" % ex)
        except Exception as ex:
            return self.error_response(500, "Unable to save session: %s" % ex)

    def DELETE(self, request, **kwargs):
        try:
            session_id = self._valid_recorder_id(kwargs.get('session_id'))
            # do not permit param tampering
            key = course_event_key(request.GET.get('uwnetid', ''),
                                   request.GET.get('name', ''),
                                   request.GET.get('eid', ''),
                                   request.GET.get('rid', ''))

            if key != request.GET.get("key", None):
                raise InvalidParamException('Invalid Client Key')

            self._session_api.deleteSessions([session_id])
            self._audit_log.info('%s deleted session %s' %
                                 (request.user, session_id))
            return self.json_response({
                'deleted_recording_id': session_id
            })
        except InvalidParamException as err:
            return self.error_response(400, "Invalid Parameter: %s" % err)

    def _valid_folder(self, name, external_id):
        try:
            folder_id = Validation().panopto_id(external_id)
            return folder_id
        except InvalidParamException:
            pass

        try:
            if external_id and len(external_id):
                folders = self._session_api.getAllFoldersByExternalId(
                    [external_id])
                if folders and len(folders) == 1 and len(folders[0]):
                    return folders[0][0].Id

            folders = self._session_api.getFoldersList(search_query=name)
            if folders and len(folders):
                for folder in folders:
                    if folder.Name == name:
                        folder_id = folder.Id
                        if external_id and len(external_id):
                            self._session_api.updateFolderExternalId(
                                folder_id, external_id)

                        return folder_id

            new_folder = self._session_api.addFolder(name)
            if not new_folder:
                raise InvalidParamException('Cannot add folder: %s' % name)

            new_folder_id = new_folder.Id

            if external_id and len(external_id):
                self._session_api.updateFolderExternalId(
                    new_folder_id, external_id)

            return new_folder_id
        except Exception as ex:
            raise InvalidParamException('Cannot add folder: %s' % ex)

    def _validate_session(self, request_body):
        session = {}
        data = json.loads(request_body)

        session['recording_id'] = data.get("recording_id", "")
        session['uwnetid'] = data.get("uwnetid", "")
        session['name'] = self._valid_recording_name(data.get("name", "").strip())
        session['external_id'] = self._valid_external_id(
            data.get("external_id", "").strip())
        session['recorder_id'] = self._valid_recorder_id(
            data.get("recorder_id", "").strip())
        session['folder_external_id'] = data.get(
            "folder_external_id", "").strip()

        session['session_id'] = data.get("session_id", "").strip()
        if len(session['session_id']):
            self._valid_external_id(session['session_id'])

        # do not permit param tamperings
        key = course_event_key(session['uwnetid'], session['name'],
                               session['external_id'], session['recorder_id'])
        if key != data.get("key", ''):
            raise InvalidParamException('Invalid Client Key')

        session['is_broadcast'] = self._valid_boolean(data.get("is_broadcast", False))
        session['is_public'] = self._valid_boolean(data.get("is_public", False))
        session['start_time'] = self._valid_time(data.get("start_time", "").strip())
        session['end_time'] = self._valid_time(data.get("end_time", "").strip())
        session['folder_name'] = data.get("folder_name", "").strip()
        session['folder_id'] = self._valid_folder(session['folder_name'],
                                                  session['folder_external_id'])
        session['folder_creators'] = data.get("creators", None)
        return session

    def _valid_external_id(self, external_id):
        if external_id and len(external_id):
            return external_id

        raise InvalidParamException('bad external_id')

    def _valid_recorder_id(self, recorder_id):
        if (recorder_id):
            return Validation().panopto_id(recorder_id)

        raise InvalidParamException('missing recorder id')

    def _valid_recording_name(self, name):
        if name and len(name):
            return name

        raise InvalidParamException('bad recording name')

    def _valid_boolean(self, is_broadcast):
        if not (is_broadcast is None or type(is_broadcast) == bool):
            raise InvalidParamException('bad broadcast flag')

        return is_broadcast

    def _valid_time(self, time):
        if time and len(time):
            return time

        raise InvalidParamException('bad time value')

    def _sync_creators(self, folder_id, folder_creators):
        messages = []
        new_creator_ids = []
        deleted_creator_ids = []
        current_creators = get_panopto_folder_creators(folder_id)
        for creator in folder_creators:
            if creator not in current_creators:
                try:
                    new_creator_ids.append(self._get_panopto_user_id(creator))
                except PanoptoUserException as ex:
                    messages.append('Invalid UWNetId %s' % creator)

        for creator in current_creators:
            if creator not in folder_creators:
                try:
                    deleted_creator_ids.append(self._get_panopto_user_id(creator))
                except PanoptoUserException as ex:
                    messages.append('Invalid UWNetId %s' % creator)

        if len(new_creator_ids):
            try:
                self._access_api.grantUsersAccessToFolder(
                    folder_id, new_creator_ids, 'Creator')
            except PanoptoAPIException as ex:
                match = re.match(r'.*Server raised fault: \'(.+)\'$', str(ex))
                messages.append('%s: %s' % (creator, match.group(1) if match else str(ex)))

        if len(deleted_creator_ids):
            try:
                self._access_api.revokeUsersAccessFromFolder(
                    folder_id, deleted_creator_ids, 'Creator')
            except PanoptoAPIException as ex:
                match = re.match(r'.*Server raised fault: \'(.+)\'$', str(ex))
                messages.append('%s: %s' % (creator, match.group(1) if match else str(ex)))

        return messages

    def _get_panopto_user_id(self, netid):
        key = "%s\%s" % (settings.PANOPTO_API_APP_ID, netid)
        user = self._user_api.getUserByKey(key)
        if not user or user['UserId'] == '00000000-0000-0000-0000-000000000000':
            raise PanoptoUserException('Unprovisioned UWNetId: %s' % (netid))

        return user['UserId']
Beispiel #8
0
 def __init__(self):
     self._api = RemoteRecorderManagement()
     # timeout in hours
     self._space_list_cache_timeout = 1
class Recorder(RESTDispatch):
    def __init__(self):
        self._api = RemoteRecorderManagement()
        # timeout in hours
        self._space_list_cache_timeout = 1

    def GET(self, request, **kwargs):
        recorder_id = kwargs.get('recorder_id')
        if request.GET.get('timeout'):
            self._space_list_cache_timeout = float(request.GET.get('timeout'))
        if (recorder_id):
            return self._get_recorder_details(recorder_id)
        else:
            return self._list_recorders()

    def PUT(self, request, **kwargs):
        recorder_id = kwargs.get('recorder_id')
        try:
            Validation().panopto_id(recorder_id)
            data = json.loads(request.body)
            external_id = data.get('external_id', None)
            if external_id is not None:
                rv = self._api.updateRemoteRecorderExternalId(
                    recorder_id, external_id)
                try:
                    cache_entry = RecorderCacheEntry.objects.get(
                        recorder_id=recorder_id)
                    cache_entry.recorder_external_id = external_id
                    cache_entry.save()
                except RecorderCacheEntry.DoesNotExist:
                    pass

            return self._get_recorder_details(recorder_id)
        except (MissingParamException, InvalidParamException,
                PanoptoAPIException) as err:
            return self.error_response(400, message="%s" % err)

    def _get_recorder_details(self, recorder_id):
        try:
            recorders = get_api_recorder_details(self._api, recorder_id)
        except (RecorderException, PanoptoAPIException, MissingParamException,
                InvalidParamException) as err:
            return self.error_response(400, message="%s" % err)

        if recorders is None:
            return self.error_response(404, message="No Recorder Found")

        return self._recorder_rep(recorders)

    def _recorder_rep(self, recorders):
        reps = []
        for recorder in recorders:
            rep = {
                'id': recorder.Id,
                'external_id': recorder.ExternalId,
                'name': recorder.Name,
                'settings_url': recorder.SettingsUrl,
                'state': recorder.State,
                'space': None,
                'scheduled_recordings': []
            }

            if recorder.ScheduledRecordings and hasattr(
                    recorder.ScheduledRecordings, 'guid'):
                for recording in recorder.ScheduledRecordings.guid:
                    rep['scheduled_recordings'].append(recording)

            if recorder.ExternalId:
                try:
                    space = get_space_by_id(recorders[0].ExternalId)
                    rep['space'] = {
                        'space_id': space.space_id,
                        'name': space.name,
                        'formal_name': space.formal_name
                    }
                except DataFailureException as err:
                    logger.error('Cannot get space for id: %s: %s' %
                                 (recorders[0].ExternalId, err))

            reps.append(rep)

        return self.json_response(reps)

    def _list_recorders(self):
        try:
            rec_cache = RecorderCache.objects.all()[0]
            now = pytz.UTC.localize(datetime.datetime.now())
            timeout = datetime.timedelta(hours=self._space_list_cache_timeout)
            if (now - timeout) > rec_cache.created_date:
                self._scrub_recorder_cache(rec_cache)

        except (IndexError, RecorderCache.DoesNotExist):
            try:
                recorders = self._api.listRecorders()
                rec_cache = self._cache_recorders(recorders)
            except PanoptoAPIException as err:
                return self.error_response(400, message="%s" % err)

        rep = []
        for recorder in RecorderCacheEntry.objects.filter(cache=rec_cache):
            rep.append({
                'id': recorder.recorder_id,
                'external_id': recorder.recorder_external_id,
                'name': recorder.name,
                'scheduled_recordings': []
            })

        return self.json_response(rep)

    def _cache_recorders(self, recorders):
        rec_cache = RecorderCache()
        rec_cache.save()
        for recorder in recorders:
            RecorderCacheEntry.objects.create(
                cache=rec_cache,
                recorder_id=recorder.Id,
                recorder_external_id=recorder.ExternalId or '',
                name=recorder.Name)

        return rec_cache

    def _scrub_recorder_cache(self, rec_cache):
        RecorderCacheEntry.objects.filter(cache=rec_cache).delete()
        rec_cache.delete()
        raise RecorderCache.DoesNotExist()
 def __init__(self):
     self._api = RemoteRecorderManagement()
     # timeout in hours
     self._space_list_cache_timeout = 1
class Recorder(RESTDispatch):
    def __init__(self):
        self._api = RemoteRecorderManagement()
        # timeout in hours
        self._space_list_cache_timeout = 1

    def GET(self, request, **kwargs):
        recorder_id = kwargs.get('recorder_id')
        if request.GET.get('timeout'):
            self._space_list_cache_timeout = float(request.GET.get('timeout'))
        if (recorder_id):
            return self._get_recorder_details(recorder_id)
        else:
            return self._list_recorders()

    def PUT(self, request, **kwargs):
        recorder_id = kwargs.get('recorder_id')
        try:
            Validation().panopto_id(recorder_id)
            data = json.loads(request.body)
            external_id = data.get('external_id', None)
            if external_id is not None:
                rv = self._api.updateRemoteRecorderExternalId(recorder_id,
                                                              external_id)
                try:
                    cache_entry = RecorderCacheEntry.objects.get(
                        recorder_id=recorder_id)
                    cache_entry.recorder_external_id = external_id
                    cache_entry.save()
                except RecorderCacheEntry.DoesNotExist:
                    pass

            return self._get_recorder_details(recorder_id)
        except (MissingParamException, InvalidParamException,
                PanoptoAPIException) as err:
            return self.error_response(400, message="%s" % err)

    def _get_recorder_details(self, recorder_id):
        try:
            recorders = get_api_recorder_details(self._api, recorder_id)
        except (RecorderException, PanoptoAPIException,
                MissingParamException, InvalidParamException) as err:
            return self.error_response(400, message="%s" % err)

        if recorders is None:
            return self.error_response(404, message="No Recorder Found")

        return self._recorder_rep(recorders)

    def _recorder_rep(self, recorders):
        reps = []
        for recorder in recorders:
            rep = {
                'id': recorder.Id,
                'external_id': recorder.ExternalId,
                'name': recorder.Name,
                'settings_url': recorder.SettingsUrl,
                'state': recorder.State,
                'space': None,
                'scheduled_recordings': []
            }

            if recorder.ScheduledRecordings and hasattr(
                    recorder.ScheduledRecordings, 'guid'):
                for recording in recorder.ScheduledRecordings.guid:
                    rep['scheduled_recordings'].append(recording)

            if recorder.ExternalId:
                try:
                    space = get_space_by_id(recorders[0].ExternalId)
                    rep['space'] = {
                        'space_id': space.space_id,
                        'name': space.name,
                        'formal_name': space.formal_name
                    }
                except DataFailureException as err:
                    logger.error('Cannot get space for id: %s: %s' %
                                 (recorders[0].ExternalId, err))

            reps.append(rep)

        return self.json_response(reps)

    def _list_recorders(self):
        try:
            rec_cache = RecorderCache.objects.all()[0]
            now = pytz.UTC.localize(datetime.datetime.now())
            timeout = datetime.timedelta(hours=self._space_list_cache_timeout)
            if (now - timeout) > rec_cache.created_date:
                self._scrub_recorder_cache(rec_cache)

        except (IndexError, RecorderCache.DoesNotExist):
            try:
                recorders = self._api.listRecorders()
                rec_cache = self._cache_recorders(recorders)
            except PanoptoAPIException as err:
                return self.error_response(400, message="%s" % err)

        rep = []
        for recorder in RecorderCacheEntry.objects.filter(cache=rec_cache):
            rep.append({
                'id': recorder.recorder_id,
                'external_id': recorder.recorder_external_id,
                'name': recorder.name,
                'scheduled_recordings': []
            })

        return self.json_response(rep)

    def _cache_recorders(self, recorders):
        rec_cache = RecorderCache()
        rec_cache.save()
        for recorder in recorders:
            RecorderCacheEntry.objects.create(
                cache=rec_cache,
                recorder_id=recorder.Id,
                recorder_external_id=recorder.ExternalId or '',
                name=recorder.Name)

        return rec_cache

    def _scrub_recorder_cache(self, rec_cache):
        RecorderCacheEntry.objects.filter(cache=rec_cache).delete()
        rec_cache.delete()
        raise RecorderCache.DoesNotExist()
class Command(BaseCommand):
    help = "Matchrecording session dates to SWS meeting times"

    option_list = BaseCommand.option_list + (
        make_option('--commit', dest='commit', action="store_true",
                    default=False,
                    help='Update Panopto recording with SWS meeting time'),
        make_option('--stdin', dest='stdin', action="store_true",
                    default=False,
                    help='get Panopto session external ids on standard input'),
    )

    def handle(self, *args, **options):
        self._commit = options['commit']
        self._courses = {}
        self._session = SessionManagement()
        self._recorder = RemoteRecorderManagement()

        if options['stdin']:
            for line in sys.stdin:
                self._process_course(line.rstrip('\n'))
        else:
            for session in args:
                self._process_course(session)

    def _process_course(self, session_id):
        # 2015-spring-PSYCH-202-A-2015-06-04
        course = re.match(r'^(20[0-9]{2})-(winter|spring|summer|autumn)'
                          r'-([A-Z ]+)-([0-9]{3})-([A-Z][A-Z0-9]*)-2*',
                          session_id)

        if course:
            label = "%s,%s,%s,%s/%s" % (
                course.group(1), course.group(2), course.group(3),
                course.group(4), course.group(5))

            if label not in self._courses:
                now = datetime.datetime.now(
                    tz.tzlocal()).replace(second=0, microsecond=0)
                section = get_section_by_label(
                    label, include_instructor_not_on_time_schedule=False)
                (start, end) = self._lecture_times(section)
                self._courses[label] = {
                    'start': start.split(':'),
                    'end': end.split(':')
                }

            offered = self._courses[label]
        else:
            print >> sys.stderr, "unrecognized session id: %s" % session_id
            return

        pan_session = self._session.getSessionsByExternalId([session_id])
        if 'Session' in pan_session and len(pan_session.Session) == 1:
            # broken-ass suds.
            fsuds = re.match(r'.*\<a\:StartTime\>([^<]+)\<\/a\:StartTime\>.*',
                             self._session._api.last_received().plain())
            if not fsuds:
                Exception('Untrustable time')

            pan_start = parser.parse(fsuds.group(1))
            pan_start_local = pan_start.astimezone(tz.tzlocal())
            sws_start_local = pan_start_local.replace(
                hour=int(offered['start'][0]),
                minute=int(offered['start'][1]))
            sws_end_local = pan_start_local.replace(
                hour=int(offered['end'][0]),
                minute=int(offered['end'][1]))

            schedule_delta = sws_start_local - pan_start_local

            duration_delta = (sws_end_local - sws_start_local).seconds - int(
                pan_session.Session[0].Duration)

            if schedule_delta or duration_delta:
                pan_start = (pan_start_local +
                             schedule_delta).astimezone(tz.tzutc())

                duration = pan_session.Session[0].Duration
                if duration_delta:
                    duration += duration_delta

                pan_end = pan_start + datetime.timedelta(0, duration)

                adjustment = [session_id, '(%s)' % pan_session.Session[0].Id,
                              '' if self._commit else 'WOULD', 'RESCHEDULE',
                              fsuds.group(1), 'TO',
                              pan_start.isoformat(), ':']

                if schedule_delta.days < 0:
                    adjustment.append("(-%s shift)" % (datetime.timedelta() -
                                                       schedule_delta))
                else:
                    adjustment.append("(%s shift)" % schedule_delta)

                if duration_delta:
                    adjustment.append('AND DURATION')
                    adjustment.append("%s" % duration_delta)
                    adjustment.append('seconds')

                print >> sys.stderr, ' '.join(adjustment)

                if self._commit:
                    result = self._recorder.updateRecordingTime(
                        pan_session.Session[0].Id,
                        pan_start.isoformat(),
                        pan_end.isoformat())
                    if not result:
                        print >> sys.stderr, "FAIL: null return value"
                    elif result.ConflictsExist:
                        print >> sys.stderr, "CONFLICT: %s" % (
                            result.ConflictingSessions[0][0].SessionName)
                    else:
                        print >> sys.stderr, "UPDATED %s" % (
                            result.SessionIDs[0][0])
            else:
                print >> sys.stderr, "%s: UNCHANGED" % (session_id)

        else:
            print >> sys.stderr, "unrecognized session id: %s" % session_id

    def _lecture_times(self, section):
        for meeting in section.meetings:
            if (meeting.meeting_type in ['lecture', 'quiz', 'seminar'] and
                    meeting.start_time and meeting.end_time):
                return meeting.start_time, meeting.end_time

        Exception("no lecture times set")
def get_recorder_details(recorder_id):
    return get_api_recorder_details(RemoteRecorderManagement(), recorder_id)