vmmgr/usr/lib/python3.8/vmmgr/actionqueue.py

129 lines
4.8 KiB
Python

# -*- coding: utf-8 -*-
from enum import Enum
from collections import deque
from threading import Lock
from spoc.config import LOCK_FILE
from spoc.flock import locked
class ActionItemType(Enum):
IMAGE_DOWNLOAD = 1
IMAGE_UNPACK = 2
IMAGE_DELETE = 3
APP_DOWNLOAD = 4
APP_UNPACK = 5
APP_INSTALL = 6
APP_UPDATE = 7
APP_UNINSTALL = 8
class ActionItem:
def __init__(self, action_type, key, action, show_progress=True):
self.type = action_type
self.key = key
self.action = action
self.show_progress = show_progress
self.units_total = 1
self.units_done = 0
def run(self):
if self.show_progress:
self.action(self)
else:
self.action()
self.units_done = 1
class ActionAppQueue:
def __init__(self, action):
self.action = action
self.queue = []
self.started = False
self.exception = None
self.index = 0
def download_image(self, image):
self.queue.append(ActionItem(ActionItemType.IMAGE_DOWNLOAD, image.name, image.download))
self.queue.append(ActionItem(ActionItemType.IMAGE_UNPACK, image.name, image.unpack_downloaded))
def delete_image(self, image):
self.queue.append(ActionItem(ActionItemType.IMAGE_DELETE, image.name, image.delete, False))
def install_app(self, app):
self.queue.append(ActionItem(ActionItemType.APP_DOWNLOAD, app.name, app.download))
self.queue.append(ActionItem(ActionItemType.APP_UNPACK, app.name, app.unpack_downloaded))
self.queue.append(ActionItem(ActionItemType.APP_INSTALL, app.name, app.install, False))
def update_app(self, app):
self.queue.append(ActionItem(ActionItemType.APP_DOWNLOAD, app.name, app.download))
self.queue.append(ActionItem(ActionItemType.APP_UNPACK, app.name, app.unpack_downloaded))
self.queue.append(ActionItem(ActionItemType.APP_UPDATE, app.name, app.update, False))
def uninstall_app(self, app):
self.queue.append(ActionItem(ActionItemType.APP_UNINSTALL, app.name, app.uninstall, False))
def process(self):
for item in self.queue:
self.index += 1
item.run()
class ActionQueue:
def __init__(self):
self.actions = {}
self.queue = deque()
self.lock = Lock()
self.is_running = False
def get_actions(self):
# Return copy of actions, so they can be read and traversed without state changes
with self.lock:
return self.actions.copy()
def enqueue_action(self, app_name, action):
# Enqueue action
with self.lock:
if app_name in self.actions:
# If the app already has a pending action, reject any other actions
return
# Create empty queue to be populated with actions just before execution
self.actions[app_name] = ActionAppQueue(action)
self.queue.append(app_name)
@locked(LOCK_FILE)
def process_actions(self):
# Main method for deferred queue processing called by WSGI close handler
with self.lock:
# If the queue is being processesd by another thread, allow this thread to be terminated
if self.is_running:
return
while True:
with self.lock:
# Try to get an item from queue
app_name = None
if self.queue:
app_name = self.queue.popleft()
# If there are no more queued items, unset the processing flag and allow the thread to be terminated
if not app_name:
self.is_running = False
return
# If there is an item to be processed, set processing flags and exit the lock
self.is_running = True
app_queue = self.actions[app_name]
try:
# Call the method passed in app_queue.action to populate the queue of the actual actions to be taken in relation to the current local repository state
app_queue.action(app_name, app_queue)
# Process the freshly populated queue of actions related to the particular app
app_queue.process()
# If the actions finished without errors, restore nominal state by deleting the item from action list
self.clear_action(app_name)
except BaseException as e:
# If the action failed, store the exception and leave it in the list for manual clearance
with self.lock:
app_queue.exception = e
def clear_action(self, app_name):
# Restore nominal state by deleting the item from action list
with self.lock:
try:
del self.actions[app_name]
except KeyError:
pass