Skip to main content

If you have ever used D3, you may have interacted with the PEQS file which contains the hold entries for print jobs.

Out of the box, jBASE stores its hold entries in a header/detail style:

  • The header can be found in jspool_log keyed by DEVJOBS*job
  • The detail is under the jobs directory and then under the relevant formqueue_n directory.

Both jspool_log and jobs are located under the directory defined by the environment variable JBCSPOOLERDIR.

Use jdiag if you're not sure where JBCSPOOLERDIR points.

At the end of this post is the source code for a jEDI driver that will put the above header/detail and make it appear like the PEQS file.

To use this (once it's compiled/cataloged):
create-file peqs type=object class=peqsjedi​

You should get the following output:

[ 417 ] File peqs]D created , type = OBJECT
[ 417 ] File peqs created , type = OBJECT

We can now list it:

$ LIST peqs

PAGE    1                                                                                                11:00:16  23 MAY 2022

 job #    Job Name..    Statuses    Copies    Q Num        Size    Date.....    Time....    Owner....

     1                  HS               1        0         307    23 MAY 22    10:52:41    pfalson
     2                  HS               1        0         399    23 MAY 22    10:52:52    pfalson
     3                  HS               1        0       57383    23 MAY 22    10:52:55    pfalson


 3 Records Listed

...and jstat it...

$ jstat peqs
File: ./peqs
Type: peqsjedi

Total jobs:  3
Total bytes: 58089

..and of course edit (or any program you wish to write opening the "peqs" file you create)...

$ ED peqs 1
1
TOP
.p
TOP
001  Port   PID    Account    Locale Location               Date       Time
002  1      67469  pfalson    C      Peter Falson           23 MAY 22  09:01:30
003 *2      78073  pfalson    en_US  Peter Falson           23 MAY 22  10:52:35
004  6000   76654  pfalson    en_US  Peter Falson           23 MAY 22  10:41:49
BOTTOM
.


And now - as promised - the source

    INCLUDE JBC.h
    DEFC VAR SpoolerFileDetails(INT)
    equ asterisk to '*'
!
! This is an example driver for the new jEDI type of JABBA OBJECT.
! It is based on the dynobject.jabba template under the samples directory.
!
! To use this. compile as follows
!
! BASIC . peqsjedi.jabba
! CATALOG . peqsjedi.jabba
!
! Recommended usage:
!
!     create-file fileName type=object class=peqsjedi
!
! This will create a stub file of name 'filename' in the current directory (and a fileName]D for the dictionary)
!
! In all the methods, the convention is that 'return 0' means the operation was successful
! and any other integer returned denotes some sort of error
!
!
! Method:: createfile(fileName , command)
!
! fileName: The name of the file we are creating
! command:  We add here any extra information which will be saved
!           in the stub file that the create-file creates.
!
! Return value: 0 shows we can allow the create-file to continue, and other is an error
!
    method peqsjedi::createfile(fileName , command )
!
! If this is the DICT portion just return
!
        this->original_name = fileName
        if index(fileName, ']D', 1) then
            this->dict = 1
            return 0
        end
!
! This is the DATA section but we really want a regular jBASE dictionary file so
! we'll remove the ]D stub and create a regular jBASE dictionary
!
        execute RM_CMD:' ':fileName:']D' capturing nada
        execute 'CREATE-FILE DICT ':fileName:' 1' capturing nada
!
! Populate the DICT with some useful dictionaries to use by jQL
!
        this->initDicts(fileName)
