prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Chunked uploads not attempting retries
  I forgot to implement the actual retry-portion for the jQuery file uploading. As such, jQuery file upload will only try to upload a given chunk once. See here:
  
  https://github.com/blueimp/jQuery-File-Upload/wiki/Chunked-file-uploads
  
  </issue>
  <code>
  [start of app/grandchallenge/jqfileupload/views.py]
  1 import re
  2 from datetime import timedelta
  3 
  4 from django.utils.timezone import now
  5 from rest_framework import mixins
  6 from rest_framework.parsers import FormParser, MultiPartParser
  7 from rest_framework.response import Response
  8 from rest_framework.status import HTTP_400_BAD_REQUEST
  9 from rest_framework.viewsets import GenericViewSet
  10 from rest_framework_guardian.filters import ObjectPermissionsFilter
  11 
  12 from grandchallenge.core.permissions.rest_framework import (
  13     DjangoObjectOnlyPermissions,
  14 )
  15 from grandchallenge.jqfileupload.models import StagedFile
  16 from grandchallenge.jqfileupload.serializers import StagedFileSerializer
  17 
  18 
  19 class StagedFileViewSet(
  20     mixins.CreateModelMixin,
  21     mixins.RetrieveModelMixin,
  22     mixins.ListModelMixin,
  23     GenericViewSet,
  24 ):
  25     serializer_class = StagedFileSerializer
  26     queryset = StagedFile.objects.all()
  27     parser_classes = (FormParser, MultiPartParser)
  28     permission_classes = (DjangoObjectOnlyPermissions,)
  29     filter_backends = (ObjectPermissionsFilter,)
  30 
  31     def create(self, request, *args, **kwargs):
  32         if "HTTP_CONTENT_RANGE" in self.request.META:
  33             if not self.range_header or not self.range_match:
  34                 return Response(
  35                     {"status": "Client did not supply valid Content-Range"},
  36                     status=HTTP_400_BAD_REQUEST,
  37                 )
  38 
  39         return super().create(request, *args, **kwargs)
  40 
  41     def get_serializer(self, *args, **kwargs):
  42         data = [
  43             self._handle_file(uploaded_file)
  44             for uploaded_file in self.request.FILES.values()
  45         ]
  46 
  47         if data:
  48             kwargs.update({"many": True, "data": data})
  49 
  50         return super().get_serializer(*args, **kwargs)
  51 
  52     @property
  53     def user_pk_str(self):
  54         return str(self.request.user.pk)
  55 
  56     @property
  57     def client_id(self):
  58         return self.request.POST.get("X-Upload-ID")
  59 
  60     @property
  61     def range_header(self):
  62         return self.request.META.get("HTTP_CONTENT_RANGE")
  63 
  64     @property
  65     def range_match(self):
  66         return re.match(
  67             r"bytes (?P<start>[0-9]{1,32})-(?P<end>[0-9]{1,32})/(?P<length>\*|[0-9]{1,32})",
  68             self.range_header,
  69         )
  70 
  71     def _handle_file(self, uploaded_file):
  72         if "HTTP_CONTENT_RANGE" in self.request.META:
  73             start_byte = int(self.range_match.group("start"))
  74             end_byte = int(self.range_match.group("end"))
  75             if (self.range_match.group("length") is None) or (
  76                 self.range_match.group("length") == "*"
  77             ):
  78                 total_size = None
  79             else:
  80                 total_size = int(self.range_match.group("length"))
  81         else:
  82             start_byte = 0
  83             end_byte = uploaded_file.size - 1
  84             total_size = uploaded_file.size
  85 
  86         return {
  87             "client_id": self.client_id,
  88             "end_byte": end_byte,
  89             "file": uploaded_file,
  90             "filename": uploaded_file.name,
  91             "start_byte": start_byte if start_byte is not None else 0,
  92             "timeout": now() + timedelta(hours=6),
  93             "total_size": total_size,
  94             "user_pk_str": self.user_pk_str,
  95         }
  96
  [end of app/grandchallenge/jqfileupload/views.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/app/grandchallenge/jqfileupload/views.py b/app/grandchallenge/jqfileupload/views.py
  --- a/app/grandchallenge/jqfileupload/views.py
  +++ b/app/grandchallenge/jqfileupload/views.py
  @@ -3,6 +3,7 @@
   
   from django.utils.timezone import now
   from rest_framework import mixins
  +from rest_framework.decorators import action
   from rest_framework.parsers import FormParser, MultiPartParser
   from rest_framework.response import Response
   from rest_framework.status import HTTP_400_BAD_REQUEST
  @@ -35,7 +36,6 @@
                       {"status": "Client did not supply valid Content-Range"},
                       status=HTTP_400_BAD_REQUEST,
                   )
  -
           return super().create(request, *args, **kwargs)
   
       def get_serializer(self, *args, **kwargs):
  @@ -93,3 +93,21 @@
               "total_size": total_size,
               "user_pk_str": self.user_pk_str,
           }
  +
  +    def _find_last_end_byte(self, files):
  +        last_end_byte = -1
  +        for file in files:
  +            if file["start_byte"] != last_end_byte + 1:
  +                return last_end_byte
  +            last_end_byte = file["end_byte"]
  +        return last_end_byte
  +
  +    @action(detail=False, methods=["get"])
  +    def get_current_file_size(self, request):
  +        client_id = request.GET.get("file", None)
  +        files = (
  +            StagedFile.objects.filter(client_id=client_id)
  +            .order_by("start_byte")
  +            .values("start_byte", "end_byte")
  +        )
  +        return Response({"current_size": self._find_last_end_byte(files)})
  
