<commit_before>from datetime import datetime
import time
import string

from django.shortcuts import get_object_or_404
from django.db import transaction
from django.db.models import Q, Count
from django.core.validators import ValidationError
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import IsAuthenticatedOrReadOnly, AllowAny
from rest_framework.decorators import action, list_route, detail_route

import snotes20.serializers as serializers
import snotes20.models as models
import snotes20.editors as editors
import snotes20.contenttypes as contenttypes

def find_doc_name(prefix, sep='-'):
    if not models.Document.objects.filter(name=prefix).exists():
        return prefix

    for letter in range(1, 26):
        name = prefix + sep + letter
        if not models.Document.objects.filter(name=name).exists():
            return name
    raise Exception()

def create_doc_from_episode(request, episode_pk, number):
    if episode_pk is None:
        return Response(status=status.HTTP_400_BAD_REQUEST)

    episode = get_object_or_404(models.Episode, pk=episode_pk)
    episode.number = number

    today = datetime.now()

    doc = models.Document()

    doc.name = episode.podcast.slug + '-'

    if episode.number is not None:
        doc.name += episode.number
    else:
        doc.name += today.strftime('%Y-%m-%d')
        doc.name = find_doc_name(doc.name)

    doc.editor = models.EDITOR_ETHERPAD
    doc.creator = request.user

    meta = models.DocumentMeta()

    with transaction.atomic():
        meta.save()
        doc.meta = meta

        doc.save()

        episode.document = doc
        episode.save()

    return Response({'name': doc.name}, status=status.HTTP_201_CREATED)

def create_doc_from_nolive(request):
    if 'podcast' not in request.DATA or 'number' not in request.DATA:
        return Response(status=status.HTTP_400_BAD_REQUEST)

    number = request.DATA['number']

    podcast = get_object_or_404(models.Podcast, pk=request.DATA['podcast'])

    with transaction.atomic():
        episode = models.Episode(
            podcast=podcast,
            creator=request.user,
            type=models.TYPE_PODCAST
        )

        episode.save()

        return create_doc_from_episode(request, episode.pk, number)

def get_doc_impl(document):
    if document is not None:
        data = serializers.DocumentSerializer(instance=document).data
        return document, Response(data)
    else:
        return None, Response(None, status=status.HTTP_404_NOT_FOUND)

def get_doc_by_episode(request, pk=None):
    number = request.QUERY_PARAMS['number']
    podcast = request.QUERY_PARAMS['podcast']

    episode = get_object_or_404(models.Episode, number=number, podcast__slugs__slug=podcast)

    return get_doc_impl(episode.document)

def get_doc(request, pk=None):
    return get_doc_impl(get_object_or_404(models.Document, pk=pk))

