from __future__ import absolute_import
import json
from datetime import datetime
from decisionTable import DecisionTable
from mylinux import view
from mylinux.constants import error as ERR
from mylinux.libs import AppErr
[docs]class Installed:
'''Class for handeling installed file.
Args:
path (str): Absolute path to installed file.
Attributes:
__inited (boo): Was class allready inited?
__selectTableRule (str): Define CSV like decision table rule for selecting packages.
__executeTableRule (str): Define CSV like decision table rule for executing packages.
__selectTable (cla): ``DecisionTable`` class activated with select table rule.
__executeTable (cla): ``DecisionTable`` class activated with execute table rule.
__requirdPackageKeys (arr-str): Required packages keys in every package which is installed.
__path (str): See args info.
__data (dic): All installed data.
Raises:
AppErr.developer: If class was already inited.
Other Parameters:
Set data to __data.
Note:
Installed class for every package executing/selecting action
is using ``decisionTable`` package which makes logic
visual more nice than raw if/else/elif code...
For more info visit: https://decisiontable.readthedocs.org
'''
__inited = False
__selectTableRule = """
packageState configState config action new_packageState new_configState
================================================================================================
None None False install install install
ok ok False purge purge purge
. . True purge ok purge
. . True update ok update
ok error False purge purge purge
. . True update ok update
. . True install ok install
. . True purge ok purge
error install False purge purge purge
. . False install install install
None error True purge None purge
error purge False purge purge purge
ok None True install ok install
. . False purge purge purge
* * * * ERROR ERROR"""
__executeTableRule = """
packageState configState execute err_package err_config new_packageState new_configState
===============================================================================================================
install install installAll False False ok ok
. . . . True ok error
. . . True None error install
purge purge purgeAll False False None None
. . . False True None error
. . . True None error purge
ok purge purgeConfig None True ok error
. . . None False ok None
ok update updateConfig None False ok ok
. . . None True ok error
ok install installConfig None False ok ok
. . . None True ok error
None purge purgeConfig None False None None
. . . None True None error"""
__selectTable = DecisionTable(__selectTableRule)
__executeTable = DecisionTable(__executeTableRule)
__requiredPackageKeys = ['created', 'configState', 'stdout', 'lastChange', 'stderr', 'packageState']
def __init__(self, path):
if not Installed.__inited:
Installed.__inited = True
self.__path = path
self.__data = None
self.__setData()
else:
raise AppErr.developer(ERR.singletone)
[docs] def __call__(self):
"""
Return:
__data
"""
return self.__data
[docs] def __getitem__(self, item):
"""
Args:
item (str)
Return:
__data[item]
"""
return self.__data[item]
@property
def path(self):
"""
Return:
__path
"""
return self.__path
@property
def selectTableRule(self):
"""
Return:
__selectTableRule
"""
return self.__selectTableRule
@property
def executeTableRule(self):
"""
Return:
__executeTableRule
"""
return self.__executeTableRule
@property
def requiredPackageKeys(self):
"""
Return:
__requiredPackageKeys
"""
return self.__requiredPackageKeys
def __setData(self):
"""Seting installed data.
Other Parameters:
Check if file on __path exist.
Check if json format is correct.
If pre checks fails show error report.
Set __data with data from __path.
"""
error = []
try:
with open(self.__path) as f:
try:
self.__data = json.load(f)
except Exception as err:
error.append(['ERROR', 'Json load', ' '.join(list(err.args)) ])
except Exception:
error.append(['FAIL', 'File open', 'No such file or directory'])
if not error:
for package in self.__data:
for reqKey in self.__requiredPackageKeys:
if not reqKey in self.__data[package]:
error.append(['ERROR', 'Json key', ERR.Format.keyMissingIn(reqKey, package)])
if error:
view.Tli.raiseError(AppErr.model,self.__path,error)
def __createPackage(self, packageName):
"""Create new installed package.
Args:
packageName (str): Name for new package.
Other Parameters:
Check if packageName don't exist in installed.
If pre check fails show error report.
Create new package in installed.
"""
if not packageName in self.__data:
self.__data[packageName] = {
'lastChange': str(datetime.now()),
'created': str(datetime.now()),
'stdout': None,
'stderr': None,
'packageState': None,
'configState': None
}
else:
view.Tli.raiseError(AppErr.user,'Create package',
['FAIL', packageName, ERR.Format.alreadyExistIn('installed')],
headers=['Status', 'Package', 'Message']
)
def __changePackageValues(self, packageName, **args):
"""Handler for updating packages data.
Args:
packageName (str): Package name whose values are going to change.
**args : Keys and values for package data.
Other Parameters:
Check if key in args is permited to be changed.
If check fails show error report.
Set key data to new value from args.
Update last change value in package.
"""
change = False
error = []
for key in args:
if not key in ['stdout', 'stderr', 'packageState', 'configState']:
error.append(['ERROR', 'Json key', ERR.Format.keyNotValidFor(key, 'installed package')])
else:
self.__data[packageName][key] = args[key]
change = True
if error:
view.Tli.raiseError(
AppErr.developer,
'{0} in {1}'.format(self.__changePackageValues.__name__,__name__),
error
)
elif change:
self.__data[packageName]['lastChange'] = str(datetime.now())
[docs] def getNames(self):
"""Get packages names from installed
Return:
Array of str. of installed packages.
"""
return [key for key in self.__data]
[docs] def packageAfterExecution(self, packageName, err_package, err_config, stdout='', stderr=''):
"""Set package in installed new set of data
Args:
packageName (str): Package which states will be updated.
err_package (str): Does have package after execution error?
err_config (str): Does config have execution error?
stdout (str): Stout from execution.
stderr (str): Possible error message from terminal.
Other Parameters:
Get package and config state base on execution information.
Update package in installed with new set of data.
"""
new_packageState, new_configState = self.__executeTable.decision(
['new_packageState', 'new_configState'],
packageState=self.__data[packageName]['packageState'],
configState=self.__data[packageName]['configState'],
err_package=err_package,
err_config=err_config
)
self.__changePackageValues(
packageName,
packageState=new_packageState,
configState=new_configState,
stdout=stdout,
stderr=stderr
)
[docs] def decidePackageExecution(self, packageName):
"""Decide what action should be executed on package.
Args:
packageName (str): Package name
Returns:
[package state executing decision, config -//- decision]
"""
return self.__executeTable.decision(
['execute'],
packageState=self.__data[packageName]['packageState'],
configState=self.__data[packageName]['configState']
)
[docs] def getStates(self, key, packageNames, filler):
"""Hellper method for TUI select class.
Get infos data value from all installed package key.
If package is not installed replace states with filler.
Args:
key (str): Package key from installed package data.
packageNames (arr-str): All package names so that key value is extracted.
filler (str): If package is not in installed replace value with this.
Return:
If not packageNames return arr-str of all key values from installed packages data.
If packageNames return arr-str of all selected package key values from installed packages data.
"""
if packageNames:
return [self.__data[package][key] if package in self.__data else filler for package in packageNames]
else:
return [package[key] for package in self.__data]
[docs] def createOrUpdateSelected(self, config, action, packageNames):
"""Method for creation or updating installed packages data.
If package in installed exist it will update its data. Else
the package will be created.
Args:
config (boo): Is action want to be executed on configs too?
action (str): What action will be made on packageNames?
packageNames (arr-str): What packages names should be executed with an action?
Other Parameters:
If package do not exist in installed create one with None package state and None config state.
If package do not exist but don't have right action show error report.
Make select decision for package base on action and config boolean value.
If right decision was not finded show hinting report.
If decision was finded change package values.
"""
error = []
for packageName in packageNames:
# If not exist create package with states: None,None
if not packageName in self.__data:
if action == 'install' and not config:
self.__createPackage(packageName)
else:
view.Tli.raiseError(
AppErr.user,
self.__path,
['FAIL', packageName, ERR.Format.notInstalled()],
headers=view.Tli.reportHeaders
)
# Get decisions return from decision table
new_packageState, new_configState = self.__selectTable.decision(
['new_packageState', 'new_configState'],
packageState=self.__data[packageName]['packageState'],
configState=self.__data[packageName]['configState'],
config=config,
action=action,
)
# Check if new states have error response from decision table
if new_configState == 'ERROR' or new_packageState == 'ERROR':
# Find hinting for the current state...
allConfigs, allActions = self.__selectTable.allDecisions(
['config', 'action'],
packageState=self.__data[packageName]['packageState'],
configState=self.__data[packageName]['configState'],
)
hinting = []
for i in range(len(allConfigs)):
if str(allConfigs[i]) != '*' and str(allActions[i]) != '*':
if allConfigs[i] == 'True':
hinting.append('config ' + allActions[i])
else:
hinting.append(allActions[i])
error.append([
'FAIL',
packageName,
self.__data[packageName]['packageState'],
self.__data[packageName]['configState'],
'||'.join(hinting)
])
continue
else:
# If every thing went ok update installed package values
self.__changePackageValues(packageName,
packageState=new_packageState,
configState=new_configState,
)
if error:
view.Tli.raiseError(
AppErr.user,
'Select table rule conflict',
error,
headers=['Status', 'Package', 'packageState', 'configState', 'Possible select actions']
)
[docs] def override(self):
"""Update or override __data variable
Other Parameters:
Check if file on __path exist.
Check if json format is ok.
If pre checks fail show error report.
"""
try:
with open(self.__path, 'w') as f:
try:
json.dump(self.__data, f, indent=4)
except Exception as err:
view.Tli.raiseError(
AppErr.model,
self.__path,
['ERROR', 'Json dump', ' '.join(list(err.args))]
)
except Exception:
view.Tli.raiseError(
AppErr.model,
self.__path,
['ERROR', 'File write', "Can't write to file"]
)