Working on zip
This commit is contained in:
parent
b8d1f2feb0
commit
8b2f968c2d
10
app.py
10
app.py
|
|
@ -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>')
|
||||
|
|
|
|||
|
|
@ -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"'}
|
||||
)
|
||||
Loading…
Reference in New Issue