Working on zip

This commit is contained in:
Thomas Forgione 2024-07-29 10:21:11 +02:00
parent b8d1f2feb0
commit 8b2f968c2d
2 changed files with 81 additions and 20 deletions

10
app.py
View File

@ -7,7 +7,7 @@ import os
from os.path import join
import sqlite3
import uuid
from . import db, config, scanner, calibration, tar
from . import db, config, scanner, calibration, archive
app = Flask(__name__)
@ -285,14 +285,14 @@ def download_object(id: int):
]
# Create archive file to send
archive = tar.TarSender()
tar = archive.TarSender()
for calibration_index, (calib, acquisitions) in enumerate(acquisitions_grouped):
calibration_dir = join(config.CALIBRATION_DIR, str(calib.id))
# Add calibration images
for image in os.listdir(calibration_dir):
archive.add_file(
tar.add_file(
f'object/{calibration_index}/calibration/{image}',
join(calibration_dir, image)
)
@ -302,12 +302,12 @@ def download_object(id: int):
acquisition_dir = join(config.OBJECT_DIR, str(object.id), str(acquisition.id))
for image in os.listdir(acquisition_dir):
archive.add_file(
tar.add_file(
f'object/{calibration_index}/{acquisition_index}/{image}',
join(acquisition_dir, image)
)
return archive.response()
return tar.response()
@app.route('/static/<path:path>')

View File

@ -1,7 +1,12 @@
import builtins
from datetime import datetime
from flask import Response
import functools
import os
import zlib
# Chunks for crc 32 computation
CRC32_CHUNK_SIZE = 65_536
# 4MiB chunks
CHUNK_SIZE = 4_194_304
@ -12,20 +17,8 @@ SPACE = ord(' ')
# ASCII value for zero
ZERO = ord('0')
# Specification de l'entête du fichier
# Numéro Nom Début Taille Description
# 1 name 0 100 Nom du fichier
# 2 mode 100 8 Permissions
# 3 uid 108 8 Propriétaire (inutilisé si format étendu)
# 4 gid 116 8 Groupe (inutilisé si format étendu)
# 5 size 124 12 Taille du fichier en octets.
# 6 mtime 136 12 Dernière modification en temps Unix.
# 7 chksum 148 8 Somme de contrôle de l'en-tête où ce champ est considéré comme rempli d'espaces (32)
# 8 type flag 156 1 Type de fichier
# 9 linkname 157 100 Nom du fichier pointé par ce lien symbolique (Si le type indique un lien symbolique)
def header_chunk(filename: str, filepath: str) -> bytes:
def tar_header_chunk(filename: str, filepath: str) -> bytes:
# Returns the octal representation without the initial
def oct(i: int) -> str:
@ -71,17 +64,22 @@ def header_chunk(filename: str, filepath: str) -> bytes:
return bytes(buffer)
class TarSender:
class ArchiveSender:
def __init__(self):
self.files: dict[str, str] = {}
def add_file(self, filename: str, filepath: str):
self.files[filename] = filepath
def response(self):
raise NotImplementedError("Abstract method")
class TarSender(ArchiveSender):
def response(self):
def generate():
for name, file in self.files.items():
yield header_chunk(name, file)
yield tar_header_chunk(name, file)
bytes_sent = 0
@ -104,3 +102,66 @@ class TarSender:
mimetype='application/x-tar',
headers={'Content-Disposition': 'attachment; filename="archive.tar"'}
)
def crc32(filename):
with open(filename, 'rb') as fh:
hash = 0
while True:
s = fh.read(CRC32_CHUNK_SIZE)
if not s:
break
hash = zlib.crc32(s, hash)
return hash
def zip_local_file_header(filename: str, filepath: str, fileindex: int) -> bytes:
buffer_size = 30 + len(filename)
buffer = bytearray(buffer_size)
stat = os.stat(filepath)
# Field 1: local file header signature (buffer[0:4])
buffer[0:4] = b'PK\x03\x04'
# Field 2: version needed to extract (minimum) (buffer[4:6])
buffer[4:6] = b'\x0a'
# Field 3: general purpose bit flag (buffer[6:8]), leave at 0
# Field 4: compression mode (buffer[8:10]), leave at 0 (uncompressed)
# Field 5: file last modification time (buffer[10:14])
mtime = datetime.fromtimestramp(stat.st_mtime)
buffer[10:12] = (mtime.second // 2) | (mtime.minute << 5) | (mtime.hour << 11)
buffer[12:14] = mtime.day | (mtime.month << 5) | ((mtime.year - 1980) << 9)
# Field 6: crc-32 of uncompressed data (buffer[14:18])
buffer[14:18] = crc32(filepath).to_bytes(4)
# Field 7: compressed size (buffer[18:22])
buffer[18:22] = stat.st_size.to_bytes(4)
# Field 8: uncompressed size (buffer[22:26])
buffer[22:26] = stat.st_size.to_bytes(4)
# Field 9: filename length (buffer[26:28])
buffer[26:29] = len(filename).to_bytes(4)
# Field 10: extra field length (buffer[28:30])
# Field 11: filename (buffer[30:30+len(filename)])
buffer[30:30+len(filename)] = filename.encode('ascii')
return buffer
class ZipSender(ArchiveSender):
def response(self):
def generate():
yield 'oops'
return Response(
generate(),
mimetype='application/zip',
headers={'Content-Disposition': 'attachment; filename="archive.zip"'}
)