rda-python-common 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rda_python_common/PgCMD.py +603 -0
- rda_python_common/PgDBI.py +2306 -0
- rda_python_common/PgFile.py +3118 -0
- rda_python_common/PgLOG.py +1689 -0
- rda_python_common/PgLock.py +640 -0
- rda_python_common/PgOPT.py +1740 -0
- rda_python_common/PgSIG.py +1164 -0
- rda_python_common/PgSplit.py +299 -0
- rda_python_common/PgUtil.py +1854 -0
- rda_python_common/__init__.py +0 -0
- rda_python_common/pg_cmd.py +493 -0
- rda_python_common/pg_dbi.py +1885 -0
- rda_python_common/pg_file.py +2462 -0
- rda_python_common/pg_lock.py +533 -0
- rda_python_common/pg_log.py +1352 -0
- rda_python_common/pg_opt.py +1447 -0
- rda_python_common/pg_pass.py +92 -0
- rda_python_common/pg_sig.py +879 -0
- rda_python_common/pg_split.py +260 -0
- rda_python_common/pg_util.py +1534 -0
- rda_python_common/pgpassword.py +92 -0
- rda_python_common-2.0.0.dist-info/METADATA +20 -0
- rda_python_common-2.0.0.dist-info/RECORD +27 -0
- rda_python_common-2.0.0.dist-info/WHEEL +5 -0
- rda_python_common-2.0.0.dist-info/entry_points.txt +3 -0
- rda_python_common-2.0.0.dist-info/licenses/LICENSE +21 -0
- rda_python_common-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1447 @@
|
|
|
1
|
+
#
|
|
2
|
+
###############################################################################
|
|
3
|
+
#
|
|
4
|
+
# Title : pg_opt.py
|
|
5
|
+
#
|
|
6
|
+
# Author : Zaihua Ji, zji@ucar.edu
|
|
7
|
+
# Date : 08/26/2020
|
|
8
|
+
# 2025-01-10 transferred to package rda_python_common from
|
|
9
|
+
# https://github.com/NCAR/rda-shared-libraries.git
|
|
10
|
+
# 2025-12-01 convert to class PgOPT
|
|
11
|
+
# Purpose : python library module for holding global varaibles
|
|
12
|
+
# functions for processing options and other global functions
|
|
13
|
+
#
|
|
14
|
+
# Github : https://github.com/NCAR/rda-pyhon-common.git
|
|
15
|
+
#
|
|
16
|
+
###############################################################################
|
|
17
|
+
#
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import re
|
|
21
|
+
import time
|
|
22
|
+
from os import path as op
|
|
23
|
+
from .pg_file import PgFile
|
|
24
|
+
|
|
25
|
+
class PgOPT(PgFile):
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__() # initialize parent class
|
|
29
|
+
self.OUTPUT = None
|
|
30
|
+
self.CMDOPTS = {}
|
|
31
|
+
self.INOPTS = {}
|
|
32
|
+
# global variables are used by all applications and this package.
|
|
33
|
+
# they need be initialized in application specified packages
|
|
34
|
+
self.ALIAS = {}
|
|
35
|
+
self.TBLHASH = {}
|
|
36
|
+
###############################################################################
|
|
37
|
+
# valid options the first hash value: 0 means mode option, 1 means single-value
|
|
38
|
+
# option, 2 means multiple-value option, and >=4 means action option the second
|
|
39
|
+
# hash values are long option names, either hash keys (considered as short
|
|
40
|
+
# option names) or the associated long names can be used. All options, except for
|
|
41
|
+
# multi-line value ones, can be specified on command line, while single-value and
|
|
42
|
+
# multi-value options, except option -IM for input files, can also given in input
|
|
43
|
+
# files long value option names are used in output files all letters of option
|
|
44
|
+
# names are case insensitive.
|
|
45
|
+
#
|
|
46
|
+
# The third hash value define bit flags,
|
|
47
|
+
# For Action Options:
|
|
48
|
+
# -1 - VSN card actions
|
|
49
|
+
# >0 - setions
|
|
50
|
+
#
|
|
51
|
+
# For Mode Options:
|
|
52
|
+
# 1 - mode for archiving actions
|
|
53
|
+
# 2 - mode for set actions
|
|
54
|
+
#
|
|
55
|
+
# For Single-Value Info Options:
|
|
56
|
+
# 1(0x001) - auto set value
|
|
57
|
+
# 2(0x002) - manually set value
|
|
58
|
+
# 16(0x010) - convert to integer from commandline and input files, set to 0 if empty
|
|
59
|
+
# 32(0x020) - time field
|
|
60
|
+
# 128(0x080) - '' allowed for single letter value
|
|
61
|
+
# 256(0x100) - date field
|
|
62
|
+
#
|
|
63
|
+
# For Multi-Value Info Options:
|
|
64
|
+
# 1(0x001) - one for multiple
|
|
65
|
+
# 2(0x002) - auto-set,
|
|
66
|
+
# 4(0x004) - expanded from one
|
|
67
|
+
# 8(0x008) - validated
|
|
68
|
+
# 16(0x010) - convert to integer from commandline and input files, set to 0 if empty
|
|
69
|
+
# 32(0x020) - time field
|
|
70
|
+
# 64(0x040) - text field allowing multiple lines
|
|
71
|
+
# 128(0x080) - '' allowed for single letter value
|
|
72
|
+
# 256(0x100) - date field
|
|
73
|
+
#
|
|
74
|
+
# The fourth hash values defined retrictions for single letter values
|
|
75
|
+
###############################################################################
|
|
76
|
+
self.OPTS = {}
|
|
77
|
+
# global initial optional values
|
|
78
|
+
self.PGOPT = {
|
|
79
|
+
'ACTS' : 0, # carry current action bits
|
|
80
|
+
'UACTS' : 0, # carry dsarch skip check UD action bits
|
|
81
|
+
'CACT' : '', # current short action name
|
|
82
|
+
'IFCNT' : 0, # 1 to read a single Input File at a time
|
|
83
|
+
'ANAME' : '', # cache the application name if set
|
|
84
|
+
'TABLE' : '', # table name the action is on
|
|
85
|
+
'UID' : 0, # user.uid
|
|
86
|
+
'MSET' : 'SA', # Action for multiple sets
|
|
87
|
+
'WIDTH' : 128, # max column width
|
|
88
|
+
'TXTBIT' : 64, # text field bit (0x1000) allow multiple lines
|
|
89
|
+
'PEMAX' : 12, # max count of reuqest partition errors for auto reprocesses
|
|
90
|
+
'PTMAX' : 24, # max number of partitions for a single request
|
|
91
|
+
'REMAX' : 2, # max count of reuqest errors for auto reprocesses
|
|
92
|
+
'RSMAX' : 100, # max count of gatherxml with options -R -S
|
|
93
|
+
'RCNTL' : None, # placehold for a request control record
|
|
94
|
+
'dcm' : "dcm",
|
|
95
|
+
'sdp' : "sdp",
|
|
96
|
+
'rcm' : "rcm",
|
|
97
|
+
'scm' : "scm",
|
|
98
|
+
'wpg' : "",
|
|
99
|
+
'gatherxml' : "gatherxml",
|
|
100
|
+
'cosconvert' : "cosconvert",
|
|
101
|
+
'emllog' : self.LGWNEM,
|
|
102
|
+
'emlerr' : self.LOGERR|self.EMEROL,
|
|
103
|
+
'emerol' : self.LOGWRN|self.EMEROL,
|
|
104
|
+
'emlsum' : self.LOGWRN|self.EMLSUM,
|
|
105
|
+
'emlsep' : self.LGWNEM|self.SEPLIN,
|
|
106
|
+
'wrnlog' : self.LOGWRN,
|
|
107
|
+
'errlog' : self.LOGERR,
|
|
108
|
+
'extlog' : self.LGEREX,
|
|
109
|
+
'PTYPE' : "CPRV",
|
|
110
|
+
'WDTYP' : "ADNU",
|
|
111
|
+
'HFTYP' : "DS",
|
|
112
|
+
'SDTYP' : "PORWUV",
|
|
113
|
+
'GXTYP' : "DP"
|
|
114
|
+
}
|
|
115
|
+
# global default parameters
|
|
116
|
+
self.params = {
|
|
117
|
+
'ES' : "<=>",
|
|
118
|
+
'AO' : "<!>",
|
|
119
|
+
'DV' : "<:>"
|
|
120
|
+
}
|
|
121
|
+
self.WTYPE = {
|
|
122
|
+
'A' : "ARCO",
|
|
123
|
+
'D' : "DATA",
|
|
124
|
+
'N' : "NCAR",
|
|
125
|
+
'U' : "UNKNOWN",
|
|
126
|
+
}
|
|
127
|
+
self.HTYPE = {
|
|
128
|
+
'D' : "DOCUMENT",
|
|
129
|
+
'S' : "SOFTWARE",
|
|
130
|
+
'U' : "UNKNOWN"
|
|
131
|
+
}
|
|
132
|
+
self.HPATH = {
|
|
133
|
+
'D' : "docs",
|
|
134
|
+
'S' : "software",
|
|
135
|
+
'U' : "help"
|
|
136
|
+
}
|
|
137
|
+
self.MTYPE = {
|
|
138
|
+
'P' : "PRIMARY",
|
|
139
|
+
'A' : "ARCHIVING",
|
|
140
|
+
'V' : "VERSION",
|
|
141
|
+
'W' : "WORKING",
|
|
142
|
+
'R' : "ORIGINAL",
|
|
143
|
+
'B' : "BACKUP",
|
|
144
|
+
'O' : "OFFSITE",
|
|
145
|
+
'C' : "CHRONOPOLIS",
|
|
146
|
+
'U' : "UNKNOWN"
|
|
147
|
+
}
|
|
148
|
+
self.STYPE = {
|
|
149
|
+
'O' : "OFFLINE",
|
|
150
|
+
'P' : "PRIMARY",
|
|
151
|
+
'R' : "ORIGINAL",
|
|
152
|
+
'V' : "VERSION",
|
|
153
|
+
'W' : "WORKING",
|
|
154
|
+
'U' : "UNKNOWN"
|
|
155
|
+
}
|
|
156
|
+
self.BTYPE = {
|
|
157
|
+
'B' : "BACKUPONLY",
|
|
158
|
+
'D' : "BACKDRDATA",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# process and parsing input information
|
|
162
|
+
# aname - application name such as 'dsarch', 'dsupdt', and 'dsrqst'
|
|
163
|
+
def parsing_input(self, aname):
|
|
164
|
+
self.PGLOG['LOGFILE'] = aname + ".log"
|
|
165
|
+
self.PGOPT['ANAME'] = aname
|
|
166
|
+
self.dssdb_dbname()
|
|
167
|
+
argv = sys.argv[1:]
|
|
168
|
+
if not argv: self.show_usage(aname)
|
|
169
|
+
self.cmdlog("{} {}".format(aname, ' '.join(argv)))
|
|
170
|
+
# process command line options to fill option values
|
|
171
|
+
option = infile = None
|
|
172
|
+
needhelp = 0
|
|
173
|
+
helpopts = {}
|
|
174
|
+
for param in argv:
|
|
175
|
+
if re.match(r'^(-{0,2}help|-H)$', param, re.I):
|
|
176
|
+
if option: helpopts[option] = self.OPTS[option]
|
|
177
|
+
needhelp = 1
|
|
178
|
+
continue
|
|
179
|
+
ms = re.match(r'^-([a-zA-Z]\w*)$', param)
|
|
180
|
+
if ms: # option parameter
|
|
181
|
+
param = ms.group(1)
|
|
182
|
+
if option and not needhelp and option not in self.params:
|
|
183
|
+
val = self.get_default_info(option)
|
|
184
|
+
if val is not None:
|
|
185
|
+
self.set_option_value(option, val)
|
|
186
|
+
else:
|
|
187
|
+
self.parameter_error("-" + option, "missval")
|
|
188
|
+
option = self.get_option_key(param)
|
|
189
|
+
if needhelp:
|
|
190
|
+
helpopts[option] = self.OPTS[option]
|
|
191
|
+
break
|
|
192
|
+
# set mode/action options
|
|
193
|
+
if self.OPTS[option][0]&3 == 0: self.set_option_value(option)
|
|
194
|
+
elif option:
|
|
195
|
+
ms =re.match(r"^\'(.*)\'$", param)
|
|
196
|
+
if ms: param = ms.group(1)
|
|
197
|
+
self.set_option_value(option, param)
|
|
198
|
+
elif self.find_dataset_id(param):
|
|
199
|
+
self.set_option_value('DS', param)
|
|
200
|
+
else:
|
|
201
|
+
option = self.get_option_key(param, 3, 1)
|
|
202
|
+
if option:
|
|
203
|
+
self.set_option_value(option)
|
|
204
|
+
if needhelp:
|
|
205
|
+
helpopts[option] = self.OPTS[option]
|
|
206
|
+
break
|
|
207
|
+
elif op.exists(param): # assume input file
|
|
208
|
+
infile = param
|
|
209
|
+
else:
|
|
210
|
+
self.parameter_error(param)
|
|
211
|
+
if needhelp: self.show_usage(aname, helpopts)
|
|
212
|
+
if option and option not in self.params:
|
|
213
|
+
val = self.get_default_info(option)
|
|
214
|
+
if val is not None:
|
|
215
|
+
self.set_option_value(option, val)
|
|
216
|
+
else:
|
|
217
|
+
self.parameter_error("-" + option, "missval")
|
|
218
|
+
# check if only an input filename is given on command line following aname
|
|
219
|
+
if infile:
|
|
220
|
+
if 'IF' in self.params:
|
|
221
|
+
self.parameter_error(infile)
|
|
222
|
+
else:
|
|
223
|
+
self.params['IF'] = [infile]
|
|
224
|
+
# process given one or multiple input files to fill option values
|
|
225
|
+
if 'IF' in self.params:
|
|
226
|
+
self.PGOPT['IFCNT'] = 1 if self.PGOPT['CACT'] == 'AQ' else 0
|
|
227
|
+
if self.OPTS['DS'][0] == 1:
|
|
228
|
+
param = self.validate_infile_names(self.params['DS']) if 'DS' in self.params else 0
|
|
229
|
+
else:
|
|
230
|
+
param = 1
|
|
231
|
+
self.get_input_info(self.params['IF'])
|
|
232
|
+
if not param and 'DS' in self.params: self.validate_infile_names(self.params['DS'])
|
|
233
|
+
if not self.PGOPT['ACTS']: self.parameter_error(aname, "missact") # no action enter
|
|
234
|
+
if 'DB' in self.params:
|
|
235
|
+
dcnt = len(self.params['DB'])
|
|
236
|
+
for i in range(dcnt):
|
|
237
|
+
if i == 0:
|
|
238
|
+
self.PGLOG['DBGLEVEL'] = self.params['DB'][0]
|
|
239
|
+
elif i == 1:
|
|
240
|
+
self.PGLOG['DBGPATH'] = self.params['DB'][1]
|
|
241
|
+
elif i == 2:
|
|
242
|
+
self.PGLOG['DBGFILE'] = self.params['DB'][2]
|
|
243
|
+
self.pgdbg(self.PGLOG['DBGLEVEL'])
|
|
244
|
+
if 'GZ' in self.params: self.PGLOG['GMTZ'] = self.diffgmthour()
|
|
245
|
+
if 'BG' in self.params: self.PGLOG['BCKGRND'] = 1
|
|
246
|
+
|
|
247
|
+
# check and get default value for info option, return None if not available
|
|
248
|
+
def get_default_info(self, opt):
|
|
249
|
+
olist = self.OPTS[opt]
|
|
250
|
+
if olist[0]&3 and len(olist) > 3:
|
|
251
|
+
odval = olist[3]
|
|
252
|
+
if not odval or isinstance(odval, int):
|
|
253
|
+
return odval
|
|
254
|
+
else:
|
|
255
|
+
return odval[0] # return the first char of a default string
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
# set output file name handler now
|
|
259
|
+
def open_output(self, outfile = None):
|
|
260
|
+
if outfile: # result output file
|
|
261
|
+
try:
|
|
262
|
+
self.OUTPUT = open(outfile, 'w')
|
|
263
|
+
except Exception as e:
|
|
264
|
+
self.pglog("{}: Error open file to write - {}".format(outfile, str(e)), self.PGOPT['extlog'])
|
|
265
|
+
else: # result to STDOUT
|
|
266
|
+
self.OUTPUT = sys.stdout
|
|
267
|
+
|
|
268
|
+
# return 1 if valid infile names; sys.exit(1) otherwise
|
|
269
|
+
def validate_infile_names(self, dsid):
|
|
270
|
+
i = 0
|
|
271
|
+
for infile in self.params['IF']:
|
|
272
|
+
if not self.validate_one_infile(infile, dsid): return self.FAILURE
|
|
273
|
+
i += 1
|
|
274
|
+
if self.PGOPT['IFCNT'] and i >= self.PGOPT['IFCNT']: break
|
|
275
|
+
return i
|
|
276
|
+
|
|
277
|
+
# validate an input filename against dsid
|
|
278
|
+
def validate_one_infile(self, infile, dsid):
|
|
279
|
+
ndsid = self.find_dataset_id(infile)
|
|
280
|
+
if ndsid == None:
|
|
281
|
+
return self.pglog("{}: No dsid identified in Input file name {}!".format(dsid, infile), self.PGOPT['extlog'])
|
|
282
|
+
fdsid = self.format_dataset_id(ndsid)
|
|
283
|
+
if fdsid != dsid:
|
|
284
|
+
return self.pglog("{}: Different dsid {} found in Input file name {}!".format(dsid, fdsid, infile), self.PGOPT['extlog'])
|
|
285
|
+
return self.SUCCESS
|
|
286
|
+
|
|
287
|
+
# gather input information from input files
|
|
288
|
+
def get_input_info(self, infiles, table = None):
|
|
289
|
+
i = 0
|
|
290
|
+
for file in infiles:
|
|
291
|
+
i += self.process_infile(file, table)
|
|
292
|
+
if not self.PGOPT['IFCNT'] and self.PGOPT['CACT'] == 'AQ': self.PGOPT['IFCNT'] = 1
|
|
293
|
+
if self.PGOPT['IFCNT']: break
|
|
294
|
+
return i
|
|
295
|
+
|
|
296
|
+
# validate and get info from a single input file
|
|
297
|
+
def read_one_infile(self, infile):
|
|
298
|
+
dsid = self.params['DS']
|
|
299
|
+
del self.params['DS']
|
|
300
|
+
if self.OPTS['DS'][2]&2: self.OPTS['DS'][2] &= ~2
|
|
301
|
+
if 'DS' in self.CMDOPTS: del self.CMDOPTS['DS']
|
|
302
|
+
self.clean_input_values()
|
|
303
|
+
self.process_infile(infile)
|
|
304
|
+
if 'DS' in self.params: dsid = self.params['DS']
|
|
305
|
+
if dsid: self.validate_one_infile(infile, dsid)
|
|
306
|
+
return dsid
|
|
307
|
+
|
|
308
|
+
# gather input option values from one input file
|
|
309
|
+
# return 0 if nothing retireved if table is not null
|
|
310
|
+
def process_infile(self, infile, table = None):
|
|
311
|
+
if not op.exists(infile): self.pglog(infile + ": Input file not exists", self.PGOPT['extlog'])
|
|
312
|
+
if table:
|
|
313
|
+
self.pglog("Gather '{}' information from input file '{}'..." .format(table, infile), self.PGOPT['wrnlog'])
|
|
314
|
+
else:
|
|
315
|
+
self.pglog("Gather information from input file '{}'...".format(infile), self.PGOPT['wrnlog'])
|
|
316
|
+
try:
|
|
317
|
+
fd = open(infile, 'r')
|
|
318
|
+
except Exception as e:
|
|
319
|
+
self.pglog("{}: Error Open input file - {}!".format(infile, str(e)), self.PGOPT['extlog'])
|
|
320
|
+
else:
|
|
321
|
+
lines = fd.readlines()
|
|
322
|
+
fd.close()
|
|
323
|
+
opt = None
|
|
324
|
+
columns = []
|
|
325
|
+
chktbl = 1 if table else -1
|
|
326
|
+
mpes = r'^(\w+)\s*{}\s*(.*)$'.format(self.params['ES'])
|
|
327
|
+
mpao = r'^(\w+)\s*{}'.format(self.params['AO'])
|
|
328
|
+
# column count, column index, value count, value index, line index, option-set count, end divider flag
|
|
329
|
+
colcnt = colidx = valcnt = validx = linidx = setcnt = enddiv = 0
|
|
330
|
+
for line in lines:
|
|
331
|
+
linidx += 1
|
|
332
|
+
if linidx%50000 == 0:
|
|
333
|
+
self.pglog("{}: {} lines read".format(infile, linidx), self.PGOPT['wrnlog'])
|
|
334
|
+
if 'NT' not in self.params: line = self.pgtrim(line, 2)
|
|
335
|
+
if not line:
|
|
336
|
+
if opt: self.set_option_value(opt, '', 1, linidx, line, infile)
|
|
337
|
+
continue # skip empty lines
|
|
338
|
+
if chktbl > 0:
|
|
339
|
+
if re.match(r'^\[{}\]$'.format(table), line, re.I): # found entry for table
|
|
340
|
+
chktbl = 0
|
|
341
|
+
self.clean_input_values() # clean previously saved input values
|
|
342
|
+
continue
|
|
343
|
+
else:
|
|
344
|
+
ms = re.match(r'^\[(\w+)\]$', line)
|
|
345
|
+
if ms:
|
|
346
|
+
if chktbl == 0: break # stop at next sub-title
|
|
347
|
+
if not self.PGOPT['MSET']:
|
|
348
|
+
self.input_error(linidx, line, infile, ms.group(1) + ": Cannt process sub-title")
|
|
349
|
+
elif self.PGOPT['CACT'] != self.PGOPT['MSET']:
|
|
350
|
+
self.input_error(linidx, line, infile, "Use Action -{} to Set multiple sub-titles".format(self.PGOPT['MSET']))
|
|
351
|
+
break # stop getting info if no table given or a different table
|
|
352
|
+
if colcnt == 0: # check single value and action lines first
|
|
353
|
+
ms = re.match(mpes, line)
|
|
354
|
+
if ms: # one value assignment
|
|
355
|
+
key = ms.group(1).strip()
|
|
356
|
+
val = ms.group(2)
|
|
357
|
+
if val and 'NT' not in self.params: val = val.strip()
|
|
358
|
+
opt = self.get_option_key(key, 1, 0, linidx, line, infile, table)
|
|
359
|
+
self.set_option_value(opt, val, 0, linidx, line, infile)
|
|
360
|
+
if not self.OPTS[opt][2]&self.PGOPT['TXTBIT']: opt = None
|
|
361
|
+
setcnt += 1
|
|
362
|
+
continue
|
|
363
|
+
ms = re.match(mpao, line)
|
|
364
|
+
if ms: # set mode or action option
|
|
365
|
+
key = self.get_option_key(ms.group(1).strip(), 4, 0, linidx, line, infile, table)
|
|
366
|
+
self.set_option_value(key, '', 0, linidx, line, infile)
|
|
367
|
+
setcnt += 1
|
|
368
|
+
continue
|
|
369
|
+
# check mutiple value assignment for one or more multi-value options
|
|
370
|
+
values = line.split(self.params['DV'])
|
|
371
|
+
valcnt = len(values)
|
|
372
|
+
if colcnt == 0:
|
|
373
|
+
while colcnt < valcnt:
|
|
374
|
+
key = values[colcnt].strip()
|
|
375
|
+
if not key: break
|
|
376
|
+
opt = self.get_option_key(key, 2, 1, linidx, line, infile, table)
|
|
377
|
+
if not opt: break
|
|
378
|
+
columns.append(opt)
|
|
379
|
+
if opt in self.params: del self.params[opt]
|
|
380
|
+
colcnt += 1
|
|
381
|
+
if colcnt < valcnt:
|
|
382
|
+
if colcnt == (valcnt-1):
|
|
383
|
+
enddiv = 1
|
|
384
|
+
else:
|
|
385
|
+
self.input_error(linidx, line, infile, "Multi-value Option Name missed for column {}".format(colcnt+1))
|
|
386
|
+
opt = None
|
|
387
|
+
continue
|
|
388
|
+
elif valcnt == 1:
|
|
389
|
+
if re.match(mpes, line):
|
|
390
|
+
self.input_error(linidx, line, infile, "Cannot set single value option after Multi-value Options")
|
|
391
|
+
elif re.match(mpao, line):
|
|
392
|
+
self.input_error(linidx, line, infile, "Cannot set acttion/mode option after Multi-value Options")
|
|
393
|
+
if opt: # add to multipe line value
|
|
394
|
+
val = values.pop(0)
|
|
395
|
+
valcnt -= 1
|
|
396
|
+
if val and 'NT' not in self.params: val = val.strip()
|
|
397
|
+
self.set_option_value(opt, val, 1, linidx, line, infile)
|
|
398
|
+
setcnt += 1
|
|
399
|
+
if valcnt == 0: continue # continue to check multiple line value
|
|
400
|
+
colidx += 1
|
|
401
|
+
opt = None
|
|
402
|
+
reduced = 0
|
|
403
|
+
valcnt += colidx
|
|
404
|
+
if valcnt > colcnt:
|
|
405
|
+
if enddiv:
|
|
406
|
+
val = values.pop()
|
|
407
|
+
if not val.strip():
|
|
408
|
+
valcnt -= 1
|
|
409
|
+
reduced = 1
|
|
410
|
+
if valcnt > colcnt:
|
|
411
|
+
self.input_error(linidx, line, infile, "Too many values({}) provided for {} columns".format(valcnt+colidx, colcnt))
|
|
412
|
+
if values:
|
|
413
|
+
for val in values:
|
|
414
|
+
opt = columns[colidx]
|
|
415
|
+
colidx += 1
|
|
416
|
+
if val and 'NT' not in self.params: val = val.strip()
|
|
417
|
+
self.set_option_value(opt, val, 0, linidx, line, infile)
|
|
418
|
+
setcnt += 1
|
|
419
|
+
colidx += (reduced-enddiv)
|
|
420
|
+
if colidx == colcnt:
|
|
421
|
+
colidx = 0 # done with gathering values of a multi-value line
|
|
422
|
+
opt = None
|
|
423
|
+
elif opt and not self.OPTS[opt][2]&self.PGOPT['TXTBIT']:
|
|
424
|
+
colidx += 1
|
|
425
|
+
opt = None
|
|
426
|
+
if setcnt > 0:
|
|
427
|
+
if colidx:
|
|
428
|
+
if colidx < colcnt:
|
|
429
|
+
self.input_error(linidx, '', infile, "{} of {} values missed".format(colcnt-colidx, colcnt))
|
|
430
|
+
elif enddiv:
|
|
431
|
+
self.input_error(linidx, '', infile, "Miss end divider '{}'".format(self.params['DV']))
|
|
432
|
+
return 1 # read something
|
|
433
|
+
else:
|
|
434
|
+
if table: self.pglog("No option information found for '{}'".format(table), self.WARNLG)
|
|
435
|
+
return 0 # read nothing
|
|
436
|
+
|
|
437
|
+
# clean self.params for input option values when set mutiple tables
|
|
438
|
+
def clean_input_values(self):
|
|
439
|
+
# clean previously saved input values if any
|
|
440
|
+
for opt in self.INOPTS:
|
|
441
|
+
del self.params[opt]
|
|
442
|
+
self.INOPTS = {}
|
|
443
|
+
|
|
444
|
+
# build a hash record for add or update of a table record
|
|
445
|
+
def build_record(self, flds, pgrec, tname, idx = 0):
|
|
446
|
+
record = {}
|
|
447
|
+
if not flds: return record
|
|
448
|
+
hash = self.TBLHASH[tname]
|
|
449
|
+
for key in flds:
|
|
450
|
+
if key not in hash: continue
|
|
451
|
+
opt = hash[key][0]
|
|
452
|
+
field = hash[key][3] if len(hash[key]) == 4 else hash[key][1]
|
|
453
|
+
ms = re.search(r'\.(.+)$', field)
|
|
454
|
+
if ms: field = ms.group(1)
|
|
455
|
+
if opt in self.params:
|
|
456
|
+
if self.OPTS[opt][0] == 1:
|
|
457
|
+
val = self.params[opt]
|
|
458
|
+
else:
|
|
459
|
+
if self.OPTS[opt][2]&2 and pgrec and field in pgrec and pgrec[field]: continue
|
|
460
|
+
val = self.params[opt][idx]
|
|
461
|
+
sval = pgrec[field] if pgrec and field in pgrec else None
|
|
462
|
+
if sval is None:
|
|
463
|
+
if val == '': val = None
|
|
464
|
+
elif isinstance(sval, int):
|
|
465
|
+
if isinstance(val, str): val = (int(val) if val else None) # change '' to None for int
|
|
466
|
+
if self.pgcmp(sval, val, 1): record[field] = val # record new or changed value
|
|
467
|
+
return record
|
|
468
|
+
|
|
469
|
+
# set global variable self.PGOPT['UID'] with value of user.uid, fatal if unsuccessful
|
|
470
|
+
def set_uid(self, aname):
|
|
471
|
+
self.set_email_logact()
|
|
472
|
+
if 'LN' not in self.params:
|
|
473
|
+
self.params['LN'] = self.PGLOG['CURUID']
|
|
474
|
+
elif self.params['LN'] != self.PGLOG['CURUID']:
|
|
475
|
+
self.params['MD'] = 1 # make sure this set if running as another user
|
|
476
|
+
if 'NE' not in self.params: self.PGLOG['EMLADDR'] = self.params['LN']
|
|
477
|
+
if 'DM' in self.params and re.match(r'^(start|begin)$', self.params['DM'], re.I):
|
|
478
|
+
msg = "'{}' must start Daemon '{} -{}' as '{}'".format(self.PGLOG['CURUID'], aname, self.PGOPT['CACT'], self.params['LN'])
|
|
479
|
+
else:
|
|
480
|
+
msg = "'{}' runs '{} -{}' as '{}'!".format(self.PGLOG['CURUID'], aname, self.PGOPT['CACT'], self.params['LN'])
|
|
481
|
+
self.pglog(msg, self.PGOPT['wrnlog'])
|
|
482
|
+
self.set_specialist_environments(self.params['LN'])
|
|
483
|
+
if 'LN' not in self.params: self.pglog("Could not get user login name", self.PGOPT['extlog'])
|
|
484
|
+
self.validate_dataset()
|
|
485
|
+
if self.OPTS[self.PGOPT['CACT']][2] > 0: self.validate_dsowner(aname)
|
|
486
|
+
pgrec = self.pgget("dssdb.user", "uid", "logname = '{}' AND until_date IS NULL".format(self.params['LN']), self.PGOPT['extlog'])
|
|
487
|
+
if not pgrec: self.pglog("Could not get user.uid for " + self.params['LN'], self.PGOPT['extlog'])
|
|
488
|
+
self.PGOPT['UID'] = pgrec['uid']
|
|
489
|
+
self.open_output(self.params['OF'] if 'OF' in self.params else None)
|
|
490
|
+
|
|
491
|
+
# set global variable self.PGOPT['UID'] as 0 for a sudo user
|
|
492
|
+
def set_sudo_uid(self, aname, uid):
|
|
493
|
+
self.set_email_logact()
|
|
494
|
+
if self.PGLOG['CURUID'] != uid:
|
|
495
|
+
if 'DM' in self.params and re.match(r'^(start|begin)$', self.params['DM'], re.I):
|
|
496
|
+
msg = "'{}': must start Daemon '{} -{} as '{}'".format(self.PGLOG['CURUID'], aname, self.params['CACT'], uid)
|
|
497
|
+
else:
|
|
498
|
+
msg = "'{}': must run '{} -{}' as '{}'".format(self.PGLOG['CURUID'], aname, self.params['CACT'], uid)
|
|
499
|
+
self.pglog(msg, self.PGOPT['extlog'])
|
|
500
|
+
self.PGOPT['UID'] = 0
|
|
501
|
+
self.params['LN'] = self.PGLOG['CURUID']
|
|
502
|
+
|
|
503
|
+
# set global variable self.PGOPT['UID'] as 0 for root user
|
|
504
|
+
def set_root_uid(self, aname):
|
|
505
|
+
self.set_email_logact()
|
|
506
|
+
if self.PGLOG['CURUID'] != "root":
|
|
507
|
+
if 'DM' in self.params and re.match(r'^(start|begin)$', self.params['DM'], re.I):
|
|
508
|
+
msg = "'{}': you must start Daemon '{} -{} as 'root'".format(self.PGLOG['CURUID'], aname, self.params['CACT'])
|
|
509
|
+
else:
|
|
510
|
+
msg = "'{}': you must run '{} -{}' as 'root'".format(self.PGLOG['CURUID'], aname, self.params['CACT'])
|
|
511
|
+
self.pglog(msg, self.PGOPT['extlog'])
|
|
512
|
+
self.PGOPT['UID'] = 0
|
|
513
|
+
self.params['LN'] = self.PGLOG['CURUID']
|
|
514
|
+
|
|
515
|
+
# set email logging bits
|
|
516
|
+
def set_email_logact(self):
|
|
517
|
+
if 'NE' in self.params:
|
|
518
|
+
self.PGLOG['LOGMASK'] &= ~self.EMLALL # remove all email bits
|
|
519
|
+
elif 'SE' in self.params:
|
|
520
|
+
self.PGLOG['LOGMASK'] &= ~self.EMLLOG # no normal email
|
|
521
|
+
|
|
522
|
+
# validate dataset owner
|
|
523
|
+
# return: 0 or fatal if not valid, 1 if valid, -1 if can not be validated
|
|
524
|
+
def validate_dsowner(self, aname, dsid = None, logname = None, pgds = 0, logact = 0):
|
|
525
|
+
if not logname: logname = (self.params['LN'] if 'LN' in self.params else self.PGLOG['CURUID'])
|
|
526
|
+
if logname == self.PGLOG['GDEXUSER']: return 1
|
|
527
|
+
dsids = {}
|
|
528
|
+
if dsid:
|
|
529
|
+
dsids[dsid] = 1
|
|
530
|
+
elif 'DS' in self.params:
|
|
531
|
+
if self.OPTS['DS'][0] == 2:
|
|
532
|
+
for dsid in self.params['DS']:
|
|
533
|
+
dsids[dsid] = 1
|
|
534
|
+
else:
|
|
535
|
+
dsids[self.params['DS']] = 1
|
|
536
|
+
else:
|
|
537
|
+
return -1
|
|
538
|
+
if not pgds and 'MD' in self.params: pgds = 1
|
|
539
|
+
if not logact: logact = self.PGOPT['extlog']
|
|
540
|
+
for dsid in dsids:
|
|
541
|
+
if not self.pgget("dsowner", "", "dsid = '{}' AND specialist = '{}'".format(dsid, logname), self.PGOPT['extlog']):
|
|
542
|
+
if not self.pgget("dssgrp", "", "logname = '{}'".format(logname), self.PGOPT['extlog']):
|
|
543
|
+
return self.pglog("'{}' is not DSS Specialist!".format(logname), logact)
|
|
544
|
+
elif not pgds:
|
|
545
|
+
return self.pglog("'{}' not listed as Specialist of '{}'\nRun '{}' with Option -MD!".format(logname, dsid, aname), logact)
|
|
546
|
+
return 1
|
|
547
|
+
|
|
548
|
+
# validate dataset
|
|
549
|
+
def validate_dataset(self):
|
|
550
|
+
cnt = 1
|
|
551
|
+
if 'DS' in self.params:
|
|
552
|
+
if self.OPTS['DS'][0] == 2:
|
|
553
|
+
for dsid in self.params['DS']:
|
|
554
|
+
cnt = self.pgget("dataset", "", "dsid = '{}'".format(dsid), self.PGOPT['extlog'])
|
|
555
|
+
if cnt == 0: break
|
|
556
|
+
else:
|
|
557
|
+
dsid = self.params['DS']
|
|
558
|
+
cnt = self.pgget("dataset", "", "dsid = '{}'".format(dsid), self.PGOPT['extlog'])
|
|
559
|
+
if not cnt: self.pglog(dsid + " not exists in RDADB!", self.PGOPT['extlog'])
|
|
560
|
+
|
|
561
|
+
# validate given group indices or group names
|
|
562
|
+
def validate_groups(self, parent = 0):
|
|
563
|
+
if parent:
|
|
564
|
+
gi = 'PI'
|
|
565
|
+
gn = 'PN'
|
|
566
|
+
else:
|
|
567
|
+
gi = 'GI'
|
|
568
|
+
gn = 'GN'
|
|
569
|
+
if (self.OPTS[gi][2]&8): return # already validated
|
|
570
|
+
dcnd = "dsid = '{}'".format(self.params['DS'])
|
|
571
|
+
if gi in self.params:
|
|
572
|
+
grpcnt = len(self.params[gi])
|
|
573
|
+
i = 0
|
|
574
|
+
while i < grpcnt:
|
|
575
|
+
gidx = self.params[gi][i]
|
|
576
|
+
if not isinstance(gidx, int) and re.match(r'^(!|<|>|<>)$', gidx): break
|
|
577
|
+
i += 1
|
|
578
|
+
if i >= grpcnt: # normal group index given
|
|
579
|
+
for i in range(grpcnt):
|
|
580
|
+
gidx = self.params[gi][i]
|
|
581
|
+
gidx = int(gidx) if gidx else 0
|
|
582
|
+
self.params[gi][i] = gidx
|
|
583
|
+
if gidx == 0 or (i > 0 and gidx == self.params[gi][i-1]): continue
|
|
584
|
+
if not self.pgget("dsgroup", '', "{} AND gindex = {}".format(dcnd, gidx), self.PGOPT['extlog']):
|
|
585
|
+
if i > 0 and parent and self.params['GI']:
|
|
586
|
+
j = 0
|
|
587
|
+
while j < i:
|
|
588
|
+
if gidx == self.params['GI'][j]: break
|
|
589
|
+
j += 1
|
|
590
|
+
if j < i: continue
|
|
591
|
+
self.pglog("Group Index {} not in RDADB for {}".format(gidx, self.params['DS']), self.PGOPT['extlog'])
|
|
592
|
+
else: # found none-equal condition sign
|
|
593
|
+
pgrec = self.pgmget("dsgroup", "DISTINCT gindex", dcnd + self.get_field_condition("gindex", self.params[gi]), self.PGOPT['extlog'])
|
|
594
|
+
grpcnt = (len(pgrec['gindex']) if pgrec else 0)
|
|
595
|
+
if grpcnt == 0:
|
|
596
|
+
self.pglog("No Group matches given Group Index condition for " + self.params['DS'], self.PGOPT['extlog'])
|
|
597
|
+
self.params[gi] = pgrec['gindex']
|
|
598
|
+
elif gn in self.params:
|
|
599
|
+
self.params[gi] = self.group_id_to_index(self.params[gn])
|
|
600
|
+
self.OPTS[gi][2] |= 8 # set validated flag
|
|
601
|
+
|
|
602
|
+
# get group index array from given group IDs
|
|
603
|
+
def group_id_to_index(self, grpids):
|
|
604
|
+
count = len(grpids) if grpids else 0
|
|
605
|
+
if count == 0: return None
|
|
606
|
+
indices = []
|
|
607
|
+
dcnd = "dsid = '{}'".format(self.params['DS'])
|
|
608
|
+
i = 0
|
|
609
|
+
while i < count:
|
|
610
|
+
gid = grpids[i]
|
|
611
|
+
if gid and (re.match(r'^(!|<|>|<>)$', gid) or gid.find('%') > -1): break
|
|
612
|
+
i += 1
|
|
613
|
+
if i >= count: # normal group id given
|
|
614
|
+
for i in range(count):
|
|
615
|
+
gid = grpids[i]
|
|
616
|
+
if not gid:
|
|
617
|
+
indices.append(0)
|
|
618
|
+
elif i and gid == grpids[i-1]:
|
|
619
|
+
indices.append(indices[i-1])
|
|
620
|
+
else:
|
|
621
|
+
pgrec = self.pgget("dsgroup", "gindex", "{} AND grpid = '{}'".format(dcnd, gid), self.PGOPT['extlog'])
|
|
622
|
+
if not pgrec: self.pglog("Group ID {} not in RDADB for {}".format(gid, self.params['DS']), self.PGOPT['extlog'])
|
|
623
|
+
indices.append(pgrec['gindex'])
|
|
624
|
+
return indices
|
|
625
|
+
else: # found wildcard and/or none-equal condition sign
|
|
626
|
+
pgrec = self.pgmget("dsgroup", "DISTINCT gindex", dcnd + self.get_field_condition("grpid", grpids, 1), self.PGOPT['extlog'])
|
|
627
|
+
count = (len(pgrec['gindex']) if pgrec else 0)
|
|
628
|
+
if count == 0: self.pglog("No Group matches given Group ID condition for " + self.params['DS'], self.PGOPT['extlog'])
|
|
629
|
+
return pgrec['gindex']
|
|
630
|
+
|
|
631
|
+
# get group ID array from given group indices
|
|
632
|
+
def group_index_to_id(self, indices):
|
|
633
|
+
count = len(indices) if indices else 0
|
|
634
|
+
if count == 0: return None
|
|
635
|
+
grpids = []
|
|
636
|
+
dcnd = "dsid = '{}'".format(self.params['DS'])
|
|
637
|
+
i = 0
|
|
638
|
+
while i < count:
|
|
639
|
+
gidx = indices[i]
|
|
640
|
+
if not isinstance(gidx, int) and re.match(r'^(!|<|>|<>)$', gidx): break
|
|
641
|
+
i += 1
|
|
642
|
+
if i >= count: # normal group index given
|
|
643
|
+
for i in range(count):
|
|
644
|
+
gidx = indices[i]
|
|
645
|
+
if not gidx:
|
|
646
|
+
grpids.append('') # default value
|
|
647
|
+
elif i and gidx == indices[i-1]:
|
|
648
|
+
grpids.append(grpids[i-1])
|
|
649
|
+
else:
|
|
650
|
+
pgrec = self.pgget("dsgroup", "grpid", "{} AND gindex = {}".format(dcnd, gidx), self.PGOPT['extlog'])
|
|
651
|
+
if not pgrec: self.pglog("Group Index {} not in RDADB for {}".format(gidx, self.params['DS']), self.PGOPT['extlog'])
|
|
652
|
+
grpids.append(pgrec['grpid'])
|
|
653
|
+
return grpids
|
|
654
|
+
else: # found none-equal condition sign
|
|
655
|
+
pgrec = self.pgmget("dsgroup", "DISTINCT grpid", dcnd + self.get_field_condition("gindex", indices), self.PGOPT['extlog'])
|
|
656
|
+
count = (len(pgrec['grpid']) if pgrec else 0)
|
|
657
|
+
if count == 0: self.pglog("No Group matches given Group Index condition for " + self.params['DS'], self.PGOPT['extlog'])
|
|
658
|
+
return pgrec['grpid']
|
|
659
|
+
|
|
660
|
+
# validate order fields and
|
|
661
|
+
# get an array of order fields that are not in given fields
|
|
662
|
+
def append_order_fields(self, oflds, flds, tname, excludes = None):
|
|
663
|
+
orders = ''
|
|
664
|
+
hash = self.TBLHASH[tname]
|
|
665
|
+
for ofld in oflds:
|
|
666
|
+
ufld = ofld.upper()
|
|
667
|
+
if ufld not in hash or excludes and excludes.find(ufld) > -1: continue
|
|
668
|
+
if flds and flds.find(ufld) > -1: continue
|
|
669
|
+
orders += ofld
|
|
670
|
+
return orders
|
|
671
|
+
|
|
672
|
+
# validate mutiple values for given fields
|
|
673
|
+
def validate_multiple_values(self, tname, count, flds = None):
|
|
674
|
+
opts = []
|
|
675
|
+
hash = self.TBLHASH[tname]
|
|
676
|
+
if flds:
|
|
677
|
+
for fld in flds:
|
|
678
|
+
if fld in hash: opts.append(hash[fld][0])
|
|
679
|
+
else:
|
|
680
|
+
for fld in hash:
|
|
681
|
+
opts.append(hash[fld][0])
|
|
682
|
+
self.validate_multiple_options(count, opts, (1 if tname == 'htarfile' else 0))
|
|
683
|
+
|
|
684
|
+
# validate multiple values for given options
|
|
685
|
+
def validate_multiple_options(self, count, opts, remove = 0):
|
|
686
|
+
for opt in opts:
|
|
687
|
+
if opt not in self.params or self.OPTS[opt][0] != 2: continue # no value given or not multiple value option
|
|
688
|
+
cnt = len(self.params[opt])
|
|
689
|
+
if cnt == 1 and count > 1 and self.OPTS[opt][2]&1:
|
|
690
|
+
val0 = self.params[opt][0]
|
|
691
|
+
self.params[opt] = [val0]*count
|
|
692
|
+
self.OPTS[opt][2] |= 4 # expanded
|
|
693
|
+
cnt = count
|
|
694
|
+
if cnt != count:
|
|
695
|
+
if count == 1 and cnt > 1 and self.OPTS[opt][2]&self.PGOPT['TXTBIT']:
|
|
696
|
+
self.params[opt][0] = ' '.join(self.params[opt])
|
|
697
|
+
elif remove and cnt == 1 and count > 1:
|
|
698
|
+
del self.params[opt]
|
|
699
|
+
elif cnt < count:
|
|
700
|
+
self.pglog("Multi-value Option {}({}): {} Given and {} needed".format(opt, self.OPTS[opt][1], cnt, count), self.PGOPT['extlog'])
|
|
701
|
+
|
|
702
|
+
# get field keys for a RDADB table, include all if !include
|
|
703
|
+
def get_field_keys(self, tname, include = None, exclude = None):
|
|
704
|
+
fields = ''
|
|
705
|
+
hash = self.TBLHASH[tname]
|
|
706
|
+
for fld in hash:
|
|
707
|
+
if include and include.find(fld) < 0: continue
|
|
708
|
+
if exclude and exclude.find(fld) > -1: continue
|
|
709
|
+
opt = hash[fld][0]
|
|
710
|
+
if opt in self.params: fields += fld
|
|
711
|
+
return fields if fields else None
|
|
712
|
+
|
|
713
|
+
# get a string for fields of a RDADB table
|
|
714
|
+
def get_string_fields(self, flds, tname, include = None, exclude = None):
|
|
715
|
+
fields = []
|
|
716
|
+
hash = self.TBLHASH[tname]
|
|
717
|
+
for fld in flds:
|
|
718
|
+
ufld = fld.upper() # in case
|
|
719
|
+
if include and include.find(ufld) < 0: continue
|
|
720
|
+
if exclude and exclude.find(ufld) > -1: continue
|
|
721
|
+
if ufld not in hash:
|
|
722
|
+
self.pglog("Invalid field '{}' to get from '{}'".format(fld, tname), self.PGOPT['extlog'])
|
|
723
|
+
elif hash[ufld][0] not in self.OPTS:
|
|
724
|
+
self.pglog("Option '{}' is not defined for field '{} - {}'".format(hash[ufld][0], ufld, hash[ufld][1]), self.PGOPT['extlog'])
|
|
725
|
+
if len(hash[ufld]) == 4:
|
|
726
|
+
fname = "{} {}".format(hash[ufld][3], hash[ufld][1])
|
|
727
|
+
else:
|
|
728
|
+
fname = hash[ufld][1]
|
|
729
|
+
fields.append(fname)
|
|
730
|
+
return ', '.join(fields)
|
|
731
|
+
|
|
732
|
+
# get max count for given options
|
|
733
|
+
def get_max_count(self, opts):
|
|
734
|
+
count = 0
|
|
735
|
+
for opt in opts:
|
|
736
|
+
if opt not in self.params: continue
|
|
737
|
+
cnt = len(self.params[opt])
|
|
738
|
+
if cnt > count: count = cnt
|
|
739
|
+
return count
|
|
740
|
+
|
|
741
|
+
# get a string of fields of a RDADB table for sorting
|
|
742
|
+
def get_order_string(self, flds, tname, exclude = None):
|
|
743
|
+
orders = []
|
|
744
|
+
hash = self.TBLHASH[tname]
|
|
745
|
+
for fld in flds:
|
|
746
|
+
if fld.islower():
|
|
747
|
+
desc = " DESC"
|
|
748
|
+
fld = fld.upper()
|
|
749
|
+
else:
|
|
750
|
+
desc = ""
|
|
751
|
+
if exclude and exclude.find(fld) > -1: continue
|
|
752
|
+
orders.append(hash[fld][1] + desc)
|
|
753
|
+
return (" ORDER BY " + ', '.join(orders)) if orders else ''
|
|
754
|
+
|
|
755
|
+
# get a string for column titles of a given table
|
|
756
|
+
def get_string_titles(self, flds, hash, lens):
|
|
757
|
+
titles = []
|
|
758
|
+
colcnt = len(flds)
|
|
759
|
+
for i in range(colcnt):
|
|
760
|
+
fld = flds[i]
|
|
761
|
+
if fld not in hash: continue
|
|
762
|
+
opt = hash[fld][0]
|
|
763
|
+
if opt not in self.OPTS: self.pglog("ERROR: Undefined option " + opt, self.PGOPT['extlog'])
|
|
764
|
+
title = self.OPTS[opt][1]
|
|
765
|
+
if lens:
|
|
766
|
+
if len(title) > lens[i]: title = opt
|
|
767
|
+
title = "{:{}}".format(title, lens[i])
|
|
768
|
+
titles.append(title)
|
|
769
|
+
return self.params['DV'].join(titles) + self.params['DV']
|
|
770
|
+
|
|
771
|
+
# display error message and exit
|
|
772
|
+
def parameter_error(self, p, opt = None, lidx = 0, line = 0, infile = None):
|
|
773
|
+
if not opt:
|
|
774
|
+
errmsg = "value passed in without leading info option"
|
|
775
|
+
elif opt == "continue":
|
|
776
|
+
errmsg = "error processing input file on continue Line"
|
|
777
|
+
elif opt == 'specified':
|
|
778
|
+
errmsg = "option -{}/-{} is specified already".format(p, self.OPTS[p][1])
|
|
779
|
+
elif opt == "mixed":
|
|
780
|
+
errmsg = "single-value option mixed with multi-value option"
|
|
781
|
+
elif opt == "missact":
|
|
782
|
+
errmsg = "No Action Option is specified"
|
|
783
|
+
elif opt == "missval":
|
|
784
|
+
errmsg = "No value provided following Info Option"
|
|
785
|
+
elif opt == 'duplicate':
|
|
786
|
+
errmsg = "multiple actions not allowed"
|
|
787
|
+
elif opt == "delayed":
|
|
788
|
+
errmsg = "delayed Mode option not supported"
|
|
789
|
+
elif self.OPTS[opt][0] == 0:
|
|
790
|
+
errmsg = "value follows Mode Option -{}/-{}".format(opt, self.OPTS[opt][1])
|
|
791
|
+
elif self.OPTS[opt][0] == 1:
|
|
792
|
+
errmsg = "multiple values follow single-value Option -{}/-{}".format(opt, self.OPTS[opt][1])
|
|
793
|
+
elif self.OPTS[opt][0] >= 4:
|
|
794
|
+
errmsg = "value follows Action Option -{}/-{}".format(opt, self.OPTS[opt][1])
|
|
795
|
+
else:
|
|
796
|
+
errmsg = None
|
|
797
|
+
if errmsg:
|
|
798
|
+
if lidx:
|
|
799
|
+
self.input_error(lidx, line, infile, "{} - {}".format(p, errmsg))
|
|
800
|
+
else:
|
|
801
|
+
self.pglog("ERROR: {} - {}".format(p, errmsg), self.PGOPT['extlog'])
|
|
802
|
+
|
|
803
|
+
# wrap function to self.pglog() for error in input files
|
|
804
|
+
def input_error(self, lidx, line, infile, errmsg):
|
|
805
|
+
self.pglog("ERROR at {}({}): {}\n {}".format(infile, lidx, line, errmsg), self.PGOPT['extlog'])
|
|
806
|
+
|
|
807
|
+
# wrap function to self.pglog() for error for action
|
|
808
|
+
def action_error(self, errmsg, cact = None):
|
|
809
|
+
msg = "ERROR"
|
|
810
|
+
if self.PGOPT['ANAME']: msg += " " + self.PGOPT['ANAME']
|
|
811
|
+
if not cact: cact = self.PGOPT['CACT']
|
|
812
|
+
if cact: msg += " for Action {} ({})".format(cact, self.OPTS[cact][1])
|
|
813
|
+
if 'DS' in self.params:
|
|
814
|
+
if self.OPTS['DS'][0] == 1:
|
|
815
|
+
msg += " of " + self.params['DS']
|
|
816
|
+
elif self.OPTS['DS'][0] == 2 and len(self.params['DS']) == 1:
|
|
817
|
+
msg += " of " + self.params['DS'][0]
|
|
818
|
+
msg += ": " + errmsg
|
|
819
|
+
if self.PGLOG['DSCHECK']: self.record_dscheck_error(msg, self.PGOPT['extlog'])
|
|
820
|
+
self.pglog(msg, self.PGOPT['extlog'])
|
|
821
|
+
|
|
822
|
+
# get the valid option for given parameter by checking if the given option
|
|
823
|
+
# name matches either an valid option key (short name) or its long name
|
|
824
|
+
# flag: 1 - value key only, 2 - multi-value key only, 3 - action key only,
|
|
825
|
+
# 4 - mode&action key only
|
|
826
|
+
def get_option_key(self, p, flag = 0, skip = 0, lidx = 0, line = None, infile = None, table = None):
|
|
827
|
+
if p is None: p = ''
|
|
828
|
+
opt = self.get_short_option(p)
|
|
829
|
+
errmsg = None
|
|
830
|
+
if opt:
|
|
831
|
+
if flag == 1:
|
|
832
|
+
if self.OPTS[opt][0]&3 == 0: errmsg = "NOT a Value Option"
|
|
833
|
+
elif flag == 2:
|
|
834
|
+
if self.OPTS[opt][0]&2 == 0: errmsg = "NOT a Multi-Value Option"
|
|
835
|
+
elif flag == 3:
|
|
836
|
+
if self.OPTS[opt][0] < 4:
|
|
837
|
+
if lidx:
|
|
838
|
+
errmsg = "NOT an Action Option"
|
|
839
|
+
else:
|
|
840
|
+
errmsg = "Miss leading '-' for none action option"
|
|
841
|
+
elif flag == 4:
|
|
842
|
+
if self.OPTS[opt][0]&3:
|
|
843
|
+
errmsg = "NOT a Mode/Action Option"
|
|
844
|
+
if errmsg: errmsg = "{}({}) - {}".format(opt, self.OPTS[opt][1], errmsg)
|
|
845
|
+
elif not skip:
|
|
846
|
+
if p:
|
|
847
|
+
errmsg = "-{} - Unknown Option".format(p)
|
|
848
|
+
else:
|
|
849
|
+
errmsg = "'' - Empty Option Name"
|
|
850
|
+
if errmsg:
|
|
851
|
+
if lidx:
|
|
852
|
+
self.input_error(lidx, line, infile, errmsg)
|
|
853
|
+
else:
|
|
854
|
+
self.pglog("ERROR: " + errmsg, self.PGOPT['extlog'])
|
|
855
|
+
elif opt and (table or self.PGOPT['IFCNT'] and self.OPTS[opt][0] == 2):
|
|
856
|
+
self.INOPTS[opt] = 1
|
|
857
|
+
return opt
|
|
858
|
+
|
|
859
|
+
# set values to given options, ignore options set in input files if the options
|
|
860
|
+
# already set on command line
|
|
861
|
+
def set_option_value(self, opt, val = None, cnl = 0, lidx = 0, line = None, infile = None):
|
|
862
|
+
if opt in self.CMDOPTS and lidx: # in input file, but given on command line already
|
|
863
|
+
if opt not in self.params: self.params[opt] = self.CMDOPTS[opt]
|
|
864
|
+
return
|
|
865
|
+
if val is None: val = ''
|
|
866
|
+
if self.OPTS[opt][0]&3:
|
|
867
|
+
if self.OPTS[opt][2]&16:
|
|
868
|
+
if not val:
|
|
869
|
+
val = 0
|
|
870
|
+
elif re.match(r'^\d+$', val):
|
|
871
|
+
val = int(val)
|
|
872
|
+
elif val and (opt == 'DS' or opt == 'OD'):
|
|
873
|
+
val = self.format_dataset_id(val)
|
|
874
|
+
errmsg = None
|
|
875
|
+
if not cnl and self.OPTS[opt][0]&3:
|
|
876
|
+
if opt in self.params:
|
|
877
|
+
if self.OPTS[opt][0] == 2:
|
|
878
|
+
if self.OPTS[opt][2]&2: del self.params[opt] # clean auto set values
|
|
879
|
+
elif self.params[opt] != val and not self.OPTS[opt][2]&1:
|
|
880
|
+
errmsg = "'{}', multiple values not allowed for Single-Value Option".format(val)
|
|
881
|
+
if not errmsg and (not self.PGOPT['CACT'] or self.OPTS[self.PGOPT['CACT']][2]):
|
|
882
|
+
dstr = self.OPTS[opt][3] if len(self.OPTS[opt]) > 3 else None
|
|
883
|
+
if dstr:
|
|
884
|
+
vlen = len(val)
|
|
885
|
+
ms = re.match(r'^!(\w*)', dstr)
|
|
886
|
+
if ms:
|
|
887
|
+
dstr = ms.group(1)
|
|
888
|
+
if vlen == 1 and dstr.find(val) > -1: errmsg = "{}: character must not be one of '{}'".format(val, str)
|
|
889
|
+
elif vlen > 1 or (vlen == 0 and not self.OPTS[opt][2]&128) or (vlen == 1 and dstr.find(val) < 0):
|
|
890
|
+
errmsg = "{} single-letter value must be one of '{}'".format(val, dstr)
|
|
891
|
+
if not errmsg:
|
|
892
|
+
if self.OPTS[opt][0] == 2: # multiple value option
|
|
893
|
+
if opt not in self.params:
|
|
894
|
+
self.params[opt] = [val] # set the first value
|
|
895
|
+
if opt == 'QF' and self.PGOPT['ACTS'] == self.OPTS['DL'][0]: self.OPTS['FS'][3] = 'ANT'
|
|
896
|
+
else:
|
|
897
|
+
if cnl:
|
|
898
|
+
rowidx = len(self.params[opt]) - 1
|
|
899
|
+
if self.params[opt][rowidx]:
|
|
900
|
+
if not re.match(r'^(DE|DI|DM|DW)$', opt):
|
|
901
|
+
errmsg = "Multi-line value not allowed"
|
|
902
|
+
else:
|
|
903
|
+
self.params[opt][rowidx] += "\n" + val # multiple line value
|
|
904
|
+
else:
|
|
905
|
+
self.params[opt][rowidx] = val
|
|
906
|
+
else:
|
|
907
|
+
self.params[opt].append(val) # add next value
|
|
908
|
+
elif self.OPTS[opt][0] == 1: # single value option
|
|
909
|
+
if cnl and opt in self.params:
|
|
910
|
+
if val: errmsg = "Multi-line value not allowed"
|
|
911
|
+
elif self.OPTS[opt][2]&2 and self.pgcmp(self.params[opt], val):
|
|
912
|
+
errmsg = "{}: Single-Value Info Option has value '{}' already".format(val, self.params[opt])
|
|
913
|
+
else:
|
|
914
|
+
self.params[opt] = val
|
|
915
|
+
self.OPTS[opt][2] |= 2
|
|
916
|
+
elif val:
|
|
917
|
+
if self.OPTS[opt][0] == 0 and re.match(r'^(Y|N)$', val, re.I):
|
|
918
|
+
self.params[opt] = 1 if (val == 'Y' or val == 'y') else 0
|
|
919
|
+
else:
|
|
920
|
+
self.parameter_error(val, opt, lidx, line, infile) # no value for flag or action options
|
|
921
|
+
elif opt not in self.params:
|
|
922
|
+
self.params[opt] = 1 # set flag or action option
|
|
923
|
+
if self.OPTS[opt][0] > 2:
|
|
924
|
+
if self.PGOPT['ACTS']: self.parameter_error(opt, "duplicate", lidx ,line, infile) # no duplicated action options
|
|
925
|
+
self.PGOPT['ACTS'] = self.OPTS[opt][0] # add action bit
|
|
926
|
+
self.PGOPT['CACT'] = opt # add action name
|
|
927
|
+
if opt == "SB": self.PGOPT['MSET'] = opt
|
|
928
|
+
if errmsg:
|
|
929
|
+
if lidx:
|
|
930
|
+
self.input_error(lidx, line, infile, "{}({}) - {}".format(opt, self.OPTS[opt][1], errmsg))
|
|
931
|
+
else:
|
|
932
|
+
self.pglog("ERROR: {}({}) - {}".format(opt, self.OPTS[opt][1], errmsg), self.PGOPT['extlog'])
|
|
933
|
+
if not lidx: self.CMDOPTS[opt] = self.params[opt] # record options set on command lines
|
|
934
|
+
|
|
935
|
+
# get width for a single row if in column format
|
|
936
|
+
def get_row_width(self, pgrec):
|
|
937
|
+
slen = len(self.params['DV'])
|
|
938
|
+
width = 0
|
|
939
|
+
for key in pgrec:
|
|
940
|
+
wd = 0
|
|
941
|
+
for val in pgrec[key]:
|
|
942
|
+
if not val: continue
|
|
943
|
+
if not isinstance(val, str): val = str(val)
|
|
944
|
+
if key == 'note':
|
|
945
|
+
vlen = val.find('\n') + 1
|
|
946
|
+
else:
|
|
947
|
+
vlen = 0
|
|
948
|
+
if vlen < 1: vlen = len(val)
|
|
949
|
+
if vlen > wd: wd = vlen # get max width of each column
|
|
950
|
+
# accumulate all column width plus length of delimiter to get row width
|
|
951
|
+
if width: width += slen
|
|
952
|
+
width += wd
|
|
953
|
+
return width
|
|
954
|
+
|
|
955
|
+
# get a short option name by searching dict self.OPTS and self.ALIAS
|
|
956
|
+
def get_short_option(self, p):
|
|
957
|
+
plen = len(p)
|
|
958
|
+
if plen == 2:
|
|
959
|
+
p = p.upper()
|
|
960
|
+
if p in self.OPTS: return p
|
|
961
|
+
for opt in self.OPTS: # get main option first
|
|
962
|
+
if not self.pgcmp(self.OPTS[opt][1], p, 1): return opt
|
|
963
|
+
for opt in self.ALIAS: # then check alias option
|
|
964
|
+
for key in self.ALIAS[opt]:
|
|
965
|
+
if not self.pgcmp(key, p, 1): return opt
|
|
966
|
+
return None
|
|
967
|
+
|
|
968
|
+
# print result in column format, with multiple values each row
|
|
969
|
+
def print_column_format(self, pgrec, flds, hash, lens, retbuf = 0):
|
|
970
|
+
rowcnt = -1
|
|
971
|
+
colcnt = len(flds)
|
|
972
|
+
buf = ''
|
|
973
|
+
fields = []
|
|
974
|
+
flens = []
|
|
975
|
+
for i in range(colcnt):
|
|
976
|
+
fld = flds[i]
|
|
977
|
+
if fld in hash:
|
|
978
|
+
fld = hash[fld][1]
|
|
979
|
+
ms = re.search(r'\.(.+)$', fld)
|
|
980
|
+
if ms: fld = ms.group(1)
|
|
981
|
+
if fld in pgrec:
|
|
982
|
+
fields.append(fld)
|
|
983
|
+
flens.append((lens[i] if lens else 0))
|
|
984
|
+
if rowcnt < 0: rowcnt = len(pgrec[fld])
|
|
985
|
+
else:
|
|
986
|
+
self.pglog(fld + ": Unkown field name", self.PGOPT['extlog'])
|
|
987
|
+
colcnt = len(fields)
|
|
988
|
+
for i in range(rowcnt):
|
|
989
|
+
offset = 0
|
|
990
|
+
values = []
|
|
991
|
+
for j in range(colcnt):
|
|
992
|
+
fld = fields[j]
|
|
993
|
+
idx = -1
|
|
994
|
+
val = pgrec[fld][i]
|
|
995
|
+
slen = flens[j]
|
|
996
|
+
if val is None:
|
|
997
|
+
val = ''
|
|
998
|
+
elif isinstance(val, str):
|
|
999
|
+
idx = val.find("\n")
|
|
1000
|
+
if idx > 0:
|
|
1001
|
+
val = "\n" + val
|
|
1002
|
+
idx = 0
|
|
1003
|
+
else:
|
|
1004
|
+
val = str(val)
|
|
1005
|
+
if slen:
|
|
1006
|
+
if idx < 0:
|
|
1007
|
+
val = "{:{}}".format(val, slen)
|
|
1008
|
+
else:
|
|
1009
|
+
val += "\n{:{}}".format(' ', offset)
|
|
1010
|
+
offset += slen
|
|
1011
|
+
values.append(val)
|
|
1012
|
+
line = self.params['DV'].join(values) + self.params['DV'] + "\n"
|
|
1013
|
+
if retbuf:
|
|
1014
|
+
buf += line
|
|
1015
|
+
else:
|
|
1016
|
+
self.OUTPUT.write(line)
|
|
1017
|
+
return buf if retbuf else rowcnt
|
|
1018
|
+
|
|
1019
|
+
# print result in row format, with single value on each row
|
|
1020
|
+
def print_row_format(self, pgrec, flds, hash):
|
|
1021
|
+
for fld in flds:
|
|
1022
|
+
if fld not in hash: continue
|
|
1023
|
+
line = "{}{}".format(self.OPTS[hash[fld][0]][1], self.params['ES'])
|
|
1024
|
+
field = hash[fld][1]
|
|
1025
|
+
ms = re.search(r'\.(.+)$', field)
|
|
1026
|
+
if ms: field = ms.group(1)
|
|
1027
|
+
if field in pgrec:
|
|
1028
|
+
value = pgrec[field]
|
|
1029
|
+
if value is not None: line += str(value)
|
|
1030
|
+
self.OUTPUT.write(line + "\n")
|
|
1031
|
+
|
|
1032
|
+
# compress/uncompress given files and change the formats accordingly
|
|
1033
|
+
def compress_files(self, files, formats, count):
|
|
1034
|
+
if 'UZ' in self.params:
|
|
1035
|
+
strcmp = 'Uncompress'
|
|
1036
|
+
actcmp = 0
|
|
1037
|
+
else:
|
|
1038
|
+
strcmp = 'Compress'
|
|
1039
|
+
actcmp = 1
|
|
1040
|
+
fmtcnt = len(formats)
|
|
1041
|
+
if not fmtcnt: return files # just in case
|
|
1042
|
+
s = 's' if count > 1 else ''
|
|
1043
|
+
self.pglog("{}ing {} File{} for {} ...".format(strcmp, count, s, self.params['DS']), self.PGOPT['wrnlog'])
|
|
1044
|
+
cmpcnt = 0
|
|
1045
|
+
for i in range(count):
|
|
1046
|
+
fmt = formats[i] if(i < fmtcnt and formats[i]) else formats[0]
|
|
1047
|
+
(ofile, fmt) = self.compress_local_file(files[i], fmt, actcmp, self.PGOPT['extlog'])
|
|
1048
|
+
if ofile != files[i]:
|
|
1049
|
+
files[i] = ofile
|
|
1050
|
+
cmpcnt += 1
|
|
1051
|
+
self.pglog("{}/{} Files {}ed for {}".format(cmpcnt, count, strcmp, self.params['DS']) , self.PGOPT['emllog'])
|
|
1052
|
+
if 'ZD' in self.params: del self.params['ZD']
|
|
1053
|
+
if 'UZ' in self.params: del self.params['UZ']
|
|
1054
|
+
return files
|
|
1055
|
+
|
|
1056
|
+
# get hash condition
|
|
1057
|
+
# tname - table name to identify a table hash
|
|
1058
|
+
# noand - 1 for not add leading 'AND'
|
|
1059
|
+
def get_hash_condition(self, tname, include = None, exclude = None, noand = 0):
|
|
1060
|
+
condition = ''
|
|
1061
|
+
hash = self.TBLHASH[tname]
|
|
1062
|
+
for key in hash:
|
|
1063
|
+
if include and include.find(key) < 0: continue
|
|
1064
|
+
if exclude and exclude.find(key) > -1: continue
|
|
1065
|
+
opt = hash[key][0]
|
|
1066
|
+
if opt not in self.params: continue # no option value
|
|
1067
|
+
flg = hash[key][2]
|
|
1068
|
+
if flg < 0: # condition is ignore for this option
|
|
1069
|
+
self.pglog("Condition given per Option -{} (-{}) is ignored".format(opt, self.OPTS[opt][1]), self.PGOPT['errlog'])
|
|
1070
|
+
continue
|
|
1071
|
+
fld = hash[key][1]
|
|
1072
|
+
condition += self.get_field_condition(fld, self.params[opt], flg, noand)
|
|
1073
|
+
noand = 0
|
|
1074
|
+
return condition
|
|
1075
|
+
|
|
1076
|
+
# set default self.params value for given opt empty the value if 'all' is given
|
|
1077
|
+
def set_default_value(self, opt, dval = None):
|
|
1078
|
+
flag = self.OPTS[opt][0]
|
|
1079
|
+
if flag&3 == 0: return # skip if not single&multiple value options
|
|
1080
|
+
oval = 0
|
|
1081
|
+
if opt in self.params:
|
|
1082
|
+
if flag == 1:
|
|
1083
|
+
oval = self.params[opt]
|
|
1084
|
+
else:
|
|
1085
|
+
count = len(self.params[opt])
|
|
1086
|
+
if count == 1:
|
|
1087
|
+
oval = self.params[opt][0]
|
|
1088
|
+
elif count > 1:
|
|
1089
|
+
return # multiple values given already
|
|
1090
|
+
if oval:
|
|
1091
|
+
if re.match(r'^all$', oval, re.I):
|
|
1092
|
+
del self.params[opt] # remove option value for all
|
|
1093
|
+
return # value given already
|
|
1094
|
+
if dval:
|
|
1095
|
+
# set default value
|
|
1096
|
+
if flag == 1:
|
|
1097
|
+
self.params[opt] = dval
|
|
1098
|
+
else:
|
|
1099
|
+
self.params[opt] = [dval]
|
|
1100
|
+
|
|
1101
|
+
# add/strip COS block for give file name and cosflg if given/not-given cosfile
|
|
1102
|
+
# return the file size after the convertion
|
|
1103
|
+
def cos_convert(self, locfile, cosflg, cosfile = None):
|
|
1104
|
+
if cosfile:
|
|
1105
|
+
cmd = "cosconvert -{} {} {}".format(cosflg, cosfile, locfile)
|
|
1106
|
+
else:
|
|
1107
|
+
cmd = "cosconvert -{} {}".format(cosflg.lower(), locfile)
|
|
1108
|
+
cosfile = locfile
|
|
1109
|
+
self.pgsystem(cmd)
|
|
1110
|
+
info = self.check_local_file(cosfile)
|
|
1111
|
+
if not info:
|
|
1112
|
+
return self.pglog("Error - " + cmd, self.PGOPT['errlog']) # should not happen
|
|
1113
|
+
else:
|
|
1114
|
+
return info['data_size']
|
|
1115
|
+
|
|
1116
|
+
# evaluate count of values for given options
|
|
1117
|
+
def get_option_count(self, opts):
|
|
1118
|
+
count = 0
|
|
1119
|
+
for opt in opts:
|
|
1120
|
+
if opt in self.params:
|
|
1121
|
+
cnt = len(self.params[opt])
|
|
1122
|
+
if cnt > count: count = cnt
|
|
1123
|
+
if count > 0: self.validate_multiple_options(count, opts)
|
|
1124
|
+
return count
|
|
1125
|
+
|
|
1126
|
+
# gather subgroup indices recursively for given condition
|
|
1127
|
+
# dsid: Dataset Id
|
|
1128
|
+
# pidx: parent group index
|
|
1129
|
+
# gtype: group type if not empty, P - public groups only)
|
|
1130
|
+
# Return: array reference of group indices
|
|
1131
|
+
def get_all_subgroups(self, dcnd, pidx, gtype = None):
|
|
1132
|
+
gidxs = [pidx]
|
|
1133
|
+
gflds = "gindex"
|
|
1134
|
+
if gtype: gflds += ", grptype"
|
|
1135
|
+
grecs = self.pgmget("dsgroup", gflds, "{} and pindex = {}".format(dcnd, pidx), self.LGWNEX)
|
|
1136
|
+
if not grecs: return gidxs
|
|
1137
|
+
gcnt = len(grecs['gindex'])
|
|
1138
|
+
for i in range(gcnt):
|
|
1139
|
+
gidx = grecs['gindex'][i]
|
|
1140
|
+
if abs(gidx) <= abs(pidx) or gtype and grecs['grptype'][i] != gtype: continue
|
|
1141
|
+
subs = self.get_all_subgroups(dcnd, gidx, gtype)
|
|
1142
|
+
gidxs.extend(subs)
|
|
1143
|
+
return gidxs
|
|
1144
|
+
|
|
1145
|
+
# gather public subgroup indices recursively for given condition. A group index is
|
|
1146
|
+
# gathered only if there are data files right under it. The pidx is included too
|
|
1147
|
+
# if file count of it larger then zero.
|
|
1148
|
+
# dsid: Dataset Id
|
|
1149
|
+
# pidx: parent group index
|
|
1150
|
+
# cfld: count field (dwebcnt, nwebcnt, savedcnt)
|
|
1151
|
+
# pfcnt: file count for parent group index pidx 0 to skip)
|
|
1152
|
+
# Return: array reference of group indices
|
|
1153
|
+
def get_data_subgroups(self, dcnd, pidx, cfld, pfcnt = 0):
|
|
1154
|
+
if not pfcnt: # get file count for the parent group
|
|
1155
|
+
pfcnt = self.group_file_count(dcnd, pidx, cfld)
|
|
1156
|
+
if not pfcnt: return None
|
|
1157
|
+
gflds = "gindex, " + cfld
|
|
1158
|
+
gcnd = "{} AND pindex = {} AND {} > 0".format(dcnd, pidx, cfld)
|
|
1159
|
+
grecs = self.pgmget("dsgroup", gflds, gcnd, self.LGWNEX)
|
|
1160
|
+
if not grecs: return ([pidx] if pfcnt > 0 else None)
|
|
1161
|
+
gcnt = len(grecs['gindex'])
|
|
1162
|
+
gidxs = []
|
|
1163
|
+
for i in range(gcnt):
|
|
1164
|
+
gidx = grecs['gindex'][i]
|
|
1165
|
+
fcnt = grecs[cfld][i]
|
|
1166
|
+
if fcnt == 0 or abs(gidx) <= abs(pidx): continue
|
|
1167
|
+
subs = self.get_data_subgroups(dcnd, gidx, cfld, fcnt)
|
|
1168
|
+
if subs: gidxs.extend(subs)
|
|
1169
|
+
pfcnt -= fcnt
|
|
1170
|
+
if pfcnt > 0: gidxs.insert(0, pidx)
|
|
1171
|
+
return (gidxs if gidxs else None)
|
|
1172
|
+
|
|
1173
|
+
# get group file count for given count field name
|
|
1174
|
+
def group_file_count(self, cnd, gidx, cfld):
|
|
1175
|
+
if gidx:
|
|
1176
|
+
table = "dsgroup"
|
|
1177
|
+
cnd += " AND gindex = {}".format(gidx)
|
|
1178
|
+
else:
|
|
1179
|
+
table = "dataset"
|
|
1180
|
+
pgrec = self.pgget(table, cfld, cnd)
|
|
1181
|
+
return (pgrec[cfld] if pgrec else 0)
|
|
1182
|
+
|
|
1183
|
+
# set file format for actions -AM/-AW from given local files
|
|
1184
|
+
def set_file_format(self, count):
|
|
1185
|
+
if 'LF' in self.params:
|
|
1186
|
+
files = self.params['LF']
|
|
1187
|
+
else:
|
|
1188
|
+
return
|
|
1189
|
+
fmtcnt = 0
|
|
1190
|
+
fmts = [None] * count
|
|
1191
|
+
for i in range(count):
|
|
1192
|
+
fmt = self.get_file_format(files[i])
|
|
1193
|
+
if fmt:
|
|
1194
|
+
fmtcnt += 1
|
|
1195
|
+
fmts[i] = fmt
|
|
1196
|
+
if fmtcnt:
|
|
1197
|
+
self.params['AF'] = fmts
|
|
1198
|
+
self.OPTS['AF'][2] |= 2
|
|
1199
|
+
|
|
1200
|
+
# get frequency information
|
|
1201
|
+
@staticmethod
|
|
1202
|
+
def get_control_frequency(frequency):
|
|
1203
|
+
val = nf = 0
|
|
1204
|
+
unit = None
|
|
1205
|
+
ms = re.match(r'^(\d+)([YMWDHNS])$', frequency, re.I)
|
|
1206
|
+
if ms:
|
|
1207
|
+
val = int(ms.group(1))
|
|
1208
|
+
unit = ms.group(2).upper()
|
|
1209
|
+
else:
|
|
1210
|
+
ms = re.match(r'^(\d+)M/(\d+)', frequency, re.I)
|
|
1211
|
+
if ms:
|
|
1212
|
+
val = int(ms.group(1))
|
|
1213
|
+
nf = int(ms.group(2))
|
|
1214
|
+
unit = 'M'
|
|
1215
|
+
if nf < 2 or nf > 10 or (30%nf): val = 0
|
|
1216
|
+
if not val:
|
|
1217
|
+
if nf:
|
|
1218
|
+
unit = "fraction of month frequency '{}' MUST be (2,3,5,6,10)".format(frequency)
|
|
1219
|
+
elif unit:
|
|
1220
|
+
val = "frequency '{}' MUST be larger than 0".format(frequency)
|
|
1221
|
+
elif re.search(r'/(\d+)$', frequency):
|
|
1222
|
+
val = "fractional frequency '{}' for month ONLY".format(frequency)
|
|
1223
|
+
else:
|
|
1224
|
+
val = "invalid frequency '{}', unit must be (Y,M,W,D,H)".format(frequency)
|
|
1225
|
+
return (None, unit)
|
|
1226
|
+
freq = [0]*7 # initialize the frequence list
|
|
1227
|
+
uidx = {'Y' : 0, 'D' : 2, 'H' : 3, 'N' : 4, 'S' : 5}
|
|
1228
|
+
if unit == 'M':
|
|
1229
|
+
freq[1] = val
|
|
1230
|
+
if nf: freq[6] = nf # number of fractions in a month
|
|
1231
|
+
elif unit == 'W':
|
|
1232
|
+
freq[2] = 7 * val
|
|
1233
|
+
elif unit in uidx:
|
|
1234
|
+
freq[uidx[unit]] = val
|
|
1235
|
+
return (freq, unit)
|
|
1236
|
+
|
|
1237
|
+
# check if valid data time for given pindex
|
|
1238
|
+
def valid_data_time(self, pgrec, cstr = None, logact = 0):
|
|
1239
|
+
if pgrec['pindex'] and pgrec['datatime']:
|
|
1240
|
+
(freq, unit) = self.get_control_frequency(pgrec['frequency'])
|
|
1241
|
+
if not freq:
|
|
1242
|
+
if cstr: self.pglog("{}: {}".format(cstr, unit), logact)
|
|
1243
|
+
return self.FAILURE
|
|
1244
|
+
dtime = self.adddatetime(pgrec['datatime'], freq[0], freq[1], freq[2], freq[3], freq[4], freq[5], freq[6])
|
|
1245
|
+
if self.pgget("dcupdt", "", "cindex = {} AND datatime < '{}'".format(pgrec['pindex'], dtime), self.PGOPT['extlog']):
|
|
1246
|
+
if cstr: self.pglog("{}: MUST be processed After Control Index {}".format(cstr, pgrec['pindex']), logact)
|
|
1247
|
+
return self.FAILURE
|
|
1248
|
+
return self.SUCCESS
|
|
1249
|
+
|
|
1250
|
+
# publish filelists for given datasets
|
|
1251
|
+
def publish_dataset_filelist(self, dsids):
|
|
1252
|
+
for dsid in dsids:
|
|
1253
|
+
self.pgsystem("publish_filelist " + dsid, self.PGOPT['wrnlog'], 7)
|
|
1254
|
+
|
|
1255
|
+
# get the current active version index for given dsid
|
|
1256
|
+
def get_version_index(self, dsid, logact = 0):
|
|
1257
|
+
pgrec = self.pgget("dsvrsn", "vindex", "dsid = '{}' AND status = 'A'".format(dsid), logact)
|
|
1258
|
+
return (pgrec['vindex'] if pgrec else 0)
|
|
1259
|
+
|
|
1260
|
+
# append given format (data or archive) sfmt to format string sformat
|
|
1261
|
+
@staticmethod
|
|
1262
|
+
def append_format_string(sformat, sfmt, chkend = 0):
|
|
1263
|
+
mp = r'(^|\.){}$' if chkend else r'(^|\.){}(\.|$)'
|
|
1264
|
+
if sfmt:
|
|
1265
|
+
if not sformat:
|
|
1266
|
+
sformat = sfmt
|
|
1267
|
+
else:
|
|
1268
|
+
for fmt in re.split(r'\.', sfmt):
|
|
1269
|
+
if not re.search(mp.format(fmt), sformat, re.I): sformat += '.' + fmt
|
|
1270
|
+
return sformat
|
|
1271
|
+
|
|
1272
|
+
# get request type string or shared info
|
|
1273
|
+
@staticmethod
|
|
1274
|
+
def request_type(rtype, idx = 0):
|
|
1275
|
+
RTYPE = {
|
|
1276
|
+
'C' : ["Customized Data", 0],
|
|
1277
|
+
'D' : ["CDP Link", 0],
|
|
1278
|
+
'M' : ["Delayed Mode Data", 1],
|
|
1279
|
+
'N' : ["NCARDAP(THREDDS) Data Server", 0],
|
|
1280
|
+
'Q' : ["Database Query", 0],
|
|
1281
|
+
'R' : ["Realtime Data", 0],
|
|
1282
|
+
'S' : ["Subset Data", 0],
|
|
1283
|
+
'T' : ["Subset/Format-Conversion Data", 0],
|
|
1284
|
+
'F' : ["Format Conversion Data", 1], # web
|
|
1285
|
+
'A' : ["Archive Format Conversion", 1], # web
|
|
1286
|
+
'P' : ["Plot Chart", 0],
|
|
1287
|
+
'U' : ["Data", 0]
|
|
1288
|
+
}
|
|
1289
|
+
if rtype not in RTYPE: rtype = 'U'
|
|
1290
|
+
return RTYPE[rtype][idx]
|
|
1291
|
+
|
|
1292
|
+
# email notice of for user
|
|
1293
|
+
def send_request_email_notice(self, pgrqst, errmsg, fcount, rstat, readyfile = None, pgpart = None):
|
|
1294
|
+
pgcntl = self.PGOPT['RCNTL']
|
|
1295
|
+
rhome = self.params['WH'] if 'WH' in self.params and self.params['WH'] else self.PGLOG['RQSTHOME']
|
|
1296
|
+
if errmsg:
|
|
1297
|
+
if pgpart:
|
|
1298
|
+
if self.cache_partition_email_error(pgpart['rindex'], errmsg): return rstat
|
|
1299
|
+
enote = "email_part_error"
|
|
1300
|
+
else:
|
|
1301
|
+
enote = "email_error"
|
|
1302
|
+
elif fcount == 0:
|
|
1303
|
+
if pgcntl and pgcntl['empty_out'] == 'Y':
|
|
1304
|
+
enote = "email_empty"
|
|
1305
|
+
else:
|
|
1306
|
+
errmsg = "NO output data generated"
|
|
1307
|
+
if pgpart:
|
|
1308
|
+
if self.cache_partition_email_error(pgpart['rindex'], errmsg): return rstat
|
|
1309
|
+
enote = "email_part_error"
|
|
1310
|
+
else:
|
|
1311
|
+
enote = "email_error"
|
|
1312
|
+
elif 'EN' in self.params and self.params['EN'][0]:
|
|
1313
|
+
enote = self.params['EN'][0]
|
|
1314
|
+
elif pgrqst['enotice']:
|
|
1315
|
+
enote = pgrqst['enotice']
|
|
1316
|
+
elif pgcntl and pgcntl['enotice']:
|
|
1317
|
+
enote = pgcntl['enotice']
|
|
1318
|
+
elif pgrqst['globus_transfer'] == 'Y' and pgrqst['task_id']:
|
|
1319
|
+
enote = "email_notice_globus"
|
|
1320
|
+
else:
|
|
1321
|
+
enote = "email_" + ("command" if pgrqst['location'] else "notice")
|
|
1322
|
+
if enote[0] not in '/.': enote = "{}/notices/{}".format(rhome, enote)
|
|
1323
|
+
finfo = self.check_local_file(enote, 128)
|
|
1324
|
+
if not finfo:
|
|
1325
|
+
if finfo is None:
|
|
1326
|
+
ferror = "file not exists"
|
|
1327
|
+
else:
|
|
1328
|
+
ferror = "Error check file"
|
|
1329
|
+
else:
|
|
1330
|
+
ef = open(enote, 'r') # open email notice file
|
|
1331
|
+
ferror = None
|
|
1332
|
+
if ferror:
|
|
1333
|
+
if errmsg:
|
|
1334
|
+
self.pglog("{}: {}\nCannot email error to {}@ucar.edu: {}".format(enote, ferror, self.PGLOG['CURUID'], errmsg),
|
|
1335
|
+
(self.PGOPT['errlog'] if rstat else self.PGOPT['extlog']))
|
|
1336
|
+
return "E"
|
|
1337
|
+
else:
|
|
1338
|
+
errmsg = self.pglog("{}: {}\nCannot email notice to {}".format(enote, ferror, pgrqst['email']), self.PGOPT['errlog']|self.RETMSG)
|
|
1339
|
+
enote = rhome + "/notices/email_error"
|
|
1340
|
+
ef = open(enote, 'r')
|
|
1341
|
+
rstat = 'E'
|
|
1342
|
+
ebuf = ''
|
|
1343
|
+
ebuf += ef.read()
|
|
1344
|
+
ef.close()
|
|
1345
|
+
einfo = {}
|
|
1346
|
+
einfo['HOSTNAME'] = self.PGLOG['HOSTNAME']
|
|
1347
|
+
einfo['DSID'] = pgrqst['dsid']
|
|
1348
|
+
einfo['DSSURL'] = self.PGLOG['DSSURL']
|
|
1349
|
+
if pgrqst['location']:
|
|
1350
|
+
einfo['WHOME'] = pgrqst['location']
|
|
1351
|
+
else:
|
|
1352
|
+
einfo['WHOME'] = self.PGLOG['RQSTURL']
|
|
1353
|
+
einfo['SENDER'] = pgrqst['specialist'] + "@ucar.edu"
|
|
1354
|
+
einfo['RECEIVER'] = pgrqst['email']
|
|
1355
|
+
einfo['RTYPE'] = self.request_type(pgrqst['rqsttype'])
|
|
1356
|
+
self.add_carbon_copy() # clean carbon copy email in case not empty
|
|
1357
|
+
exclude = (einfo['SENDER'] if errmsg else einfo['RECEIVER'])
|
|
1358
|
+
if not errmsg and pgcntl and pgcntl['ccemail']:
|
|
1359
|
+
self.add_carbon_copy(pgcntl['ccemail'], 1, exclude, pgrqst['specialist'])
|
|
1360
|
+
if self.PGLOG['CURUID'] != pgrqst['specialist'] and self.PGLOG['CURUID'] != self.PGLOG['GDEXUSER']:
|
|
1361
|
+
self.add_carbon_copy(self.PGLOG['CURUID'], 1, exclude)
|
|
1362
|
+
if 'CC' in self.params: self.add_carbon_copy(self.params['CC'], 0, exclude)
|
|
1363
|
+
einfo['CCD'] = self.PGLOG['CCDADDR']
|
|
1364
|
+
einfo['RINDEX'] = str(pgrqst['rindex'])
|
|
1365
|
+
einfo['RQSTID'] = pgrqst['rqstid']
|
|
1366
|
+
pgrec = self.pgget("dataset", "title", "dsid = '{}'".format(pgrqst['dsid']), self.PGOPT['extlog'])
|
|
1367
|
+
einfo['DSTITLE'] = pgrec['title'] if pgrec and pgrec['title'] else ''
|
|
1368
|
+
einfo['SUBJECT'] = ''
|
|
1369
|
+
if errmsg:
|
|
1370
|
+
einfo['ERRMSG'] = self.get_error_command(int(time.time()), self.PGOPT['errlog']) + errmsg
|
|
1371
|
+
einfo['SUBJECT'] = "Error "
|
|
1372
|
+
if pgpart:
|
|
1373
|
+
einfo['PARTITION'] = " partition"
|
|
1374
|
+
einfo['PTIDX'] = "(PTIDX{})".format(pgpart['pindex'])
|
|
1375
|
+
einfo['SUBJECT'] += "Process Partitions of "
|
|
1376
|
+
else:
|
|
1377
|
+
einfo['PARTITION'] = einfo['PTIDX'] = ''
|
|
1378
|
+
einfo['SUBJECT'] += "Build "
|
|
1379
|
+
einfo['SUBJECT'] += "{} Rqst{} from {}".format(einfo['RTYPE'], pgrqst['rindex'], pgrqst['dsid'])
|
|
1380
|
+
else:
|
|
1381
|
+
if fcount == 0:
|
|
1382
|
+
einfo['SUBJECT'] += "NO Output:"
|
|
1383
|
+
else:
|
|
1384
|
+
einfo['SUBJECT'] += "Completed:"
|
|
1385
|
+
einfo['DAYS'] = str(self.PGOPT['VP'])
|
|
1386
|
+
pgrec = self.pgget("dssgrp", "lstname, fstname, phoneno",
|
|
1387
|
+
"logname = '{}'".format(self.PGLOG['CURUID']), self.PGOPT['extlog'])
|
|
1388
|
+
if pgrec:
|
|
1389
|
+
einfo['SPECIALIST'] = "{} {}".format(pgrec['fstname'], pgrec['lstname'])
|
|
1390
|
+
einfo['PHONENO'] = pgrec['phoneno']
|
|
1391
|
+
einfo['SUBJECT'] += f" {pgrqst['dsid']} {einfo['RTYPE']} request {pgrqst['rindex']}"
|
|
1392
|
+
if pgrqst['note']:
|
|
1393
|
+
einfo['RNOTE'] = "\nRequest Detail:\n{}\n".format(pgrqst['note'])
|
|
1394
|
+
elif fcount > 0 and pgrqst['rinfo']:
|
|
1395
|
+
einfo['RNOTE'] = "\nRequest Detail:\n{}\n".format(pgrqst['rinfo'])
|
|
1396
|
+
else:
|
|
1397
|
+
einfo['RNOTE'] = ""
|
|
1398
|
+
if pgrqst['globus_transfer'] == 'Y' and pgrqst['task_id']:
|
|
1399
|
+
einfo['GLOBUS_TASK_URL'] = "https://app.globus.org/activity/" + pgrqst['task_id']
|
|
1400
|
+
for ekey in einfo:
|
|
1401
|
+
if ekey == 'CCD' and not einfo['CCD']:
|
|
1402
|
+
mp = r'Cc:\s*<CCD>\s*'
|
|
1403
|
+
rep = ''
|
|
1404
|
+
else:
|
|
1405
|
+
mp = r'<{}>'.format(ekey)
|
|
1406
|
+
rep = einfo[ekey]
|
|
1407
|
+
if rep is None:
|
|
1408
|
+
self.pglog("{}.{}: None ekey value for reuqest email".format(pgrqst['rindex'], ekey),
|
|
1409
|
+
self.PGOPT['wrnlog']|self.FRCLOG)
|
|
1410
|
+
rep = ''
|
|
1411
|
+
ebuf = re.sub(mp, rep, ebuf)
|
|
1412
|
+
if self.PGLOG['DSCHECK'] and not pgpart:
|
|
1413
|
+
tbl = "dscheck"
|
|
1414
|
+
cnd = "cindex = {}".format(self.PGLOG['DSCHECK']['cindex'])
|
|
1415
|
+
else:
|
|
1416
|
+
tbl = "dsrqst"
|
|
1417
|
+
cnd = "rindex = {}".format(pgrqst['rindex'])
|
|
1418
|
+
if self.send_customized_email(f"{tbl}.{cnd}", ebuf, 0):
|
|
1419
|
+
if errmsg:
|
|
1420
|
+
self.pglog("Error Email sent to {} for {}.{}:\n{}".format(einfo['SENDER'], tbl, cnd, errmsg), self.PGOPT['errlog'])
|
|
1421
|
+
readyfile = None
|
|
1422
|
+
else:
|
|
1423
|
+
self.pglog("{}Email sent to {} for {}.{}\nSubset: {}".format(("Customized " if pgrqst['enotice'] else ""), einfo['RECEIVER'], tbl, cnd, einfo['SUBJECT']),
|
|
1424
|
+
self.PGOPT['wrnlog']|self.FRCLOG)
|
|
1425
|
+
else:
|
|
1426
|
+
if not self.cache_customized_email(tbl, "einfo", cnd, ebuf, 0): return 'E'
|
|
1427
|
+
if errmsg:
|
|
1428
|
+
self.pglog("Error Email {} cached to {}.einfo for {}:\n{}".format(einfo['SENDER'], tbl, cnd, errmsg), self.PGOPT['errlog'])
|
|
1429
|
+
readyfile = None
|
|
1430
|
+
else:
|
|
1431
|
+
self.pglog("{}Email {} cached to {}.einfo for {}\nSubset: {}".format(("Customized " if pgrqst['enotice'] else ""), einfo['RECEIVER'], tbl, cnd, einfo['SUBJECT']),
|
|
1432
|
+
self.PGOPT['wrnlog']|self.FRCLOG)
|
|
1433
|
+
if readyfile:
|
|
1434
|
+
rf = open(readyfile, 'w')
|
|
1435
|
+
rf.write(ebuf)
|
|
1436
|
+
rf.close()
|
|
1437
|
+
self.set_local_mode(readyfile, 1, self.PGLOG['FILEMODE'])
|
|
1438
|
+
return rstat
|
|
1439
|
+
|
|
1440
|
+
# cache partition process error to existing email buffer
|
|
1441
|
+
def cache_partition_email_error(self, ridx, errmsg):
|
|
1442
|
+
pkey = "<PARTERR>"
|
|
1443
|
+
pgrec = self.pgget("dsrqst", 'einfo', "rindex = {}".format(ridx), self.PGOPT['extlog'])
|
|
1444
|
+
if not (pgrec and pgrec['einfo'] and pgrec['einfo'].find(pkey) > -1): return 0
|
|
1445
|
+
errmsg = self.get_error_command(int(time.time()), self.PGOPT['errlog']) + ("{}\n{}".format(errmsg, pkey))
|
|
1446
|
+
pgrec['einfo'] = re.sub(pkey, errmsg, pgrec['einfo'])
|
|
1447
|
+
return self.pgupdt("dsrqst", pgrec, "rindex = {}".format(ridx), self.PGOPT['extlog'])
|