In November I discussed zipping files in the IFS using the Java Jar command in QShell, and Djurre Postma posted in the comments that two new APIs had been introduced in IBM i 7.1 to perform the same function without using the Qshell:
- QzipZip – to zip files and folders.
- QzipUnzip – to do the opposite and extract files and folders from a zipped file.
At first using them looks more complicated than using the Java Jar command. The documentation provided by IBM is for calling the APIs using C. But once I determined how to code the equivalent in "all free" RPGLE it was a lot easier than I first assumed.
The modules for these two APIs are in a service program, QSYS/QZIPUTIL. Rather than having to remember to bind the service program to the program I was creating I could place it in a binding directory and use the BNDDIR keyword in the Control options, the free format H-spec, to ensure that it would be included at compile time.
I am not going to go into what binding directories are in this post. But I will give the commands I used to create the binding directory and add the service program to it:
01 CRTBNDDIR BNDDIR(MYLIB/TESTRPG) TEXT('For pgm TESTRPG') 02 ADDBNDDIRE BNDDIR(MYLIB/TESTRPG) OBJ((QZIPUTIL)) 03 WRKBNDDIRE BNDDIR(MYLIB/TESTRPG) |
On line 1, above, I am creating the binding directory in my library, MYLIB.
On line 2 I am adding the QZIPUTIL service program to the binding directory.
You can then use the command on line 3 to see that QZIPUTIL has been added to the binding directory.
I can then use the BNDDIR keyword in the Control options (ctl-opt) to define the binding directory I just created.
01 ctl-opt dftactgrp(*no) bnddir('TESTRPG') ; |
Both of the APIs have five parameters:
QzipZip:
- File to zip details
- Zip file details
- Format name - ZIP00100 (ZIP-zero-zero-1-zero-zero)
- User’s zip options
- Error structure
QzipUnZip:
- Zip file details
- Folder to place unzipped files
- Format name - UNZIP100 (UNZIP1-zero-zero)
- User’s unzip options
- Error structure
All of these parameters are found in the source member QZIPUTIL in QSYSINC/QRPGLESRC. I am not going to list the contents of the member here, as you should have it on your IBM i.
You will notice that all of the data structures are defined with these three keywords:
- QUALIFIED - means that all the data structure’s subfields must be qualified by the data structure’s name. For example: Data_Structure.Sub_field.
- ALIGN - aligns float, integer and unsigned subfields.
- TEMPLATE - indicates that this data structure’s subfields cannot be used. Another data structure should be defined with the LIKEDS definition before the subfields can be used.
The prototype definitions for QzipZip and QzipUnzip are also included in this source member.
Within QZIPUTIL is a copy statement for the member SYSTYPES in QSYSINC/QRPGLESRC this is where the data structure Qlg_Path_Name_t is defined.
In my program I copy in the QZIPUTIL source member, see below, on line 2. And then define my data structures, lines 3-6, using the LIKEDS to the templates in the copied source member. As I have used the LIKEDS keyword the end-ds keyword is not needed to end the data structure definition.
02 /copy qsysinc/qrpglesrc,qziputil 03 dcl-ds File_Path likeds(Qlg_Path_Name_t) inz(*LIKEDS) ; 04 dcl-ds Archive_Path likeds(Qlg_Path_Name_t) inz(*LIKEDS) ; 05 dcl-ds Zip00100 likeds(Qzip_Zip_Options_ZIP00100_T) inz(*LIKEDS) ; 06 dcl-ds Unzip100 likeds(Qzip_Unzip_Options_UNZIP100_T) inz(*LIKEDS) ; |
The data structure Qlg_Path_Name_t, and by use of the LIKEDS so do File_Path and Archive_Path, contains the following fields:
Subfield | Attribute |
CCSID | Integer 10,0 |
Country_ID | Alphanumeric 2 |
Language_ID | Alphanumeric 3 |
Reserved | Alphanumeric 3 |
Path_Type | Unsigned integer 10,0 |
Path_Length | Integer 10,0 |
Path_name_delimiter | Alphanumeric 2 |
Reserved2 | Alphanumeric 10 |
Path_Name | Alphanumeric 32,767 |
Most of these subfields I do not need as I am going to use the system default, see below. The only fields I am going to give a non-default value to be the delimiter character, path name, and the length of the path name.
08 File_Path.CCSID = 0 ; 09 File_Path.Country_ID = *allx'00' ; 10 File_Path.Language_ID = *allx'00' ; 11 File_Path.Reserved = *allx'00' ; 12 File_Path.Path_Type = 0 ; 13 File_Path.Path_Name_Delimiter = '/' ; 14 File_Path.Reserved2 = *allx'00' ; 20 File_Path.Path_Name = '/MyFolder/testfile.csv' ; 21 File_Path.Path_Length = %len(%trimr(File_Path.Path_Name)) ; |
As the first part of the File_Path and Archive_Path data structures are the same I can initialize the Archive_Path subfields like this, see line 21:
15 Archive_Path = File_Path ; 22 Archive_Path.Path_Name = '/MyFolder/testfile.zip' ; 23 Archive_Path.Path_Length = %len(%trimr(Archive_Path.Path_Name)) ; |
Notice that I have used the %trimr built in function before the %len, this is to ensure that the value passed by the %len is truly the length of the path name and not 32,767.
As I am going to zip first then I need to fill the subfield in the Zip00100 data structure. The template Qzip_Zip_Options_ZIP00100_T data structure defines the following subfields:
Subfield | Attribute |
Verbose_option | Alphanumeric 10 |
Subtree_Option | Alphanumeric 6 |
Comment | Alphanumeric 512 |
Comment_Length | Unsigned integer 10,0 |
I am not going to use any of these options, therefore I define them as:
16 Zip00100.Verbose_Option = '*NONE' ; 17 Zip00100.Subtree_Option = '*NONE' ; 18 Zip00100.Comment = ' ' ; 19 Zip00100.Comment_Length = %len(Zip00100.Comment) ; |
If I wanted to save the “subtree" I would have used ‘*ALL’ to save and zip all of the subfolders and files within them.
Verbose is tricky as to use it, ’*VERBOSE’, means that the verbose messages are sent to the stdout, which the IBM i does not handle and it would have to be handled by the calling program.
I am not going to use a comment in the zipped file, therefore, I am blanking it, and the comment length will be zero.
The last parameter needed by QzipZip is the error data structure. This is the standard QUSEC data structure. This is copied into the program from the QUSEC member in QSYSINC/QRPGLESRC:
07 /copy qsysinc/qrpglesrc,qusec |
I explained how to use this data structure in the post QCAPCMD another alternative to QCMDEXC.
Now I can call QzipZip:
24 QzipZip(File_Path : Archive_Path : 'ZIP00100' : Zip00100 : QUSEC) ; 25 if (%subst(QUSEI:1:4) = 'CPFA') ; // From QUSEC |
Any errors are captured in the QUSEI subfield of the QUSEC data structure. All of the errors for these APIs all start ‘CPFA’.
Having zipped let me now unzip.
The unzip uses the Unzip100 data structure which contains the following fields:
Subfield | Attribute |
Verbose_Option | Alphanumeric 10 |
Replace_Option | Alphanumeric 6 |
Like I did before I do not want to be verbose, but I do want replace the existing file:
28 Unzip100.Verbose_Option = '*NONE' ; 29 Unzip100.Replace_Option = '*YES' ; |
The only other change I need to make is to the File_Path data structure. I am going to use it for the name of the folder to place the unzipped file. I have found that if I leave ‘/MyFolder’ in the path name a subfolder called ‘MyFolder’ within the folder ‘MyFolder’ is created. If I use a slash (/) then the file will be unzipped in the current folder.
30 File_Path.Path_Name = '/' ; 31 File_Path.Path_Length = %len(%trimr(File_Path.Path_Name)) ; |
Now I can call QzipUnzip:
32 QzipUnzip(Archive_Path : File_Path: 'UNZIP100' : Unzip100 : QUSEC) ; 33 if (%subst(QUSEI:1:4) = 'CPFA') ; // From QUSEC |
Below is the program in its entirety:
01 ctl-opt dftactgrp(*no) bnddir('TESTRPG') ; 02 /copy qsysinc/qrpglesrc,qziputil 03 dcl-ds File_Path likeds(Qlg_Path_Name_t) inz(*LIKEDS) ; 04 dcl-ds Archive_Path likeds(Qlg_Path_Name_t) inz(*LIKEDS) ; 05 dcl-ds Zip00100 likeds(Qzip_Zip_Options_ZIP00100_T) inz(*LIKEDS) ; 06 dcl-ds Unzip100 likeds(Qzip_Unzip_Options_UNZIP100_T) inz(*LIKEDS) ; 07 /copy qsysinc/qrpglesrc,qusec // Default values for Path name data structures 08 File_Path.CCSID = 0 ; 09 File_Path.Country_ID = *allx'00' ; 10 File_Path.Language_ID = *allx'00' ; 11 File_Path.Reserved = *allx'00' ; 12 File_Path.Path_Type = 0 ; 13 File_Path.Path_Name_Delimiter = '/' ; 14 File_Path.Reserved2 = *allx'00' ; 15 Archive_Path = File_Path ; // Default values for ZIP00100 data structure 16 Zip00100.Verbose_Option = '*NONE' ; 17 Zip00100.Subtree_Option = '*NONE' ; 18 Zip00100.Comment = ' ' ; 19 Zip00100.Comment_Length = %len(Zip00100.Comment) ; 20 File_Path.Path_Name = '/MyFolder/testfile.csv' ; 21 File_Path.Path_Length = %len(%trimr(File_Path.Path_Name)) ; 22 Archive_Path.Path_Name = '/MyFolder/testfile.zip' ; 23 Archive_Path.Path_Length = %len(%trimr(Archive_Path.Path_Name)) ; 24 QzipZip(File_Path : Archive_Path : 'ZIP00100' : Zip00100 : QUSEC) ; 25 if (%subst(QUSEI:1:4) = 'CPFA') ; // From QUSEC 26 dsply QUSEI ; 27 endif ; 28 Unzip100.Verbose_Option = '*NONE' ; 29 Unzip100.Replace_Option = '*YES' ; 30 File_Path.Path_Name = '/' ; 31 File_Path.Path_Length = %len(%trimr(File_Path.Path_Name)) ; 32 QzipUnzip(Archive_Path : File_Path: 'UNZIP100' : Unzip100 : QUSEC) ; 33 if (%subst(QUSEI:1:4) = 'CPFA') ; // From QUSEC 34 dsply QUSEI ; 35 endif ; 36 *inlr = *on ; |
I am sure you can come up with a better error handling process than using the DSPLY operation as I have done in this example.
For those of you who cannot use the "all free" RPGLE this is how the definition area of the program would be coded in fixed format:
01 H dftactgrp(*no) bnddir('TESTRPG') 02 /copy qsysinc/qrpglesrc,qziputil 03 D File_Path DS likeds(Qlg_Path_Name_t) 04 D inz(*LIKEDS) 05 D Archive_Path DS likeds(Qlg_Path_Name_t) 06 D inz(*LIKEDS) 07 D Zip00100 DS likeds(Qzip_Zip_Options_ZIP00100_T) 08 D inz(*LIKEDS) 09 D Unzip100 DS likeds(Qzip_Unzip_Options_UNZIP100_T) 10 D inz(*LIKEDS) 11 /copy qsysinc/qrpglesrc,qusec |
You can learn more about these from the IBM web site:
Simon...ese fue un gran aporte para los que aun seguimos en la senda del AS/400 con RPG.
ReplyDeleteTranslation by Google Translate:
Delete"Simon ... that was a great addition for those who still we continue on the path of AS/400 RPG."
Dear Simon, i tried qzipunzip. It's work but into the extracted csv file i have some strange caracters so i can'tread this csv files... butbif i open csv file with Excel and save it..then the rpg program can read the content...i don't know why..
ReplyDelete