Website: http://technology.amis.nl/blog qui fournit un code permettant de créer un fichier zip contenant plusieurs fichiers dans un BLOB : Parsing a Microsoft Word docx, and unzip zipfiles, with PL/SQL
C'était une excellente base de départ, mais on ne pouvait que créer un fichier zip, puis le lire, mais pas moyen de maintenir.
1/ Analyse
Comprendre comment est généré un fichier zip, très simple en fait, chaque fichier (compressé) est concaténé au précédent, et tout à la fin, on met les infos globales du zip et de tous les fichiers.
Donc pour rajouter un fichier, il suffit de supprimer les infos globales, concaténer son fichier compressé et recréer les infos globales.
J'ai donc créé la procédure INIT_ZIP qui modifie le Blob Zippé en supprimant les infos globales [appelé Central Directory], après avoir mémorisé le commentaire.
2/ Débugages
a) Après chargement de mes zip créés sous linux (par un zip -j), j'avais un plantage lorsque je rajoutais des fichiers
Il s'avère que dans le zip créé sous Linux, l'extrafield est différent entre l'entête et le directory (8 bytes de plus)
Correction de FINISH_ZIP
b) J'ai eu un plantage en prod car un fichier faisait 0 octet.
Debug Fonction FILE2BLOB si la source est à 0 octet
3/ Cosmétique
Ajout d'un paramètre Compression (add1file),
Ajout des informations Date Maj, size_comp et size_uncomp dans File_List
Voici le script finalisé : [ATTACH]184650d1/a/a/a" />
4/ Etape finale : L'écran Forms afin de lister les fichiers zip, télécharger les fichiers compressés, ou les zip
Le seul problème que j'ai eu a été sur la gestion de la récupération de la liste du zip avec un filtre par nom directement depuis le package As_zip qui me plantait Forms.
J'ai donc créé une fonction de type pipelined et de faire un curseur pour renseigner le bloc liste
Code sql : | Sélectionner tout |
SELECT filename, datemaj, size_comp, size_uncomp FROM TABLE (F_LISTING_ZIP(:archives.nomdoc, :q1.nomfic))
Code sql : | Sélectionner tout |
1 2 | CREATE OR REPLACE TYPE TYP_REC_VARCHAR AS OBJECT (filename VARCHAR2(2000), datemaj DATE, size_comp NUMBER, size_uncomp NUMBER); CREATE OR REPLACE TYPE TYP_TAB_VARCHAR AS TABLE OF TYP_REC_VARCHAR; |
Code sql : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | FUNCTION F_LISTING_ZIP (p_doc IN VARCHAR2, p_nomfic IN VARCHAR2 DEFAULT NULL) RETURN TYP_TAB_VARCHAR PIPELINED AS zip_files as_zip.file_list; v_doc BLOB; -- p_doc : Doc Zip pour récupérer le blob -- p_nomfic : Afficher la liste des noms de fichier contenant p_nomfic BEGIN /* drop TYPE TYP_TAB_VARCHAR CREATE OR REPLACE TYPE TYP_REC_VARCHAR AS OBJECT (filename VARCHAR2(2000), datemaj DATE, size_comp NUMBER, size_uncomp NUMBER); CREATE OR REPLACE TYPE TYP_TAB_VARCHAR AS TABLE OF TYP_REC_VARCHAR; */ SELECT blob_zip INTO v_doc FROM ARCHIVE_FIC WHERE nomdoc = p_doc; zip_files := as_zip.get_file_list( v_doc); IF zip_files IS NOT NULL THEN FOR i IN zip_files.FIRST .. zip_files.LAST LOOP IF (p_nomfic IS NOT NULL AND zip_files(i).filename LIKE '%'|| p_nomfic ||'%') OR p_nomfic IS NULL THEN PIPE ROW( TYP_REC_VARCHAR (zip_files(i).filename, zip_files(i).datemaj, zip_files(i).size_comp, zip_files(i).size_uncomp)); END IF; END LOOP; END IF; RETURN; END; |
5/ Exemples :
zipper un le fichier toto.pdf (présent dans le directory MYDIR) dans le directory MYDIR et s'appelant toto.zip
Code sql : | Sélectionner tout |
1 2 3 4 5 6 7 | DECLARE v_zip BLOB; BEGIN AS_ZIP.Add1File(v_zip, 'toto.pdf', AS_ZIP.File2Blob( 'MYDIR', 'toto.pdf' ) ); AS_ZIP.Finish_Zip(v_zip); AS_ZIP.Save_Zip(v_zip, 'MYDIR', 'toto.zip' ); END; |
Rajout du fichier toto.txt à mon fichier toto.zip
Code sql : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | DECLARE v_zip BLOB; BEGIN v_zip := AS_ZIP.File2Blob( 'MYDIR', 'toto.zip' ); AS_ZIP.Init_Zip(v_zip); AS_ZIP.Add1File(v_zip, 'toto.txt', AS_ZIP.File2Blob( 'MYDIR', 'toto.txt' ) ); AS_ZIP.Finish_Zip(v_zip); AS_ZIP.Save_Zip(v_zip, 'MYDIR', 'toto.zip' ); END; |
Dézipper un fichier zip dans un directory Oracle (Attention, les noms de fichier peuvent contenir des répertoires, ce code ne le gère pas)
On affiche le nom de fichier, la date et les tailles.
Code sql : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | DECLARE v_zip BLOB; zip_files AS_ZIP.file_list; v_doc BLOB; BEGIN v_zip := AS_ZIP.File2Blob(p_dir =>'MYDIR', p_file_name => 'toto.zip'); zip_files := AS_ZIP.Get_File_List( v_zip ); FOR i IN zip_files.FIRST() .. zip_files.LAST LOOP dbms_output.put_line(DBMS_LOB.SUBSTR(zip_files(i).filename,2000,1) ||' '|| TO_CHAR(zip_files(i).datemaj,'DD/MM/RRRR HH24:MI:SS') ||' size:'|| zip_files(i).size_comp ||' / '|| zip_files(i).size_uncomp); v_doc := AS_ZIP.Get_File(v_zip, DBMS_LOB.SUBSTR(zip_files(i).filename,2000,1) ); AS_ZIP.Save_Zip(p_zipped_blob => v_doc, p_dir => 'MYDIR', p_filename => DBMS_LOB.SUBSTR(zip_files(i).filename,2000,1)); END LOOP; END; |
Toutes les lignes de chargement de fichier zip depuis le serveur [v_zip := AS_ZIP.File2Blob(p_dir =>'MYDIR', p_file_name => 'toto.zip');] peut bien sur être remplacé par un SELECT
Attention toutefois aux limitations sur les BLOB (il faut en faire une copie avant de l'utiliser, à moins de passer par un select for update, mais plus dangereux)
Code sql : | Sélectionner tout |
1 2 3 4 5 6 7 8 | dbms_lob.createtemporary( v_zip, TRUE ); SELECT BLOB_ZIP INTO v_lob FROM ARCHIVE_FIC WHERE NOMZIP = 'archive_2015'; DBMS_LOB.append(v_zip, v_lob); AS_ZIP.Init_Zip(v_zip); AS_ZIP.Add1File(v_zip, ....); AS_ZIP.Finish_Zip(v_zip); ... dbms_lob.freetemporary(v_zip); |