コード例 #1
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def _clean(request, room):
    with ac_api_client(room.sco.acc) as api:
        room.deleted_sco = room.sco
        room.save()
        api.request('sco-delete', {'sco-id': room.sco.sco_id}, raise_error=False)
        room.sco = None
    return _update_room(request, room)
コード例 #2
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def timed_full_import():
    years = [2009, 2010, 2011, 2012, 2013, 2014]
    months = [(1, 3), (4, 7), (8, 10), (9, 12)]
    for acc in ACCluster.objects.all():
        nr = 0
        for year in years:
            for month in months:
                begin = datetime(year=year, month=month[0], day=1)
                end = datetime(year=year, month=month[1], day=31)
                with ac_api_client(acc) as api:
                    try:
                        r = api.request('report-bulk-objects',
                                        {'filter-out-type': 'meeting',
                                         'filter-gte-date-modified': begin.isoformat(),
                                         'filter-lte-date-modified': end.isoformat()},
                                        raise_error=False)
                        if r:
                            nr = 0
                            for row in r.et.xpath("//row"):
                                Content.create(acc, api, row)
                                nr += 1
                    except ACPException as e:
                        logging.error('ACPException in content.timed_full_import')
                        logging.error('Period %s %s-%s failed for cluster %s.' % (year, month[0], month[1], acc))
                        logging.error(e)
                        logging.error(traceback.format_exc())
                        pass
                    except Exception as e:
                        logging.error('Exception in content.timed_full_import')
                        logging.error('Period %s %s-%s failed for cluster %s.' % (year, month[0], month[1], acc))
                        logging.error(e)
                        logging.error(traceback.format_exc())
                        pass
                    logging.info("%s: Imported %d content objects." % (acc, nr))
コード例 #3
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def import_acc_sessions(acc,since=0,room_last_visited=False):
    with ac_api_client(acc) as api:
        p = {'sort': 'asc','sort1': 'date-created','filter-type': 'meeting'}

        begin = None
        if since > 0:
            begin = datetime.now()-timedelta(seconds=since)
            begin = begin.replace(microsecond=0)

        if begin is not None:
            p['filter-gte-date-created'] = begin.isoformat()

        r = api.request('report-bulk-consolidated-transactions',p,True)
        nr = 0
        ne = 0
        for tx in r.et.findall(".//row"):
            try:
                tx = UserMeetingTransaction.create(acc,tx)
                if tx:
                    if room_last_visited:
                        rooms = Room.objects.filter(sco=tx.sco)
                        if len(rooms) == 1:
                            room = rooms[0]
                            if room.lastvisited is None or room.lastvisited < tx.date_closed:
                                room.lastvisited = tx.date_created
                                room.save()
                    nr += 1
            except Exception,ex:
                logging.error(ex)
                ne += 1
        logging.info("%s: Imported %d transactions with %d errors" % (acc,nr,ne))
コード例 #4
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def timed_full_import():
    years = [2009, 2010, 2011, 2012, 2013, 2014]
    months = [(1, 3), (4, 7), (8, 10), (9, 12)]
    for acc in ACCluster.objects.all():
        for year in years:
            for month in months:
                begin = datetime(year=year, month=month[0], day=1)
                end = datetime(year=year, month=month[1], day=31)
                with ac_api_client(acc) as api:
                    p = {'sort': 'asc', 'sort1': 'date-created', 'filter-type': 'meeting',
                         'filter-gte-date-created': begin.isoformat(), 'filter-lte-date-created': end.isoformat()}
                    r = api.request('report-bulk-consolidated-transactions', p, False)
                    nr = 0
                    ne = 0
                    for tx in r.et.findall(".//row"):
                        try:
                            tx = UserMeetingTransaction.create(acc, tx)
                            if tx:
                                rooms = Room.objects.filter(sco=tx.sco)
                                if len(rooms) == 1:
                                    room = rooms[0]
                                    if room.lastvisited is None or room.lastvisited < tx.date_closed:
                                        room.lastvisited = tx.date_created
                                        room.save()
                                nr += 1
                        except Exception, ex:
                            logging.error(ex)
                            ne += 1
                    logging.info("%s: Imported %d transactions with %d errors" % (acc, nr, ne))
