rda-python-dsarch 3.0.16__tar.gz → 3.0.18__tar.gz

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.
Files changed (21) hide show
  1. {rda_python_dsarch-3.0.16/src/rda_python_dsarch.egg-info → rda_python_dsarch-3.0.18}/PKG-INFO +2 -2
  2. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/pyproject.toml +2 -2
  3. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch/dsarch.py +292 -246
  4. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch/dsarch.usg +22 -8
  5. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch/pg_arch.py +29 -3
  6. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18/src/rda_python_dsarch.egg-info}/PKG-INFO +2 -2
  7. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch.egg-info/requires.txt +1 -1
  8. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/LICENSE +0 -0
  9. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/MANIFEST.in +0 -0
  10. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/README.md +0 -0
  11. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/setup.cfg +0 -0
  12. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch/PgArch.py +0 -0
  13. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch/PgMeta.py +0 -0
  14. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch/__init__.py +0 -0
  15. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch/ds_arch.py +0 -0
  16. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch/pg_meta.py +0 -0
  17. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch.egg-info/SOURCES.txt +0 -0
  18. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch.egg-info/dependency_links.txt +0 -0
  19. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch.egg-info/entry_points.txt +0 -0
  20. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/src/rda_python_dsarch.egg-info/top_level.txt +0 -0
  21. {rda_python_dsarch-3.0.16 → rda_python_dsarch-3.0.18}/tests/test_dsarch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dsarch
3
- Version: 3.0.16
3
+ Version: 3.0.18
4
4
  Summary: RDA Python package for utility program dsarch archive RDA data
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dsarch
@@ -11,7 +11,7 @@ Classifier: Development Status :: 5 - Production/Stable
11
11
  Requires-Python: >=3.7
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: rda_python_common
14
+ Requires-Dist: rda_python_common>=3.0.31
15
15
  Requires-Dist: rda_python_setuid
16
16
  Requires-Dist: rda_python_miscs
17
17
  Dynamic: license-file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rda_python_dsarch"
7
- version = "3.0.16"
7
+ version = "3.0.18"
8
8
  authors = [
9
9
  { name="Zaihua Ji", email="zji@ucar.edu" },
10
10
  ]
@@ -18,7 +18,7 @@ classifiers = [
18
18
  "Development Status :: 5 - Production/Stable",
19
19
  ]
20
20
  dependencies = [
21
- "rda_python_common",
21
+ "rda_python_common>=3.0.31",
22
22
  "rda_python_setuid",
23
23
  "rda_python_miscs",
24
24
  ]
@@ -119,7 +119,7 @@ class DsArch(PgArch, PgMeta):
119
119
  elif 'XM' in self.params:
120
120
  self.crosscopy_help_files('Move')
121
121
  else:
122
- self.archive_help_files()
122
+ self.archive_in_passes(self.archive_help_files)
123
123
  if 'CL' in self.params: self.delete_local_files()
124
124
  elif self.PGOPT['ACTS'] == self.OPTS['AQ'][0]:
125
125
  if 'XC' in self.params:
@@ -130,7 +130,7 @@ class DsArch(PgArch, PgMeta):
130
130
  elif self.PGOPT['ACTS'] == self.OPTS['AS'][0]:
131
131
  self.ALLCNT = len(self.params['SF']) if 'SF' in self.params else len(self.params['LF'])
132
132
  self.cache_group_info(self.ALLCNT)
133
- self.archive_saved_files()
133
+ self.archive_in_passes(self.archive_saved_files)
134
134
  if 'CL' in self.params: self.delete_local_files()
135
135
  elif self.PGOPT['ACTS'] == self.OPTS['AW'][0]:
136
136
  self.ALLCNT = len(self.params['WF']) if 'WF' in self.params else len(self.params['LF'])
@@ -141,8 +141,7 @@ class DsArch(PgArch, PgMeta):
141
141
  elif 'XM' in self.params:
142
142
  self.crosscopy_web_files('Move')
143
143
  else:
144
- self.archive_web_files()
145
- if self.PGSIG['BPROC'] > 1: self.set_webfile_info()
144
+ self.archive_in_passes(self.archive_web_files)
146
145
  if 'CL' in self.params: self.delete_local_files()
147
146
  elif self.PGOPT['ACTS'] & self.OPTS['CG'][0]:
148
147
  self.ALLCNT = len(self.params['GI'])
@@ -355,7 +354,7 @@ class DsArch(PgArch, PgMeta):
355
354
  else:
356
355
  self.cmdlog()
357
356
 
358
- def archive_web_files(self):
357
+ def archive_web_files(self, cpass=0):
359
358
  """
360
359
  Archive local files as web files on GLADE and/or the object store.
361
360
 
@@ -369,6 +368,11 @@ class DsArch(PgArch, PgMeta):
369
368
 
370
369
  Retries the whole batch if transient errors occur, stopping when the
371
370
  error count stabilises. Sets self.RETSTAT on unresolved failures.
371
+
372
+ Args:
373
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
374
+ copy pass (copies in up to N child processes, no records) and then 2
375
+ for the record pass (see archive_in_passes())
372
376
  """
373
377
  tname = 'wfile'
374
378
  dftloc = None
@@ -379,7 +383,7 @@ class DsArch(PgArch, PgMeta):
379
383
  dslocflags = set()
380
384
  dflags = {}
381
385
  self.pglog("Archive {} Web file{} of {} ...".format(self.ALLCNT, s, dsid), self.WARNLG)
382
- if 'ZD' in self.params or 'UZ' in self.params:
386
+ if cpass != 2 and ('ZD' in self.params or 'UZ' in self.params):
383
387
  self.compress_localfile_list(self.PGOPT['CACT'], self.ALLCNT)
384
388
  if 'QF' in self.params: self.params['QF'] = self.get_bid_numbers(self.params['QF'])
385
389
  self.validate_multiple_values(tname, self.ALLCNT)
@@ -406,19 +410,15 @@ class DsArch(PgArch, PgMeta):
406
410
  self.params['SZ'] = [0]*self.ALLCNT
407
411
  self.OPTS['SZ'][2] |= 2
408
412
  if 'MC' not in self.params: self.params['MC'] = [None]*self.ALLCNT
409
- reorder = errcnt = metatotal = metacnt = self.ADDCNT = self.MODCNT = bgcnt = acnt = ocnt = 0
413
+ reorder = errcnt = metatotal = metacnt = self.ADDCNT = self.MODCNT = acnt = ocnt = 0
410
414
  perrcnt = self.ALLCNT
411
415
  efiles = [1]*self.ALLCNT
412
- if self.PGSIG['BPROC'] > 1:
413
- afiles = [None]*self.ALLCNT
414
- lfiles = [None]*self.ALLCNT
415
- ofiles = [None]*self.ALLCNT
416
416
  fnames = None
417
417
  override = self.OVERRIDE
418
418
  if not override and 'GF' in self.params: override = self.OVRIDE
419
419
  while True:
420
420
  for i in range(bidx, self.ALLCNT):
421
- if self.PGSIG['BPROC'] < 2 and i > bidx and ((i-bidx)%20) == 0:
421
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
422
422
  if metacnt >= self.PGOPT['RSMAX']:
423
423
  metatotal += self.process_metadata("W", metacnt, self.PGOPT['emerol'])
424
424
  metacnt = 0
@@ -470,14 +470,20 @@ class DsArch(PgArch, PgMeta):
470
470
  if warch and locfile == afile: warch = 0
471
471
  vsnctl = 1 if pgrec and pgrec['vindex'] and pgrec['data_size'] else 0
472
472
  chksum = self.params['MC'][i]
473
+ gdone = odone = 0
474
+ if cpass == 2 and i in self.CPFILES: # copied in the -AL copy pass; recopy only what failed
475
+ (gdone, odone) = self.check_pass_copies(i, wfile, afile if warch else None, ofile if oarch else None, bucket, lsize)
476
+ if gdone: warch = 0; acnt += 1
477
+ if odone: oarch = 0; ocnt += 1
478
+ if self.PGLOG['DSCHECK'] and (gdone or odone) and not (warch or oarch): chksize += lsize
473
479
  if warch and (vsnctl or not override):
474
480
  info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE)
475
481
  if info:
476
482
  if pgrec and chksum and pgrec['checksum'] and pgrec['checksum'] == chksum:
