Example #1
0
 def __init__(self, dispatcher):
     self.infinote_pool = InfinotePool(self)
     self.diff_mp = diff_match_patch()
     self._update_hyki_graph()
     dispatcher.signals.subscribe('ws_disconnect', self.leave_hyki_article)
     dispatcher.signals.subscribe('view_changed', self.leave_hyki_article, filters = [(r'/hyki/(?P<slug>[^/]+)/',True),(r'/hyki/(?P<slug>[^/]+)/edit/$',False)])       
Example #2
0
class WS_Hyki(object):
    '''
    Websocket handler for the hyki. Mainly involves infinote operations.
    '''       
    
    def __init__(self, dispatcher):
        self.infinote_pool = InfinotePool(self)
        self.diff_mp = diff_match_patch()
        self._update_hyki_graph()
        dispatcher.signals.subscribe('ws_disconnect', self.leave_hyki_article)
        dispatcher.signals.subscribe('view_changed', self.leave_hyki_article, filters = [(r'/hyki/(?P<slug>[^/]+)/',True),(r'/hyki/(?P<slug>[^/]+)/edit/$',False)])       
        


    def view_hyki_article(self, client, slug):
        """Infinote client wants to join the subscription log based on the content-id(cid). In this case within the hyki, the
        client requests the cid from the page-slug, subscribes to the channel and starts sending operation requests out, 
        with the cid included. When the client disconnects(other page view or websocket closes), the client gets 
        unsubscribed from the log.

        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :return: dict - wiki article state information or html-layout data response
        """
        try:
            article = HykiArticle.objects.get(slug=slug)
            article_text = self._get_revision_text(article)
            revisions = len(HykiRevision.objects.filter(article = article))
            client.role = 'view'
            subscriber = self.infinote_pool.subscribe(client, slug, article_text, 'hyki', self._signal_presence) 
            main = render_to_string("hyki/read_article.html", {"profile":client.profile,'page':article})
            return {
                'data':{
                    'dom':{'main':main},
                    'page':{
                        'title':article.slug,
                        'state':subscriber['state'],
                        'log':subscriber['log'],
                        'revisions':revisions
                    }, 
                    'online':subscriber['online'],
                    'uid':client.profile.pk
                }
            }
        except ObjectDoesNotExist:
            main = render_to_string("hyki/create_article.html", {"profile":profile,"page":{'slug':slug}})
            return {'data':{'dom':{'main':main},'page':{'title':slug, 'state':False}}}
            

    @WSAuth.is_authenticated
    def edit_hyki_article(self, client, slug):
        """Joins an infinote subscription pool and returns document state information to sync client-side infinote editor with,
        or return a new-article template view
        
        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :return: dict - wiki article state information or html-layout data response
        """
        try:
            article = HykiArticle.objects.get(slug=slug)
            article_text = self._get_revision_text(article)
            revisions = len(HykiRevision.objects.filter(article = article))
            client.role = 'edit'
            subscriber = self.infinote_pool.subscribe(client, slug, article_text, 'hyki', self._signal_presence) 
            form = EditArticleForm(initial={'id':article.pk, 'slug':slug})
            main = render_to_string('hyki/edit_article.html',  {'form': form,"page":article})
            if len(client.transport.view_history) > 1:
                if '/hyki/%s/edit/' % slug not in client.transport.view_history[-2]:
                    publish_activity(client.profile, _('Wiki article editing'),'/hyki/%s/edit/' % slug,[0,2,2,0,0])
            else:
                publish_activity(client.profile, _('Wiki article editing'),'/hyki/%s/edit/' % slug,[0,2,2,0,0])
            return {
                'data':{
                    'dom':{'main':main},
                    'page':{
                        'title':article.slug,
                        'state':subscriber['state'],
                        'log':subscriber['log'],
                        'revisions':revisions
                    }, 
                    'online':subscriber['online'],
                    'uid':client.profile.pk
                }
            }
        except ObjectDoesNotExist:
            main = render_to_string("hyki/create_article.html", {"profile":client.profile,"page":{'slug':slug}})
            return {'data':{'dom':{'main':main},'page':{'title':slug, 'state':False}}}


    def _signal_presence(self, client, online, app_pool, item_id):
        """Callback function when someone subscribes or unsubscribes to the infinote pool"""
        client.remote('/data/%s/%s/online/update/' % (app_pool, item_id),{'online':online})


    def _signal_operation(self, client, app_pool, item_id, operation_type, params):
        """Client callback function for an infinote operation"""
        client.remote('/data/%s/%s/%s/' % (app_pool, item_id, operation_type), params)


    def _signal_caret(self, client, app_pool, item_id, params):
        """Client callback function for a caret operation"""
        client.remote('/data/%s/%s/caret/' % (app_pool, item_id), {'id': params['id'],'cursor':params['cursor']})
        

    def leave_hyki_article(self, client):
        """Remove client page subscription after client left the wiki page or disconnected. This is tracked
        by signal observers defined in the controller constructor.
        
        :param Client client: The requesting client
        """
        self.infinote_pool.unsubscribe(client, 'hyki', self._signal_presence)
                

    @WSAuth.is_authenticated
    def request_hyki_insert(self, client, slug, params):
        """Infinote client insert operation request on the subscription log
        
        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :param dict params: Contains the operation parameters
        :return: dict - status data response
        """
        self.infinote_pool.request_insert(client,'hyki', slug, params, self._signal_operation)
        return {'status':{'code':'OP_OK'}}
            

    @WSAuth.is_authenticated
    def request_hyki_remove(self, client, slug, params):
        """Infinote client delete operation request on the subscription log

        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :param dict params: Contains the operation parameters
        :return: dict - status data response
        """
        self.infinote_pool.request_delete(client,'hyki', slug, params, self._signal_operation)
        return {'status':{'code':'OP_OK'}}
        
                
    @WSAuth.is_authenticated       
    def request_hyki_undo(self, client, slug, params):
        """Infinote client undo operation request on the subscription log

        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :param dict params: Contains the operation parameters
        :return: dict - status data response
        """
        self.infinote_pool.request_undo(client,'hyki', slug, params, self._signal_operation)
        return {'status':{'code':'OP_OK'}}
        
           
    def update_remote_caret(self, client, slug, params):
        """Infinote client caret move broadcast

        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :param dict params: Contains the operation parameters
        """
        self.infinote_pool.update_caret(client,'hyki', slug, params, self._signal_caret)
        
                
    @WSAuth.is_authenticated
    def edit_history(self, client, slug):
        """Render the wiki page history view and return an overview of all page revisions for time-warp slider

        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :return: dict - Revision data and html-layout data response
        """
        article = HykiArticle.objects.get(slug = slug)  
        revisions = HykiRevision.objects.filter(article = article)
        rev_data = []
        for revision in revisions:
            rev_data.append({'submit_comments':revision.submit_comments,'submit_date':str(revision.submit_date),'patch':revision.patch,
            'submit_profile':'%s %s' % (revision.submit_profile.first_name,revision.submit_profile.last_name)})
        rev_data.reverse()
        form = EditArticleHistoryForm(initial={'undo':len(revisions)})
        main = render_to_string("hyki/edit_history.html", {'page':article,'form':form})        
        return {
            'data':{
                'dom':{'main':main}
            },
            'page':{
                'slug':article.slug,
                'revisions':rev_data
            }
        } 
        

    @WSAuth.is_authenticated
    def save_page(self, client, slug, params):
        """Saves an infinote document's state to the database and notifies participants of the action

        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :return: dict - Revision data and html-layout data response
        """
        _content = params['content']
        del params['content']
        form = EditArticleForm(params)
        
        if form.is_valid():
            
            try:
                article = HykiArticle.objects.get(slug = slug)  
            except HykiArticle.DoesNotExist:   
                article = HykiArticle()
                article.slug = slug
                article.save()                
                self._update_hyki_graph()
                publish_activity(client.profile, _('Wiki article created'),'/hyki/%s/' % slug,[0,1,1,0,0])
                for _client in HWIOS.ws_realm.pool.get_clients():
                    #this client is watching our attempt to create a new article. Send it a request to resubscribe to the article                    
                    if client != _client and '/hyki/' in _client.transport.view_history[-1]:
                        _client.remote('/hyki/%s/' % slug,{
                            'status':{
                                'code':'HYKI_PAGE_CREATED',
                                'i18n':_('%(first_name)s %(last_name)s just created article %(slug)s!') % {'first_name':client.profile.first_name,'last_name': client.profile.last_name,'slug':slug},
                                'type': HWIOS.ws_realm._t['notify-info']
                            }
                        })
                return self.edit_hyki_article(client,slug)
            article.last_modified = datetime.now()            
            #construct a revision
            previous_text = self._get_revision_text(article)
            patch = self.diff_mp.patch_make(previous_text, _content)
            #if the patch applied, save a revision
            if len(patch) > 0:
                revision = HykiRevision()
                revision.article = article
                revision.patch = self.diff_mp.patch_toText(patch)
                revision.submit_profile = client.profile
                revision.submit_comments = form.cleaned_data["submit_comments"]
                revision.save()
                revisions = HykiRevision.objects.filter(article = article)
                #evaluate queryset
                revision_count = len(revisions)
                last_revision = revisions[0]
                '''send our latest rev with the notification to other users, just in case we have someone fooling around in history'''
                rev_data = {'submit_comments':last_revision.submit_comments,'submit_date':str(last_revision.submit_date),'patch':last_revision.patch,
                'submit_profile':'%s %s' % (last_revision.submit_profile.first_name,last_revision.submit_profile.last_name)}
                article.content = _content                
                article.save()
                publish_activity(client.profile, _('Wiki article saved'),'/hyki/%s/' % slug,[0,1,1,0,0])
                response = {
                    'status':{
                        'code':'HYKI_EDIT_OK',
                        'i18n':_('Hyki article %(slug)s stored...') % {'slug':slug},
                        'type': HWIOS.ws_realm._t['notify-info'],
                        'state': '/hyki/%s/' % slug,
                    }
                }
                main = render_to_string('hyki/read_article.html',{"profile":client.profile, "page":article})
                response.update({'data':{'dom':{'main':main},'page':{'title':article.slug, 'slug':slug}}})
            else:
                response = {
                    'status':{
                        'code':'HYKI_EDIT_NO_CHANGE',
                        'i18n':_('No changes found in hyki article. Page was not stored...'),
                        'type': HWIOS.ws_realm._t['notify-warning'],
                        'state': '/hyki/%s/' % slug,
                    }
                }
                main = render_to_string('hyki/read_article.html',{"profile":client.profile, "page":article})
                response.update({'data':{'dom':{'main':main},'page':{'title':article.slug, 'slug':slug}}})
                return response
            #Inform all users that are editing this article. Update history views with the latest revision
            for target_client in HWIOS.ws_realm.pool.subscription['hyki'][slug]['clients']: 
                if client != target_client and target_client.role == 'edit':
                    target_client.remote("/data/hyki/%s/saved/" % slug,{
                        'status':{
                            'code':'HYKI_PAGE_SAVED',
                            'type': HWIOS.ws_realm._t['notify-info'],
                            'i18n':_('Hyki article was stored by %(first_name)s %(last_name)s as revision %(revision)s: %(comments)s') % {'first_name':client.profile.first_name, 'last_name':client.profile.last_name,'revision':revision_count,'comments':last_revision.submit_comments}
                        },
                        'data':{'revision':rev_data}
                    })  
            return response
        else:
            try:
                article = HykiArticle.objects.get(slug=slug)
                main = render_to_string('hyki/edit_article.html',  {'form': form,"page":article})
            except HykiArticle.DoesNotExist:
                main = render_to_string("hyki/create_article.html", {"slug":slug,"form":form})
            response = {
                'status':{
                    'code':'FORM_INVALID',
                    'i18n':_('Invalid Form!'),
                    'type': HWIOS.ws_realm._t['notify-warning']
                },
                'data':{'dom':{'main':main}}
            }
            return response  
        
     
    @WSAuth.is_authenticated
    def delete_page(self, client, slug, params = None):
        """Deletes an infinote document from the infinote pool and database, and notifies participants of the action

        :param Client client: The requesting client
        :param str slug: The wiki slug reference
        :return: dict - Status and html-layout data response
        """
        if params == None:
            try:
                article = HykiArticle.objects.get(slug=slug)
            except HykiArticle.DoesNotExist:
                return False
            dialog = render_to_string("hyki/delete_article_confirm.html", {"page":article})
            return {
                'data':{
                    'dom':{'dialog':dialog}
                }
            }
        try:
            article = HykiArticle.objects.get(slug=slug)
        except HykiArticle.DoesNotExist:
            return {
                'status':{
                    'code':'HYKI_INVALID_PAGE',
                    'i18n':_('Invalid article'),
                    'type': HWIOS.ws_realm._t['notify-error']
                }
            }
        main = render_to_string("hyki/create_article.html", {"profile":profile,"page":{'slug':slug}})
        for _client in HWIOS.ws_realm.pool.get_clients():
            if client != _client and '/hyki/' in _client.transport.view_history[-1]:
                _client.remote('/data/hyki/%s/deleted/' % slug,{
                    'status':{
                        'code':'HYKI_PAGE_DELETED',
                        'i18n':_('%(first_name)s %(last_name)s removed the current article...') % {'first_name':client.profile.first_name,'last_name':client.profile.last_name},
                        'type': HWIOS.ws_realm._t['notify-info']
                    },
                    'data':{
                        'dom':{'main':main},
                        'page':{'title':slug, 'content':False,'slug':slug}
                    }
                })              
        del HWIOS.ws_realm.pool.subscription['hyki'][slug]
        article.delete()
        publish_activity(client.profile, _('Wiki article deleted'),'/hyki/%s/' % slug,[0,1,4,0,0])
        self._update_hyki_graph()
        return {
            'status':{
                'code':'HYKI_DELETE_OK',
                'i18n':_('Hyki article deleted successfully...'),
                'type': HWIOS.ws_realm._t['notify-info']
            },
            'data':{
                'dom':{'main':main}
            }
        }        
        
        
    @WSAuth.is_authenticated
    def notify_editors(self, client, slug, revision):
        '''Relies on correct chronical ordering of revisions in the view'''
        article = HykiArticle.objects.get(slug=slug)
        _rev = HykiRevision.objects.filter(article = article)
        _revision = _rev[len(_rev) - revision]
        for _client in HWIOS.ws_realm.pool.subscription['hyki'][slug]['clients']: 
            if _client.profile.uuid != client.profile.uuid:
                _client.remote('/data/hyki/'+slug+'/edit/notify/',{
                    'status':{
                        'code':'HYKI_HISTORY_RESTORE_OK',
                        'i18n':_('Client %(first_name)s %(last_name)s restored article text to revision %(revision)s: \'%(comments)s\'') % {'first_name':client.profile.first_name, 'last_name':client.profile.last_name, 'revision':revision, 'comments':_revision.submit_comments},
                        'type': HWIOS.ws_realm._t['notify-info']
                        }})        
        publish_activity(client.profile, _('Wiki revision restored'),'/hyki/%s/' % slug,[0,1,3,0,0])
        return {}
            
    def _get_revision_text(self, article, revision = None):
        """Retrieve the page's current persistent revision text from the stored patches"""
        revisions = HykiRevision.objects.filter(article = article)
        patches = []
        for revision in revisions:
            patches.append(self.diff_mp.patch_fromText(revision.patch))
        patches.reverse()
        text = ''
        for patch in patches:            
            text = self.diff_mp.patch_apply(patch, text)[0]
        return text          
        
                        
    def _get_editing_clients(self, slug):
        editing_clients = []
        for _client in HWIOS.ws_realm.pool.subscription['hyki'][slug]['clients']:
            if _client.role == 'edit':  
                 editing_clients.append('%s %s' % (_client.profile.first_name, _client.profile.last_name))
        return editing_clients
        
        
    def plasmoid_get_hyki_tree(self, client):
        jit_graph = self.get_hyki_graph()
        response = {'data':{'tree':jit_graph}}
        return response 
        
        
    #write a python-graph of hyki articles(nodes) and their links(edges) to other articles
    def _update_hyki_graph(self):
        import re
        from pygraph.classes.graph import graph
        pattern = re.compile(LINK_RE)
        articles = HykiArticle.objects.all()
        gr = graph()
        if len(articles) > 0:
            for article in articles:
                if article.id not in gr.nodes():
                    self.create_node(gr, article)
                article_text = self._get_revision_text(article)
                results = pattern.findall(article_text)
                for link in results:
                    _source = link[7].split('/')[1]
                    _link = link[7].split('/')[2]
                    if _source == 'hyki':
                        #adjacency article links
                        for _article in articles:
                            #match link with article
                            if _article.slug == _link:
                                #doesnt point to itself
                                if _article != article:
                                    #target node doesn't exist yet
                                    if _article.id not in gr.nodes():
                                        self.create_node(gr, _article)
                                        gr.add_edge((article.id, _article.id))
                                    else:
                                        if not gr.has_edge((_article.id,article.id)):
                                            gr.add_edge((article.id, _article.id))
            self.graph_info = self.format_graph_to_thejit_tree(gr)
        else:
            self.graph_info = False


    def create_node(self, graph, article):
        graph.add_node(article.id)
        graph.add_node_attribute(article.id,{
            'name':article.slug,
            'data':{}
        })


    def format_graph_to_thejit_graph(self,graph):
        thejit_graph = []
        for node in graph.nodes():
            jit_node = graph.node_attr[node][0]
            jit_node['id'] = node
            jit_node['adjacencies'] = graph.node_neighbors[node]
            thejit_graph.append(jit_node)
        return thejit_graph


    def format_graph_to_thejit_tree(self, graph):
        """ If you have a 'disconnected' graph, this will fail """
        thejit_graph = self.make_node(graph, graph.nodes()[0], [])
        return thejit_graph


    def make_node(self, graph, node, all_nodes):
        """ Are there any 'data' attrs inside `graph` we can use? """
        jit_node = {}
        jit_node['id'] = node
        jit_node['name'] = graph.node_attr[node][0]['name']
        jit_children = []
        for child in graph.node_neighbors[node]:
            if child in all_nodes:
                # Avoid infinite recursion!
                continue
            jit_children.append(self.make_node(graph, child, all_nodes + [child]))
        jit_node['children'] = jit_children
        return jit_node


    def get_hyki_graph(self):
        return self.graph_info
