Транслируйте целую папку с помощью 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-файл с тем же именем, что и загруженная папка. И получит размер файла всей папки и всех вложенных папок.

Вернуться на верх