!
! Look for any additional qualifiers
!
! In this example we're not going to do anything but
! you could parse each command line in the "options" property
!
! the ->next method (below) assumes its parameters have
! been initialised (if we don't we get a compiler warning)
!
        keynxt = '' ; value = '' ; type = ''

        iter = new object('$iterator' , this->options)
        loop while iter->next(keynxt , value , type) gt 0 do
        repeat

        return 0
    end method

    method peqsjedi::peqsjedi()
!
! For this jEDI we're allowing debugging (just in case) and/or
! tracing. In this case the value of the environment variable
! PEQSVERBOSE controls what type.
!
        if not(getenv('PEQSVERBOSE',Verbose)) then Verbose = 0
        if Verbose > 1 then
            if Verbose = 9 then debug
        end
        this->openSpooler()
        this->Verbose = Verbose
    end method

    method peqsjedi::open()
!
! We need to open the current directory because
! during the create-file process a Q pointer is
! written out which we need to detect in the read method
!
        open '.' to fileVar else
            return 1 ;! should never happen but stranger things....
        end
        this->fileVar = fileVar
        this->openSpooler()
        return 0    ;! Return with a good return code
    end method

    method peqsjedi::close()
!
! The close method has nothing to do in this case
!
        return
    end method

!
! read an item. Parameters are:
! record:     This is where we return any data
! job:        The item id to read
! flags:      A string of one or more characters as follows
!                 L       A lock is to be taken.
!                 W       A lock is to be taken, but do not wait.
!                 R       A lock is to be taken, but a read lock only
!                 U       Unlock the lock (for WRITE's only)
!                 S       No lock, just return the status of the lock.
!                 A       The unlock applies to ALL record keys
!                 F       Field read/write, see 'fieldnbr' below
!                         This is normally taken care of by the jEDI interface
!                         but you can over-ride this behaviour with 'field_reads'
!                         setting to 1 in the open method.
!            NOTE:  In the 'open' we leave 'driver_locks' at 0, which
!                   means jEDI itself does the record locking, not us, so
!                   the above mentioned flags aren't used by this driver.
!
! fieldnbr:   For field reads (e.g. readv) the attribute number if 'F'
!               exists in the 'flags' string above.
!
! The value to return is as follows.
!
! 0          The record was read in and is okay, locks taken (if necessary)
! 1          The record does not exist.
! 2          The record is locked and we are returning because 'W' was in the  'flags'
!
    method peqsjedi::read(record, job, flags, fieldnbr)
        if job matches "1N0N" then
            rc = this->parseIncomingJob(job, @false)
        end else rc = 0

        if this->pathFound then
            f.queue = this->fqueue
            id = this->jobId
        end else
!
! We're probably in the middle of the CREATE-FILE
!
            f.queue = this->fileVar
            id = job
        end

        read record from f.queue,id else
            rc = 1
            record = ''
        end

        return rc
    end method


    method peqsjedi::write(record, job, flags, fieldnbr)
        if job matches "1N0N" then
            rc = this->parseIncomingJob(job, @false)
            if this->pathFound then
                f.queue = this->fqueue
                Verbose = this->Verbose
                if Verbose then crt 'Write job ':job:' back to ':this->filePath
                write record on f.queue,job
            end
            return rc
        end
        write record on this->fileVar,job on error
            return 2
        end
        return 0
    end method

!
! Delete an item from the file.
!
! job:   The item id to delete
! flags: Not used
!
! Return:   0 shows the delete worked, else an unspecified error
!
    method peqsjedi::delete(job , flags)
        if job matches "1N0N" then
            execute 'sp-delete ':job capturing io
            if len(io) then
                print
                print io
                print
                return 1
            end
        end else
            delete this->fileVar, job
        end
        return 0
    end method

    method peqsjedi::select(selectVar)
!
! Start off a SELECT of all the DEVJOBS* items
!
        selCmd = 'SELECT ':this->LogPath
        selCmd := ' WITH *A0 = "DEVJOBS*]"'

        execute selCmd capturing Nada

        selectVar = ''
!
! We only want the job nbrs which is the other part
! of our DEVJOBS* item id
!
        loop while readnext id do selectVar<-1> = field(id, asterisk, 2) repeat

        return 0
    end method

    method peqsjedi::selectend(selectVar)
        return 0
    end method


    method peqsjedi::readnext(job, selectVar)
!
! read in the next item from the select list.
!
! Return: 0 if we have an item, 1 if end of list, any other is an error
!
        readnext job from selectVar else
            return 1          ;! EOF
        end
        return 0
    end method

    method peqsjedi::clearfile()
!
! CLEARFILE on the file
!
        crt
        crt 'Operation not supported'
        crt
        rqm
        return 0
    end method

    method peqsjedi::deletefile(flags)
!
! DELETEFILE on the file
!
! flags: Not really used
!
        execute RM_CMD:' ':this->path_name
        return 0
    end method

    method peqsjedi::sync()
!
! SYNC. This means that all data that might be in cache, waiting to
! go to a network, not gone to disc etc., should be flushed now.
!
! This is not relevant for this example
!
        return 0
    end method

!
! This method is called on "jstat"
!
    method peqsjedi::stats(options)
!
! Whatever we want here.
! "options" are the command line arguments
! We could get creative...but not today :-)
! For now let's just give the total jobs and byte count
!
        totalBytes = 0
        totalJobs = 0

        selectVar = ''
        this->select(selectVar)
        job = ''
        loop
            rc = this->readnext(job, selectVar)
        while rc eq 0 do
            ++totalJobs
            this->parseIncomingJob(job, @true)
            totalBytes += this->initial_size
        repeat

        print "File: ":this->path_name
        print "Type: peqsjedi"
        print
        print "Total jobs:  ":totalJobs
        print "Total bytes: ":totalBytes

        return 0
    end method

    method peqsjedi::ioctl(command , data_in , data_out)
!
! An ioctl() function call has been used.
! See the file JBC.h for all the JIOCTL_xxx values you might want to handle.
!
        rc  = 1     ;! Default to an unknown command
        begin case
!        case command eq JIOCTL_COMMAND_....
!
!            rc = 0
        end case
        return rc
    end method

!
! Lock: Perform locking on this file.
!
! Usually, this won't get called because in the ::open() method
! we leave 'driver_locks' set to 0. This means normally that
! we let jBASE handle the record locks.
!
! However, other drivers may well use the external database locking,
! and so we will get calls here.
! The 'flags' variable is a number of characters to describe the actions
! and is one or more of the following:
!
! 'L'     Take a lock on the item id in the 'itemid' parameter.
! 'U'     Unlock a record based on the item id in the 'itemid' parameter.
! 'A'     Unlock ALL records in the file and ignore the 'itemid' parameter.
!
! Note that the 'flags' might contain both 'U' and 'A', and 'A' has precedence.
!
! So, in normal working, this method won't get called,
! unless if you set the following in the ::open() method
!
! this->driver_locks = 1
!
! then this method will be called. Good for testing!
!
    method peqsjedi::lock(job , flags)
        begin case
            case index(flags,'A',1)
                crt 'UNLOCK ALL records'
            case index(flags,'U',1)
                crt 'UNLOCK item id ':job
            case index(flags,'L',1)
                crt 'LOCK item id ':job
        end case
        return 0
    end method

!
! Internal method used throughout to open the jspool_log file
!
    method peqsjedi::openSpooler()
        if not(this->$hasproperty('fspool')) then
            open SpoolerFileDetails(2) to f.spool else
                stop 201,SpoolerFileDetails(2)
            end
!
! Get the full path of this file into LogPath
!
            LogPath = ''
            rc = ioctl(f.spool, JBC_COMMAND_GETFILENAME, LogPath)
!
! Now store these vars in our class
!
            this->fspool = f.spool
            this->LogPath = LogPath
        end
    end method

!
! This method is called during create-file to populate the ]D (dict)
! with some useful dictionaries for jQL
!
    method peqsjedi::initDicts(fileName)
        open 'DICT',fileName to f.dict then
            read hasAT from f.dict,'@' else
                this->makeDicts(f.dict)
            end
            close f.dict
        end
        return 0
    end method

    method peqsjedi::makeDicts(f.dict)
        this->openSpooler()
        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Account'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;21;21)'
        r.dict<9> = 'L'
        r.dict<10> = 10
        write r.dict on f.dict,'JACCOUNT'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Copies'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;3;3)'
        r.dict<9> = 'R'
        r.dict<10> = 6
        write r.dict on f.dict,'JCOPIES'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Date'
        r.dict<7> = 'D2'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;10;10)'
        r.dict<9> = 'R'
        r.dict<10> = 9
        write r.dict on f.dict,'JDATE'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = ' job #'
        r.dict<9> = 'R'
        r.dict<10> = 6
        write r.dict on f.dict,'JNUM'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Options'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;8;8)'
        r.dict<9> = 'L'
        r.dict<10> = 4
        write r.dict on f.dict,'JOPTIONS'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = ' Port #'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;7;7)'
        r.dict<9> = 'R'
        r.dict<10> = 7
        write r.dict on f.dict,'JPORT'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Curr Pos'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;5;5)'
        r.dict<9> = 'R'
        r.dict<10> = 8
        write r.dict on f.dict,'JPOS'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Q Num'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;1;1)'
        r.dict<9> = 'R'
        r.dict<10> = 5
        write r.dict on f.dict,'JQNUM'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Report'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;13;13)'
        r.dict<9> = 'R'
        r.dict<10> = 9
        write r.dict on f.dict,'JREPORT'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Job Security'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;16;16)'
        r.dict<9> = 'L'
        r.dict<10> = 10
        write r.dict on f.dict,'JSECURITY'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = '    Size'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;4;4)'
        r.dict<9> = 'R'
        r.dict<10> = 8
        write r.dict on f.dict,'JSIZE'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Stat'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;2;2)'
        r.dict<9> = 'R'
        r.dict<10> = 4
        write r.dict on f.dict,'JSTATUS'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Job Name'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;19;19)'
        r.dict<9> = 'L'
        r.dict<10> = 10
        write r.dict on f.dict,'JNAME'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Statuses'
        r.dict<8> = 'A;if N(JSTATUS) = "1" then "QUEUED" else if N(JSTATUS) = "2"  then "UNKNOWN(2)" else if N(JSTATUS) = "3" then "UNKNOWN3" else if N(JSTATUS) = "4" then "OPEN" else if N(JSTATUS) = "5" then "HS" else N(JSTATUS)'
        r.dict<9> = 'L'
        r.dict<10> = 8
        write r.dict on f.dict,'JSTATUSES'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Time'
        r.dict<7> = 'MTS'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;9;9)'
        r.dict<9> = 'R'
        r.dict<10> = 8
        write r.dict on f.dict,'JTIME'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Owner'
        r.dict<8> = 'F;CDEVJOBS*;0;:;(T':this->LogPath:';X;6;6)'
        r.dict<9> = 'L'
        r.dict<10> = 9
        write r.dict on f.dict,'JUSERNAME'

        r.dict = 'A' ; r.dict<2> = '0'
        r.dict<3> = 'Peqs #'
        r.dict<9> = 'R'
        r.dict<10> = 6
        write r.dict on f.dict,'PEQS'
