Skip to content

Commit e786fa8

Browse files
authored
Remove storage entry if resumable storage has been enabled and the stored URL has been voided (#105)
* Add new test case when stored url has been voided * Implement special case handler when initialization raises error due to voided url in the storage
1 parent 332e4b6 commit e786fa8

File tree

2 files changed

+67
-6
lines changed

2 files changed

+67
-6
lines changed

tests/test_uploader.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import io
3+
import tempfile
34
from base64 import b64encode
45
from unittest import mock
56

@@ -91,6 +92,51 @@ def test_url(self, filename: str):
9192
self.assertEqual(resumable_uploader.url, "http://tusd.tusdemo.net/files/foo_bar")
9293
self.assertEqual(resumable_uploader.offset, 10)
9394

95+
@parametrize(
96+
"filename",
97+
[FILEPATH_TEXT, FILEPATH_BINARY],
98+
)
99+
@responses.activate
100+
def test_url_voided(self, filename: str):
101+
# Test that voided stored url are cleared
102+
responses.add(
103+
responses.POST,
104+
self.client.url,
105+
adding_headers={"location": "http://tusd.tusdemo.net/files/foo"},
106+
)
107+
responses.add(
108+
responses.HEAD,
109+
"http://tusd.tusdemo.net/files/foo",
110+
status=404,
111+
)
112+
113+
# Create temporary storage file.
114+
temp_fp = tempfile.NamedTemporaryFile(delete=False)
115+
storage = filestorage.FileStorage(temp_fp.name)
116+
uploader = self.client.uploader(
117+
file_path=filename, store_url=True, url_storage=storage
118+
)
119+
120+
# Conduct only POST creation so that we'd get a storage entry.
121+
uploader.upload(stop_at=-1)
122+
key = uploader._get_fingerprint()
123+
# First ensure that an entry was created and stored.
124+
self.assertIsNotNone(uploader.url)
125+
self.assertIsNotNone(storage.get_item(key))
126+
127+
# Now start a new upload, resuming where we left off.
128+
resumed_uploader = self.client.uploader(
129+
file_path=filename, store_url=True, url_storage=storage
130+
)
131+
# HEAD response was 404 so url and storage has to be voided.
132+
self.assertIsNone(resumed_uploader.url)
133+
self.assertIsNone(storage.get_item(key))
134+
135+
# Remove the temporary storage file.
136+
storage.close()
137+
temp_fp.close()
138+
os.remove(temp_fp.name)
139+
94140
def test_request_length(self):
95141
self.uploader.chunk_size = 200
96142
self.assertEqual(self.uploader.get_request_length(), 200)
@@ -203,19 +249,19 @@ def test_upload_empty(self):
203249
# Upload URL being set means the POST request was sent and the empty
204250
# file was uploaded without a single PATCH request.
205251
self.assertTrue(uploader.url)
206-
252+
207253
@mock.patch('tusclient.uploader.uploader.TusRequest')
208254
def test_upload_checksum(self, request_mock):
209255
self.mock_request(request_mock)
210256
self.uploader.upload_checksum = True
211257
self.uploader.upload()
212258
self.assertEqual(self.uploader.offset, self.uploader.get_file_size())
213-
259+
214260
@parametrize("chunk_size", [1, 2, 3, 4, 5, 6])
215261
@responses.activate
216262
def test_upload_length_deferred(self, chunk_size: int):
217263
upload_url = f"{self.client.url}test_upload_length_deferred"
218-
264+
219265
responses.head(
220266
upload_url,
221267
adding_headers={"upload-offset": "0", "Upload-Defer-Length": "1"},
@@ -228,7 +274,7 @@ def test_upload_length_deferred(self, chunk_size: int):
228274
)
229275
self.assertTrue(uploader.upload_length_deferred)
230276
self.assertTrue(uploader.stop_at is None)
231-
277+
232278
offset = 0
233279
while not (offset + chunk_size > 5):
234280
next_offset = min(offset + chunk_size, 5)
@@ -245,7 +291,7 @@ def test_upload_length_deferred(self, chunk_size: int):
245291
adding_headers={"upload-offset": "5"},
246292
match=[matchers.header_matcher(last_req_headers)],
247293
)
248-
294+
249295
uploader.upload()
250296
self.assertEqual(uploader.offset, 5)
251297
self.assertEqual(uploader.stop_at, 5)

tusclient/uploader/baseuploader.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ def __init_url_and_offset(self, url: Optional[str] = None):
238238
If resumability is enabled, this would try to get the url from storage if available,
239239
otherwise it would request a new upload url from the tus server.
240240
"""
241+
key = None
241242
if url:
242243
self.set_url(url)
243244

@@ -246,7 +247,21 @@ def __init_url_and_offset(self, url: Optional[str] = None):
246247
self.set_url(self.url_storage.get_item(key))
247248

248249
if self.url:
249-
self.offset = self.get_offset()
250+
try:
251+
self.offset = self.get_offset()
252+
except TusCommunicationError as error:
253+
# Special cases where url is still considered valid with given response code.
254+
special_case_codes = [423]
255+
# Process case where stored url is no longer valid.
256+
if (
257+
key
258+
and 400 <= error.status_code <= 499
259+
and error.status_code not in special_case_codes
260+
):
261+
self.url = None
262+
self.url_storage.remove_item(key)
263+
else:
264+
raise error
250265

251266
def _get_fingerprint(self):
252267
with self.get_file_stream() as stream:

0 commit comments

Comments
 (0)