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
------------------------------