コード例 #5
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def _goto(request, room, clean=True, promote=False):
    if room.is_locked():
        return respond_to(request, {"text/html": "apps/room/retry.html"}, {'room': room, 'wait': 10})

    now = time.time()
    lastvisit = room.lastvisit()
    room.lastvisited = datetime.now()

    with ac_api_client(room.sco.acc) as api:
        api.poll_user_counts(room)
    if clean:
        # don't clean the room unless you get a good status code from the call to the usermanager api above
        if room.self_cleaning and room.user_count == 0:
            if (room.user_count == 0) and (abs(lastvisit - now) > settings.GRACE):
                room.lock("Locked for cleaning")
                try:
                    room = _clean(request, room)
                finally:
                    room.unlock()

        if room.host_count == 0 and room.allow_host:
            return respond_to(request, {"text/html": "apps/room/launch.html"}, {'room': room})
    else:
        room.save()

    key = None
    if request.user.is_authenticated() and room.sco.acc.cross_domain_sso:
        key = _random_key(20)
        user_principal = api.find_user(request.user.username)
        principal_id = user_principal.get('principal-id')
        with ac_api_client(room.sco.acc) as api:
            api.request("user-update-pwd", {"user-id": principal_id, 'password': key, 'password-verify': key}, True)
            if promote and room.self_cleaning:
                if user_principal:
                    api.request('permissions-update',
                                {'acl-id': room.sco.sco_id, 'principal-id': user_principal.get('principal-id'),
                                 'permission-id': 'host'}, True)

    r = api.request('sco-info', {'sco-id': room.sco.sco_id}, True)
    urlpath = r.et.findtext('.//sco/url-path')
    start_user_counts_poll(room, 10)
    if key:
        try:
            user_client = ACPClient(room.sco.acc.api_url, request.user.username, key, cache=False)
            return user_client.redirect_to(room.sco.acc.url + urlpath)
        except Exception, e:
            pass
コード例 #6
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def clean_old_rooms():
    for acc in ACCluster.objects.all():
        then = datetime.now() - timedelta(days=30)
        then = then.replace(microsecond=0)
        with ac_api_client(acc) as api:
            for room in Room.objects.filter(lastvisited__lt=then):
                logging.debug("room %s was last used %s" % (room.name,humanize.naturalday(room.lastvisited)))
                send_message.apply_async([room.creator,"You have an unused meetingroom at %s" % acc.name ,"Do you still need %s (%s)?" % (room.name,room.permalink())])
コード例 #7
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def occupation(request, rid):
    room = get_object_or_404(Room, pk=rid)
    with ac_api_client(room.sco.acc) as api:
        api.poll_user_counts(room)
        d = {'nusers': room.user_count, 'nhosts': room.host_count}
        return respond_to(request,
                          {'text/html': 'apps/room/fragments/occupation.txt',
                           'application/json': json_response(d, request)},
                          d)
コード例 #8
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def leave_group(group,**kwargs):
    user = kwargs['user']
    acc = acc_for_user(user)
    with ac_api_client(acc) as api:
        principal = api.find_principal("login", user.username, "user")
        if principal:
            gp = api.find_group(group.name)
            if gp:
                api.remove_member(principal.get('principal-id'),gp.get('principal-id'))
コード例 #9
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def import_recent_user_counts():
    for acc in ACCluster.objects.all():
        with ac_api_client(acc) as api:
            nr = 0
            then = datetime.now()-timedelta(seconds=600)
            for room in Room.objects.filter((Q(lastupdated__gt=then) | Q(lastvisited__gt=then)) & Q(sco__acc=acc)):
                api.poll_user_counts(room)
                nr += 1
            logging.info("%s: Checked usage for %d rooms since %s" % (acc,nr,then))
コード例 #10
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def poll_user_counts(room,niter=0):
    logging.debug("polling user_counts for room %s" % room.name)
    with ac_api_client(room.sco.acc) as api:
        (nusers,nhosts) = api.poll_user_counts(room)
        if nusers > 0:
            logging.debug("room occupied by %d users and %d hosts, checking again in 20 ..." % (nusers,nhosts))
            poll_user_counts.apply_async(args=[room],kwargs={'niter': 0},countdown=20)
        elif niter > 0:
            logging.debug("room empty, will check again in 5 for %d more times ..." % (niter -1))
            poll_user_counts.apply_async(args=[room],kwargs={'niter': niter-1},countdown=5)
        return (nusers,nhosts)
コード例 #11
0
ファイル: models.py プロジェクト: SUNET/meetingtools
def get_sco_shortcuts(acc,shortcut_id):
    key = "ac:shortcuts:%s" % acc
    shortcuts = cache.get(key)
    if not shortcuts:
        shortcuts = {}
        with ac_api_client(acc) as api:
            r = api.request('sco-shortcuts')
            for sco_elt in r.et.findall(".//sco"):
                shortcuts[sco_elt.get('type')] = get_sco(acc,sco_elt.get('sco-id'))
        cache.set(key,shortcuts)
    return shortcuts.get(shortcut_id,None)