477
- self.pglog("Web-{}: Same-Checksum ARCHIVED at {}:{}".format(winfo, info['date_modified'], info['time_modified']), self.PGOPT['emllog'])
483
+ self.pglog("Web-{}: Same-Checksum ARCHIVED at {}:{}".format(winfo, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['emllog']))
478
484
  warch = 0
479
- elif info['data_size'] == lsize:
480
- self.pglog("Web-{}: Same-Size ARCHIVED at {}:{}".format(winfo, info['date_modified'], info['time_modified']), self.PGOPT['wrnlog'])
485
+ elif not override and info['data_size'] == lsize:
486
+ self.pglog("Web-{}: Same-Size ARCHIVED at {}:{}".format(winfo, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['wrnlog']))
481
487
  warch = 0
482
488
  elif vsnctl and not override:
483
489
  self.pglog("Web-{}: Cannot rearchive version controlled file".format(winfo), self.PGOPT['extlog'])
@@ -493,10 +499,10 @@ class DsArch(PgArch, PgMeta):
493
499
  info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
494
500
  if info:
495
501
  if pgrec and chksum and pgrec['checksum'] and pgrec['checksum'] == chksum:
496
- self.pglog("Object-{}-{}: Same-Checksum ARCHIVED at {}:{}".format(bucket, ofile, info['date_modified'], info['time_modified']), self.PGOPT['emllog'])
502
+ self.pglog("Object-{}-{}: Same-Checksum ARCHIVED at {}:{}".format(bucket, ofile, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['emllog']))
497
503
  oarch = 0
498
- elif info['data_size'] == lsize:
499
- self.pglog("Object-{}-{}: Same-Size ARCHIVED at {}:{}".format(bucket, ofile, info['date_modified'], info['time_modified']), self.PGOPT['wrnlog'])
504
+ elif not override and info['data_size'] == lsize:
505
+ self.pglog("Object-{}-{}: Same-Size ARCHIVED at {}:{}".format(bucket, ofile, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['wrnlog']))
500
506
  oarch = 0
501
507
  elif vsnctl and not override:
502
508
  self.pglog("Object-{}-{}: Cannot rearchive version controlled file".format(bucket, ofile), self.PGOPT['extlog'])
@@ -510,6 +516,10 @@ class DsArch(PgArch, PgMeta):
510
516
  dflags['O'] = bucket
511
517
  continue
512
518
  if (warch + oarch) > 0:
519
+ if cpass == 1: # -AL copy pass: copy in a child process; the record pass sets the record
520
+ self.run_copy(i, self.copy_archive_file, afile if warch else None, ofile if oarch else None,
521
+ lfile, bucket, (replace if oarch else 0), self.PGOPT['emerol']|override)
522
+ continue
513
523
  if warch:
514
524
  if not self.local_copy_local(afile, lfile, self.PGOPT['emerol']|override):
515
525
  errcnt += 1
@@ -526,40 +536,41 @@ class DsArch(PgArch, PgMeta):
526
536
  continue
527
537
  ocnt += 1
528
538
  if self.PGLOG['DSCHECK']: chksize += lsize
529
- if self.PGSIG['BPROC'] > 1:
530
- afiles[i] = afile
531
- lfiles[i] = lfile
532
- ofiles[i] = ofile
533
- bgcnt += 1
534
- if self.PGSIG['BPROC'] < 2:
535
- if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
536
- info = None
537
- if afile: # also when already in place, so a directory gets its real total size
538
- info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE)
539
- elif oarch:
540
- info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
541
- elif pgrec:
542
- info = self.get_file_origin_info(wfile, pgrec)
543
- elif locflag == 'O':
544
- info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
545
- wid = self.set_one_webfile(i, pgrec, wfile, fnames, type, info)
546
- if not wid:
547
- self.params['LF'][i] = self.params['WF'][i] = None
548
- continue
549
- if 'GX' in self.params and self.PGOPT['GXTYP'].find(type) > -1:
550
- metacnt += self.record_meta_gather('W', dsid, wfile, self.params['DF'][i])
551
- self.cache_meta_tindex(dsid, wid, 'W')
552
- if pgrec and pgrec['meta_link'] and pgrec['meta_link'] != 'N':
553
- if 'DX' in self.params or self.PGOPT['GXTYP'].find(type) < 0 and self.PGOPT['GXTYP'].find(pgrec['type']) > -1:
554
- metacnt += self.record_meta_delete('W', dsid, wfile)
555
- elif 'GI' in self.params:
556
- gindex = self.params['GI'][i]
557
- if gindex != pgrec['gindex'] and (gindex or (self.OPTS['GI'][2]&2) == 0):
558
- metacnt += self.record_meta_summary('W', dsid, gindex, pgrec['gindex'])
559
- if errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
539
+ if cpass == 1: continue # the record pass sets the record, also for a file not copied
540
+ if gdone: warch = 1 # as if copied in this pass, for the file info below
541
+ if odone: oarch = 1
542
+ if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
543
+ info = None
544
+ if afile: # also when already in place, so a directory gets its real total size
545
+ info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE)
546
+ elif oarch:
547
+ info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
548
+ elif pgrec:
549
+ info = self.get_file_origin_info(wfile, pgrec)
550
+ elif locflag == 'O':
551
+ info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
552
+ wid = self.set_one_webfile(i, pgrec, wfile, fnames, type, info)
553
+ if not wid:
554
+ self.params['LF'][i] = self.params['WF'][i] = None
555
+ continue
556
+ if 'GX' in self.params and self.PGOPT['GXTYP'].find(type) > -1:
557
+ metacnt += self.record_meta_gather('W', dsid, wfile, self.params['DF'][i])
558
+ self.cache_meta_tindex(dsid, wid, 'W')
559
+ if pgrec and pgrec['meta_link'] and pgrec['meta_link'] != 'N':
560
+ if 'DX' in self.params or self.PGOPT['GXTYP'].find(type) < 0 and self.PGOPT['GXTYP'].find(pgrec['type']) > -1:
561
+ metacnt += self.record_meta_delete('W', dsid, wfile)
562
+ elif 'GI' in self.params:
563
+ gindex = self.params['GI'][i]
564
+ if gindex != pgrec['gindex'] and (gindex or (self.OPTS['GI'][2]&2) == 0):
565
+ metacnt += self.record_meta_summary('W', dsid, gindex, pgrec['gindex'])
566
+ if cpass == 1 or errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
560
567
  perrcnt = errcnt