!
! Write out the default listing PHrase
!
        r.dict = 'PH'
        r.dict<2> = ' BY JNUM JNUM JNAME JSTATUSES JCOPIES JQNUM JSIZE JDATE JTIME JUSERNAME id-SUPP'
        write r.dict on f.dict,'@'
        return 0
    end method

!
! Given a job nbr, this method sets various properties
! in our "this" object
!
    method peqsjedi::parseIncomingJob(job, detail_required)
        pathFound = @false
        k.log = 'DEVJOBS*':job
        f.spool = this->fspool
        read job_detail from f.spool,k.log else
            return 1
        end
        fullPath = job_detail<22>
        lastSlash = count(fullPath,dir_delim_ch)
        filePath = oconv(fullPath,'G0':dir_delim_ch:lastSlash)
        this->jobId = field(fullPath,dir_delim_ch,lastSlash+1)
        Verbose = this->Verbose
        if Verbose then
            crt 'FullPath = "':fullPath:'"'
            crt 'FilePath = "':filePath:'"'
            crt 'JobID    = "':this->jobId:'"'
        end
        open filePath to f.queue then pathFound = @true
        this->fqueue = f.queue
        this->pathFound = pathFound
        this->filePath = filePath

        if detail_required then
            this->form_queue_number = job_detail<1>
            this->status    = job_detail<2>
            this->copies    = job_detail<3>
            this->initial_size  = job_detail<4>
            this->output_pos   = job_detail<5>
            this->username   = job_detail<6>
            this->portno    = job_detail<7>
            this->assign_options  = job_detail<8>
            this->time    = job_detail<9>
            this->date    = job_detail<10>
            this->edit_portno   = job_detail<11>
            this->edit_pid   = job_detail<12>
            this->report_no   = job_detail<13>
            this->align_flag   = job_detail<14>
            this->background_pid  = job_detail<15>
            this->security_jobs  = job_detail<16>
            this->security_altowners = job_detail<17>
            this->username_real  = job_detail<18>
            this->hold_file_name  = job_detail<19>
            this->application_id  = job_detail<20>
            this->username_jbase  = job_detail<21>
            this->unix_filename  = job_detail<22>
            this->timestamp   = job_detail<23>
            this->savecopies   = job_detail<24>
            this->spoolgroup   = job_detail<25>
        end
        return 0
    end method


------------------------------
Peter Falson
Rocket Internal - All Brands
------------------------------