コード例 #12
0
ファイル: models.py プロジェクト: SUNET/meetingtools
def sco_mkdir(acc,path):
    p = path.split("/")
    p0 = p.pop(0)
    folder_sco = get_sco_shortcuts(acc,p0) #note that first part of path must be the @type of the tree, not the name
    assert folder_sco is not None,ValueError("Unable to find shortcut '%s" % p0)
    folder_sco_id = folder_sco.sco_id
    with ac_api_client(acc) as api:
        for n in p:
            sco_id = _isdir(api,folder_sco_id,n)
            if sco_id is None:
                sco_id = _mkdir(api,folder_sco_id,n)
            folder_sco_id = sco_id
    return get_sco(acc,folder_sco_id)
コード例 #13
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def _user_meeting_folder(request, acc):
    if not session(request, 'my_meetings_sco_id'):
        with ac_api_client(acc) as api:
            userid = request.user.username
            folders = api.request('sco-search-by-field',
                                  {'filter-type': 'folder', 'field': 'name', 'query': userid}).et.xpath(
                '//sco[folder-name="User Meetings"]')
            logging.debug("user meetings folder: " + pformat(folders))
            #folder = next((f for f in folders if f.findtext('.//folder-name') == 'User Meetings'), None)
            if folders and len(folders) > 0:
                session(request, 'my_meetings_sco_id', folders[0].get('sco-id'))

    return session(request, 'my_meetings_sco_id')
コード例 #14
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def import_acc(acc, since=0):
    with ac_api_client(acc) as api:
        if since > 0:
            then = datetime.now()-timedelta(seconds=since)
            then = then.replace(microsecond=0)
            r = api.request('report-bulk-objects',
                            {'filter-out-type': 'meeting', 'filter-gt-date-modified': then.isoformat()})
        else:
            r = api.request('report-bulk-objects', {'filter-out-type': 'meeting'})
        if r:
            nr = 0
            for row in r.et.xpath("//row"):
                Content.create(acc, api, row)
                nr += 1
            logging.info("%s: Imported %d content objects." % (acc, nr))
コード例 #15
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def import_sessions():
    for room in Room.objects.all():
        with ac_api_client(room.sco.acc) as api:
            p = {'sco-id': room.sco.sco_id,'sort-date-created': 'asc'}
            if room.lastvisited is not None:
                last = room.lastvisited
                last.replace(microsecond=0)
                p['filter-gt-date-created'] = last.isoformat()
            r = api.request('report-meeting-sessions',p)
            for row in r.et.xpath("//row"):
                date_created = iso8601.parse_date(row.findtext("date-created"))
                logging.debug("sco_id=%d lastvisited: %s" % (room.sco.sco_id,date_created))
                room.lastvisited = date_created
                room.save()
                break
コード例 #16
0
ファイル: models.py プロジェクト: SUNET/meetingtools
    def info(self,raise_errors=False):
        with ac_api_client(self.acc) as api:
            r = api.request('sco-info',{'sco-id':self.sco_id},raise_errors)
            if r.status_code == 'no-data':
                if raise_errors:
                    raise ValueError("No data about %s" % self)
                else:
                    return None

            d = dict()
            for sco_elt in r.et.findall(".//sco"): #only one but this degrades nicely
                dt = datetime.now() # a fallback just in case
                dt_text = sco_elt.findtext('date-created')
                if dt_text is not None and len(dt_text) > 0:
                    dt = iso8601.parse_date(sco_elt.findtext('date-created'))
                    d['timecreated']=dt
                for a in ('description','name','url-path'):
                    v = sco_elt.findtext(a)
                    if v is not None:
                        d[a] = v
            return d
コード例 #17
0
ファイル: models.py プロジェクト: SUNET/meetingtools
def publish_archive(room,sco_id,tags=None):
    acc = room.sco.acc
    sco = get_sco(acc,sco_id)

    info = sco.info(True)
    dt = info['timecreated']
    folder_sco = sco_mkdir(acc,"content/%d/%s/%s" % (dt.year,dt.month,dt.day))
    with ac_api_client(acc) as api:
        ar,create = Archive.objects.get_or_create(sco=sco,folder_sco=folder_sco,room=room)
        ar.timecreated=info['timecreated']
        if 'description' in info:
            ar.description = info['description']
        if 'name' in info:
            ar.name = info['name']
        ar.save()
        try:
            r = api.request('sco-move',{'sco-id':sco_id,'folder-id':folder_sco.sco_id},True)
            r = api.request('permissions-update',{'acl-id':sco_id,'permission-id': 'view','principal-id':'public-access'})
        except Exception,ex:
            ar.delete()
            raise ex