Example #3
0
class WS_Plasmoids(object):


    def __init__(self, dispatcher):
        self.infinote_pool = InfinotePool(self)
        dispatcher.signals.subscribe('ws_disconnect', self.disconnect_plasmoid_editor)
        dispatcher.signals.subscribe('view_changed', self.disconnect_plasmoid_editor, filters = [(r'/plasmoids/(?P<plasmoid_uuid>[^/]+)/edit/',True),(r'/plasmoids/(?P<plasmoid_uuid>[^/]+)/edit/',False)])
        

    def list_plasmoids(self, client):
        plasmoids = Plasmoid.objects.all()
        main = render_to_string("plasmoids/read_plasmoids.html", {'plasmoids':plasmoids})
        return {'data':{'dom':{'main':main}}}


    def create_plasmoid(self, client):
        '''
        Creates a new plasmoid
        '''
        form = EditPlasmoidForm()
        plasmoid = Plasmoid()
        plasmoid.uuid = str(uuid.uuid4())
        main = render_to_string("plasmoids/create_plasmoid.html",{'plasmoid':plasmoid,'form': form})
        return {
            'data':{
                'dom':{'main':main},
                'plasmoid':{'uuid':plasmoid.uuid}
            }
        }

        
    def edit_plasmoid(self, client, plasmoid_uuid):
        '''
        Creates a new plasmoid, or subscribes to an existing plasmoid using uuid.
        Includes layout.
        
        '''
        try:
            plasmoid = Plasmoid.objects.get(pk = plasmoid_uuid)
        except ObjectDoesNotExist:
            plasmoid = Plasmoid()
        client.role = 'edit'
        form = EditPlasmoidForm(initial={'slug':plasmoid.slug,'type':plasmoid.type,'target':plasmoid.target,'visible':plasmoid.visible})
        main = render_to_string("plasmoids/edit_plasmoid.html",{'plasmoid':plasmoid, 'form': form})
        subscriber = self.infinote_pool.subscribe(client, plasmoid_uuid, plasmoid.script, 'plasmoids', self.signal_presence)
        publish_activity(client.profile, _('Plasmoid editing'),'/plasmoids/%s/edit/' % plasmoid_uuid,[0,0,4,0,0])
        return {
            'data':{
                'page':subscriber, 
                'uid': client.profile.pk,
                'online':subscriber['online'],
                'dom':{'main':main}
            }
        }
        
        
    def save_plasmoid(self, client, plasmoid_uuid, form):
        _content = form['content']
        del form['content']
        form = EditPlasmoidForm(form)
        if form.is_valid():
            try:
                plasmoid = Plasmoid.objects.get(pk = plasmoid_uuid)
            except Plasmoid.DoesNotExist:
                plasmoid = Plasmoid()
                publish_activity(client.profile, _('Plasmoid created'),'/plasmoids/%s/edit/' % plasmoid_uuid,[0,0,4,0,0])
            plasmoid.slug = form.cleaned_data['slug']
            plasmoid.script = _content
            plasmoid.type = form.cleaned_data['type']
            plasmoid.target = form.cleaned_data['target']
            plasmoid.visible = int(form.cleaned_data['visible'])                
            plasmoid.last_modified = datetime.now()
            plasmoid.save()
            
            client_response, tpl_params = self._get_plasmoids(client)
            #UPDATE ROUTES
            HWIOS.plasmoids.get_routes()
            client_response.update({
                'status':{
                    'code':'PLASMOID_EDIT_OK',
                    'i18n':_('Plasmoid %(slug)s stored...') % {'slug':plasmoid.slug},
                    'type': HWIOS.ws_realm._t['notify-info'],
                }
            })
            notify_others(client, client_response,'/plasmoids/modified/', '^/plasmoids/$', tpl_params)
            publish_activity(client.profile, _('Plasmoid saved'),'/plasmoids/%s/edit/' % plasmoid_uuid,[0,0,4,0,0])
            return client_response
        else:
            try:
                plasmoid = Plasmoid.objects.get(pk = plasmoid_uuid)
            except ObjectDoesNotExist:
                plasmoid = Plasmoid()
            main = render_to_string("plasmoids/edit_plasmoid.html", {'plasmoid':plasmoid, "form":form})
            response = {
                'status':{
                    'code':'FORM_INVALID',
                    'i18n':_('Invalid Form!'),
                    'type': HWIOS.ws_realm._t['notify-warning']
                },
                'data':{'dom':{'main':main}}
            }
            return response
            

    
    def delete_plasmoids(self, client, params = None):
        if params == None:
            dialog = render_to_string("plasmoids/delete_plasmoid_confirmation.html")
            return {'data':{'dom':{'dialog':dialog}}}
        else:
            _count = 0
            regex_modifier = ''
            for slug in params:
                plasmoid = Plasmoid.objects.get(pk=slug)
                plasmoid.delete()
                if regex_modifier != '':
                    regex_modifier = '%s|%s' % (regex_modifier, slug)
                else:
                    regex_modifier = '%s' % slug
                _count +=1
            client_response, tpl_params = self._get_plasmoids(client)
            _target_state = '/plasmoids/'
            client_response.update({
                'status':{
                    'code':'DELETE_OK',
                    'i18n':_('%s plasmoid(s) deleted...' % _count),
                    'type': HWIOS.ws_realm._t['notify-info'],
                    'state': _target_state,
                }
            })
            notify_others(client, client_response,'/plasmoids/modified/', '^/plasmoids/(%s)/edit/$' % regex_modifier, tpl_params, _target_state)
            notify_others(client, client_response,'/plasmoids/modified/', '^/plasmoids/$', tpl_params, _target_state)
            publish_activity(client.profile, _('Plasmoid(s) deleted'),'/plasmoids/',[0,0,4,0,0])
            return client_response
            

    def connect_plasmoid_editor(self, client, plasmoid_uuid):
        '''
        Synchronizes a layer's processing.js information
        '''
        try:
            hdjs_text = HDJS.objects.get(slug=plasmoid_uuid)   
        except ObjectDoesNotExist:
            hdjs_text = ''
        client.role = 'edit'
        subscriber = self.infinote_pool.subscribe(client, plasmoid_uuid, hdjs_text, 'plasmoids', self.signal_presence)    
        return {'data':{'page':subscriber, 'uid': client.profile.pk,'online':subscriber['online']}}
        
        
    def disconnect_plasmoid_editor(self, client, plasmoid_uuid = None):
        self.infinote_pool.unsubscribe(client, 'plasmoids', self.signal_presence)  

                        
    def request_plasmoid_insert(self, client, plasmoid_uuid, params):
        self.infinote_pool.request_insert(client,'plasmoids', plasmoid_uuid, params, self.signal_operation)      
        
        
    def request_plasmoid_delete(self, client, plasmoid_uuid, params):
        self.infinote_pool.request_delete(client,'plasmoids', plasmoid_uuid, params, self.signal_operation)       
        
        
    def request_plasmoid_undo(self, client, plasmoid_uuid, params):
        self.infinote_pool.request_undo(client,'plasmoids', plasmoid_uuid, params, self.signal_operation)
        

    def update_remote_caret(self, client, plasmoid_uuid, params):
        self.infinote_pool.update_caret(client,'plasmoids', plasmoid_uuid, params, self.signal_caret)
        

    def signal_presence(self, client, online, app_pool, item_id):
        client.remote('/data/%s/%s/online/update/' % (app_pool, item_id),{'online':online}) 
        
        
    def signal_operation(self, client, app_pool, item_id, operation_type, params):
        client.remote('/data/%s/%s/%s/' % (app_pool, item_id, operation_type), params)  
        
        
    def signal_caret(self, client, app_pool, item_id, params):
        client.remote('/data/%s/%s/caret/' % (app_pool, item_id), params)    
        
    
    def play_plasmoid_script(self, client, plasmoid_uuid):
        state = self.infinote_pool.get_state('plasmoids',plasmoid_id)
        others = self.infinote_pool.get_other_clients(client,'plasmoids',plasmoid_id)
        return {'data':{'script':state[1]}}
        

    def _get_plasmoids(self, client):
        plasmoids = Plasmoid.objects.all()
        tpl_params = {"plasmoids":plasmoids}
        main = render_to_string("plasmoids/read_plasmoids.html", {'plasmoids':plasmoids})
        return [
            {'data':{'dom':{'main':main}}},
            {'main':{'tpl':'plasmoids/read_plasmoids.html','params':tpl_params}}
        ]
        
