"""Parse on-share manifest.json / preinstall.json into draft DB rows. The reverse of serializer.py. `build_scope` turns a parsed manifest dict into a ManifestScope with its ordered entries and child rows (not yet committed). `discover_share` walks a GE-Enforce share root and yields (scopename, phase, manifest_dict) for common + every gea-shopfloor-* runtime manifest, skipping `.bak` variants. `load_preinstall` reads a preinstall.json. Import is a rebuild: the caller replaces a scope's draft rows so re-running is idempotent. """ import json import os from .models import ( ManifestScope, ManifestEntry, ManifestEntryPcType, ManifestEntryHostname, ManifestEntryMachineNumber, ManifestInUseCheck, ManifestInUseCheckProcess, ) from .serializer import _SCALAR_FIELDS, _BOOL_FLAGS # manifest key -> model attr (reverse of the serializer's scalar map). _KEY_TO_ATTR = {mk: attr for attr, mk in _SCALAR_FIELDS} _FLAG_TO_ATTR = {mk: attr for attr, mk in _BOOL_FLAGS} # Scalar model attributes that a populate resets (so an update clears fields the # new payload omits). name/entrytype are required and set explicitly. _RESETTABLE_ATTRS = [attr for _, attr in _SCALAR_FIELDS if attr not in ('name', 'entrytype')] + ['comment', 'regvalue'] def populate_entry(entry, entry_dict): """Set every field + child on an existing (or new) ManifestEntry from one Applications[] entry dict. Existing children are replaced. Returns entry.""" entry.name = entry_dict.get('Name') entry.entrytype = entry_dict.get('Type') entry.comment = entry_dict.get('_comment') for attr in _RESETTABLE_ATTRS: if attr in ('comment', 'regvalue'): continue setattr(entry, attr, None) for key, attr in _KEY_TO_ATTR.items(): if attr in ('name', 'entrytype'): continue if key in entry_dict and entry_dict[key] is not None: setattr(entry, attr, entry_dict[key]) # RegValue stored as its raw JSON literal so DWord vs string typing survives. entry.regvalue = (json.dumps(entry_dict['RegValue']) if 'RegValue' in entry_dict else None) for key, attr in _FLAG_TO_ATTR.items(): setattr(entry, attr, bool(entry_dict.get(key))) # Multi-value filters -> child rows (replace, preserve order). entry.pctypes = [ManifestEntryPcType(sortorder=i, pctypevalue=value) for i, value in enumerate(entry_dict.get('PCTypes') or [])] entry.hostnames = [ManifestEntryHostname(sortorder=i, hostnamepattern=value) for i, value in enumerate(entry_dict.get('TargetHostnames') or [])] entry.machinenumbers = [ ManifestEntryMachineNumber(sortorder=i, machinenumber=str(value)) for i, value in enumerate(entry_dict.get('TargetMachineNumbers') or [])] # InUseCheck object + Processes[]. inuse = entry_dict.get('InUseCheck') if inuse: check = ManifestInUseCheck(behavior=inuse.get('Behavior')) for i, proc in enumerate(inuse.get('Processes') or []): check.processes.append(ManifestInUseCheckProcess( sortorder=i, processname=proc.get('Name'), exepath=proc.get('ExePath'), gracefulclosetimeoutsec=proc.get('GracefulCloseTimeoutSec'))) entry.inusecheck = check else: entry.inusecheck = None return entry def build_entry(entry_dict, sortorder): """Build a new ManifestEntry (+ children) from one Applications[] entry.""" entry = ManifestEntry(sortorder=sortorder) return populate_entry(entry, entry_dict) def build_scope(scopename, phase, manifest_dict, iscommon=False): """Build a ManifestScope (+ entries) from a parsed manifest dict.""" scope = ManifestScope( scopename=scopename, phase=phase, manifestversion=str(manifest_dict.get('Version', '1.0')), topcomment=manifest_dict.get('_comment'), site=manifest_dict.get('Site'), iscommon=iscommon) for i, entry_dict in enumerate(manifest_dict.get('Applications') or []): scope.entries.append(build_entry(entry_dict, i)) return scope def discover_share(shareroot): """Yield (scopename, phase, manifest_dict) for runtime manifests on a share. Reads `common/manifest.json` and every `gea-shopfloor-*/manifest.json`. Skips `.bak` / `.pre-*.bak` variants (only the exact `manifest.json`). """ for name in sorted(os.listdir(shareroot)): if name != 'common' and not name.startswith('gea-shopfloor-'): continue path = os.path.join(shareroot, name, 'manifest.json') if not os.path.isfile(path): continue with open(path) as handle: manifest = json.load(handle) yield name, 'runtime', manifest def load_manifest_file(path): """Parse a single manifest.json / preinstall.json file.""" with open(path) as handle: return json.load(handle)