コード例 #18
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def import_transactions():
    for acc in ACCluster.objects.all():
        then = datetime.now() - timedelta(seconds=1200)
        then = then.replace(microsecond=0)
        with ac_api_client(acc) as api:
            seen = {}
            r = api.request('report-bulk-consolidated-transactions',
                            {'filter-type': 'meeting', 'sort-date-created': 'asc',
                             'filter-gt-date-created': then.isoformat()})
            for row in r.et.xpath("//row"):
                sco_id = row.get('sco-id')
                logging.debug("last session for sco_id=%s" % sco_id)
                if not seen.get(sco_id,False): #pick the first session for each room - ie the one last created
                    seen[sco_id] = True
                    try:
                        room = Room.objects.get(sco__acc=acc,sco__sco_id=sco_id)
                        date_created = iso8601.parse_date(row.findtext("date-created"))
                        room.lastvisited = date_created
                        room.save()
                    except ObjectDoesNotExist:
                        pass # we only care about rooms we know about here
コード例 #19
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def import_acc(acc,since=0):
    with ac_api_client(acc) as api:
        r = None
        if since > 0:
            then = datetime.now()-timedelta(seconds=since)
            then = then.replace(microsecond=0)
            r = api.request('report-bulk-objects',{'filter-type': 'meeting','filter-gt-date-modified': then.isoformat()})
        else:
            r = api.request('report-bulk-objects',{'filter-type': 'meeting'})

        nr = 0
        ne = 0
        for row in r.et.xpath("//row"):
            try:
                _import_one_room(acc,api,row)
                nr += 1
            except Exception,ex:
                logging.error(ex)
                ne += 1

        logging.info("%s: Imported %d rooms and got %d errors" % (acc,nr,ne))
コード例 #20
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def _user_templates(request, acc, folder_sco):
    templates = []
    with ac_api_client(acc) as api:
        if folder_sco:
            my_templates = api.request('sco-contents', {'sco-id': folder_sco.sco_id, 'filter-type': 'folder'}).et.xpath(
                './/sco[folder-name="My Templates"][0]')
            if my_templates and len(my_templates) > 0:
                my_templates_sco_id = my_templates[0].get('sco_id')
                meetings = api.request('sco-contents', {'sco-id': my_templates_sco_id, 'filter-type': 'meeting'})
                if meetings:
                    templates += [(get_sco(acc, r.get('sco-id')), r.findtext('name')) for r in
                                  meetings.et.findall('.//sco')]

        shared_templates_sco = get_sco_shortcuts(acc, 'shared-meeting-templates')
        shared_templates = api.request('sco-contents',
                                       {'sco-id': shared_templates_sco.sco_id, 'filter-type': 'meeting'})
        if shared_templates:
            templates += [(get_sco(acc, r.get('sco-id')).id, r.findtext('name')) for r in
                          shared_templates.et.findall('.//sco')]

    return templates
コード例 #21
0
ファイル: tasks.py プロジェクト: SUNET/meetingtools
def timed_full_import():
    years = [2009, 2010, 2011, 2012, 2013, 2014]
    months = [(1, 3), (4, 7), (8, 10), (9, 12)]
    for acc in ACCluster.objects.all():
        for year in years:
            for month in months:
                begin = datetime(year=year, month=month[0], day=1)
                end = datetime(year=year, month=month[1], day=31)
                with ac_api_client(acc) as api:
                    r = api.request('report-bulk-objects', {'filter-type': 'meeting',
                                                            'filter-gte-date-modified': begin.isoformat(),
                                                            'filter-lte-date-modified': end.isoformat()})
                    nr = 0
                    ne = 0
                    for row in r.et.xpath("//row"):
                        try:
                            _import_one_room(acc, api, row)
                            nr += 1
                        except Exception, ex:
                            logging.error(ex)
                            ne += 1
                    logging.info("%s: Imported %d rooms and got %d errors" % (acc, nr, ne))
