Wednesday, May 7, 2014

2 APIs to help with zip and unzipping files

qzipzip qzipunzip zip file

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:

3 comments:

  1. Manuel Antonio Ramirez RaygadaMay 10, 2014 at 8:13 PM

    Simon...ese fue un gran aporte para los que aun seguimos en la senda del AS/400 con RPG.

    ReplyDelete
    Replies
    1. Translation by Google Translate:

      "Simon ... that was a great addition for those who still we continue on the path of AS/400 RPG."

      Delete
  2. 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

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.