Add 'Tasks' for controlled execution of multiple external tools
authorFelix Domke <tmbinc@elitedvb.net>
Mon, 14 Apr 2008 23:31:18 +0000 (23:31 +0000)
committerFelix Domke <tmbinc@elitedvb.net>
Mon, 14 Apr 2008 23:31:18 +0000 (23:31 +0000)
lib/python/Components/Makefile.am
lib/python/Components/Task.py [new file with mode: 0644]

index 5236909..f344046 100644 (file)
@@ -17,4 +17,4 @@ install_PYTHON = \
        FIFOList.py ServiceEventTracker.py Input.py TimerSanityCheck.py FileList.py \
        MultiContent.py MediaPlayer.py TunerInfo.py VideoWindow.py ChoiceList.py \
        Element.py Playlist.py ParentalControl.py ParentalControlList.py \
        FIFOList.py ServiceEventTracker.py Input.py TimerSanityCheck.py FileList.py \
        MultiContent.py MediaPlayer.py TunerInfo.py VideoWindow.py ChoiceList.py \
        Element.py Playlist.py ParentalControl.py ParentalControlList.py \
-       Ipkg.py SelectionList.py Scanner.py SystemInfo.py
+       Ipkg.py SelectionList.py Scanner.py SystemInfo.py Task.py
diff --git a/lib/python/Components/Task.py b/lib/python/Components/Task.py
new file mode 100644 (file)
index 0000000..1db19f7
--- /dev/null
@@ -0,0 +1,227 @@
+# A Job consists of many "Tasks".
+# A task is the run of an external tool, with proper methods for failure handling
+
+from Tools.CList import CList
+
+class Job:
+       NOT_STARTED, IN_PROGRESS, FINISHED, FAILED = range(4)
+       def __init__(self, name):
+               self.tasks = [ ]
+               self.current_task = 0
+               self.callback = None
+               self.name = name
+               self.finished = False
+
+               self.state_changed = CList()
+
+               self.status = self.NOT_STARTED
+
+       # description is a dict
+       def fromDescription(self, description):
+               pass
+
+       def createDescription(self):
+               return None
+
+       def addTask(self, task):
+               self.tasks.append(task)
+
+       def start(self, callback):
+               assert self.callback is None
+               self.callback = callback
+               self.status = self.IN_PROGRESS
+               self.state_changed()
+               self.runNext()
+
+       def runNext(self):
+               if self.current_task == len(self.tasks):
+                       self.callback(self, [])
+                       self.status = self.FINISHED
+                       self.state_changed()
+               else:
+                       self.tasks[self.current_task].run(self.taskCallback)
+                       self.state_changed()
+
+       def taskCallback(self, res):
+               if len(res):
+                       print ">>> Error:", res
+                       self.status = self.FAILED
+                       self.state_changed()
+                       self.callback(self, res)
+               else:
+                       self.current_task += 1
+                       self.runNext()
+
+class Task:
+       def __init__(self, name):
+               self.name = name
+               self.workspace = "/tmp"
+               self.immediate_preconditions = [ ]
+               self.global_preconditions = [ ]
+               self.postconditions = [ ]
+               self.returncode = None
+               self.initial_input = None
+
+               self.cmd = None
+               self.args = [ ]
+
+       def setCommandline(self, cmd, args):
+               self.cmd = cmd
+               self.args = args
+
+       def setTool(self, tool):
+               self.cmd = tool
+               self.args = [tool]
+               self.global_preconditions.append(ToolExistsPrecondition())
+               self.postconditions.append(ReturncodePostcondition())
+
+       def checkPreconditions(self, immediate = False):
+               not_met = [ ]
+               if immediate:
+                       preconditions = self.immediate_preconditions
+               else:
+                       preconditions = self.global_preconditions
+               for precondition in preconditions:
+                       if not precondition.check(self):
+                               not_met.append(precondition)
+               return not_met
+
+       def run(self, callback):
+               failed_preconditions = self.checkPreconditions(True)
+               if len(failed_preconditions):
+                       errback(failed_preconditions)
+                       return
+
+               self.callback = callback
+               from enigma import eConsoleAppContainer
+               self.container = eConsoleAppContainer()
+               self.container.appClosed.get().append(self.processFinished)
+               self.container.dataAvail.get().append(self.processOutput)
+
+               assert self.cmd is not None
+               assert len(self.args) >= 1
+
+               print "execute:", self.container.execute(self.cmd, self.args), self.cmd, self.args
+               if self.initial_input:
+                       self.writeInput(self.initial_input)
+
+       def prepare(self):
+               pass
+
+       def cleanup(self, failed):
+               pass
+
+       def processOutput(self, data):
+               pass
+
+       def processFinished(self, returncode):
+               self.returncode = returncode
+               self.finish()
+
+       def finish(self):
+               self.afterRun()
+               not_met = [ ]
+               for postcondition in self.postconditions:
+                       if not postcondition.check(self):
+                               not_met.append(postcondition)
+
+               self.callback(not_met)
+
+       def afterRun(self):
+               pass
+
+       def writeInput(self, input):
+               self.container.write(input)
+
+class JobManager:
+       def __init__(self):
+               self.active_jobs = [ ]
+               self.failed_jobs = [ ]
+               self.job_classes = [ ]
+               self.active_job = None
+
+       def AddJob(self, job):
+               self.active_jobs.append(job)
+               self.kick()
+
+       def kick(self):
+               if self.active_job is None:
+                       if len(self.active_jobs):
+                               self.active_job = self.active_jobs.pop(0)
+                               self.active_job.start(self.jobDone)
+
+       def jobDone(self, job, problems):
+               print "job", job, "completed with", problems
+               if problems:
+                       self.failed_jobs.append(self.active_job)
+
+               self.active_job = None
+               self.kick()
+
+# some examples:
+#class PartitionExistsPostcondition:
+#      def __init__(self, device):
+#              self.device = device
+#
+#      def check(self, task):
+#              import os
+#              return os.access(self.device + "part1", os.F_OK)
+#
+#class CreatePartitionTask(Task):
+#      def __init__(self, device):
+#              Task.__init__(self, _("Create Partition"))
+#              self.device = device
+#              self.setTool("/sbin/sfdisk")
+#              self.args += ["-f", self.device + "disc"]
+#              self.initial_input = "0,\n;\n;\n;\ny\n"
+#              self.postconditions.append(PartitionExistsPostcondition(self.device))
+#
+#class CreateFilesystemTask(Task):
+#      def __init__(self, device, partition = 1, largefile = True):
+#              Task.__init__(self, _("Create Filesystem"))
+#              self.setTool("/sbin/mkfs.ext")
+#              if largefile:
+#                      self.args += ["-T", "largefile"]
+#              self.args.append("-m0")
+#              self.args.append(device + "part%d" % partition)
+#
+#class FilesystemMountTask(Task):
+#      def __init__(self, device, partition = 1, filesystem = "ext3"):
+#              Task.__init__(self, _("Mounting Filesystem"))
+#              self.setTool("/bin/mount")
+#              if filesystem is not None:
+#                      self.args += ["-t", filesystem]
+#              self.args.append(device + "part%d" % partition)
+#
+#class DiskspacePrecondition:
+#      def __init__(self, diskspace_required):
+#              self.diskspace_required = diskspace_required
+#
+#      def check(self, task):
+#              return getFreeDiskspace(task.workspace) >= self.diskspace_required
+#
+#class ToolExistsPrecondition:
+#      def check(self, task):
+#              import os
+#              return os.access(task.cmd, os.X_OK)
+#
+#class ReturncodePostcondition:
+#      def check(self, task):
+#              return task.returncode == 0
+#
+#class HDDInitJob(Job):
+#      def __init__(self, device):
+#              Job.__init__(self, _("Initialize Harddisk"))
+#              self.device = device
+#              self.fromDescription(self.createDescription())
+#
+#      def fromDescription(self, description):
+#              self.device = description["device"]
+#              self.addTask(CreatePartitionTask(self.device))
+#              self.addTask(CreateFilesystemTask(self.device))
+#              self.addTask(FilesystemMountTask(self.device))
+#
+#      def createDescription(self):
+#              return {"device": self.device}
+
+job_manager = JobManager()