129 lines
4.8 KiB
Python
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
|