Example #4
0
 def __init__(self, dispatcher):
     self.infinote_pool = InfinotePool(self)
     dispatcher.signals.subscribe('ws_disconnect', self.disconnect_plasmoid_editor)
     dispatcher.signals.subscribe('view_changed', self.disconnect_plasmoid_editor, filters = [(r'/plasmoids/(?P<plasmoid_uuid>[^/]+)/edit/',True),(r'/plasmoids/(?P<plasmoid_uuid>[^/]+)/edit/',False)])
Example #5
0
class WS_Pages(object):
    """
    Websocket controller class for the plasmoid module
    """

    def __init__(self, dispatcher):
        self.infinote_pool = InfinotePool(self)
        dispatcher.signals.subscribe('ws_disconnect', self.disconnect_entity_editor)
        dispatcher.signals.subscribe('view_changed', self.disconnect_entity_editor, filters = [(r'/pages/entities/(?P<uuid>[^/]+)/edit/',True),(r'/pages/entities/(?P<uuid>[^/]+)/edit/',False)])
        

    @WSAuth.is_staff
    def list_pages(self, client):
        """Render the view that shows an overview of all plasmoids

        :param Client client: The requesting client 
        :return: dict - Html-layout data response
        """
        anchors = PageAnchor.objects.all()
        entities = PageEntity.objects.all()
        main = render_to_string("pages/list_pages.html", {'anchors':anchors,'entities':entities})
        return {'data':{'dom':{'main':main}}}


    @WSAuth.is_staff
    def create_anchor(self, client, form = None):
        """Render and returns the create plasmoid view

        :param Client client: The requesting client
        :return: dict - Data and html-layout response
        """
        if form == None:
            form = CreateAnchorForm()
            page = PageAnchor()
            main = render_to_string("pages/create_anchor.html",{'page':page,'form': form})
            return {
                'data':{
                    'dom':{'main':main},
                }
            }
        else:
            _uuid = str(uuid.uuid4())
            form = CreateAnchorForm(form)
            response = self._try_save_anchor(client, _uuid, form)
            return response


    @WSAuth.is_staff
    def edit_anchor(self, client, uuid, form = None):
        """
        Edit an existing or a new plasmoid. In both cases, the infinote subscription pool defines the plasmoid view, not the model. This makes it
        possible to edit a new plasmoid, that's not yet in the database.

        :param Client client: The requesting client
        :param str plasmoid_uuid: The uuid of the plasmoid to edit
        :return: dict - Data and html-layout response
        """
        if form == None:
            try:
                page = PageAnchor.objects.get(pk = uuid)
            except ObjectDoesNotExist:
                return {
                    'status':{
                        'code':'PAGE_INVALID',
                        'i18n':_('Invalid page!'),
                        'type': HWIOS.ws_realm._t['notify-error'],
                    }
                }
            client.role = 'edit'
            form = EditAnchorForm(initial={'slug':page.slug,'target':page.target,'access':page.access})
            main = render_to_string("pages/edit_anchor.html",{'page':page, 'form': form})
            #subscriber = self.infinote_pool.subscribe(client, plasmoid_uuid, plasmoid.script, 'plasmoids', self._signal_presence)
            publish_activity(client.profile, _('Page editing'),'/pages/%s/edit/' % uuid,[0,0,4,0,0])
            return {
                'data':{
                    'dom':{'main':main}
                }
            }
        else:
            form = EditAnchorForm(form)
            response = self._try_save_anchor(client, uuid, form)
            return response



    @WSAuth.is_staff
    def delete_anchors(self, client, params = None):
        """
        Delete an existing plasmoid from the database and subscription pool, render/show the general plasmoid overview and notify others.

        :param Client client: The requesting client
        :param str plasmoid_uuid: The uuid of the plasmoid to delete
        :return: dict - Status and html-layout response
        """
        if params == None:
            dialog = render_to_string("pages/delete_anchor_confirmation.html")
            return {'data':{'dom':{'dialog':dialog}}}
        else:
            _count = 0
            regex_modifier = ''
            for slug in params:
                page = PageAnchor.objects.get(pk=slug)
                page.delete()
                if regex_modifier != '':
                    regex_modifier = '%s|%s' % (regex_modifier, slug)
                else:
                    regex_modifier = '%s' % slug
                _count +=1
            HWIOS.anchors.get_routes()
            client_response, tpl_params = self._get_pages(client)
            _target_state = '/pages/'
            client_response.update({
                'status':{
                    'code':'DELETE_OK',
                    'i18n':_('%s page(s) deleted...' % _count),
                    'type': HWIOS.ws_realm._t['notify-info'],
                    'state': _target_state,
                }
            })
            notify_others(client, client_response,'/pages/modified/', '^/pages/(%s)/edit/$' % regex_modifier, tpl_params, _target_state)
            notify_others(client, client_response,'/pages/modified/', '^/pages/$', tpl_params, _target_state)
            publish_activity(client.profile, _('Page(s) deleted'),'/pages/',[0,0,4,0,0])
            return client_response
            
            

    @WSAuth.is_staff
    def _try_save_anchor(self, client, uuid, form):
        """
        Save an existing or a new page, render/show the general plasmoid overview and notify others.

        :param Client client: The requesting client
        :param str plasmoid_uuid: The uuid of the plasmoid to save
        :return: dict - Status and html-layout response
        """
        if form.is_valid():
            try:
                page = PageAnchor.objects.get(pk = uuid)
            except PageAnchor.DoesNotExist:
                page = PageAnchor()
                publish_activity(client.profile, _('Page created'),'/pages/%s/edit/' % uuid,[0,0,4,0,0])
            page.slug = form.cleaned_data['slug']
            page.target = form.cleaned_data['target']
            page.access = int(form.cleaned_data['access'])
            page.last_modified = datetime.now()
            page.save()

            client_response, tpl_params = self._get_pages(client)
            #UPDATE ROUTES
            #HWIOS.pages.get_routes()
            client_response.update({
                'status':{
                    'code':'PAGE_EDIT_OK',
                    'i18n':_('Page %(slug)s stored...') % {'slug':page.slug},
                    'type': HWIOS.ws_realm._t['notify-info'],
                }
            })
            #notify_others(client, client_response,'/plasmoids/modified/', '^/plasmoids/$', tpl_params)
            #publish_activity(client.profile, _('Plasmoid saved'),'/plasmoids/%s/edit/' % plasmoid_uuid,[0,0,4,0,0])
            return client_response
        else:
            try:
                page = PageAnchor.objects.get(pk = uuid)
                main = render_to_string("pages/edit_anchor.html", {'page':page, "form":form})
            #new
            except ObjectDoesNotExist:
                page = PageAnchor()
                main = render_to_string("pages/create_anchor.html", {'form':form})
            response = {
                'status':{
                    'code':'FORM_INVALID',
                    'i18n':_('Invalid Form!'),
                    'type': HWIOS.ws_realm._t['notify-warning']
                },
                'data':{'dom':{'main':main}}
            }
            return response

                

    @WSAuth.is_staff
    def create_entity(self, client, uuid, form = None):
        """Render and returns the create plasmoid view

        :param Client client: The requesting client
        :return: dict - Data and html-layout response
        """
        if form == None:
            form = EntityForm()
            entity = PageEntity()
            entity.uuid = uuid
            subscriber = self.infinote_pool.subscribe(client, uuid, '', 'entities', self._signal_presence)
            main = render_to_string("pages/create_entity.html",{'entity':entity,'form': form})
            return {
                'data':{
                    'ce':subscriber,
                    'uid': client.profile.pk,
                    'dom':{'main':main},
                }
            }
        else:
            print "PROCESS NEW!"
            _content = form['content']
            del form['content']
            form = EntityForm(form)
            form.content = _content
            response = self._try_save_entity(client, uuid, form)
            return response

        

    @WSAuth.is_staff
    def edit_entity(self, client, uuid, form = None):
        """
        Edit an existing or a new plasmoid. In both cases, the infinote subscription pool defines the plasmoid view, not the model. This makes it
        possible to edit a new plasmoid, that's not yet in the database.

        :param Client client: The requesting client
        :param str plasmoid_uuid: The uuid of the plasmoid to edit
        :return: dict - Data and html-layout response
        """
        if form == None:
            try:
                entity = PageEntity.objects.get(pk = uuid)
            except ObjectDoesNotExist:
                entity = PageEntity()
                entity.code = ''
            client.role = 'edit'
            form = EntityForm(initial={'slug':entity.slug,'anchor':entity.anchor,'type':entity.type})
            main = render_to_string("pages/edit_entity.html",{'entity':entity, 'form': form})
            subscriber = self.infinote_pool.subscribe(client, uuid, entity.code, 'entities', self._signal_presence)
            publish_activity(client.profile, _('Entity editing'),'/pages/entities/%s/edit/' % uuid,[0,0,4,0,0])
            return {
                'data':{
                    'ce':subscriber,
                    'uid': client.profile.pk,
                    'dom':{'main':main}
                }
            } 
        else:
            print "PROCESS NEW!"
            _content = form['content']
            del form['content']
            form = EntityForm(form)
            form.content = _content
            response = self._try_save_entity(client, uuid, form)
            return response

        

    @WSAuth.is_staff
    def _try_save_entity(self, client, uuid, form):
        """
        Save an existing or a new plasmoid, render/show the general plasmoid overview and notify others.

        :param Client client: The requesting client
        :param str plasmoid_uuid: The uuid of the plasmoid to save
        :return: dict - Status and html-layout response
        """
        if form.is_valid():
            try:
                entity = PageEntity.objects.get(pk = uuid)
                entity.slug = form.cleaned_data['slug']
                entity.anchor = form.cleaned_data['anchor']
                entity.code = form.content
                entity.type = form.cleaned_data['type']
                entity.last_modified = datetime.now()
                entity.save()
                client_response, tpl_params = self._get_pages(client)
                client_response.update({
                    'status':{
                        'code':'ENTITY_EDIT_OK',
                        'i18n':_('Page entity %(slug)s updated...') % {'slug':entity.slug},
                        'type': HWIOS.ws_realm._t['notify-info'],
                        'state': '/pages/',
                    }
                })
            except PageEntity.DoesNotExist:
                entity = PageEntity()
                publish_activity(client.profile, _('Page entity created'),'/pages/entities/%s/edit/' % uuid,[0,0,4,0,0])
                entity.slug = form.cleaned_data['slug']
                entity.anchor = form.cleaned_data['anchor']
                entity.code = form.content
                entity.type = form.cleaned_data['type']
                entity.last_modified = datetime.now()
                entity.save()
                client_response, tpl_params = self._get_pages(client)
                client_response.update({
                    'status':{
                        'code':'ENTITY_CREATE_OK',
                        'i18n':_('Page entity %(slug)s created...') % {'slug':entity.slug},
                        'type': HWIOS.ws_realm._t['notify-info'],
                        'state': '/pages/',
                    }
                })
            
            #UPDATE ROUTES
            HWIOS.anchors.get_routes()
            notify_others(client, client_response,'/pages/entities/modified/', '^/pages/$', tpl_params)
            publish_activity(client.profile, _('Page entity saved'),'/pages/entities/%s/edit/' % uuid,[0,0,4,0,0])
            return client_response
        else:
            try:
                entity = PageEntity.objects.get(pk = uuid)
                main = render_to_string("pages/edit_entity.html", {'entity':entity, "form":form})
            except ObjectDoesNotExist:
                entity = PageEntity()
                entity.slug = _('New Entity')
                entity.uuid = uuid
                main = render_to_string("pages/create_entity.html", {'entity':entity, "form":form})
            response = {
                'status':{
                    'code':'FORM_INVALID',
                    'i18n':_('Invalid Form!'),
                    'type': HWIOS.ws_realm._t['notify-warning']
                },
                'data':{'dom':{'main':main}}
            }
            return response


    @WSAuth.is_staff
    def delete_entities(self, client, params = None):
        """
        Delete an existing plasmoid from the database and subscription pool, render/show the general plasmoid overview and notify others.

        :param Client client: The requesting client
        :param str plasmoid_uuid: The uuid of the plasmoid to delete
        :return: dict - Status and html-layout response
        """
        if params == None:
            dialog = render_to_string("pages/delete_entity_confirmation.html")
            return {'data':{'dom':{'dialog':dialog}}}
        else:
            _count = 0
            regex_modifier = ''
            for slug in params:
                entity = PageEntity.objects.get(pk=slug)
                entity.delete()
                if regex_modifier != '':
                    regex_modifier = '%s|%s' % (regex_modifier, slug)
                else:
                    regex_modifier = '%s' % slug
                _count +=1
            HWIOS.anchors.get_routes()
            client_response, tpl_params = self._get_pages(client)
            _target_state = '/pages/'
            client_response.update({
                'status':{
                    'code':'DELETE_OK',
                    'i18n':_('%s page(s) deleted...' % _count),
                    'type': HWIOS.ws_realm._t['notify-info'],
                    'state': _target_state,
                }
            })
            notify_others(client, client_response,'/pages/modified/', '^/pages/entities/(%s)/edit/$' % regex_modifier, tpl_params, _target_state)
            notify_others(client, client_response,'/pages/modified/', '^/pages/$', tpl_params, _target_state)
            publish_activity(client.profile, _('Entities deleted'),'/pages/',[0,0,4,0,0])
            return client_response
            


    @WSAuth.is_staff        
    def connect_entity_editor(self, client, uuid):
        """
        Legacy code that's a bit redundant now. Problem was that we didn't want regular users to edit shared js-code. Will be revived
        later to add some interactivity to the pad' presentation functionality

        :param Client client: The requesting client
        :param str plasmoid_uuid: The uuid of the plasmoid to connect to
        :return: dict - Data and html-layout response
        """
        try:
            hdjs_text = HDJS.objects.get(slug=plasmoid_uuid)   
        except ObjectDoesNotExist:
            hdjs_text = ''
        client.role = 'edit'
        subscriber = self.infinote_pool.subscribe(client, plasmoid_uuid, hdjs_text, 'plasmoids', self._signal_presence)
        return {'data':{'page':subscriber, 'uid': client.profile.pk,'online':subscriber['online']}}
        
        
    def disconnect_entity_editor(self, client, uuid = None):
        """Unsubscribe from the infinote plasmoid pool on certain events like disconnect and view_changed
        
        :param Client client: The requesting client
        :param str plasmoid_uuid: The uuid of the plasmoid to disconnect from the infinote pool
        """
        self.infinote_pool.unsubscribe(client, 'entities', self._signal_presence)

                        
    def request_entity_insert(self, client, uuid, params):
        """Insert operation for a specific plasmoid in the infinote pool

        :param Client client: The requesting client
        :param str uuid: The uuid of the plasmoid to operate on in the infinote pool
        :param dict params: The params that are used to succesfully perform the infinote operation
        """
        self.infinote_pool.request_insert(client,'entities', uuid, params, self._signal_operation)
        
        
    def request_entity_delete(self, client, uuid, params):
        """Delete operation for a specific plasmoid in the infinote pool

        :param Client client: The requesting client
        :param str uuid: The uuid of the plasmoid to operate on in the infinote pool
        :param dict params: The params that are used to succesfully perform the infinote operation
        """
        self.infinote_pool.request_delete(client,'entities', uuid, params, self._signal_operation)
        
        
    def request_entity_undo(self, client, uuid, params):
        """Uno operation for a specific plasmoid in the infinote pool

        :param Client client: The requesting client
        :param str uuid: The uuid of the plasmoid to operate on in the infinote pool
        :param dict params: The params that are used to succesfully perform the infinote operation
        """
        self.infinote_pool.request_undo(client,'entities', uuid, params, self._signal_operation)
        

    def update_remote_caret(self, client, uuid, params):
        """Move caret operation for a specific plasmoid in the infinote pool

        :param Client client: The requesting client
        :param str uuid: The uuid of the plasmoid to operate on in the infinote pool
        :param dict params: The params that are used to succesfully perform the infinote operation
        """
        self.infinote_pool.update_caret(client,'entities', uuid, params, self._signal_caret)
        

    def _signal_presence(self, client, online, app_pool, item_id):
        client.remote('/data/pages/%s/%s/online/update/' % (app_pool, item_id),{'online':online}) 
        
        
    def _signal_operation(self, client, app_pool, item_id, operation_type, params):
        client.remote('/data/pages/%s/%s/%s/' % (app_pool, item_id, operation_type), params)  
        
        
    def _signal_caret(self, client, app_pool, item_id, params):
        client.remote('/data/pages/%s/%s/caret/' % (app_pool, item_id), params)
        

    def _get_pages(self, client):
        """Notify_others helper that renders and prepares the show plasmoids view"""
        anchors = PageAnchor.objects.all()
        entities = PageEntity.objects.all()
        main = render_to_string("pages/list_pages.html", {'anchors':anchors,'entities':entities})
        tpl_params = {'anchors':anchors,'entities':entities}
        main = render_to_string("pages/list_pages.html", tpl_params)
        return [
            {'data':{'dom':{'main':main}}},
            {'main':{'tpl':'pages/list_pages.html','params':tpl_params}}
        ]