class DocumentViewSet(viewsets.ViewSet):
    permission_classes = (IsAuthenticatedOrReadOnly,)

    def create(self, request):
        type = request.QUERY_PARAMS.get('type')

        if not request.user.is_authenticated():
            raise PermissionDenied()

        if type == 'fromepisode':
            return create_doc_from_episode(request, request.DATA.get('episode', None), request.DATA.get('number', None))
        elif type == 'nonlive':
            return create_doc_from_nolive(request)
        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)

    def retrieve(self, request, pk=None):
        type = request.QUERY_PARAMS.get('type')
        mode = 'view'
        if 'edit' in request.QUERY_PARAMS and request.QUERY_PARAMS['edit'] == 'true':
            mode = 'edit'

        if type == 'byepisode':
            doc, resp = get_doc_by_episode(request)
        else:
            doc, resp = get_doc(request, pk)

        if mode == 'edit' and not request.user.has_perm('o_edit_document', doc):
            raise PermissionDenied()
        elif mode == 'view' and not request.user.has_perm('o_view_document', doc):
            raise PermissionDenied()

        if resp.status_code == 200 and request.user.is_authenticated() and 'edit' in request.QUERY_PARAMS:
            editor = editors.EditorFactory.get_editor(doc.editor)
            session_id = editor.generate_session(doc, request.user)
            resp.set_cookie('sessionID', session_id)

            doc.edit_date = datetime.now()

        doc.access_date = datetime.now()
        doc.save(update_fields=['edit_date', 'access_date'])

        return resp

    @action(methods=['POST', 'DELETE'])
    def contributed(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)
        exists = document.meta.shownoters.filter(user=request.user).exists()

        if request.method == 'POST' and not exists:
            shownoter = models.Shownoter(user=request.user)
            shownoter.save()
            document.meta.shownoters.add(shownoter)
        elif request.method == 'DELETE' and exists:
            document.meta.shownoters.filter(user=request.user).delete()

        return Response(status=status.HTTP_202_ACCEPTED)

    @action(methods=['POST'])
    def number(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_404_NOT_FOUND)

        episode = document.episode

        if episode.number is not None:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        try:
            episode.number = int(request.DATA['number'])
            episode.full_clean()
        except:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        episode.save()

        return Response(status=status.HTTP_202_ACCEPTED)

    @list_route()
    def todo(self, request):
        if not request.user.has_perm('snotes20.publish_episode') and not models.Podcast.objects.filter(mums=request.user).exists():
            raise PermissionDenied()

        qry = models.Document.objects.filter(episode__isnull=False)\
                                     .annotate(Count('episode__publications'))\
                                     .annotate(Count('episode__publicationrequests'))\
                                     .filter(Q(episode__publications__count=0) |
                                             Q(episode__publicationrequests__count__gt=0))

        if 'search' in request.QUERY_PARAMS:
            key = request.QUERY_PARAMS['search']
            qry = qry.filter(episode__podcast__title__icontains=key)

        return Response({
            'data': serializers.DocumentSerializer(qry[:15], many=True).data,
            'count': qry.count()
        })

    @action(methods=['POST', 'DELETE'])
    def shownoters(self, request, pk=None):
        if 'name' not in request.DATA and 'id' not in request.DATA:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_404_NOT_FOUND)

        episode = document.episode

        if not request.user.has_perm('o_publish_episode', episode):
            raise PermissionDenied()

        user = None

        try:
            if 'name' in request.DATA:
                user = models.NUser.objects.get(username=request.DATA['name'])
            else:
                user = models.NUser.objects.get(id=request.DATA['id'])
        except models.NUser.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)

        if request.method == 'DELETE':
            document.meta.shownoters.filter(user=user).delete()
        elif request.method == 'POST':
            shownoter = models.Shownoter(user=user)
            shownoter.save()
            document.meta.shownoters.add(shownoter)

        return Response(status=status.HTTP_202_ACCEPTED)

    @action(methods=['POST', 'DELETE'])
    def podcasters(self, request, pk=None):
        if 'name' not in request.DATA:
            raise PermissionDenied()

        name = request.DATA['name']
        document = get_object_or_404(models.Document, pk=pk)
        exists = any(rpodcaster.name == name for rpodcaster in document.meta.podcasters.all())

        if request.method == 'POST' and not exists:
            rpodcaster = models.RawPodcaster(name=name, meta=document.meta)
            try:
                rpodcaster.clean_fields()
            except ValidationError:
                return Response(status=status.HTTP_400_BAD_REQUEST)
            document.meta.podcasters.add(rpodcaster)
        elif request.method == 'DELETE' and exists:
            document.meta.podcasters.get(name=name).delete()

        return Response(status=status.HTTP_202_ACCEPTED)

    @action(methods=['POST', 'GET'])
    def chat(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if request.method == 'POST':
            if 'message' not in request.DATA:
                raise PermissionDenied()

            issuer = models.ChatMessageIssuer()
            issuer.type = models.CHAT_MSG_ISSUER_USER
            issuer.user = request.user
            issuer.save()

            msg = models.ChatMessage()
            msg.message = request.DATA['message']
            msg.document = document
            msg.issuer = issuer
            msg.order = int(round(time.time() * 1000))
            msg.save()

            return Response(status=status.HTTP_202_ACCEPTED)
        elif request.method == 'GET':
            msgs = document.messages.all()

            try:
                if 'since' in request.QUERY_PARAMS:
                    msgs = msgs.filter(order__gt=int(request.QUERY_PARAMS['since']))
            except:
                return Response([], status=status.HTTP_200_OK)

            msgs = msgs.order_by('order')

            data = serializers.ChatMessageSerializer(msgs, many=True).data
            return Response(data, status=status.HTTP_200_OK)

    @detail_route(methods=['POST'], permission_classes=(AllowAny,))
    def text(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        type = 'osf'

        source = document

        if 'type' in request.QUERY_PARAMS:
            type = request.QUERY_PARAMS['type']

        if 'pub' in request.QUERY_PARAMS:
            pub = request.QUERY_PARAMS['pub']

            if pub == 'newest':
                source = document.episode.publications.order_by('create_date')[:1][0]
            else:
                try:
                    num = int(pub)
                    source = document.episode.publications.order_by('create_date')[num:num + 1][0]
                except IndexError:
                    return Response(status=status.HTTP_404_NOT_FOUND)
                except ValueError:
                    return Response(status=status.HTTP_400_BAD_REQUEST)

        if type == 'json':
            try:
                data = source.state.osfdocumentstate.to_dict()
            except models.OSFDocumentState.DoesNotExist:
                return Response(status=status.HTTP_503_SERVICE_UNAVAILABLE)
        elif type == 'raw':
            data  = source.raw_state.text
        elif type == 'osf':
            data = None
        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        response = Response({'data': data}, status=status.HTTP_200_OK)
        return response

    @action(methods=['GET'])
    def errors(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)
        errors = document.state.errors.all()
        data = serializers.DocumentStateErrorSerializer(errors, many=True).data
        return Response(data, status=status.HTTP_200_OK)

    @action(methods=['POST', 'GET'])
    def publications(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_400_BAD_REQUEST)

        episode = document.episode

        if request.method == 'POST':
            if not request.user.is_authenticated() or not request.user.has_perm('o_publish_episode', episode):
                return Response(status=status.HTTP_403_FORBIDDEN)

            podcasters = request.DATA['podcasters']
            request.DATA['podcasters'] = []

            cover = None
            if 'cover' in request.DATA:
                cover = request.DATA['cover']
                del request.DATA['cover']

            request.DATA['create_date'] = datetime.now()

            serialized = serializers.PublicationSerializer(data=request.DATA)

            if not serialized.is_valid():
                return Response(serialized.errors, status=status.HTTP_400_BAD_REQUEST)

            pub = serialized.object

            prepped = contenttypes.prep_state(document)

            with transaction.atomic():
                raw_state, state = contenttypes.get_state(prepped)
                state.save()

                if cover is not None:
                    if cover['id'] == 'new':
                        cover = models.Cover.from_url(request.user, cover['file'])
                    else:
                        cover = models.Cover.objects.get(pk=cover['id'])

                    episode.cover = cover
                    episode.save()

                pub.creator = request.user
                pub.state = state
                pub.raw_state = raw_state
                pub.episode = episode

                pub.save()

                pub.shownoters.add(*document.meta.shownoters.all())

                episode.publicationrequests.all().delete()

            return Response(status=status.HTTP_201_CREATED)
        elif request.method == 'GET':
            return Response(episode.publications)

    @action(methods=['POST'])
    def publicationrequests(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_400_BAD_REQUEST)

        episode = document.episode

        if episode.publicationrequests.count() != 0:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        comment = ""

        if 'comment' in request.DATA:
            comment = request.DATA['comment']

        request = models.PublicationRequest(episode=episode,
                                            comment=comment,
                                            requester=request.user,
                                            create_date=datetime.now())
        try:
            request.clean_fields()
        except ValidationError:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        request.save()

        return Response(status=status.HTTP_201_CREATED)

    @action(methods=['GET'])
    def canpublish(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_400_BAD_REQUEST)

        episode = document.episode

        if not request.user.is_authenticated() or not request.user.has_perm('o_publish_episode', episode):
            return Response(status=status.HTTP_403_FORBIDDEN)

        return Response(status=status.HTTP_200_OK)

    #def list(self, request):
    #    pass

    #def update(self, request, pk=None):
    #    pass

    #def partial_update(self, request, pk=None):
    #    pass

    #def destroy(self, request, pk=None):
    #    pass<commit_msg>text api allow GET<commit_after>from datetime import datetime
import time
import string

from django.shortcuts import get_object_or_404
from django.db import transaction
from django.db.models import Q, Count
from django.core.validators import ValidationError
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import IsAuthenticatedOrReadOnly, AllowAny
from rest_framework.decorators import action, list_route, detail_route

import snotes20.serializers as serializers
import snotes20.models as models
import snotes20.editors as editors
import snotes20.contenttypes as contenttypes

def find_doc_name(prefix, sep='-'):
    if not models.Document.objects.filter(name=prefix).exists():
        return prefix

    for letter in range(1, 26):
        name = prefix + sep + letter
        if not models.Document.objects.filter(name=name).exists():
            return name
    raise Exception()

def create_doc_from_episode(request, episode_pk, number):
    if episode_pk is None:
        return Response(status=status.HTTP_400_BAD_REQUEST)

    episode = get_object_or_404(models.Episode, pk=episode_pk)
    episode.number = number

    today = datetime.now()

    doc = models.Document()

    doc.name = episode.podcast.slug + '-'

    if episode.number is not None:
        doc.name += episode.number
    else:
        doc.name += today.strftime('%Y-%m-%d')
        doc.name = find_doc_name(doc.name)

    doc.editor = models.EDITOR_ETHERPAD
    doc.creator = request.user

    meta = models.DocumentMeta()

    with transaction.atomic():
        meta.save()
        doc.meta = meta

        doc.save()

        episode.document = doc
        episode.save()

    return Response({'name': doc.name}, status=status.HTTP_201_CREATED)

def create_doc_from_nolive(request):
    if 'podcast' not in request.DATA or 'number' not in request.DATA:
        return Response(status=status.HTTP_400_BAD_REQUEST)

    number = request.DATA['number']

    podcast = get_object_or_404(models.Podcast, pk=request.DATA['podcast'])

    with transaction.atomic():
        episode = models.Episode(
            podcast=podcast,
            creator=request.user,
            type=models.TYPE_PODCAST
        )

        episode.save()

        return create_doc_from_episode(request, episode.pk, number)

def get_doc_impl(document):
    if document is not None:
        data = serializers.DocumentSerializer(instance=document).data
        return document, Response(data)
    else:
        return None, Response(None, status=status.HTTP_404_NOT_FOUND)

def get_doc_by_episode(request, pk=None):
    number = request.QUERY_PARAMS['number']
    podcast = request.QUERY_PARAMS['podcast']

    episode = get_object_or_404(models.Episode, number=number, podcast__slugs__slug=podcast)

    return get_doc_impl(episode.document)

def get_doc(request, pk=None):
    return get_doc_impl(get_object_or_404(models.Document, pk=pk))

class DocumentViewSet(viewsets.ViewSet):
    permission_classes = (IsAuthenticatedOrReadOnly,)

    def create(self, request):
        type = request.QUERY_PARAMS.get('type')

        if not request.user.is_authenticated():
            raise PermissionDenied()

        if type == 'fromepisode':
            return create_doc_from_episode(request, request.DATA.get('episode', None), request.DATA.get('number', None))
        elif type == 'nonlive':
            return create_doc_from_nolive(request)
        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)

    def retrieve(self, request, pk=None):
        type = request.QUERY_PARAMS.get('type')
        mode = 'view'
        if 'edit' in request.QUERY_PARAMS and request.QUERY_PARAMS['edit'] == 'true':
            mode = 'edit'

        if type == 'byepisode':
            doc, resp = get_doc_by_episode(request)
        else:
            doc, resp = get_doc(request, pk)

        if mode == 'edit' and not request.user.has_perm('o_edit_document', doc):
            raise PermissionDenied()
        elif mode == 'view' and not request.user.has_perm('o_view_document', doc):
            raise PermissionDenied()

        if resp.status_code == 200 and request.user.is_authenticated() and 'edit' in request.QUERY_PARAMS:
            editor = editors.EditorFactory.get_editor(doc.editor)
            session_id = editor.generate_session(doc, request.user)
            resp.set_cookie('sessionID', session_id)

            doc.edit_date = datetime.now()

        doc.access_date = datetime.now()
        doc.save(update_fields=['edit_date', 'access_date'])

        return resp

    @action(methods=['POST', 'DELETE'])
    def contributed(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)
        exists = document.meta.shownoters.filter(user=request.user).exists()

        if request.method == 'POST' and not exists:
            shownoter = models.Shownoter(user=request.user)
            shownoter.save()
            document.meta.shownoters.add(shownoter)
        elif request.method == 'DELETE' and exists:
            document.meta.shownoters.filter(user=request.user).delete()

        return Response(status=status.HTTP_202_ACCEPTED)

    @action(methods=['POST'])
    def number(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_404_NOT_FOUND)

        episode = document.episode

        if episode.number is not None:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        try:
            episode.number = int(request.DATA['number'])
            episode.full_clean()
        except:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        episode.save()

        return Response(status=status.HTTP_202_ACCEPTED)

    @list_route()
    def todo(self, request):
        if not request.user.has_perm('snotes20.publish_episode') and not models.Podcast.objects.filter(mums=request.user).exists():
            raise PermissionDenied()

        qry = models.Document.objects.filter(episode__isnull=False)\
                                     .annotate(Count('episode__publications'))\
                                     .annotate(Count('episode__publicationrequests'))\
                                     .filter(Q(episode__publications__count=0) |
                                             Q(episode__publicationrequests__count__gt=0))

        if 'search' in request.QUERY_PARAMS:
            key = request.QUERY_PARAMS['search']
            qry = qry.filter(episode__podcast__title__icontains=key)

        return Response({
            'data': serializers.DocumentSerializer(qry[:15], many=True).data,
            'count': qry.count()
        })

    @action(methods=['POST', 'DELETE'])
    def shownoters(self, request, pk=None):
        if 'name' not in request.DATA and 'id' not in request.DATA:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_404_NOT_FOUND)

        episode = document.episode

        if not request.user.has_perm('o_publish_episode', episode):
            raise PermissionDenied()

        user = None

        try:
            if 'name' in request.DATA:
                user = models.NUser.objects.get(username=request.DATA['name'])
            else:
                user = models.NUser.objects.get(id=request.DATA['id'])
        except models.NUser.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)

        if request.method == 'DELETE':
            document.meta.shownoters.filter(user=user).delete()
        elif request.method == 'POST':
            shownoter = models.Shownoter(user=user)
            shownoter.save()
            document.meta.shownoters.add(shownoter)

        return Response(status=status.HTTP_202_ACCEPTED)

    @action(methods=['POST', 'DELETE'])
    def podcasters(self, request, pk=None):
        if 'name' not in request.DATA:
            raise PermissionDenied()

        name = request.DATA['name']
        document = get_object_or_404(models.Document, pk=pk)
        exists = any(rpodcaster.name == name for rpodcaster in document.meta.podcasters.all())

        if request.method == 'POST' and not exists:
            rpodcaster = models.RawPodcaster(name=name, meta=document.meta)
            try:
                rpodcaster.clean_fields()
            except ValidationError:
                return Response(status=status.HTTP_400_BAD_REQUEST)
            document.meta.podcasters.add(rpodcaster)
        elif request.method == 'DELETE' and exists:
            document.meta.podcasters.get(name=name).delete()

        return Response(status=status.HTTP_202_ACCEPTED)

    @action(methods=['POST', 'GET'])
    def chat(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if request.method == 'POST':
            if 'message' not in request.DATA:
                raise PermissionDenied()

            issuer = models.ChatMessageIssuer()
            issuer.type = models.CHAT_MSG_ISSUER_USER
            issuer.user = request.user
            issuer.save()

            msg = models.ChatMessage()
            msg.message = request.DATA['message']
            msg.document = document
            msg.issuer = issuer
            msg.order = int(round(time.time() * 1000))
            msg.save()

            return Response(status=status.HTTP_202_ACCEPTED)
        elif request.method == 'GET':
            msgs = document.messages.all()

            try:
                if 'since' in request.QUERY_PARAMS:
                    msgs = msgs.filter(order__gt=int(request.QUERY_PARAMS['since']))
            except:
                return Response([], status=status.HTTP_200_OK)

            msgs = msgs.order_by('order')

            data = serializers.ChatMessageSerializer(msgs, many=True).data
            return Response(data, status=status.HTTP_200_OK)

    @detail_route(methods=['POST', 'GET'], permission_classes=(AllowAny,))
    def text(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        type = 'osf'

        source = document

        if 'type' in request.QUERY_PARAMS:
            type = request.QUERY_PARAMS['type']

        if 'pub' in request.QUERY_PARAMS:
            pub = request.QUERY_PARAMS['pub']

            if pub == 'newest':
                source = document.episode.publications.order_by('create_date')[:1][0]
            else:
                try:
                    num = int(pub)
                    source = document.episode.publications.order_by('create_date')[num:num + 1][0]
                except IndexError:
                    return Response(status=status.HTTP_404_NOT_FOUND)
                except ValueError:
                    return Response(status=status.HTTP_400_BAD_REQUEST)

        if type == 'json':
            try:
                data = source.state.osfdocumentstate.to_dict()
            except models.OSFDocumentState.DoesNotExist:
                return Response(status=status.HTTP_503_SERVICE_UNAVAILABLE)
        elif type == 'raw':
            data  = source.raw_state.text
        elif type == 'osf':
            data = None
        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        response = Response({'data': data}, status=status.HTTP_200_OK)
        return response

    @action(methods=['GET'])
    def errors(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)
        errors = document.state.errors.all()
        data = serializers.DocumentStateErrorSerializer(errors, many=True).data
        return Response(data, status=status.HTTP_200_OK)

    @action(methods=['POST', 'GET'])
    def publications(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_400_BAD_REQUEST)

        episode = document.episode

        if request.method == 'POST':
            if not request.user.is_authenticated() or not request.user.has_perm('o_publish_episode', episode):
                return Response(status=status.HTTP_403_FORBIDDEN)

            podcasters = request.DATA['podcasters']
            request.DATA['podcasters'] = []

            cover = None
            if 'cover' in request.DATA:
                cover = request.DATA['cover']
                del request.DATA['cover']

            request.DATA['create_date'] = datetime.now()

            serialized = serializers.PublicationSerializer(data=request.DATA)

            if not serialized.is_valid():
                return Response(serialized.errors, status=status.HTTP_400_BAD_REQUEST)

            pub = serialized.object

            prepped = contenttypes.prep_state(document)

            with transaction.atomic():
                raw_state, state = contenttypes.get_state(prepped)
                state.save()

                if cover is not None:
                    if cover['id'] == 'new':
                        cover = models.Cover.from_url(request.user, cover['file'])
                    else:
                        cover = models.Cover.objects.get(pk=cover['id'])

                    episode.cover = cover
                    episode.save()

                pub.creator = request.user
                pub.state = state
                pub.raw_state = raw_state
                pub.episode = episode

                pub.save()

                pub.shownoters.add(*document.meta.shownoters.all())

                episode.publicationrequests.all().delete()

            return Response(status=status.HTTP_201_CREATED)
        elif request.method == 'GET':
            return Response(episode.publications)

    @action(methods=['POST'])
    def publicationrequests(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_400_BAD_REQUEST)

        episode = document.episode

        if episode.publicationrequests.count() != 0:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        comment = ""

        if 'comment' in request.DATA:
            comment = request.DATA['comment']

        request = models.PublicationRequest(episode=episode,
                                            comment=comment,
                                            requester=request.user,
                                            create_date=datetime.now())
        try:
            request.clean_fields()
        except ValidationError:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        request.save()

        return Response(status=status.HTTP_201_CREATED)

    @action(methods=['GET'])
    def canpublish(self, request, pk=None):
        document = get_object_or_404(models.Document, pk=pk)

        if not hasattr(document, 'episode'):
            return Response(status=status.HTTP_400_BAD_REQUEST)

        episode = document.episode

        if not request.user.is_authenticated() or not request.user.has_perm('o_publish_episode', episode):
            return Response(status=status.HTTP_403_FORBIDDEN)

        return Response(status=status.HTTP_200_OK)

    #def list(self, request):
    #    pass

    #def update(self, request, pk=None):
    #    pass

    #def partial_update(self, request, pk=None):
    #    pass

    #def destroy(self, request, pk=None):
    #    pass<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# Copyright 2012-2015 Spotify AB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
This module provides a class :class:`MockTarget`, an implementation of :py:class:`~luigi.target.Target`.
:class:`MockTarget` contains all data in-memory.
The main purpose is unit testing workflows without writing to disk.
"""

import multiprocessing
from io import BytesIO

import sys
import warnings

from luigi import six
from luigi import target
from luigi.format import get_default_format, MixedUnicodeBytes

class MockFileSystem(target.FileSystem):
    """
    MockFileSystem inspects/modifies _data to simulate file system operations.
    """
    _data = None

    def get_all_data(self):
        # This starts a server in the background, so we don't want to do it in the global scope
        if MockFileSystem._data is None:
            MockFileSystem._data = multiprocessing.Manager().dict()
        return MockFileSystem._data

    def get_data(self, fn):
        return self.get_all_data()[fn]

    def exists(self, path):
        return MockTarget(path).exists()

    def remove(self, path, recursive=True, skip_trash=True):
        """
        Removes the given mockfile. skip_trash doesn't have any meaning.
        """
        if recursive:
            to_delete = []
            for s in self.get_all_data().keys():
                if s.startswith(path):
                    to_delete.append(s)
            for s in to_delete:
                self.get_all_data().pop(s)
        else:
            self.get_all_data().pop(path)

    def listdir(self, path):
        """
        listdir does a prefix match of self.get_all_data(), but doesn't yet support globs.
        """
        return [s for s in self.get_all_data().keys()
                if s.startswith(path)]

    def isdir(self, path):
        return any(self.listdir(path))

    def mkdir(self, path, parents=True, raise_if_exists=False):
        """
        mkdir is a noop.
        """
        pass

    def clear(self):
        self.get_all_data().clear()

class MockTarget(target.FileSystemTarget):
    fs = MockFileSystem()

    def __init__(self, fn, is_tmp=None, mirror_on_stderr=False, format=None):
        self._mirror_on_stderr = mirror_on_stderr
        self._fn = fn
        if format is None:
            format = get_default_format()

        # Allow to write unicode in file for retrocompatibility
        if six.PY2:
            format = format >> MixedUnicodeBytes

        self.format = format

    def exists(self,):
        return self._fn in self.fs.get_all_data()

    def rename(self, path, raise_if_exists=False):
        if raise_if_exists and path in self.fs.get_all_data():
            raise RuntimeError('Destination exists: %s' % path)
        contents = self.fs.get_all_data().pop(self._fn)
        self.fs.get_all_data()[path] = contents

    @property
    def path(self):
        return self._fn

    def open(self, mode):
        fn = self._fn
        mock_target = self

        class Buffer(BytesIO):
            # Just to be able to do writing + reading from the same buffer

            _write_line = True

            def set_wrapper(self, wrapper):
                self.wrapper = wrapper

            def write(self, data):
                if six.PY3:
                    stderrbytes = sys.stderr.buffer
                else:
                    stderrbytes = sys.stderr

                if mock_target._mirror_on_stderr:
                    if self._write_line:
                        sys.stderr.write(fn + ": ")
                    stderrbytes.write(data)
                    if (data[-1]) == '\n':
                        self._write_line = True
                    else:
                        self._write_line = False
                super(Buffer, self).write(data)

            def close(self):
                if mode == 'w':
                    try:
                        mock_target.wrapper.flush()
                    except AttributeError:
                        pass
                    mock_target.fs.get_all_data()[fn] = self.getvalue()
                super(Buffer, self).close()

            def __exit__(self, exc_type, exc_val, exc_tb):
                if not exc_type:
                    self.close()

            def __enter__(self):
                return self

            def readable(self):
                return mode == 'r'

            def writeable(self):
                return mode == 'w'

            def seekable(self):
                return False

        if mode == 'w':
            wrapper = self.format.pipe_writer(Buffer())
            wrapper.set_wrapper(wrapper)
            return wrapper
        else:
            return self.format.pipe_reader(Buffer(self.fs.get_all_data()[fn]))

class MockFile(MockTarget):
    def __init__(self, *args, **kwargs):
        warnings.warn("MockFile has been renamed MockTarget", DeprecationWarning, stacklevel=2)
        super(MockFile, self).__init__(*args, **kwargs)
<commit_msg>Fix running nose with xunit. (#1659)<commit_after># -*- coding: utf-8 -*-
#
# Copyright 2012-2015 Spotify AB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
This module provides a class :class:`MockTarget`, an implementation of :py:class:`~luigi.target.Target`.
:class:`MockTarget` contains all data in-memory.
The main purpose is unit testing workflows without writing to disk.
"""

import multiprocessing
from io import BytesIO

import sys
import warnings

from luigi import six
from luigi import target
from luigi.format import get_default_format, MixedUnicodeBytes

class MockFileSystem(target.FileSystem):
    """
    MockFileSystem inspects/modifies _data to simulate file system operations.
    """
    _data = None

    def get_all_data(self):
        # This starts a server in the background, so we don't want to do it in the global scope
        if MockFileSystem._data is None:
            MockFileSystem._data = multiprocessing.Manager().dict()
        return MockFileSystem._data

    def get_data(self, fn):
        return self.get_all_data()[fn]

    def exists(self, path):
        return MockTarget(path).exists()

    def remove(self, path, recursive=True, skip_trash=True):
        """
        Removes the given mockfile. skip_trash doesn't have any meaning.
        """
        if recursive:
            to_delete = []
            for s in self.get_all_data().keys():
                if s.startswith(path):
                    to_delete.append(s)
            for s in to_delete:
                self.get_all_data().pop(s)
        else:
            self.get_all_data().pop(path)

    def listdir(self, path):
        """
        listdir does a prefix match of self.get_all_data(), but doesn't yet support globs.
        """
        return [s for s in self.get_all_data().keys()
                if s.startswith(path)]

    def isdir(self, path):
        return any(self.listdir(path))

    def mkdir(self, path, parents=True, raise_if_exists=False):
        """
        mkdir is a noop.
        """
        pass

    def clear(self):
        self.get_all_data().clear()

class MockTarget(target.FileSystemTarget):
    fs = MockFileSystem()

    def __init__(self, fn, is_tmp=None, mirror_on_stderr=False, format=None):
        self._mirror_on_stderr = mirror_on_stderr
        self._fn = fn
        if format is None:
            format = get_default_format()

        # Allow to write unicode in file for retrocompatibility
        if six.PY2:
            format = format >> MixedUnicodeBytes

        self.format = format

    def exists(self,):
        return self._fn in self.fs.get_all_data()

    def rename(self, path, raise_if_exists=False):
        if raise_if_exists and path in self.fs.get_all_data():
            raise RuntimeError('Destination exists: %s' % path)
        contents = self.fs.get_all_data().pop(self._fn)
        self.fs.get_all_data()[path] = contents

    @property
    def path(self):
        return self._fn

    def open(self, mode):
        fn = self._fn
        mock_target = self

        class Buffer(BytesIO):
            # Just to be able to do writing + reading from the same buffer

            _write_line = True

            def set_wrapper(self, wrapper):
                self.wrapper = wrapper

            def write(self, data):
                if mock_target._mirror_on_stderr:
                    if self._write_line:
                        sys.stderr.write(fn + ": ")
                    if six.binary_type:
                        sys.stderr.write(data.decode('utf8'))
                    else:
                        sys.stderr.write(data)
                    if (data[-1]) == '\n':
                        self._write_line = True
                    else:
                        self._write_line = False
                super(Buffer, self).write(data)

            def close(self):
                if mode == 'w':
                    try:
                        mock_target.wrapper.flush()
                    except AttributeError:
                        pass
                    mock_target.fs.get_all_data()[fn] = self.getvalue()
                super(Buffer, self).close()

            def __exit__(self, exc_type, exc_val, exc_tb):
                if not exc_type:
                    self.close()

            def __enter__(self):
                return self

            def readable(self):
                return mode == 'r'

            def writeable(self):
                return mode == 'w'

            def seekable(self):
                return False

        if mode == 'w':
            wrapper = self.format.pipe_writer(Buffer())
            wrapper.set_wrapper(wrapper)
            return wrapper
        else:
            return self.format.pipe_reader(Buffer(self.fs.get_all_data()[fn]))

class MockFile(MockTarget):
    def __init__(self, *args, **kwargs):
        warnings.warn("MockFile has been renamed MockTarget", DeprecationWarning, stacklevel=2)
        super(MockFile, self).__init__(*args, **kwargs)
<|endoftext|>


<commit_before>"""
.. module:: NoteOrganiser
    :synopsis: Define the NoteOrganiser class

.. moduleauthor:: Benjamin Audren <benjamin.audren@gmail.com>
"""
from __future__ import unicode_literals
# Main imports
import sys
from PySide import QtGui
from PySide import QtCore

# Local imports
from noteorganiser.popups import SetExternalEditor
from noteorganiser.frames import Library, Editing, Preview
from noteorganiser.logger import create_logger
import noteorganiser.configuration as conf

class NoteOrganiser(QtGui.QMainWindow):
    """
    Main Program

    The main window will consist of three tabs, to help you navigate your
    notes: Library, editing and previewing.
    """
    states = [
        'library',  # The starting one, displaying the notebooks
        'editing',
        'preview']

    def __init__(self, info):
        QtGui.QMainWindow.__init__(self)

        # Store reference to the info class
        self.info = info
        # Shortcut for the logger
        self.log = self.info.logger

        self.initUI()
        self.initLogic()
        self.show()

    def initUI(self):
        """Initialise all the User Interface"""
        self.log.info("Starting UI init of %s" % self.__class__.__name__)
        self.initWidgets()
        self.initMenuBar()
        self.initStatusBar()
        self.log.info("Finished UI init of %s" % self.__class__.__name__)

        # set this to be half-screen, on the left
        self.settings = QtCore.QSettings("audren", "NoteOrganiser")
        if self.settings.value("geometry"):
            self.restoreGeometry(self.settings.value("geometry"))
        else:
            self.geometry = QtGui.QApplication.desktop().screenGeometry()
            self.setGeometry(
                0, 0, self.geometry.width()/2., self.geometry.height())
        self.setWindowTitle('Note Organiser')

    def initMenuBar(self):
        """Defining the menu bar"""
        self.log.info("Creating Menu Bar")
        # Exit
        exitAction = QtGui.QAction('&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.cleanClose)

        # Toggle displaying empty folders
        toggleEmptyAction = QtGui.QAction('display empty folders', self)
        toggleEmptyAction.setShortcut('Ctrl+T')
        toggleEmptyAction.setStatusTip('Toggle the display of empty folders')
        toggleEmptyAction.setCheckable(True)
        toggleEmptyAction.setChecked(self.info.display_empty)
        toggleEmptyAction.triggered.connect(
            self.library.shelves.toggleDisplayEmpty)

        # show popup for external editor commandline
        externalEditor = QtGui.QAction('set external Editor', self)
        externalEditor.setStatusTip(
            'Set the Commandline for the external Editor')
        externalEditor.triggered.connect(self.setExternalEditor)

        # Zoom-in
        zoomInAction = QtGui.QAction('Zoom-in', self)
        zoomInAction.setShortcut('Ctrl++')
        zoomInAction.setStatusTip('Zoom in')
        zoomInAction.triggered.connect(self.zoomIn)

        # Zoom-out
        zoomOutAction = QtGui.QAction('Zoom-out', self)
        zoomOutAction.setShortcut('Ctrl+-')
        zoomOutAction.setStatusTip('Zoom out')
        zoomOutAction.triggered.connect(self.zoomOut)

        # Reset Size
        resetSizeAction = QtGui.QAction('Reset-size', self)
        resetSizeAction.setShortcut('Ctrl+0')
        resetSizeAction.setStatusTip('Reset size')
        resetSizeAction.triggered.connect(self.resetSize)

        # Create the menu
        menubar = self.menuBar()
        # File menu
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(exitAction)

        # Options menu
        optionsMenu = menubar.addMenu('&Options')
        optionsMenu.addAction(toggleEmptyAction)
        optionsMenu.addAction(externalEditor)

        # Display menu
        displayMenu = menubar.addMenu('&Display')
        displayMenu.addAction(zoomInAction)
        displayMenu.addAction(zoomOutAction)
        displayMenu.addAction(resetSizeAction)

    def setExternalEditor(self):
        """set the variable for the external editor"""
        self.popup = SetExternalEditor(self)
        #this will show the popup
        ok = self.popup.exec_()
        # the return code is True if successfull
        if ok:
            #Recover the field
            self.info.externalEditor = self.popup.commandline

    def initStatusBar(self):
        """Defining the status bar"""
        self.log.info("Creating Status Bar")
        self.statusBar()

    def initWidgets(self):
        """Creating the tabbed widget containing the three main tabs"""
        # Creating the tabbed widget
        self.tabs = QtGui.QTabWidget(self)

        # Creating the three tabs. Through their parent, they will recover the
        # reference to the list of notebooks.
        self.library = Library(self)
        self.editing = Editing(self)
        self.preview = Preview(self)

        # Adding them to the tabs widget
        self.tabs.addTab(self.library, "&Library")
        self.tabs.addTab(self.editing, "&Editing")
        self.tabs.addTab(self.preview, "Previe&w")

        # Set the tabs widget to be the center widget of the main window
        self.log.info("Setting the central widget")
        self.setCentralWidget(self.tabs)

    def initLogic(self):
        """Linking signals from widgets to functions"""
        self.state = 'library'
        # Connect slots to signal
        # * shelves refresh to the editing refresh
        self.library.shelves.refreshSignal.connect(self.editing.refresh)
        # * shelves switchTab to the own switchTab method
        self.library.shelves.switchTabSignal.connect(self.switchTab)
        # * editing preview to preview loadNotebook, and switch the tab
        self.editing.loadNotebook.connect(self.previewNotebook)

    @QtCore.Slot(str, str)
    def switchTab(self, tab, notebook):
        """Switch Tab to the desired target"""
        self.tabs.setCurrentIndex(self.states.index(tab))
        if tab == 'editing':
            self.editing.switchNotebook(notebook)

    @QtCore.Slot(str)
    def previewNotebook(self, notebook):
        if self.preview.loadNotebook(notebook):
            self.switchTab('preview', notebook)

    def closeEvent(self, event):
        self.cleanClose()

    def cleanClose(self):
        """Overload the closeEvent to store the geometry"""
        self.settings = QtCore.QSettings("audren", "NoteOrganiser")
        self.settings.setValue("geometry", self.saveGeometry())
        self.close()

    def zoomIn(self):
        """send a zoom-in signal to the current tab"""
        self.tabs.currentWidget().zoomIn()

    def zoomOut(self):
        """send a zoom-out signal to the current tab"""
        self.tabs.currentWidget().zoomOut()

    def resetSize(self):
        self.tabs.currentWidget().resetSize()

def main(args):
    """Create the application, and execute it"""
    # Initialise the main Qt application
    application = QtGui.QApplication(args)

    # Define a logger
    logger = create_logger('INFO', 'file')
    # Recover the folder path and the notebooks
    root, notebooks, folders = conf.initialise(logger)

    # Create an instance of the Information class to store all this.
    info = conf.Information(logger, root, notebooks, folders)

    # Define the main window
    NoteOrganiser(info)

    # Run
    sys.exit(application.exec_())

if __name__ == "__main__":
    main(sys.argv)
<commit_msg>Cleaned up main for easier testing<commit_after>"""
.. module:: NoteOrganiser
    :synopsis: Define the NoteOrganiser class

.. moduleauthor:: Benjamin Audren <benjamin.audren@gmail.com>
"""
from __future__ import unicode_literals
# Main imports
import sys
from PySide import QtGui
from PySide import QtCore

# Local imports
from noteorganiser.popups import SetExternalEditor
from noteorganiser.frames import Library, Editing, Preview
from noteorganiser.logger import create_logger
import noteorganiser.configuration as conf

class NoteOrganiser(QtGui.QMainWindow):
    """
    Main Program

    The main window will consist of three tabs, to help you navigate your
    notes: Library, editing and previewing.
    """
    states = [
        'library',  # The starting one, displaying the notebooks
        'editing',
        'preview']

    def __init__(self):
        QtGui.QMainWindow.__init__(self)

        # Define a logger
        logger = create_logger('INFO', 'file')
        # Recover the folder path and the notebooks
        root, notebooks, folders = conf.initialise(logger)

        # Create an instance of the Information class to store all this.
        info = conf.Information(logger, root, notebooks, folders)

        # Store reference to the info class
        self.info = info
        # Shortcut for the logger
        self.log = self.info.logger

        self.initUI()
        self.initLogic()
        self.show()

    def initUI(self):
        """Initialise all the User Interface"""
        self.log.info("Starting UI init of %s" % self.__class__.__name__)
        self.initWidgets()
        self.initMenuBar()
        self.initStatusBar()
        self.log.info("Finished UI init of %s" % self.__class__.__name__)

        # set this to be half-screen, on the left
        self.settings = QtCore.QSettings("audren", "NoteOrganiser")
        if self.settings.value("geometry"):
            self.restoreGeometry(self.settings.value("geometry"))
        else:
            self.geometry = QtGui.QApplication.desktop().screenGeometry()
            self.setGeometry(
                0, 0, self.geometry.width()/2., self.geometry.height())
        self.setWindowTitle('Note Organiser')

    def initMenuBar(self):
        """Defining the menu bar"""
        self.log.info("Creating Menu Bar")
        # Exit
        exitAction = QtGui.QAction('&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.cleanClose)

        # Toggle displaying empty folders
        toggleEmptyAction = QtGui.QAction('display empty folders', self)
        toggleEmptyAction.setShortcut('Ctrl+T')
        toggleEmptyAction.setStatusTip('Toggle the display of empty folders')
        toggleEmptyAction.setCheckable(True)
        toggleEmptyAction.setChecked(self.info.display_empty)
        toggleEmptyAction.triggered.connect(
            self.library.shelves.toggleDisplayEmpty)

        # show popup for external editor commandline
        externalEditor = QtGui.QAction('set external Editor', self)
        externalEditor.setStatusTip(
            'Set the Commandline for the external Editor')
        externalEditor.triggered.connect(self.setExternalEditor)

        # Zoom-in
        zoomInAction = QtGui.QAction('Zoom-in', self)
        zoomInAction.setShortcut('Ctrl++')
        zoomInAction.setStatusTip('Zoom in')
        zoomInAction.triggered.connect(self.zoomIn)

        # Zoom-out
        zoomOutAction = QtGui.QAction('Zoom-out', self)
        zoomOutAction.setShortcut('Ctrl+-')
        zoomOutAction.setStatusTip('Zoom out')
        zoomOutAction.triggered.connect(self.zoomOut)

        # Reset Size
        resetSizeAction = QtGui.QAction('Reset-size', self)
        resetSizeAction.setShortcut('Ctrl+0')
        resetSizeAction.setStatusTip('Reset size')
        resetSizeAction.triggered.connect(self.resetSize)

        # Create the menu
        menubar = self.menuBar()
        # File menu
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(exitAction)

        # Options menu
        optionsMenu = menubar.addMenu('&Options')
        optionsMenu.addAction(toggleEmptyAction)
        optionsMenu.addAction(externalEditor)

        # Display menu
        displayMenu = menubar.addMenu('&Display')
        displayMenu.addAction(zoomInAction)
        displayMenu.addAction(zoomOutAction)
        displayMenu.addAction(resetSizeAction)

    def setExternalEditor(self):
        """set the variable for the external editor"""
        self.popup = SetExternalEditor(self)
        #this will show the popup
        ok = self.popup.exec_()
        # the return code is True if successfull
        if ok:
            #Recover the field
            self.info.externalEditor = self.popup.commandline

    def initStatusBar(self):
        """Defining the status bar"""
        self.log.info("Creating Status Bar")
        self.statusBar()

    def initWidgets(self):
        """Creating the tabbed widget containing the three main tabs"""
        # Creating the tabbed widget
        self.tabs = QtGui.QTabWidget(self)

        # Creating the three tabs. Through their parent, they will recover the
        # reference to the list of notebooks.
        self.library = Library(self)
        self.editing = Editing(self)
        self.preview = Preview(self)

        # Adding them to the tabs widget
        self.tabs.addTab(self.library, "&Library")
        self.tabs.addTab(self.editing, "&Editing")
        self.tabs.addTab(self.preview, "Previe&w")

        # Set the tabs widget to be the center widget of the main window
        self.log.info("Setting the central widget")
        self.setCentralWidget(self.tabs)

    def initLogic(self):
        """Linking signals from widgets to functions"""
        self.state = 'library'
        # Connect slots to signal
        # * shelves refresh to the editing refresh
        self.library.shelves.refreshSignal.connect(self.editing.refresh)
        # * shelves switchTab to the own switchTab method
        self.library.shelves.switchTabSignal.connect(self.switchTab)
        # * editing preview to preview loadNotebook, and switch the tab
        self.editing.loadNotebook.connect(self.previewNotebook)

    @QtCore.Slot(str, str)
    def switchTab(self, tab, notebook):
        """Switch Tab to the desired target"""
        self.tabs.setCurrentIndex(self.states.index(tab))
        if tab == 'editing':
            self.editing.switchNotebook(notebook)

    @QtCore.Slot(str)
    def previewNotebook(self, notebook):
        if self.preview.loadNotebook(notebook):
            self.switchTab('preview', notebook)

    def closeEvent(self, event):
        self.cleanClose()

    def cleanClose(self):
        """Overload the closeEvent to store the geometry"""
        self.settings = QtCore.QSettings("audren", "NoteOrganiser")
        self.settings.setValue("geometry", self.saveGeometry())
        self.close()

    def zoomIn(self):
        """send a zoom-in signal to the current tab"""
        self.tabs.currentWidget().zoomIn()

    def zoomOut(self):
        """send a zoom-out signal to the current tab"""
        self.tabs.currentWidget().zoomOut()

    def resetSize(self):
        self.tabs.currentWidget().resetSize()

def main(args):
    """Create the application, and execute it"""
    # Initialise the main Qt application
    application = QtGui.QApplication(args)

    # Define the main window
    NoteOrganiser()

    # Run
    sys.exit(application.exec_())

if __name__ == "__main__":
    main(sys.argv)
<|endoftext|>


<commit_before>"""
HappyBase version module.

This module defines the package version for use in __init__.py and
setup.py.
"""

__version__ = '0.6'
<commit_msg>Bump version to 0.7dev<commit_after>"""
HappyBase version module.

This module defines the package version for use in __init__.py and
setup.py.
"""

__version__ = '0.7dev'
<|endoftext|>


<commit_before>"""
haproxstats.utils
~~~~~~~~~~~~~~~~~~

This module provides functions, constants and classes that are used within
haproxystats.
"""
import os
import stat
from collections import defaultdict, deque
import io
import socket
import shutil
import logging
import time
import glob
import pyinotify
import pandas

log = logging.getLogger('root')  # pylint: disable=I0011,C0103

FILE_SUFFIX_INFO = '_info'
FILE_SUFFIX_STAT = '_stat'
CMD_SUFFIX_MAP = {'info': FILE_SUFFIX_INFO, 'stat': FILE_SUFFIX_STAT}

class BrokenConnection(Exception):
    def __init__(self, raised):
        self.raised = raised

def is_unix_socket(path):
    """Check if path is a valid UNIX socket.

    Arguments:
        path (str): A file name path

    Returns:
        True if path is a valid UNIX socket otherwise False.
    """
    mode = os.stat(path).st_mode

    return stat.S_ISSOCK(mode)

def concat_csv(csv_files):
    """Performs a concatenation along several csv files.

    Arguments:
        csv_files (lst): A list of csv files.

    Returns:
        A pandas data frame object or None if fails to parse csv_files
    """
    data_frames = []
    for csv_file in csv_files:
        try:
            data_frame = pandas.read_csv(csv_file, low_memory=False)
        except ValueError as error:
            log.error('Pandas failed to parse %s file with: %s', csv_file,
                      error)
        else:
            data_frames.append(data_frame)
    if data_frames:
        return pandas.concat(data_frames)
    else:
        return None

def get_files(path, suffix):
    """Returns the files from a directory which match a suffix

    Arguments:
        path (str): Pathname
        suffix (str): Suffix to match against

    Returns:
        A list of filenames
    """
    files = [filename
             for filename in glob.glob(path + '/*{s}'.format(s=suffix))]

    return files

def retries(retries=3,
            interval=0.9,
            backoff=3,
            delay=10,
            exceptions=(ConnectionResetError, ConnectionRefusedError,
                        ConnectionAbortedError, BrokenPipeError, OSError),
            exception_to_raise=BrokenConnection):
    """A decorator which implements a retry logic.

    Arguments:
        retries (int): Maximum times to retry
        interval (float): Sleep this many seconds between retries
        backoff (int): Multiply interval by this factor after each failure
        exceptions (tuple): A list of exceptions to catch
        exception_to_raise (obj): An exception to raise when maximum tries
            have been reached.

    The decorator calls the function up to retries times if it raises an
    exception from the tuple. The decorated function will only be retried if
    it raises one of the specified exceptions.
    """
    def dec(func):
        def decorated_func(*args, **kwargs):
            backoff_interval = interval
            raised = None
            attempt = 0  # times to attempt a connect after a failure
            if retries == -1:  # -1 means retry indefinitely
                attempt = -1
            elif retries == 0:  # Zero means don't retry
                attempt = 1
            else:  # any other value means retry N times
                attempt = retries + 1
            while attempt != 0:
                if raised:
                    log.error('caught "%s" at "%s", remaining tries %s, '
                              'sleeping for %.2f seconds', raised,
                              func.__name__, attempt, backoff_interval)
                    time.sleep(backoff_interval)
                    backoff_interval = backoff_interval * backoff
                try:
                    return func(*args, **kwargs)
                except exceptions as error:
                    raised = error
                else:
                    raised = None
                    break

                attempt -= 1

            if raised:
                raise exception_to_raise(raised=raised)

        return decorated_func

    return dec

class Dispatcher(object):
    """Dispatch data to different handlers"""
    def __init__(self):
        self.handlers = defaultdict(list)

    def register(self, signal, callback):
        """Register a callback to a singal

        Multiple callbacks can be assigned to the same signal.

        Arguments:
            signal (str): The name of the signal
            callbacl (obj): A callable object to call for the given signal.
            """
        self.handlers[signal].append(callback)

    def unregister(self, signal, callback):
        """Unregister a callback to a singal

        Arguments:
            signal (str): The name of the signal
            callbacl (obj): A callable object to call for the given signal.
        """
        try:
            self.handlers[signal].remove(callback)
        except ValueError:
            log.debug('tried to unregister %s from unknown %s signal',
                      callback, signal)

    def signal(self, signal, **kwargs):
        """Run registered handlers

        Arguments:
            signal (str): A registered signal
        """
        if signal in self.handlers:
            for handler in self.handlers.get(signal):
                handler(**kwargs)

class GraphiteHandler():
    """A handler to send data to graphite.

    Arguments:
        server (str): Server name or IP address.
        port (int): Port to connect to
        retries (int): Numbers to retry on connection failure
        interval (float): Time to sleep between retries
        connect_timeout (float): Timeout on connection
        write_timeout (float): Timeout on sending data
        delay (float): Time to delay a connection attempt after last failure
        backoff (float): Multiply interval by this factor after each failure
        queue_size (int): Maximum size of the queue
        """
    def __init__(self,
                 server,
                 port=3002,
                 retries=1,
                 interval=2,
                 connect_timeout=1,
                 write_timeout=1,
                 delay=4,
                 backoff=2,
                 queue_size=1000000):
        self.server = server
        self.port = port
        self.retries = retries
        self.interval = interval
        self.connect_timeout = connect_timeout
        self.write_timeout = write_timeout
        self.delay = delay
        self.backoff = backoff
        self.queue_size = queue_size
        self.dqueue = deque([], maxlen=self.queue_size)
        self.connection = None
        self.timer = None
        self.failures = 1
        self.exceptions = (ConnectionResetError, ConnectionRefusedError,
                           ConnectionAbortedError, BrokenPipeError, OSError,
                           socket.timeout)

        log.debug('connect timeout %.2fsecs write timeout %.2fsecs',
                  self.connect_timeout, self.write_timeout)

    def open(self):
        """Open a connection to graphite relay."""
        try:
            self.connect()
        except BrokenConnection as error:
            log.error('failed to connect to %s on port %s: %s', self.server,
                      self.port, error.raised)
        else:
            self.connection.settimeout(self.write_timeout)
            log.info('successfully connected to %s on port %s', self.server,
                     self.port)

    @property
    def connect(self):
        """A convenient wrapper so we can pass arguments to decorator"""
        @retries(retries=self.retries, interval=self.interval,
                 backoff=self.backoff, exceptions=self.exceptions,
                 exception_to_raise=BrokenConnection)
        def _create_connection():
            """Try to open a connection.

            Exceptions are caught by the decorator which implements the retry
            logic.
            """
            log.info('connecting to %s on port %s', self.server, self.port)
            self.connection =\
                socket.create_connection((self.server, self.port),
                                         timeout=self.connect_timeout)

        return _create_connection

    def send(self, **kwargs):
        """Send data to graphite relay"""
        self.dqueue.appendleft(kwargs.get('data'))

        while len(self.dqueue) != 0:
            item = self.dqueue.popleft()
            try:
                self.connection.sendall(bytes(item, 'utf-8'))
            # AttributeError means that open() method failed, all other
            # exceptions indicate that connection died.
            except (AttributeError, BrokenPipeError, ConnectionResetError,
                    ConnectionAbortedError, ConnectionAbortedError,
                    OSError) as exc:
                self.dqueue.appendleft(item)
                # Only try to connect again if some time has passed
                if self.timer is None:  # It's 1st failure
                    self.timer = time.time()
                elif time.time() - self.timer > self.delay:
                    log.error('caught %s while sending data to graphite', exc)
                    log.warning('%s secs passed since last connection failure '
                                'trying to connect graphite', self.delay)
                    self.timer = time.time()
                    self.open()
                return
            except socket.timeout:
                # Don't leak FDs
                self.close()
                self.open()
                return
            else:
                # Consume all items from the local deque before return to
                # the caller. This causes a small delay to the caller at the
                # benefit of flushing data as soon as possible which avoids
                # gaps in graphs.
                continue

    def close(self, **kwargs):
        """Close TCP connection to graphite relay"""
        log.info('closing connection to %s on port %s', self.server, self.port)
        try:
            self.connection.close()
        except (ConnectionRefusedError, ConnectionResetError,
                ConnectionAbortedError) as exc:
            log.warning('closing connection failed: %s', exc)
        else:
            log.info('successfully closed connection to %s on port %s',
                     self.server, self.port)

dispatcher = Dispatcher()  # pylint: disable=I0011,C0103

class FileHandler():
    """A handler to write data to a file"""
    def __init__(self):
        self._input = None
        self._output = None

    def open(self):
        self._input = io.StringIO()

    def send(self, **kwargs):
        """Write data to a file-like object"""
        self._input.write(kwargs.get('data'))

    def set_path(self, filepath):
        """Set the filepath to send data.

        Arguments:
            filepath (str): The pathname of the file
        """
        log.debug('filepath for local-store set to %s', filepath)
        try:
            self._output = open(filepath, 'w')
        except (OSError, PermissionError) as error:
            log.error('failed to create %s: %s', filepath, error)

    def loop(self, **kwargs):
        """Rotate the file"""
        base_dir = os.path.join(kwargs.get('local_store'),
                                kwargs.get('epoch_time'))
        try:
            os.makedirs(base_dir)
        except (OSError, PermissionError) as error:
            # errno 17 => file exists
            if error.errno != 17:
                log.error('failed to make directory %s: %s', base_dir, error)
        self.set_path(filepath=os.path.join(base_dir, 'stats'))

    def flush(self, **kwargs):
        """Flush data to disk"""
        self._input.seek(0)
        try:
            shutil.copyfileobj(self._input, self._output)
            self._output.flush()
            self._output.close()
        except (OSError, PermissionError, AttributeError) as error:
            log.error('failed to flush data to file: %s', error)
            self._input.close()

        self.open()

class EventHandler(pyinotify.ProcessEvent):
    """An event handler for inotify to push items to a queue.

    If the event isn't for a directory no action is taken.

    Arguments:
        tasks (queue obj): A queue to put items.
    """
    def my_init(self, tasks):
        self.tasks = tasks  # pylint: disable=W0201

    def _put_item_to_queue(self, pathname):
        """Add item to queue if and only if the pathname is a directory"""
        if os.path.isdir(pathname):
            log.info('putting %s in queue', pathname)
            self.tasks.put(pathname)
        else:
            log.info("ignore %s as it isn't directory", pathname)

    def process_IN_CREATE(self, event):  # pylint: disable=C0103
        "Invoked when a directory is created."
        log.debug('received an event for CREATE')
        self._put_item_to_queue(event.pathname)

    def process_IN_MOVED_TO(self, event):  # pylint: disable=C0103
        "Invoked when a directory/file is moved"
        log.debug('received an event for MOVE')
        self._put_item_to_queue(event.pathname)
<commit_msg>Add docstrings in functions<commit_after>"""
haproxstats.utils
~~~~~~~~~~~~~~~~~~

This module provides functions, constants and classes that are used within
haproxystats.
"""
import os
import stat
from collections import defaultdict, deque
import io
import socket
import shutil
import logging
import time
import glob
import pyinotify
import pandas

log = logging.getLogger('root')  # pylint: disable=I0011,C0103

FILE_SUFFIX_INFO = '_info'
FILE_SUFFIX_STAT = '_stat'
CMD_SUFFIX_MAP = {'info': FILE_SUFFIX_INFO, 'stat': FILE_SUFFIX_STAT}

class BrokenConnection(Exception):
    """A wrapper of all possible exception during a TCP connect"""
    def __init__(self, raised):
        self.raised = raised

def is_unix_socket(path):
    """Check if path is a valid UNIX socket.

    Arguments:
        path (str): A file name path

    Returns:
        True if path is a valid UNIX socket otherwise False.
    """
    mode = os.stat(path).st_mode

    return stat.S_ISSOCK(mode)

def concat_csv(csv_files):
    """Performs a concatenation along several csv files.

    Arguments:
        csv_files (lst): A list of csv files.

    Returns:
        A pandas data frame object or None if fails to parse csv_files
    """
    data_frames = []
    for csv_file in csv_files:
        try:
            data_frame = pandas.read_csv(csv_file, low_memory=False)
        except ValueError as error:
            log.error('Pandas failed to parse %s file with: %s', csv_file,
                      error)
        else:
            data_frames.append(data_frame)
    if data_frames:
        return pandas.concat(data_frames)
    else:
        return None

def get_files(path, suffix):
    """Returns the files from a directory which match a suffix

    Arguments:
        path (str): Pathname
        suffix (str): Suffix to match against

    Returns:
        A list of filenames
    """
    files = [filename
             for filename in glob.glob(path + '/*{s}'.format(s=suffix))]

    return files

def retries(retries=3,
            interval=0.9,
            backoff=3,
            delay=10,
            exceptions=(ConnectionResetError, ConnectionRefusedError,
                        ConnectionAbortedError, BrokenPipeError, OSError),
            exception_to_raise=BrokenConnection):
    """A decorator which implements a retry logic.

    Arguments:
        retries (int): Maximum times to retry
        interval (float): Sleep this many seconds between retries
        backoff (int): Multiply interval by this factor after each failure
        exceptions (tuple): A list of exceptions to catch
        exception_to_raise (obj): An exception to raise when maximum tries
            have been reached.

    The decorator calls the function up to retries times if it raises an
    exception from the tuple. The decorated function will only be retried if
    it raises one of the specified exceptions.
    """
    def dec(func):
        def decorated_func(*args, **kwargs):
            backoff_interval = interval
            raised = None
            attempt = 0  # times to attempt a connect after a failure
            if retries == -1:  # -1 means retry indefinitely
                attempt = -1
            elif retries == 0:  # Zero means don't retry
                attempt = 1
            else:  # any other value means retry N times
                attempt = retries + 1
            while attempt != 0:
                if raised:
                    log.error('caught "%s" at "%s", remaining tries %s, '
                              'sleeping for %.2f seconds', raised,
                              func.__name__, attempt, backoff_interval)
                    time.sleep(backoff_interval)
                    backoff_interval = backoff_interval * backoff
                try:
                    return func(*args, **kwargs)
                except exceptions as error:
                    raised = error
                else:
                    raised = None
                    break

                attempt -= 1

            if raised:
                raise exception_to_raise(raised=raised)

        return decorated_func

    return dec

class Dispatcher(object):
    """Dispatch data to different handlers"""
    def __init__(self):
        self.handlers = defaultdict(list)

    def register(self, signal, callback):
        """Register a callback to a singal

        Multiple callbacks can be assigned to the same signal.

        Arguments:
            signal (str): The name of the signal
            callbacl (obj): A callable object to call for the given signal.
            """
        self.handlers[signal].append(callback)

    def unregister(self, signal, callback):
        """Unregister a callback to a singal

        Arguments:
            signal (str): The name of the signal
            callbacl (obj): A callable object to call for the given signal.
        """
        try:
            self.handlers[signal].remove(callback)
        except ValueError:
            log.debug('tried to unregister %s from unknown %s signal',
                      callback, signal)

    def signal(self, signal, **kwargs):
        """Run registered handlers

        Arguments:
            signal (str): A registered signal
        """
        if signal in self.handlers:
            for handler in self.handlers.get(signal):
                handler(**kwargs)

class GraphiteHandler():
    """A handler to send data to graphite.

    Arguments:
        server (str): Server name or IP address.
        port (int): Port to connect to
        retries (int): Numbers to retry on connection failure
        interval (float): Time to sleep between retries
        connect_timeout (float): Timeout on connection
        write_timeout (float): Timeout on sending data
        delay (float): Time to delay a connection attempt after last failure
        backoff (float): Multiply interval by this factor after each failure
        queue_size (int): Maximum size of the queue
        """
    def __init__(self,
                 server,
                 port=3002,
                 retries=1,
                 interval=2,
                 connect_timeout=1,
                 write_timeout=1,
                 delay=4,
                 backoff=2,
                 queue_size=1000000):
        self.server = server
        self.port = port
        self.retries = retries
        self.interval = interval
        self.connect_timeout = connect_timeout
        self.write_timeout = write_timeout
        self.delay = delay
        self.backoff = backoff
        self.queue_size = queue_size
        self.dqueue = deque([], maxlen=self.queue_size)
        self.connection = None
        self.timer = None
        self.failures = 1
        self.exceptions = (ConnectionResetError, ConnectionRefusedError,
                           ConnectionAbortedError, BrokenPipeError, OSError,
                           socket.timeout)

        log.debug('connect timeout %.2fsecs write timeout %.2fsecs',
                  self.connect_timeout, self.write_timeout)

    def open(self):
        """Open a connection to graphite relay."""
        try:
            self.connect()
        except BrokenConnection as error:
            log.error('failed to connect to %s on port %s: %s', self.server,
                      self.port, error.raised)
        else:
            self.connection.settimeout(self.write_timeout)
            log.info('successfully connected to %s on port %s', self.server,
                     self.port)

    @property
    def connect(self):
        """A convenient wrapper so we can pass arguments to decorator"""
        @retries(retries=self.retries, interval=self.interval,
                 backoff=self.backoff, exceptions=self.exceptions,
                 exception_to_raise=BrokenConnection)
        def _create_connection():
            """Try to open a connection.

            Exceptions are caught by the decorator which implements the retry
            logic.
            """
            log.info('connecting to %s on port %s', self.server, self.port)
            self.connection =\
                socket.create_connection((self.server, self.port),
                                         timeout=self.connect_timeout)

        return _create_connection

    def send(self, **kwargs):
        """Send data to graphite relay"""
        self.dqueue.appendleft(kwargs.get('data'))

        while len(self.dqueue) != 0:
            item = self.dqueue.popleft()
            try:
                self.connection.sendall(bytes(item, 'utf-8'))
            # AttributeError means that open() method failed, all other
            # exceptions indicate that connection died.
            except (AttributeError, BrokenPipeError, ConnectionResetError,
                    ConnectionAbortedError, ConnectionAbortedError,
                    OSError) as exc:
                self.dqueue.appendleft(item)
                # Only try to connect again if some time has passed
                if self.timer is None:  # It's 1st failure
                    self.timer = time.time()
                elif time.time() - self.timer > self.delay:
                    log.error('caught %s while sending data to graphite', exc)
                    log.warning('%s secs passed since last connection failure '
                                'trying to connect graphite', self.delay)
                    self.timer = time.time()
                    self.open()
                return
            except socket.timeout:
                # Don't leak FDs
                self.close()
                self.open()
                return
            else:
                # Consume all items from the local deque before return to
                # the caller. This causes a small delay to the caller at the
                # benefit of flushing data as soon as possible which avoids
                # gaps in graphs.
                continue

    def close(self, **kwargs):
        """Close TCP connection to graphite relay"""
        log.info('closing connection to %s on port %s', self.server, self.port)
        try:
            self.connection.close()
        except (ConnectionRefusedError, ConnectionResetError,
                ConnectionAbortedError) as exc:
            log.warning('closing connection failed: %s', exc)
        else:
            log.info('successfully closed connection to %s on port %s',
                     self.server, self.port)

dispatcher = Dispatcher()  # pylint: disable=I0011,C0103

class FileHandler():
    """A handler to write data to a file"""
    def __init__(self):
        self._input = None
        self._output = None

    def open(self):
        """Build a stringIO object in memory read to be used."""
        self._input = io.StringIO()

    def send(self, **kwargs):
        """Write data to a file-like object"""
        self._input.write(kwargs.get('data'))

    def set_path(self, filepath):
        """Set the filepath to send data.

        Arguments:
            filepath (str): The pathname of the file
        """
        log.debug('filepath for local-store set to %s', filepath)
        try:
            self._output = open(filepath, 'w')
        except (OSError, PermissionError) as error:
            log.error('failed to create %s: %s', filepath, error)

    def loop(self, **kwargs):
        """Rotate the file"""
        base_dir = os.path.join(kwargs.get('local_store'),
                                kwargs.get('epoch_time'))
        try:
            os.makedirs(base_dir)
        except (OSError, PermissionError) as error:
            # errno 17 => file exists
            if error.errno != 17:
                log.error('failed to make directory %s: %s', base_dir, error)
        self.set_path(filepath=os.path.join(base_dir, 'stats'))

    def flush(self, **kwargs):
        """Flush data to disk"""
        self._input.seek(0)
        try:
            shutil.copyfileobj(self._input, self._output)
            self._output.flush()
            self._output.close()
        except (OSError, PermissionError, AttributeError) as error:
            log.error('failed to flush data to file: %s', error)
            self._input.close()

        self.open()

class EventHandler(pyinotify.ProcessEvent):
    """An event handler for inotify to push items to a queue.

    If the event isn't for a directory no action is taken.

    Arguments:
        tasks (queue obj): A queue to put items.
    """
    def my_init(self, tasks):
        self.tasks = tasks  # pylint: disable=W0201

    def _put_item_to_queue(self, pathname):
        """Add item to queue if and only if the pathname is a directory"""
        if os.path.isdir(pathname):
            log.info('putting %s in queue', pathname)
            self.tasks.put(pathname)
        else:
            log.info("ignore %s as it isn't directory", pathname)

    def process_IN_CREATE(self, event):  # pylint: disable=C0103
        "Invoked when a directory is created."
        log.debug('received an event for CREATE')
        self._put_item_to_queue(event.pathname)

    def process_IN_MOVED_TO(self, event):  # pylint: disable=C0103
        "Invoked when a directory/file is moved"
        log.debug('received an event for MOVE')
        self._put_item_to_queue(event.pathname)
<|endoftext|>


<commit_before>import os
from webob import exc
from lwr.util import get_mapped_file, copy_to_path, verify_is_in_directory
from lwr.framework import Controller
from lwr.manager_factory import DEFAULT_MANAGER_NAME

class LwrController(Controller):

    def __init__(self, **kwargs):
        super(LwrController, self).__init__(**kwargs)

    def _check_access(self, req, environ, start_response):
        if req.app.private_key:
            sent_private_key = req.GET.get("private_key", None)
            if not (req.app.private_key == sent_private_key):
                return exc.HTTPUnauthorized()(environ, start_response)

    def _prepare_controller_args(self, req, args):
        managers = req.app.managers
        manager_name = args.get('manager_name', DEFAULT_MANAGER_NAME)
        args['manager'] = managers[manager_name]

@LwrController(response_type='json')
def setup(manager, job_id, tool_id=None, tool_version=None):
    job_id = manager.setup_job(job_id, tool_id, tool_version)
    working_directory = manager.working_directory(job_id)
    outputs_directory = manager.outputs_directory(job_id)
    return {"working_directory": working_directory,
            "outputs_directory": outputs_directory,
            "path_separator": os.sep,
            "job_id": job_id}

@LwrController()
def clean(manager, job_id):
    manager.clean_job_directory(job_id)

@LwrController()
def launch(manager, job_id, command_line):
    manager.launch(job_id, command_line)

@LwrController(response_type='json')
def check_complete(manager, job_id):
    status = manager.get_status(job_id)
    if status == 'complete':
        return_code = manager.return_code(job_id)
        stdout_contents = manager.stdout_contents(job_id)
        stderr_contents = manager.stderr_contents(job_id)
        return {"complete": "true",
                "status": "status",
                "returncode": return_code,
                "stdout": stdout_contents,
                "stderr": stderr_contents}
    elif status == 'cancelled':
        return {"complete": "true",
                "status": status}
    else:
        return {"complete": "false", "status": status}

@LwrController()
def kill(manager, job_id):
    manager.kill(job_id)

@LwrController(response_type='json')
def upload_tool_file(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.job_directory(job_id), name, body)

@LwrController(response_type='json')
def upload_input(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.inputs_directory(job_id), name, body)

@LwrController(response_type='json')
def upload_extra_input(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.inputs_directory(job_id), name, body, allow_nested_files=True)

@LwrController(response_type='json')
def upload_config_file(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.configs_directory(job_id), name, body)

@LwrController(response_type='json')
def upload_working_directory_file(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.tool_files_directory(job_id), name, body)

@LwrController(response_type='file')
def download_output(manager, job_id, name, output_type="direct"):
    return _output_path(manager, job_id, name, output_type)

def _output_path(manager, job_id, name, output_type):
    """
    """
    directory = manager.outputs_directory(job_id)
    if output_type == "task" or output_type == "work_dir":
        directory = manager.working_directory(job_id)
    path = os.path.join(directory, name)
    verify_is_in_directory(path, directory)
    return path

@LwrController(response_type='json')
def get_output_type(manager, job_id, name):
    outputs_directory = manager.outputs_directory(job_id)
    working_directory = manager.working_directory(job_id)
    if os.path.exists(os.path.join(outputs_directory, name)):
        return "direct"
    elif os.path.exists(os.path.join(working_directory, name)):
        return "task"
    else:
        return "none"

def _handle_upload_to_directory(directory, remote_path, body, allow_nested_files=False):
    path = get_mapped_file(directory, remote_path, allow_nested_files=allow_nested_files)
    copy_to_path(body, path)
    return {"path": path}
<commit_msg>Fix bug preventing tool wrappers from be written to the new tool_files directory.<commit_after>import os
from webob import exc
from lwr.util import get_mapped_file, copy_to_path, verify_is_in_directory
from lwr.framework import Controller
from lwr.manager_factory import DEFAULT_MANAGER_NAME

class LwrController(Controller):

    def __init__(self, **kwargs):
        super(LwrController, self).__init__(**kwargs)

    def _check_access(self, req, environ, start_response):
        if req.app.private_key:
            sent_private_key = req.GET.get("private_key", None)
            if not (req.app.private_key == sent_private_key):
                return exc.HTTPUnauthorized()(environ, start_response)

    def _prepare_controller_args(self, req, args):
        managers = req.app.managers
        manager_name = args.get('manager_name', DEFAULT_MANAGER_NAME)
        args['manager'] = managers[manager_name]

@LwrController(response_type='json')
def setup(manager, job_id, tool_id=None, tool_version=None):
    job_id = manager.setup_job(job_id, tool_id, tool_version)
    working_directory = manager.working_directory(job_id)
    outputs_directory = manager.outputs_directory(job_id)
    return {"working_directory": working_directory,
            "outputs_directory": outputs_directory,
            "path_separator": os.sep,
            "job_id": job_id}

@LwrController()
def clean(manager, job_id):
    manager.clean_job_directory(job_id)

@LwrController()
def launch(manager, job_id, command_line):
    manager.launch(job_id, command_line)

@LwrController(response_type='json')
def check_complete(manager, job_id):
    status = manager.get_status(job_id)
    if status == 'complete':
        return_code = manager.return_code(job_id)
        stdout_contents = manager.stdout_contents(job_id)
        stderr_contents = manager.stderr_contents(job_id)
        return {"complete": "true",
                "status": "status",
                "returncode": return_code,
                "stdout": stdout_contents,
                "stderr": stderr_contents}
    elif status == 'cancelled':
        return {"complete": "true",
                "status": status}
    else:
        return {"complete": "false", "status": status}

@LwrController()
def kill(manager, job_id):
    manager.kill(job_id)

@LwrController(response_type='json')
def upload_tool_file(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.tool_files_directory(job_id), name, body)

@LwrController(response_type='json')
def upload_input(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.inputs_directory(job_id), name, body)

@LwrController(response_type='json')
def upload_extra_input(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.inputs_directory(job_id), name, body, allow_nested_files=True)

@LwrController(response_type='json')
def upload_config_file(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.configs_directory(job_id), name, body)

@LwrController(response_type='json')
def upload_working_directory_file(manager, job_id, name, body):
    return _handle_upload_to_directory(manager.tool_files_directory(job_id), name, body)

@LwrController(response_type='file')
def download_output(manager, job_id, name, output_type="direct"):
    return _output_path(manager, job_id, name, output_type)

def _output_path(manager, job_id, name, output_type):
    """
    """
    directory = manager.outputs_directory(job_id)
    if output_type == "task" or output_type == "work_dir":
        directory = manager.working_directory(job_id)
    path = os.path.join(directory, name)
    verify_is_in_directory(path, directory)
    return path

@LwrController(response_type='json')
def get_output_type(manager, job_id, name):
    outputs_directory = manager.outputs_directory(job_id)
    working_directory = manager.working_directory(job_id)
    if os.path.exists(os.path.join(outputs_directory, name)):
        return "direct"
    elif os.path.exists(os.path.join(working_directory, name)):
        return "task"
    else:
        return "none"

def _handle_upload_to_directory(directory, remote_path, body, allow_nested_files=False):
    path = get_mapped_file(directory, remote_path, allow_nested_files=allow_nested_files)
    copy_to_path(body, path)
    return {"path": path}
<|endoftext|>