561
- self.pglog("Rearchive failed {}/{} Web file{} for {}".format(errcnt, self.ALLCNT. s, dsid), self.PGOPT['emllog'])
568
+ self.pglog("Rearchive failed {}/{} Web file{} for {}".format(errcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
562
569
  errcnt = self.reset_errmsg(0)
570
+ if cpass == 1: # the record pass checks the copies, retries failed ones and sets the records
571
+ self.wait_copy_children()
572
+ self.reset_errmsg(0) # the record pass finds and counts any error again
573
+ return
563
574
  if errcnt:
564
575
  if 'CL' in self.params: del self.params['CL']
565
576
  self.RETSTAT = self.reset_errmsg(errcnt)
@@ -567,13 +578,6 @@ class DsArch(PgArch, PgMeta):
567
578
  if efiles[i]: self.params['WF'][i] = ''
568
579
  if self.PGLOG['DSCHECK']:
569
580
  self.check_storage_dflags(dflags, self.PGLOG['DSCHECK'], self.PGOPT['emerol'])
570
- if bgcnt:
571
- self.check_background(None, 0, self.LOGWRN, 1)
572
- for i in range(self.ALLCNT):
573
- if afiles[i]:
574
- self.validate_gladearch(afiles[i], lfiles[i], i)
575
- if ofiles[i]:
576
- self.validate_objectarch(ofiles[i], lfiles[i], bucket, i)
577
581
  if acnt > 0:
578
582
  self.pglog("{} of {} Web file{} archived for {}".format(acnt, self.ALLCNT, s, self.params['DS']), self.PGOPT['emllog'])
579
583
  if ocnt > 0:
@@ -590,7 +594,7 @@ class DsArch(PgArch, PgMeta):
590
594
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
591
595
  if dslocflags: self.set_dataset_locflag(dsid, dslocflags.pop())
592
596
 
593
- def archive_help_files(self):
597
+ def archive_help_files(self, cpass=0):
594
598
  """
595
599
  Archive local files as dataset help/documentation files on GLADE/object store.
596
600
 
@@ -598,6 +602,11 @@ class DsArch(PgArch, PgMeta):
598
602
  file path (RTPATH['H<type>']) and registering records in the hfile table.
599
603
  Handles both GLADE (warch) and object-store (oarch) destinations based on
600
604
  the file's location flag. Sets self.RETSTAT on unresolved failures.
605
+
606
+ Args:
607
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
608
+ copy pass (copies in up to N child processes, no records) and then 2
609
+ for the record pass (see archive_in_passes())
601
610
  """
602
611
  tname = 'hfile'
603
612
  dsid = self.params['DS']
@@ -631,18 +640,14 @@ class DsArch(PgArch, PgMeta):
631
640
  self.params['SZ'] = [0]*self.ALLCNT
632
641
  self.OPTS['SZ'][2] |= 2
633
642
  if 'MC' not in self.params: self.params['MC'] = [None]*self.ALLCNT
634
- reorder = errcnt = self.ADDCNT = self.MODCNT = bgcnt = acnt = ocnt = 0
643
+ reorder = errcnt = self.ADDCNT = self.MODCNT = acnt = ocnt = 0
635
644
  perrcnt = self.ALLCNT
636
645
  efiles = [1]*self.ALLCNT
637
- if self.PGSIG['BPROC'] > 1:
638
- afiles = [None]*self.ALLCNT
639
- lfiles = [None]*self.ALLCNT
640
- ofiles = [None]*self.ALLCNT
641
646
  override = self.OVERRIDE
642
647
  fnames = None
643
648
  while True:
644
649
  for i in range(bidx, self.ALLCNT):
645
- if self.PGSIG['BPROC'] < 2 and i > bidx and ((i-bidx)%20) == 0:
650
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
646
651
  if self.PGLOG['DSCHECK']:
647
652
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
648
653
  if 'EM' in self.params:
@@ -694,14 +699,20 @@ class DsArch(PgArch, PgMeta):
694
699
  self.pglog("{}: Archive Help file from {} ...".format(hfile, locfile), self.WARNLG)
695
700
  if harch and locfile == afile: harch = 0
696
701
  chksum = self.params['MC'][i]
702
+ gdone = odone = 0
703
+ if cpass == 2 and i in self.CPFILES: # copied in the -AL copy pass; recopy only what failed
704
+ (gdone, odone) = self.check_pass_copies(i, hfile, afile if harch else None, ofile if oarch else None, bucket, lsize)
705
+ if gdone: harch = 0; acnt += 1
706
+ if odone: oarch = 0; ocnt += 1
707
+ if self.PGLOG['DSCHECK'] and (gdone or odone) and not (harch or oarch): chksize += lsize
697
708
  if harch and not self.OVERRIDE:
698
709
  info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE)
699
710
  if info:
700
711
  if pgrec and chksum and pgrec['checksum'] and pgrec['checksum'] == chksum:
701
- self.pglog("Help-{}: Same-Checksum ARCHIVED at {}:{}".format(hinfo, info['date_modified'], info['time_modified']), self.PGOPT['emllog'])
712
+ self.pglog("Help-{}: Same-Checksum ARCHIVED at {}:{}".format(hinfo, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['emllog']))
702
713
  harch = 0
703
714
  elif info['data_size'] == lsize:
704
- self.pglog("Help-{}: Same-Size ARCHIVED at {}:{}".format(hinfo, info['date_modified'], info['time_modified']), self.PGOPT['wrnlog'])
715
+ self.pglog("Help-{}: Same-Size ARCHIVED at {}:{}".format(hinfo, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['wrnlog']))
705
716
  harch = 0
706
717
  elif info is not None:
707
718
  errcnt += 1
@@ -712,10 +723,10 @@ class DsArch(PgArch, PgMeta):
712
723
  info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
713
724
  if info:
714
725
  if pgrec and chksum and pgrec['checksum'] and pgrec['checksum'] == chksum:
715
- self.pglog("Object-{}-{}: Same-Checksum ARCHIVED at {}:{}".format(bucket, ofile, info['date_modified'], info['time_modified']), self.PGOPT['emllog'])
726
+ self.pglog("Object-{}-{}: Same-Checksum ARCHIVED at {}:{}".format(bucket, ofile, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['emllog']))
716
727
  oarch = 0
717
- elif info['data_size'] == lsize:
718
- self.pglog("Object-{}-{}: Same-Size ARCHIVED at {}:{}".format(bucket, ofile, info['date_modified'], info['time_modified']), self.PGOPT['wrnlog'])
728
+ elif not override and info['data_size'] == lsize:
729
+ self.pglog("Object-{}-{}: Same-Size ARCHIVED at {}:{}".format(bucket, ofile, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['wrnlog']))
719
730
  oarch = 0
720
731
  else:
721
732
  replace = 1
@@ -725,6 +736,10 @@ class DsArch(PgArch, PgMeta):
725
736
  dflags['O'] = bucket
726
737
  continue
727
738
  if (harch + oarch) > 0:
739
+ if cpass == 1: # -AL copy pass: copy in a child process; the record pass sets the record
740
+ self.run_copy(i, self.copy_archive_file, afile if harch else None, ofile if oarch else None,
741
+ lfile, bucket, (replace if oarch else 0), self.PGOPT['emerol']|override)
742
+ continue
728
743
  if harch:
729
744
  if not self.local_copy_local(afile, lfile, self.PGOPT['emerol']|override):
730
745
  errcnt += 1
@@ -740,30 +755,31 @@ class DsArch(PgArch, PgMeta):
740
755
  continue
741
756
  ocnt += 1
742
757
  if self.PGLOG['DSCHECK']: chksize += lsize
743
- if self.PGSIG['BPROC'] > 1:
744
- afiles[i] = afile
745
- lfiles[i] = lfile
746
- ofiles[i] = ofile
747
- bgcnt += 1
748
- if self.PGSIG['BPROC'] < 2:
749
- if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
750
- info = None
751
- if harch:
752
- info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE)
753
- elif oarch:
754
- info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
755
- elif pgrec:
756
- info = self.get_file_origin_info(hfile, pgrec)
757
- elif locflag == 'O':
758
- info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
759
- hid = self.set_one_helpfile(i, pgrec, hfile, fnames, type, info)
760
- if not hid:
761
- self.params['LF'][i] = self.params['WF'][i] = None
762
- continue
763
- if errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
758
+ if cpass == 1: continue # the record pass sets the record, also for a file not copied
759
+ if gdone: harch = 1 # as if copied in this pass, for the file info below
760
+ if odone: oarch = 1
761
+ if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
762
+ info = None
763
+ if harch:
764
+ info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE)
765
+ elif oarch:
766
+ info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
767
+ elif pgrec:
768
+ info = self.get_file_origin_info(hfile, pgrec)
769
+ elif locflag == 'O':
770
+ info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
771
+ hid = self.set_one_helpfile(i, pgrec, hfile, fnames, type, info)
772
+ if not hid:
773
+ self.params['LF'][i] = self.params['WF'][i] = None
774
+ continue
775
+ if cpass == 1 or errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
764
776
  perrcnt = errcnt
