Транслируйте целую папку с помощью Django, не считывая все в память
Предпосылка похожа на этот вопрос. Но принятый ответ не только требует, чтобы каждый файл находился в памяти. В ответе приведен пример использования итератора, но он не работает с open(file, 'rb').read(chunk_size)
.
Более того, это решение на самом деле не загружает папки. Оно только группирует несколько файлов вместе.
Мое решение здесь, но по сути, вам нужно работать с папками иначе, чем с файлами. Окончательный код представляет собой модифицированную версию chipx86's и allista's примеров.
В частности, yield_tar
заменяется на
@classmethod
def yield_tar(cls, file_data_iterable, removed_index):
stream = FileStream()
tar = tarfile.TarFile.open(mode='w|', fileobj=stream, bufsize=tarfile.BLOCKSIZE)
for file in file_data_iterable:
if file.is_file():
file_name = str(file.absolute())[removed_index:]
file_size = file.lstat().st_size
file_date = file.lstat().st_mtime
file_path = file.absolute()
tar_info = tarfile.TarInfo(file_name)
tar_info.size = int(file_size)
tar_info.mtime = file_date
tar.addfile(tar_info)
yield stream.pop()
with open(file_path, 'rb') as file_data:
while True:
data = file_data.read(tarfile.BLOCKSIZE)
if not data:
break
tar.fileobj.write(data)
yield stream.pop()
blocks, remainder = divmod(tar_info.size, tarfile.BLOCKSIZE)
if remainder > 0:
tar.fileobj.write(tarfile.NUL * (tarfile.BLOCKSIZE - remainder))
yield stream.pop()
blocks += 1
tar.offset += blocks * tarfile.BLOCKSIZE
else:
tar_info = tarfile.TarInfo(str(file.absolute())[removed_index:])
tar_info.type = b'5'
tar_info.mode = 0o744
tar.addfile(tar_info)
tar.close()
yield stream.pop()
И используется
def download_directory(path):
downloadpath = pathlib.Path(os.path.join(srcs, path))
files = [p for p in sorted(downloadpath.rglob(f'*'))]
sizes = [p.lstat().st_size for p in files if p.is_file()]
response = FileResponse(
FileStream.yield_tar(files, len(str(downloadpath.absolute())) - len(downloadpath.name)),
content_type="application/x-tar"
)
response["Content-Length"] = sum(sizes)
response["Content-Disposition"] = f'attachment; filename="{pathlib.Path(path).name}.tar"'
return response
Таким образом клиент получит разархивированный tar-файл с тем же именем, что и загруженная папка. И получит размер файла всей папки и всех вложенных папок.