verification_info: {"golden_diff": "diff --git a/app/grandchallenge/jqfileupload/views.py b/app/grandchallenge/jqfileupload/views.py\n--- a/app/grandchallenge/jqfileupload/views.py\n+++ b/app/grandchallenge/jqfileupload/views.py\n@@ -3,6 +3,7 @@\n \n from django.utils.timezone import now\n from rest_framework import mixins\n+from rest_framework.decorators import action\n from rest_framework.parsers import FormParser, MultiPartParser\n from rest_framework.response import Response\n from rest_framework.status import HTTP_400_BAD_REQUEST\n@@ -35,7 +36,6 @@\n                     {\"status\": \"Client did not supply valid Content-Range\"},\n                     status=HTTP_400_BAD_REQUEST,\n                 )\n-\n         return super().create(request, *args, **kwargs)\n \n     def get_serializer(self, *args, **kwargs):\n@@ -93,3 +93,21 @@\n             \"total_size\": total_size,\n             \"user_pk_str\": self.user_pk_str,\n         }\n+\n+    def _find_last_end_byte(self, files):\n+        last_end_byte = -1\n+        for file in files:\n+            if file[\"start_byte\"] != last_end_byte + 1:\n+                return last_end_byte\n+            last_end_byte = file[\"end_byte\"]\n+        return last_end_byte\n+\n+    @action(detail=False, methods=[\"get\"])\n+    def get_current_file_size(self, request):\n+        client_id = request.GET.get(\"file\", None)\n+        files = (\n+            StagedFile.objects.filter(client_id=client_id)\n+            .order_by(\"start_byte\")\n+            .values(\"start_byte\", \"end_byte\")\n+        )\n+        return Response({\"current_size\": self._find_last_end_byte(files)})\n", "issue": "Chunked uploads not attempting retries\nI forgot to implement the actual retry-portion for the jQuery file uploading. As such, jQuery file upload will only try to upload a given chunk once. See here:\r\n\r\nhttps://github.com/blueimp/jQuery-File-Upload/wiki/Chunked-file-uploads\n", "before_files": [{"content": "import re\nfrom datetime import timedelta\n\nfrom django.utils.timezone import now\nfrom rest_framework import mixins\nfrom rest_framework.parsers import FormParser, MultiPartParser\nfrom rest_framework.response import Response\nfrom rest_framework.status import HTTP_400_BAD_REQUEST\nfrom rest_framework.viewsets import GenericViewSet\nfrom rest_framework_guardian.filters import ObjectPermissionsFilter\n\nfrom grandchallenge.core.permissions.rest_framework import (\n    DjangoObjectOnlyPermissions,\n)\nfrom grandchallenge.jqfileupload.models import StagedFile\nfrom grandchallenge.jqfileupload.serializers import StagedFileSerializer\n\n\nclass StagedFileViewSet(\n    mixins.CreateModelMixin,\n    mixins.RetrieveModelMixin,\n    mixins.ListModelMixin,\n    GenericViewSet,\n):\n    serializer_class = StagedFileSerializer\n    queryset = StagedFile.objects.all()\n    parser_classes = (FormParser, MultiPartParser)\n    permission_classes = (DjangoObjectOnlyPermissions,)\n    filter_backends = (ObjectPermissionsFilter,)\n\n    def create(self, request, *args, **kwargs):\n        if \"HTTP_CONTENT_RANGE\" in self.request.META:\n            if not self.range_header or not self.range_match:\n                return Response(\n                    {\"status\": \"Client did not supply valid Content-Range\"},\n                    status=HTTP_400_BAD_REQUEST,\n                )\n\n        return super().create(request, *args, **kwargs)\n\n    def get_serializer(self, *args, **kwargs):\n        data = [\n            self._handle_file(uploaded_file)\n            for uploaded_file in self.request.FILES.values()\n        ]\n\n        if data:\n            kwargs.update({\"many\": True, \"data\": data})\n\n        return super().get_serializer(*args, **kwargs)\n\n    @property\n    def user_pk_str(self):\n        return str(self.request.user.pk)\n\n    @property\n    def client_id(self):\n        return self.request.POST.get(\"X-Upload-ID\")\n\n    @property\n    def range_header(self):\n        return self.request.META.get(\"HTTP_CONTENT_RANGE\")\n\n    @property\n    def range_match(self):\n        return re.match(\n            r\"bytes (?P<start>[0-9]{1,32})-(?P<end>[0-9]{1,32})/(?P<length>\\*|[0-9]{1,32})\",\n            self.range_header,\n        )\n\n    def _handle_file(self, uploaded_file):\n        if \"HTTP_CONTENT_RANGE\" in self.request.META:\n            start_byte = int(self.range_match.group(\"start\"))\n            end_byte = int(self.range_match.group(\"end\"))\n            if (self.range_match.group(\"length\") is None) or (\n                self.range_match.group(\"length\") == \"*\"\n            ):\n                total_size = None\n            else:\n                total_size = int(self.range_match.group(\"length\"))\n        else:\n            start_byte = 0\n            end_byte = uploaded_file.size - 1\n            total_size = uploaded_file.size\n\n        return {\n            \"client_id\": self.client_id,\n            \"end_byte\": end_byte,\n            \"file\": uploaded_file,\n            \"filename\": uploaded_file.name,\n            \"start_byte\": start_byte if start_byte is not None else 0,\n            \"timeout\": now() + timedelta(hours=6),\n            \"total_size\": total_size,\n            \"user_pk_str\": self.user_pk_str,\n        }\n", "path": "app/grandchallenge/jqfileupload/views.py"}]}