Repository: comic/grand-challenge.org

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


Discussion:

Resolving patch:
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 @@ def create(self, request, *args, **kwargs):
                     {"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 @@ def _handle_file(self, uploaded_file):
             "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)})


Test patch:
diff --git a/app/tests/jqfileupload_tests/test_api.py b/app/tests/jqfileupload_tests/test_api.py
--- a/app/tests/jqfileupload_tests/test_api.py
+++ b/app/tests/jqfileupload_tests/test_api.py
@@ -4,9 +4,10 @@
 import pytest
 from rest_framework.authtoken.models import Token
 
+from grandchallenge.jqfileupload.models import StagedFile
 from grandchallenge.jqfileupload.widgets.uploader import StagedAjaxFile
 from grandchallenge.subdomains.utils import reverse
-from tests.factories import UserFactory
+from tests.factories import StagedFileFactory, UserFactory
 from tests.jqfileupload_tests.utils import (
     create_partial_upload_file_request,
     create_upload_file_request,
@@ -385,3 +386,31 @@ def test_inconsistent_chunks_rfc7233(client):
         extra_headers={"HTTP_AUTHORIZATION": f"Token {token}"},
     )
     assert part_2.status_code == 400
+
+
+@pytest.mark.django_db
+def test_get_current_file_size(client):
+    uid = uuid.uuid4()
+    for x in range(0, 1000, 50):
+        StagedFileFactory(
+            client_id="foo", file_id=uid, start_byte=x, end_byte=x + 49
+        )
+    token = Token.objects.create(user=UserFactory())
+
+    response = client.get(
+        path=reverse("api:staged-file-get-current-file-size"),
+        data={"file": "foo"},
+        HTTP_AUTHORIZATION=f"Token {token}",
+    )
+    parsed_json = response.json()
+    assert parsed_json["current_size"] == 999
+
+    StagedFile.objects.order_by("start_byte")[11].delete()
+
+    response = client.get(
+        path=reverse("api:staged-file-get-current-file-size"),
+        data={"file": "foo"},
+        HTTP_AUTHORIZATION=f"Token {token}",
+    )
+    parsed_json = response.json()
+    assert parsed_json["current_size"] == 549