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
|
from os.path import join
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import uuid
|
import uuid
|
||||||
from . import db, config, scanner, calibration, tar
|
from . import db, config, scanner, calibration, archive
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
@ -285,14 +285,14 @@ def download_object(id: int):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Create archive file to send
|
# Create archive file to send
|
||||||
archive = tar.TarSender()
|
tar = archive.TarSender()
|
||||||
|
|
||||||
for calibration_index, (calib, acquisitions) in enumerate(acquisitions_grouped):
|
for calibration_index, (calib, acquisitions) in enumerate(acquisitions_grouped):
|
||||||
calibration_dir = join(config.CALIBRATION_DIR, str(calib.id))
|
calibration_dir = join(config.CALIBRATION_DIR, str(calib.id))
|
||||||
|
|
||||||
# Add calibration images
|
# Add calibration images
|
||||||
for image in os.listdir(calibration_dir):
|
for image in os.listdir(calibration_dir):
|
||||||
archive.add_file(
|
tar.add_file(
|
||||||
f'object/{calibration_index}/calibration/{image}',
|
f'object/{calibration_index}/calibration/{image}',
|
||||||
join(calibration_dir, 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))
|
acquisition_dir = join(config.OBJECT_DIR, str(object.id), str(acquisition.id))
|
||||||
|
|
||||||
for image in os.listdir(acquisition_dir):
|
for image in os.listdir(acquisition_dir):
|
||||||
archive.add_file(
|
tar.add_file(
|
||||||
f'object/{calibration_index}/{acquisition_index}/{image}',
|
f'object/{calibration_index}/{acquisition_index}/{image}',
|
||||||
join(acquisition_dir, image)
|
join(acquisition_dir, image)
|
||||||
)
|
)
|
||||||
|
|
||||||
return archive.response()
|
return tar.response()
|
||||||
|
|
||||||
|
|
||||||
@app.route('/static/<path:path>')
|
@app.route('/static/<path:path>')
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
import builtins
|
import builtins
|
||||||
|
from datetime import datetime
|
||||||
from flask import Response
|
from flask import Response
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
# Chunks for crc 32 computation
|
||||||
|
CRC32_CHUNK_SIZE = 65_536
|
||||||
|
|
||||||
# 4MiB chunks
|
# 4MiB chunks
|
||||||
CHUNK_SIZE = 4_194_304
|
CHUNK_SIZE = 4_194_304
|
||||||
|
|
@ -12,20 +17,8 @@ SPACE = ord(' ')
|
||||||
# ASCII value for zero
|
# ASCII value for zero
|
||||||
ZERO = ord('0')
|
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 tar_header_chunk(filename: str, filepath: str) -> bytes:
|
||||||
def header_chunk(filename: str, filepath: str) -> bytes:
|
|
||||||
|
|
||||||
# Returns the octal representation without the initial
|
# Returns the octal representation without the initial
|
||||||
def oct(i: int) -> str:
|
def oct(i: int) -> str:
|
||||||
|
|
@ -71,17 +64,22 @@ def header_chunk(filename: str, filepath: str) -> bytes:
|
||||||
return bytes(buffer)
|
return bytes(buffer)
|
||||||
|
|
||||||
|
|
||||||
class TarSender:
|
class ArchiveSender:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.files: dict[str, str] = {}
|
self.files: dict[str, str] = {}
|
||||||
|
|
||||||
def add_file(self, filename: str, filepath: str):
|
def add_file(self, filename: str, filepath: str):
|
||||||
self.files[filename] = filepath
|
self.files[filename] = filepath
|
||||||
|
|
||||||
|
def response(self):
|
||||||
|
raise NotImplementedError("Abstract method")
|
||||||
|
|
||||||
|
|
||||||
|
class TarSender(ArchiveSender):
|
||||||
def response(self):
|
def response(self):
|
||||||
def generate():
|
def generate():
|
||||||
for name, file in self.files.items():
|
for name, file in self.files.items():
|
||||||
yield header_chunk(name, file)
|
yield tar_header_chunk(name, file)
|
||||||
|
|
||||||
bytes_sent = 0
|
bytes_sent = 0
|
||||||
|
|
||||||
|
|
@ -104,3 +102,66 @@ class TarSender:
|
||||||
mimetype='application/x-tar',
|
mimetype='application/x-tar',
|
||||||
headers={'Content-Disposition': 'attachment; filename="archive.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