コード例 #22
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def room_recordings(request, room):
    acc = room.sco.acc
    with ac_api_client(acc) as api:
        r = api.request('sco-expanded-contents', {'sco-id': room.sco.sco_id, 'filter-icon': 'archive'}, True)
        return [{'published': False,
                 'name': sco_elt.findtext('name'),
                 'sco': get_sco(acc, sco_elt.get('sco-id')),
                 'url': room.sco.acc.make_url(sco_elt.findtext('url-path')),
                 'dl': room.sco.acc.make_dl_url(sco_elt.findtext('url-path')),
                 'description': sco_elt.findtext('description'),
                 'date_created': iso8601.parse_date(sco_elt.findtext('date-created')),
                 'date_modified': iso8601.parse_date(sco_elt.findtext('date-modified'))} for sco_elt in
                r.et.findall(".//sco")] + [
                   {'published': True,
                    'ar': ar,
                    'name': ar.name,
                    'description': ar.description,
                    'sco': ar.sco,
                    'url': room.sco.acc.make_url(ar.urlpath),
                    'dl': room.sco.acc.make_url(ar.urlpath),
                    'date_created': ar.timecreated,
                    'date_modified': ar.lastupdated} for ar in
                   room.archives.all().prefetch_related("creator", "sco", "folder_sco", "source_sco", "deleted_sco")
               ]
コード例 #23
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def delete(request, id):
    room = get_object_or_404(Room, pk=id)
    if request.method == 'POST':
        form = DeleteRoomForm(request.POST)
        if form.is_valid():
            with ac_api_client(room.sco.acc) as api:
                api.request('sco-delete', {'sco-id': room.sco.sco_id}, raise_error=False)
                #clear_acl(room)
                #room.sco.delete()
                #if room.folder_sco is not None:
                #room.folder_sco.delete()
                #if room.deleted_sco is not None:
                #room.deleted_sco.delete()
            room.delete()
            return redirect_to("/rooms")
    else:
        form = DeleteRoomForm()

    return respond_to(request, {'text/html': 'edit.html'},
                      {'form': form,
                       'formtitle': 'Delete %s' % room.name,
                       'cancelurl': '/rooms',
                       'cancelname': 'Cancel',
                       'submitname': 'Delete Room'})
コード例 #24
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def accounts_login_federated(request):
    if request.user.is_authenticated():
        profile, created = UserProfile.objects.get_or_create(user=request.user)
        if created:
            profile.identifier = request.user.username
            profile.user = request.user
            profile.save()        
        
        update = False
        fn = meta1(request,'givenName')
        ln = meta1(request,'sn')
        cn = meta1(request,'cn')
        if not cn:
            cn = meta1(request,'displayName')
        logging.debug("cn=%s" % cn)
        if not cn and fn and ln:
            cn = "%s %s" % (fn,ln)
        if not cn:
            cn = profile.identifier
            
        mail = meta1(request,'mail')
        
        idp = meta1(request,'Shib-Identity-Provider')
        
        for attrib_name, meta_value in (('display_name',cn),('email',mail),('idp',idp)):
            attrib_value = getattr(profile, attrib_name)
            if meta_value and not attrib_value:
                setattr(profile,attrib_name,meta_value)
                update = True
                
        if request.user.password == "":
            request.user.password = "******"
            update = True
            
        if update:
            request.user.save()
        
        # Allow auto_now to kick in for the lastupdated field
        #profile.lastupdated = datetime.datetime.now()    
        profile.save()

        next = request.session.get("after_login_redirect", None)
        if not next and request.GET.has_key('next'):
            next = request.GET['next']
        else:
            next = settings.DEFAULT_URL

        acc = acc_for_user(request.user)
        with ac_api_client(request) as api:
            # make sure the principal is created before shooting off 
            principal = api.find_or_create_principal("login", request.user.username, "user", 
                                                             {'type': "user",
                                                              'has-children': "0",
                                                              'first-name':fn,
                                                              'last-name':ln,
                                                              'email':mail,
                                                              'send-email': 0,
                                                              'login':request.user.username,
                                                              'ext-login':request.user.username})

            #co_import_from_request(request)
            import_user_rooms(acc, api, request.user)
            
            member_or_employee = _is_member_or_employee(request.user)
            for gn in ('live-admins','seminar-admins'):
                group = api.find_builtin(gn)
                if group:
                    api.add_remove_member(principal.get('principal-id'),group.get('principal-id'),member_or_employee)
            
            #(lp,domain) = uid.split('@')
            #for a in ('student','employee','member'):
            #    affiliation = "%s@%s" % (a,domain)
            #    group = connect_api.find_or_create_principal('name',affiliation,'group',{'type': 'group','has-children':'1','name': affiliation})
            #    member = affiliation in affiliations
            #    connect_api.add_remove_member(principal.get('principal-id'),group.get('principal-id'),member)
                
            #for e in epe:
            #    group = connect_api.find_or_create_principal('name',e,'group',{'type': 'group','has-children':'1','name': e})
            #    if group:
            #        connect_api.add_remove_member(principal.get('principal-id'),group.get('principal-id'),True)

            if next is not None:
                return redirect_to(next)
    else:
        pass

    return redirect_to(settings.LOGIN_URL)