765
- self.pglog("Rearchive failed {}/{} Help file{} for {}".format(errcnt, self.ALLCNT. s, dsid), self.PGOPT['emllog'])
777
+ self.pglog("Rearchive failed {}/{} Help file{} for {}".format(errcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
766
778
  errcnt = self.reset_errmsg(0)
779
+ if cpass == 1: # the record pass checks the copies, retries failed ones and sets the records
780
+ self.wait_copy_children()
781
+ self.reset_errmsg(0) # the record pass finds and counts any error again
782
+ return
767
783
  if errcnt:
768
784
  if 'CL' in self.params: del self.params['CL']
769
785
  self.RETSTAT = self.reset_errmsg(errcnt)
@@ -771,13 +787,6 @@ class DsArch(PgArch, PgMeta):
771
787
  if efiles[i]: self.params['HF'][i] = ''
772
788
  if self.PGLOG['DSCHECK']:
773
789
  self.check_storage_dflags(dflags, self.PGLOG['DSCHECK'], self.PGOPT['emerol'])
774
- if bgcnt:
775
- self.check_background(None, 0, self.LOGWRN, 1)
776
- for i in range(self.ALLCNT):
777
- if afiles[i]:
778
- self.validate_gladearch(afiles[i], lfiles[i], i)
779
- if ofiles[i]:
780
- self.validate_objectarch(ofiles[i], lfiles[i], bucket, i)
781
790
  if acnt > 0:
782
791
  self.pglog("{} of {} Help file{} archived for {}".format(acnt, self.ALLCNT, s, self.params['DS']), self.PGOPT['emllog'])
783
792
  if ocnt > 0:
@@ -791,7 +800,7 @@ class DsArch(PgArch, PgMeta):
791
800
  self.reset_rdadb_version(dsid)
792
801
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
793
802
 
794
- def archive_saved_files(self):
803
+ def archive_saved_files(self, cpass=0):
795
804
  """
796
805
  Archive local files as dataset saved files on GLADE.
797
806
 
@@ -799,6 +808,11 @@ class DsArch(PgArch, PgMeta):
799
808
  type constraints, handles version control checks, and registers or updates
800
809
  the saved file record in RDADB via set_one_savedfile(). Retries on
801
810
  transient copy failures. Sets self.RETSTAT on unresolved failures.
811
+
812
+ Args:
813
+ cpass -- 0 for a single pass; with -AL N, archive_in_passes() calls 1 for the
814
+ copy pass (copies in up to N child processes, no records) and then 2
815
+ for the record pass (see archive_in_passes())
802
816
  """
803
817
  tname = 'sfile'
804
818
  dftloc = 'G'
@@ -808,7 +822,7 @@ class DsArch(PgArch, PgMeta):
808
822
  bidx = chksize = 0
809
823
  dflags = {}
810
824
  self.pglog("Archive {} Saved file{} of {} ...".format(self.ALLCNT, s, dsid), self.WARNLG)
811
- if 'ZD' in self.params or 'UZ' in self.params:
825
+ if cpass != 2 and ('ZD' in self.params or 'UZ' in self.params):
812
826
  self.compress_localfile_list(self.PGOPT['CACT'], self.ALLCNT)
813
827
  if 'QF' in self.params: self.params['QF'] = self.get_bid_numbers(self.params['QF'])
814
828
  self.validate_multiple_values(tname, self.ALLCNT)
@@ -832,18 +846,15 @@ class DsArch(PgArch, PgMeta):
832
846
  self.params['SZ'] = [0]*self.ALLCNT
833
847
  self.OPTS['SZ'][2] |= 2
834
848
  if 'MC' not in self.params: self.params['MC'] = [None]*self.ALLCNT
835
- reorder = errcnt = self.ADDCNT = self.MODCNT = bgcnt = acnt = 0
849
+ reorder = errcnt = self.ADDCNT = self.MODCNT = acnt = 0
836
850
  perrcnt = self.ALLCNT
837
851
  efiles = [1]*self.ALLCNT
838
- if self.PGSIG['BPROC'] > 1:
839
- afiles = [None]*self.ALLCNT
840
- lfiles = [None]*self.ALLCNT
841
852
  fnames = None
842
853
  override = self.OVERRIDE
843
854
  if not override and 'GF' in self.params: override = self.OVRIDE
844
855
  while True:
845
856
  for i in range(bidx, self.ALLCNT):
846
- if self.PGSIG['BPROC'] < 2 and i > bidx and ((i-bidx)%20) == 0:
857
+ if cpass != 1 and i > bidx and ((i-bidx)%20) == 0:
847
858
  if self.PGLOG['DSCHECK']:
848
859
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
849
860
  if 'EM' in self.params:
@@ -895,14 +906,20 @@ class DsArch(PgArch, PgMeta):
895
906
  self.pglog("{}: Archive Saved file from {} ...".format(sinfo, locfile), self.WARNLG)
896
907
  vsnctl = 1 if pgrec and pgrec['vindex'] and pgrec['data_size'] else 0
897
908
  chksum = self.params['MC'][i]
909
+ if cpass == 2 and i in self.CPFILES: # copied in the -AL copy pass; recopy only if it failed
910
+ (gdone, odone) = self.check_pass_copies(i, sfile, afile if sarch else None, None, None, lsize)
911
+ if gdone:
912
+ sarch = 0
913
+ acnt += 1
914
+ if self.PGLOG['DSCHECK']: chksize += lsize
898
915
  if sarch and (vsnctl or not override):
899
916
  info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE)
900
917
  if info:
901
918
  if pgrec and chksum and pgrec['checksum'] and pgrec['checksum'] == chksum:
902
- self.pglog("Saved-{}: Same-Checksum ARCHIVED at {}:{}".format(sinfo, info['date_modified'], info['time_modified']), self.PGOPT['emllog'])
919
+ self.pglog("Saved-{}: Same-Checksum ARCHIVED at {}:{}".format(sinfo, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['emllog']))
903
920
  sarch = 0
904
- elif info['data_size'] == lsize:
905
- self.pglog("Saved-{}: Same-Size ARCHIVED at {}:{}".format(sinfo, info['date_modified'], info['time_modified']), self.PGOPT['wrnlog'])
921
+ elif not override and info['data_size'] == lsize:
922
+ self.pglog("Saved-{}: Same-Size ARCHIVED at {}:{}".format(sinfo, info['date_modified'], info['time_modified']), (0 if cpass == 1 else self.PGOPT['wrnlog']))
906
923
  sarch = 0
907
924
  elif vsnctl and not override:
908
925
  self.pglog("Saved-{}: Cannot rearchive version controlled file".format(sinfo), self.PGOPT['extlog'])
@@ -914,29 +931,32 @@ class DsArch(PgArch, PgMeta):
914
931
  dflags['G'] = self.PGLOG['DECSHOME']
915
932
  continue
916
933
  if sarch:
934
+ if cpass == 1: # -AL copy pass: copy in a child process; the record pass sets the record
935
+ self.run_copy(i, self.copy_archive_file, afile, None, lfile, None, 0, self.PGOPT['emerol']|override)
936
+ continue
917
937
  if not self.local_copy_local(afile, lfile, self.PGOPT['emerol']|override):
918
938
  errcnt += 1
919
939
  efiles[i] = 1
920
940
  continue
921
941
  acnt += 1
922
942
  if self.PGLOG['DSCHECK']: chksize += lsize
923
- if self.PGSIG['BPROC'] > 1:
924
- afiles[i] = afile
925
- lfiles[i] = lfile
926
- bgcnt += 1
927
- if self.PGSIG['BPROC'] < 2:
928
- if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
929
- info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE) # also when already in place
930
- if not info and pgrec:
931
- info = self.get_file_origin_info(sfile, pgrec)
932
- sid = self.set_one_savedfile(i, pgrec, sfile, fnames, type, info)
933
- if not sid:
934
- self.params['LF'][i] = self.params['SF'][i] = None
935
- continue
936
- if errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
943
+ if cpass == 1: continue # the record pass sets the record, also for a file not copied
944
+ if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
945
+ info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE) # also when already in place
946
+ if not info and pgrec:
947
+ info = self.get_file_origin_info(sfile, pgrec)
948
+ sid = self.set_one_savedfile(i, pgrec, sfile, fnames, type, info)
949
+ if not sid:
950
+ self.params['LF'][i] = self.params['SF'][i] = None
951
+ continue
952
+ if cpass == 1 or errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
937
953
  perrcnt = errcnt