コード例 #25
0
ファイル: views.py プロジェクト: SUNET/meetingtools
                room = Room.objects.create(sco=r['sco'],
                                           source_sco=r['source_sco'],
                                           name=r['name'],
                                           urlpath=r['urlpath'],
                                           description=r['description'],
                                           creator=request.user,
                                           folder_sco=r['folder_sco'])
            except Exception, e:
                room = None
                pass

    if not room:
        return None

    logging.debug("+++ looking at user counts")
    with ac_api_client(acc) as api:
        userlist = api.request('meeting-usermanager-user-list', {'sco-id': room.sco.sco_id}, False)
        if userlist.status_code() == 'ok':
            room.user_count = int(userlist.et.xpath("count(.//userdetails)"))
            room.host_count = int(userlist.et.xpath("count(.//userdetails/role[text() = 'host'])"))
            room.save()

    return room


@login_required
def list_rooms(request, username=None):
    user = request.user
    if username:
        try:
            user = User.objects.get(username=username)
コード例 #26
0
ファイル: views.py プロジェクト: SUNET/meetingtools
def _update_room(request, room, data=dict(), acc=None):
    params = {'type': 'meeting'}

    if acc is None:
        acc = acc_for_user(request.user)

    for attr, param in (
        ('sco', 'sco-id'), ('folder_sco', 'folder-id'), ('source_sco', 'source-sco-id'), ('urlpath', 'url-path'),
('name', 'name'), ('description', 'description')):
        v = None
        if hasattr(room, attr):
            v = getattr(room, attr)
        logging.debug("%s,%s = %s" % (attr, param, repr(v)))
        if data.has_key(attr) and data[attr]:
            v = data[attr]

        if v:
            if isinstance(v, (str, unicode)):
                params[param] = v
            elif hasattr(v, 'sco_id'):
                params[param] = v.sco_id # support ACObject instances
            elif hasattr(v, '__getitem__'):
                params[param] = v[0]
            else:
                params[param] = repr(v)

    logging.debug(pformat(params))
    with ac_api_client(acc) as api:
        r = api.request('sco-update', params, True)
        sco_elt = r.et.find(".//sco")
        if sco_elt:
            sco_id = sco_elt.get('sco-id')
            if sco_id:
                data['sco'] = get_sco(acc, sco_id)

            source_sco_id = r.et.find(".//sco").get('sco-source-id')
            if source_sco_id:
                data['source_sco'] = get_sco(acc, source_sco_id)

            room.sco = data['sco']
            room.save()

        sco_id = room.sco.sco_id

        assert (sco_id is not None and sco_id > 0)

        user_principal = api.find_user(room.creator.username)
        #api.request('permissions-reset',{'acl-id': sco_id},True)
        api.request('permissions-update', {'acl-id': sco_id,
                                           'principal-id': user_principal.get('principal-id'),
                                           'permission-id': 'host'}, False)  # owner is always host

        if data.has_key('access'):
            access = data['access']
            if access == 'public':
                allow(room, 'anyone', 'view-hidden')
            elif access == 'private':
                allow(room, 'anyone', 'remove')

        # XXX figure out how to keep the room permissions in sync with the AC permissions
        for ace in acl(room):
            principal_id = None
            if ace.group:
                principal = api.find_group(ace.group.name)
                if principal:
                    principal_id = principal.get('principal-id')
            elif ace.user:
                principal = api.find_user(ace.user.username)
                if principal:
                    principal_id = principal.get('principal-id')
            else:
                principal_id = "public-access"

            if principal_id:
                api.request('permissions-update',
                            {'acl-id': room.sco_id, 'principal-id': principal_id, 'permission-id': ace.permission},
                            False)

        room.deleted_sco = None # if we just cleaned a room we zero out the deleted_sco_id field to indicate the room is now ready for use
        room.save() # a second save here to avoid races
        return room