938
954
  self.pglog("Rearchive failed {}/{} Saved file{} for {}!".format(errcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
939
955
  errcnt = self.reset_errmsg(0)
956
+ if cpass == 1: # the record pass checks the copies, retries failed ones and sets the records
957
+ self.wait_copy_children()
958
+ self.reset_errmsg(0) # the record pass finds and counts any error again
959
+ return
940
960
  if errcnt:
941
961
  if 'CL' in self.params: del self.params['CL']
942
962
  self.RETSTAT = self.reset_errmsg(errcnt)
@@ -944,10 +964,6 @@ class DsArch(PgArch, PgMeta):
944
964
  if efiles[i]: self.params['SF'][i] = ''
945
965
  if self.PGLOG['DSCHECK']:
946
966
  self.check_storage_dflags(dflags, self.PGLOG['DSCHECK'], self.PGOPT['emerol'])
947
- if bgcnt:
948
- self.check_background(None, 0, self.LOGWRN, 1)
949
- for i in range(self.ALLCNT):
950
- if afiles[i]: self.validate_gladearch(afiles[i], lfiles[i], i)
951
967
  if acnt > 0:
952
968
  self.pglog("{} of {} Saved file{} archived for {}".format(acnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
953
969
  if self.PGLOG['DSCHECK']:
@@ -981,18 +997,14 @@ class DsArch(PgArch, PgMeta):
981
997
  if bidx == self.ALLCNT: return
982
998
  self.set_rearchive_filenumber(dsid, bidx, self.ALLCNT, 4)
983
999
  chksize = self.PGLOG['DSCHECK']['size']
984
- reorder = errcnt = metatotal = metacnt = self.MODCNT = bgcnt = acnt = ocnt = 0
1000
+ reorder = errcnt = metatotal = metacnt = self.MODCNT = acnt = ocnt = 0
985
1001
  perrcnt = self.ALLCNT
986
1002
  efiles = [1]*self.ALLCNT
987
1003
  self.params['LC'] = ['B']*self.ALLCNT
988
- if self.PGSIG['BPROC'] > 1:
989
- afiles = [None]*self.ALLCNT
990
- ofiles = [None]*self.ALLCNT
991
- warchs = [None]*self.ALLCNT
992
1004
  fnames = None
993
1005
  while True:
994
1006
  for i in range(bidx, self.ALLCNT):
995
- if self.PGSIG['BPROC'] < 2 and i > bidx and ((i-bidx)%20) == 0:
1007
+ if i > bidx and ((i-bidx)%20) == 0:
996
1008
  if self.PGLOG['DSCHECK']:
997
1009
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
998
1010
  if metacnt >= self.PGOPT['RSMAX']:
@@ -1058,29 +1070,23 @@ class DsArch(PgArch, PgMeta):
1058
1070
  self.params['LC'][i] = 'O'
1059
1071
  self.delete_local_file(afile, self.PGOPT['extlog'])
1060
1072
  ocnt += 1
1061
- if self.PGSIG['BPROC'] > 1:
1062
- afiles[i] = afile
1063
- ofiles[i] = ofile
1064
- warchs[i] = warch
1065
- bgcnt += 1
1066
1073
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
1067
- if self.PGSIG['BPROC'] < 2:
1068
- if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
1069
- info = self.get_file_origin_info(wfile, pgrec)
1070
- wid = self.set_one_webfile(i, pgrec, wfile, fnames, type, info)
1071
- if not wid:
1072
- self.params['WF'][i] = None
1073
- continue
1074
- if 'GX' in self.params and self.PGOPT['GXTYP'].find(type) > -1:
1075
- metacnt += self.record_meta_gather('W', dsid, wfile, self.params['DF'][i])
1076
- self.cache_meta_tindex(dsid, wid, 'W')
1077
- if pgrec['meta_link'] and pgrec['meta_link'] != 'N':
1078
- if 'DX' in self.params or self.PGOPT['GXTYP'].find(type) < 0 and self.PGOPT['GXTYP'].find(pgrec['type']) > -1:
1079
- metacnt += self.record_meta_delete('W', dsid, wfile)
1080
- elif 'GI' in self.params:
1081
- gindex = self.params['GI'][i]
1082
- if gindex != pgrec['gindex'] and (gindex or (self.OPTS['GI'][2]&2) == 0):
1083
- metacnt += self.record_meta_summary('W', dsid, gindex, pgrec['gindex'])
1074
+ if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
1075
+ info = self.get_file_origin_info(wfile, pgrec)
1076
+ wid = self.set_one_webfile(i, pgrec, wfile, fnames, type, info)
1077
+ if not wid:
1078
+ self.params['WF'][i] = None
1079
+ continue
1080
+ if 'GX' in self.params and self.PGOPT['GXTYP'].find(type) > -1:
1081
+ metacnt += self.record_meta_gather('W', dsid, wfile, self.params['DF'][i] if 'DF' in self.params else pgrec['data_format'])
1082
+ self.cache_meta_tindex(dsid, wid, 'W')
1083
+ if pgrec['meta_link'] and pgrec['meta_link'] != 'N':
1084
+ if 'DX' in self.params or self.PGOPT['GXTYP'].find(type) < 0 and self.PGOPT['GXTYP'].find(pgrec['type']) > -1:
1085
+ metacnt += self.record_meta_delete('W', dsid, wfile)
1086
+ elif 'GI' in self.params:
1087
+ gindex = self.params['GI'][i]
1088
+ if gindex != pgrec['gindex'] and (gindex or (self.OPTS['GI'][2]&2) == 0):
1089
+ metacnt += self.record_meta_summary('W', dsid, gindex, pgrec['gindex'])
1084
1090
  if errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
1085
1091
  perrcnt = errcnt
1086
1092
  self.pglog("Recopy failed {}/{} Web file{} for {}".format(errcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
@@ -1091,13 +1097,6 @@ class DsArch(PgArch, PgMeta):
1091
1097
  if efiles[i]: self.params['WF'][i] = ''
1092
1098
  if self.PGLOG['DSCHECK']:
1093
1099
  self.check_storage_dflags(dflags, self.PGLOG['DSCHECK'], self.PGOPT['emerol'])
1094
- if bgcnt:
1095
- self.check_background(None, 0, self.LOGWRN, 1)
1096
- for i in range(self.ALLCNT):
1097
- if warchs[i]:
1098
- self.validate_gladearch(afiles[i], "{}-{}".format(bucket, ofiles[i]), i)
1099
- elif ofiles[i]:
1100
- self.validate_objectarch(ofiles[i], afiles[i], bucket, i)
1101
1100
  astr = 'Moved' if aname == 'Move' else 'Copied'
1102
1101
  if acnt > 0: self.pglog("{} of {} Web file{} Cross {} for {}".format(acnt, self.ALLCNT, s, astr, dsid), self.PGOPT['emllog'])
1103
1102
  if ocnt > 0: self.pglog("{} of {} Object file{} Cross {} for {}".format(ocnt, self.ALLCNT, s, astr, dsid), self.PGOPT['emllog'])
@@ -1135,18 +1134,14 @@ class DsArch(PgArch, PgMeta):
1135
1134
  if bidx == self.ALLCNT: return
1136
1135
  self.set_rearchive_filenumber(dsid, bidx, self.ALLCNT, 4)
1137
1136
  chksize = self.PGLOG['DSCHECK']['size']
1138
- reorder = errcnt = self.MODCNT = bgcnt = acnt = ocnt = 0
1137
+ reorder = errcnt = self.MODCNT = acnt = ocnt = 0
1139
1138
  perrcnt = self.ALLCNT
1140
1139
  efiles = [1]*self.ALLCNT
1141
1140
  self.params['LC'] = ['B']*self.ALLCNT
1142
- if self.PGSIG['BPROC'] > 1:
1143
- afiles = [None]*self.ALLCNT
1144
- ofiles = [None]*self.ALLCNT
1145
- harchs = [None]*self.ALLCNT
1146
1141
  fnames = None
1147
1142
  while True:
1148
1143
  for i in range(bidx, self.ALLCNT):
1149
- if self.PGSIG['BPROC'] < 2 and i > bidx and ((i-bidx)%20) == 0:
1144
+ if i > bidx and ((i-bidx)%20) == 0:
1150
1145
  if self.PGLOG['DSCHECK']:
1151
1146
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
1152
1147
  if 'EM' in self.params:
@@ -1222,19 +1217,13 @@ class DsArch(PgArch, PgMeta):
1222
1217
  self.params['LC'][i] = 'O'
1223
1218
  self.delete_local_file(afile, self.PGOPT['extlog'])
1224
1219
  ocnt += 1
1225
- if self.PGSIG['BPROC'] > 1:
1226
- afiles[i] = afile
1227
- ofiles[i] = ofile
1228
- harchs[i] = harch
1229
- bgcnt += 1
1230
1220
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
1231
- if self.PGSIG['BPROC'] < 2:
1232
- if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
1233
- info = self.get_file_origin_info(hfile, pgrec)
1234
- hid = self.set_one_helpfile(i, pgrec, hfile, fnames, type, info)
1235
- if not hid:
1236
- self.params['HF'][i] = None
1237
- continue
1221
+ if not fnames: fnames = self.get_field_keys(tname, None, "G") # get setting fields if not yet
1222
+ info = self.get_file_origin_info(hfile, pgrec)
1223
+ hid = self.set_one_helpfile(i, pgrec, hfile, fnames, type, info)
1224
+ if not hid:
1225
+ self.params['HF'][i] = None
1226
+ continue
1238
1227
  if errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
1239
1228
  perrcnt = errcnt
1240
1229
  self.pglog("Re{} {} Help file{} for {}".format(aname, errcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
@@ -1245,13 +1234,6 @@ class DsArch(PgArch, PgMeta):
1245
1234
  if efiles[i]: self.params['SF'][i] = ''
1246
1235
  if self.PGLOG['DSCHECK']:
1247
1236
  self.check_storage_dflags(dflags, self.PGLOG['DSCHECK'], self.PGOPT['emerol'])
1248
- if bgcnt:
1249
- self.check_background(None, 0, self.LOGWRN, 1)
1250
- for i in range(self.ALLCNT):
1251
- if harchs[i]:
1252
- self.validate_gladearch(afiles[i], "{}-{}".format(bucket, ofiles[i]), i)
1253
- elif ofiles[i]:
1254
- self.validate_objectarch(ofiles[i], afiles[i], bucket, i)
1255
1237
  astr = 'Moved' if aname == 'Move' else 'Copied'
1256
1238
  if acnt > 0: self.pglog("{} of {} Help file{} Cross {} for {}".format(acnt, self.ALLCNT, s, astr, dsid), self.PGOPT['emllog'])
1257
1239
  if ocnt > 0: self.pglog("{} of {} Object file{} Cross {} for {}".format(ocnt, self.ALLCNT, s, astr, dsid), self.PGOPT['emllog'])
@@ -1864,16 +1846,13 @@ class DsArch(PgArch, PgMeta):
1864
1846
  if bidx == self.ALLCNT: return
1865
1847
  self.set_rearchive_filenumber(dsid, bidx, self.ALLCNT, 4)
1866
1848
  chksize = self.PGLOG['DSCHECK']['size']
1867
- reorder = errcnt = self.MODCNT = bgcnt = bcnt = dcnt = 0
1849
+ reorder = errcnt = self.MODCNT = bcnt = dcnt = 0
1868
1850
  perrcnt = self.ALLCNT
1869
1851
  efiles = [1]*self.ALLCNT
1870
- if self.PGSIG['BPROC'] > 1:
1871
- qfiles = [None]*self.ALLCNT
1872
- barchs = [None]*self.ALLCNT
1873
1852
  fnames = None
1874
1853
  while True:
1875
1854
  for i in range(bidx, self.ALLCNT):
1876
- if self.PGSIG['BPROC'] < 2 and i > bidx and ((i-bidx)%20) == 0:
1855
+ if i > bidx and ((i-bidx)%20) == 0:
1877
1856
  if self.PGLOG['DSCHECK']:
1878
1857
  self.set_dscheck_dcount(i, chksize, self.PGOPT['extlog'])
1879
1858
  if 'EM' in self.params:
@@ -1930,17 +1909,12 @@ class DsArch(PgArch, PgMeta):
1930
1909
  dflags['D'] = dpoint
1931
1910
  continue
1932
1911
  dcnt += 1
1933
- if self.PGSIG['BPROC'] > 1:
1934
- qfiles[i] = qfile
1935
- barchs[i] = barch
1936
- bgcnt += 1
1937
1912
  if self.PGLOG['DSCHECK']: chksize += pgrec['data_size']
1938
- if self.PGSIG['BPROC'] < 2:
1939
- if not fnames: fnames = self.get_field_keys(tname) # get setting fields if not yet
1940
- bid = self.set_one_backfile(i, pgrec, bfile, fnames, qtype)
1941
- if not bid:
1942
- self.params['QF'][i] = None
1943
- continue
1913
+ if not fnames: fnames = self.get_field_keys(tname) # get setting fields if not yet
1914
+ bid = self.set_one_backfile(i, pgrec, bfile, fnames, qtype)
1915
+ if not bid:
1916
+ self.params['QF'][i] = None
1917
+ continue
1944
1918
  if errcnt == 0 or errcnt >= perrcnt or self.PGLOG['DSCHECK']: break
1945
1919
  perrcnt = errcnt
1946
1920
  self.pglog("Recopy {} Quasar file{} for {}".format(errcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
@@ -1951,13 +1925,6 @@ class DsArch(PgArch, PgMeta):
1951
1925
  if efiles[i]: self.params['QF'][i] = ''
1952
1926
  if self.PGLOG['DSCHECK']:
1953
1927
  self.check_storage_dflags(dflags, self.PGLOG['DSCHECK'], self.PGOPT['emerol'])
1954
- if bgcnt:
1955
- self.check_background(None, 0, self.LOGWRN, 1)
1956
- for i in range(self.ALLCNT):
1957
- if barchs[i]:
1958
- self.validate_backarch(qfiles[i], "{}-{}".format(dpoint, qfiles[i]), bpoint, i)
1959
- elif qfiles[i]:
1960
- self.validate_backarch(qfiles[i], "{}-{}".format(bpoint, qfiles[i]), dpoint, i)
1961
1928
  if bcnt > 0: self.pglog("{} of {} Backup file{} Cross Copied for {}".format(bcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
1962
1929
  if dcnt > 0: self.pglog("{} of {} Drdata file{} Cross Copied for {}".format(dcnt, self.ALLCNT, s, dsid), self.PGOPT['emllog'])
1963
1930
  if self.PGLOG['DSCHECK']:
@@ -1968,57 +1935,136 @@ class DsArch(PgArch, PgMeta):
1968
1935
  self.reset_rdadb_version(dsid)
1969
1936
  if 'EM' in self.params: self.PGLOG['PRGMSG'] = ""
1970
1937
 
1971
- def validate_gladearch(self, file, ofile, i):
1938
+ def archive_in_passes(self, archfunc):
1972
1939
  """
1973
- Verify that a GLADE archive file exists and matches the expected file info.
1940
+ Run an archive action (archive_web/help/saved_files) once, or in two passes for -AL.
1974
1941
 
1975
- Logs a fatal error when validation fails, setting the file entry at
1976
- index *i* to None so it is skipped in subsequent processing.
1942
+ With -AL N (N > 1, so PGSIG['MPROC'] > 1) the action runs twice:
1943
+ 1. copy pass -- the usual checks, then each file that needs a copy is copied
1944
+ in one of up to N child processes (run_copy()); no records are set;
1945
+ 2. record pass -- the usual single process loop; a file the copy pass copied
1946
+ is checked (check_pass_copies()) instead of copied again, and copied again
1947
+ only if its child copy failed; records, dscheck progress and metadata are
1948
+ all set here exactly as without -AL.
1977
1949
 
1978
1950
  Args:
1979
- file -- original local file name (for error messages)
1980
- ofile -- expected absolute GLADE archive path to check
1981
- i -- file index (used to null out params entries on failure)
1982
- """
1983
- info = self.check_local_file(file, 6, self.PGOPT['errlog']|self.PFSIZE)
1984
- if not info:
1985
- self.pglog("Error archiving {} to {}".format(ofile, file), self.LGEREX)
1986
- elif self.OPTS['SZ'][2]&2 and self.params['SZ'] and info['data_size'] != self.params['SZ'][i]:
1987
- self.pglog("Wrong Sizes: ({}){}/({}){}".format(file, info['data_size'], ofile, self.params['SZ'][i]), self.LGEREX)
1988
- else:
1989
- self.set_local_mode(file, 1, self.PGLOG['FILEMODE'], info['mode'], info['logname'], self.PGOPT['errlog'])
1951
+ archfunc -- bound archive method taking the pass number (0, 1 or 2)
1952
+ """
1953
+ if self.PGSIG['MPROC'] < 2: return archfunc()
1954
+ self.CPFILES = {}
1955
+ archfunc(1)
1956
+ archfunc(2)
1957
+ self.CPFILES = {}
1990
1958
 
1991
- def validate_objectarch(self, file, ofile, bucket, i):
1959
+ def run_copy(self, i, copyfunc, *args):
1992
1960
  """
1993
- Verify that an object-store file exists and matches the expected file info.
1961
+ Run the copy of file *i* in a child process for the -AL copy pass.
1962
+
1963
+ Waits for a free child slot (up to PGSIG['MPROC'] running), forks, and runs
1964
+ copyfunc(*args) in the child, which then leaves with os._exit(): it skips the
1965
+ parent's exit handling, so it cannot close the parent's database connection,
1966
+ send email or end the run; NOQUIT also keeps an EXITLG error in the copy from
1967
+ exiting early. The parent records the child's process name in
1968
+ self.CPFILES[i] (its exit code then shows up in self.CEXITS) and returns at
1969
+ once. If no child can be started, the copy runs in the parent and
1970
+ self.CPFILES[i] is 1 or 0 for its result.
1994
1971
 
1995
1972
  Args:
1996
- file -- original local file name (for error messages)
1997
- ofile -- object-store key path to check
1998
- bucket -- object-store bucket name
1999
- i -- file index (used to null out params entries on failure)
1973
+ i -- file index
1974
+ copyfunc -- function doing the copy; returns true on success
1975
+ args -- arguments of copyfunc
1976
+ """
1977
+ for fh in (sys.stdout, sys.stderr): # nothing buffered is written twice by the child
1978
+ try:
1979
+ fh.flush()
1980
+ except Exception:
1981
+ pass
1982
+ pname = "dsarch_copy_{}".format(i)
1983
+ self.CEXITS.pop(pname, None)
1984
+ stat = self.start_child(pname, self.LOGWRN, 1)
1985
+ if stat <= 0: # cannot fork, copy in the parent
1986
+ self.CPFILES[i] = 1 if copyfunc(*args) else 0
1987
+ return self.CPFILES[i]
1988
+ if self.PGSIG['PPID'] > 1: # in the child process
1989
+ self.PGLOG['NOQUIT'] = 1
1990
+ ret = 0
1991
+ try:
1992
+ ret = copyfunc(*args)
1993
+ except BaseException as e:
1994
+ self.pglog("Copy {}: {}".format(i, str(e)), self.LOGERR)
1995
+ for fh in (sys.stdout, sys.stderr):
1996
+ try:
1997
+ fh.flush()
1998
+ except Exception:
1999
+ pass
2000
+ os._exit(0 if ret else 1)
2001
+ self.CPFILES[i] = pname # the child's exit code shows up in self.CEXITS[pname]
2002
+ return 1
2003
+
2004
+ def wait_copy_children(self):
2005
+ """Wait until all child processes of the -AL copy pass are done."""
2006
+ if self.PGSIG['MPROC'] > 1: self.check_child(None, 0, self.LOGWRN, 1)
2007
+
2008
+ def copy_archive_file(self, afile, ofile, lfile, bucket, replace, logact):
2009
+ """
2010
+ Copy one local file to GLADE and/or the object store; run in a copy pass child.
2011
+
2012
+ Args:
2013
+ afile -- absolute GLADE target path, or None for no GLADE copy
2014
+ ofile -- object store key, or None for no object copy
2015
+ lfile -- local source file
2016
+ bucket -- object store bucket
2017
+ replace -- true to delete the existing object first
2018
+ logact -- logging flags for the copies
2019
+
2020
+ Returns:
2021
+ 1 if all copies succeeded, 0 otherwise
2000
2022
  """
2001
- info = self.check_object_file(file, bucket, 0, self.PGOPT['errlog'])
2002
- if not info:
2003
- self.pglog("Error archiving {} to {}-{}".format(ofile, bucket, file), self.LGEREX)
2004
- elif self.OPTS['SZ'][2]&2 and self.params['SZ'] and info['data_size'] != self.params['SZ'][i]:
2005
- self.pglog("Wrong Sizes: ({}-{}){}/({}){}".format(bucket, file, info['data_size'], ofile, self.params['SZ'][i]), self.LGEREX)
2023
+ if afile and not self.local_copy_local(afile, lfile, logact): return 0
2024
+ if ofile:
2025
+ if replace: self.delete_object_file(ofile, bucket)
2026
+ if not self.local_copy_object(ofile, lfile, bucket, None, logact): return 0
2027
+ return 1
2006
2028
 
2007
- def validate_backarch(self, file, ofile, endpoint, i):
2029
+ def check_pass_copies(self, i, fname, afile, ofile, bucket, lsize):
2008
2030
  """
2009
- Verify that a Quasar backup file exists at the given endpoint.
2031
+ Check the targets the -AL copy pass copied a file to, for the record pass.
2032
+
2033
+ A copy whose child exited with an error counts as failed even when the
2034
+ target holds an older file of the same size (as with -OE); when the exit
2035
+ code is unknown, a target of size *lsize* counts as copied.
2010
2036
 
2011
2037
  Args:
2012
- file -- original local file name (for error messages)
2013
- ofile -- backup file path on the Quasar endpoint
2014
- endpoint -- Globus endpoint identifier string
2015
- i -- file index (used to null out params entries on failure)
2016
- """
2017
- info = self.check_backup_file(file, endpoint, 0, self.PGOPT['errlog'])
2018
- if not info:
2019
- self.pglog("Error archiving {} to {}-{}".format(ofile, endpoint, file), self.LGEREX)
2020
- elif self.OPTS['SZ'][2]&2 and self.params['SZ'] and info['data_size'] != self.params['SZ'][i]:
2021
- self.pglog("Wrong Sizes: ({}-{}){}/({}){}".format(endpoint, file, info['data_size'], ofile, self.params['SZ'][i]), self.LGEREX)
2038
+ i -- file index, the key of self.CPFILES
2039
+ fname -- archived file name, for the message
2040
+ afile -- GLADE target path the copy pass copied to, or None
2041
+ ofile -- object store key the copy pass copied to, or None
2042
+ bucket -- object store bucket
2043
+ lsize -- size of the local source file
2044
+
2045
+ Returns:
2046
+ (gdone, odone) -- 1 for each target that holds the file with size *lsize*;
2047
+ a 0 for a target given means its copy failed and is to be copied again
2048
+ """
2049
+ gdone = odone = 0
2050
+ cstat = self.CPFILES.get(i)
2051
+ if isinstance(cstat, str): # copied in a child: failed if it exited with an error
2052
+ ecode = self.CEXITS.get(cstat)
2053
+ failed = ecode is not None and ecode != 0
2054
+ else: # copied in the parent: 1 or 0
2055
+ failed = cstat == 0
2056
+ if failed:
2057
+ self.pglog("{}: copy in child process failed, copy again".format(fname), self.PGOPT['wrnlog'])
2058
+ return (0, 0)
2059
+ if afile:
2060
+ info = self.check_local_file(afile, 1, self.PGOPT['emerol']|self.PFSIZE)
2061
+ if info and info['data_size'] == lsize: gdone = 1
2062
+ if ofile:
2063
+ info = self.check_object_file(ofile, bucket, 1, self.PGOPT['emerol'])
2064
+ if info and info['data_size'] == lsize: odone = 1
2065
+ if afile and not gdone or ofile and not odone:
2066
+ self.pglog("{}: copy in child process failed, copy again".format(fname), self.PGOPT['wrnlog'])
2067
+ return (gdone, odone)
2022
2068
 
2023
2069
  def get_dataset_info(self):
2024
2070
  """
@@ -1249,7 +1249,8 @@ IMMA_2.4_2007.tar<:>37<:>IMMA<:>Z.TAR<:>D<:>656820224<:>
1249
1249
  -(NE|NoEmail) - suppresses email notification on failure
1250
1250
  -(NT|NoTrim) - skips trimming of spaces and comments from input
1251
1251
  values, speeding up input file processing
1252
- -(OE|OverrideExist) - overwrites existing Saved files
1252
+ -(OE|OverrideExist) - overwrites existing Saved files, even ones of the
1253
+ same size
1253
1254
  -(RO|ResetOrder) - resets display order indices to match the order
1254
1255
  files are given per -SF. Alternatively, use -ON
1255
1256
  (-OrderNames) to reorder all files in the dataset
@@ -1347,7 +1348,8 @@ IMMA_2.4_2007.tar<:>37<:>IMMA<:>Z.TAR<:>D<:>656820224<:>
1347
1348
  -(NE|NoEmail) - suppresses email notification on failure
1348
1349
  -(NT|NoTrim) - skips trimming of spaces and comments from input
1349
1350
  values, speeding up input file processing
1350
- -(OE|OverrideExist) - overwrites existing Web files
1351
+ -(OE|OverrideExist) - overwrites existing Web files, even ones of the
1352
+ same size
1351
1353
  -(RO|ResetOrder) - resets display order indices to match the order
1352
1354
  files are given per -WF. Alternatively, use -ON
1353
1355
  (-OrderNames) to reorder all files in the dataset
@@ -1438,7 +1440,8 @@ IMMA_2.4_2007.tar<:>37<:>IMMA<:>Z.TAR<:>D<:>656820224<:>
1438
1440
  -(NE|NoEmail) - suppresses email notification on failure
1439
1441
  -(NT|NoTrim) - skips trimming of spaces and comments from input
1440
1442
  values, speeding up input file processing
1441
- -(OE|OverrideExist) - overwrites existing Help files
1443
+ -(OE|OverrideExist) - overwrites existing Help files, even ones of the
1444
+ same size
1442
1445
  -(RO|ResetOrder) - resets display order indices to match the order
1443
1446
  files are given per -HF. Alternatively, use -ON
1444
1447
  (-OrderNames) to reorder all files in the dataset
@@ -1988,9 +1991,16 @@ They come in two forms:
1988
1991
  A single-value Info option accepts exactly one value. Providing no
1989
1992
  value or more than one causes an error.
1990
1993
 
1991
- -AL or -AsyncLimit (Alias: -AsynchronousLimit), caps the number of
1992
- simultaneous background processes spawned for 'cp' and 'gatherxml'
1993
- calls. Has no effect when running as a PBS batch job.
1994
+ -AL or -AsyncLimit (Alias: -AsynchronousLimit), the number of
1995
+ processes that run at the same time. For -AW, -AH and -AS (not with
1996
+ -XC/-XM), up to this many child processes copy the files in a first
1997
+ pass; a second pass then checks each copy, copies again any that
1998
+ failed, and sets the file records as usual. It also caps the
1999
+ 'gatherxml' and 'dcm' calls run in the background. Cross copies
2000
+ (-XC/-XM) and -AQ run one file at a time. Works with -BP (-d) too:
2001
+ the PBS batch job then gets one cpu and 1gb of memory per process
2002
+ (select=1:ncpus=N:mem=Ngb) unless -QS already requests select,
2003
+ ncpus or mem.
1994
2004
 
1995
2005
  -AO or -ActOption (Alias: -ActionOption), the token that marks
1996
2006
  Action and Mode options in input files. Defaults to '<!>'. Change
@@ -2063,7 +2073,9 @@ They come in two forms:
2063
2073
 
2064
2074
  -QS or -QsubOptions (Alias: -PBSOptions), passes options to qsub
2065
2075
  when running 'dsarch' as a PBS batch job. Quote the value on the
2066
- command line, e.g., -QS '-l walltime=12:00:00'.
2076
+ command line, e.g., -QS '-l walltime=12:00:00'. With -AL N (N > 1),
2077
+ 'select=1:ncpus=N:mem=Ngb' is added to these options unless they
2078
+ already request select, ncpus or mem.
2067
2079
 
2068
2080
  -VS or -ValidSize, the minimum acceptable file size (in bytes) for
2069
2081
  archiving. Files smaller than this threshold are rejected.
@@ -2111,7 +2123,9 @@ They come in two forms:
2111
2123
  exclude specific hosts (e.g., '-d PBS' to use PBS batch hosts).
2112
2124
  An optional retry limit (1-99) can also be specified, e.g.,
2113
2125
  '-d 5'; defaults to 2. Archive actions (-AS, -AW, -AH, -AQ) retry
2114
- automatically after storage system outage.
2126
+ automatically after storage system outage. Add -AL N to run the
2127
+ batch job with N parallel copies and a matching PBS cpu/memory
2128
+ request, e.g., '-d -AL 4'.
2115
2129
 
2116
2130
  -BS or -BackupStatus, the current Quasar backup state for a file:
2117
2131
  'A' - archived to Quasar (complete)
@@ -694,16 +694,19 @@ class PgArch(PgOPT, PgCMD, PgSplit):
694
694
  self.PGLOG['LOGMASK'] &= ~self.EMLALL # turn off all email acts
695
695
  if 'EM' in self.params: del self.params['EM']
696
696
  if 'BP' in self.params:
697
- if 'AL' in self.params: self.params['AL'] = 0
698
697
  if 'EM' not in self.params: self.PGLOG['LOGMASK'] &= ~self.EMLALL # turn off email if not yet
699
698
  self.PGOPT['emerol'] |= self.EXITLG
700
699
  # set command line Batch options
701
700
  self.set_qsub_walltime()
702
701
  self.set_batch_options(self.params, 1)
702
+ self.set_qsub_resources() # after -QS, so a user resource request is kept
703
703
  self.init_dscheck(0, '', "dsarch", self.params['DS'], cact, self.PGLOG['CURDIR'], self.params['LN'],
704
704
  self.params['BP'], self.PGOPT['emerol'])
705
705
  if 'EM' in self.params: self.PGOPT['emlerr'] = self.LOGERR|self.EMEROL
706
- self.start_none_daemon('dsarch', self.PGOPT['CACT'], self.params['LN'], 1, self.params['WI'], 1, self.params['AL'])
706
+ # -AL N: up to N child processes copy files in the copy pass of -AW/-AH/-AS (see
707
+ # DsArch.archive_in_passes()) and up to N background processes run gatherxml/dcm
708
+ pcnt = self.params['AL'] if self.params['AL'] and self.params['AL'] > 1 else 1
709
+ self.start_none_daemon('dsarch', self.PGOPT['CACT'], self.params['LN'], pcnt, self.params['WI'], 1, pcnt)
707
710
 
708
711
  def set_qsub_walltime(self):
709
712
  """
@@ -734,7 +737,30 @@ class PgArch(PgOPT, PgCMD, PgSplit):
734
737
  hr = 24
735
738
  self.set_one_boption('qoptions', '-l walltime={}:00:00'.format(hr), 1)
736
739
  return
737
-
740
+
741
+ def set_qsub_resources(self):
742
+ """
743
+ Request PBS cpus and memory for the -AL background processes of a -d batch job.
744
+
745
+ With -AL N (N > 1) the batch job runs up to N background copies at once,
746
+ so its PBS chunk gets select=1:ncpus=N:mem=Ngb (1gb per process), added
747
+ to the existing '-l' list of the qsub options (after any walltime) or as
748
+ a new '-l'. Leaves the qsub options alone when -AL is under 2, when they
749
+ already request select/ncpus/mem (a -QS from the user wins), or when they
750
+ are dynamic options starting with '!'.
751
+ """
752
+ pcnt = self.params['AL'] if 'AL' in self.params else 0
753
+ if not pcnt or pcnt < 2: return
754
+ qopts = self.BOPTIONS['qoptions'] or ''
755
+ if qopts.startswith('!') or re.search(r'(select|ncpus|mem)=', qopts): return
756
+ res = "select=1:ncpus={0}:mem={0}gb".format(pcnt)
757
+ ms = re.search(r'-l\s+(\S+)', qopts)
758
+ if ms:
759
+ qopts = qopts[:ms.end(1)] + ',' + res + qopts[ms.end(1):]
760
+ else:
761
+ qopts = (qopts + ' ' if qopts else '') + '-l ' + res
762
+ self.set_one_boption('qoptions', qopts, 1)
763
+
738
764
  def build_localfile_list(self, cact, dir):
739
765
  """
740
766
  Populate params['LF'] from all files found under a local directory.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dsarch
3
- Version: 3.0.16
3
+ Version: 3.0.18
4
4
  Summary: RDA Python package for utility program dsarch archive RDA data
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dsarch
@@ -11,7 +11,7 @@ Classifier: Development Status :: 5 - Production/Stable
11
11
  Requires-Python: >=3.7
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: rda_python_common
14
+ Requires-Dist: rda_python_common>=3.0.31
15
15
  Requires-Dist: rda_python_setuid
16
16
  Requires-Dist: rda_python_miscs
17
17
  Dynamic: license-file
@@ -1,3 +1,3 @@
1
- rda_python_common
1
+ rda_python_common>=3.0.31
2
2
  rda_python_setuid
3
3
  rda_python_miscs